diff options
Diffstat (limited to 'app/src/main/java')
16 files changed, 877 insertions, 691 deletions
diff --git a/app/src/main/java/dev/msfjarvis/aps/ui/onboarding/fragments/RepoLocationFragment.kt b/app/src/main/java/dev/msfjarvis/aps/ui/onboarding/fragments/RepoLocationFragment.kt index 08090d1b..37c23cea 100644 --- a/app/src/main/java/dev/msfjarvis/aps/ui/onboarding/fragments/RepoLocationFragment.kt +++ b/app/src/main/java/dev/msfjarvis/aps/ui/onboarding/fragments/RepoLocationFragment.kt @@ -19,9 +19,9 @@ import com.github.michaelbull.result.onFailure import com.github.michaelbull.result.runCatching import com.google.android.material.dialog.MaterialAlertDialogBuilder import dev.msfjarvis.aps.R -import dev.msfjarvis.aps.ui.settings.UserPreference import dev.msfjarvis.aps.databinding.FragmentRepoLocationBinding import dev.msfjarvis.aps.data.repo.PasswordRepository +import dev.msfjarvis.aps.ui.settings.DirectorySelectionActivity import dev.msfjarvis.aps.util.settings.PasswordSortOrder import dev.msfjarvis.aps.util.settings.PreferenceKeys import dev.msfjarvis.aps.util.extensions.finish @@ -31,11 +31,13 @@ import dev.msfjarvis.aps.util.extensions.listFilesRecursively import dev.msfjarvis.aps.util.extensions.performTransactionWithBackStack import dev.msfjarvis.aps.util.extensions.sharedPrefs import dev.msfjarvis.aps.util.extensions.viewBinding +import android.content.Intent import java.io.File class RepoLocationFragment : Fragment(R.layout.fragment_repo_location) { private val settings by lazy(LazyThreadSafetyMode.NONE) { requireActivity().applicationContext.sharedPrefs } + private val directorySelectIntent by lazy(LazyThreadSafetyMode.NONE) { Intent(requireContext(), DirectorySelectionActivity::class.java) } private val binding by viewBinding(FragmentRepoLocationBinding::bind) private val sortOrder: PasswordSortOrder get() = PasswordSortOrder.getSortOrder(settings) @@ -57,7 +59,7 @@ class RepoLocationFragment : Fragment(R.layout.fragment_repo_location) { } private val externalDirPermGrantedAction = createPermGrantedAction { - externalDirectorySelectAction.launch(UserPreference.createDirectorySelectionIntent(requireContext())) + externalDirectorySelectAction.launch(directorySelectIntent) } private val repositoryUsePermGrantedAction = createPermGrantedAction { @@ -65,7 +67,7 @@ class RepoLocationFragment : Fragment(R.layout.fragment_repo_location) { } private val repositoryChangePermGrantedAction = createPermGrantedAction { - repositoryInitAction.launch(UserPreference.createDirectorySelectionIntent(requireContext())) + repositoryInitAction.launch(directorySelectIntent) } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { @@ -102,7 +104,7 @@ class RepoLocationFragment : Fragment(R.layout.fragment_repo_location) { } else { // Unlikely we have storage permissions without user ever selecting a directory, // but let's not assume. - externalDirectorySelectAction.launch(UserPreference.createDirectorySelectionIntent(requireContext())) + externalDirectorySelectAction.launch(directorySelectIntent) } } else { MaterialAlertDialogBuilder(requireActivity()) @@ -119,7 +121,7 @@ class RepoLocationFragment : Fragment(R.layout.fragment_repo_location) { if (!isPermissionGranted(Manifest.permission.WRITE_EXTERNAL_STORAGE)) { repositoryChangePermGrantedAction.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE) } else { - repositoryInitAction.launch(UserPreference.createDirectorySelectionIntent(requireContext())) + repositoryInitAction.launch(directorySelectIntent) } } .show() diff --git a/app/src/main/java/dev/msfjarvis/aps/ui/onboarding/fragments/WelcomeFragment.kt b/app/src/main/java/dev/msfjarvis/aps/ui/onboarding/fragments/WelcomeFragment.kt index 696aba17..34459208 100644 --- a/app/src/main/java/dev/msfjarvis/aps/ui/onboarding/fragments/WelcomeFragment.kt +++ b/app/src/main/java/dev/msfjarvis/aps/ui/onboarding/fragments/WelcomeFragment.kt @@ -11,8 +11,8 @@ import android.view.View import androidx.annotation.Keep import androidx.fragment.app.Fragment import dev.msfjarvis.aps.R -import dev.msfjarvis.aps.ui.settings.UserPreference import dev.msfjarvis.aps.databinding.FragmentWelcomeBinding +import dev.msfjarvis.aps.ui.settings.SettingsActivity import dev.msfjarvis.aps.util.extensions.performTransactionWithBackStack import dev.msfjarvis.aps.util.extensions.viewBinding @@ -25,6 +25,6 @@ class WelcomeFragment : Fragment(R.layout.fragment_welcome) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) binding.letsGo.setOnClickListener { parentFragmentManager.performTransactionWithBackStack(CloneFragment.newInstance()) } - binding.settingsButton.setOnClickListener { startActivity(Intent(requireContext(), UserPreference::class.java)) } + binding.settingsButton.setOnClickListener { startActivity(Intent(requireContext(), SettingsActivity::class.java)) } } } diff --git a/app/src/main/java/dev/msfjarvis/aps/ui/passwords/PasswordStore.kt b/app/src/main/java/dev/msfjarvis/aps/ui/passwords/PasswordStore.kt index 49620ac8..bdd8c9bb 100644 --- a/app/src/main/java/dev/msfjarvis/aps/ui/passwords/PasswordStore.kt +++ b/app/src/main/java/dev/msfjarvis/aps/ui/passwords/PasswordStore.kt @@ -42,7 +42,6 @@ import dev.msfjarvis.aps.ui.main.LaunchActivity import dev.msfjarvis.aps.R import dev.msfjarvis.aps.util.viewmodel.SearchableRepositoryViewModel import dev.msfjarvis.aps.ui.folderselect.SelectFolderActivity -import dev.msfjarvis.aps.ui.settings.UserPreference import dev.msfjarvis.aps.util.autofill.AutofillMatcher import dev.msfjarvis.aps.ui.crypto.BasePgpActivity.Companion.getLongName import dev.msfjarvis.aps.ui.crypto.DecryptActivity @@ -55,6 +54,8 @@ import dev.msfjarvis.aps.ui.dialogs.FolderCreationDialogFragment import dev.msfjarvis.aps.ui.onboarding.activity.OnboardingActivity import dev.msfjarvis.aps.data.password.PasswordItem import dev.msfjarvis.aps.data.repo.PasswordRepository +import dev.msfjarvis.aps.ui.settings.DirectorySelectionActivity +import dev.msfjarvis.aps.ui.settings.SettingsActivity import dev.msfjarvis.aps.util.settings.PreferenceKeys import dev.msfjarvis.aps.util.extensions.base64 import dev.msfjarvis.aps.util.extensions.commitChange @@ -300,7 +301,7 @@ class PasswordStore : BaseGitActivity() { when (id) { R.id.user_pref -> { runCatching { - startActivity(Intent(this, UserPreference::class.java)) + startActivity(Intent(this, SettingsActivity::class.java)) }.onFailure { e -> e.printStackTrace() } @@ -377,7 +378,7 @@ class PasswordStore : BaseGitActivity() { private fun checkLocalRepository() { val repo = PasswordRepository.initialize() if (repo == null) { - directorySelectAction.launch(UserPreference.createDirectorySelectionIntent(this)) + directorySelectAction.launch(Intent(this, DirectorySelectionActivity::class.java)) } else { checkLocalRepository(PasswordRepository.getRepositoryDirectory()) } diff --git a/app/src/main/java/dev/msfjarvis/aps/ui/settings/AutofillSettings.kt b/app/src/main/java/dev/msfjarvis/aps/ui/settings/AutofillSettings.kt new file mode 100644 index 00000000..74b407fd --- /dev/null +++ b/app/src/main/java/dev/msfjarvis/aps/ui/settings/AutofillSettings.kt @@ -0,0 +1,126 @@ +/* + * Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved. + * SPDX-License-Identifier: GPL-3.0-only + */ + +package dev.msfjarvis.aps.ui.settings + +import de.Maxr1998.modernpreferences.PreferenceScreen +import de.Maxr1998.modernpreferences.helpers.editText +import de.Maxr1998.modernpreferences.helpers.onClick +import de.Maxr1998.modernpreferences.helpers.singleChoice +import de.Maxr1998.modernpreferences.helpers.switch +import de.Maxr1998.modernpreferences.preferences.SwitchPreference +import de.Maxr1998.modernpreferences.preferences.choice.SelectionItem +import dev.msfjarvis.aps.BuildConfig +import dev.msfjarvis.aps.R +import dev.msfjarvis.aps.util.autofill.DirectoryStructure +import dev.msfjarvis.aps.util.extensions.autofillManager +import dev.msfjarvis.aps.util.settings.PreferenceKeys +import android.annotation.SuppressLint +import android.content.Intent +import android.net.Uri +import android.os.Build +import android.provider.Settings +import androidx.annotation.RequiresApi +import androidx.appcompat.widget.AppCompatTextView +import androidx.fragment.app.FragmentActivity +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleEventObserver +import com.github.androidpasswordstore.autofillparser.BrowserAutofillSupportLevel +import com.github.androidpasswordstore.autofillparser.getInstalledBrowsersWithAutofillSupportLevel +import com.google.android.material.dialog.MaterialAlertDialogBuilder + +class AutofillSettings(private val activity: FragmentActivity) : SettingsProvider { + + private val isAutofillServiceEnabled: Boolean + get() { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return false + return activity.autofillManager?.hasEnabledAutofillServices() == true + } + + @RequiresApi(Build.VERSION_CODES.O) + private fun showAutofillDialog(pref: SwitchPreference) { + val observer = LifecycleEventObserver { _, event -> + when (event) { + Lifecycle.Event.ON_RESUME -> { + pref.checked = isAutofillServiceEnabled + } + else -> { + } + } + } + MaterialAlertDialogBuilder(activity).run { + setTitle(R.string.pref_autofill_enable_title) + @SuppressLint("InflateParams") + val layout = + activity.layoutInflater.inflate(R.layout.oreo_autofill_instructions, null) + val supportedBrowsersTextView = + layout.findViewById<AppCompatTextView>(R.id.supportedBrowsers) + supportedBrowsersTextView.text = + getInstalledBrowsersWithAutofillSupportLevel(context).joinToString( + separator = "\n" + ) { + val appLabel = it.first + val supportDescription = when (it.second) { + BrowserAutofillSupportLevel.None -> activity.getString(R.string.oreo_autofill_no_support) + BrowserAutofillSupportLevel.FlakyFill -> activity.getString(R.string.oreo_autofill_flaky_fill_support) + BrowserAutofillSupportLevel.PasswordFill -> activity.getString(R.string.oreo_autofill_password_fill_support) + BrowserAutofillSupportLevel.PasswordFillAndSaveIfNoAccessibility -> activity.getString(R.string.oreo_autofill_password_fill_and_conditional_save_support) + BrowserAutofillSupportLevel.GeneralFill -> activity.getString(R.string.oreo_autofill_general_fill_support) + BrowserAutofillSupportLevel.GeneralFillAndSave -> activity.getString(R.string.oreo_autofill_general_fill_and_save_support) + } + "$appLabel: $supportDescription" + } + setView(layout) + setPositiveButton(R.string.dialog_ok) { _, _ -> + val intent = Intent(Settings.ACTION_REQUEST_SET_AUTOFILL_SERVICE).apply { + data = Uri.parse("package:${BuildConfig.APPLICATION_ID}") + } + activity.startActivity(intent) + } + setNegativeButton(R.string.dialog_cancel, null) + setOnDismissListener { pref.checked = isAutofillServiceEnabled } + activity.lifecycle.addObserver(observer) + show() + } + } + + override fun provideSettings(builder: PreferenceScreen.Builder) { + builder.apply { + switch(PreferenceKeys.AUTOFILL_ENABLE) { + titleRes = R.string.pref_autofill_enable_title + visible = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O + defaultValue = isAutofillServiceEnabled + onClick { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return@onClick true + if (isAutofillServiceEnabled) { + activity.autofillManager?.disableAutofillServices() + } else { + showAutofillDialog(this) + } + false + } + } + val values = activity.resources.getStringArray(R.array.oreo_autofill_directory_structure_values) + val titles = activity.resources.getStringArray(R.array.oreo_autofill_directory_structure_entries) + val items = values.zip(titles).map { SelectionItem(it.first, it.second, null) } + singleChoice(PreferenceKeys.OREO_AUTOFILL_DIRECTORY_STRUCTURE, items) { + initialSelection = DirectoryStructure.DEFAULT.value + dependency = PreferenceKeys.AUTOFILL_ENABLE + titleRes = R.string.oreo_autofill_preference_directory_structure + } + editText(PreferenceKeys.OREO_AUTOFILL_DEFAULT_USERNAME) { + dependency = PreferenceKeys.AUTOFILL_ENABLE + titleRes = R.string.preference_default_username_title + summaryProvider = { activity.getString(R.string.preference_default_username_summary) } + } + editText(PreferenceKeys.OREO_AUTOFILL_CUSTOM_PUBLIC_SUFFIXES) { + dependency = PreferenceKeys.AUTOFILL_ENABLE + titleRes = R.string.preference_custom_public_suffixes_title + summaryProvider = { activity.getString(R.string.preference_custom_public_suffixes_summary) } + textInputHintRes = R.string.preference_custom_public_suffixes_hint + } + } + } +} diff --git a/app/src/main/java/dev/msfjarvis/aps/ui/settings/DirectorySelectionActivity.kt b/app/src/main/java/dev/msfjarvis/aps/ui/settings/DirectorySelectionActivity.kt new file mode 100644 index 00000000..41cd254b --- /dev/null +++ b/app/src/main/java/dev/msfjarvis/aps/ui/settings/DirectorySelectionActivity.kt @@ -0,0 +1,56 @@ +/* + * Copyright © 2014-2021 The Android Password Store Authors. All Rights Reserved. + * SPDX-License-Identifier: GPL-3.0-only + */ + +package dev.msfjarvis.aps.ui.settings + +import dev.msfjarvis.aps.R +import dev.msfjarvis.aps.util.extensions.sharedPrefs +import dev.msfjarvis.aps.util.settings.PreferenceKeys +import android.net.Uri +import androidx.appcompat.app.AppCompatActivity +import android.os.Bundle +import android.os.Environment +import android.provider.DocumentsContract +import androidx.activity.result.contract.ActivityResultContracts +import androidx.core.content.edit +import com.github.ajalt.timberkt.d +import com.google.android.material.dialog.MaterialAlertDialogBuilder + +class DirectorySelectionActivity : AppCompatActivity() { + + @Suppress("DEPRECATION") + private val directorySelectAction = registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) { uri: Uri? -> + if (uri == null) return@registerForActivityResult + + d { "Selected repository URI is $uri" } + // TODO: This is fragile. Workaround until PasswordItem is backed by DocumentFile + val docId = DocumentsContract.getTreeDocumentId(uri) + val split = docId.split(":".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() + val path = if (split.size > 1) split[1] else split[0] + val repoPath = "${Environment.getExternalStorageDirectory()}/$path" + val prefs = sharedPrefs + + d { "Selected repository path is $repoPath" } + + if (Environment.getExternalStorageDirectory().path == repoPath) { + MaterialAlertDialogBuilder(this) + .setTitle(resources.getString(R.string.sdcard_root_warning_title)) + .setMessage(resources.getString(R.string.sdcard_root_warning_message)) + .setPositiveButton(resources.getString(R.string.sdcard_root_warning_remove_everything)) { _, _ -> + prefs.edit { putString(PreferenceKeys.GIT_EXTERNAL_REPO, uri.path) } + } + .setNegativeButton(R.string.dialog_cancel, null) + .show() + } + prefs.edit { putString(PreferenceKeys.GIT_EXTERNAL_REPO, repoPath) } + setResult(RESULT_OK) + finish() + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + directorySelectAction.launch(null) + } +} diff --git a/app/src/main/java/dev/msfjarvis/aps/ui/settings/GeneralSettings.kt b/app/src/main/java/dev/msfjarvis/aps/ui/settings/GeneralSettings.kt new file mode 100644 index 00000000..91df87d0 --- /dev/null +++ b/app/src/main/java/dev/msfjarvis/aps/ui/settings/GeneralSettings.kt @@ -0,0 +1,104 @@ +/* + * Copyright © 2014-2021 The Android Password Store Authors. All Rights Reserved. + * SPDX-License-Identifier: GPL-3.0-only + */ + +package dev.msfjarvis.aps.ui.settings + +import de.Maxr1998.modernpreferences.PreferenceScreen +import de.Maxr1998.modernpreferences.helpers.checkBox +import de.Maxr1998.modernpreferences.helpers.onClick +import de.Maxr1998.modernpreferences.helpers.singleChoice +import de.Maxr1998.modernpreferences.preferences.choice.SelectionItem +import dev.msfjarvis.aps.R +import dev.msfjarvis.aps.util.auth.BiometricAuthenticator +import dev.msfjarvis.aps.util.extensions.sharedPrefs +import dev.msfjarvis.aps.util.settings.PreferenceKeys +import android.content.pm.ShortcutManager +import android.os.Build +import androidx.core.content.edit +import androidx.core.content.getSystemService +import androidx.fragment.app.FragmentActivity + +class GeneralSettings(private val activity: FragmentActivity) : SettingsProvider { + + override fun provideSettings(builder: PreferenceScreen.Builder) { + builder.apply { + val themeValues = activity.resources.getStringArray(R.array.app_theme_values) + val themeOptions = activity.resources.getStringArray(R.array.app_theme_options) + val themeItems = themeValues.zip(themeOptions).map { SelectionItem(it.first, it.second, null) } + singleChoice(PreferenceKeys.APP_THEME, themeItems) { + initialSelection = activity.resources.getString(R.string.app_theme_def) + titleRes = R.string.pref_app_theme_title + } + + val sortValues = activity.resources.getStringArray(R.array.sort_order_values) + val sortOptions = activity.resources.getStringArray(R.array.sort_order_entries) + val sortItems = sortValues.zip(sortOptions).map { SelectionItem(it.first, it.second, null) } + singleChoice(PreferenceKeys.SORT_ORDER, sortItems) { + initialSelection = sortValues[0] + titleRes = R.string.pref_sort_order_title + } + + checkBox(PreferenceKeys.FILTER_RECURSIVELY) { + titleRes = R.string.pref_recursive_filter_title + summaryRes = R.string.pref_recursive_filter_summary + defaultValue = true + } + + checkBox(PreferenceKeys.SEARCH_ON_START) { + titleRes = R.string.pref_search_on_start_title + summaryRes = R.string.pref_search_on_start_summary + defaultValue = false + } + + checkBox(PreferenceKeys.SHOW_HIDDEN_CONTENTS) { + titleRes = R.string.pref_show_hidden_title + summaryRes = R.string.pref_show_hidden_summary + defaultValue = false + } + + checkBox(PreferenceKeys.BIOMETRIC_AUTH) { + titleRes = R.string.pref_biometric_auth_title + defaultValue = false + }.apply { + val canAuthenticate = BiometricAuthenticator.canAuthenticate(activity) + if (!canAuthenticate) { + enabled = false + checked = false + summaryRes = R.string.pref_biometric_auth_summary_error + } else { + summaryRes = R.string.pref_biometric_auth_summary + onClick { + enabled = false + val isChecked = checked + activity.sharedPrefs.edit { + BiometricAuthenticator.authenticate(activity) { result -> + when (result) { + is BiometricAuthenticator.Result.Success -> { + // Apply the changes + putBoolean(PreferenceKeys.BIOMETRIC_AUTH, checked) + enabled = true + } + else -> { + // If any error occurs, revert back to the previous state. This + // catch-all clause includes the cancellation case. + putBoolean(PreferenceKeys.BIOMETRIC_AUTH, !checked) + checked = !isChecked + enabled = true + } + } + } + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) { + activity.getSystemService<ShortcutManager>()?.apply { + removeDynamicShortcuts(dynamicShortcuts.map { it.id }.toMutableList()) + } + } + false + } + } + } + } + } +} diff --git a/app/src/main/java/dev/msfjarvis/aps/ui/settings/MiscSettings.kt b/app/src/main/java/dev/msfjarvis/aps/ui/settings/MiscSettings.kt new file mode 100644 index 00000000..08da760c --- /dev/null +++ b/app/src/main/java/dev/msfjarvis/aps/ui/settings/MiscSettings.kt @@ -0,0 +1,76 @@ +/* + * Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved. + * SPDX-License-Identifier: GPL-3.0-only + */ + +package dev.msfjarvis.aps.ui.settings + +import de.Maxr1998.modernpreferences.PreferenceScreen +import de.Maxr1998.modernpreferences.helpers.checkBox +import de.Maxr1998.modernpreferences.helpers.onClick +import de.Maxr1998.modernpreferences.helpers.pref +import dev.msfjarvis.aps.BuildConfig +import dev.msfjarvis.aps.R +import dev.msfjarvis.aps.util.services.PasswordExportService +import dev.msfjarvis.aps.util.settings.PreferenceKeys +import android.content.Context +import android.content.Intent +import android.net.Uri +import android.os.Build +import androidx.activity.result.contract.ActivityResultContracts +import androidx.documentfile.provider.DocumentFile +import androidx.fragment.app.FragmentActivity + +class MiscSettings(activity: FragmentActivity) : SettingsProvider { + + private val storeExportAction = activity.registerForActivityResult(object : ActivityResultContracts.OpenDocumentTree() { + override fun createIntent(context: Context, input: Uri?): Intent { + return super.createIntent(context, input).apply { + flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or + Intent.FLAG_GRANT_WRITE_URI_PERMISSION or + Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION or + Intent.FLAG_GRANT_PREFIX_URI_PERMISSION + } + } + }) { uri: Uri? -> + if (uri == null) return@registerForActivityResult + val targetDirectory = DocumentFile.fromTreeUri(activity.applicationContext, uri) + + if (targetDirectory != null) { + val service = Intent(activity.applicationContext, PasswordExportService::class.java).apply { + action = PasswordExportService.ACTION_EXPORT_PASSWORD + putExtra("uri", uri) + } + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + activity.startForegroundService(service) + } else { + activity.startService(service) + } + } + } + + override fun provideSettings(builder: PreferenceScreen.Builder) { + builder.apply { + pref(PreferenceKeys.EXPORT_PASSWORDS) { + titleRes = R.string.prefs_export_passwords_title + summaryRes = R.string.prefs_export_passwords_summary + onClick { + storeExportAction.launch(null) + true + } + } + checkBox(PreferenceKeys.CLEAR_CLIPBOARD_20X) { + defaultValue = false + titleRes = R.string.pref_clear_clipboard_title + summaryRes = R.string.pref_clear_clipboard_summary + } + checkBox(PreferenceKeys.ENABLE_DEBUG_LOGGING) { + defaultValue = false + titleRes = R.string.pref_debug_logging_title + summaryRes = R.string.pref_debug_logging_summary + visible = !BuildConfig.DEBUG + } + } + } +} diff --git a/app/src/main/java/dev/msfjarvis/aps/ui/settings/PasswordSettings.kt b/app/src/main/java/dev/msfjarvis/aps/ui/settings/PasswordSettings.kt new file mode 100644 index 00000000..9b7eb01c --- /dev/null +++ b/app/src/main/java/dev/msfjarvis/aps/ui/settings/PasswordSettings.kt @@ -0,0 +1,119 @@ +/* + * Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved. + * SPDX-License-Identifier: GPL-3.0-only + */ + +package dev.msfjarvis.aps.ui.settings + +import de.Maxr1998.modernpreferences.Preference +import de.Maxr1998.modernpreferences.PreferenceScreen +import de.Maxr1998.modernpreferences.helpers.categoryHeader +import de.Maxr1998.modernpreferences.helpers.checkBox +import de.Maxr1998.modernpreferences.helpers.editText +import de.Maxr1998.modernpreferences.helpers.onCheckedChange +import de.Maxr1998.modernpreferences.helpers.onClick +import de.Maxr1998.modernpreferences.helpers.onSelectionChange +import de.Maxr1998.modernpreferences.helpers.singleChoice +import de.Maxr1998.modernpreferences.preferences.CheckBoxPreference +import de.Maxr1998.modernpreferences.preferences.choice.SelectionItem +import dev.msfjarvis.aps.R +import dev.msfjarvis.aps.util.extensions.getString +import dev.msfjarvis.aps.util.extensions.sharedPrefs +import dev.msfjarvis.aps.util.pwgenxkpwd.XkpwdDictionary +import dev.msfjarvis.aps.util.settings.PreferenceKeys +import android.text.InputType +import android.widget.Toast +import androidx.activity.result.contract.ActivityResultContracts +import androidx.core.content.edit +import androidx.fragment.app.FragmentActivity +import java.io.File + +class PasswordSettings(private val activity: FragmentActivity) : SettingsProvider { + + private val sharedPrefs by lazy(LazyThreadSafetyMode.NONE) { activity.sharedPrefs } + private val storeCustomXkpwdDictionaryAction = activity.registerForActivityResult(ActivityResultContracts.OpenDocument()) { uri -> + if (uri == null) return@registerForActivityResult + + Toast.makeText( + activity, + activity.resources.getString(R.string.xkpwgen_custom_dict_imported, uri.path), + Toast.LENGTH_SHORT + ).show() + + sharedPrefs.edit { putString(PreferenceKeys.PREF_KEY_CUSTOM_DICT, uri.toString()) } + + val inputStream = activity.contentResolver.openInputStream(uri) + val customDictFile = File(activity.filesDir.toString(), XkpwdDictionary.XKPWD_CUSTOM_DICT_FILE).outputStream() + inputStream?.copyTo(customDictFile, 1024) + inputStream?.close() + customDictFile.close() + } + + override fun provideSettings(builder: PreferenceScreen.Builder) { + builder.apply { + val customDictPref = CheckBoxPreference(PreferenceKeys.PREF_KEY_IS_CUSTOM_DICT).apply { + titleRes = R.string.pref_xkpwgen_custom_wordlist_enabled_title + summaryRes = R.string.pref_xkpwgen_custom_dict_summary_off + summaryOnRes = R.string.pref_xkpwgen_custom_dict_summary_on + visible = sharedPrefs.getString(PreferenceKeys.PREF_KEY_PWGEN_TYPE) == "xkpasswd" + onCheckedChange { + requestRebind() + true + } + } + val customDictPathPref = Preference(PreferenceKeys.PREF_KEY_CUSTOM_DICT).apply { + dependency = PreferenceKeys.PREF_KEY_IS_CUSTOM_DICT + titleRes = R.string.pref_xkpwgen_custom_dict_picker_title + summary = sharedPrefs.getString(PreferenceKeys.PREF_KEY_CUSTOM_DICT) + ?: activity.resources.getString(R.string.pref_xkpwgen_custom_dict_picker_summary) + visible = sharedPrefs.getString(PreferenceKeys.PREF_KEY_PWGEN_TYPE) == "xkpasswd" + onClick { + storeCustomXkpwdDictionaryAction.launch(arrayOf("*/*")) + true + } + } + val values = activity.resources.getStringArray(R.array.pwgen_provider_values) + val labels = activity.resources.getStringArray(R.array.pwgen_provider_labels) + val items = values.zip(labels).map { SelectionItem(it.first, it.second, null) } + singleChoice( + PreferenceKeys.PREF_KEY_PWGEN_TYPE, + items, + ) { + initialSelection = "classic" + titleRes = R.string.pref_password_generator_type_title + onSelectionChange { selection -> + val xkpasswdEnabled = selection == "xkpasswd" + customDictPathPref.visible = xkpasswdEnabled + customDictPref.visible = xkpasswdEnabled + customDictPref.requestRebind() + customDictPathPref.requestRebind() + true + } + } + // We initialize them early and add them manually to be able to manually force a rebind + // when the password generator type is changed. + addPreferenceItem(customDictPref) + addPreferenceItem(customDictPathPref) + editText(PreferenceKeys.GENERAL_SHOW_TIME) { + titleRes = R.string.pref_clipboard_timeout_title + summaryProvider = { activity.getString(R.string.pref_clipboard_timeout_summary) } + textInputType = InputType.TYPE_CLASS_NUMBER + } + checkBox(PreferenceKeys.SHOW_PASSWORD) { + titleRes = R.string.show_password_pref_title + summaryRes = R.string.show_password_pref_summary + defaultValue = true + } + checkBox(PreferenceKeys.SHOW_EXTRA_CONTENT) { + titleRes = R.string.show_extra_content_pref_title + summaryRes = R.string.show_extra_content_pref_summary + defaultValue = true + } + checkBox(PreferenceKeys.COPY_ON_DECRYPT) { + titleRes = R.string.pref_copy_title + summaryRes = R.string.pref_copy_summary + defaultValue = false + } + } + } +} diff --git a/app/src/main/java/dev/msfjarvis/aps/ui/settings/RepositorySettings.kt b/app/src/main/java/dev/msfjarvis/aps/ui/settings/RepositorySettings.kt new file mode 100644 index 00000000..32d04c9b --- /dev/null +++ b/app/src/main/java/dev/msfjarvis/aps/ui/settings/RepositorySettings.kt @@ -0,0 +1,198 @@ +/* + * Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved. + * SPDX-License-Identifier: GPL-3.0-only + */ + +package dev.msfjarvis.aps.ui.settings + +import de.Maxr1998.modernpreferences.Preference +import de.Maxr1998.modernpreferences.PreferenceScreen +import de.Maxr1998.modernpreferences.helpers.checkBox +import de.Maxr1998.modernpreferences.helpers.onCheckedChange +import de.Maxr1998.modernpreferences.helpers.onClick +import de.Maxr1998.modernpreferences.helpers.pref +import dev.msfjarvis.aps.R +import dev.msfjarvis.aps.data.repo.PasswordRepository +import dev.msfjarvis.aps.ui.git.config.GitConfigActivity +import dev.msfjarvis.aps.ui.git.config.GitServerConfigActivity +import dev.msfjarvis.aps.ui.proxy.ProxySelectorActivity +import dev.msfjarvis.aps.ui.sshkeygen.ShowSshKeyFragment +import dev.msfjarvis.aps.ui.sshkeygen.SshKeyGenActivity +import dev.msfjarvis.aps.ui.sshkeygen.SshKeyImportActivity +import dev.msfjarvis.aps.util.extensions.getEncryptedGitPrefs +import dev.msfjarvis.aps.util.extensions.getString +import dev.msfjarvis.aps.util.extensions.sharedPrefs +import dev.msfjarvis.aps.util.extensions.snackbar +import dev.msfjarvis.aps.util.settings.GitSettings +import dev.msfjarvis.aps.util.settings.PreferenceKeys +import android.content.Intent +import android.content.pm.ShortcutManager +import android.os.Build +import androidx.core.content.edit +import androidx.core.content.getSystemService +import androidx.fragment.app.FragmentActivity +import com.github.michaelbull.result.onFailure +import com.github.michaelbull.result.runCatching +import com.google.android.material.dialog.MaterialAlertDialogBuilder + +class RepositorySettings(private val activity: FragmentActivity) : SettingsProvider { + + private val encryptedPreferences by lazy(LazyThreadSafetyMode.NONE) { activity.getEncryptedGitPrefs() } + + private fun <T : FragmentActivity> launchActivity(clazz: Class<T>) { + activity.startActivity(Intent(activity, clazz)) + } + + private fun selectExternalGitRepository() { + MaterialAlertDialogBuilder(activity) + .setTitle(activity.resources.getString(R.string.external_repository_dialog_title)) + .setMessage(activity.resources.getString(R.string.external_repository_dialog_text)) + .setPositiveButton(R.string.dialog_ok) { _, _ -> + launchActivity(DirectorySelectionActivity::class.java) + } + .setNegativeButton(R.string.dialog_cancel, null) + .show() + } + + override fun provideSettings(builder: PreferenceScreen.Builder) { + builder.apply { + pref(PreferenceKeys.GIT_SERVER_INFO) { + titleRes = R.string.pref_edit_git_server_settings + visible = PasswordRepository.isGitRepo() + onClick { + launchActivity(GitServerConfigActivity::class.java) + true + } + } + pref(PreferenceKeys.PROXY_SETTINGS) { + titleRes = R.string.pref_edit_proxy_settings + visible = GitSettings.url?.startsWith("https") == true && PasswordRepository.isGitRepo() + onClick { + launchActivity(ProxySelectorActivity::class.java) + true + } + } + pref(PreferenceKeys.GIT_CONFIG) { + titleRes = R.string.pref_edit_git_config + visible = PasswordRepository.isGitRepo() + onClick { + launchActivity(GitConfigActivity::class.java) + true + } + } + pref(PreferenceKeys.SSH_KEY) { + titleRes = R.string.pref_import_ssh_key_title + visible = PasswordRepository.isGitRepo() + onClick { + launchActivity(SshKeyImportActivity::class.java) + true + } + } + pref(PreferenceKeys.SSH_KEYGEN) { + titleRes = R.string.pref_ssh_keygen_title + onClick { + launchActivity(SshKeyGenActivity::class.java) + true + } + } + pref(PreferenceKeys.SSH_SEE_KEY) { + titleRes = R.string.pref_ssh_see_key_title + visible = PasswordRepository.isGitRepo() + onClick { + ShowSshKeyFragment().show(activity.supportFragmentManager, "public_key") + true + } + } + pref(PreferenceKeys.CLEAR_SAVED_PASS) { + fun Preference.updatePref() { + val sshPass = encryptedPreferences.getString(PreferenceKeys.SSH_KEY_LOCAL_PASSPHRASE) + val httpsPass = encryptedPreferences.getString(PreferenceKeys.HTTPS_PASSWORD) + if (sshPass == null && httpsPass == null) { + visible = false + return + } + when { + httpsPass != null -> titleRes = R.string.clear_saved_passphrase_https + sshPass != null -> titleRes = R.string.clear_saved_passphrase_ssh + } + visible = true + requestRebind() + } + onClick { + updatePref() + true + } + updatePref() + } + pref(PreferenceKeys.SSH_OPENKEYSTORE_CLEAR_KEY_ID) { + titleRes = R.string.pref_title_openkeystore_clear_keyid + visible = activity.sharedPrefs.getString(PreferenceKeys.SSH_OPENKEYSTORE_KEYID)?.isNotEmpty() + ?: false + onClick { + activity.sharedPrefs.edit { putString(PreferenceKeys.SSH_OPENKEYSTORE_KEYID, null) } + visible = false + true + } + } + val deleteRepoPref = pref(PreferenceKeys.GIT_DELETE_REPO) { + titleRes = R.string.pref_git_delete_repo_title + summaryRes = R.string.pref_git_delete_repo_summary + visible = !activity.sharedPrefs.getBoolean(PreferenceKeys.GIT_EXTERNAL, false) + onClick { + val repoDir = PasswordRepository.getRepositoryDirectory() + MaterialAlertDialogBuilder(activity) + .setTitle(R.string.pref_dialog_delete_title) + .setMessage(activity.getString(R.string.dialog_delete_msg, repoDir)) + .setCancelable(false) + .setPositiveButton(R.string.dialog_delete) { dialogInterface, _ -> + runCatching { + PasswordRepository.getRepositoryDirectory().deleteRecursively() + PasswordRepository.closeRepository() + }.onFailure { + it.message?.let { message -> + activity.snackbar(message = message) + } + } + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) { + activity.getSystemService<ShortcutManager>()?.apply { + removeDynamicShortcuts(dynamicShortcuts.map { it.id }.toMutableList()) + } + } + activity.sharedPrefs.edit { putBoolean(PreferenceKeys.REPOSITORY_INITIALIZED, false) } + dialogInterface.cancel() + activity.finish() + } + .setNegativeButton(R.string.dialog_do_not_delete) { dialogInterface, _ -> run { dialogInterface.cancel() } } + .show() + true + } + } + checkBox(PreferenceKeys.GIT_EXTERNAL) { + titleRes = R.string.pref_external_repository_title + summaryRes = R.string.pref_external_repository_summary + onCheckedChange { checked -> + deleteRepoPref.visible = !checked + deleteRepoPref.requestRebind() + PasswordRepository.closeRepository() + activity.sharedPrefs.edit { putBoolean(PreferenceKeys.REPO_CHANGED, true) } + true + } + } + pref(PreferenceKeys.GIT_EXTERNAL_REPO) { + val externalRepo = activity.sharedPrefs.getString(PreferenceKeys.GIT_EXTERNAL_REPO) + if (externalRepo != null) { + summary = externalRepo + } else { + summaryRes = R.string.pref_select_external_repository_summary_no_repo_selected + } + titleRes = R.string.pref_select_external_repository_title + dependency = PreferenceKeys.GIT_EXTERNAL + onClick { + selectExternalGitRepository() + true + } + } + } + } +} diff --git a/app/src/main/java/dev/msfjarvis/aps/ui/settings/SettingsActivity.kt b/app/src/main/java/dev/msfjarvis/aps/ui/settings/SettingsActivity.kt new file mode 100644 index 00000000..7db80023 --- /dev/null +++ b/app/src/main/java/dev/msfjarvis/aps/ui/settings/SettingsActivity.kt @@ -0,0 +1,93 @@ +/* + * Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved. + * SPDX-License-Identifier: GPL-3.0-only + */ + +package dev.msfjarvis.aps.ui.settings + +import de.Maxr1998.modernpreferences.PreferencesAdapter +import de.Maxr1998.modernpreferences.helpers.screen +import de.Maxr1998.modernpreferences.helpers.subScreen +import dev.msfjarvis.aps.R +import dev.msfjarvis.aps.databinding.ActivityPreferenceRecyclerviewBinding +import dev.msfjarvis.aps.util.extensions.viewBinding +import android.os.Bundle +import android.view.MenuItem +import androidx.appcompat.app.AppCompatActivity + +class SettingsActivity : AppCompatActivity() { + + private val miscSettings = MiscSettings(this) + private val autofillSettings = AutofillSettings(this) + private val passwordSettings = PasswordSettings(this) + private val repositorySettings = RepositorySettings(this) + private val generalSettings = GeneralSettings(this) + + private val binding by viewBinding(ActivityPreferenceRecyclerviewBinding::inflate) + private val preferencesAdapter: PreferencesAdapter + get() = binding.preferenceRecyclerView.adapter as PreferencesAdapter + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(binding.root) + val screen = screen(this) { + subScreen { + titleRes = R.string.pref_category_general_title + iconRes = R.drawable.app_settings_alt_24px + generalSettings.provideSettings(this) + } + subScreen { + titleRes = R.string.pref_category_autofill_title + iconRes = R.drawable.ic_wysiwyg_24px + autofillSettings.provideSettings(this) + } + subScreen { + titleRes = R.string.pref_category_passwords_title + iconRes = R.drawable.ic_lock_open_24px + passwordSettings.provideSettings(this) + } + subScreen { + titleRes = R.string.pref_category_repository_title + iconRes = R.drawable.ic_call_merge_24px + repositorySettings.provideSettings(this) + } + subScreen { + titleRes = R.string.pref_category_misc_title + iconRes = R.drawable.ic_miscellaneous_services_24px + miscSettings.provideSettings(this) + } + } + val adapter = PreferencesAdapter(screen) + adapter.onScreenChangeListener = PreferencesAdapter.OnScreenChangeListener { subScreen, entering -> + supportActionBar?.title = if (!entering) { + getString(R.string.action_settings) + } else { + getString(subScreen.titleRes) + } + } + savedInstanceState?.getParcelable<PreferencesAdapter.SavedState>("adapter") + ?.let(adapter::loadSavedState) + binding.preferenceRecyclerView.adapter = adapter + } + + override fun onSaveInstanceState(outState: Bundle) { + super.onSaveInstanceState(outState) + outState.putParcelable("adapter", preferencesAdapter.getSavedState()) + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + return when (item.itemId) { + android.R.id.home -> if (!preferencesAdapter.goBack()) { + super.onOptionsItemSelected(item) + } else { + true + } + else -> super.onOptionsItemSelected(item) + } + } + + override fun onBackPressed() { + if (!preferencesAdapter.goBack()) + super.onBackPressed() + } +} diff --git a/app/src/main/java/dev/msfjarvis/aps/ui/settings/SettingsProvider.kt b/app/src/main/java/dev/msfjarvis/aps/ui/settings/SettingsProvider.kt new file mode 100644 index 00000000..b8398b94 --- /dev/null +++ b/app/src/main/java/dev/msfjarvis/aps/ui/settings/SettingsProvider.kt @@ -0,0 +1,19 @@ +/* + * Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved. + * SPDX-License-Identifier: GPL-3.0-only + */ + +package dev.msfjarvis.aps.ui.settings + +import de.Maxr1998.modernpreferences.PreferenceScreen + +/** + * Used to generate a uniform API for all settings UI classes. + */ +interface SettingsProvider { + + /** + * Inserts the settings items for the class into the given [builder]. + */ + fun provideSettings(builder: PreferenceScreen.Builder) +} diff --git a/app/src/main/java/dev/msfjarvis/aps/ui/settings/UserPreference.kt b/app/src/main/java/dev/msfjarvis/aps/ui/settings/UserPreference.kt deleted file mode 100644 index 315ebbda..00000000 --- a/app/src/main/java/dev/msfjarvis/aps/ui/settings/UserPreference.kt +++ /dev/null @@ -1,674 +0,0 @@ -/* - * Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved. - * SPDX-License-Identifier: GPL-3.0-only - */ -package dev.msfjarvis.aps.ui.settings - -import android.annotation.SuppressLint -import android.content.Context -import android.content.Intent -import android.content.SharedPreferences -import android.content.pm.ShortcutManager -import android.net.Uri -import android.os.Build -import android.os.Bundle -import android.os.Environment -import android.provider.DocumentsContract -import android.provider.Settings -import android.text.TextUtils -import android.view.MenuItem -import android.widget.Toast -import androidx.activity.result.contract.ActivityResultContracts.OpenDocument -import androidx.activity.result.contract.ActivityResultContracts.OpenDocumentTree -import androidx.annotation.RequiresApi -import androidx.appcompat.app.AppCompatActivity -import androidx.appcompat.widget.AppCompatTextView -import androidx.core.content.edit -import androidx.core.content.getSystemService -import androidx.documentfile.provider.DocumentFile -import androidx.preference.CheckBoxPreference -import androidx.preference.EditTextPreference -import androidx.preference.ListPreference -import androidx.preference.Preference -import androidx.preference.PreferenceFragmentCompat -import androidx.preference.SwitchPreferenceCompat -import com.github.ajalt.timberkt.Timber.tag -import com.github.ajalt.timberkt.d -import com.github.ajalt.timberkt.w -import com.github.androidpasswordstore.autofillparser.BrowserAutofillSupportLevel -import com.github.androidpasswordstore.autofillparser.getInstalledBrowsersWithAutofillSupportLevel -import com.github.michaelbull.result.getOr -import com.github.michaelbull.result.onFailure -import com.github.michaelbull.result.runCatching -import com.google.android.material.dialog.MaterialAlertDialogBuilder -import dev.msfjarvis.aps.BuildConfig -import dev.msfjarvis.aps.util.services.PasswordExportService -import dev.msfjarvis.aps.R -import dev.msfjarvis.aps.ui.crypto.BasePgpActivity -import dev.msfjarvis.aps.ui.git.config.GitConfigActivity -import dev.msfjarvis.aps.ui.git.config.GitServerConfigActivity -import dev.msfjarvis.aps.util.git.sshj.SshKey -import dev.msfjarvis.aps.util.pwgenxkpwd.XkpwdDictionary -import dev.msfjarvis.aps.ui.sshkeygen.ShowSshKeyFragment -import dev.msfjarvis.aps.ui.sshkeygen.SshKeyGenActivity -import dev.msfjarvis.aps.ui.proxy.ProxySelectorActivity -import dev.msfjarvis.aps.util.auth.BiometricAuthenticator -import dev.msfjarvis.aps.data.repo.PasswordRepository -import dev.msfjarvis.aps.util.settings.PreferenceKeys -import dev.msfjarvis.aps.util.extensions.autofillManager -import dev.msfjarvis.aps.util.extensions.getEncryptedGitPrefs -import dev.msfjarvis.aps.util.extensions.getString -import dev.msfjarvis.aps.util.extensions.sharedPrefs -import java.io.File - -typealias ClickListener = Preference.OnPreferenceClickListener -typealias ChangeListener = Preference.OnPreferenceChangeListener - -class UserPreference : AppCompatActivity() { - - private lateinit var prefsFragment: PrefsFragment - private var fromIntent = false - - @Suppress("DEPRECATION") - private val directorySelectAction = registerForActivityResult(OpenDocumentTree()) { uri: Uri? -> - if (uri == null) return@registerForActivityResult - - tag(TAG).d { "Selected repository URI is $uri" } - // TODO: This is fragile. Workaround until PasswordItem is backed by DocumentFile - val docId = DocumentsContract.getTreeDocumentId(uri) - val split = docId.split(":".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() - val path = if (split.size > 1) split[1] else split[0] - val repoPath = "${Environment.getExternalStorageDirectory()}/$path" - val prefs = sharedPrefs - - tag(TAG).d { "Selected repository path is $repoPath" } - - if (Environment.getExternalStorageDirectory().path == repoPath) { - MaterialAlertDialogBuilder(this) - .setTitle(getString(R.string.sdcard_root_warning_title)) - .setMessage(getString(R.string.sdcard_root_warning_message)) - .setPositiveButton("Remove everything") { _, _ -> - prefs.edit { putString(PreferenceKeys.GIT_EXTERNAL_REPO, uri.path) } - } - .setNegativeButton(R.string.dialog_cancel, null) - .show() - } - prefs.edit { putString(PreferenceKeys.GIT_EXTERNAL_REPO, repoPath) } - if (fromIntent) { - setResult(RESULT_OK) - finish() - } - - } - - private val sshKeyImportAction = registerForActivityResult(OpenDocument()) { uri: Uri? -> - if (uri == null) return@registerForActivityResult - runCatching { - SshKey.import(uri) - - Toast.makeText(this, resources.getString(R.string.ssh_key_success_dialog_title), Toast.LENGTH_LONG).show() - setResult(RESULT_OK) - finish() - }.onFailure { e -> - MaterialAlertDialogBuilder(this) - .setTitle(resources.getString(R.string.ssh_key_error_dialog_title)) - .setMessage(e.message) - .setPositiveButton(resources.getString(R.string.dialog_ok), null) - .show() - } - } - - private val storeExportAction = registerForActivityResult(object : OpenDocumentTree() { - override fun createIntent(context: Context, input: Uri?): Intent { - return super.createIntent(context, input).apply { - flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or - Intent.FLAG_GRANT_WRITE_URI_PERMISSION or - Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION or - Intent.FLAG_GRANT_PREFIX_URI_PERMISSION - } - } - }) { uri: Uri? -> - if (uri == null) return@registerForActivityResult - val targetDirectory = DocumentFile.fromTreeUri(applicationContext, uri) - - if (targetDirectory != null) { - val service = Intent(applicationContext, PasswordExportService::class.java).apply { - action = PasswordExportService.ACTION_EXPORT_PASSWORD - putExtra("uri", uri) - } - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - startForegroundService(service) - } else { - startService(service) - } - } - } - - private val storeCustomXkpwdDictionaryAction = registerForActivityResult(OpenDocument()) { uri -> - if (uri == null) return@registerForActivityResult - - Toast.makeText( - this, - this.resources.getString(R.string.xkpwgen_custom_dict_imported, uri.path), - Toast.LENGTH_SHORT - ).show() - - sharedPrefs.edit { putString(PreferenceKeys.PREF_KEY_CUSTOM_DICT, uri.toString()) } - - val customDictPref = prefsFragment.findPreference<Preference>(PreferenceKeys.PREF_KEY_CUSTOM_DICT) - setCustomDictSummary(customDictPref, uri) - // copy user selected file to internal storage - val inputStream = contentResolver.openInputStream(uri) - val customDictFile = File(filesDir.toString(), XkpwdDictionary.XKPWD_CUSTOM_DICT_FILE).outputStream() - inputStream?.copyTo(customDictFile, 1024) - inputStream?.close() - customDictFile.close() - - setResult(RESULT_OK) - } - - class PrefsFragment : PreferenceFragmentCompat() { - - private var autoFillEnablePreference: SwitchPreferenceCompat? = null - private var clearSavedPassPreference: Preference? = null - private var viewSshKeyPreference: Preference? = null - private lateinit var oreoAutofillDependencies: List<Preference> - private lateinit var prefsActivity: UserPreference - private lateinit var sharedPreferences: SharedPreferences - private lateinit var encryptedPreferences: SharedPreferences - - override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { - prefsActivity = requireActivity() as UserPreference - val context = requireContext() - sharedPreferences = preferenceManager.sharedPreferences - encryptedPreferences = requireActivity().getEncryptedGitPrefs() - - addPreferencesFromResource(R.xml.preference) - - // Git preferences - val gitServerPreference = findPreference<Preference>(PreferenceKeys.GIT_SERVER_INFO) - val openkeystoreIdPreference = findPreference<Preference>(PreferenceKeys.SSH_OPENKEYSTORE_CLEAR_KEY_ID) - val gitConfigPreference = findPreference<Preference>(PreferenceKeys.GIT_CONFIG) - val sshKeyPreference = findPreference<Preference>(PreferenceKeys.SSH_KEY) - val sshKeygenPreference = findPreference<Preference>(PreferenceKeys.SSH_KEYGEN) - viewSshKeyPreference = findPreference(PreferenceKeys.SSH_SEE_KEY) - clearSavedPassPreference = findPreference(PreferenceKeys.CLEAR_SAVED_PASS) - val deleteRepoPreference = findPreference<Preference>(PreferenceKeys.GIT_DELETE_REPO) - val externalGitRepositoryPreference = findPreference<Preference>(PreferenceKeys.GIT_EXTERNAL) - val selectExternalGitRepositoryPreference = findPreference<Preference>(PreferenceKeys.PREF_SELECT_EXTERNAL) - - if (!PasswordRepository.isGitRepo()) { - listOfNotNull( - gitServerPreference, - gitConfigPreference, - sshKeyPreference, - viewSshKeyPreference, - clearSavedPassPreference, - ).forEach { - it.parent?.removePreference(it) - } - } - - // General preferences - val showTimePreference = findPreference<Preference>(PreferenceKeys.GENERAL_SHOW_TIME) - val clearClipboard20xPreference = findPreference<CheckBoxPreference>(PreferenceKeys.CLEAR_CLIPBOARD_20X) - - // Autofill preferences - autoFillEnablePreference = findPreference(PreferenceKeys.AUTOFILL_ENABLE) - val oreoAutofillDirectoryStructurePreference = findPreference<ListPreference>(PreferenceKeys.OREO_AUTOFILL_DIRECTORY_STRUCTURE) - val oreoAutofillDefaultUsername = findPreference<EditTextPreference>(PreferenceKeys.OREO_AUTOFILL_DEFAULT_USERNAME) - val oreoAutofillCustomPublixSuffixes = findPreference<EditTextPreference>(PreferenceKeys.OREO_AUTOFILL_CUSTOM_PUBLIC_SUFFIXES) - oreoAutofillDependencies = listOfNotNull( - oreoAutofillDirectoryStructurePreference, - oreoAutofillDefaultUsername, - oreoAutofillCustomPublixSuffixes, - ) - oreoAutofillCustomPublixSuffixes?.apply { - setOnBindEditTextListener { - it.isSingleLine = false - it.setHint(R.string.preference_custom_public_suffixes_hint) - } - } - - // Misc preferences - val appVersionPreference = findPreference<Preference>(PreferenceKeys.APP_VERSION) - - selectExternalGitRepositoryPreference?.summary = sharedPreferences.getString(PreferenceKeys.GIT_EXTERNAL_REPO) - ?: getString(R.string.no_repo_selected) - deleteRepoPreference?.isVisible = !sharedPreferences.getBoolean(PreferenceKeys.GIT_EXTERNAL, false) - clearClipboard20xPreference?.isVisible = sharedPreferences.getString(PreferenceKeys.GENERAL_SHOW_TIME)?.toInt() != 0 - openkeystoreIdPreference?.isVisible = sharedPreferences.getString(PreferenceKeys.SSH_OPENKEYSTORE_KEYID)?.isNotEmpty() - ?: false - - updateAutofillSettings() - updateClearSavedPassphrasePrefs() - - appVersionPreference?.summary = "Version: ${BuildConfig.VERSION_NAME}" - - sshKeyPreference?.onPreferenceClickListener = ClickListener { - prefsActivity.getSshKey() - true - } - - sshKeygenPreference?.onPreferenceClickListener = ClickListener { - prefsActivity.makeSshKey(true) - true - } - - viewSshKeyPreference?.onPreferenceClickListener = ClickListener { - val df = ShowSshKeyFragment() - df.show(parentFragmentManager, "public_key") - true - } - - clearSavedPassPreference?.onPreferenceClickListener = ClickListener { - encryptedPreferences.edit { - if (encryptedPreferences.getString(PreferenceKeys.HTTPS_PASSWORD) != null) - remove(PreferenceKeys.HTTPS_PASSWORD) - else if (encryptedPreferences.getString(PreferenceKeys.SSH_KEY_LOCAL_PASSPHRASE) != null) - remove(PreferenceKeys.SSH_KEY_LOCAL_PASSPHRASE) - } - updateClearSavedPassphrasePrefs() - true - } - - openkeystoreIdPreference?.onPreferenceClickListener = ClickListener { - sharedPreferences.edit { putString(PreferenceKeys.SSH_OPENKEYSTORE_KEYID, null) } - it.isVisible = false - true - } - - gitServerPreference?.onPreferenceClickListener = ClickListener { - startActivity(Intent(prefsActivity, GitServerConfigActivity::class.java)) - true - } - - gitConfigPreference?.onPreferenceClickListener = ClickListener { - startActivity(Intent(prefsActivity, GitConfigActivity::class.java)) - true - } - - deleteRepoPreference?.onPreferenceClickListener = ClickListener { - val repoDir = PasswordRepository.getRepositoryDirectory() - MaterialAlertDialogBuilder(prefsActivity) - .setTitle(R.string.pref_dialog_delete_title) - .setMessage(resources.getString(R.string.dialog_delete_msg, repoDir)) - .setCancelable(false) - .setPositiveButton(R.string.dialog_delete) { dialogInterface, _ -> - runCatching { - PasswordRepository.getRepositoryDirectory().deleteRecursively() - PasswordRepository.closeRepository() - }.onFailure { - // TODO Handle the different cases of exceptions - } - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) { - requireContext().getSystemService<ShortcutManager>()?.apply { - removeDynamicShortcuts(dynamicShortcuts.map { it.id }.toMutableList()) - } - } - sharedPreferences.edit { putBoolean(PreferenceKeys.REPOSITORY_INITIALIZED, false) } - dialogInterface.cancel() - prefsActivity.finish() - } - .setNegativeButton(R.string.dialog_do_not_delete) { dialogInterface, _ -> run { dialogInterface.cancel() } } - .show() - - true - } - - selectExternalGitRepositoryPreference?.summary = - sharedPreferences.getString(PreferenceKeys.GIT_EXTERNAL_REPO) - ?: context.getString(R.string.no_repo_selected) - selectExternalGitRepositoryPreference?.onPreferenceClickListener = ClickListener { - prefsActivity.selectExternalGitRepository() - true - } - - val resetRepo = Preference.OnPreferenceChangeListener { _, o -> - deleteRepoPreference?.isVisible = !(o as Boolean) - PasswordRepository.closeRepository() - sharedPreferences.edit { putBoolean(PreferenceKeys.REPO_CHANGED, true) } - true - } - - selectExternalGitRepositoryPreference?.onPreferenceChangeListener = resetRepo - externalGitRepositoryPreference?.onPreferenceChangeListener = resetRepo - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - autoFillEnablePreference?.onPreferenceClickListener = ClickListener { - onEnableAutofillClick() - true - } - } - - findPreference<Preference>(PreferenceKeys.EXPORT_PASSWORDS)?.apply { - isVisible = sharedPreferences.getBoolean(PreferenceKeys.REPOSITORY_INITIALIZED, false) - onPreferenceClickListener = Preference.OnPreferenceClickListener { - prefsActivity.exportPasswords() - true - } - } - - showTimePreference?.onPreferenceChangeListener = ChangeListener { _, newValue: Any? -> - runCatching { - val isEnabled = newValue.toString().toInt() != 0 - clearClipboard20xPreference?.isVisible = isEnabled - true - }.getOr(false) - } - - showTimePreference?.summaryProvider = Preference.SummaryProvider<Preference> { - getString(R.string.pref_clipboard_timeout_summary, sharedPreferences.getString - (PreferenceKeys.GENERAL_SHOW_TIME, "45")) - } - - findPreference<CheckBoxPreference>(PreferenceKeys.ENABLE_DEBUG_LOGGING)?.isVisible = !BuildConfig.ENABLE_DEBUG_FEATURES - - findPreference<CheckBoxPreference>(PreferenceKeys.BIOMETRIC_AUTH)?.apply { - val canAuthenticate = BiometricAuthenticator.canAuthenticate(prefsActivity) - - if (!canAuthenticate) { - isEnabled = false - isChecked = false - summary = getString(R.string.biometric_auth_summary_error) - } else { - setOnPreferenceClickListener { - isEnabled = false - sharedPreferences.edit { - val checked = isChecked - BiometricAuthenticator.authenticate(requireActivity()) { result -> - when (result) { - is BiometricAuthenticator.Result.Success -> { - // Apply the changes - putBoolean(PreferenceKeys.BIOMETRIC_AUTH, checked) - isEnabled = true - } - else -> { - // If any error occurs, revert back to the previous state. This - // catch-all clause includes the cancellation case. - putBoolean(PreferenceKeys.BIOMETRIC_AUTH, !checked) - isChecked = !checked - isEnabled = true - } - } - } - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) { - requireContext().getSystemService<ShortcutManager>()?.apply { - removeDynamicShortcuts(dynamicShortcuts.map { it.id }.toMutableList()) - } - } - } - true - } - } - } - - findPreference<Preference>(PreferenceKeys.PROXY_SETTINGS)?.onPreferenceClickListener = ClickListener { - startActivity(Intent(requireContext(), ProxySelectorActivity::class.java)) - true - } - - val prefCustomXkpwdDictionary = findPreference<Preference>(PreferenceKeys.PREF_KEY_CUSTOM_DICT) - prefCustomXkpwdDictionary?.onPreferenceClickListener = ClickListener { - prefsActivity.storeCustomDictionaryPath() - true - } - val dictUri = sharedPreferences.getString(PreferenceKeys.PREF_KEY_CUSTOM_DICT) ?: "" - - if (!TextUtils.isEmpty(dictUri)) { - setCustomDictSummary(prefCustomXkpwdDictionary, Uri.parse(dictUri)) - } - - val prefIsCustomDict = findPreference<CheckBoxPreference>(PreferenceKeys.PREF_KEY_IS_CUSTOM_DICT) - val prefCustomDictPicker = findPreference<Preference>(PreferenceKeys.PREF_KEY_CUSTOM_DICT) - val prefPwgenType = findPreference<ListPreference>(PreferenceKeys.PREF_KEY_PWGEN_TYPE) - updateXkPasswdPrefsVisibility(prefPwgenType?.value, prefIsCustomDict, prefCustomDictPicker) - - prefPwgenType?.onPreferenceChangeListener = ChangeListener { _, newValue -> - updateXkPasswdPrefsVisibility(newValue, prefIsCustomDict, prefCustomDictPicker) - true - } - - prefIsCustomDict?.onPreferenceChangeListener = ChangeListener { _, newValue -> - if (!(newValue as Boolean)) { - val customDictFile = File(context.filesDir, XkpwdDictionary.XKPWD_CUSTOM_DICT_FILE) - if (customDictFile.exists() && !customDictFile.delete()) { - w { "Failed to delete custom XkPassword dictionary: $customDictFile" } - } - prefCustomDictPicker?.setSummary(R.string.xkpwgen_pref_custom_dict_picker_summary) - } - true - } - } - - private fun updateXkPasswdPrefsVisibility(newValue: Any?, prefIsCustomDict: CheckBoxPreference?, prefCustomDictPicker: Preference?) { - when (newValue as String) { - BasePgpActivity.KEY_PWGEN_TYPE_CLASSIC -> { - prefIsCustomDict?.isVisible = false - prefCustomDictPicker?.isVisible = false - } - BasePgpActivity.KEY_PWGEN_TYPE_XKPASSWD -> { - prefIsCustomDict?.isVisible = true - prefCustomDictPicker?.isVisible = true - } - } - } - - private fun updateAutofillSettings() { - val isAutofillServiceEnabled = prefsActivity.isAutofillServiceEnabled - val isAutofillSupported = prefsActivity.isAutofillServiceSupported - if (!isAutofillSupported) { - autoFillEnablePreference?.isVisible = false - } else { - autoFillEnablePreference?.isChecked = isAutofillServiceEnabled - } - oreoAutofillDependencies.forEach { - it.isVisible = isAutofillServiceEnabled - } - } - - private fun updateClearSavedPassphrasePrefs() { - clearSavedPassPreference?.apply { - val sshPass = encryptedPreferences.getString(PreferenceKeys.SSH_KEY_LOCAL_PASSPHRASE) - val httpsPass = encryptedPreferences.getString(PreferenceKeys.HTTPS_PASSWORD) - if (sshPass == null && httpsPass == null) { - isVisible = false - return@apply - } - title = when { - httpsPass != null -> getString(R.string.clear_saved_passphrase_https) - sshPass != null -> getString(R.string.clear_saved_passphrase_ssh) - else -> null - } - isVisible = true - } - } - - private fun updateViewSshPubkeyPref() { - viewSshKeyPreference?.isVisible = SshKey.canShowSshPublicKey - } - - @RequiresApi(Build.VERSION_CODES.O) - private fun onEnableAutofillClick() { - if (prefsActivity.isAutofillServiceEnabled) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) - prefsActivity.autofillManager!!.disableAutofillServices() - else - throw IllegalStateException("isAutofillServiceEnabled == true, but Build.VERSION.SDK_INT < Build.VERSION_CODES.O") - } else { - MaterialAlertDialogBuilder(prefsActivity).run { - setTitle(R.string.pref_autofill_enable_title) - @SuppressLint("InflateParams") - val layout = - layoutInflater.inflate(R.layout.oreo_autofill_instructions, null) - val supportedBrowsersTextView = - layout.findViewById<AppCompatTextView>(R.id.supportedBrowsers) - supportedBrowsersTextView.text = - getInstalledBrowsersWithAutofillSupportLevel(context).joinToString( - separator = "\n" - ) { - val appLabel = it.first - val supportDescription = when (it.second) { - BrowserAutofillSupportLevel.None -> getString(R.string.oreo_autofill_no_support) - BrowserAutofillSupportLevel.FlakyFill -> getString(R.string.oreo_autofill_flaky_fill_support) - BrowserAutofillSupportLevel.PasswordFill -> getString(R.string.oreo_autofill_password_fill_support) - BrowserAutofillSupportLevel.PasswordFillAndSaveIfNoAccessibility -> getString(R.string.oreo_autofill_password_fill_and_conditional_save_support) - BrowserAutofillSupportLevel.GeneralFill -> getString(R.string.oreo_autofill_general_fill_support) - BrowserAutofillSupportLevel.GeneralFillAndSave -> getString(R.string.oreo_autofill_general_fill_and_save_support) - } - "$appLabel: $supportDescription" - } - setView(layout) - setPositiveButton(R.string.dialog_ok) { _, _ -> - val intent = Intent(Settings.ACTION_REQUEST_SET_AUTOFILL_SERVICE).apply { - data = Uri.parse("package:${BuildConfig.APPLICATION_ID}") - } - startActivity(intent) - } - setNegativeButton(R.string.dialog_cancel, null) - setOnDismissListener { updateAutofillSettings() } - show() - } - } - } - - override fun onResume() { - super.onResume() - updateAutofillSettings() - updateClearSavedPassphrasePrefs() - updateViewSshPubkeyPref() - } - } - - override fun onBackPressed() { - super.onBackPressed() - setResult(RESULT_OK) - finish() - } - - public override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - when (intent?.getStringExtra("operation")) { - "get_ssh_key" -> getSshKey() - "make_ssh_key" -> makeSshKey(false) - "git_external" -> { - fromIntent = true - selectExternalGitRepository() - } - } - prefsFragment = PrefsFragment() - - supportFragmentManager - .beginTransaction() - .replace(android.R.id.content, prefsFragment) - .commit() - - supportActionBar?.setDisplayHomeAsUpEnabled(true) - } - - @Suppress("Deprecation") // for Environment.getExternalStorageDirectory() - fun selectExternalGitRepository() { - MaterialAlertDialogBuilder(this) - .setTitle(this.resources.getString(R.string.external_repository_dialog_title)) - .setMessage(this.resources.getString(R.string.external_repository_dialog_text)) - .setPositiveButton(R.string.dialog_ok) { _, _ -> - directorySelectAction.launch(null) - } - .setNegativeButton(R.string.dialog_cancel, null) - .show() - } - - override fun onOptionsItemSelected(item: MenuItem): Boolean { - return when (item.itemId) { - android.R.id.home -> { - setResult(RESULT_OK) - onBackPressed() - true - } - else -> super.onOptionsItemSelected(item) - } - } - - private fun importSshKey() { - sshKeyImportAction.launch(arrayOf("*/*")) - } - - /** - * Opens a file explorer to import the private key - */ - private fun getSshKey() { - if (SshKey.exists) { - MaterialAlertDialogBuilder(this).run { - setTitle(R.string.ssh_keygen_existing_title) - setMessage(R.string.ssh_keygen_existing_message) - setPositiveButton(R.string.ssh_keygen_existing_replace) { _, _ -> - importSshKey() - } - setNegativeButton(R.string.ssh_keygen_existing_keep) { _, _ -> } - show() - } - } else { - importSshKey() - } - } - - /** - * Exports the passwords - */ - private fun exportPasswords() { - storeExportAction.launch(null) - } - - /** - * Opens a key generator to generate a public/private key pair - */ - fun makeSshKey(fromPreferences: Boolean) { - val intent = Intent(applicationContext, SshKeyGenActivity::class.java) - startActivity(intent) - if (!fromPreferences) { - setResult(RESULT_OK) - finish() - } - } - - /** - * Pick custom xkpwd dictionary from sdcard - */ - private fun storeCustomDictionaryPath() { - storeCustomXkpwdDictionaryAction.launch(arrayOf("*/*")) - } - - private val isAutofillServiceSupported: Boolean - get() { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return false - return autofillManager?.isAutofillSupported != null - } - - private val isAutofillServiceEnabled: Boolean - get() { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return false - return autofillManager?.hasEnabledAutofillServices() == true - } - - companion object { - - private const val TAG = "UserPreference" - - fun createDirectorySelectionIntent(context: Context): Intent { - return Intent(context, UserPreference::class.java).run { - putExtra("operation", "git_external") - } - } - - /** - * Set custom dictionary summary - */ - @JvmStatic - private fun setCustomDictSummary(customDictPref: Preference?, uri: Uri) { - val fileName = uri.path?.substring(uri.path?.lastIndexOf(":")!! + 1) - customDictPref?.summary = "Selected dictionary: $fileName" - } - } -} diff --git a/app/src/main/java/dev/msfjarvis/aps/ui/sshkeygen/SshKeyGenActivity.kt b/app/src/main/java/dev/msfjarvis/aps/ui/sshkeygen/SshKeyGenActivity.kt index b260b77e..efcdd0f3 100644 --- a/app/src/main/java/dev/msfjarvis/aps/ui/sshkeygen/SshKeyGenActivity.kt +++ b/app/src/main/java/dev/msfjarvis/aps/ui/sshkeygen/SshKeyGenActivity.kt @@ -144,6 +144,7 @@ class SshKeyGenActivity : AppCompatActivity() { .setTitle(getString(R.string.error_generate_ssh_key)) .setMessage(getString(R.string.ssh_key_error_dialog_text) + e.message) .setPositiveButton(getString(R.string.dialog_ok)) { _, _ -> + setResult(RESULT_OK) finish() } .show() diff --git a/app/src/main/java/dev/msfjarvis/aps/ui/sshkeygen/SshKeyImportActivity.kt b/app/src/main/java/dev/msfjarvis/aps/ui/sshkeygen/SshKeyImportActivity.kt new file mode 100644 index 00000000..1bb1056e --- /dev/null +++ b/app/src/main/java/dev/msfjarvis/aps/ui/sshkeygen/SshKeyImportActivity.kt @@ -0,0 +1,61 @@ +/* + * Copyright © 2014-2021 The Android Password Store Authors. All Rights Reserved. + * SPDX-License-Identifier: GPL-3.0-only + */ + +package dev.msfjarvis.aps.ui.sshkeygen + +import dev.msfjarvis.aps.R +import dev.msfjarvis.aps.util.git.sshj.SshKey +import android.net.Uri +import android.os.Bundle +import android.widget.Toast +import androidx.activity.result.contract.ActivityResultContracts +import androidx.appcompat.app.AppCompatActivity +import com.github.michaelbull.result.onFailure +import com.github.michaelbull.result.runCatching +import com.google.android.material.dialog.MaterialAlertDialogBuilder + +class SshKeyImportActivity : AppCompatActivity() { + + private val sshKeyImportAction = registerForActivityResult(ActivityResultContracts.OpenDocument()) { uri: Uri? -> + if (uri == null) { + finish() + return@registerForActivityResult + } + runCatching { + SshKey.import(uri) + Toast.makeText(this, resources.getString(R.string.ssh_key_success_dialog_title), Toast.LENGTH_LONG).show() + setResult(RESULT_OK) + finish() + }.onFailure { e -> + MaterialAlertDialogBuilder(this) + .setTitle(resources.getString(R.string.ssh_key_error_dialog_title)) + .setMessage(e.message) + .setPositiveButton(resources.getString(R.string.dialog_ok)) { _, _ -> finish() } + .show() + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + if (SshKey.exists) { + MaterialAlertDialogBuilder(this).run { + setTitle(R.string.ssh_keygen_existing_title) + setMessage(R.string.ssh_keygen_existing_message) + setPositiveButton(R.string.ssh_keygen_existing_replace) { _, _ -> + importSshKey() + } + setNegativeButton(R.string.ssh_keygen_existing_keep) { _, _ -> finish() } + setOnCancelListener { finish() } + show() + } + } else { + importSshKey() + } + } + + private fun importSshKey() { + sshKeyImportAction.launch(arrayOf("*/*")) + } +} diff --git a/app/src/main/java/dev/msfjarvis/aps/util/autofill/AutofillPreferences.kt b/app/src/main/java/dev/msfjarvis/aps/util/autofill/AutofillPreferences.kt index aa70bacb..bcc5aac1 100644 --- a/app/src/main/java/dev/msfjarvis/aps/util/autofill/AutofillPreferences.kt +++ b/app/src/main/java/dev/msfjarvis/aps/util/autofill/AutofillPreferences.kt @@ -9,8 +9,10 @@ import android.os.Build import androidx.annotation.RequiresApi import com.github.androidpasswordstore.autofillparser.Credentials import dev.msfjarvis.aps.data.password.PasswordEntry +import dev.msfjarvis.aps.util.extensions.getString import dev.msfjarvis.aps.util.extensions.sharedPrefs import dev.msfjarvis.aps.util.services.getDefaultUsername +import dev.msfjarvis.aps.util.settings.PreferenceKeys import java.io.File import java.nio.file.Paths @@ -110,8 +112,7 @@ enum class DirectoryStructure(val value: String) { companion object { - const val PREFERENCE = "oreo_autofill_directory_structure" - private val DEFAULT = FileBased + val DEFAULT = FileBased private val reverseMap = values().associateBy { it.value } fun fromValue(value: String?) = if (value != null) reverseMap[value] ?: DEFAULT else DEFAULT @@ -121,7 +122,7 @@ enum class DirectoryStructure(val value: String) { object AutofillPreferences { fun directoryStructure(context: Context): DirectoryStructure { - val value = context.sharedPrefs.getString(DirectoryStructure.PREFERENCE, null) + val value = context.sharedPrefs.getString(PreferenceKeys.OREO_AUTOFILL_DIRECTORY_STRUCTURE) return DirectoryStructure.fromValue(value) } diff --git a/app/src/main/java/dev/msfjarvis/aps/util/git/operation/GitOperation.kt b/app/src/main/java/dev/msfjarvis/aps/util/git/operation/GitOperation.kt index 44292fc6..83c25a7e 100644 --- a/app/src/main/java/dev/msfjarvis/aps/util/git/operation/GitOperation.kt +++ b/app/src/main/java/dev/msfjarvis/aps/util/git/operation/GitOperation.kt @@ -15,7 +15,6 @@ import com.github.michaelbull.result.onFailure import com.github.michaelbull.result.runCatching import com.google.android.material.dialog.MaterialAlertDialogBuilder import dev.msfjarvis.aps.R -import dev.msfjarvis.aps.ui.settings.UserPreference import dev.msfjarvis.aps.util.git.GitCommandExecutor import dev.msfjarvis.aps.util.settings.AuthMode import dev.msfjarvis.aps.util.settings.GitSettings @@ -25,6 +24,8 @@ import dev.msfjarvis.aps.util.git.sshj.SshKey import dev.msfjarvis.aps.util.git.sshj.SshjSessionFactory import dev.msfjarvis.aps.util.auth.BiometricAuthenticator import dev.msfjarvis.aps.data.repo.PasswordRepository +import dev.msfjarvis.aps.ui.sshkeygen.SshKeyGenActivity +import dev.msfjarvis.aps.ui.sshkeygen.SshKeyImportActivity import kotlin.coroutines.resume import kotlin.coroutines.suspendCoroutine import kotlinx.coroutines.Dispatchers @@ -92,9 +93,11 @@ abstract class GitOperation(protected val callingActivity: FragmentActivity) { private fun getSshKey(make: Boolean) { runCatching { - // Ask the UserPreference to provide us with the ssh-key - val intent = Intent(callingActivity.applicationContext, UserPreference::class.java) - intent.putExtra("operation", if (make) "make_ssh_key" else "get_ssh_key") + val intent = if (make) { + Intent(callingActivity.applicationContext, SshKeyGenActivity::class.java) + } else { + Intent(callingActivity.applicationContext, SshKeyImportActivity::class.java) + } callingActivity.startActivity(intent) }.onFailure { e -> e(e) |