Autofill UX improvements and code cleanup (#626)
Co-authored-by: Aditya Wasan <adityawasan55@gmail.com> Signed-off-by: Harsh Shandilya <msfjarvis@gmail.com>
This commit is contained in:
parent
d350a27611
commit
74bb1c4357
3 changed files with 64 additions and 71 deletions
|
@ -90,6 +90,10 @@ dependencies {
|
|||
}
|
||||
}
|
||||
implementation deps.androidx.material
|
||||
|
||||
implementation deps.kotlin.coroutines.android
|
||||
implementation deps.kotlin.coroutines.core
|
||||
|
||||
implementation deps.third_party.commons_io
|
||||
implementation deps.third_party.commons_codec
|
||||
implementation deps.third_party.fastscroll
|
||||
|
|
|
@ -6,8 +6,6 @@ package com.zeapo.pwdstore.autofill
|
|||
|
||||
import android.accessibilityservice.AccessibilityService
|
||||
import android.app.PendingIntent
|
||||
import android.content.ClipData
|
||||
import android.content.ClipboardManager
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.SharedPreferences
|
||||
|
@ -15,15 +13,16 @@ import android.content.pm.ApplicationInfo
|
|||
import android.content.pm.PackageManager
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
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.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.zeapo.pwdstore.PasswordEntry
|
||||
|
@ -39,6 +38,11 @@ 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.apache.commons.io.FileUtils
|
||||
|
@ -46,7 +50,7 @@ import org.openintents.openpgp.IOpenPgpService2
|
|||
import org.openintents.openpgp.OpenPgpError
|
||||
import timber.log.Timber
|
||||
|
||||
class AutofillService : AccessibilityService() {
|
||||
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)
|
||||
|
@ -76,6 +80,12 @@ class AutofillService : AccessibilityService() {
|
|||
instance = this
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
instance = null
|
||||
cancel()
|
||||
}
|
||||
|
||||
override fun onServiceConnected() {
|
||||
super.onServiceConnected()
|
||||
serviceConnection = OpenPgpServiceConnection(this@AutofillService, "org.sufficientlysecure.keychain")
|
||||
|
@ -135,7 +145,7 @@ class AutofillService : AccessibilityService() {
|
|||
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(event)
|
||||
dismissDialog()
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -145,7 +155,7 @@ class AutofillService : AccessibilityService() {
|
|||
return
|
||||
} else {
|
||||
// nothing to do if not password field focus
|
||||
dismissDialog(event)
|
||||
dismissDialog()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
@ -182,9 +192,7 @@ class AutofillService : AccessibilityService() {
|
|||
if (info == null) return
|
||||
|
||||
// save the dialog's corresponding window so we can use getWindows() in dismissDialog
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
window = info!!.window
|
||||
}
|
||||
window = info!!.window
|
||||
|
||||
val packageName: String
|
||||
val appName: String
|
||||
|
@ -246,18 +254,8 @@ class AutofillService : AccessibilityService() {
|
|||
}
|
||||
|
||||
// dismiss the dialog if the window has changed
|
||||
private fun dismissDialog(event: AccessibilityEvent) {
|
||||
// the default keyboard showing/hiding is a window state changed event
|
||||
// on Android 5+ we can use getWindows() to determine when the original window is not visible
|
||||
// on Android 4.3 we have to use window state changed events and filter out the keyboard ones
|
||||
// there may be other exceptions...
|
||||
val dismiss: Boolean = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
!windows.contains(window)
|
||||
} else {
|
||||
!(event.eventType == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED &&
|
||||
event.packageName != null &&
|
||||
event.packageName.toString().contains("inputmethod"))
|
||||
}
|
||||
private fun dismissDialog() {
|
||||
val dismiss = !windows.contains(window)
|
||||
if (dismiss && dialog != null && dialog!!.isShowing) {
|
||||
dialog!!.dismiss()
|
||||
dialog = null
|
||||
|
@ -380,9 +378,12 @@ class AutofillService : AccessibilityService() {
|
|||
builder.setMessage(getString(R.string.autofill_paste_username, password.username))
|
||||
|
||||
dialog = builder.create()
|
||||
this.setDialogType(dialog)
|
||||
dialog!!.window!!.addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE)
|
||||
dialog!!.window!!.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
|
||||
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()
|
||||
}
|
||||
|
||||
|
@ -446,8 +447,8 @@ class AutofillService : AccessibilityService() {
|
|||
}
|
||||
|
||||
dialog = builder.create()
|
||||
setDialogType(dialog)
|
||||
dialog?.window?.apply {
|
||||
setDialogType(this)
|
||||
val density = context.resources.displayMetrics.density
|
||||
addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE)
|
||||
setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE)
|
||||
|
@ -458,15 +459,13 @@ class AutofillService : AccessibilityService() {
|
|||
dialog?.show()
|
||||
}
|
||||
|
||||
private fun setDialogType(dialog: AlertDialog?) {
|
||||
dialog?.window?.apply {
|
||||
setType(
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O)
|
||||
WindowManager.LayoutParams.TYPE_SYSTEM_ALERT
|
||||
else
|
||||
WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
|
||||
)
|
||||
}
|
||||
@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() {}
|
||||
|
@ -482,7 +481,7 @@ class AutofillService : AccessibilityService() {
|
|||
}
|
||||
}
|
||||
|
||||
private fun decryptAndVerify() {
|
||||
private fun decryptAndVerify() = launch {
|
||||
packageName = info!!.packageName
|
||||
val data: Intent
|
||||
if (resultData == null) {
|
||||
|
@ -492,40 +491,45 @@ class AutofillService : AccessibilityService() {
|
|||
data = resultData!!
|
||||
resultData = null
|
||||
}
|
||||
var `is`: InputStream? = null
|
||||
try {
|
||||
`is` = FileUtils.openInputStream(items[lastWhichItem])
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
|
||||
var inputStream: InputStream? = null
|
||||
withContext(Dispatchers.IO) {
|
||||
try {
|
||||
inputStream = FileUtils.openInputStream(items[lastWhichItem])
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
cancel("", e)
|
||||
}
|
||||
}
|
||||
|
||||
val os = ByteArrayOutputStream()
|
||||
|
||||
val api = OpenPgpApi(this@AutofillService, serviceConnection!!.service!!)
|
||||
// TODO we are dropping frames, (did we before??) find out why and maybe make this async
|
||||
val result = api.executeApi(data, `is`, os)
|
||||
val result = api.executeApi(data, inputStream, os)
|
||||
when (result?.getIntExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR)) {
|
||||
OpenPgpApi.RESULT_CODE_SUCCESS -> {
|
||||
try {
|
||||
val entry = PasswordEntry(os)
|
||||
pasteText(info!!, entry.password)
|
||||
|
||||
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()) {
|
||||
if (entry?.hasUsername() == true) {
|
||||
lastPassword = entry
|
||||
val ttl = Integer.parseInt(settings!!.getString("general_show_time", "45")!!)
|
||||
Toast.makeText(this, getString(R.string.autofill_toast_username, ttl), Toast.LENGTH_LONG).show()
|
||||
Toast.makeText(applicationContext, getString(R.string.autofill_toast_username, ttl), Toast.LENGTH_LONG).show()
|
||||
lastPasswordMaxDate = System.currentTimeMillis() + ttl * 1000L
|
||||
}
|
||||
} catch (e: UnsupportedEncodingException) {
|
||||
Timber.tag(Constants.TAG).e(e, "UnsupportedEncodingException")
|
||||
Timber.tag(Constants.TAG).e(e)
|
||||
}
|
||||
}
|
||||
OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED -> {
|
||||
Timber.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(this@AutofillService, AutofillActivity::class.java)
|
||||
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)
|
||||
|
@ -533,9 +537,7 @@ class AutofillService : AccessibilityService() {
|
|||
OpenPgpApi.RESULT_CODE_ERROR -> {
|
||||
val error = result.getParcelableExtra<OpenPgpError>(OpenPgpApi.RESULT_ERROR)
|
||||
if (error != null) {
|
||||
Toast.makeText(this@AutofillService,
|
||||
"Error from OpenKeyChain : " + error.message,
|
||||
Toast.LENGTH_LONG).show()
|
||||
Toast.makeText(applicationContext, "Error from OpenKeyChain : ${error.message}", Toast.LENGTH_LONG).show()
|
||||
Timber.tag(Constants.TAG).e("onError getErrorId: ${error.errorId}")
|
||||
Timber.tag(Constants.TAG).e("onError getMessage: ${error.message}")
|
||||
}
|
||||
|
@ -548,25 +550,8 @@ class AutofillService : AccessibilityService() {
|
|||
// 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)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
val args = Bundle()
|
||||
args.putCharSequence(AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, text)
|
||||
node.performAction(AccessibilityNodeInfo.ACTION_SET_TEXT, args)
|
||||
} else {
|
||||
val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
||||
var clip = ClipData.newPlainText("autofill_pm", text)
|
||||
clipboard.setPrimaryClip(clip)
|
||||
node.performAction(AccessibilityNodeInfo.ACTION_PASTE)
|
||||
|
||||
clip = ClipData.newPlainText("autofill_pm", "")
|
||||
clipboard.setPrimaryClip(clip)
|
||||
if (settings!!.getBoolean("clear_clipboard_20x", false)) {
|
||||
for (i in 0..19) {
|
||||
clip = ClipData.newPlainText(i.toString(), i.toString())
|
||||
clipboard.setPrimaryClip(clip)
|
||||
}
|
||||
}
|
||||
}
|
||||
val args = bundleOf(Pair(AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, text))
|
||||
node.performAction(AccessibilityNodeInfo.ACTION_SET_TEXT, args)
|
||||
node.recycle()
|
||||
}
|
||||
|
||||
|
|
|
@ -20,6 +20,10 @@ ext.deps = [
|
|||
],
|
||||
|
||||
kotlin: [
|
||||
coroutines: [
|
||||
android: 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.3',
|
||||
core: 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.3',
|
||||
],
|
||||
stdlib8: 'org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.3.61'
|
||||
],
|
||||
|
||||
|
|
Loading…
Reference in a new issue