New clipboard manager #1343

This commit is contained in:
J-Jamet
2022-05-31 18:22:01 +02:00
parent 531ebcae85
commit 6c9f359fae
12 changed files with 96 additions and 113 deletions

View File

@@ -1,5 +1,6 @@
KeePassDX(3.4.5)
* Fix device credential unlocking #1344
* New clipboard manager #1343
* Keep screen on by default when viewing an entry
KeePassDX(3.4.4)

View File

@@ -4,14 +4,14 @@ apply plugin: 'kotlin-parcelize'
apply plugin: 'kotlin-kapt'
android {
compileSdkVersion 31
buildToolsVersion "31.0.0"
compileSdkVersion 32
buildToolsVersion "32.0.0"
ndkVersion "21.4.7075529"
defaultConfig {
applicationId "com.kunzisoft.keepass"
minSdkVersion 15
targetSdkVersion 31
targetSdkVersion 32
versionCode = 114
versionName = "3.4.5"
multiDexEnabled true

View File

@@ -27,7 +27,6 @@ import com.kunzisoft.keepass.view.TemplateView
import com.kunzisoft.keepass.view.hideByFading
import com.kunzisoft.keepass.view.showByFading
import com.kunzisoft.keepass.viewmodels.EntryViewModel
import java.util.*
class EntryFragment: DatabaseFragment() {
@@ -158,11 +157,9 @@ class EntryFragment: DatabaseFragment() {
setOnCopyActionClickListener { field ->
mClipboardHelper?.timeoutCopyToClipboard(
TemplateField.getLocalizedName(context, field.name),
field.protectedValue.stringValue,
getString(
R.string.copy_field,
TemplateField.getLocalizedName(context, field.name)
)
field.protectedValue.isProtected
)
}
}
@@ -251,8 +248,7 @@ class EntryFragment: DatabaseFragment() {
fun launchEntryCopyEducationAction() {
val appNameString = getString(R.string.app_name)
mClipboardHelper?.timeoutCopyToClipboard(appNameString,
getString(R.string.copy_field, appNameString))
mClipboardHelper?.timeoutCopyToClipboard(appNameString, appNameString)
}
companion object {

View File

@@ -78,9 +78,11 @@ class PassphraseGeneratorFragment : DatabaseFragment() {
View.VISIBLE else View.GONE
val clipboardHelper = ClipboardHelper(context)
passphraseCopyView?.setOnClickListener {
clipboardHelper.timeoutCopyToClipboard(passKeyView.passwordString,
getString(R.string.copy_field,
getString(R.string.entry_password)))
clipboardHelper.timeoutCopyToClipboard(
getString(R.string.passphrase),
passKeyView.passwordString,
true
)
}
wordCaseAdapter = ArrayAdapter(context,

View File

@@ -99,9 +99,11 @@ class PasswordGeneratorFragment : DatabaseFragment() {
View.VISIBLE else View.GONE
val clipboardHelper = ClipboardHelper(context)
passwordCopyView?.setOnClickListener {
clipboardHelper.timeoutCopyToClipboard(passKeyView.passwordString,
getString(R.string.copy_field,
getString(R.string.entry_password)))
clipboardHelper.timeoutCopyToClipboard(
getString(R.string.password),
passKeyView.passwordString,
true
)
}
}

View File

@@ -21,13 +21,13 @@ package com.kunzisoft.keepass.adapters
import android.content.Context
import android.graphics.Color
import android.util.Log
import android.util.TypedValue
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import android.widget.Toast
import androidx.annotation.ColorInt
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.SortedList
@@ -521,13 +521,14 @@ class NodesAdapter (private val context: Context,
}
holder?.otpContainer?.setOnClickListener {
otpElement?.token?.let { token ->
Toast.makeText(
context,
context.getString(R.string.copy_field,
TemplateField.getLocalizedName(context, TemplateField.LABEL_TOKEN)),
Toast.LENGTH_LONG
).show()
mClipboardHelper.copyToClipboard(token)
try {
mClipboardHelper.copyToClipboard(
TemplateField.getLocalizedName(context, TemplateField.LABEL_TOKEN),
token
)
} catch (e: Exception) {
Log.e(TAG, "Unable to copy the OTP token", e)
}
}
}
}

View File

@@ -1,22 +0,0 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.exception
class ClipboardException(e: Exception) : Exception(e)

View File

@@ -33,6 +33,10 @@ class ClipboardEntryNotificationField : Parcelable {
private var id: NotificationFieldId = NotificationFieldId.UNKNOWN
var label: String = ""
val isSensitive: Boolean
get() {
return id == NotificationFieldId.PASSWORD
}
val actionKey: String
get() = getActionKey(id)

View File

@@ -31,7 +31,6 @@ import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.timeout.ClipboardHelper
import com.kunzisoft.keepass.timeout.TimeoutHelper.NEVER
import com.kunzisoft.keepass.utils.LOCK_ACTION
import java.util.*
class ClipboardEntryNotificationService : LockNotificationService() {
@@ -75,7 +74,7 @@ class ClipboardEntryNotificationService : LockNotificationService() {
}
ACTION_CLEAN_CLIPBOARD == intent.action -> {
mTimerJob?.cancel()
cleanClipboard()
clipboardHelper?.cleanClipboard()
stopNotificationAndSendLockIfNeeded()
}
else -> for (actionKey in ClipboardEntryNotificationField.allActionKeys) {
@@ -153,7 +152,11 @@ class ClipboardEntryNotificationService : LockNotificationService() {
try {
var generatedValue = fieldToCopy.getGeneratedValue(mEntryInfo)
clipboardHelper?.copyToClipboard(fieldToCopy.label, generatedValue)
clipboardHelper?.copyToClipboard(
fieldToCopy.label,
generatedValue,
fieldToCopy.isSensitive
)
val builder = buildNewNotification()
.setSmallIcon(R.drawable.notification_ic_clipboard_key_24dp)
@@ -186,13 +189,17 @@ class ClipboardEntryNotificationService : LockNotificationService() {
// New auto generated value
if (generatedValue != newGeneratedValue) {
generatedValue = newGeneratedValue
clipboardHelper?.copyToClipboard(fieldToCopy.label, generatedValue)
clipboardHelper?.copyToClipboard(
fieldToCopy.label,
generatedValue,
fieldToCopy.isSensitive
)
}
}) {
stopNotificationAndSendLockIfNeeded()
// Clean password only if no next field
if (nextFields.size <= 0)
cleanClipboard()
clipboardHelper?.cleanClipboard()
}
} else {
// No timer
@@ -202,25 +209,15 @@ class ClipboardEntryNotificationService : LockNotificationService() {
} catch (e: Exception) {
Log.e(TAG, "Clipboard can't be populate", e)
}
}
private fun cleanClipboard() {
try {
clipboardHelper?.cleanClipboard()
} catch (e: Exception) {
Log.e(TAG, "Clipboard can't be cleaned", e)
}
}
override fun onTaskRemoved(rootIntent: Intent?) {
cleanClipboard()
clipboardHelper?.cleanClipboard()
super.onTaskRemoved(rootIntent)
}
override fun onDestroy() {
cleanClipboard()
clipboardHelper?.cleanClipboard()
super.onDestroy()
}

View File

@@ -24,117 +24,117 @@ import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.os.Build
import android.os.PersistableBundle
import android.text.SpannableString
import android.text.method.LinkMovementMethod
import android.text.util.Linkify
import android.util.Log
import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.exception.ClipboardException
import com.kunzisoft.keepass.settings.PreferencesUtil
import java.util.*
class ClipboardHelper(private val context: Context) {
class ClipboardHelper(context: Context) {
private var mAppContext = context.applicationContext
private var mClipboardManager: ClipboardManager? = null
private val mTimer = Timer()
private fun getClipboardManager(): ClipboardManager? {
if (mClipboardManager == null)
mClipboardManager = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager?
mClipboardManager = mAppContext.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager?
return mClipboardManager
}
fun timeoutCopyToClipboard(text: String, toastString: String = "") {
if (toastString.isNotEmpty())
Toast.makeText(context, toastString, Toast.LENGTH_LONG).show()
fun timeoutCopyToClipboard(label: String, text: String, sensitive: Boolean = false) {
try {
copyToClipboard(text)
} catch (e: ClipboardException) {
copyToClipboard(label, text, sensitive)
} catch (e: Exception) {
showClipboardErrorDialog()
return
}
val clipboardTimeout = PreferencesUtil.getClipboardTimeout(context)
val clipboardTimeout = PreferencesUtil.getClipboardTimeout(mAppContext)
if (clipboardTimeout > 0) {
mTimer.schedule(ClearClipboardTask(context, text), clipboardTimeout)
mTimer.schedule(ClearClipboardTask(text), clipboardTimeout)
}
}
fun getClipboard(context: Context): CharSequence {
if (getClipboardManager()?.hasPrimaryClip() == true) {
val data = getClipboardManager()?.primaryClip
if (data != null && data.itemCount > 0) {
val text = data.getItemAt(0).coerceToText(context)
if (text != null) {
return text
fun copyToClipboard(label: String, value: String, sensitive: Boolean = false) {
getClipboardManager()?.setPrimaryClip(ClipData.newPlainText(DEFAULT_LABEL, value).apply {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
description.extras = PersistableBundle().apply {
putBoolean("android.content.extra.IS_SENSITIVE", sensitive)
}
}
})
if (label.isNotEmpty() && Build.VERSION.SDK_INT < Build.VERSION_CODES.S_V2) {
Toast.makeText(
mAppContext,
mAppContext.getString(
R.string.copy_field,
label
),
Toast.LENGTH_LONG
).show()
}
return ""
}
@Throws(ClipboardException::class)
fun copyToClipboard(value: String) {
copyToClipboard("", value)
}
@Throws(ClipboardException::class)
fun copyToClipboard(label: String, value: String) {
try {
getClipboardManager()?.setPrimaryClip(ClipData.newPlainText(label, value))
} catch (e: Exception) {
throw ClipboardException(e)
}
}
@Throws(ClipboardException::class)
fun cleanClipboard(label: String = "") {
fun cleanClipboard() {
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
getClipboardManager()?.clearPrimaryClip()
} else {
copyToClipboard(label, "")
copyToClipboard(DEFAULT_LABEL, "")
}
} catch (e: Exception) {
throw ClipboardException(e)
Log.e("ClipboardHelper", "Unable to clean the clipboard", e)
}
}
// Task which clears the clipboard, and sends a toast to the foreground.
private inner class ClearClipboardTask (private val mCtx: Context,
private val mClearText: String) : TimerTask() {
private inner class ClearClipboardTask (private val mClearText: String) : TimerTask() {
override fun run() {
val currentClip = getClipboard(mCtx).toString()
if (currentClip == mClearText) {
try {
cleanClipboard()
R.string.clipboard_cleared
} catch (e: ClipboardException) {
R.string.clipboard_error_clear
if (getClipboard(mAppContext).toString() == mClearText) {
cleanClipboard()
}
}
private fun getClipboard(context: Context): CharSequence {
if (getClipboardManager()?.hasPrimaryClip() == true) {
val data = getClipboardManager()?.primaryClip
if (data != null && data.itemCount > 0) {
val text = data.getItemAt(0).coerceToText(context)
if (text != null) {
return text
}
}
}
return ""
}
}
private fun showClipboardErrorDialog() {
val textDescription = context.getString(R.string.clipboard_error)
val textDescription = mAppContext.getString(R.string.clipboard_error)
val spannableString = SpannableString(textDescription)
val textView = TextView(context).apply {
val textView = TextView(mAppContext).apply {
text = spannableString
autoLinkMask = Activity.RESULT_OK
movementMethod = LinkMovementMethod.getInstance()
}
Linkify.addLinks(spannableString, Linkify.WEB_URLS)
AlertDialog.Builder(context)
AlertDialog.Builder(mAppContext)
.setTitle(R.string.clipboard_error_title)
.setView(textView)
.setPositiveButton(android.R.string.ok) { dialog, _ -> dialog.dismiss() }
.show()
}
companion object {
private const val DEFAULT_LABEL = ""
}
}

View File

@@ -1,2 +1,3 @@
* Fix device credential unlocking #1344
* New clipboard manager #1343
* Keep screen on by default when viewing an entry

View File

@@ -1,2 +1,3 @@
* Correction du déblocage par identifiants de l'appareil #1344
* Nouveau gestionnaire de presse-papier #1343
* Garder l'écran allumé par défaut lors d'une visualisation d'entrée