aboutsummaryrefslogtreecommitdiffstats
path: root/app/src/main/java/dev/msfjarvis/aps/ui/settings/UserPreference.kt
diff options
context:
space:
mode:
authorHarsh Shandilya <me@msfjarvis.dev>2020-12-05 06:07:18 +0530
committerGitHub <noreply@github.com>2020-12-05 06:07:18 +0530
commit5e66d99c852ea67a88b650c03b0e8d55e83eccde (patch)
treeaee323a08253fe7b863976eff92a1bd412bd1430 /app/src/main/java/dev/msfjarvis/aps/ui/settings/UserPreference.kt
parent8eb55f18a11d6b2f155c7c9d48ca833313ee13db (diff)
downloadHardenedPass-5e66d99c852ea67a88b650c03b0e8d55e83eccde.tar.gz
HardenedPass-5e66d99c852ea67a88b650c03b0e8d55e83eccde.zip
Refactor package structure (#1233)
* idea: default test runner to Gradle * Kick off package structure revamp * Reparent all classes under dev.msfjarvis.aps Signed-off-by: Harsh Shandilya <me@msfjarvis.dev>
Diffstat (limited to 'app/src/main/java/dev/msfjarvis/aps/ui/settings/UserPreference.kt')
-rw-r--r--app/src/main/java/dev/msfjarvis/aps/ui/settings/UserPreference.kt676
1 files changed, 676 insertions, 0 deletions
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
new file mode 100644
index 00000000..df51562f
--- /dev/null
+++ b/app/src/main/java/dev/msfjarvis/aps/ui/settings/UserPreference.kt
@@ -0,0 +1,676 @@
+/*
+ * 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.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 {
+ // Handle action bar item clicks here. The action bar will
+ // automatically handle clicks on the Home/Up button, so long
+ // as you specify a parent activity in AndroidManifest.xml.
+ return when (item.itemId) {
+ android.R.id.home -> {
+ setResult(RESULT_OK)
+ finish()
+ 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"
+ }
+ }
+}