mirror of
https://github.com/Kunzisoft/KeePassDX.git
synced 2025-12-04 15:49:33 +01:00
Better temp advanced unlocking implementation
This commit is contained in:
@@ -19,10 +19,7 @@
|
||||
*/
|
||||
package com.kunzisoft.keepass.app.database
|
||||
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.ServiceConnection
|
||||
import android.content.*
|
||||
import android.net.Uri
|
||||
import android.os.IBinder
|
||||
import android.util.Log
|
||||
@@ -42,66 +39,95 @@ class CipherDatabaseAction(context: Context) {
|
||||
// Temp DAO to easily remove content if object no longer in memory
|
||||
private var useTempDao = PreferencesUtil.isTempAdvancedUnlockEnable(applicationContext)
|
||||
|
||||
private val mIntentAdvancedUnlockService = Intent(applicationContext,
|
||||
AdvancedUnlockNotificationService::class.java)
|
||||
private var mBinder: AdvancedUnlockNotificationService.AdvancedUnlockBinder? = null
|
||||
private var mServiceConnection: ServiceConnection? = null
|
||||
|
||||
private var mDatabaseListeners = LinkedList<DatabaseListener>()
|
||||
private var mDatabaseListeners = LinkedList<CipherDatabaseListener>()
|
||||
private var mAdvancedUnlockBroadcastReceiver = AdvancedUnlockNotificationService.AdvancedUnlockReceiver {
|
||||
deleteAll()
|
||||
removeAllDataAndDetach()
|
||||
}
|
||||
|
||||
fun reloadPreferences() {
|
||||
useTempDao = PreferencesUtil.isTempAdvancedUnlockEnable(applicationContext)
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
private fun attachService(performedAction: () -> Unit) {
|
||||
// Check if a service is currently running else do nothing
|
||||
if (mBinder != null) {
|
||||
private fun serviceActionTask(startService: Boolean = false, performedAction: () -> Unit) {
|
||||
// Check if a service is currently running else call action without info
|
||||
if (startService && mServiceConnection == null) {
|
||||
attachService(performedAction)
|
||||
} else {
|
||||
performedAction.invoke()
|
||||
} else if (mServiceConnection == null) {
|
||||
mServiceConnection = object : ServiceConnection {
|
||||
override fun onServiceConnected(name: ComponentName?, serviceBinder: IBinder?) {
|
||||
mBinder = (serviceBinder as AdvancedUnlockNotificationService.AdvancedUnlockBinder)
|
||||
performedAction.invoke()
|
||||
}
|
||||
|
||||
override fun onServiceDisconnected(name: ComponentName?) {
|
||||
mBinder = null
|
||||
mServiceConnection = null
|
||||
mDatabaseListeners.forEach {
|
||||
it.onDatabaseCleared()
|
||||
}
|
||||
}
|
||||
}
|
||||
applicationContext.bindService(mIntentAdvancedUnlockService,
|
||||
mServiceConnection!!,
|
||||
Context.BIND_ABOVE_CLIENT)
|
||||
if (mBinder == null) {
|
||||
try {
|
||||
applicationContext.startService(mIntentAdvancedUnlockService)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Unable to start cipher action", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun registerDatabaseListener(listener: DatabaseListener) {
|
||||
mDatabaseListeners.add(listener)
|
||||
@Synchronized
|
||||
private fun attachService(performedAction: () -> Unit) {
|
||||
applicationContext.registerReceiver(mAdvancedUnlockBroadcastReceiver, IntentFilter().apply {
|
||||
addAction(AdvancedUnlockNotificationService.REMOVE_ADVANCED_UNLOCK_KEY_ACTION)
|
||||
})
|
||||
|
||||
mServiceConnection = object : ServiceConnection {
|
||||
override fun onServiceConnected(name: ComponentName?, serviceBinder: IBinder?) {
|
||||
mBinder = (serviceBinder as AdvancedUnlockNotificationService.AdvancedUnlockBinder)
|
||||
performedAction.invoke()
|
||||
}
|
||||
|
||||
override fun onServiceDisconnected(name: ComponentName?) {
|
||||
onClear()
|
||||
}
|
||||
}
|
||||
try {
|
||||
AdvancedUnlockNotificationService.bindService(applicationContext,
|
||||
mServiceConnection!!,
|
||||
Context.BIND_AUTO_CREATE)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Unable to start cipher action", e)
|
||||
performedAction.invoke()
|
||||
}
|
||||
}
|
||||
|
||||
fun unregisterDatabaseListener(listener: DatabaseListener) {
|
||||
mDatabaseListeners.remove(listener)
|
||||
@Synchronized
|
||||
private fun detachService() {
|
||||
try {
|
||||
applicationContext.unregisterReceiver(mAdvancedUnlockBroadcastReceiver)
|
||||
} catch (e: Exception) {}
|
||||
|
||||
mServiceConnection?.let {
|
||||
AdvancedUnlockNotificationService.unbindService(applicationContext, it)
|
||||
}
|
||||
}
|
||||
|
||||
interface DatabaseListener {
|
||||
fun onDatabaseCleared()
|
||||
private fun removeAllDataAndDetach() {
|
||||
detachService()
|
||||
onClear()
|
||||
}
|
||||
|
||||
fun registerDatabaseListener(listenerCipher: CipherDatabaseListener) {
|
||||
mDatabaseListeners.add(listenerCipher)
|
||||
}
|
||||
|
||||
fun unregisterDatabaseListener(listenerCipher: CipherDatabaseListener) {
|
||||
mDatabaseListeners.remove(listenerCipher)
|
||||
}
|
||||
|
||||
private fun onClear() {
|
||||
mBinder = null
|
||||
mServiceConnection = null
|
||||
mDatabaseListeners.forEach {
|
||||
it.onCipherDatabaseCleared()
|
||||
}
|
||||
}
|
||||
|
||||
interface CipherDatabaseListener {
|
||||
fun onCipherDatabaseCleared()
|
||||
}
|
||||
|
||||
fun getCipherDatabase(databaseUri: Uri,
|
||||
cipherDatabaseResultListener: (CipherDatabaseEntity?) -> Unit) {
|
||||
if (useTempDao) {
|
||||
attachService {
|
||||
serviceActionTask {
|
||||
cipherDatabaseResultListener.invoke(mBinder?.getCipherDatabase(databaseUri))
|
||||
}
|
||||
} else {
|
||||
@@ -126,7 +152,8 @@ class CipherDatabaseAction(context: Context) {
|
||||
fun addOrUpdateCipherDatabase(cipherDatabaseEntity: CipherDatabaseEntity,
|
||||
cipherDatabaseResultListener: (() -> Unit)? = null) {
|
||||
if (useTempDao) {
|
||||
attachService {
|
||||
// The only case to create service (not needed to get an info)
|
||||
serviceActionTask(true) {
|
||||
mBinder?.addOrUpdateCipherDatabase(cipherDatabaseEntity)
|
||||
cipherDatabaseResultListener?.invoke()
|
||||
}
|
||||
@@ -151,7 +178,7 @@ class CipherDatabaseAction(context: Context) {
|
||||
fun deleteByDatabaseUri(databaseUri: Uri,
|
||||
cipherDatabaseResultListener: (() -> Unit)? = null) {
|
||||
if (useTempDao) {
|
||||
attachService {
|
||||
serviceActionTask {
|
||||
mBinder?.deleteByDatabaseUri(databaseUri)
|
||||
cipherDatabaseResultListener?.invoke()
|
||||
}
|
||||
@@ -168,14 +195,19 @@ class CipherDatabaseAction(context: Context) {
|
||||
}
|
||||
|
||||
fun deleteAll() {
|
||||
attachService {
|
||||
mBinder?.deleteAll()
|
||||
if (useTempDao) {
|
||||
serviceActionTask {
|
||||
mBinder?.deleteAll()
|
||||
}
|
||||
}
|
||||
// To erase the residues
|
||||
IOActionTask(
|
||||
{
|
||||
cipherDatabaseDao.deleteAll()
|
||||
}
|
||||
).execute()
|
||||
// Unbind
|
||||
removeAllDataAndDetach()
|
||||
}
|
||||
|
||||
companion object : SingletonHolderParameter<CipherDatabaseAction, Context>(::CipherDatabaseAction) {
|
||||
|
||||
@@ -36,7 +36,6 @@ import com.kunzisoft.keepass.activities.stylish.StylishFragment
|
||||
import com.kunzisoft.keepass.app.database.CipherDatabaseAction
|
||||
import com.kunzisoft.keepass.database.exception.IODatabaseException
|
||||
import com.kunzisoft.keepass.education.PasswordActivityEducation
|
||||
import com.kunzisoft.keepass.services.AdvancedUnlockNotificationService
|
||||
import com.kunzisoft.keepass.settings.PreferencesUtil
|
||||
import com.kunzisoft.keepass.view.AdvancedUnlockInfoView
|
||||
|
||||
@@ -68,7 +67,7 @@ class AdvancedUnlockFragment: StylishFragment(), AdvancedUnlockManager.AdvancedU
|
||||
|
||||
private lateinit var cipherDatabaseAction : CipherDatabaseAction
|
||||
|
||||
private var cipherDatabaseListener: CipherDatabaseAction.DatabaseListener? = null
|
||||
private var cipherDatabaseListener: CipherDatabaseAction.CipherDatabaseListener? = null
|
||||
|
||||
// Only to fix multiple fingerprint menu #332
|
||||
private var mAllowAdvancedUnlockMenu = false
|
||||
@@ -402,9 +401,10 @@ class AdvancedUnlockFragment: StylishFragment(), AdvancedUnlockManager.AdvancedU
|
||||
fun connect(databaseUri: Uri) {
|
||||
showViews(true)
|
||||
this.databaseFileUri = databaseUri
|
||||
cipherDatabaseListener = object: CipherDatabaseAction.DatabaseListener {
|
||||
override fun onDatabaseCleared() {
|
||||
deleteEncryptedDatabaseKey()
|
||||
cipherDatabaseListener = object: CipherDatabaseAction.CipherDatabaseListener {
|
||||
override fun onCipherDatabaseCleared() {
|
||||
advancedUnlockManager?.closeBiometricPrompt()
|
||||
checkUnlockAvailability()
|
||||
}
|
||||
}
|
||||
cipherDatabaseAction.apply {
|
||||
@@ -435,14 +435,12 @@ class AdvancedUnlockFragment: StylishFragment(), AdvancedUnlockManager.AdvancedU
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.M)
|
||||
fun deleteEncryptedDatabaseKey() {
|
||||
allowOpenBiometricPrompt = false
|
||||
mAdvancedUnlockInfoView?.setIconViewClickListener(false, null)
|
||||
advancedUnlockManager?.closeBiometricPrompt()
|
||||
databaseFileUri?.let { databaseUri ->
|
||||
cipherDatabaseAction.deleteByDatabaseUri(databaseUri) {
|
||||
checkUnlockAvailability()
|
||||
}
|
||||
}
|
||||
} ?: checkUnlockAvailability()
|
||||
}
|
||||
|
||||
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
|
||||
@@ -479,7 +477,6 @@ class AdvancedUnlockFragment: StylishFragment(), AdvancedUnlockManager.AdvancedU
|
||||
mBuilderListener?.retrieveCredentialForEncryption()?.let { credential ->
|
||||
advancedUnlockManager?.encryptData(credential)
|
||||
}
|
||||
AdvancedUnlockNotificationService.startServiceForTimeout(requireContext())
|
||||
}
|
||||
Mode.EXTRACT_CREDENTIAL -> {
|
||||
// retrieve the encrypted value from preferences
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
package com.kunzisoft.keepass.services
|
||||
|
||||
import android.app.PendingIntent
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.*
|
||||
import android.net.Uri
|
||||
import android.os.Binder
|
||||
import android.os.IBinder
|
||||
@@ -46,58 +45,46 @@ class AdvancedUnlockNotificationService : NotificationService() {
|
||||
return getString(R.string.advanced_unlock)
|
||||
}
|
||||
|
||||
override fun onBind(intent: Intent): IBinder? {
|
||||
super.onBind(intent)
|
||||
return mActionTaskBinder
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
mTempCipherDao = ArrayList()
|
||||
}
|
||||
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
super.onStartCommand(intent, flags, startId)
|
||||
override fun onBind(intent: Intent): IBinder {
|
||||
super.onBind(intent)
|
||||
|
||||
val deleteIntent = Intent(this, AdvancedUnlockNotificationService::class.java).apply {
|
||||
action = ACTION_REMOVE_KEYS
|
||||
}
|
||||
val pendingDeleteIntent = PendingIntent.getService(this, 0, deleteIntent, PendingIntent.FLAG_UPDATE_CURRENT)
|
||||
val pendingDeleteIntent = PendingIntent.getBroadcast(this,
|
||||
4577, Intent(REMOVE_ADVANCED_UNLOCK_KEY_ACTION), 0)
|
||||
val biometricUnlockEnabled = PreferencesUtil.isBiometricUnlockEnable(this)
|
||||
val notificationBuilder = buildNewNotification().apply {
|
||||
val notificationBuilder = buildNewNotification().apply {
|
||||
setSmallIcon(if (biometricUnlockEnabled) {
|
||||
R.drawable.notification_ic_fingerprint_unlock_24dp
|
||||
} else {
|
||||
R.drawable.notification_ic_device_unlock_24dp
|
||||
})
|
||||
intent?.let {
|
||||
setContentTitle(getString(R.string.advanced_unlock))
|
||||
}
|
||||
setContentTitle(getString(R.string.advanced_unlock))
|
||||
setContentText(getString(R.string.advanced_unlock_tap_delete))
|
||||
setContentIntent(pendingDeleteIntent)
|
||||
// Unfortunately swipe is disabled in lollipop+
|
||||
setDeleteIntent(pendingDeleteIntent)
|
||||
}
|
||||
|
||||
when (intent?.action) {
|
||||
ACTION_TIMEOUT -> {
|
||||
val notificationTimeoutMilliSecs = PreferencesUtil.getAdvancedUnlockTimeout(this)
|
||||
// Not necessarily a foreground service
|
||||
if (mTimerJob == null && notificationTimeoutMilliSecs != TimeoutHelper.NEVER) {
|
||||
defineTimerJob(notificationBuilder, notificationTimeoutMilliSecs) {
|
||||
stopSelf()
|
||||
}
|
||||
} else {
|
||||
startForeground(notificationId, notificationBuilder.build())
|
||||
}
|
||||
val notificationTimeoutMilliSecs = PreferencesUtil.getAdvancedUnlockTimeout(this)
|
||||
// Not necessarily a foreground service
|
||||
if (mTimerJob == null && notificationTimeoutMilliSecs != TimeoutHelper.NEVER) {
|
||||
defineTimerJob(notificationBuilder, notificationTimeoutMilliSecs) {
|
||||
sendBroadcast(Intent(REMOVE_ADVANCED_UNLOCK_KEY_ACTION))
|
||||
}
|
||||
ACTION_REMOVE_KEYS -> {
|
||||
stopSelf()
|
||||
}
|
||||
else -> {}
|
||||
} else {
|
||||
startForeground(notificationId, notificationBuilder.build())
|
||||
}
|
||||
|
||||
return START_STICKY
|
||||
return mActionTaskBinder
|
||||
}
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
mTempCipherDao = ArrayList()
|
||||
override fun onUnbind(intent: Intent?): Boolean {
|
||||
stopSelf()
|
||||
return super.onUnbind(intent)
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
@@ -105,22 +92,32 @@ class AdvancedUnlockNotificationService : NotificationService() {
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val CHANNEL_ADVANCED_UNLOCK_ID = "com.kunzisoft.keepass.notification.channel.unlock"
|
||||
|
||||
private const val ACTION_TIMEOUT = "ACTION_TIMEOUT"
|
||||
private const val ACTION_REMOVE_KEYS = "ACTION_REMOVE_KEYS"
|
||||
|
||||
fun startServiceForTimeout(context: Context) {
|
||||
if (PreferencesUtil.isTempAdvancedUnlockEnable(context)) {
|
||||
context.startService(Intent(context, AdvancedUnlockNotificationService::class.java).apply {
|
||||
action = ACTION_TIMEOUT
|
||||
})
|
||||
class AdvancedUnlockReceiver(var removeKeyAction: () -> Unit): BroadcastReceiver() {
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
intent.action?.let {
|
||||
when (it) {
|
||||
REMOVE_ADVANCED_UNLOCK_KEY_ACTION -> {
|
||||
removeKeyAction.invoke()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun stopService(context: Context) {
|
||||
context.stopService(Intent(context, AdvancedUnlockNotificationService::class.java))
|
||||
companion object {
|
||||
private const val CHANNEL_ADVANCED_UNLOCK_ID = "com.kunzisoft.keepass.notification.channel.unlock"
|
||||
const val REMOVE_ADVANCED_UNLOCK_KEY_ACTION = "com.kunzisoft.keepass.REMOVE_ADVANCED_UNLOCK_KEY"
|
||||
|
||||
// Only one service connection
|
||||
fun bindService(context: Context, serviceConnection: ServiceConnection, flags: Int) {
|
||||
context.bindService(Intent(context,
|
||||
AdvancedUnlockNotificationService::class.java),
|
||||
serviceConnection,
|
||||
flags)
|
||||
}
|
||||
|
||||
fun unbindService(context: Context, serviceConnection: ServiceConnection) {
|
||||
context.unbindService(serviceConnection)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -45,7 +45,6 @@ import com.kunzisoft.keepass.app.database.FileDatabaseHistoryAction
|
||||
import com.kunzisoft.keepass.biometric.AdvancedUnlockManager
|
||||
import com.kunzisoft.keepass.education.Education
|
||||
import com.kunzisoft.keepass.icons.IconPackChooser
|
||||
import com.kunzisoft.keepass.services.AdvancedUnlockNotificationService
|
||||
import com.kunzisoft.keepass.settings.preference.IconPackListPreference
|
||||
import com.kunzisoft.keepass.settings.preferencedialogfragment.DurationDialogFragmentCompat
|
||||
import com.kunzisoft.keepass.utils.UriUtil
|
||||
@@ -374,7 +373,6 @@ class NestedAppSettingsFragment : NestedSettingsFragment() {
|
||||
}
|
||||
})
|
||||
}
|
||||
AdvancedUnlockNotificationService.stopService(activity.applicationContext)
|
||||
CipherDatabaseAction.getInstance(activity.applicationContext).deleteAll()
|
||||
}
|
||||
.setNegativeButton(resources.getString(android.R.string.cancel)
|
||||
|
||||
Reference in New Issue
Block a user