diff options
Diffstat (limited to 'app/src/main/java')
3 files changed, 142 insertions, 9 deletions
diff --git a/app/src/main/java/com/zeapo/pwdstore/UserPreference.kt b/app/src/main/java/com/zeapo/pwdstore/UserPreference.kt index ea3bac38..4d68834b 100644 --- a/app/src/main/java/com/zeapo/pwdstore/UserPreference.kt +++ b/app/src/main/java/com/zeapo/pwdstore/UserPreference.kt @@ -41,7 +41,9 @@ import com.github.ajalt.timberkt.w import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.snackbar.Snackbar import com.zeapo.pwdstore.autofill.AutofillPreferenceActivity +import com.zeapo.pwdstore.autofill.AutofillService import com.zeapo.pwdstore.autofill.oreo.BrowserAutofillSupportLevel +import com.zeapo.pwdstore.autofill.oreo.ChromeCompatFix import com.zeapo.pwdstore.autofill.oreo.getInstalledBrowsersWithAutofillSupportLevel import com.zeapo.pwdstore.crypto.BasePgpActivity import com.zeapo.pwdstore.crypto.GetKeyIdsActivity @@ -73,6 +75,7 @@ class UserPreference : AppCompatActivity() { class PrefsFragment : PreferenceFragmentCompat() { private var autoFillEnablePreference: SwitchPreferenceCompat? = null + private var oreoAutofillChromeCompatFix: SwitchPreferenceCompat? = null private var clearSavedPassPreference: Preference? = null private lateinit var autofillDependencies: List<Preference> private lateinit var oreoAutofillDependencies: List<Preference> @@ -118,6 +121,7 @@ class UserPreference : AppCompatActivity() { // Autofill preferences autoFillEnablePreference = findPreference(PreferenceKeys.AUTOFILL_ENABLE) + oreoAutofillChromeCompatFix = findPreference(PreferenceKeys.OREO_AUTOFILL_CHROME_COMPAT_FIX) 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) @@ -276,6 +280,16 @@ class UserPreference : AppCompatActivity() { true } + oreoAutofillChromeCompatFix?.onPreferenceClickListener = ClickListener { + if (oreoAutofillChromeCompatFix!!.isChecked) { + startActivity(Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS)) + true + } else { + // Service will disable itself on startup if the preference has the value false. + false + } + } + findPreference<Preference>(PreferenceKeys.EXPORT_PASSWORDS)?.apply { isVisible = sharedPreferences.getBoolean(PreferenceKeys.REPOSITORY_INITIALIZED, false) onPreferenceClickListener = Preference.OnPreferenceClickListener { @@ -398,16 +412,20 @@ class UserPreference : AppCompatActivity() { } private fun updateAutofillSettings() { - val isAccessibilityServiceEnabled = callingActivity.isAccessibilityServiceEnabled + val isAccessibilityAutofillServiceEnabled = callingActivity.isAccessibilityAutofillServiceEnabled val isAutofillServiceEnabled = callingActivity.isAutofillServiceEnabled autoFillEnablePreference?.isChecked = - isAccessibilityServiceEnabled || isAutofillServiceEnabled + isAccessibilityAutofillServiceEnabled || isAutofillServiceEnabled autofillDependencies.forEach { - it.isVisible = isAccessibilityServiceEnabled + it.isVisible = isAccessibilityAutofillServiceEnabled } oreoAutofillDependencies.forEach { it.isVisible = isAutofillServiceEnabled } + oreoAutofillChromeCompatFix?.apply { + isChecked = callingActivity.isChromeCompatFixServiceEnabled + isVisible = callingActivity.isChromeCompatFixServiceSupported + } } private fun updateClearSavedPassphrasePrefs() { @@ -428,13 +446,16 @@ class UserPreference : AppCompatActivity() { } private fun onEnableAutofillClick() { - if (callingActivity.isAccessibilityServiceEnabled) { + if (callingActivity.isAccessibilityAutofillServiceEnabled) { startActivity(Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS)) } else if (callingActivity.isAutofillServiceEnabled) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { callingActivity.autofillManager!!.disableAutofillServices() - else + ChromeCompatFix.setStatusInPreferences(requireContext(), false) + updateAutofillSettings() + } else { throw IllegalStateException("isAutofillServiceEnabled == true, but Build.VERSION.SDK_INT < Build.VERSION_CODES.O") + } } else { val enableOreoAutofill = callingActivity.isAutofillServiceSupported MaterialAlertDialogBuilder(callingActivity).run { @@ -710,14 +731,32 @@ class UserPreference : AppCompatActivity() { File("$filesDir/.ssh_key").writeText(lines.joinToString("\n")) } - private val isAccessibilityServiceEnabled: Boolean + private val isAccessibilityAutofillServiceEnabled: Boolean get() { val am = getSystemService<AccessibilityManager>() ?: return false val runningServices = am .getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_GENERIC) return runningServices - .map { it.id.substringBefore("/") } - .any { it == BuildConfig.APPLICATION_ID } + .mapNotNull { it?.resolveInfo?.serviceInfo } + .any { it.packageName == BuildConfig.APPLICATION_ID && it.name == AutofillService::class.java.name } + } + + private val isChromeCompatFixServiceEnabled: Boolean + get() { + val am = getSystemService<AccessibilityManager>() ?: return false + val runningServices = am + .getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_GENERIC) + return runningServices + .mapNotNull { it?.resolveInfo?.serviceInfo } + .any { it.packageName == BuildConfig.APPLICATION_ID && it.name == ChromeCompatFix::class.java.name } + } + + private val isChromeCompatFixServiceSupported: Boolean + get() { + // Autofill compat mode is only available starting with Android Pie and only makes sense + // when used with Autofill enabled. + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) return false + return isAutofillServiceEnabled } private val isAutofillServiceSupported: Boolean diff --git a/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/ChromeCompatFix.kt b/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/ChromeCompatFix.kt new file mode 100644 index 00000000..75d9539a --- /dev/null +++ b/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/ChromeCompatFix.kt @@ -0,0 +1,93 @@ +/* + * Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved. + * SPDX-License-Identifier: GPL-3.0-only + */ +package com.zeapo.pwdstore.autofill.oreo + +import android.accessibilityservice.AccessibilityService +import android.content.Context +import android.content.SharedPreferences +import android.os.Build +import android.os.Handler +import android.os.Looper +import android.view.accessibility.AccessibilityEvent +import androidx.annotation.RequiresApi +import androidx.core.content.edit +import androidx.preference.PreferenceManager +import com.github.ajalt.timberkt.i +import com.github.ajalt.timberkt.v +import com.github.ajalt.timberkt.w +import com.zeapo.pwdstore.utils.PreferenceKeys +import com.zeapo.pwdstore.utils.autofillManager + +@RequiresApi(Build.VERSION_CODES.P) +class ChromeCompatFix : AccessibilityService() { + + companion object { + fun setStatusInPreferences(context: Context, enabled: Boolean) { + PreferenceManager.getDefaultSharedPreferences(context).edit { + putBoolean(PreferenceKeys.OREO_AUTOFILL_CHROME_COMPAT_FIX, enabled) + } + } + } + + private val isEnabledInPreferences + get() = PreferenceManager.getDefaultSharedPreferences(this).getBoolean(PreferenceKeys.OREO_AUTOFILL_CHROME_COMPAT_FIX, true) + + private val handler = Handler(Looper.getMainLooper()) + private val forceRootNodePopulation = Runnable { + val rootPackageName = rootInActiveWindow?.packageName.toString() + v { "$rootPackageName: forced root node population" } + } + private val disableListener = SharedPreferences.OnSharedPreferenceChangeListener { prefs: SharedPreferences, key: String -> + if (key != PreferenceKeys.OREO_AUTOFILL_CHROME_COMPAT_FIX) + return@OnSharedPreferenceChangeListener + if (!isEnabledInPreferences) { + i { "Disabled in settings, shutting down..." } + disableSelf() + } + } + + override fun onAccessibilityEvent(event: AccessibilityEvent) { + handler.removeCallbacks(forceRootNodePopulation) + when (event.eventType) { + AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED, AccessibilityEvent.TYPE_ANNOUNCEMENT -> { + // WINDOW_STATE_CHANGED: Triggered on long press in a text field, replacement for + // the missing Autofill action menu item. + // ANNOUNCEMENT: Triggered when a password field is selected. + // + // These events are triggered only by user actions and thus don't need to be handled + // with debounce. However, they only trigger Autofill popups on the *next* input + // field selected by the user. + forceRootNodePopulation.run() + v { "${event.packageName} (${AccessibilityEvent.eventTypeToString(event.eventType)}): forced root node population" } + } + AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED -> { + // WINDOW_CONTENT_CHANGED: Triggered whenever the page contents change. + // + // This event is triggered many times during page load, which makes a debounce + // necessary to prevent huge performance regressions in Chrome. However, it is the + // only event that reliably runs before the user selects a text field. + handler.postDelayed(forceRootNodePopulation, 300) + v { "${event.packageName} (${AccessibilityEvent.eventTypeToString(event.eventType)}): debounced root node population" } + } + } + } + + override fun onServiceConnected() { + super.onServiceConnected() + // Allow the service to be activated only if the Autofill service is already enabled. + if (autofillManager?.hasEnabledAutofillServices() != true) { + w { "Autofill service not enabled, shutting down..." } + disableSelf() + return + } + // Update preferences if the user manually activated the service. + setStatusInPreferences(this, true) + + PreferenceManager.getDefaultSharedPreferences(this).registerOnSharedPreferenceChangeListener(disableListener) + } + + override fun onInterrupt() {} +} + diff --git a/app/src/main/java/com/zeapo/pwdstore/utils/PreferenceKeys.kt b/app/src/main/java/com/zeapo/pwdstore/utils/PreferenceKeys.kt index 05f9c741..7d019508 100644 --- a/app/src/main/java/com/zeapo/pwdstore/utils/PreferenceKeys.kt +++ b/app/src/main/java/com/zeapo/pwdstore/utils/PreferenceKeys.kt @@ -35,6 +35,7 @@ object PreferenceKeys { const val OPENPGP_KEY_IDS_SET = "openpgp_key_ids_set" const val OPENPGP_KEY_ID_PREF = "openpgp_key_id_pref" const val OPENPGP_PROVIDER_LIST = "openpgp_provider_list" + const val OREO_AUTOFILL_CHROME_COMPAT_FIX = "oreo_autofill_chrome_compat_fix" const val OREO_AUTOFILL_CUSTOM_PUBLIC_SUFFIXES = "oreo_autofill_custom_public_suffixes" const val OREO_AUTOFILL_DEFAULT_USERNAME = "oreo_autofill_default_username" const val OREO_AUTOFILL_DIRECTORY_STRUCTURE = "oreo_autofill_directory_structure" |