Remove accessibility autofill support (#1162)

* autofill: remove Accessibility backend

Signed-off-by: Harsh Shandilya <me@msfjarvis.dev>

* CHANGELOG: update

Signed-off-by: Harsh Shandilya <me@msfjarvis.dev>
This commit is contained in:
Harsh Shandilya 2020-10-22 23:38:47 +05:30 committed by GitHub
parent 260145ce16
commit 4a9151870d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
32 changed files with 39 additions and 1796 deletions

View file

@ -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

View file

@ -11,9 +11,6 @@
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission
android:name="android.permission.BIND_ACCESSIBILITY_SERVICE"
tools:ignore="ProtectedPermissions" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
@ -92,17 +89,6 @@
android:parentActivityName=".PasswordStore"
android:theme="@style/NoBackgroundTheme" />
<service
android:name=".autofill.AutofillService"
android:enabled="@bool/enable_accessibility_autofill"
android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
<intent-filter>
<action android:name="android.accessibilityservice.AccessibilityService" />
</intent-filter>
<meta-data
android:name="android.accessibilityservice"
android:resource="@xml/autofill_config" />
</service>
<service
android:name=".ClipboardService"
android:process=":clipboard_service_process" />
@ -120,15 +106,6 @@
android:resource="@xml/oreo_autofill_service" />
</service>
<activity
android:name=".autofill.AutofillActivity"
android:documentLaunchMode="intoExisting"
android:excludeFromRecents="true"
android:theme="@style/AppTheme"
tools:ignore="UnusedAttribute" />
<activity android:name=".autofill.AutofillPreferenceActivity" />
<activity android:name=".SelectFolderActivity" />
<activity
android:name=".sshkeygen.SshKeyGenActivity"

View file

@ -4,7 +4,6 @@
*/
package com.zeapo.pwdstore
import android.accessibilityservice.AccessibilityServiceInfo
import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
@ -18,10 +17,10 @@ import android.provider.DocumentsContract
import android.provider.Settings
import android.text.TextUtils
import android.view.MenuItem
import android.view.accessibility.AccessibilityManager
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts.OpenDocument
import androidx.activity.result.contract.ActivityResultContracts.OpenDocumentTree
import androidx.annotation.RequiresApi
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.AppCompatTextView
import androidx.core.content.edit
@ -42,7 +41,6 @@ import com.github.michaelbull.result.getOr
import com.github.michaelbull.result.onFailure
import com.github.michaelbull.result.runCatching
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.zeapo.pwdstore.autofill.AutofillPreferenceActivity
import com.zeapo.pwdstore.crypto.BasePgpActivity
import com.zeapo.pwdstore.git.GitConfigActivity
import com.zeapo.pwdstore.git.GitServerConfigActivity
@ -173,7 +171,6 @@ class UserPreference : AppCompatActivity() {
private var autoFillEnablePreference: SwitchPreferenceCompat? = null
private var clearSavedPassPreference: Preference? = null
private var viewSshKeyPreference: Preference? = null
private lateinit var autofillDependencies: List<Preference>
private lateinit var oreoAutofillDependencies: List<Preference>
private lateinit var prefsActivity: UserPreference
private lateinit var sharedPreferences: SharedPreferences
@ -220,16 +217,6 @@ class UserPreference : AppCompatActivity() {
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)
val autoFillAppsPreference = findPreference<Preference>(PreferenceKeys.AUTOFILL_APPS)
val autoFillDefaultPreference = findPreference<CheckBoxPreference>(PreferenceKeys.AUTOFILL_DEFAULT)
val autoFillAlwaysShowDialogPreference = findPreference<CheckBoxPreference>(PreferenceKeys.AUTOFILL_ALWAYS)
val autoFillShowFullNamePreference = findPreference<CheckBoxPreference>(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<Preference>(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<AppCompatTextView>(R.id.supportedBrowsers)
supportedBrowsersTextView.text =
getInstalledBrowsersWithAutofillSupportLevel(context).joinToString(
separator = "\n"
) {
val appLabel = it.first
val supportDescription = when (it.second) {
BrowserAutofillSupportLevel.None -> getString(R.string.oreo_autofill_no_support)
BrowserAutofillSupportLevel.FlakyFill -> getString(R.string.oreo_autofill_flaky_fill_support)
BrowserAutofillSupportLevel.PasswordFill -> getString(R.string.oreo_autofill_password_fill_support)
BrowserAutofillSupportLevel.GeneralFill -> getString(R.string.oreo_autofill_general_fill_support)
BrowserAutofillSupportLevel.GeneralFillAndSave -> getString(R.string.oreo_autofill_general_fill_and_save_support)
}
"$appLabel: $supportDescription"
@SuppressLint("InflateParams")
val layout =
layoutInflater.inflate(R.layout.oreo_autofill_instructions, null)
val supportedBrowsersTextView =
layout.findViewById<AppCompatTextView>(R.id.supportedBrowsers)
supportedBrowsersTextView.text =
getInstalledBrowsersWithAutofillSupportLevel(context).joinToString(
separator = "\n"
) {
val appLabel = it.first
val supportDescription = when (it.second) {
BrowserAutofillSupportLevel.None -> getString(R.string.oreo_autofill_no_support)
BrowserAutofillSupportLevel.FlakyFill -> getString(R.string.oreo_autofill_flaky_fill_support)
BrowserAutofillSupportLevel.PasswordFill -> getString(R.string.oreo_autofill_password_fill_support)
BrowserAutofillSupportLevel.GeneralFill -> getString(R.string.oreo_autofill_general_fill_support)
BrowserAutofillSupportLevel.GeneralFillAndSave -> getString(R.string.oreo_autofill_general_fill_and_save_support)
}
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<AccessibilityManager>() ?: 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

View file

@ -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<PendingIntent>("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
}
}

View file

@ -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<String>? = 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<View>(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<View>(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<String>(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<View>(R.id.matched) as ListView).adapter = adapter
// delete items by clicking them
(view.findViewById<View>(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<View>(R.id.use_default) as RadioButton).toggle()
"/first" -> (view.findViewById<View>(R.id.first) as RadioButton).toggle()
"/never" -> (view.findViewById<View>(R.id.never) as RadioButton).toggle()
else -> {
(view.findViewById<View>(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<View>(R.id.match) as RadioButton).toggle()
val intent = Intent(activity, PasswordStore::class.java)
intent.putExtra("matchWith", true)
startActivityForResult(intent, MATCH_WITH)
}
view.findViewById<View>(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<EditText>(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<RadioGroup>(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
}
}

View file

@ -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<FloatingActionButton>(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<Void, Void, Void>() {
val weakReference = WeakReference(activity)
override fun onPreExecute() {
weakReference.get()?.apply {
runOnUiThread { findViewById<View>(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<AutofillRecyclerAdapter.AppInfo>()
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")!!))
}
}
}
}
}
}
}
}

View file

@ -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<AppInfo>,
private val activity: AutofillPreferenceActivity
) : RecyclerView.Adapter<AutofillRecyclerAdapter.ViewHolder>(), PopupTextProvider {
private val apps: SortedList<AppInfo>
private val allApps: ArrayList<AppInfo> // for filtering, maintain a list of all
private var browserIcon: Drawable? = null
init {
val callback = object : SortedListAdapterCallback<AppInfo>(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)
}
}
}

View file

@ -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<File> = 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<File> {
val passList = PasswordRepository.getFilesList(path)
if (passList.size == 0) return ArrayList()
val items = ArrayList<File>()
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<CharSequence>(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<PendingIntent>(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<OpenPgpError>(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
}
}

View file

@ -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"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

View file

@ -1,64 +0,0 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved.
~ SPDX-License-Identifier: GPL-3.0-only
-->
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingLeft="24dp"
android:paddingTop="20dp"
android:paddingRight="24dp"
android:paddingBottom="20dp">
<androidx.appcompat.widget.AppCompatTextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/pref_autofill_enable_msg"
android:textSize="16sp" />
<androidx.appcompat.widget.AppCompatImageView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
android:adjustViewBounds="true"
android:contentDescription="@string/autofill_ins_1_hint"
android:src="@drawable/autofill_ins_1" />
<androidx.appcompat.widget.AppCompatImageView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:adjustViewBounds="true"
android:contentDescription="@string/autofill_ins_2_hint"
android:src="@drawable/autofill_ins_2" />
<androidx.appcompat.widget.AppCompatTextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/pref_autofill_enable_msg2"
android:textSize="16sp" />
<androidx.appcompat.widget.AppCompatImageView
android:layout_width="match_parent"
android:layout_height="114dp"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
android:contentDescription="@string/autofill_ins_3_hint"
android:src="@drawable/autofill_ins_3" />
<androidx.appcompat.widget.AppCompatTextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/pref_autofill_enable_msg3"
android:textSize="16sp" />
</LinearLayout>
</ScrollView>

View file

@ -1,43 +0,0 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved.
~ SPDX-License-Identifier: GPL-3.0-only
-->
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/autofill_recycler"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="none"
tools:itemCount="20"
tools:listitem="@layout/autofill_row_layout" />
<ProgressBar
android:id="@+id/progress_bar"
style="?android:attr/progressBarStyleLarge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:indeterminate="true"
android:visibility="gone" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_alignParentBottom="true"
android:layout_gravity="bottom|end"
android:layout_margin="@dimen/fab_compat_margin"
android:src="@drawable/ic_add_48dp"
app:backgroundTint="?attr/colorSecondary"
app:borderWidth="0dp"
app:elevation="6dp"
app:pressedTranslationZ="12dp"
app:rippleColor="?attr/colorSecondary" />
</RelativeLayout>

View file

@ -1,41 +0,0 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved.
~ SPDX-License-Identifier: GPL-3.0-only
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="64dp"
android:background="?android:attr/selectableItemBackground"
android:gravity="center_vertical"
android:orientation="horizontal"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/app_icon"
android:layout_width="48dp"
android:layout_height="48dp"
android:contentDescription="@string/app_icon_hint" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_marginStart="8dp"
android:gravity="center_vertical"
android:orientation="vertical">
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/app_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/secondary_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textColor="?android:attr/textColor" />
</LinearLayout>
</LinearLayout>

View file

@ -1,84 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved.
~ SPDX-License-Identifier: GPL-3.0-only
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingLeft="24dp"
android:paddingTop="20dp"
android:paddingRight="24dp"
android:paddingBottom="20dp">
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="URL">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/webURL"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textUri" />
</com.google.android.material.textfield.TextInputLayout>
<RadioGroup
android:id="@+id/autofill_radiogroup"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<RadioButton
android:id="@+id/use_default"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:checked="false"
android:text="@string/autofill_apps_default" />
<RadioButton
android:id="@+id/first"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:checked="false"
android:text="@string/autofill_apps_first" />
<RadioButton
android:id="@+id/match"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:checked="false"
android:text="@string/autofill_apps_match_ellipsis" />
<ListView
android:id="@+id/matched"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_weight="1" />
<com.google.android.material.button.MaterialButton
android:id="@+id/matchButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="+"
tools:ignore="HardcodedText" />
<RadioButton
android:id="@+id/never"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:checked="false"
android:text="@string/autofill_apps_never" />
</RadioGroup>
</LinearLayout>

View file

@ -1,17 +0,0 @@
<!--
~ Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved.
~ SPDX-License-Identifier: GPL-3.0-only
-->
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:pwstore="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
tools:context=".pwdstore.autofill.AutofillPreferenceActivity">
<item
android:id="@+id/action_search"
android:icon="@drawable/ic_search_24dp"
android:title="@string/action_search"
pwstore:actionViewClass="androidx.appcompat.widget.SearchView"
pwstore:showAsAction="ifRoom|collapseActionView" />
</menu>

View file

@ -93,14 +93,6 @@
<string name="app_icon_hint">أيقونة التطبيق</string>
<!-- Oreo Autofill -->
<!-- Autofill -->
<string name="autofill_apps_default">إستخدم الإعداد الإفتراضي</string>
<string name="autofill_apps_match_ellipsis">طابق مع ...</string>
<string name="autofill_apps_match">طابق مع</string>
<string name="autofill_apps_never">لا تطابق أبدا</string>
<string name="autofill_apps_delete">حذف</string>
<string name="autofill_pick">إختر</string>
<string name="autofill_pick_and_match">إختر و طابق مع ...</string>
<string name="autofill_paste">إلصاق</string>
<string name="new_password_title">كلمة السر الجديدة</string>
<!-- OpenKeychain errors -->
<!-- Password creation failure -->

View file

@ -79,9 +79,6 @@
<string name="pref_recursive_filter">Rekurzivní filtrování</string>
<string name="pref_recursive_filter_hint">Rekurzivní hledání hesel v aktuálním adresáři.</string>
<string name="pref_autofill_enable_title">Povolit automatické vyplňování</string>
<string name="pref_autofill_enable_msg">Ť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.</string>
<string name="pref_autofill_enable_msg2">Jakmile bude služba zapnuta, po kliknutí na pole hesla bude zobrazeno dialogové okno pokud je pro používanou aplikaci heslo dostupné.</string>
<string name="pref_autofill_enable_msg3">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.</string>
<string name="pref_autofill_apps_title">Nastavení aplikace a webové stránky</string>
<string name="pref_autofill_default_title">Automaticky párovat ve výchozím nastavení</string>
<string name="pref_autofill_default_hint">Ve výchozím nastavení \'Automaticky párovat\' u aplikací bez vybraného nastavení. Jinak, \'Nikdy nepárovat.\'</string>
@ -126,13 +123,6 @@
<string name="send_plaintext_password_to">Odeslat heslo jako plaintext za použití…</string>
<!-- Oreo Autofill -->
<!-- Autofill -->
<string name="autofill_description">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šší.</string>
<string name="autofill_apps_default">Použít výchozí nastavení</string>
<string name="autofill_apps_first">Automaticky spárovat</string>
<string name="autofill_apps_match_ellipsis">Spárovat s…</string>
<string name="autofill_apps_match">Spárovat s</string>
<string name="autofill_apps_never">Nikdy nepárovat</string>
<string name="autofill_apps_delete">Smazat</string>
<!-- OpenKeychain errors -->
<!-- Password creation failure -->
<!-- GitException messages -->

View file

@ -98,9 +98,6 @@
<string name="pref_type_independent_sort_order">Typ unabhängig</string>
<string name="pref_recently_used_sort_order">Zuletzt verwendet</string>
<string name="pref_autofill_enable_title">Autofill aktivieren</string>
<string name="pref_autofill_enable_msg">Wähle OK, um zu den Bedienungshilfen-Einstellungen zu gelangen. Dort aktiviere oder deaktiviere den Password Store unter Dienste.</string>
<string name="pref_autofill_enable_msg2">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.</string>
<string name="pref_autofill_enable_msg3">Password Store versucht das Passwort zu der App automatisch herauszufinden. Du kannst diese Standard-Einstellung ändern und den Abgleich per App anpassen.</string>
<string name="pref_autofill_apps_title">App und Websiten Einstellungen</string>
<string name="pref_autofill_default_title">Standardmäßig automatisch abgleichen</string>
<string name="pref_autofill_default_hint">Standard auf \'Automatisch abgleichen\' für Apps ohne eine Standardeinstellung, andernfalls \'Niemals abgleichen.\'</string>
@ -170,23 +167,8 @@
<string name="oreo_autofill_warning_publisher_install_time">Installiert: %1$s</string>
<string name="oreo_autofill_warning_publisher_warning_sign_description">Warnung</string>
<!-- Autofill -->
<string name="autofill_description">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.</string>
<string name="autofill_apps_default">Nutze Standardeinstellung</string>
<string name="autofill_apps_first">Automatisch abgleichen</string>
<string name="autofill_apps_match_ellipsis">Abgleichen mit…</string>
<string name="autofill_apps_match">Abgleichen mit</string>
<string name="autofill_apps_never">Niemals abgleichen</string>
<string name="autofill_apps_delete">Löschen</string>
<string name="autofill_pick">Auswählen…</string>
<string name="autofill_pick_and_match">Auswählen und merken…</string>
<string name="autofill_paste">Einfügen</string>
<string name="autofill_paste_username">Benutzername einfügen?\n\n%s</string>
<string name="autofill_toast_username">Wähle ein editierbares Feld um den Benutzernamen einzufügen.\nDer Benutzername ist für %d Sekunden verfügbar.</string>
<string name="ssh_key_does_not_exist">Der private SSH-Schlüssel konnte nicht geöffnet werden. Bitte überprüfen Sie, ob die Datei existiert</string>
<string name="new_password_title">Neues Passwort</string>
<string name="autofill_ins_1_hint">Bildschirmfoto Accessibility Services</string>
<string name="autofill_ins_2_hint">Bildschirmfoto des Schalters in Accessibility Services</string>
<string name="autofill_ins_3_hint">Bildschirmfoto von Autofill in Aktion</string>
<string name="biometric_auth_error">Authentifizierungsfehler</string>
<string name="biometric_auth_error_reason">Authentifizierungsfehler: %s</string>
<string name="biometric_auth_title">Biometrische Authentifizierung aktivieren</string>

View file

@ -79,9 +79,6 @@
<string name="pref_type_independent_sort_order">Independiente del tipo</string>
<string name="pref_autofill_title">Autollenado</string>
<string name="pref_autofill_enable_title">Habilitar autollenado</string>
<string name="pref_autofill_enable_msg">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.</string>
<string name="pref_autofill_enable_msg2">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.</string>
<string name="pref_autofill_enable_msg3">Password Store intenta ligar las contraseñas automáticamente. Puedes cambiar este ajuste predeterminado y cada ajuste por aplicación.</string>
<string name="pref_autofill_apps_title">Ajustes de aplicaciones y sitios web</string>
<string name="pref_autofill_default_title">Ligar automáticamente por defecto</string>
<string name="pref_autofill_default_hint">Por defecto usar \'Ligar automáticamente\' para aplicaciones sin ajustes personalizados. De otra forma, usar \'Nunca ligar.\'</string>
@ -149,23 +146,8 @@
<string name="oreo_autofill_general_fill_support">Rellena las credenciales</string>
<string name="oreo_autofill_password_fill_support">Rellenar las contraseñas</string>
<!-- Autofill -->
<string name="autofill_description">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.</string>
<string name="autofill_apps_default">Usar la opción predeterminada</string>
<string name="autofill_apps_first">Ligar automáticamente</string>
<string name="autofill_apps_match_ellipsis">Ligar a…</string>
<string name="autofill_apps_match">Ligar a</string>
<string name="autofill_apps_never">Nunca ligar</string>
<string name="autofill_apps_delete">Eliminar</string>
<string name="autofill_pick">Seleccionar…</string>
<string name="autofill_pick_and_match">Seleccionar y ligar…</string>
<string name="autofill_paste">Pegar</string>
<string name="autofill_paste_username">Pegar nombre de usuario?\n\n%s</string>
<string name="autofill_toast_username">Selecciona un campo editable para pegar el nombre de usuario.\nNombre de usuario disponible por %d segundos.</string>
<string name="ssh_key_does_not_exist">Imposible abrir la llave privada SSH. Por favor verifica que el archivo exista</string>
<string name="new_password_title">Nueva contraseña</string>
<string name="autofill_ins_1_hint">Pantalla de Servicios de Accesibilidad</string>
<string name="autofill_ins_2_hint">Pantalla de activación en Servicios de Accesibilidad</string>
<string name="autofill_ins_3_hint">Pantalla de servicio de autollenado en acción</string>
<string name="git_operation_remember_passphrase">Recordar contraseñagit (inseguro)</string>
<string name="abort_rebase">Abortar rebase</string>
<!-- OpenKeychain errors -->

View file

@ -113,9 +113,6 @@
<string name="pref_recently_used_sort_order">Récemment utilisé</string>
<string name="pref_autofill_title">Saisie automatique</string>
<string name="pref_autofill_enable_title">Saisie automatique</string>
<string name="pref_autofill_enable_msg">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.</string>
<string name="pref_autofill_enable_msg2">Lorsque le service est activé une fenêtre de dialogue apparaitra lorsque vous cliquez sur un champ de type mot de passe.</string>
<string name="pref_autofill_enable_msg3">Password Store proposera alors le mot de passe. Vous pouvez changer ce réglage ou effectuer une correspondance spéfique par application.</string>
<string name="pref_autofill_apps_title">Paramètres Application et site web</string>
<string name="pref_autofill_default_title">Correspondance automatique par défaut</string>
<string name="pref_autofill_default_hint">Default to \"Automatically match\" for apps without custom settings. Otherwise, \"Never match.\"</string>
@ -208,22 +205,8 @@
<string name="oreo_autofill_enable_dialog_instructions">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.</string>
<string name="oreo_autofill_enable_dialog_installed_browsers">Prise en charge du remplissage automatique avec les navigateurs installés:</string>
<!-- Autofill -->
<string name="autofill_description">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.</string>
<string name="autofill_apps_default">Utiliser les paramètres par défaut</string>
<string name="autofill_apps_first">Correspondance automatique</string>
<string name="autofill_apps_match_ellipsis">Correspondance avec…</string>
<string name="autofill_apps_match">Correspondance avec</string>
<string name="autofill_apps_never">Aucun correspondance</string>
<string name="autofill_apps_delete">Supprimer</string>
<string name="autofill_pick">Choisir…</string>
<string name="autofill_pick_and_match">Choisir et correspondre…</string>
<string name="autofill_paste">Coller</string>
<string name="autofill_paste_username">Coller le nom d\'utilisateur?\n\n%s</string>
<string name="ssh_key_does_not_exist">Impossible d\'ouvrir la clef ssh, merci de vérifier que le ficher existe</string>
<string name="new_password_title">Nouveau mot de passe</string>
<string name="autofill_ins_1_hint">Capture des services d\'accessibilité</string>
<string name="autofill_ins_2_hint">Capture de bascule dans les services d\'accessibilité</string>
<string name="autofill_ins_3_hint">Capture du service de remplissage automatique en action</string>
<string name="git_operation_remember_passphrase">Se rappeler de la phrase secrète dans la configuration de l\'application (peu sûr)</string>
<string name="reset_to_remote">Réinitialisation dure de la branche distante</string>
<string name="biometric_prompt_title">Identification biométrique</string>

View file

@ -56,9 +56,6 @@
<string name="pref_recursive_filter_hint">現在のディレクトリーのパスワードを再帰的に検索します。</string>
<string name="pref_autofill_title">自動入力</string>
<string name="pref_autofill_enable_title">自動入力を有効にする</string>
<string name="pref_autofill_enable_msg">OK をタップするとアクセシビリティ設定に移動します。 ここで、サービスの下の Password Store をタップし、右上のスイッチをタップしてオンまたはオフにします。</string>
<string name="pref_autofill_enable_msg2">サービスがオンになると、アプリのパスワードフィールドをクリックすとダイアログが表示され、一致するアプリのパスワードが存在すると表示されます。</string>
<string name="pref_autofill_enable_msg3">Password Store は、自動的にアプリをパスワードと照合します。 このデフォルト設定を変更することも、アプリごとに設定を変更することもできます。</string>
<string name="pref_autofill_apps_title">アプリとウェブサイトの設定</string>
<string name="pref_autofill_default_title">デフォルトで自動的に一致</string>
<string name="pref_autofill_default_hint">カスタム設定のないアプリは、デフォルトは \'自動的に一致\' になります。 それ以外の場合は、\'一致しない\'。</string>
@ -100,13 +97,6 @@
<string name="send_plaintext_password_to">パスワードをプレーンテキストとして送信…</string>
<!-- Oreo Autofill -->
<!-- Autofill -->
<string name="autofill_description">アプリのパスワードフィールドを自動入力します。 Android バージョン 4.3 以降でのみ動作します。 Android 5.0 以降のクリップボードには依存しません。</string>
<string name="autofill_apps_default">デフォルト設定を使用する</string>
<string name="autofill_apps_first">自動的に一致</string>
<string name="autofill_apps_match_ellipsis">一致…</string>
<string name="autofill_apps_match">一致</string>
<string name="autofill_apps_never">一致しない</string>
<string name="autofill_apps_delete">削除</string>
<!-- OpenKeychain errors -->
<!-- Password creation failure -->
<!-- GitException messages -->

View file

@ -115,9 +115,6 @@
<string name="pref_recently_used_sort_order">Usado recentemente</string>
<string name="pref_autofill_title">Preenchimento Automático</string>
<string name="pref_autofill_enable_title">Ativar preenchimento automático</string>
<string name="pref_autofill_enable_msg">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.</string>
<string name="pref_autofill_enable_msg2">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.</string>
<string name="pref_autofill_enable_msg3">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.</string>
<string name="pref_autofill_apps_title">Configurações de apps e sites</string>
<string name="pref_autofill_default_title">Corresponder automaticamente por padrão</string>
<string name="pref_autofill_default_hint">Padrão para \'correspondência automática\' para aplicativos sem configurações personalizadas. Caso contrário, \'Nunca corresponder.\'</string>
@ -216,23 +213,8 @@
<string name="oreo_autofill_enable_dialog_instructions">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.</string>
<string name="oreo_autofill_enable_dialog_installed_browsers">Suporte ao preenchimento automático com navegadores instalados:</string>
<!-- Autofill -->
<string name="autofill_description">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.</string>
<string name="autofill_apps_default">Usar configurações padrão</string>
<string name="autofill_apps_first">Corresponder automaticamente</string>
<string name="autofill_apps_match_ellipsis">Combinar com…</string>
<string name="autofill_apps_match">Combinar com</string>
<string name="autofill_apps_never">Nunca coincidir</string>
<string name="autofill_apps_delete">Excluir</string>
<string name="autofill_pick">Escolher…</string>
<string name="autofill_pick_and_match">Escolher e combinar…</string>
<string name="autofill_paste">Colar</string>
<string name="autofill_paste_username">Colar usuário?\n\n%s</string>
<string name="autofill_toast_username">Selecione um campo editável para colar o nome de usuário.\nNome de usuário está disponível por %d segundos.</string>
<string name="ssh_key_does_not_exist">Não foi possível abrir a chave privada ssh, por favor verifique se o arquivo existe</string>
<string name="new_password_title">Nova senha</string>
<string name="autofill_ins_1_hint">Captura de tela de serviços de acessibilidade</string>
<string name="autofill_ins_2_hint">Captura de tela de alternância nos serviços de acessibilidade</string>
<string name="autofill_ins_3_hint">Captura de tela do serviço de preenchimento automático em ação</string>
<string name="clear_saved_passphrase_ssh">Limpar a frase secreta salva para chave SSH local</string>
<string name="clear_saved_passphrase_https">Limpar senha HTTPS salva</string>
<string name="git_operation_remember_passphrase">Lembrar senha da chave</string>

View file

@ -119,9 +119,6 @@
<string name="pref_recently_used_sort_order">Недавно использованные</string>
<string name="pref_autofill_title">Автозаполнение</string>
<string name="pref_autofill_enable_title">Включить автозаполнение</string>
<string name="pref_autofill_enable_msg">Нажмите ОК чтобы перейти в Специальные Возможности. Выберите Password Store в службах, а потом нажмите на переключатель.</string>
<string name="pref_autofill_enable_msg2">После активации сервиса, при клике на поле ввода пароля в приложении появится диалоговое окно, если подходящий пароль существует</string>
<string name="pref_autofill_enable_msg3">Password Store пытается сопоставлять пароли с приложениями автоматически. Вы можете изменить это поведение.</string>
<string name="pref_autofill_apps_title">Настройки приложений и веб сайтов</string>
<string name="pref_autofill_default_title">Автоматически сопоставлять по умолчанию</string>
<string name="pref_autofill_default_hint">По умолчанию \'Автоматически сопоставлять\' для приложений без пользовательских настроек. В другом случае, \'Никогда не сопоставлять\'</string>
@ -220,23 +217,8 @@
<string name="oreo_autofill_enable_dialog_instructions">Чтобы включить эту функцию, нажмите ОК, чтобы перейти к настройкам автозаполнения. Там выберите Password Store из списка и подтвердите запрос подтверждения, нажав ОК.</string>
<string name="oreo_autofill_enable_dialog_installed_browsers">Поддержка автозаполнения установленными браузерами:</string>
<!-- Autofill -->
<string name="autofill_description">Автозаполнение паролей в приложениях. Работает только в Android 4.3 и выше. Не использует буфер обмена в Android 5.0 и выше.</string>
<string name="autofill_apps_default">Использовать настройки по умолчанию</string>
<string name="autofill_apps_first">Автоматически сопоставлять</string>
<string name="autofill_apps_match_ellipsis">Сопоставить с</string>
<string name="autofill_apps_match">Сопоставить с</string>
<string name="autofill_apps_never">Никогда не сопоставлять</string>
<string name="autofill_apps_delete">Удалить</string>
<string name="autofill_pick">Выбрать…</string>
<string name="autofill_pick_and_match">Выбрать и сопоставить…</string>
<string name="autofill_paste">Вставить</string>
<string name="autofill_paste_username">Вставить имя пользователя?\n\n%s</string>
<string name="autofill_toast_username">Выберите поле ввода для вставки имени пользователя.\nИмя пользователя можно вставить в течение %d секунд.</string>
<string name="ssh_key_does_not_exist">Невозможно открыть приватный ключ ssh, пожалуйста проверьте, что файл существует</string>
<string name="new_password_title">Новый пароль</string>
<string name="autofill_ins_1_hint">Снимок экрана сервисов доступности</string>
<string name="autofill_ins_2_hint">Снимок экрана переключателя в сервисах доступности</string>
<string name="autofill_ins_3_hint">Снимок экрана сервиса автозаполнения в действии</string>
<string name="clear_saved_passphrase_ssh">Очистить сохраненную кодовую фразу для локального SSH ключа</string>
<string name="clear_saved_passphrase_https">Очистить сохраненный пароль HTTPS</string>
<string name="git_operation_remember_passphrase">Заполнить парольную фразу в конфигурации приложнеия (небезопасно)</string>

View file

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved.
~ SPDX-License-Identifier: GPL-3.0-only
-->
<resources>
<bool name="enable_accessibility_autofill">false</bool>
</resources>

View file

@ -55,9 +55,6 @@
<string name="pref_recursive_filter">搜索子文件夹</string>
<string name="pref_recursive_filter_hint">在当前目录的子目录中查找密码</string>
<string name="pref_autofill_enable_title">启动自动填充</string>
<string name="pref_autofill_enable_msg">点击 OK 进入无障碍设置. 在那里的服务选项中选中点选 Password Store 并单击右上方的开关将其开启或关闭</string>
<string name="pref_autofill_enable_msg2">服务启动之后, 在你点击一个应用的密码栏时如果该应用有匹配的密码则会弹出对话框.</string>
<string name="pref_autofill_enable_msg3">Password Store 会自动尝试将应用与密码匹配. 你可以更改这个默认设置以及每个应用的匹配设置.</string>
<string name="pref_autofill_apps_title">应用及网站设置</string>
<string name="pref_autofill_default_title">默认为自动匹配</string>
<string name="pref_autofill_default_hint">无自定义设置的应用将默认采用 \'自动匹配\' . 否则将采用\'从不匹配\'</string>
@ -98,13 +95,6 @@
<string name="send_plaintext_password_to">将密码以纯文本发送…</string>
<!-- Oreo Autofill -->
<!-- Autofill -->
<string name="autofill_description">在app中自动输入密码. 此功能只在 Andorid 4.3 及以上版本中可用. 在 Andorid 5.0 及以上版本中不依赖剪贴板</string>
<string name="autofill_apps_default">使用默认设置</string>
<string name="autofill_apps_first">自动匹配</string>
<string name="autofill_apps_match_ellipsis">匹配…</string>
<string name="autofill_apps_match">匹配</string>
<string name="autofill_apps_never">从不匹配</string>
<string name="autofill_apps_delete">删除</string>
<!-- OpenKeychain errors -->
<!-- Password creation failure -->
<!-- GitException messages -->

View file

@ -53,9 +53,6 @@
<string name="pref_recursive_filter">搜尋子資料夾</string>
<string name="pref_recursive_filter_hint">在目前目錄的子目錄中查詢密碼</string>
<string name="pref_autofill_enable_title">啟動自動填入</string>
<string name="pref_autofill_enable_msg">點擊 OK 進入無障礙設定. 在那裡的請選擇 Password Store 並點擊右上方的開關將其開起或關閉</string>
<string name="pref_autofill_enable_msg2">服務起動後, 在你點擊一個 app 的密碼欄時如果該 app 有匹配的密碼將會彈出對話框.</string>
<string name="pref_autofill_enable_msg3">Password Store 會自動嘗試將 app 與密碼匹配. 你可以更改這個預設選項以及每個 app 的個別設定.</string>
<string name="pref_autofill_apps_title">app 及網站設定</string>
<string name="pref_autofill_default_title">使用自動選取</string>
<string name="pref_autofill_default_hint">無自定設定的 app 將預設使用 \'自動選取\' . 否則將使用\'手動\'</string>
@ -96,13 +93,6 @@
<string name="send_plaintext_password_to">將密碼以純文字傳送…</string>
<!-- Oreo Autofill -->
<!-- Autofill -->
<string name="autofill_description">在app中自動填入密碼. 此功能只能在 Andorid 4.3 及以上版本中使用. 在 Andorid 5.0 及以上版本中不需要剪貼簿</string>
<string name="autofill_apps_default">使用預設值</string>
<string name="autofill_apps_first">自動選取</string>
<string name="autofill_apps_match_ellipsis">匹配…</string>
<string name="autofill_apps_match">自動填入</string>
<string name="autofill_apps_never">手動</string>
<string name="autofill_apps_delete">刪除</string>
<!-- OpenKeychain errors -->
<!-- Password creation failure -->
<!-- GitException messages -->

View file

@ -5,6 +5,5 @@
<resources>
<bool name="leak_canary_allow_in_non_debuggable_build">true</bool>
<bool name="enable_accessibility_autofill">true</bool>
<bool name="light_status_bar">true</bool>
</resources>

View file

@ -137,9 +137,6 @@
<string name="pref_recently_used_sort_order">Recently used</string>
<string name="pref_autofill_title">Autofill</string>
<string name="pref_autofill_enable_title">Enable Autofill</string>
<string name="pref_autofill_enable_msg">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.</string>
<string name="pref_autofill_enable_msg2">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.</string>
<string name="pref_autofill_enable_msg3">Password Store attempts to match apps with passwords automatically. You can change this default setting and also matching settings per-app.</string>
<string name="pref_autofill_apps_title">App and website settings</string>
<string name="pref_autofill_default_title">Automatically match by default</string>
<string name="pref_autofill_default_hint">Default to \'Automatically match\' for apps without custom settings. Otherwise, \'Never match.\'</string>
@ -266,23 +263,8 @@
<string name="oreo_autofill_enable_dialog_installed_browsers">Autofill support with installed browsers:</string>
<!-- Autofill -->
<string name="autofill_description">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.</string>
<string name="autofill_apps_default">Use default setting</string>
<string name="autofill_apps_first">Automatically match</string>
<string name="autofill_apps_match_ellipsis">Match with…</string>
<string name="autofill_apps_match">Match with</string>
<string name="autofill_apps_never">Never match</string>
<string name="autofill_apps_delete">Delete</string>
<string name="autofill_pick">Pick…</string>
<string name="autofill_pick_and_match">Pick and match…</string>
<string name="autofill_paste">Paste</string>
<string name="autofill_paste_username">Paste username?\n\n%s</string>
<string name="autofill_toast_username">Select an editable field to paste the username.\nUsername is available for %d seconds.</string>
<string name="ssh_key_does_not_exist">Unable to open the ssh private key, please check that the file exists</string>
<string name="new_password_title">New password</string>
<string name="autofill_ins_1_hint">Screenshot of accessibility services</string>
<string name="autofill_ins_2_hint">Screenshot of toggle in accessibility services</string>
<string name="autofill_ins_3_hint">Screenshot of autofill service in action</string>
<string name="clear_saved_passphrase_ssh">Clear saved passphrase for local SSH key</string>
<string name="clear_saved_passphrase_https">Clear saved HTTPS password</string>
<string name="git_operation_remember_passphrase">Remember key passphrase</string>

View file

@ -1,13 +0,0 @@
<!--
~ Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved.
~ SPDX-License-Identifier: GPL-3.0-only
-->
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
android:accessibilityEventTypes="typeViewFocused|typeViewClicked|typeWindowStateChanged|typeWindowContentChanged"
android:accessibilityFeedbackType="feedbackGeneric"
android:accessibilityFlags="flagDefault|flagRetrieveInteractiveWindows|flagReportViewIds"
android:canRetrieveWindowContent="true"
android:description="@string/autofill_description"
android:notificationTimeout="100"
android:summary="@string/autofill_description" />

View file

@ -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" />
<Preference
app:key="autofill_apps"
app:title="@string/pref_autofill_apps_title" />
<CheckBoxPreference
app:defaultValue="true"
app:key="autofill_default"
app:summary="@string/pref_autofill_default_hint"
app:title="@string/pref_autofill_default_title" />
<CheckBoxPreference
app:defaultValue="false"
app:key="autofill_always"
app:title="@string/pref_autofill_always_title" />
<CheckBoxPreference
app:defaultValue="false"
app:key="autofill_full_path"
app:summary="@string/pref_autofill_full_path_hint"
app:title="@string/pref_autofill_full_path_title" />
</PreferenceCategory>
<PreferenceCategory app:title="@string/pref_repository_title">