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:
parent
260145ce16
commit
4a9151870d
32 changed files with 39 additions and 1796 deletions
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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")!!))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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 |
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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 -->
|
||||
|
|
|
@ -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 -->
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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 -->
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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 -->
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
|
@ -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 -->
|
||||
|
|
|
@ -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 -->
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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" />
|
|
@ -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">
|
||||
|
|
Loading…
Reference in a new issue