From bebe43468330f182ea35e01ace555ce8d0512aeb Mon Sep 17 00:00:00 2001 From: Fabian Henneke Date: Tue, 24 Mar 2020 14:03:40 +0100 Subject: Add support for Oreo Autofill (#653) Adds support for the Autofill feature first available in Android Oreo. In compatible apps and browsers, login forms are automatically detected and the user is presented with options to fill or generate credentials. In most apps and certain browsers, the service will also offer to create new Password Store entries from credentials entered into login forms. Signed-off-by: Harsh Shandilya Co-authored-by: Harsh Shandilya --- .../main/java/com/zeapo/pwdstore/UserPreference.kt | 126 +++++++++++++++------ 1 file changed, 94 insertions(+), 32 deletions(-) (limited to 'app/src/main/java/com/zeapo/pwdstore/UserPreference.kt') diff --git a/app/src/main/java/com/zeapo/pwdstore/UserPreference.kt b/app/src/main/java/com/zeapo/pwdstore/UserPreference.kt index 8667110a..895338e9 100644 --- a/app/src/main/java/com/zeapo/pwdstore/UserPreference.kt +++ b/app/src/main/java/com/zeapo/pwdstore/UserPreference.kt @@ -6,7 +6,6 @@ package com.zeapo.pwdstore import android.accessibilityservice.AccessibilityServiceInfo import android.app.Activity -import android.content.Context import android.content.Intent import android.content.pm.ShortcutManager import android.net.Uri @@ -20,6 +19,7 @@ import android.view.MenuItem import android.view.accessibility.AccessibilityManager import android.widget.Toast import androidx.appcompat.app.AppCompatActivity +import androidx.appcompat.widget.AppCompatTextView import androidx.biometric.BiometricManager import androidx.core.content.getSystemService import androidx.documentfile.provider.DocumentFile @@ -32,6 +32,8 @@ import androidx.preference.SwitchPreferenceCompat 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.oreo.BrowserAutofillSupportLevel +import com.zeapo.pwdstore.autofill.oreo.getInstalledBrowsersWithAutofillSupportLevel import com.zeapo.pwdstore.crypto.PgpActivity import com.zeapo.pwdstore.git.GitActivity import com.zeapo.pwdstore.pwgenxkpwd.XkpwdDictionary @@ -40,6 +42,7 @@ import com.zeapo.pwdstore.sshkeygen.SshKeyGenActivity import com.zeapo.pwdstore.utils.PasswordRepository import com.zeapo.pwdstore.utils.auth.AuthenticationResult import com.zeapo.pwdstore.utils.auth.Authenticator +import com.zeapo.pwdstore.utils.autofillManager import java.io.File import java.io.IOException import java.time.LocalDateTime @@ -127,9 +130,7 @@ class UserPreference : AppCompatActivity() { openkeystoreIdPreference?.isVisible = sharedPreferences.getString("ssh_openkeystore_keyid", null)?.isNotEmpty() ?: false - // see if the autofill service is enabled and check the preference accordingly - autoFillEnablePreference?.isChecked = callingActivity.isServiceEnabled - autofillDependencies.forEach { it?.isVisible = callingActivity.isServiceEnabled } + updateAutofillSettings() appVersionPreference?.summary = "Version: ${BuildConfig.VERSION_NAME}" @@ -242,24 +243,7 @@ class UserPreference : AppCompatActivity() { } autoFillEnablePreference?.onPreferenceClickListener = ClickListener { - var isEnabled = callingActivity.isServiceEnabled - if (isEnabled) { - startActivity(Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS)) - } else { - MaterialAlertDialogBuilder(callingActivity) - .setTitle(R.string.pref_autofill_enable_title) - .setView(R.layout.autofill_instructions) - .setPositiveButton(R.string.dialog_ok) { _, _ -> - startActivity(Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS)) - } - .setNegativeButton(R.string.dialog_cancel, null) - .setOnDismissListener { - isEnabled = callingActivity.isServiceEnabled - autoFillEnablePreference?.isChecked = isEnabled - autofillDependencies.forEach { it?.isVisible = isEnabled } - } - .show() - } + onEnableAutofillClick() true } @@ -370,11 +354,79 @@ class UserPreference : AppCompatActivity() { } } + private fun updateAutofillSettings() { + val isAccessibilityServiceEnabled = callingActivity.isAccessibilityServiceEnabled + autoFillEnablePreference?.isChecked = + isAccessibilityServiceEnabled || callingActivity.isAutofillServiceEnabled + autofillDependencies.forEach { + it?.isVisible = isAccessibilityServiceEnabled + } + } + + private fun onEnableAutofillClick() { + if (callingActivity.isAccessibilityServiceEnabled) { + startActivity(Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS)) + } else if (callingActivity.isAutofillServiceEnabled) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) + callingActivity.autofillManager!!.disableAutofillServices() + else + throw IllegalStateException("isAutofillServiceEnabled == true, but Build.VERSION.SDK_INT < Build.VERSION_CODES.O") + } else { + val enableOreoAutofill = callingActivity.isAutofillServiceSupported + MaterialAlertDialogBuilder(callingActivity).run { + setTitle(R.string.pref_autofill_enable_title) + if (enableOreoAutofill && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + val layout = + layoutInflater.inflate(R.layout.oreo_autofill_instructions, null) + val supportedBrowsersTextView = + layout.findViewById(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.Fill -> getString(R.string.oreo_autofill_fill_support) + BrowserAutofillSupportLevel.FillAndSave -> getString(R.string.oreo_autofill_fill_and_save_support) + } + "$appLabel: $supportDescription" + } + setView(layout) + } else { + setView(R.layout.autofill_instructions) + } + setPositiveButton(R.string.dialog_ok) { _, _ -> + val intent = + if (enableOreoAutofill && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + Intent(Settings.ACTION_REQUEST_SET_AUTOFILL_SERVICE).apply { + data = Uri.parse("package:${BuildConfig.APPLICATION_ID}") + } + } else { + Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS) + } + startActivity(intent) + } + setNegativeButton(R.string.dialog_cancel, null) + setOnDismissListener { + val isEnabled = + if (enableOreoAutofill && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + callingActivity.isAutofillServiceEnabled + } else { + callingActivity.isAccessibilityServiceEnabled + } + autoFillEnablePreference?.isChecked = isEnabled + autofillDependencies.forEach { it?.isVisible = isEnabled } + } + show() + } + } + } + override fun onResume() { super.onResume() - val isEnabled = callingActivity.isServiceEnabled - autoFillEnablePreference?.isChecked = isEnabled - autofillDependencies.forEach { it?.isVisible = isEnabled } + updateAutofillSettings() } } @@ -487,16 +539,26 @@ class UserPreference : AppCompatActivity() { } } - // Returns whether the autofill service is enabled - private val isServiceEnabled: Boolean + private val isAccessibilityServiceEnabled: Boolean get() { - val am = this - .getSystemService(Context.ACCESSIBILITY_SERVICE) as AccessibilityManager + val am = getSystemService(AccessibilityManager::class.java) val runningServices = am - .getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_GENERIC) + .getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_GENERIC) return runningServices - .map { it.id.substringBefore("/") } - .any { it == BuildConfig.APPLICATION_ID } + .map { it.id.substringBefore("/") } + .any { it == BuildConfig.APPLICATION_ID } + } + + 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 } override fun onActivityResult( -- cgit v1.2.3