mirror of
https://github.com/android-password-store/Android-Password-Store
synced 2025-08-31 14:25:28 +00:00
Resolve various memory leaks (#637)
This migrates the clipboard clear logic into a foreground service that allows us to also provide a notification that runs the clear task immediately on click, rather than wait for the timeout. Co-authored-by: Aditya Wasan <adityawasan55@gmail.com> Signed-off-by: Harsh Shandilya <me@msfjarvis.dev>
This commit is contained in:
@@ -9,6 +9,7 @@ All notable changes to this project will be documented in this file.
|
|||||||
- Swipe on password list to synchronize repository
|
- Swipe on password list to synchronize repository
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
- Resolve memory leaks on password decryption
|
||||||
- Can't delete folders containing a password
|
- Can't delete folders containing a password
|
||||||
|
|
||||||
## [1.5.0] - 2020-02-21
|
## [1.5.0] - 2020-02-21
|
||||||
|
@@ -80,9 +80,12 @@ dependencies {
|
|||||||
implementation deps.androidx.annotation
|
implementation deps.androidx.annotation
|
||||||
implementation deps.androidx.appcompat
|
implementation deps.androidx.appcompat
|
||||||
implementation deps.androidx.biometric
|
implementation deps.androidx.biometric
|
||||||
implementation deps.androidx.core_ktx
|
|
||||||
implementation deps.androidx.constraint_layout
|
implementation deps.androidx.constraint_layout
|
||||||
|
implementation deps.androidx.core_ktx
|
||||||
implementation deps.androidx.documentfile
|
implementation deps.androidx.documentfile
|
||||||
|
implementation deps.androidx.lifecycle_runtime_ktx
|
||||||
|
implementation deps.androidx.local_broadcast_manager
|
||||||
|
implementation deps.androidx.material
|
||||||
implementation deps.androidx.preference
|
implementation deps.androidx.preference
|
||||||
implementation deps.androidx.swiperefreshlayout
|
implementation deps.androidx.swiperefreshlayout
|
||||||
constraints {
|
constraints {
|
||||||
@@ -90,7 +93,6 @@ dependencies {
|
|||||||
because 'versions above 1.0.0 have an accessibility related bug that causes crashes'
|
because 'versions above 1.0.0 have an accessibility related bug that causes crashes'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
implementation deps.androidx.material
|
|
||||||
|
|
||||||
implementation deps.kotlin.coroutines.android
|
implementation deps.kotlin.coroutines.android
|
||||||
implementation deps.kotlin.coroutines.core
|
implementation deps.kotlin.coroutines.core
|
||||||
|
@@ -14,6 +14,7 @@
|
|||||||
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
|
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
|
||||||
<!--suppress DeprecatedClassUsageInspection -->
|
<!--suppress DeprecatedClassUsageInspection -->
|
||||||
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
|
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
|
||||||
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:name=".Application"
|
android:name=".Application"
|
||||||
@@ -53,6 +54,9 @@
|
|||||||
android:name="android.accessibilityservice"
|
android:name="android.accessibilityservice"
|
||||||
android:resource="@xml/autofill_config" />
|
android:resource="@xml/autofill_config" />
|
||||||
</service>
|
</service>
|
||||||
|
<service
|
||||||
|
android:name=".ClipboardService"
|
||||||
|
android:process=":clipboard_service_process" />
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".autofill.AutofillActivity"
|
android:name=".autofill.AutofillActivity"
|
||||||
|
156
app/src/main/java/com/zeapo/pwdstore/ClipboardService.kt
Normal file
156
app/src/main/java/com/zeapo/pwdstore/ClipboardService.kt
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved.
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
*/
|
||||||
|
package com.zeapo.pwdstore
|
||||||
|
|
||||||
|
import android.app.NotificationChannel
|
||||||
|
import android.app.NotificationManager
|
||||||
|
import android.app.PendingIntent
|
||||||
|
import android.app.Service
|
||||||
|
import android.content.ClipboardManager
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.SharedPreferences
|
||||||
|
import android.os.Build
|
||||||
|
import android.os.IBinder
|
||||||
|
import androidx.core.app.NotificationCompat
|
||||||
|
import androidx.core.content.getSystemService
|
||||||
|
import androidx.localbroadcastmanager.content.LocalBroadcastManager
|
||||||
|
import androidx.preference.PreferenceManager
|
||||||
|
import com.zeapo.pwdstore.utils.ClipboardUtils
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.cancel
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.isActive
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import timber.log.Timber
|
||||||
|
|
||||||
|
class ClipboardService : Service() {
|
||||||
|
|
||||||
|
private val scope = CoroutineScope(Job() + Dispatchers.Main)
|
||||||
|
private val settings: SharedPreferences by lazy { PreferenceManager.getDefaultSharedPreferences(this) }
|
||||||
|
|
||||||
|
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||||
|
if (intent != null) {
|
||||||
|
when (intent.action) {
|
||||||
|
ACTION_CLEAR -> {
|
||||||
|
clearClipboard()
|
||||||
|
stopForeground(true)
|
||||||
|
stopSelf()
|
||||||
|
return super.onStartCommand(intent, flags, startId)
|
||||||
|
}
|
||||||
|
|
||||||
|
ACTION_START -> {
|
||||||
|
val time = try {
|
||||||
|
Integer.parseInt(settings.getString("general_show_time", "45") as String)
|
||||||
|
} catch (e: NumberFormatException) {
|
||||||
|
45
|
||||||
|
}
|
||||||
|
|
||||||
|
if (time == 0) {
|
||||||
|
stopSelf()
|
||||||
|
}
|
||||||
|
|
||||||
|
createNotification()
|
||||||
|
scope.launch {
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
startTimer(time)
|
||||||
|
}
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
emitBroadcast()
|
||||||
|
clearClipboard()
|
||||||
|
stopForeground(true)
|
||||||
|
stopSelf()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return START_NOT_STICKY
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.onStartCommand(intent, flags, startId)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBind(intent: Intent?): IBinder? {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroy() {
|
||||||
|
scope.cancel()
|
||||||
|
super.onDestroy()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun clearClipboard() {
|
||||||
|
val deepClear = settings.getBoolean("clear_clipboard_20x", false)
|
||||||
|
val clipboardManager = getSystemService<ClipboardManager>()
|
||||||
|
|
||||||
|
if (clipboardManager is ClipboardManager) {
|
||||||
|
scope.launch {
|
||||||
|
ClipboardUtils.clearClipboard(clipboardManager, deepClear)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Timber.tag("ClipboardService").d("Cannot get clipboard manager service")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun startTimer(showTime: Int) {
|
||||||
|
var current = 0
|
||||||
|
while (scope.isActive && current < showTime) {
|
||||||
|
// Block for 1s or until cancel is signalled
|
||||||
|
current++
|
||||||
|
delay(1000)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun emitBroadcast() {
|
||||||
|
val localBroadcastManager = LocalBroadcastManager.getInstance(this)
|
||||||
|
val clearIntent = Intent(ACTION_CLEAR)
|
||||||
|
localBroadcastManager.sendBroadcast(clearIntent)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createNotification() {
|
||||||
|
createNotificationChannel()
|
||||||
|
val clearIntent = Intent(this, ClipboardService::class.java)
|
||||||
|
clearIntent.action = ACTION_CLEAR
|
||||||
|
val pendingIntent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
PendingIntent.getForegroundService(this, 0, clearIntent, PendingIntent.FLAG_UPDATE_CURRENT)
|
||||||
|
} else {
|
||||||
|
PendingIntent.getService(this, 0, clearIntent, PendingIntent.FLAG_UPDATE_CURRENT)
|
||||||
|
}
|
||||||
|
|
||||||
|
val notification = NotificationCompat.Builder(this, CHANNEL_ID)
|
||||||
|
.setContentTitle(getString(R.string.app_name))
|
||||||
|
.setContentText(getString(R.string.tap_clear_clipboard))
|
||||||
|
.setSmallIcon(R.drawable.ic_launcher_foreground)
|
||||||
|
.setContentIntent(pendingIntent)
|
||||||
|
.setUsesChronometer(true)
|
||||||
|
.setPriority(NotificationCompat.PRIORITY_LOW)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
startForeground(1, notification)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createNotificationChannel() {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
val serviceChannel = NotificationChannel(
|
||||||
|
CHANNEL_ID,
|
||||||
|
getString(R.string.app_name),
|
||||||
|
NotificationManager.IMPORTANCE_LOW
|
||||||
|
)
|
||||||
|
val manager = getSystemService<NotificationManager>()
|
||||||
|
if (manager != null) {
|
||||||
|
manager.createNotificationChannel(serviceChannel)
|
||||||
|
} else {
|
||||||
|
Timber.tag("ClipboardService").d("Failed to create notification channel")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
companion object {
|
||||||
|
private const val ACTION_CLEAR = "ACTION_CLEAR_CLIPBOARD"
|
||||||
|
private const val ACTION_START = "ACTION_START_CLIPBOARD_TIMER"
|
||||||
|
private const val CHANNEL_ID = "NotificationService"
|
||||||
|
}
|
||||||
|
}
|
@@ -254,6 +254,11 @@ class PasswordStore : AppCompatActivity() {
|
|||||||
return super.onOptionsItemSelected(item)
|
return super.onOptionsItemSelected(item)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onDestroy() {
|
||||||
|
plist = null
|
||||||
|
super.onDestroy()
|
||||||
|
}
|
||||||
|
|
||||||
fun openSettings(view: View?) {
|
fun openSettings(view: View?) {
|
||||||
val intent: Intent
|
val intent: Intent
|
||||||
try {
|
try {
|
||||||
|
@@ -518,7 +518,7 @@ class AutofillService : AccessibilityService(), CoroutineScope by CoroutineScope
|
|||||||
if (entry?.hasUsername() == true) {
|
if (entry?.hasUsername() == true) {
|
||||||
lastPassword = entry
|
lastPassword = entry
|
||||||
val ttl = Integer.parseInt(settings!!.getString("general_show_time", "45")!!)
|
val ttl = Integer.parseInt(settings!!.getString("general_show_time", "45")!!)
|
||||||
Toast.makeText(applicationContext, getString(R.string.autofill_toast_username, ttl), Toast.LENGTH_LONG).show()
|
withContext(Dispatchers.Main) { Toast.makeText(applicationContext, getString(R.string.autofill_toast_username, ttl), Toast.LENGTH_LONG).show() }
|
||||||
lastPasswordMaxDate = System.currentTimeMillis() + ttl * 1000L
|
lastPasswordMaxDate = System.currentTimeMillis() + ttl * 1000L
|
||||||
}
|
}
|
||||||
} catch (e: UnsupportedEncodingException) {
|
} catch (e: UnsupportedEncodingException) {
|
||||||
@@ -537,7 +537,7 @@ class AutofillService : AccessibilityService(), CoroutineScope by CoroutineScope
|
|||||||
OpenPgpApi.RESULT_CODE_ERROR -> {
|
OpenPgpApi.RESULT_CODE_ERROR -> {
|
||||||
val error = result.getParcelableExtra<OpenPgpError>(OpenPgpApi.RESULT_ERROR)
|
val error = result.getParcelableExtra<OpenPgpError>(OpenPgpApi.RESULT_ERROR)
|
||||||
if (error != null) {
|
if (error != null) {
|
||||||
Toast.makeText(applicationContext, "Error from OpenKeyChain : ${error.message}", Toast.LENGTH_LONG).show()
|
withContext(Dispatchers.Main) { 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 getErrorId: ${error.errorId}")
|
||||||
Timber.tag(Constants.TAG).e("onError getMessage: ${error.message}")
|
Timber.tag(Constants.TAG).e("onError getMessage: ${error.message}")
|
||||||
}
|
}
|
||||||
|
@@ -5,17 +5,17 @@
|
|||||||
package com.zeapo.pwdstore.crypto
|
package com.zeapo.pwdstore.crypto
|
||||||
|
|
||||||
import android.app.PendingIntent
|
import android.app.PendingIntent
|
||||||
|
import android.content.BroadcastReceiver
|
||||||
import android.content.ClipData
|
import android.content.ClipData
|
||||||
import android.content.ClipboardManager
|
import android.content.ClipboardManager
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.content.IntentFilter
|
||||||
import android.content.IntentSender
|
import android.content.IntentSender
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import android.graphics.Typeface
|
import android.graphics.Typeface
|
||||||
import android.os.AsyncTask
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.ConditionVariable
|
|
||||||
import android.os.Handler
|
|
||||||
import android.text.TextUtils
|
import android.text.TextUtils
|
||||||
import android.text.format.DateUtils
|
import android.text.format.DateUtils
|
||||||
import android.text.method.PasswordTransformationMethod
|
import android.text.method.PasswordTransformationMethod
|
||||||
@@ -27,13 +27,15 @@ import android.view.View
|
|||||||
import android.view.WindowManager
|
import android.view.WindowManager
|
||||||
import android.widget.Button
|
import android.widget.Button
|
||||||
import android.widget.CheckBox
|
import android.widget.CheckBox
|
||||||
import android.widget.ProgressBar
|
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.constraintlayout.widget.ConstraintLayout
|
import androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import androidx.localbroadcastmanager.content.LocalBroadcastManager
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import com.google.android.material.snackbar.Snackbar
|
import com.google.android.material.snackbar.Snackbar
|
||||||
|
import com.zeapo.pwdstore.ClipboardService
|
||||||
import com.zeapo.pwdstore.PasswordEntry
|
import com.zeapo.pwdstore.PasswordEntry
|
||||||
import com.zeapo.pwdstore.R
|
import com.zeapo.pwdstore.R
|
||||||
import com.zeapo.pwdstore.UserPreference
|
import com.zeapo.pwdstore.UserPreference
|
||||||
@@ -52,7 +54,6 @@ import kotlinx.android.synthetic.main.encrypt_layout.crypto_password_edit
|
|||||||
import kotlinx.android.synthetic.main.encrypt_layout.crypto_password_file_edit
|
import kotlinx.android.synthetic.main.encrypt_layout.crypto_password_file_edit
|
||||||
import kotlinx.android.synthetic.main.encrypt_layout.generate_password
|
import kotlinx.android.synthetic.main.encrypt_layout.generate_password
|
||||||
import kotlinx.coroutines.Dispatchers.IO
|
import kotlinx.coroutines.Dispatchers.IO
|
||||||
import kotlinx.coroutines.GlobalScope
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import me.msfjarvis.openpgpktx.util.OpenPgpApi
|
import me.msfjarvis.openpgpktx.util.OpenPgpApi
|
||||||
import me.msfjarvis.openpgpktx.util.OpenPgpApi.Companion.ACTION_DECRYPT_VERIFY
|
import me.msfjarvis.openpgpktx.util.OpenPgpApi.Companion.ACTION_DECRYPT_VERIFY
|
||||||
@@ -96,10 +97,15 @@ class PgpActivity : AppCompatActivity(), OpenPgpServiceConnection.OnBound {
|
|||||||
private val relativeParentPath: String by lazy { getParentPath(fullPath, repoPath) }
|
private val relativeParentPath: String by lazy { getParentPath(fullPath, repoPath) }
|
||||||
|
|
||||||
val settings: SharedPreferences by lazy { PreferenceManager.getDefaultSharedPreferences(this) }
|
val settings: SharedPreferences by lazy { PreferenceManager.getDefaultSharedPreferences(this) }
|
||||||
private val keyIDs: MutableSet<String> by lazy {
|
private val keyIDs get() = _keyIDs
|
||||||
settings.getStringSet("openpgp_key_ids_set", mutableSetOf()) ?: emptySet()
|
private var _keyIDs = emptySet<String>()
|
||||||
}
|
|
||||||
private var mServiceConnection: OpenPgpServiceConnection? = null
|
private var mServiceConnection: OpenPgpServiceConnection? = null
|
||||||
|
private var delayTask: DelayShow? = null
|
||||||
|
private val receiver = object : BroadcastReceiver() {
|
||||||
|
override fun onReceive(context: Context?, intent: Intent?) {
|
||||||
|
delayTask?.doOnPostExecute()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
@@ -107,6 +113,7 @@ class PgpActivity : AppCompatActivity(), OpenPgpServiceConnection.OnBound {
|
|||||||
Timber.tag(TAG)
|
Timber.tag(TAG)
|
||||||
|
|
||||||
// some persistence
|
// some persistence
|
||||||
|
_keyIDs = settings.getStringSet("openpgp_key_ids_set", null) ?: emptySet()
|
||||||
val providerPackageName = settings.getString("openpgp_provider_list", "")
|
val providerPackageName = settings.getString("openpgp_provider_list", "")
|
||||||
|
|
||||||
if (TextUtils.isEmpty(providerPackageName)) {
|
if (TextUtils.isEmpty(providerPackageName)) {
|
||||||
@@ -127,7 +134,7 @@ class PgpActivity : AppCompatActivity(), OpenPgpServiceConnection.OnBound {
|
|||||||
crypto_password_file.text = name
|
crypto_password_file.text = name
|
||||||
crypto_password_file.setOnLongClickListener {
|
crypto_password_file.setOnLongClickListener {
|
||||||
val clip = ClipData.newPlainText("pgp_handler_result_pm", name)
|
val clip = ClipData.newPlainText("pgp_handler_result_pm", name)
|
||||||
clipboard.setPrimaryClip(clip)
|
clipboard.primaryClip = clip
|
||||||
showSnackbar(this.resources.getString(R.string.clipboard_username_toast_text))
|
showSnackbar(this.resources.getString(R.string.clipboard_username_toast_text))
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
@@ -157,6 +164,16 @@ class PgpActivity : AppCompatActivity(), OpenPgpServiceConnection.OnBound {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
LocalBroadcastManager.getInstance(this).registerReceiver(receiver, IntentFilter(ACTION_CLEAR))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onStop() {
|
||||||
|
LocalBroadcastManager.getInstance(this).unregisterReceiver(receiver)
|
||||||
|
super.onStop()
|
||||||
|
}
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
checkAndIncrementHotp()
|
checkAndIncrementHotp()
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
@@ -258,7 +275,7 @@ class PgpActivity : AppCompatActivity(), OpenPgpServiceConnection.OnBound {
|
|||||||
val iStream = FileUtils.openInputStream(File(fullPath))
|
val iStream = FileUtils.openInputStream(File(fullPath))
|
||||||
val oStream = ByteArrayOutputStream()
|
val oStream = ByteArrayOutputStream()
|
||||||
|
|
||||||
GlobalScope.launch(IO) {
|
lifecycleScope.launch(IO) {
|
||||||
api?.executeApiAsync(data, iStream, oStream, object : OpenPgpApi.IOpenPgpCallback {
|
api?.executeApiAsync(data, iStream, oStream, object : OpenPgpApi.IOpenPgpCallback {
|
||||||
override fun onReturn(result: Intent?) {
|
override fun onReturn(result: Intent?) {
|
||||||
when (result?.getIntExtra(RESULT_CODE, RESULT_CODE_ERROR)) {
|
when (result?.getIntExtra(RESULT_CODE, RESULT_CODE_ERROR)) {
|
||||||
@@ -470,7 +487,7 @@ class PgpActivity : AppCompatActivity(), OpenPgpServiceConnection.OnBound {
|
|||||||
|
|
||||||
val path = if (intent.getBooleanExtra("fromDecrypt", false)) fullPath else "$fullPath/$editName.gpg"
|
val path = if (intent.getBooleanExtra("fromDecrypt", false)) fullPath else "$fullPath/$editName.gpg"
|
||||||
|
|
||||||
GlobalScope.launch(IO) {
|
lifecycleScope.launch(IO) {
|
||||||
api?.executeApiAsync(data, iStream, oStream, object : OpenPgpApi.IOpenPgpCallback {
|
api?.executeApiAsync(data, iStream, oStream, object : OpenPgpApi.IOpenPgpCallback {
|
||||||
override fun onReturn(result: Intent?) {
|
override fun onReturn(result: Intent?) {
|
||||||
when (result?.getIntExtra(RESULT_CODE, RESULT_CODE_ERROR)) {
|
when (result?.getIntExtra(RESULT_CODE, RESULT_CODE_ERROR)) {
|
||||||
@@ -582,7 +599,7 @@ class PgpActivity : AppCompatActivity(), OpenPgpServiceConnection.OnBound {
|
|||||||
private fun getKeyIds(receivedIntent: Intent? = null) {
|
private fun getKeyIds(receivedIntent: Intent? = null) {
|
||||||
val data = receivedIntent ?: Intent()
|
val data = receivedIntent ?: Intent()
|
||||||
data.action = OpenPgpApi.ACTION_GET_KEY_IDS
|
data.action = OpenPgpApi.ACTION_GET_KEY_IDS
|
||||||
GlobalScope.launch(IO) {
|
lifecycleScope.launch(IO) {
|
||||||
api?.executeApiAsync(data, null, null, object : OpenPgpApi.IOpenPgpCallback {
|
api?.executeApiAsync(data, null, null, object : OpenPgpApi.IOpenPgpCallback {
|
||||||
override fun onReturn(result: Intent?) {
|
override fun onReturn(result: Intent?) {
|
||||||
when (result?.getIntExtra(RESULT_CODE, RESULT_CODE_ERROR)) {
|
when (result?.getIntExtra(RESULT_CODE, RESULT_CODE_ERROR)) {
|
||||||
@@ -689,7 +706,7 @@ class PgpActivity : AppCompatActivity(), OpenPgpServiceConnection.OnBound {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val clip = ClipData.newPlainText("pgp_handler_result_pm", pass)
|
val clip = ClipData.newPlainText("pgp_handler_result_pm", pass)
|
||||||
clipboard.setPrimaryClip(clip)
|
clipboard.primaryClip = clip
|
||||||
|
|
||||||
var clearAfter = 45
|
var clearAfter = 45
|
||||||
try {
|
try {
|
||||||
@@ -708,13 +725,13 @@ class PgpActivity : AppCompatActivity(), OpenPgpServiceConnection.OnBound {
|
|||||||
|
|
||||||
private fun copyUsernameToClipBoard(username: String) {
|
private fun copyUsernameToClipBoard(username: String) {
|
||||||
val clip = ClipData.newPlainText("pgp_handler_result_pm", username)
|
val clip = ClipData.newPlainText("pgp_handler_result_pm", username)
|
||||||
clipboard.setPrimaryClip(clip)
|
clipboard.primaryClip = clip
|
||||||
showSnackbar(resources.getString(R.string.clipboard_username_toast_text))
|
showSnackbar(resources.getString(R.string.clipboard_username_toast_text))
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun copyOtpToClipBoard(code: String) {
|
private fun copyOtpToClipBoard(code: String) {
|
||||||
val clip = ClipData.newPlainText("pgp_handler_result_pm", code)
|
val clip = ClipData.newPlainText("pgp_handler_result_pm", code)
|
||||||
clipboard.setPrimaryClip(clip)
|
clipboard.primaryClip = clip
|
||||||
showSnackbar(resources.getString(R.string.clipboard_otp_toast_text))
|
showSnackbar(resources.getString(R.string.clipboard_otp_toast_text))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -741,8 +758,8 @@ class PgpActivity : AppCompatActivity(), OpenPgpServiceConnection.OnBound {
|
|||||||
delayTask?.cancelAndSignal(true)
|
delayTask?.cancelAndSignal(true)
|
||||||
|
|
||||||
// launch a new one
|
// launch a new one
|
||||||
delayTask = DelayShow(this)
|
delayTask = DelayShow()
|
||||||
delayTask?.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR)
|
delayTask?.execute()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -758,11 +775,10 @@ class PgpActivity : AppCompatActivity(), OpenPgpServiceConnection.OnBound {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("StaticFieldLeak")
|
@Suppress("StaticFieldLeak")
|
||||||
inner class DelayShow(val activity: PgpActivity) : AsyncTask<Void, Int, Boolean>() {
|
inner class DelayShow {
|
||||||
private val pb: ProgressBar? by lazy { pbLoading }
|
|
||||||
private var skip = false
|
|
||||||
private var cancelNotify = ConditionVariable()
|
|
||||||
|
|
||||||
|
private var skip = false
|
||||||
|
private var service: Intent? = null
|
||||||
private var showTime: Int = 0
|
private var showTime: Int = 0
|
||||||
|
|
||||||
// Custom cancellation that can be triggered from another thread.
|
// Custom cancellation that can be triggered from another thread.
|
||||||
@@ -772,14 +788,25 @@ class PgpActivity : AppCompatActivity(), OpenPgpServiceConnection.OnBound {
|
|||||||
// is true, the cancelled task won't clear the clipboard.
|
// is true, the cancelled task won't clear the clipboard.
|
||||||
fun cancelAndSignal(skipClearing: Boolean) {
|
fun cancelAndSignal(skipClearing: Boolean) {
|
||||||
skip = skipClearing
|
skip = skipClearing
|
||||||
cancelNotify.open()
|
if (service != null) {
|
||||||
|
stopService(service)
|
||||||
|
service = null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val settings: SharedPreferences by lazy {
|
fun execute() {
|
||||||
PreferenceManager.getDefaultSharedPreferences(activity)
|
service = Intent(this@PgpActivity, ClipboardService::class.java).also {
|
||||||
|
it.action = ACTION_START
|
||||||
|
}
|
||||||
|
doOnPreExecute()
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
startForegroundService(service)
|
||||||
|
} else {
|
||||||
|
startService(service)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPreExecute() {
|
private fun doOnPreExecute() {
|
||||||
showTime = try {
|
showTime = try {
|
||||||
Integer.parseInt(settings.getString("general_show_time", "45") as String)
|
Integer.parseInt(settings.getString("general_show_time", "45") as String)
|
||||||
} catch (e: NumberFormatException) {
|
} catch (e: NumberFormatException) {
|
||||||
@@ -793,49 +820,12 @@ class PgpActivity : AppCompatActivity(), OpenPgpServiceConnection.OnBound {
|
|||||||
|
|
||||||
if (extraText?.text?.isNotEmpty() == true)
|
if (extraText?.text?.isNotEmpty() == true)
|
||||||
findViewById<View>(R.id.crypto_extra_show_layout)?.visibility = View.VISIBLE
|
findViewById<View>(R.id.crypto_extra_show_layout)?.visibility = View.VISIBLE
|
||||||
|
|
||||||
if (showTime == 0) {
|
|
||||||
// treat 0 as forever, and the user must exit and/or clear clipboard on their own
|
|
||||||
cancel(true)
|
|
||||||
} else {
|
|
||||||
this.pb?.max = showTime
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun doInBackground(vararg params: Void): Boolean? {
|
fun doOnPostExecute() {
|
||||||
var current = 0
|
|
||||||
while (current < showTime) {
|
|
||||||
|
|
||||||
// Block for 1s or until cancel is signalled
|
|
||||||
if (cancelNotify.block(1000)) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
current++
|
|
||||||
publishProgress(current)
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onPostExecute(b: Boolean?) {
|
|
||||||
if (skip) return
|
if (skip) return
|
||||||
checkAndIncrementHotp()
|
checkAndIncrementHotp()
|
||||||
|
|
||||||
// No need to validate clear_after_copy. It was validated in copyPasswordToClipBoard()
|
|
||||||
Timber.d("Clearing the clipboard")
|
|
||||||
val clip = ClipData.newPlainText("pgp_handler_result_pm", "")
|
|
||||||
clipboard.setPrimaryClip(clip)
|
|
||||||
if (settings.getBoolean("clear_clipboard_20x", false)) {
|
|
||||||
val handler = Handler()
|
|
||||||
for (i in 0..19) {
|
|
||||||
val count = i.toString()
|
|
||||||
handler.postDelayed(
|
|
||||||
{ clipboard.setPrimaryClip(ClipData.newPlainText(count, count)) },
|
|
||||||
(i * 500).toLong()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (crypto_password_show != null) {
|
if (crypto_password_show != null) {
|
||||||
// clear password; if decrypt changed to encrypt layout via edit button, no need
|
// clear password; if decrypt changed to encrypt layout via edit button, no need
|
||||||
if (passwordEntry?.hotpIsIncremented() == false) {
|
if (passwordEntry?.hotpIsIncremented() == false) {
|
||||||
@@ -849,10 +839,6 @@ class PgpActivity : AppCompatActivity(), OpenPgpServiceConnection.OnBound {
|
|||||||
finish()
|
finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onProgressUpdate(vararg values: Int?) {
|
|
||||||
this.pb?.progress = values[0] ?: 0
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@@ -860,13 +846,14 @@ class PgpActivity : AppCompatActivity(), OpenPgpServiceConnection.OnBound {
|
|||||||
const val REQUEST_DECRYPT = 202
|
const val REQUEST_DECRYPT = 202
|
||||||
const val REQUEST_KEY_ID = 203
|
const val REQUEST_KEY_ID = 203
|
||||||
|
|
||||||
|
private const val ACTION_CLEAR = "ACTION_CLEAR_CLIPBOARD"
|
||||||
|
private const val ACTION_START = "ACTION_START_CLIPBOARD_TIMER"
|
||||||
|
|
||||||
const val TAG = "PgpActivity"
|
const val TAG = "PgpActivity"
|
||||||
|
|
||||||
const val KEY_PWGEN_TYPE_CLASSIC = "classic"
|
const val KEY_PWGEN_TYPE_CLASSIC = "classic"
|
||||||
const val KEY_PWGEN_TYPE_XKPASSWD = "xkpasswd"
|
const val KEY_PWGEN_TYPE_XKPASSWD = "xkpasswd"
|
||||||
|
|
||||||
private var delayTask: DelayShow? = null
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the relative path to the repository
|
* Gets the relative path to the repository
|
||||||
*/
|
*/
|
||||||
|
28
app/src/main/java/com/zeapo/pwdstore/utils/ClipboardUtils.kt
Normal file
28
app/src/main/java/com/zeapo/pwdstore/utils/ClipboardUtils.kt
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved.
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
*/
|
||||||
|
package com.zeapo.pwdstore.utils
|
||||||
|
|
||||||
|
import android.content.ClipData
|
||||||
|
import android.content.ClipboardManager
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import timber.log.Timber
|
||||||
|
|
||||||
|
object ClipboardUtils {
|
||||||
|
|
||||||
|
suspend fun clearClipboard(clipboard: ClipboardManager, deepClear: Boolean = false) {
|
||||||
|
Timber.d("Clearing the clipboard")
|
||||||
|
val clip = ClipData.newPlainText("pgp_handler_result_pm", "")
|
||||||
|
clipboard.primaryClip = clip
|
||||||
|
if (deepClear) {
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
repeat(20) {
|
||||||
|
val count = (it * 500).toString()
|
||||||
|
clipboard.primaryClip = ClipData.newPlainText(count, count)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -94,16 +94,6 @@
|
|||||||
app:layout_constraintBaseline_toBaselineOf="@id/crypto_password_show_label"
|
app:layout_constraintBaseline_toBaselineOf="@id/crypto_password_show_label"
|
||||||
android:typeface="monospace" />
|
android:typeface="monospace" />
|
||||||
|
|
||||||
<ProgressBar
|
|
||||||
android:id="@+id/pbLoading"
|
|
||||||
style="?android:attr/progressBarStyleHorizontal"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="8dp"
|
|
||||||
android:layout_marginBottom="8dp"
|
|
||||||
android:visibility="invisible"
|
|
||||||
android:layout_marginTop="8dp"
|
|
||||||
app:layout_constraintTop_toBottomOf="@id/crypto_password_show_label"/>
|
|
||||||
|
|
||||||
<com.google.android.material.button.MaterialButton
|
<com.google.android.material.button.MaterialButton
|
||||||
style="@style/Widget.MaterialComponents.Button"
|
style="@style/Widget.MaterialComponents.Button"
|
||||||
android:id="@+id/crypto_password_toggle_show"
|
android:id="@+id/crypto_password_toggle_show"
|
||||||
|
@@ -305,5 +305,5 @@
|
|||||||
<string name="pref_search_from_root">Always search from root</string>
|
<string name="pref_search_from_root">Always search from root</string>
|
||||||
<string name="pref_search_from_root_hint">Search from root of store regardless of currently open directory</string>
|
<string name="pref_search_from_root_hint">Search from root of store regardless of currently open directory</string>
|
||||||
<string name="password_generator_category_title">Password Generator</string>
|
<string name="password_generator_category_title">Password Generator</string>
|
||||||
|
<string name="tap_clear_clipboard">Tap here to clear clipboard</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
@@ -34,6 +34,8 @@ ext.deps = [
|
|||||||
constraint_layout: 'androidx.constraintlayout:constraintlayout:2.0.0-beta4',
|
constraint_layout: 'androidx.constraintlayout:constraintlayout:2.0.0-beta4',
|
||||||
core_ktx: 'androidx.core:core-ktx:1.3.0-alpha01',
|
core_ktx: 'androidx.core:core-ktx:1.3.0-alpha01',
|
||||||
documentfile: 'androidx.documentfile:documentfile:1.0.1',
|
documentfile: 'androidx.documentfile:documentfile:1.0.1',
|
||||||
|
lifecycle_runtime_ktx: 'androidx.lifecycle:lifecycle-runtime-ktx:2.2.0-alpha01',
|
||||||
|
local_broadcast_manager: 'androidx.localbroadcastmanager:localbroadcastmanager:1.0.0',
|
||||||
material: 'com.google.android.material:material:1.2.0-alpha05',
|
material: 'com.google.android.material:material:1.2.0-alpha05',
|
||||||
preference: 'androidx.preference:preference:1.1.0',
|
preference: 'androidx.preference:preference:1.1.0',
|
||||||
recycler_view: 'androidx.recyclerview:recyclerview:1.0.0',
|
recycler_view: 'androidx.recyclerview:recyclerview:1.0.0',
|
||||||
|
Reference in New Issue
Block a user