From 4a9151870d8e5196248b2abc09df8fbb1593c265 Mon Sep 17 00:00:00 2001 From: Harsh Shandilya Date: Thu, 22 Oct 2020 23:38:47 +0530 Subject: Remove accessibility autofill support (#1162) * autofill: remove Accessibility backend Signed-off-by: Harsh Shandilya * CHANGELOG: update Signed-off-by: Harsh Shandilya --- CHANGELOG.md | 4 + app/src/main/AndroidManifest.xml | 23 - .../main/java/com/zeapo/pwdstore/UserPreference.kt | 108 ++-- .../zeapo/pwdstore/autofill/AutofillActivity.kt | 98 ---- .../zeapo/pwdstore/autofill/AutofillFragment.kt | 232 -------- .../autofill/AutofillPreferenceActivity.kt | 159 ------ .../pwdstore/autofill/AutofillRecyclerAdapter.kt | 180 ------- .../com/zeapo/pwdstore/autofill/AutofillService.kt | 584 --------------------- .../com/zeapo/pwdstore/utils/PreferenceKeys.kt | 4 - .../main/res/drawable-nodpi/autofill_ins_1.webp | Bin 3526 -> 0 bytes .../main/res/drawable-nodpi/autofill_ins_2.webp | Bin 2258 -> 0 bytes .../main/res/drawable-nodpi/autofill_ins_3.webp | Bin 3516 -> 0 bytes app/src/main/res/layout/autofill_instructions.xml | 64 --- app/src/main/res/layout/autofill_recycler_view.xml | 43 -- app/src/main/res/layout/autofill_row_layout.xml | 41 -- app/src/main/res/layout/fragment_autofill.xml | 84 --- app/src/main/res/menu/autofill_preference.xml | 17 - app/src/main/res/values-ar/strings.xml | 8 - app/src/main/res/values-cs/strings.xml | 10 - app/src/main/res/values-de/strings.xml | 18 - app/src/main/res/values-es/strings.xml | 18 - app/src/main/res/values-fr/strings.xml | 17 - app/src/main/res/values-ja/strings.xml | 10 - app/src/main/res/values-pt-rBR/strings.xml | 18 - app/src/main/res/values-ru/strings.xml | 18 - app/src/main/res/values-v26/bools.xml | 8 - app/src/main/res/values-zh-rCN/strings.xml | 10 - app/src/main/res/values-zh-rTW/strings.xml | 10 - app/src/main/res/values/bools.xml | 1 - app/src/main/res/values/strings.xml | 18 - app/src/main/res/xml/autofill_config.xml | 13 - app/src/main/res/xml/preference.xml | 17 - 32 files changed, 39 insertions(+), 1796 deletions(-) delete mode 100644 app/src/main/java/com/zeapo/pwdstore/autofill/AutofillActivity.kt delete mode 100644 app/src/main/java/com/zeapo/pwdstore/autofill/AutofillFragment.kt delete mode 100644 app/src/main/java/com/zeapo/pwdstore/autofill/AutofillPreferenceActivity.kt delete mode 100644 app/src/main/java/com/zeapo/pwdstore/autofill/AutofillRecyclerAdapter.kt delete mode 100644 app/src/main/java/com/zeapo/pwdstore/autofill/AutofillService.kt delete mode 100644 app/src/main/res/drawable-nodpi/autofill_ins_1.webp delete mode 100644 app/src/main/res/drawable-nodpi/autofill_ins_2.webp delete mode 100644 app/src/main/res/drawable-nodpi/autofill_ins_3.webp delete mode 100644 app/src/main/res/layout/autofill_instructions.xml delete mode 100644 app/src/main/res/layout/autofill_recycler_view.xml delete mode 100644 app/src/main/res/layout/autofill_row_layout.xml delete mode 100644 app/src/main/res/layout/fragment_autofill.xml delete mode 100644 app/src/main/res/menu/autofill_preference.xml delete mode 100644 app/src/main/res/values-v26/bools.xml delete mode 100644 app/src/main/res/xml/autofill_config.xml diff --git a/CHANGELOG.md b/CHANGELOG.md index f5c80ea5..880db063 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file. ## [Unreleased] +### Changed + +- Accessibility autofill has been removed completely due to being buggy, insecure and lacking in features. Upgrade to Android 8 or preferably later to gain access to our advanced Autofill implementation. + ## [1.13.0] - 2020-10-22 ### Fixed diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index a0a0e464..8c945909 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -11,9 +11,6 @@ - @@ -92,17 +89,6 @@ android:parentActivityName=".PasswordStore" android:theme="@style/NoBackgroundTheme" /> - - - - - - @@ -120,15 +106,6 @@ android:resource="@xml/oreo_autofill_service" /> - - - - private lateinit var oreoAutofillDependencies: List private lateinit var prefsActivity: UserPreference private lateinit var sharedPreferences: SharedPreferences @@ -220,16 +217,6 @@ class UserPreference : AppCompatActivity() { val oreoAutofillDirectoryStructurePreference = findPreference(PreferenceKeys.OREO_AUTOFILL_DIRECTORY_STRUCTURE) val oreoAutofillDefaultUsername = findPreference(PreferenceKeys.OREO_AUTOFILL_DEFAULT_USERNAME) val oreoAutofillCustomPublixSuffixes = findPreference(PreferenceKeys.OREO_AUTOFILL_CUSTOM_PUBLIC_SUFFIXES) - val autoFillAppsPreference = findPreference(PreferenceKeys.AUTOFILL_APPS) - val autoFillDefaultPreference = findPreference(PreferenceKeys.AUTOFILL_DEFAULT) - val autoFillAlwaysShowDialogPreference = findPreference(PreferenceKeys.AUTOFILL_ALWAYS) - val autoFillShowFullNamePreference = findPreference(PreferenceKeys.AUTOFILL_FULL_PATH) - autofillDependencies = listOfNotNull( - autoFillAppsPreference, - autoFillDefaultPreference, - autoFillAlwaysShowDialogPreference, - autoFillShowFullNamePreference, - ) oreoAutofillDependencies = listOfNotNull( oreoAutofillDirectoryStructurePreference, oreoAutofillDefaultUsername, @@ -347,15 +334,11 @@ class UserPreference : AppCompatActivity() { selectExternalGitRepositoryPreference?.onPreferenceChangeListener = resetRepo externalGitRepositoryPreference?.onPreferenceChangeListener = resetRepo - autoFillAppsPreference?.onPreferenceClickListener = ClickListener { - val intent = Intent(prefsActivity, AutofillPreferenceActivity::class.java) - startActivity(intent) - true - } - - autoFillEnablePreference?.onPreferenceClickListener = ClickListener { - onEnableAutofillClick() - true + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + autoFillEnablePreference?.onPreferenceClickListener = ClickListener { + onEnableAutofillClick() + true + } } findPreference(PreferenceKeys.EXPORT_PASSWORDS)?.apply { @@ -474,12 +457,12 @@ class UserPreference : AppCompatActivity() { } private fun updateAutofillSettings() { - val isAccessibilityServiceEnabled = prefsActivity.isAccessibilityServiceEnabled val isAutofillServiceEnabled = prefsActivity.isAutofillServiceEnabled - autoFillEnablePreference?.isChecked = - isAccessibilityServiceEnabled || isAutofillServiceEnabled - autofillDependencies.forEach { - it.isVisible = isAccessibilityServiceEnabled + val isAutofillSupported = prefsActivity.isAutofillServiceSupported + if (!isAutofillSupported) { + autoFillEnablePreference?.isVisible = false + } else { + autoFillEnablePreference?.isChecked = isAutofillServiceEnabled } oreoAutofillDependencies.forEach { it.isVisible = isAutofillServiceEnabled @@ -507,51 +490,40 @@ class UserPreference : AppCompatActivity() { viewSshKeyPreference?.isVisible = SshKey.canShowSshPublicKey } + @RequiresApi(Build.VERSION_CODES.O) private fun onEnableAutofillClick() { - if (prefsActivity.isAccessibilityServiceEnabled) { - startActivity(Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS)) - } else if (prefsActivity.isAutofillServiceEnabled) { + 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 { - val enableOreoAutofill = prefsActivity.isAutofillServiceSupported MaterialAlertDialogBuilder(prefsActivity).run { setTitle(R.string.pref_autofill_enable_title) - if (enableOreoAutofill && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - @SuppressLint("InflateParams") - 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.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" + @SuppressLint("InflateParams") + 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.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) } - setView(layout) - } else { - setView(R.layout.autofill_instructions) - } + "$appLabel: $supportDescription" + } + setView(layout) 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) - } + 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) @@ -670,16 +642,6 @@ class UserPreference : AppCompatActivity() { storeCustomXkpwdDictionaryAction.launch(arrayOf("*/*")) } - private val isAccessibilityServiceEnabled: Boolean - get() { - val am = getSystemService() ?: return false - val runningServices = am - .getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_GENERIC) - return runningServices - .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 diff --git a/app/src/main/java/com/zeapo/pwdstore/autofill/AutofillActivity.kt b/app/src/main/java/com/zeapo/pwdstore/autofill/AutofillActivity.kt deleted file mode 100644 index 3d040f13..00000000 --- a/app/src/main/java/com/zeapo/pwdstore/autofill/AutofillActivity.kt +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved. - * SPDX-License-Identifier: GPL-3.0-only - */ -@file:Suppress("Deprecation") - -package com.zeapo.pwdstore.autofill - -import android.app.PendingIntent -import android.content.Context -import android.content.Intent -import android.content.IntentSender -import android.content.SharedPreferences -import android.os.Bundle -import androidx.appcompat.app.AppCompatActivity -import androidx.core.content.edit -import com.github.ajalt.timberkt.Timber.tag -import com.github.ajalt.timberkt.e -import com.zeapo.pwdstore.PasswordStore -import com.zeapo.pwdstore.utils.splitLines -import org.eclipse.jgit.util.StringUtils - -// blank activity started by service for calling startIntentSenderForResult -class AutofillActivity : AppCompatActivity() { - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - val extras = intent.extras - - if (extras != null && extras.containsKey("pending_intent")) { - try { - val pi = extras.getParcelable("pending_intent") ?: return - startIntentSenderForResult(pi.intentSender, REQUEST_CODE_DECRYPT_AND_VERIFY, null, 0, 0, 0) - } catch (e: IntentSender.SendIntentException) { - tag(AutofillService.Constants.TAG).e(e) { "SendIntentException" } - } - } else if (extras != null && extras.containsKey("pick")) { - val intent = Intent(applicationContext, PasswordStore::class.java) - intent.putExtra("matchWith", true) - startActivityForResult(intent, REQUEST_CODE_PICK) - } else if (extras != null && extras.containsKey("pickMatchWith")) { - val intent = Intent(applicationContext, PasswordStore::class.java) - intent.putExtra("matchWith", true) - startActivityForResult(intent, REQUEST_CODE_PICK_MATCH_WITH) - } - } - - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - finish() // go back to the password field app - when (requestCode) { - REQUEST_CODE_DECRYPT_AND_VERIFY -> if (resultCode == RESULT_OK) { - require(data != null) - AutofillService.instance?.setResultData(data) // report the result to service - } - REQUEST_CODE_PICK -> if (resultCode == RESULT_OK) { - require(data != null) - AutofillService.instance?.setPickedPassword(data.getStringExtra("path")!!) - } - REQUEST_CODE_PICK_MATCH_WITH -> if (resultCode == RESULT_OK) { - require(data != null) - // need to not only decrypt the picked password, but also - // update the "match with" preference - val extras = intent.extras ?: return - val packageName = extras.getString("packageName") - val isWeb = extras.getBoolean("isWeb") - - val path = data.getStringExtra("path") - AutofillService.instance?.setPickedPassword(data.getStringExtra("path")!!) - - val prefs: SharedPreferences - prefs = if (!isWeb) { - applicationContext.getSharedPreferences("autofill", Context.MODE_PRIVATE) - } else { - applicationContext.getSharedPreferences("autofill_web", Context.MODE_PRIVATE) - } - prefs.edit { - when (val preference = prefs.getString(packageName, "")) { - "", "/first", "/never" -> putString(packageName, path) - else -> { - val matches = arrayListOf(*preference!!.trim { it <= ' ' }.splitLines()) - matches.add(path) - val paths = StringUtils.join(matches, "\n") - putString(packageName, paths) - } - } - } - } - } - super.onActivityResult(requestCode, resultCode, data) - } - - companion object { - - const val REQUEST_CODE_DECRYPT_AND_VERIFY = 9913 - const val REQUEST_CODE_PICK = 777 - const val REQUEST_CODE_PICK_MATCH_WITH = 778 - } -} diff --git a/app/src/main/java/com/zeapo/pwdstore/autofill/AutofillFragment.kt b/app/src/main/java/com/zeapo/pwdstore/autofill/AutofillFragment.kt deleted file mode 100644 index 87721ad2..00000000 --- a/app/src/main/java/com/zeapo/pwdstore/autofill/AutofillFragment.kt +++ /dev/null @@ -1,232 +0,0 @@ -/* - * Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved. - * SPDX-License-Identifier: GPL-3.0-only - */ -@file:Suppress("Deprecation") - -package com.zeapo.pwdstore.autofill - -import android.annotation.SuppressLint -import android.app.Dialog -import android.content.Context -import android.content.Intent -import android.content.SharedPreferences -import android.content.pm.PackageManager -import android.net.Uri -import android.os.Bundle -import android.view.View -import android.view.ViewGroup -import android.widget.AdapterView -import android.widget.ArrayAdapter -import android.widget.EditText -import android.widget.ListView -import android.widget.RadioButton -import android.widget.RadioGroup -import androidx.appcompat.app.AlertDialog -import androidx.appcompat.app.AppCompatActivity -import androidx.appcompat.widget.AppCompatTextView -import androidx.core.content.edit -import androidx.fragment.app.DialogFragment -import com.google.android.material.dialog.MaterialAlertDialogBuilder -import com.zeapo.pwdstore.PasswordStore -import com.zeapo.pwdstore.R -import com.zeapo.pwdstore.utils.resolveAttribute -import com.zeapo.pwdstore.utils.splitLines - -class AutofillFragment : DialogFragment() { - - private var adapter: ArrayAdapter? = null - private var isWeb: Boolean = false - - override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { - val builder = MaterialAlertDialogBuilder(requireContext()) - // this fragment is only created from the settings page (AutofillPreferenceActivity) - // need to interact with the recyclerAdapter which is a member of activity - val callingActivity = requireActivity() as AutofillPreferenceActivity - val inflater = callingActivity.layoutInflater - val args = requireNotNull(arguments) - - @SuppressLint("InflateParams") val view = inflater.inflate(R.layout.fragment_autofill, null) - - builder.setView(view) - - val packageName = args.getString("packageName") - val appName = args.getString("appName") - isWeb = args.getBoolean("isWeb") - - // set the dialog icon and title or webURL editText - val iconPackageName: String? - if (!isWeb) { - iconPackageName = packageName - builder.setTitle(appName) - view.findViewById(R.id.webURL).visibility = View.GONE - } else { - val browserIntent = Intent("android.intent.action.VIEW", Uri.parse("http://")) - val resolveInfo = requireContext().packageManager - .resolveActivity(browserIntent, PackageManager.MATCH_DEFAULT_ONLY) - iconPackageName = resolveInfo?.activityInfo?.packageName - builder.setTitle("Website") - (view.findViewById(R.id.webURL) as EditText).setText(packageName - ?: "com.android.browser") - } - try { - if (iconPackageName != null) { - builder.setIcon(callingActivity.packageManager.getApplicationIcon(iconPackageName)) - } - } catch (e: PackageManager.NameNotFoundException) { - e.printStackTrace() - } - - // set up the listview now for items added by button/from preferences - adapter = object : ArrayAdapter(requireContext(), android.R.layout.simple_list_item_1, android.R.id.text1) { - // set text color to black because default is white... - override fun getView(position: Int, convertView: View?, parent: ViewGroup): View { - val textView = super.getView(position, convertView, parent) as AppCompatTextView - textView.setTextColor(requireContext().resolveAttribute(android.R.attr.textColor)) - return textView - } - } - (view.findViewById(R.id.matched) as ListView).adapter = adapter - // delete items by clicking them - (view.findViewById(R.id.matched) as ListView).onItemClickListener = - AdapterView.OnItemClickListener { _, _, position, _ -> - adapter!!.remove(adapter!!.getItem(position)) - } - - // set the existing preference, if any - val prefs: SharedPreferences = if (!isWeb) { - callingActivity.applicationContext.getSharedPreferences("autofill", Context.MODE_PRIVATE) - } else { - callingActivity.applicationContext.getSharedPreferences("autofill_web", Context.MODE_PRIVATE) - } - when (val preference = prefs.getString(packageName, "")) { - "" -> (view.findViewById(R.id.use_default) as RadioButton).toggle() - "/first" -> (view.findViewById(R.id.first) as RadioButton).toggle() - "/never" -> (view.findViewById(R.id.never) as RadioButton).toggle() - else -> { - (view.findViewById(R.id.match) as RadioButton).toggle() - // trim to remove the last blank element - adapter!!.addAll(*preference!!.trim { it <= ' ' }.splitLines()) - } - } - - // add items with the + button - val matchPassword = { _: View -> - (view.findViewById(R.id.match) as RadioButton).toggle() - val intent = Intent(activity, PasswordStore::class.java) - intent.putExtra("matchWith", true) - startActivityForResult(intent, MATCH_WITH) - } - view.findViewById(R.id.matchButton).setOnClickListener(matchPassword) - - // write to preferences when OK clicked - builder.setPositiveButton(R.string.dialog_ok) { _, _ -> } - builder.setNegativeButton(R.string.dialog_cancel, null) - if (isWeb) { - builder.setNeutralButton(R.string.autofill_apps_delete) { _, _ -> - if (callingActivity.recyclerAdapter != null && - packageName != null && packageName != "") { - prefs.edit { - remove(packageName) - callingActivity.recyclerAdapter?.removeWebsite(packageName) - } - } - } - } - return builder.create() - } - - // need to the onClick here for buttons to dismiss dialog only when wanted - override fun onStart() { - super.onStart() - val ad = dialog as? AlertDialog - if (ad != null) { - val positiveButton = ad.getButton(Dialog.BUTTON_POSITIVE) - positiveButton.setOnClickListener { - val callingActivity = requireActivity() as AutofillPreferenceActivity - val dialog = requireDialog() - val args = requireNotNull(arguments) - - val prefs: SharedPreferences = if (!isWeb) { - callingActivity.applicationContext.getSharedPreferences("autofill", Context.MODE_PRIVATE) - } else { - callingActivity.applicationContext.getSharedPreferences("autofill_web", Context.MODE_PRIVATE) - } - - var packageName = args.getString("packageName", "") - if (isWeb) { - // handle some errors and don't dismiss the dialog - val webURL = dialog.findViewById(R.id.webURL) - - packageName = webURL.text.toString() - - if (packageName == "") { - webURL.error = "URL cannot be blank" - return@setOnClickListener - } - val oldPackageName = args.getString("packageName", "") - if (oldPackageName != packageName && prefs.all.containsKey(packageName)) { - webURL.error = "URL already exists" - return@setOnClickListener - } - } - - // write to preferences accordingly - prefs.edit { - val radioGroup = dialog.findViewById(R.id.autofill_radiogroup) - when (radioGroup.checkedRadioButtonId) { - R.id.use_default -> if (!isWeb) { - remove(packageName) - } else { - putString(packageName, "") - } - R.id.first -> putString(packageName, "/first") - R.id.never -> putString(packageName, "/never") - else -> { - val paths = StringBuilder() - for (i in 0 until adapter!!.count) { - paths.append(adapter!!.getItem(i)) - if (i != adapter!!.count) { - paths.append("\n") - } - } - putString(packageName, paths.toString()) - } - } - } - - // notify the recycler adapter if it is loaded - callingActivity.recyclerAdapter?.apply { - val position: Int - if (!isWeb) { - val appName = args.getString("appName", "") - position = getPosition(appName) - notifyItemChanged(position) - } else { - position = getPosition(packageName) - when (val oldPackageName = args.getString("packageName", "")) { - packageName -> notifyItemChanged(position) - "" -> addWebsite(packageName) - else -> { - prefs.edit { remove(oldPackageName) } - updateWebsite(oldPackageName, packageName) - } - } - } - } - dismiss() - } - } - } - - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - if (resultCode == AppCompatActivity.RESULT_OK && data != null) { - adapter!!.add(data.getStringExtra("path")) - } - } - - companion object { - - private const val MATCH_WITH = 777 - } -} diff --git a/app/src/main/java/com/zeapo/pwdstore/autofill/AutofillPreferenceActivity.kt b/app/src/main/java/com/zeapo/pwdstore/autofill/AutofillPreferenceActivity.kt deleted file mode 100644 index feef77a5..00000000 --- a/app/src/main/java/com/zeapo/pwdstore/autofill/AutofillPreferenceActivity.kt +++ /dev/null @@ -1,159 +0,0 @@ -/* - * Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved. - * SPDX-License-Identifier: GPL-3.0-only - */ -@file:Suppress("Deprecation") - -package com.zeapo.pwdstore.autofill - -import android.content.Context -import android.content.Intent -import android.content.pm.PackageManager -import android.os.AsyncTask -import android.os.Bundle -import android.view.Menu -import android.view.MenuItem -import android.view.View -import androidx.appcompat.app.AppCompatActivity -import androidx.appcompat.widget.SearchView -import androidx.recyclerview.widget.DividerItemDecoration -import androidx.recyclerview.widget.LinearLayoutManager -import com.google.android.material.floatingactionbutton.FloatingActionButton -import com.zeapo.pwdstore.R -import com.zeapo.pwdstore.databinding.AutofillRecyclerViewBinding -import com.zeapo.pwdstore.utils.viewBinding -import java.lang.ref.WeakReference -import java.util.ArrayList -import me.zhanghai.android.fastscroll.FastScrollerBuilder - -class AutofillPreferenceActivity : AppCompatActivity() { - - private val binding by viewBinding(AutofillRecyclerViewBinding::inflate) - internal var recyclerAdapter: AutofillRecyclerAdapter? = null // let fragment have access - private var pm: PackageManager? = null - - private var recreate: Boolean = false // flag for action on up press; origin autofill dialog? different act - - public override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setContentView(binding.root) - - val layoutManager = LinearLayoutManager(this) - with(binding) { - autofillRecycler.layoutManager = layoutManager - autofillRecycler.addItemDecoration(DividerItemDecoration(this@AutofillPreferenceActivity, DividerItemDecoration.VERTICAL)) - FastScrollerBuilder(autofillRecycler).build() - } - - pm = packageManager - - PopulateTask(this).execute() - - // if the preference activity was started from the autofill dialog - recreate = false - val extras = intent.extras - if (extras != null) { - recreate = true - - showDialog(extras.getString("packageName"), extras.getString("appName"), extras.getBoolean("isWeb")) - } - - title = "Autofill Apps" - - val fab = findViewById(R.id.fab) - fab.setOnClickListener { showDialog("", "", true) } - } - - override fun onCreateOptionsMenu(menu: Menu): Boolean { - // Inflate the menu; this adds items to the action bar if it is present. - menuInflater.inflate(R.menu.autofill_preference, menu) - val searchItem = menu.findItem(R.id.action_search) - val searchView = searchItem.actionView as SearchView - - searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener { - override fun onQueryTextSubmit(s: String): Boolean { - return false - } - - override fun onQueryTextChange(s: String): Boolean { - if (recyclerAdapter != null) { - recyclerAdapter!!.filter(s) - } - return true - } - }) - - return super.onCreateOptionsMenu(menu) - } - - override fun onOptionsItemSelected(item: MenuItem): Boolean { - // in service, we CLEAR_TASK. then we set the recreate flag. - // something of a hack, but w/o CLEAR_TASK, behaviour was unpredictable - return if (item.itemId == android.R.id.home) { - onBackPressed() - true - } else super.onOptionsItemSelected(item) - } - - fun showDialog(packageName: String?, appName: String?, isWeb: Boolean) { - val df = AutofillFragment() - val args = Bundle() - args.putString("packageName", packageName) - args.putString("appName", appName) - args.putBoolean("isWeb", isWeb) - df.arguments = args - df.show(supportFragmentManager, "autofill_dialog") - } - - companion object { - private class PopulateTask(activity: AutofillPreferenceActivity) : AsyncTask() { - - val weakReference = WeakReference(activity) - - override fun onPreExecute() { - weakReference.get()?.apply { - runOnUiThread { findViewById(R.id.progress_bar).visibility = View.VISIBLE } - } - } - - override fun doInBackground(vararg params: Void): Void? { - val pm = weakReference.get()?.pm ?: return null - val intent = Intent(Intent.ACTION_MAIN) - intent.addCategory(Intent.CATEGORY_LAUNCHER) - val allAppsResolveInfo = pm.queryIntentActivities(intent, 0) - val allApps = ArrayList() - - for (app in allAppsResolveInfo) { - allApps.add(AutofillRecyclerAdapter.AppInfo(app.activityInfo.packageName, app.loadLabel(pm).toString(), false, app.loadIcon(pm))) - } - - val prefs = weakReference.get()?.getSharedPreferences("autofill_web", Context.MODE_PRIVATE) - val prefsMap = prefs!!.all - for (key in prefsMap.keys) { - try { - allApps.add(AutofillRecyclerAdapter.AppInfo(key, key, true, pm.getApplicationIcon("com.android.browser"))) - } catch (e: PackageManager.NameNotFoundException) { - allApps.add(AutofillRecyclerAdapter.AppInfo(key, key, true, null)) - } - } - weakReference.get()?.recyclerAdapter = AutofillRecyclerAdapter(allApps, weakReference.get()!!) - return null - } - - override fun onPostExecute(ignored: Void?) { - weakReference.get()?.apply { - runOnUiThread { - with(binding) { - progressBar.visibility = View.GONE - autofillRecycler.adapter = recyclerAdapter - val extras = intent.extras - if (extras != null) { - autofillRecycler.scrollToPosition(recyclerAdapter!!.getPosition(extras.getString("appName")!!)) - } - } - } - } - } - } - } -} diff --git a/app/src/main/java/com/zeapo/pwdstore/autofill/AutofillRecyclerAdapter.kt b/app/src/main/java/com/zeapo/pwdstore/autofill/AutofillRecyclerAdapter.kt deleted file mode 100644 index ac53d32a..00000000 --- a/app/src/main/java/com/zeapo/pwdstore/autofill/AutofillRecyclerAdapter.kt +++ /dev/null @@ -1,180 +0,0 @@ -/* - * Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved. - * SPDX-License-Identifier: GPL-3.0-only - */ -@file:Suppress("Deprecation") - -package com.zeapo.pwdstore.autofill - -import android.content.Context -import android.content.SharedPreferences -import android.content.pm.PackageManager -import android.graphics.drawable.Drawable -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.appcompat.widget.AppCompatImageView -import androidx.appcompat.widget.AppCompatTextView -import androidx.recyclerview.widget.RecyclerView -import androidx.recyclerview.widget.SortedList -import androidx.recyclerview.widget.SortedListAdapterCallback -import com.zeapo.pwdstore.R -import com.zeapo.pwdstore.utils.splitLines -import java.util.ArrayList -import java.util.Locale -import me.zhanghai.android.fastscroll.PopupTextProvider - -internal class AutofillRecyclerAdapter( - allApps: List, - private val activity: AutofillPreferenceActivity -) : RecyclerView.Adapter(), PopupTextProvider { - - private val apps: SortedList - private val allApps: ArrayList // for filtering, maintain a list of all - private var browserIcon: Drawable? = null - - init { - val callback = object : SortedListAdapterCallback(this) { - // don't take into account secondary text. This is good enough - // for the limited add/remove usage for websites - override fun compare(o1: AppInfo, o2: AppInfo): Int { - return o1.appName.toLowerCase(Locale.ROOT).compareTo(o2.appName.toLowerCase(Locale.ROOT)) - } - - override fun areContentsTheSame(oldItem: AppInfo, newItem: AppInfo): Boolean { - return oldItem.appName == newItem.appName - } - - override fun areItemsTheSame(item1: AppInfo, item2: AppInfo): Boolean { - return item1.appName == item2.appName - } - } - apps = SortedList(AppInfo::class.java, callback) - apps.addAll(allApps) - this.allApps = ArrayList(allApps) - try { - browserIcon = activity.packageManager.getApplicationIcon("com.android.browser") - } catch (e: PackageManager.NameNotFoundException) { - e.printStackTrace() - } - } - - override fun getPopupText(position: Int): String { - return allApps[position].appName[0].toString().toUpperCase(Locale.getDefault()) - } - - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { - val v = LayoutInflater.from(parent.context) - .inflate(R.layout.autofill_row_layout, parent, false) - return ViewHolder(v) - } - - override fun onBindViewHolder(holder: ViewHolder, position: Int) { - val app = apps.get(position) - holder.packageName = app.packageName - holder.appName = app.appName - holder.isWeb = app.isWeb - - holder.icon.setImageDrawable(app.icon) - holder.name.text = app.appName - - holder.secondary.visibility = View.VISIBLE - - val prefs: SharedPreferences - prefs = if (app.appName != app.packageName) { - activity.applicationContext.getSharedPreferences("autofill", Context.MODE_PRIVATE) - } else { - activity.applicationContext.getSharedPreferences("autofill_web", Context.MODE_PRIVATE) - } - when (val preference = prefs.getString(holder.packageName, "")) { - "" -> { - holder.secondary.visibility = View.GONE - holder.view.setBackgroundResource(0) - } - "/first" -> holder.secondary.setText(R.string.autofill_apps_first) - "/never" -> holder.secondary.setText(R.string.autofill_apps_never) - else -> { - holder.secondary.setText(R.string.autofill_apps_match) - holder.secondary.append(" " + preference!!.splitLines()[0]) - if (preference.trim { it <= ' ' }.splitLines().size - 1 > 0) { - holder.secondary.append(" and " + - (preference.trim { it <= ' ' }.splitLines().size - 1) + " more") - } - } - } - } - - override fun getItemCount(): Int { - return apps.size() - } - - fun getPosition(appName: String): Int { - return apps.indexOf(AppInfo(null, appName, false, null)) - } - - // for websites, URL = packageName == appName - fun addWebsite(packageName: String) { - apps.add(AppInfo(packageName, packageName, true, browserIcon)) - allApps.add(AppInfo(packageName, packageName, true, browserIcon)) - } - - fun removeWebsite(packageName: String) { - apps.remove(AppInfo(null, packageName, false, null)) - allApps.remove(AppInfo(null, packageName, false, null)) // compare with equals - } - - fun updateWebsite(oldPackageName: String, packageName: String) { - apps.updateItemAt(getPosition(oldPackageName), AppInfo(packageName, packageName, true, browserIcon)) - allApps.remove(AppInfo(null, oldPackageName, false, null)) // compare with equals - allApps.add(AppInfo(null, packageName, false, null)) - } - - fun filter(s: String) { - if (s.isEmpty()) { - apps.addAll(allApps) - return - } - apps.beginBatchedUpdates() - for (app in allApps) { - if (app.appName.toLowerCase(Locale.ROOT).contains(s.toLowerCase(Locale.ROOT))) { - apps.add(app) - } else { - apps.remove(app) - } - } - apps.endBatchedUpdates() - } - - internal class AppInfo(var packageName: String?, var appName: String, var isWeb: Boolean, var icon: Drawable?) { - - override fun equals(other: Any?): Boolean { - return other is AppInfo && this.appName == other.appName - } - - override fun hashCode(): Int { - var result = packageName?.hashCode() ?: 0 - result = 31 * result + appName.hashCode() - result = 31 * result + isWeb.hashCode() - result = 31 * result + (icon?.hashCode() ?: 0) - return result - } - } - - internal inner class ViewHolder(var view: View) : RecyclerView.ViewHolder(view), View.OnClickListener { - - var name: AppCompatTextView = view.findViewById(R.id.app_name) - var icon: AppCompatImageView = view.findViewById(R.id.app_icon) - var secondary: AppCompatTextView = view.findViewById(R.id.secondary_text) - var packageName: String? = null - var appName: String? = null - var isWeb: Boolean = false - - init { - view.setOnClickListener(this) - } - - override fun onClick(v: View) { - activity.showDialog(packageName, appName, isWeb) - } - } -} diff --git a/app/src/main/java/com/zeapo/pwdstore/autofill/AutofillService.kt b/app/src/main/java/com/zeapo/pwdstore/autofill/AutofillService.kt deleted file mode 100644 index 4b40d8a4..00000000 --- a/app/src/main/java/com/zeapo/pwdstore/autofill/AutofillService.kt +++ /dev/null @@ -1,584 +0,0 @@ -/* - * Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved. - * SPDX-License-Identifier: GPL-3.0-only - */ -@file:Suppress("Deprecation") - -package com.zeapo.pwdstore.autofill - -import android.accessibilityservice.AccessibilityService -import android.app.PendingIntent -import android.content.Context -import android.content.Intent -import android.content.SharedPreferences -import android.content.pm.ApplicationInfo -import android.content.pm.PackageManager -import android.net.Uri -import android.os.Build -import android.provider.Settings -import android.view.ViewGroup.LayoutParams.WRAP_CONTENT -import android.view.Window -import android.view.WindowManager -import android.view.accessibility.AccessibilityEvent -import android.view.accessibility.AccessibilityNodeInfo -import android.view.accessibility.AccessibilityWindowInfo -import android.widget.Toast -import androidx.appcompat.app.AlertDialog -import androidx.core.os.bundleOf -import androidx.preference.PreferenceManager -import com.github.ajalt.timberkt.Timber.tag -import com.github.ajalt.timberkt.e -import com.github.ajalt.timberkt.i -import com.google.android.material.dialog.MaterialAlertDialogBuilder -import com.zeapo.pwdstore.R -import com.zeapo.pwdstore.model.PasswordEntry -import com.zeapo.pwdstore.utils.PasswordRepository -import com.zeapo.pwdstore.utils.PreferenceKeys -import com.zeapo.pwdstore.utils.splitLines -import java.io.ByteArrayOutputStream -import java.io.File -import java.io.IOException -import java.io.InputStream -import java.io.UnsupportedEncodingException -import java.net.MalformedURLException -import java.net.URL -import java.util.ArrayList -import java.util.Locale -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.cancel -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext -import me.msfjarvis.openpgpktx.util.OpenPgpApi -import me.msfjarvis.openpgpktx.util.OpenPgpServiceConnection -import org.openintents.openpgp.IOpenPgpService2 -import org.openintents.openpgp.OpenPgpError - -class AutofillService : AccessibilityService(), CoroutineScope by CoroutineScope(Dispatchers.Default) { - - private var serviceConnection: OpenPgpServiceConnection? = null - private var settings: SharedPreferences? = null - private var info: AccessibilityNodeInfo? = null // the original source of the event (the edittext field) - private var items: ArrayList = arrayListOf() // password choices - private var lastWhichItem: Int = 0 - private var dialog: AlertDialog? = null - private var window: AccessibilityWindowInfo? = null - private var resultData: Intent? = null // need the intent which contains results from user interaction - private var packageName: CharSequence? = null - private var ignoreActionFocus = false - private var webViewTitle: String? = null - private var webViewURL: String? = null - private var lastPassword: PasswordEntry? = null - private var lastPasswordMaxDate: Long = 0 - - fun setResultData(data: Intent) { - resultData = data - } - - fun setPickedPassword(path: String) { - items.add(File("${PasswordRepository.getRepositoryDirectory()}/$path.gpg")) - bindDecryptAndVerify() - } - - override fun onCreate() { - super.onCreate() - instance = this - } - - override fun onDestroy() { - super.onDestroy() - instance = null - cancel() - } - - override fun onServiceConnected() { - super.onServiceConnected() - serviceConnection = OpenPgpServiceConnection(this@AutofillService, "org.sufficientlysecure.keychain") - serviceConnection!!.bindToService() - settings = PreferenceManager.getDefaultSharedPreferences(this) - } - - override fun onAccessibilityEvent(event: AccessibilityEvent) { - // remove stored password from cache - if (lastPassword != null && System.currentTimeMillis() > lastPasswordMaxDate) { - lastPassword = null - } - - // if returning to the source app from a successful AutofillActivity - if (event.eventType == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED && - event.packageName != null && event.packageName == packageName && - resultData != null) { - bindDecryptAndVerify() - } - - // look for webView and trigger accessibility events if window changes - // or if page changes in chrome - if (event.eventType == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED || (event.eventType == AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED && - event.packageName != null && - (event.packageName == "com.android.chrome" || event.packageName == "com.android.browser"))) { - // there is a chance for getRootInActiveWindow() to return null at any time. save it. - try { - val root = rootInActiveWindow - webViewTitle = searchWebView(root) - webViewURL = null - if (webViewTitle != null) { - var nodes = root.findAccessibilityNodeInfosByViewId("com.android.chrome:id/url_bar") - if (nodes.isEmpty()) { - nodes = root.findAccessibilityNodeInfosByViewId("com.android.browser:id/url") - } - for (node in nodes) - if (node.text != null) { - try { - webViewURL = URL(node.text.toString()).host - } catch (e: MalformedURLException) { - if (e.toString().contains("Protocol not found")) { - try { - webViewURL = URL("http://" + node.text.toString()).host - } catch (ignored: MalformedURLException) { - } - } - } - } - } - } catch (e: Exception) { - // sadly we were unable to access the data we wanted - return - } - } - - // nothing to do if field is keychain app or system ui - if (event.eventType == AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED || - event.packageName != null && event.packageName == "org.sufficientlysecure.keychain" || - event.packageName != null && event.packageName == "com.android.systemui") { - dismissDialog() - return - } - - if (!event.isPassword) { - if (lastPassword != null && event.eventType == AccessibilityEvent.TYPE_VIEW_FOCUSED && event.source.isEditable) { - showPasteUsernameDialog(event.source, lastPassword!!) - return - } else { - // nothing to do if not password field focus - dismissDialog() - return - } - } - - if (dialog != null && dialog!!.isShowing) { - // the current dialog must belong to this window; ignore clicks on this password field - // why handle clicks at all then? some cases e.g. Paypal there is no initial focus event - if (event.eventType == AccessibilityEvent.TYPE_VIEW_CLICKED) { - return - } - // if it was not a click, the field was refocused or another field was focused; recreate - dialog!!.dismiss() - dialog = null - } - - // ignore the ACTION_FOCUS from decryptAndVerify otherwise dialog will appear after Fill - if (ignoreActionFocus) { - ignoreActionFocus = false - return - } - - // need to request permission before attempting to draw dialog - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !Settings.canDrawOverlays(this)) { - val intent = Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, - Uri.parse("package:" + getPackageName())) - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - startActivity(intent) - return - } - - // we are now going to attempt to fill, save AccessibilityNodeInfo for later in decryptAndVerify - // (there should be a proper way to do this, although this seems to work 90% of the time) - info = event.source - if (info == null) return - - // save the dialog's corresponding window so we can use getWindows() in dismissDialog - window = info!!.window - - val packageName: String - val appName: String - val isWeb: Boolean - - // Match with the app if a webview was not found or one was found but - // there's no title or url to go by - if (webViewTitle == null || webViewTitle == "" && webViewURL == null) { - if (info!!.packageName == null) return - packageName = info!!.packageName.toString() - - // get the app name and find a corresponding password - val packageManager = packageManager - val applicationInfo: ApplicationInfo? = try { - packageManager.getApplicationInfo(event.packageName.toString(), 0) - } catch (e: PackageManager.NameNotFoundException) { - null - } - - appName = (if (applicationInfo != null) packageManager.getApplicationLabel(applicationInfo) else "").toString() - - isWeb = false - - setAppMatchingPasswords(appName, packageName) - } else { - // now we may have found a title but webViewURL could be null - // we set packagename so that we can find the website setting entry - packageName = setWebMatchingPasswords(webViewTitle!!, webViewURL) - appName = packageName - isWeb = true - } - - // if autofill_always checked, show dialog even if no matches (automatic - // or otherwise) - if (items.isEmpty() && !settings!!.getBoolean(PreferenceKeys.AUTOFILL_ALWAYS, false)) { - return - } - showSelectPasswordDialog(packageName, appName, isWeb) - } - - private fun searchWebView(source: AccessibilityNodeInfo?, depth: Int = 10): String? { - if (source == null || depth == 0) { - return null - } - for (i in 0 until source.childCount) { - val u = source.getChild(i) ?: continue - if (u.className != null && u.className == "android.webkit.WebView") { - return if (u.contentDescription != null) { - u.contentDescription.toString() - } else "" - } - val webView = searchWebView(u, depth - 1) - if (webView != null) { - return webView - } - u.recycle() - } - return null - } - - // dismiss the dialog if the window has changed - private fun dismissDialog() { - val dismiss = !windows.contains(window) - if (dismiss && dialog != null && dialog!!.isShowing) { - dialog!!.dismiss() - dialog = null - } - } - - private fun setWebMatchingPasswords(webViewTitle: String, webViewURL: String?): String { - // Return the URL needed to open the corresponding Settings. - var settingsURL = webViewURL - - // if autofill_default is checked and prefs.getString DNE, 'Automatically match with password'/"first" otherwise "never" - val defValue = if (settings!!.getBoolean(PreferenceKeys.AUTOFILL_DEFAULT, true)) "/first" else "/never" - val prefs: SharedPreferences = getSharedPreferences("autofill_web", Context.MODE_PRIVATE) - var preference: String - - preference = defValue - if (webViewURL != null) { - val webViewUrlLowerCase = webViewURL.toLowerCase(Locale.ROOT) - val prefsMap = prefs.all - for (key in prefsMap.keys) { - // for websites unlike apps there can be blank preference of "" which - // means use default, so ignore it. - val value = prefs.getString(key, null) - val keyLowerCase = key.toLowerCase(Locale.ROOT) - if (value != null && value != "" && - (webViewUrlLowerCase.contains(keyLowerCase) || keyLowerCase.contains(webViewUrlLowerCase))) { - preference = value - settingsURL = key - } - } - } - - when (preference) { - "/first" -> { - if (!PasswordRepository.isInitialized) { - PasswordRepository.initialize() - } - items = searchPasswords(PasswordRepository.getRepositoryDirectory(), webViewTitle) - } - "/never" -> items = ArrayList() - else -> getPreferredPasswords(preference) - } - - return settingsURL!! - } - - private fun setAppMatchingPasswords(appName: String, packageName: String) { - // if autofill_default is checked and prefs.getString DNE, 'Automatically match with password'/"first" otherwise "never" - val defValue = if (settings!!.getBoolean(PreferenceKeys.AUTOFILL_DEFAULT, true)) "/first" else "/never" - val prefs: SharedPreferences = getSharedPreferences("autofill", Context.MODE_PRIVATE) - val preference: String? - - preference = prefs.getString(packageName, defValue) ?: defValue - - when (preference) { - "/first" -> { - if (!PasswordRepository.isInitialized) { - PasswordRepository.initialize() - } - items = searchPasswords(PasswordRepository.getRepositoryDirectory(), appName) - } - "/never" -> items = ArrayList() - else -> getPreferredPasswords(preference) - } - } - - // Put the newline separated list of passwords from the SharedPreferences - // file into the items list. - private fun getPreferredPasswords(preference: String) { - if (!PasswordRepository.isInitialized) { - PasswordRepository.initialize() - } - val preferredPasswords = preference.splitLines() - items = ArrayList() - for (password in preferredPasswords) { - val path = PasswordRepository.getRepositoryDirectory().toString() + "/" + password + ".gpg" - if (File(path).exists()) { - items.add(File(path)) - } - } - } - - private fun searchPasswords(path: File?, appName: String): ArrayList { - val passList = PasswordRepository.getFilesList(path) - - if (passList.size == 0) return ArrayList() - - val items = ArrayList() - - for (file in passList) { - if (file.isFile) { - if (!file.isHidden && appName.toLowerCase(Locale.ROOT).contains(file.name.toLowerCase(Locale.ROOT).replace(".gpg", ""))) { - items.add(file) - } - } else { - if (!file.isHidden) { - items.addAll(searchPasswords(file, appName)) - } - } - } - return items - } - - private fun showPasteUsernameDialog(node: AccessibilityNodeInfo, password: PasswordEntry) { - if (dialog != null) { - dialog!!.dismiss() - dialog = null - } - - val builder = MaterialAlertDialogBuilder(this, R.style.AppTheme_Dialog) - builder.setNegativeButton(R.string.dialog_cancel) { _, _ -> - dialog!!.dismiss() - dialog = null - } - builder.setPositiveButton(R.string.autofill_paste) { _, _ -> - pasteText(node, password.username) - dialog!!.dismiss() - dialog = null - } - builder.setMessage(getString(R.string.autofill_paste_username, password.username)) - - dialog = builder.create() - require(dialog != null) { "Dialog should not be null at this stage" } - dialog!!.window!!.apply { - setDialogType(this) - addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE) - clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND) - } - dialog!!.show() - } - - private fun showSelectPasswordDialog(packageName: String, appName: String, isWeb: Boolean) { - if (dialog != null) { - dialog!!.dismiss() - dialog = null - } - - val builder = MaterialAlertDialogBuilder(this, R.style.AppTheme_Dialog) - builder.setNegativeButton(R.string.dialog_cancel) { _, _ -> - dialog!!.dismiss() - dialog = null - } - builder.setNeutralButton("Settings") { _, _ -> - // TODO make icon? gear? - // the user will have to return to the app themselves. - val intent = Intent(this@AutofillService, AutofillPreferenceActivity::class.java) - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK) - intent.putExtra("packageName", packageName) - intent.putExtra("appName", appName) - intent.putExtra("isWeb", isWeb) - startActivity(intent) - } - - // populate the dialog items, always with pick + pick and match. Could - // make it optional (or make height a setting for the same effect) - val itemNames = arrayOfNulls(items.size + 2) - val passwordDirectory = PasswordRepository.getRepositoryDirectory().toString() - val autofillFullPath = settings!!.getBoolean(PreferenceKeys.AUTOFILL_FULL_PATH, false) - for (i in items.indices) { - if (autofillFullPath) { - itemNames[i] = items[i].path.replace(".gpg", "") - .replace("$passwordDirectory/", "") - } else { - itemNames[i] = items[i].name.replace(".gpg", "") - } - } - itemNames[items.size] = getString(R.string.autofill_pick) - itemNames[items.size + 1] = getString(R.string.autofill_pick_and_match) - builder.setItems(itemNames) { _, which -> - lastWhichItem = which - when { - which < items.size -> bindDecryptAndVerify() - which == items.size -> { - val intent = Intent(this@AutofillService, AutofillActivity::class.java) - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK) - intent.putExtra("pick", true) - startActivity(intent) - } - else -> { - lastWhichItem-- // will add one element to items, so lastWhichItem=items.size()+1 - val intent = Intent(this@AutofillService, AutofillActivity::class.java) - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK) - intent.putExtra("pickMatchWith", true) - intent.putExtra("packageName", packageName) - intent.putExtra("isWeb", isWeb) - startActivity(intent) - } - } - } - - dialog = builder.create() - dialog?.window?.apply { - setDialogType(this) - val density = context.resources.displayMetrics.density - addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE) - setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE) - clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND) - // arbitrary non-annoying size - setLayout((340 * density).toInt(), WRAP_CONTENT) - } - dialog?.show() - } - - @Suppress("DEPRECATION") - private fun setDialogType(window: Window) { - window.setType(if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) - WindowManager.LayoutParams.TYPE_SYSTEM_ALERT - else - WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY - ) - } - - override fun onInterrupt() {} - - private fun bindDecryptAndVerify() { - if (serviceConnection!!.service == null) { - // the service was disconnected, need to bind again - // give it a listener and in the callback we will decryptAndVerify - serviceConnection = OpenPgpServiceConnection(this@AutofillService, "org.sufficientlysecure.keychain", OnBoundListener()) - serviceConnection!!.bindToService() - } else { - decryptAndVerify() - } - } - - private fun decryptAndVerify() = launch { - packageName = info!!.packageName - val data: Intent - if (resultData == null) { - data = Intent() - data.action = OpenPgpApi.ACTION_DECRYPT_VERIFY - } else { - data = resultData!! - resultData = null - } - - var inputStream: InputStream? = null - withContext(Dispatchers.IO) { - try { - inputStream = items[lastWhichItem].inputStream() - } catch (e: IOException) { - e.printStackTrace() - cancel("", e) - } - } - - val os = ByteArrayOutputStream() - - val api = OpenPgpApi(this@AutofillService, serviceConnection!!.service!!) - val result = api.executeApi(data, inputStream, os) - when (result?.getIntExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR)) { - OpenPgpApi.RESULT_CODE_SUCCESS -> { - try { - var entry: PasswordEntry? = null - withContext(Dispatchers.IO) { - entry = PasswordEntry(os) - } - withContext(Dispatchers.Main) { pasteText(info!!, entry?.password) } - // save password entry for pasting the username as well - if (entry?.hasUsername() == true) { - lastPassword = entry - val ttl = Integer.parseInt(settings!!.getString(PreferenceKeys.GENERAL_SHOW_TIME, "45")!!) - withContext(Dispatchers.Main) { Toast.makeText(applicationContext, getString(R.string.autofill_toast_username, ttl), Toast.LENGTH_LONG).show() } - lastPasswordMaxDate = System.currentTimeMillis() + ttl * 1000L - } - } catch (e: UnsupportedEncodingException) { - tag(Constants.TAG).e(e) - } - } - OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED -> { - tag("PgpHandler").i { "RESULT_CODE_USER_INTERACTION_REQUIRED" } - val pi = result.getParcelableExtra(OpenPgpApi.RESULT_INTENT) - // need to start a blank activity to call startIntentSenderForResult - val intent = Intent(applicationContext, AutofillActivity::class.java) - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK) - intent.putExtra("pending_intent", pi) - startActivity(intent) - } - OpenPgpApi.RESULT_CODE_ERROR -> { - val error = result.getParcelableExtra(OpenPgpApi.RESULT_ERROR) - if (error != null) { - withContext(Dispatchers.Main) { Toast.makeText(applicationContext, "Error from OpenKeyChain : ${error.message}", Toast.LENGTH_LONG).show() } - tag(Constants.TAG).e { "onError getErrorId: ${error.errorId}" } - tag(Constants.TAG).e { "onError getMessage: ${error.message}" } - } - } - } - } - - private fun pasteText(node: AccessibilityNodeInfo, text: String?) { - // if the user focused on something else, take focus back - // but this will open another dialog...hack to ignore this - // & need to ensure performAction correct (i.e. what is info now?) - ignoreActionFocus = node.performAction(AccessibilityNodeInfo.ACTION_FOCUS) - val args = bundleOf(Pair(AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, text)) - node.performAction(AccessibilityNodeInfo.ACTION_SET_TEXT, args) - node.recycle() - } - - internal object Constants { - - const val TAG = "Keychain" - } - - private inner class OnBoundListener : OpenPgpServiceConnection.OnBound { - - override fun onBound(service: IOpenPgpService2) { - decryptAndVerify() - } - - override fun onError(e: Exception) { - e.printStackTrace() - } - } - - companion object { - - var instance: AutofillService? = null - private set - } -} 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 33130f78..536a0c19 100644 --- a/app/src/main/java/com/zeapo/pwdstore/utils/PreferenceKeys.kt +++ b/app/src/main/java/com/zeapo/pwdstore/utils/PreferenceKeys.kt @@ -9,11 +9,7 @@ object PreferenceKeys { const val APP_THEME = "app_theme" const val APP_VERSION = "app_version" - const val AUTOFILL_APPS = "autofill_apps" - const val AUTOFILL_ALWAYS = "autofill_always" - const val AUTOFILL_DEFAULT = "autofill_default" const val AUTOFILL_ENABLE = "autofill_enable" - const val AUTOFILL_FULL_PATH = "autofill_full_path" const val BIOMETRIC_AUTH = "biometric_auth" const val CLEAR_CLIPBOARD_20X = "clear_clipboard_20x" const val CLEAR_SAVED_PASS = "clear_saved_pass" diff --git a/app/src/main/res/drawable-nodpi/autofill_ins_1.webp b/app/src/main/res/drawable-nodpi/autofill_ins_1.webp deleted file mode 100644 index accd87f7..00000000 Binary files a/app/src/main/res/drawable-nodpi/autofill_ins_1.webp and /dev/null differ diff --git a/app/src/main/res/drawable-nodpi/autofill_ins_2.webp b/app/src/main/res/drawable-nodpi/autofill_ins_2.webp deleted file mode 100644 index f84b2025..00000000 Binary files a/app/src/main/res/drawable-nodpi/autofill_ins_2.webp and /dev/null differ diff --git a/app/src/main/res/drawable-nodpi/autofill_ins_3.webp b/app/src/main/res/drawable-nodpi/autofill_ins_3.webp deleted file mode 100644 index 23a63151..00000000 Binary files a/app/src/main/res/drawable-nodpi/autofill_ins_3.webp and /dev/null differ diff --git a/app/src/main/res/layout/autofill_instructions.xml b/app/src/main/res/layout/autofill_instructions.xml deleted file mode 100644 index e5f16a61..00000000 --- a/app/src/main/res/layout/autofill_instructions.xml +++ /dev/null @@ -1,64 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/layout/autofill_recycler_view.xml b/app/src/main/res/layout/autofill_recycler_view.xml deleted file mode 100644 index aa7b17fc..00000000 --- a/app/src/main/res/layout/autofill_recycler_view.xml +++ /dev/null @@ -1,43 +0,0 @@ - - - - - - - - - - diff --git a/app/src/main/res/layout/autofill_row_layout.xml b/app/src/main/res/layout/autofill_row_layout.xml deleted file mode 100644 index 5d3cad95..00000000 --- a/app/src/main/res/layout/autofill_row_layout.xml +++ /dev/null @@ -1,41 +0,0 @@ - - - - - - - - - - - - - - diff --git a/app/src/main/res/layout/fragment_autofill.xml b/app/src/main/res/layout/fragment_autofill.xml deleted file mode 100644 index bb1d48aa..00000000 --- a/app/src/main/res/layout/fragment_autofill.xml +++ /dev/null @@ -1,84 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/menu/autofill_preference.xml b/app/src/main/res/menu/autofill_preference.xml deleted file mode 100644 index bd3be216..00000000 --- a/app/src/main/res/menu/autofill_preference.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index 05f1dd91..2dfa86e4 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -93,14 +93,6 @@ أيقونة التطبيق - إستخدم الإعداد الإفتراضي - طابق مع ... - طابق مع - لا تطابق أبدا - حذف - إختر - إختر و طابق مع ... - إلصاق كلمة السر الجديدة diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index 97c715b5..6b4687b6 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -79,9 +79,6 @@ Rekurzivní filtrování Rekurzivní hledání hesel v aktuálním adresáři. Povolit automatické vyplňování - Ťukni na OK pro zobrazení nastavení přístupnosti. Tam poté ťuknout mezi službami na Password Store a přepínačem lze zapnout nebo vypnout. - Jakmile bude služba zapnuta, po kliknutí na pole hesla bude zobrazeno dialogové okno pokud je pro používanou aplikaci heslo dostupné. - Password Store se automaticky pokouší najít odpovídající heslo k aplikacím. Toto výchozí nastavení spolu s nastavením pro jednotlivé aplikace lze změnit. Nastavení aplikace a webové stránky Automaticky párovat ve výchozím nastavení Ve výchozím nastavení \'Automaticky párovat\' u aplikací bez vybraného nastavení. Jinak, \'Nikdy nepárovat.\' @@ -126,13 +123,6 @@ Odeslat heslo jako plaintext za použití… - Automaticky vyplňuje pole hesel v aplikacích. Funguje pouze pro verzi Androidu 4.3 a vyšší. Není závislé na schránce pro Android verze 5.0 a vyšší. - Použít výchozí nastavení - Automaticky spárovat - Spárovat s… - Spárovat s - Nikdy nepárovat - Smazat diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index f4f99924..add23eda 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -98,9 +98,6 @@ Typ unabhängig Zuletzt verwendet Autofill aktivieren - Wähle OK, um zu den Bedienungshilfen-Einstellungen zu gelangen. Dort aktiviere oder deaktiviere den Password Store unter Dienste. - Wenn der Hintergrunddienst aktiviert ist, erscheint immer dann ein Dialog, wenn du auf ein Passwortfeld in einer App klickst und ein dazu passender Eintrag existiert. - Password Store versucht das Passwort zu der App automatisch herauszufinden. Du kannst diese Standard-Einstellung ändern und den Abgleich per App anpassen. App und Websiten Einstellungen Standardmäßig automatisch abgleichen Standard auf \'Automatisch abgleichen\' für Apps ohne eine Standardeinstellung, andernfalls \'Niemals abgleichen.\' @@ -170,23 +167,8 @@ Installiert: %1$s Warnung - Füge das Passwort automatisch in Apps ein (Autofill). Funktioniert nur unter Android 4.3 und höher. Dies basiert nicht auf der Zwischenablage für Android 5.0 oder höher. - Nutze Standardeinstellung - Automatisch abgleichen - Abgleichen mit… - Abgleichen mit - Niemals abgleichen - Löschen - Auswählen… - Auswählen und merken… - Einfügen - Benutzername einfügen?\n\n%s - Wähle ein editierbares Feld um den Benutzernamen einzufügen.\nDer Benutzername ist für %d Sekunden verfügbar. Der private SSH-Schlüssel konnte nicht geöffnet werden. Bitte überprüfen Sie, ob die Datei existiert Neues Passwort - Bildschirmfoto Accessibility Services - Bildschirmfoto des Schalters in Accessibility Services - Bildschirmfoto von Autofill in Aktion Authentifizierungsfehler Authentifizierungsfehler: %s Biometrische Authentifizierung aktivieren diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index e489512a..c64287b5 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -79,9 +79,6 @@ Independiente del tipo Autollenado Habilitar autollenado - Pulsa OK para ir a los ajustes de Accesibilidad. Ahí, pulsa Password Store debajo de Servicios, luego pulsa el switch en la esquina superior derecha para (des)activarlo. - Una vez que el servicio está activo, un diálogo aparecerá cuando pulses sobre un campo de contraseña en una app si existe un registro ligado. - Password Store intenta ligar las contraseñas automáticamente. Puedes cambiar este ajuste predeterminado y cada ajuste por aplicación. Ajustes de aplicaciones y sitios web Ligar automáticamente por defecto Por defecto usar \'Ligar automáticamente\' para aplicaciones sin ajustes personalizados. De otra forma, usar \'Nunca ligar.\' @@ -149,23 +146,8 @@ Rellena las credenciales Rellenar las contraseñas - Autollena campos de contraseña en aplicaciones. Solo funciona en Android 4.3 y superiores. No depende del portapapales en Android 5.0 y superiores. - Usar la opción predeterminada - Ligar automáticamente - Ligar a… - Ligar a - Nunca ligar - Eliminar - Seleccionar… - Seleccionar y ligar… - Pegar - Pegar nombre de usuario?\n\n%s - Selecciona un campo editable para pegar el nombre de usuario.\nNombre de usuario disponible por %d segundos. Imposible abrir la llave privada SSH. Por favor verifica que el archivo exista Nueva contraseña - Pantalla de Servicios de Accesibilidad - Pantalla de activación en Servicios de Accesibilidad - Pantalla de servicio de autollenado en acción Recordar contraseñagit (inseguro) Abortar rebase diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index cac0550d..557591e8 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -113,9 +113,6 @@ Récemment utilisé Saisie automatique Saisie automatique - Tapez OK pour aller dans les paramètres d\'Accessibilité. Puis, choisissez \"Password Store\" dans \"Services\", ensuite utilisez le boutton dans le coin supérieur droit pour activer/désactiver la saisie automatique. - Lorsque le service est activé une fenêtre de dialogue apparaitra lorsque vous cliquez sur un champ de type mot de passe. - Password Store proposera alors le mot de passe. Vous pouvez changer ce réglage ou effectuer une correspondance spéfique par application. Paramètres Application et site web Correspondance automatique par défaut Default to \"Automatically match\" for apps without custom settings. Otherwise, \"Never match.\" @@ -208,22 +205,8 @@ Pour activer cette fonctionnalité, appuyez sur OK pour aller dans les paramètres de saisie automatique, sélectionnez Password Store dans la liste puis confirmez avec OK. Prise en charge du remplissage automatique avec les navigateurs installés: - Remplissage automatique des champs de type mot de passe dans les applications. Ne fonctionne que sur les version d\'Android 4.3 et supérieure. Ne dépend pas du presse-papier pour les version d\'Android 5.0 et supérieure. - Utiliser les paramètres par défaut - Correspondance automatique - Correspondance avec… - Correspondance avec - Aucun correspondance - Supprimer - Choisir… - Choisir et correspondre… - Coller - Coller le nom d\'utilisateur?\n\n%s Impossible d\'ouvrir la clef ssh, merci de vérifier que le ficher existe Nouveau mot de passe - Capture des services d\'accessibilité - Capture de bascule dans les services d\'accessibilité - Capture du service de remplissage automatique en action Se rappeler de la phrase secrète dans la configuration de l\'application (peu sûr) Réinitialisation dure de la branche distante Identification biométrique diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index 9fff40d3..177e6e90 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -56,9 +56,6 @@ 現在のディレクトリーのパスワードを再帰的に検索します。 自動入力 自動入力を有効にする - OK をタップするとアクセシビリティ設定に移動します。 ここで、サービスの下の Password Store をタップし、右上のスイッチをタップしてオンまたはオフにします。 - サービスがオンになると、アプリのパスワードフィールドをクリックすとダイアログが表示され、一致するアプリのパスワードが存在すると表示されます。 - Password Store は、自動的にアプリをパスワードと照合します。 このデフォルト設定を変更することも、アプリごとに設定を変更することもできます。 アプリとウェブサイトの設定 デフォルトで自動的に一致 カスタム設定のないアプリは、デフォルトは \'自動的に一致\' になります。 それ以外の場合は、\'一致しない\'。 @@ -100,13 +97,6 @@ パスワードをプレーンテキストとして送信… - アプリのパスワードフィールドを自動入力します。 Android バージョン 4.3 以降でのみ動作します。 Android 5.0 以降のクリップボードには依存しません。 - デフォルト設定を使用する - 自動的に一致 - 一致… - 一致 - 一致しない - 削除 diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 284b1cf6..cef14227 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -115,9 +115,6 @@ Usado recentemente Preenchimento Automático Ativar preenchimento automático - Toque em OK para ir para as Configurações de Acessibilidade. Lá, toque em Password Store nos Serviços e toque na chave no canto superior direito para ligar ou desligar. - Quando o serviço estiver ativado, uma caixa de diálogo irá aparecer quando você clicar no campo de senha em um aplicativo se existir uma senha correspondente para o aplicativo. - O Password Store tenta combinar aplicativos com senhas automaticamente. Você pode alterar essa configuração padrão e também as configurações correspondentes por aplicativo. Configurações de apps e sites Corresponder automaticamente por padrão Padrão para \'correspondência automática\' para aplicativos sem configurações personalizadas. Caso contrário, \'Nunca corresponder.\' @@ -216,23 +213,8 @@ Para ativar esse recurso, toque em OK para ir para as configurações de preenchimento automático. Lá, selecione Password Store na lista e confirme na tela confirmação com OK. Suporte ao preenchimento automático com navegadores instalados: - Preenche automaticamente campos de senha em aplicativos. Funciona apenas para versões Android 4.3 e superior. Não depende da área de transferência para Android versões 5.0 ou superior. - Usar configurações padrão - Corresponder automaticamente - Combinar com… - Combinar com - Nunca coincidir - Excluir - Escolher… - Escolher e combinar… - Colar - Colar usuário?\n\n%s - Selecione um campo editável para colar o nome de usuário.\nNome de usuário está disponível por %d segundos. Não foi possível abrir a chave privada ssh, por favor verifique se o arquivo existe Nova senha - Captura de tela de serviços de acessibilidade - Captura de tela de alternância nos serviços de acessibilidade - Captura de tela do serviço de preenchimento automático em ação Limpar a frase secreta salva para chave SSH local Limpar senha HTTPS salva Lembrar senha da chave diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 157023eb..f9340b41 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -119,9 +119,6 @@ Недавно использованные Автозаполнение Включить автозаполнение - Нажмите ОК чтобы перейти в Специальные Возможности. Выберите Password Store в службах, а потом нажмите на переключатель. - После активации сервиса, при клике на поле ввода пароля в приложении появится диалоговое окно, если подходящий пароль существует - Password Store пытается сопоставлять пароли с приложениями автоматически. Вы можете изменить это поведение. Настройки приложений и веб сайтов Автоматически сопоставлять по умолчанию По умолчанию \'Автоматически сопоставлять\' для приложений без пользовательских настроек. В другом случае, \'Никогда не сопоставлять\' @@ -220,23 +217,8 @@ Чтобы включить эту функцию, нажмите ОК, чтобы перейти к настройкам автозаполнения. Там выберите Password Store из списка и подтвердите запрос подтверждения, нажав ОК. Поддержка автозаполнения установленными браузерами: - Автозаполнение паролей в приложениях. Работает только в Android 4.3 и выше. Не использует буфер обмена в Android 5.0 и выше. - Использовать настройки по умолчанию - Автоматически сопоставлять - Сопоставить с … - Сопоставить с - Никогда не сопоставлять - Удалить - Выбрать… - Выбрать и сопоставить… - Вставить - Вставить имя пользователя?\n\n%s - Выберите поле ввода для вставки имени пользователя.\nИмя пользователя можно вставить в течение %d секунд. Невозможно открыть приватный ключ ssh, пожалуйста проверьте, что файл существует Новый пароль - Снимок экрана сервисов доступности - Снимок экрана переключателя в сервисах доступности - Снимок экрана сервиса автозаполнения в действии Очистить сохраненную кодовую фразу для локального SSH ключа Очистить сохраненный пароль HTTPS Заполнить парольную фразу в конфигурации приложнеия (небезопасно) diff --git a/app/src/main/res/values-v26/bools.xml b/app/src/main/res/values-v26/bools.xml deleted file mode 100644 index e2c7af91..00000000 --- a/app/src/main/res/values-v26/bools.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - false - diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index edc45165..cfe9312f 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -55,9 +55,6 @@ 搜索子文件夹 在当前目录的子目录中查找密码 启动自动填充 - 点击 OK 进入无障碍设置. 在那里的服务选项中选中点选 Password Store 并单击右上方的开关将其开启或关闭 - 服务启动之后, 在你点击一个应用的密码栏时如果该应用有匹配的密码则会弹出对话框. - Password Store 会自动尝试将应用与密码匹配. 你可以更改这个默认设置以及每个应用的匹配设置. 应用及网站设置 默认为自动匹配 无自定义设置的应用将默认采用 \'自动匹配\' . 否则将采用\'从不匹配\' @@ -98,13 +95,6 @@ 将密码以纯文本发送… - 在app中自动输入密码. 此功能只在 Andorid 4.3 及以上版本中可用. 在 Andorid 5.0 及以上版本中不依赖剪贴板 - 使用默认设置 - 自动匹配 - 匹配… - 匹配 - 从不匹配 - 删除 diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index f7851176..b7816727 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -53,9 +53,6 @@ 搜尋子資料夾 在目前目錄的子目錄中查詢密碼 啟動自動填入 - 點擊 OK 進入無障礙設定. 在那裡的請選擇 Password Store 並點擊右上方的開關將其開起或關閉 - 服務起動後, 在你點擊一個 app 的密碼欄時如果該 app 有匹配的密碼將會彈出對話框. - Password Store 會自動嘗試將 app 與密碼匹配. 你可以更改這個預設選項以及每個 app 的個別設定. app 及網站設定 使用自動選取 無自定設定的 app 將預設使用 \'自動選取\' . 否則將使用\'手動\' @@ -96,13 +93,6 @@ 將密碼以純文字傳送… - 在app中自動填入密碼. 此功能只能在 Andorid 4.3 及以上版本中使用. 在 Andorid 5.0 及以上版本中不需要剪貼簿 - 使用預設值 - 自動選取 - 匹配… - 自動填入 - 手動 - 刪除 diff --git a/app/src/main/res/values/bools.xml b/app/src/main/res/values/bools.xml index 475702a1..81c8b237 100644 --- a/app/src/main/res/values/bools.xml +++ b/app/src/main/res/values/bools.xml @@ -5,6 +5,5 @@ true - true true diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 2e393994..579017b9 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -137,9 +137,6 @@ Recently used Autofill Enable Autofill - Tap OK to go to Accessibility settings. There, tap Password Store under Services then tap the switch in the top right to turn it on or off. - Once the service is on, a dialog will appear when you click on a password field in an app if a matching password for the app exists. - Password Store attempts to match apps with passwords automatically. You can change this default setting and also matching settings per-app. App and website settings Automatically match by default Default to \'Automatically match\' for apps without custom settings. Otherwise, \'Never match.\' @@ -266,23 +263,8 @@ Autofill support with installed browsers: - Autofills password fields in apps. Only works for Android versions 4.3 and up. Does not rely on the clipboard for Android versions 5.0 and up. - Use default setting - Automatically match - Match with… - Match with - Never match - Delete - Pick… - Pick and match… - Paste - Paste username?\n\n%s - Select an editable field to paste the username.\nUsername is available for %d seconds. Unable to open the ssh private key, please check that the file exists New password - Screenshot of accessibility services - Screenshot of toggle in accessibility services - Screenshot of autofill service in action Clear saved passphrase for local SSH key Clear saved HTTPS password Remember key passphrase diff --git a/app/src/main/res/xml/autofill_config.xml b/app/src/main/res/xml/autofill_config.xml deleted file mode 100644 index a27e30cf..00000000 --- a/app/src/main/res/xml/autofill_config.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - diff --git a/app/src/main/res/xml/preference.xml b/app/src/main/res/xml/preference.xml index 6631133f..6b670618 100644 --- a/app/src/main/res/xml/preference.xml +++ b/app/src/main/res/xml/preference.xml @@ -25,23 +25,6 @@ app:key="oreo_autofill_custom_public_suffixes" app:summary="@string/preference_custom_public_suffixes_summary" app:title="@string/preference_custom_public_suffixes_title" /> - - - - -- cgit v1.2.3