From e5bb69ea5f12a35bef4d1494cb31328df2a5a2e7 Mon Sep 17 00:00:00 2001 From: J-Jamet Date: Tue, 23 Nov 2021 18:28:01 +0100 Subject: [PATCH 01/10] Fix startActivityResult for Autofill --- .../activities/AutofillLauncherActivity.kt | 20 +++----- .../keepass/activities/EntryEditActivity.kt | 6 ++- .../activities/FileDatabaseSelectActivity.kt | 21 +++++--- .../keepass/activities/GroupActivity.kt | 22 +++++--- .../keepass/activities/PasswordActivity.kt | 28 +++++++---- .../keepass/autofill/AutofillHelper.kt | 50 +++++++++++-------- 6 files changed, 88 insertions(+), 59 deletions(-) diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/AutofillLauncherActivity.kt b/app/src/main/java/com/kunzisoft/keepass/activities/AutofillLauncherActivity.kt index aedef5a1f..db7d6eb1f 100644 --- a/app/src/main/java/com/kunzisoft/keepass/activities/AutofillLauncherActivity.kt +++ b/app/src/main/java/com/kunzisoft/keepass/activities/AutofillLauncherActivity.kt @@ -26,6 +26,7 @@ import android.content.Intent import android.os.Build import android.view.inputmethod.InlineSuggestionsRequest import android.widget.Toast +import androidx.activity.result.ActivityResultLauncher import androidx.annotation.RequiresApi import com.kunzisoft.keepass.R import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper @@ -39,11 +40,15 @@ import com.kunzisoft.keepass.database.search.SearchHelper import com.kunzisoft.keepass.model.RegisterInfo import com.kunzisoft.keepass.model.SearchInfo import com.kunzisoft.keepass.settings.PreferencesUtil -import com.kunzisoft.keepass.utils.LOCK_ACTION @RequiresApi(api = Build.VERSION_CODES.O) class AutofillLauncherActivity : DatabaseModeActivity() { + private var mAutofillActivityResultLauncher: ActivityResultLauncher? = + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) + AutofillHelper.buildActivityResultLauncher(this, true) + else null + override fun applyCustomStyle(): Boolean { return false } @@ -118,6 +123,7 @@ class AutofillLauncherActivity : DatabaseModeActivity() { // Show the database UI to select the entry GroupActivity.launchForAutofillResult(this, openedDatabase, + mAutofillActivityResultLauncher, autofillComponent, searchInfo, false) @@ -125,6 +131,7 @@ class AutofillLauncherActivity : DatabaseModeActivity() { { // If database not open FileDatabaseSelectActivity.launchForAutofillResult(this, + mAutofillActivityResultLauncher, autofillComponent, searchInfo) } @@ -185,17 +192,6 @@ class AutofillLauncherActivity : DatabaseModeActivity() { Toast.makeText(this.applicationContext, R.string.autofill_read_only_save, Toast.LENGTH_LONG).show() } - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - AutofillHelper.onActivityResultSetResultAndFinish(this, requestCode, resultCode, data) - - if (PreferencesUtil.isAutofillCloseDatabaseEnable(this)) { - // Close the database - sendBroadcast(Intent(LOCK_ACTION)) - } - - super.onActivityResult(requestCode, resultCode, data) - } - companion object { private const val KEY_MANUAL_SELECTION = "KEY_MANUAL_SELECTION" diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/EntryEditActivity.kt b/app/src/main/java/com/kunzisoft/keepass/activities/EntryEditActivity.kt index d3bde73ca..8726d61aa 100644 --- a/app/src/main/java/com/kunzisoft/keepass/activities/EntryEditActivity.kt +++ b/app/src/main/java/com/kunzisoft/keepass/activities/EntryEditActivity.kt @@ -33,9 +33,11 @@ import android.view.Menu import android.view.MenuItem import android.view.View import android.widget.* +import androidx.activity.result.ActivityResultLauncher import androidx.activity.viewModels import androidx.annotation.RequiresApi import androidx.appcompat.app.AlertDialog +import androidx.appcompat.app.AppCompatActivity import androidx.coordinatorlayout.widget.CoordinatorLayout import androidx.core.view.isVisible import androidx.core.widget.NestedScrollView @@ -802,8 +804,9 @@ class EntryEditActivity : DatabaseLockActivity(), * Launch EntryEditActivity to add a new entry in autofill selection */ @RequiresApi(api = Build.VERSION_CODES.O) - fun launchForAutofillResult(activity: Activity, + fun launchForAutofillResult(activity: AppCompatActivity, database: Database, + activityResultLauncher: ActivityResultLauncher?, autofillComponent: AutofillComponent, groupId: NodeId<*>, searchInfo: SearchInfo? = null) { @@ -814,6 +817,7 @@ class EntryEditActivity : DatabaseLockActivity(), AutofillHelper.startActivityForAutofillResult( activity, intent, + activityResultLauncher, autofillComponent, searchInfo ) diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/FileDatabaseSelectActivity.kt b/app/src/main/java/com/kunzisoft/keepass/activities/FileDatabaseSelectActivity.kt index 5c0d740c1..f853485a6 100644 --- a/app/src/main/java/com/kunzisoft/keepass/activities/FileDatabaseSelectActivity.kt +++ b/app/src/main/java/com/kunzisoft/keepass/activities/FileDatabaseSelectActivity.kt @@ -31,8 +31,10 @@ import android.util.Log import android.view.Menu import android.view.MenuItem import android.view.View +import androidx.activity.result.ActivityResultLauncher import androidx.activity.viewModels import androidx.annotation.RequiresApi +import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.widget.Toolbar import androidx.coordinatorlayout.widget.CoordinatorLayout import androidx.recyclerview.widget.LinearLayoutManager @@ -85,6 +87,11 @@ class FileDatabaseSelectActivity : DatabaseModeActivity(), private var mExternalFileHelper: ExternalFileHelper? = null + private var mAutofillActivityResultLauncher: ActivityResultLauncher? = + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) + AutofillHelper.buildActivityResultLauncher(this) + else null + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -274,7 +281,8 @@ class FileDatabaseSelectActivity : DatabaseModeActivity(), fileNoFoundAction(exception) }, { onCancelSpecialMode() }, - { onLaunchActivitySpecialMode() }) + { onLaunchActivitySpecialMode() }, + mAutofillActivityResultLauncher) } private fun launchGroupActivityIfLoaded(database: Database) { @@ -283,7 +291,8 @@ class FileDatabaseSelectActivity : DatabaseModeActivity(), database, { onValidateSpecialMode() }, { onCancelSpecialMode() }, - { onLaunchActivitySpecialMode() }) + { onLaunchActivitySpecialMode() }, + mAutofillActivityResultLauncher) } } @@ -362,10 +371,6 @@ class FileDatabaseSelectActivity : DatabaseModeActivity(), override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - AutofillHelper.onActivityResultSetResultAndFinish(this, requestCode, resultCode, data) - } - mExternalFileHelper?.onOpenDocumentResult(requestCode, resultCode, data) { uri -> if (uri != null) { launchPasswordActivityWithPath(uri) @@ -499,11 +504,13 @@ class FileDatabaseSelectActivity : DatabaseModeActivity(), */ @RequiresApi(api = Build.VERSION_CODES.O) - fun launchForAutofillResult(activity: Activity, + fun launchForAutofillResult(activity: AppCompatActivity, + activityResultLauncher: ActivityResultLauncher?, autofillComponent: AutofillComponent, searchInfo: SearchInfo? = null) { AutofillHelper.startActivityForAutofillResult(activity, Intent(activity, FileDatabaseSelectActivity::class.java), + activityResultLauncher, autofillComponent, searchInfo) } diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/GroupActivity.kt b/app/src/main/java/com/kunzisoft/keepass/activities/GroupActivity.kt index 8c9bc7ee8..661ed1122 100644 --- a/app/src/main/java/com/kunzisoft/keepass/activities/GroupActivity.kt +++ b/app/src/main/java/com/kunzisoft/keepass/activities/GroupActivity.kt @@ -33,8 +33,10 @@ import android.view.MenuItem import android.view.View import android.view.ViewGroup import android.widget.* +import androidx.activity.result.ActivityResultLauncher import androidx.activity.viewModels import androidx.annotation.RequiresApi +import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.view.ActionMode import androidx.appcompat.widget.SearchView import androidx.appcompat.widget.Toolbar @@ -111,6 +113,11 @@ class GroupActivity : DatabaseLockActivity(), private var mSearchSuggestionAdapter: SearchEntryCursorAdapter? = null private var mOnSuggestionListener: SearchView.OnSuggestionListener? = null + private var mAutofillActivityResultLauncher: ActivityResultLauncher? = + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) + AutofillHelper.buildActivityResultLauncher(this) + else null + private var mIconColor: Int = 0 override fun onCreate(savedInstanceState: Bundle?) { @@ -243,6 +250,7 @@ class GroupActivity : DatabaseLockActivity(), EntryEditActivity.launchForAutofillResult( this@GroupActivity, database, + mAutofillActivityResultLauncher, autofillComponent, currentGroup.nodeId, searchInfo @@ -1080,10 +1088,6 @@ class GroupActivity : DatabaseLockActivity(), mGroupEditViewModel.selectIcon(icon) } - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - AutofillHelper.onActivityResultSetResultAndFinish(this, requestCode, resultCode, data) - } - // Directly used the onActivityResult in fragment mGroupFragment?.onActivityResult(requestCode, resultCode, data) } @@ -1292,8 +1296,9 @@ class GroupActivity : DatabaseLockActivity(), * ------------------------- */ @RequiresApi(api = Build.VERSION_CODES.O) - fun launchForAutofillResult(activity: Activity, + fun launchForAutofillResult(activity: AppCompatActivity, database: Database, + activityResultLaunch: ActivityResultLauncher?, autofillComponent: AutofillComponent, searchInfo: SearchInfo? = null, autoSearch: Boolean = false) { @@ -1303,6 +1308,7 @@ class GroupActivity : DatabaseLockActivity(), AutofillHelper.startActivityForAutofillResult( activity, intent, + activityResultLaunch, autofillComponent, searchInfo ) @@ -1335,11 +1341,12 @@ class GroupActivity : DatabaseLockActivity(), * Global Launch * ------------------------- */ - fun launch(activity: Activity, + fun launch(activity: AppCompatActivity, database: Database, onValidateSpecialMode: () -> Unit, onCancelSpecialMode: () -> Unit, - onLaunchActivitySpecialMode: () -> Unit) { + onLaunchActivitySpecialMode: () -> Unit, + autofillActivityResultLauncher: ActivityResultLauncher?) { EntrySelectionHelper.doSpecialAction(activity.intent, { GroupActivity.launch( @@ -1451,6 +1458,7 @@ class GroupActivity : DatabaseLockActivity(), // Here no search info found, disable auto search GroupActivity.launchForAutofillResult(activity, database, + autofillActivityResultLauncher, autofillComponent, searchInfo, false) diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/PasswordActivity.kt b/app/src/main/java/com/kunzisoft/keepass/activities/PasswordActivity.kt index e2973d645..a6f6c8999 100644 --- a/app/src/main/java/com/kunzisoft/keepass/activities/PasswordActivity.kt +++ b/app/src/main/java/com/kunzisoft/keepass/activities/PasswordActivity.kt @@ -35,9 +35,10 @@ import android.view.KeyEvent.KEYCODE_ENTER import android.view.inputmethod.EditorInfo.IME_ACTION_DONE import android.view.inputmethod.InputMethodManager import android.widget.* -import android.widget.TextView.OnEditorActionListener +import androidx.activity.result.ActivityResultLauncher import androidx.activity.viewModels import androidx.annotation.RequiresApi +import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.widget.Toolbar import androidx.coordinatorlayout.widget.CoordinatorLayout import androidx.core.app.ActivityCompat @@ -75,7 +76,7 @@ import com.kunzisoft.keepass.viewmodels.DatabaseFileViewModel import java.io.FileNotFoundException -open class PasswordActivity : DatabaseModeActivity(), AdvancedUnlockFragment.BuilderListener { +class PasswordActivity : DatabaseModeActivity(), AdvancedUnlockFragment.BuilderListener { // Views private var toolbar: Toolbar? = null @@ -113,6 +114,11 @@ open class PasswordActivity : DatabaseModeActivity(), AdvancedUnlockFragment.Bui private var mAllowAutoOpenBiometricPrompt: Boolean = true + private var mAutofillActivityResultLauncher: ActivityResultLauncher? = + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) + AutofillHelper.buildActivityResultLauncher(this) + else null + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -361,7 +367,8 @@ open class PasswordActivity : DatabaseModeActivity(), AdvancedUnlockFragment.Bui database, { onValidateSpecialMode() }, { onCancelSpecialMode() }, - { onLaunchActivitySpecialMode() } + { onLaunchActivitySpecialMode() }, + mAutofillActivityResultLauncher ) } } @@ -720,11 +727,6 @@ open class PasswordActivity : DatabaseModeActivity(), AdvancedUnlockFragment.Bui // To get device credential unlock result advancedUnlockFragment?.onActivityResult(requestCode, resultCode, data) - // To get entry in result - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - AutofillHelper.onActivityResultSetResultAndFinish(this, requestCode, resultCode, data) - } - var keyFileResult = false mExternalFileHelper?.let { keyFileResult = it.onOpenDocumentResult(requestCode, resultCode, data) { uri -> @@ -855,15 +857,17 @@ open class PasswordActivity : DatabaseModeActivity(), AdvancedUnlockFragment.Bui @RequiresApi(api = Build.VERSION_CODES.O) @Throws(FileNotFoundException::class) - fun launchForAutofillResult(activity: Activity, + fun launchForAutofillResult(activity: AppCompatActivity, databaseFile: Uri, keyFile: Uri?, + activityResultLauncher: ActivityResultLauncher?, autofillComponent: AutofillComponent, searchInfo: SearchInfo?) { buildAndLaunchIntent(activity, databaseFile, keyFile) { intent -> AutofillHelper.startActivityForAutofillResult( activity, intent, + activityResultLauncher, autofillComponent, searchInfo) } @@ -891,12 +895,13 @@ open class PasswordActivity : DatabaseModeActivity(), AdvancedUnlockFragment.Bui * Global Launch * ------------------------- */ - fun launch(activity: Activity, + fun launch(activity: AppCompatActivity, databaseUri: Uri, keyFile: Uri?, fileNoFoundAction: (exception: FileNotFoundException) -> Unit, onCancelSpecialMode: () -> Unit, - onLaunchActivitySpecialMode: () -> Unit) { + onLaunchActivitySpecialMode: () -> Unit, + autofillActivityResultLauncher: ActivityResultLauncher?) { try { EntrySelectionHelper.doSpecialAction(activity.intent, @@ -926,6 +931,7 @@ open class PasswordActivity : DatabaseModeActivity(), AdvancedUnlockFragment.Bui if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { PasswordActivity.launchForAutofillResult(activity, databaseUri, keyFile, + autofillActivityResultLauncher, autofillComponent, searchInfo) onLaunchActivitySpecialMode() diff --git a/app/src/main/java/com/kunzisoft/keepass/autofill/AutofillHelper.kt b/app/src/main/java/com/kunzisoft/keepass/autofill/AutofillHelper.kt index 760639832..98fa80d1b 100644 --- a/app/src/main/java/com/kunzisoft/keepass/autofill/AutofillHelper.kt +++ b/app/src/main/java/com/kunzisoft/keepass/autofill/AutofillHelper.kt @@ -38,7 +38,10 @@ import android.view.inputmethod.InlineSuggestionsRequest import android.widget.RemoteViews import android.widget.Toast import android.widget.inline.InlinePresentationSpec +import androidx.activity.result.ActivityResultLauncher +import androidx.activity.result.contract.ActivityResultContracts import androidx.annotation.RequiresApi +import androidx.appcompat.app.AppCompatActivity import androidx.autofill.inline.UiVersions import androidx.autofill.inline.v1.InlineSuggestionUi import androidx.core.content.ContextCompat @@ -48,19 +51,17 @@ import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper import com.kunzisoft.keepass.activities.helpers.SpecialMode import com.kunzisoft.keepass.database.element.Database import com.kunzisoft.keepass.database.element.icon.IconImage +import com.kunzisoft.keepass.database.element.template.TemplateField import com.kunzisoft.keepass.model.EntryInfo import com.kunzisoft.keepass.model.SearchInfo -import com.kunzisoft.keepass.database.element.template.TemplateField import com.kunzisoft.keepass.settings.AutofillSettingsActivity import com.kunzisoft.keepass.settings.PreferencesUtil -import kotlin.collections.ArrayList +import com.kunzisoft.keepass.utils.LOCK_ACTION @RequiresApi(api = Build.VERSION_CODES.O) object AutofillHelper { - private const val AUTOFILL_RESPONSE_REQUEST_CODE = 8165 - private const val EXTRA_ASSIST_STRUCTURE = AutofillManager.EXTRA_ASSIST_STRUCTURE const val EXTRA_INLINE_SUGGESTIONS_REQUEST = "com.kunzisoft.keepass.autofill.INLINE_SUGGESTIONS_REQUEST" @@ -430,11 +431,33 @@ object AutofillHelper { } } + fun buildActivityResultLauncher(activity: AppCompatActivity, + lockDatabase: Boolean = false): ActivityResultLauncher { + return activity.registerForActivityResult( + ActivityResultContracts.StartActivityForResult() + ) { + // Utility method to loop and close each activity with return data + if (it.resultCode == Activity.RESULT_OK) { + activity.setResult(it.resultCode, it.data) + } + if (it.resultCode == Activity.RESULT_CANCELED) { + activity.setResult(Activity.RESULT_CANCELED) + } + activity.finish() + + if (lockDatabase && PreferencesUtil.isAutofillCloseDatabaseEnable(activity)) { + // Close the database + activity.sendBroadcast(Intent(LOCK_ACTION)) + } + } + } + /** * Utility method to start an activity with an Autofill for result */ - fun startActivityForAutofillResult(activity: Activity, + fun startActivityForAutofillResult(activity: AppCompatActivity, intent: Intent, + activityResultLauncher: ActivityResultLauncher?, autofillComponent: AutofillComponent, searchInfo: SearchInfo?) { EntrySelectionHelper.addSpecialModeInIntent(intent, SpecialMode.SELECTION) @@ -446,21 +469,6 @@ object AutofillHelper { } } EntrySelectionHelper.addSearchInfoInIntent(intent, searchInfo) - activity.startActivityForResult(intent, AUTOFILL_RESPONSE_REQUEST_CODE) - } - - /** - * Utility method to loop and close each activity with return data - */ - fun onActivityResultSetResultAndFinish(activity: Activity, requestCode: Int, resultCode: Int, data: Intent?) { - if (requestCode == AUTOFILL_RESPONSE_REQUEST_CODE) { - if (resultCode == Activity.RESULT_OK) { - activity.setResult(resultCode, data) - } - if (resultCode == Activity.RESULT_CANCELED) { - activity.setResult(Activity.RESULT_CANCELED) - } - activity.finish() - } + activityResultLauncher?.launch(intent) } } From e347f57d8b8baf65cf81e3c797c0b417ff3c9ab4 Mon Sep 17 00:00:00 2001 From: J-Jamet Date: Tue, 30 Nov 2021 10:47:31 +0100 Subject: [PATCH 02/10] Refactor FileHelper and fix key file selection --- .../keepass/activities/EntryActivity.kt | 26 +- .../keepass/activities/EntryEditActivity.kt | 32 +-- .../activities/FileDatabaseSelectActivity.kt | 44 ++-- .../keepass/activities/IconPickerActivity.kt | 11 +- .../keepass/activities/PasswordActivity.kt | 32 ++- .../dialogs/AssignMasterKeyDialogFragment.kt | 36 ++- .../activities/helpers/ExternalFileHelper.kt | 236 ++++++++---------- .../keepass/settings/SettingsActivity.kt | 82 +++--- .../keepass/view/KeyFileSelectionView.kt | 45 ++++ 9 files changed, 271 insertions(+), 273 deletions(-) diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/EntryActivity.kt b/app/src/main/java/com/kunzisoft/keepass/activities/EntryActivity.kt index eb03b76da..29c58e8d3 100644 --- a/app/src/main/java/com/kunzisoft/keepass/activities/EntryActivity.kt +++ b/app/src/main/java/com/kunzisoft/keepass/activities/EntryActivity.kt @@ -62,7 +62,6 @@ import com.kunzisoft.keepass.view.hideByFading import com.kunzisoft.keepass.view.showActionErrorIfNeeded import com.kunzisoft.keepass.viewmodels.EntryViewModel import java.util.* -import kotlin.collections.HashMap class EntryActivity : DatabaseLockActivity() { @@ -84,8 +83,8 @@ class EntryActivity : DatabaseLockActivity() { private var mEntryLoaded = false private var mAttachmentFileBinderManager: AttachmentFileBinderManager? = null - private var mAttachmentsToDownload: HashMap = HashMap() private var mExternalFileHelper: ExternalFileHelper? = null + private var mAttachmentSelected: Attachment? = null private var mIcon: IconImage? = null private var mIconColor: Int = 0 @@ -133,6 +132,15 @@ class EntryActivity : DatabaseLockActivity() { // Init SAF manager mExternalFileHelper = ExternalFileHelper(this) + mExternalFileHelper?.buildCreateDocument { createdFileUri -> + mAttachmentSelected?.let { attachment -> + if (createdFileUri != null) { + mAttachmentFileBinderManager + ?.startDownloadAttachment(createdFileUri, attachment) + } + mAttachmentSelected = null + } + } // Init attachment service binder manager mAttachmentFileBinderManager = AttachmentFileBinderManager(this) @@ -209,9 +217,8 @@ class EntryActivity : DatabaseLockActivity() { } mEntryViewModel.attachmentSelected.observe(this) { attachmentSelected -> - mExternalFileHelper?.createDocument(attachmentSelected.name)?.let { requestCode -> - mAttachmentsToDownload[requestCode] = attachmentSelected - } + mAttachmentSelected = attachmentSelected + mExternalFileHelper?.createDocument(attachmentSelected.name) } mEntryViewModel.historySelected.observe(this) { historySelected -> @@ -299,15 +306,6 @@ class EntryActivity : DatabaseLockActivity() { mEntryViewModel.loadDatabase(mDatabase) } } - - mExternalFileHelper?.onCreateDocumentResult(requestCode, resultCode, data) { createdFileUri -> - if (createdFileUri != null) { - mAttachmentsToDownload[requestCode]?.let { attachmentToDownload -> - mAttachmentFileBinderManager - ?.startDownloadAttachment(createdFileUri, attachmentToDownload) - } - } - } } override fun onCreateOptionsMenu(menu: Menu): Boolean { diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/EntryEditActivity.kt b/app/src/main/java/com/kunzisoft/keepass/activities/EntryEditActivity.kt index 8726d61aa..be3825349 100644 --- a/app/src/main/java/com/kunzisoft/keepass/activities/EntryEditActivity.kt +++ b/app/src/main/java/com/kunzisoft/keepass/activities/EntryEditActivity.kt @@ -157,6 +157,21 @@ class EntryEditActivity : DatabaseLockActivity(), // To retrieve attachment mExternalFileHelper = ExternalFileHelper(this) + mExternalFileHelper?.buildOpenDocument { uri -> + uri?.let { attachmentToUploadUri -> + UriUtil.getFileData(this, attachmentToUploadUri)?.also { documentFile -> + documentFile.name?.let { fileName -> + if (documentFile.length() > MAX_WARNING_BINARY_FILE) { + FileTooBigDialogFragment.build(attachmentToUploadUri, fileName) + .show(supportFragmentManager, "fileTooBigFragment") + } else { + mEntryEditViewModel.buildNewAttachment(attachmentToUploadUri, fileName) + } + } + } + } + } + mAttachmentFileBinderManager = AttachmentFileBinderManager(this) // Verify the education views entryEditActivityEducation = EntryEditActivityEducation(this) @@ -487,21 +502,6 @@ class EntryEditActivity : DatabaseLockActivity(), IconPickerActivity.onActivityResult(requestCode, resultCode, data) { icon -> mEntryEditViewModel.selectIcon(icon) } - - mExternalFileHelper?.onOpenDocumentResult(requestCode, resultCode, data) { uri -> - uri?.let { attachmentToUploadUri -> - UriUtil.getFileData(this, attachmentToUploadUri)?.also { documentFile -> - documentFile.name?.let { fileName -> - if (documentFile.length() > MAX_WARNING_BINARY_FILE) { - FileTooBigDialogFragment.build(attachmentToUploadUri, fileName) - .show(supportFragmentManager, "fileTooBigFragment") - } else { - mEntryEditViewModel.buildNewAttachment(attachmentToUploadUri, fileName) - } - } - } - } - } } /** @@ -594,7 +594,7 @@ class EntryEditActivity : DatabaseLockActivity(), && entryEditActivityEducation.checkAndPerformedAttachmentEducation( attachmentView, { - mExternalFileHelper?.openDocument() + addNewAttachment() }, { performedNextEducation(entryEditActivityEducation) diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/FileDatabaseSelectActivity.kt b/app/src/main/java/com/kunzisoft/keepass/activities/FileDatabaseSelectActivity.kt index f853485a6..446a9e127 100644 --- a/app/src/main/java/com/kunzisoft/keepass/activities/FileDatabaseSelectActivity.kt +++ b/app/src/main/java/com/kunzisoft/keepass/activities/FileDatabaseSelectActivity.kt @@ -116,6 +116,22 @@ class FileDatabaseSelectActivity : DatabaseModeActivity(), // Open database button mExternalFileHelper = ExternalFileHelper(this) + mExternalFileHelper?.buildOpenDocument { uri -> + uri?.let { + launchPasswordActivityWithPath(uri) + } + } + mExternalFileHelper?.buildCreateDocument("application/x-keepass") { databaseFileCreatedUri -> + mDatabaseFileUri = databaseFileCreatedUri + if (mDatabaseFileUri != null) { + AssignMasterKeyDialogFragment.getInstance(true) + .show(supportFragmentManager, "passwordDialog") + } else { + val error = getString(R.string.error_create_database) + Snackbar.make(coordinatorLayout, error, Snackbar.LENGTH_LONG).asError().show() + Log.e(TAG, error) + } + } openDatabaseButtonView = findViewById(R.id.open_keyfile_button) openDatabaseButtonView?.setOpenDocumentClickListener(mExternalFileHelper) @@ -263,8 +279,9 @@ class FileDatabaseSelectActivity : DatabaseModeActivity(), * Create a new file by calling the content provider */ private fun createNewFile() { - mExternalFileHelper?.createDocument( getString(R.string.database_file_name_default) + - getString(R.string.database_file_extension_default), "application/x-keepass") + mExternalFileHelper?.createDocument( + getString(R.string.database_file_name_default) + + getString(R.string.database_file_extension_default)) } private fun fileNoFoundAction(e: FileNotFoundException) { @@ -368,29 +385,6 @@ class FileDatabaseSelectActivity : DatabaseModeActivity(), override fun onAssignKeyDialogNegativeClick(mainCredential: MainCredential) {} - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - super.onActivityResult(requestCode, resultCode, data) - - mExternalFileHelper?.onOpenDocumentResult(requestCode, resultCode, data) { uri -> - if (uri != null) { - launchPasswordActivityWithPath(uri) - } - } - - // Retrieve the created URI from the file manager - mExternalFileHelper?.onCreateDocumentResult(requestCode, resultCode, data) { databaseFileCreatedUri -> - mDatabaseFileUri = databaseFileCreatedUri - if (mDatabaseFileUri != null) { - AssignMasterKeyDialogFragment.getInstance(true) - .show(supportFragmentManager, "passwordDialog") - } else { - val error = getString(R.string.error_create_database) - Snackbar.make(coordinatorLayout, error, Snackbar.LENGTH_LONG).asError().show() - Log.e(TAG, error) - } - } - } - override fun onCreateOptionsMenu(menu: Menu): Boolean { super.onCreateOptionsMenu(menu) diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/IconPickerActivity.kt b/app/src/main/java/com/kunzisoft/keepass/activities/IconPickerActivity.kt index ff9e7694e..583c9bcac 100644 --- a/app/src/main/java/com/kunzisoft/keepass/activities/IconPickerActivity.kt +++ b/app/src/main/java/com/kunzisoft/keepass/activities/IconPickerActivity.kt @@ -82,6 +82,9 @@ class IconPickerActivity : DatabaseLockActivity() { coordinatorLayout = findViewById(R.id.icon_picker_coordinator) mExternalFileHelper = ExternalFileHelper(this) + mExternalFileHelper?.buildOpenDocument { uri -> + addCustomIcon(uri) + } uploadButton = findViewById(R.id.icon_picker_upload) @@ -309,14 +312,6 @@ class IconPickerActivity : DatabaseLockActivity() { ) } - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - super.onActivityResult(requestCode, resultCode, data) - - mExternalFileHelper?.onOpenDocumentResult(requestCode, resultCode, data) { uri -> - addCustomIcon(uri) - } - } - private fun setResult() { setResult(Activity.RESULT_OK, Intent().apply { putExtra(EXTRA_ICON, mIconImage) diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/PasswordActivity.kt b/app/src/main/java/com/kunzisoft/keepass/activities/PasswordActivity.kt index a6f6c8999..07be10b5a 100644 --- a/app/src/main/java/com/kunzisoft/keepass/activities/PasswordActivity.kt +++ b/app/src/main/java/com/kunzisoft/keepass/activities/PasswordActivity.kt @@ -148,6 +148,12 @@ class PasswordActivity : DatabaseModeActivity(), AdvancedUnlockFragment.BuilderL mRememberKeyFile = PreferencesUtil.rememberKeyFileLocations(this) mExternalFileHelper = ExternalFileHelper(this@PasswordActivity) + mExternalFileHelper?.buildOpenDocument { uri -> + if (uri != null) { + mDatabaseKeyFileUri = uri + populateKeyFileTextView(uri) + } + } keyFileSelectionView?.setOpenDocumentClickListener(mExternalFileHelper) passwordView?.setOnEditorActionListener(onEditorActionListener) @@ -727,25 +733,15 @@ class PasswordActivity : DatabaseModeActivity(), AdvancedUnlockFragment.BuilderL // To get device credential unlock result advancedUnlockFragment?.onActivityResult(requestCode, resultCode, data) - var keyFileResult = false - mExternalFileHelper?.let { - keyFileResult = it.onOpenDocumentResult(requestCode, resultCode, data) { uri -> - if (uri != null) { - mDatabaseKeyFileUri = uri - populateKeyFileTextView(uri) - } + // this block if not a key file response + // TODO advance unlock response + when (resultCode) { + DatabaseLockActivity.RESULT_EXIT_LOCK -> { + clearCredentialsViews() + closeDatabase() } - } - if (!keyFileResult) { - // this block if not a key file response - when (resultCode) { - DatabaseLockActivity.RESULT_EXIT_LOCK -> { - clearCredentialsViews() - closeDatabase() - } - Activity.RESULT_CANCELED -> { - clearCredentialsViews() - } + Activity.RESULT_CANCELED -> { + clearCredentialsViews() } } } diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/dialogs/AssignMasterKeyDialogFragment.kt b/app/src/main/java/com/kunzisoft/keepass/activities/dialogs/AssignMasterKeyDialogFragment.kt index 13c3a9d60..b976f1f54 100644 --- a/app/src/main/java/com/kunzisoft/keepass/activities/dialogs/AssignMasterKeyDialogFragment.kt +++ b/app/src/main/java/com/kunzisoft/keepass/activities/dialogs/AssignMasterKeyDialogFragment.kt @@ -22,7 +22,6 @@ package com.kunzisoft.keepass.activities.dialogs import android.app.Dialog import android.content.Context import android.content.DialogInterface -import android.content.Intent import android.net.Uri import android.os.Bundle import android.text.Editable @@ -133,6 +132,18 @@ class AssignMasterKeyDialogFragment : DatabaseDialogFragment() { keyFileSelectionView = rootView?.findViewById(R.id.keyfile_selection) mExternalFileHelper = ExternalFileHelper(this) + mExternalFileHelper?.buildOpenDocument { uri -> + uri?.let { pathUri -> + UriUtil.getFileData(requireContext(), uri)?.length()?.let { lengthFile -> + keyFileSelectionView?.error = null + keyFileCheckBox?.isChecked = true + keyFileSelectionView?.uri = pathUri + if (lengthFile <= 0L) { + showEmptyKeyFileConfirmationDialog() + } + } + } + } keyFileSelectionView?.setOpenDocumentClickListener(mExternalFileHelper) val dialog = builder.create() @@ -208,7 +219,11 @@ class AssignMasterKeyDialogFragment : DatabaseDialogFragment() { passwordRepeatTextInputLayout?.error = getString(R.string.error_pass_match) } - if (mMasterPassword == null || mMasterPassword!!.isEmpty()) { + if ((mMasterPassword == null + || mMasterPassword!!.isEmpty()) + && (keyFileCheckBox == null + || !keyFileCheckBox!!.isChecked + || keyFileSelectionView?.uri == null)) { error = true showEmptyPasswordConfirmationDialog() } @@ -282,23 +297,6 @@ class AssignMasterKeyDialogFragment : DatabaseDialogFragment() { } } - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - super.onActivityResult(requestCode, resultCode, data) - - mExternalFileHelper?.onOpenDocumentResult(requestCode, resultCode, data) { uri -> - uri?.let { pathUri -> - UriUtil.getFileData(requireContext(), uri)?.length()?.let { lengthFile -> - keyFileSelectionView?.error = null - keyFileCheckBox?.isChecked = true - keyFileSelectionView?.uri = pathUri - if (lengthFile <= 0L) { - showEmptyKeyFileConfirmationDialog() - } - } - } - } - } - companion object { private const val ALLOW_NO_MASTER_KEY_ARG = "ALLOW_NO_MASTER_KEY_ARG" diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/helpers/ExternalFileHelper.kt b/app/src/main/java/com/kunzisoft/keepass/activities/helpers/ExternalFileHelper.kt index 2792c35ef..d2467cda5 100644 --- a/app/src/main/java/com/kunzisoft/keepass/activities/helpers/ExternalFileHelper.kt +++ b/app/src/main/java/com/kunzisoft/keepass/activities/helpers/ExternalFileHelper.kt @@ -20,14 +20,16 @@ package com.kunzisoft.keepass.activities.helpers import android.annotation.SuppressLint -import android.app.Activity.RESULT_OK +import android.content.Context import android.content.Intent import android.content.pm.PackageManager import android.net.Uri import android.os.Build import android.util.Log import android.view.View -import androidx.annotation.RequiresApi +import androidx.activity.result.ActivityResultCallback +import androidx.activity.result.ActivityResultLauncher +import androidx.activity.result.contract.ActivityResultContracts import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentActivity import com.kunzisoft.keepass.activities.dialogs.FileManagerDialogFragment @@ -38,6 +40,10 @@ class ExternalFileHelper { private var activity: FragmentActivity? = null private var fragment: Fragment? = null + private var getContentResultLauncher: ActivityResultLauncher? = null + private var openDocumentResultLauncher: ActivityResultLauncher>? = null + private var createDocumentResultLauncher: ActivityResultLauncher? = null + constructor(context: FragmentActivity) { this.activity = context this.fragment = null @@ -48,94 +54,82 @@ class ExternalFileHelper { this.fragment = context } + fun buildOpenDocument(typeString: String = "*/*", + onFileSelected: ((uri: Uri?) -> Unit)?) { + + val resultCallback = ActivityResultCallback { result -> + result?.let { uri -> + UriUtil.takeUriPermission(activity?.contentResolver, uri) + onFileSelected?.invoke(uri) + } + } + + getContentResultLauncher = if (fragment != null) { + fragment?.registerForActivityResult( + GetContent(), + resultCallback + ) + } else { + activity?.registerForActivityResult( + GetContent(), + resultCallback + ) + } + + openDocumentResultLauncher = if (fragment != null) { + fragment?.registerForActivityResult( + OpenDocument(), + resultCallback + ) + } else { + activity?.registerForActivityResult( + OpenDocument(), + resultCallback + ) + } + } + + fun buildCreateDocument(typeString: String = "application/octet-stream", + onFileCreated: (fileCreated: Uri?)->Unit) { + + val resultCallback = ActivityResultCallback { result -> + onFileCreated.invoke(result) + } + + createDocumentResultLauncher = if (fragment != null) { + fragment?.registerForActivityResult( + CreateDocument(typeString), + resultCallback + ) + } else { + activity?.registerForActivityResult( + CreateDocument(typeString), + resultCallback + ) + } + } + fun openDocument(getContent: Boolean = false, typeString: String = "*/*") { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - try { - if (getContent) { - openActivityWithActionGetContent(typeString) - } else { - openActivityWithActionOpenDocument(typeString) - } - } catch (e: Exception) { - Log.e(TAG, "Unable to open document", e) - showFileManagerDialogFragment() + try { + if (getContent) { + getContentResultLauncher?.launch(typeString) + } else { + openDocumentResultLauncher?.launch(arrayOf(typeString)) } - } else { + } catch (e: Exception) { + Log.e(TAG, "Unable to open document", e) showFileManagerDialogFragment() } } - @RequiresApi(Build.VERSION_CODES.KITKAT) - private fun openActivityWithActionOpenDocument(typeString: String) { - val intentOpenDocument = Intent(Intent.ACTION_OPEN_DOCUMENT).apply { - addCategory(Intent.CATEGORY_OPENABLE) - type = typeString - addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION) - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - addFlags(Intent.FLAG_GRANT_PREFIX_URI_PERMISSION) - } - addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) - addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION) + fun createDocument(titleString: String) { + try { + createDocumentResultLauncher?.launch(titleString) + } catch (e: Exception) { + Log.e(TAG, "Unable to create document", e) + showFileManagerDialogFragment() } - if (fragment != null) - fragment?.startActivityForResult(intentOpenDocument, OPEN_DOC) - else - activity?.startActivityForResult(intentOpenDocument, OPEN_DOC) - } - - @RequiresApi(Build.VERSION_CODES.KITKAT) - private fun openActivityWithActionGetContent(typeString: String) { - val intentGetContent = Intent(Intent.ACTION_GET_CONTENT).apply { - addCategory(Intent.CATEGORY_OPENABLE) - type = typeString - addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION) - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - addFlags(Intent.FLAG_GRANT_PREFIX_URI_PERMISSION) - } - addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) - addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION) - } - if (fragment != null) - fragment?.startActivityForResult(intentGetContent, GET_CONTENT) - else - activity?.startActivityForResult(intentGetContent, GET_CONTENT) - } - - /** - * To use in onActivityResultCallback in Fragment or Activity - * @param onFileSelected Callback retrieve from data - * @return true if requestCode was captured, false elsewhere - */ - fun onOpenDocumentResult(requestCode: Int, resultCode: Int, data: Intent?, - onFileSelected: ((uri: Uri?) -> Unit)?): Boolean { - - when (requestCode) { - FILE_BROWSE -> { - if (resultCode == RESULT_OK) { - val filename = data?.dataString - var keyUri: Uri? = null - if (filename != null) { - keyUri = UriUtil.parse(filename) - } - onFileSelected?.invoke(keyUri) - } - return true - } - GET_CONTENT, OPEN_DOC -> { - if (resultCode == RESULT_OK) { - if (data != null) { - val uri = data.data - if (uri != null) { - UriUtil.takeUriPermission(activity?.contentResolver, uri) - onFileSelected?.invoke(uri) - } - } - } - return true - } - } - return false } /** @@ -155,62 +149,50 @@ class ExternalFileHelper { } } - fun createDocument(titleString: String, - typeString: String = "application/octet-stream"): Int? { - val idCode = getUnusedCreateFileRequestCode() - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - try { - val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply { - addCategory(Intent.CATEGORY_OPENABLE) - type = typeString - putExtra(Intent.EXTRA_TITLE, titleString) + class OpenDocument : ActivityResultContracts.OpenDocument() { + @SuppressLint("InlinedApi") + override fun createIntent(context: Context, input: Array): Intent { + return super.createIntent(context, input).apply { + addCategory(Intent.CATEGORY_OPENABLE) + addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + addFlags(Intent.FLAG_GRANT_PREFIX_URI_PERMISSION) } - if (fragment != null) - fragment?.startActivityForResult(intent, idCode) - else - activity?.startActivityForResult(intent, idCode) - return idCode - } catch (e: Exception) { - Log.e(TAG, "Unable to create document", e) - showFileManagerDialogFragment() + addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION) } - } else { - showFileManagerDialogFragment() } - return null } - /** - * To use in onActivityResultCallback in Fragment or Activity - * @param onFileCreated Callback retrieve from data - * @return true if requestCode was captured, false elsewhere - */ - fun onCreateDocumentResult(requestCode: Int, resultCode: Int, data: Intent?, - onFileCreated: (fileCreated: Uri?)->Unit) { - // Retrieve the created URI from the file manager - if (fileRequestCodes.contains(requestCode) && resultCode == RESULT_OK) { - onFileCreated.invoke(data?.data) - fileRequestCodes.remove(requestCode) + class GetContent : ActivityResultContracts.GetContent() { + @SuppressLint("InlinedApi") + override fun createIntent(context: Context, input: String): Intent { + return super.createIntent(context, input).apply { + addCategory(Intent.CATEGORY_OPENABLE) + addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + addFlags(Intent.FLAG_GRANT_PREFIX_URI_PERMISSION) + } + addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION) + } } } + class CreateDocument(private val typeString: String) : ActivityResultContracts.CreateDocument() { + override fun createIntent(context: Context, input: String): Intent { + return super.createIntent(context, input).apply { + addCategory(Intent.CATEGORY_OPENABLE) + type = typeString + } + } + } + + companion object { private const val TAG = "OpenFileHelper" - private const val GET_CONTENT = 25745 - private const val OPEN_DOC = 25845 - private const val FILE_BROWSE = 25645 - - private var CREATE_FILE_REQUEST_CODE_DEFAULT = 3853 - private var fileRequestCodes = ArrayList() - - private fun getUnusedCreateFileRequestCode(): Int { - val newCreateFileRequestCode = CREATE_FILE_REQUEST_CODE_DEFAULT++ - fileRequestCodes.add(newCreateFileRequestCode) - return newCreateFileRequestCode - } - @SuppressLint("InlinedApi") fun allowCreateDocumentByStorageAccessFramework(packageManager: PackageManager, typeString: String = "application/octet-stream"): Boolean { @@ -231,7 +213,7 @@ class ExternalFileHelper { fun View.setOpenDocumentClickListener(externalFileHelper: ExternalFileHelper?) { externalFileHelper?.let { fileHelper -> setOnClickListener { - fileHelper.openDocument() + fileHelper.openDocument(false) } setOnLongClickListener { fileHelper.openDocument(true) diff --git a/app/src/main/java/com/kunzisoft/keepass/settings/SettingsActivity.kt b/app/src/main/java/com/kunzisoft/keepass/settings/SettingsActivity.kt index 2baa56008..a9197d910 100644 --- a/app/src/main/java/com/kunzisoft/keepass/settings/SettingsActivity.kt +++ b/app/src/main/java/com/kunzisoft/keepass/settings/SettingsActivity.kt @@ -49,7 +49,6 @@ open class SettingsActivity private var backupManager: BackupManager? = null private var mExternalFileHelper: ExternalFileHelper? = null - private var appPropertiesFileCreationRequestCode: Int? = null private var coordinatorLayout: CoordinatorLayout? = null private var toolbar: Toolbar? = null @@ -64,6 +63,41 @@ open class SettingsActivity toolbar = findViewById(R.id.toolbar) mExternalFileHelper = ExternalFileHelper(this) + mExternalFileHelper?.buildOpenDocument { selectedFileUri -> + // Import app properties result + try { + selectedFileUri?.let { uri -> + val appProperties = Properties() + contentResolver?.openInputStream(uri)?.use { inputStream -> + appProperties.load(inputStream) + } + PreferencesUtil.setAppProperties(this, appProperties) + + // Restart the current activity + reloadActivity() + Toast.makeText(this, R.string.success_import_app_properties, Toast.LENGTH_LONG).show() + } + } catch (e: Exception) { + Toast.makeText(this, R.string.error_import_app_properties, Toast.LENGTH_LONG).show() + Log.e(TAG, "Unable to import app properties", e) + } + } + mExternalFileHelper?.buildCreateDocument { createdFileUri -> + // Export app properties result + try { + createdFileUri?.let { uri -> + contentResolver?.openOutputStream(uri)?.use { outputStream -> + PreferencesUtil + .getAppProperties(this) + .store(outputStream, getString(R.string.description_app_properties)) + } + Toast.makeText(this, R.string.success_export_app_properties, Toast.LENGTH_LONG).show() + } + } catch (e: Exception) { + Toast.makeText(this, R.string.error_export_app_properties, Toast.LENGTH_LONG).show() + Log.e(DatabaseLockActivity.TAG, "Unable to export app properties", e) + } + } if (savedInstanceState?.getString(TITLE_KEY).isNullOrEmpty()) toolbar?.setTitle(R.string.settings) @@ -217,54 +251,10 @@ open class SettingsActivity } fun exportAppProperties() { - appPropertiesFileCreationRequestCode = mExternalFileHelper?.createDocument(getString(R.string.app_properties_file_name, + mExternalFileHelper?.createDocument(getString(R.string.app_properties_file_name, DateTime.now().toLocalDateTime().toString("yyyy-MM-dd'_'HH-mm"))) } - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - super.onActivityResult(requestCode, resultCode, data) - - // Import app properties result - try { - mExternalFileHelper?.onOpenDocumentResult(requestCode, resultCode, data) { selectedFileUri -> - selectedFileUri?.let { uri -> - val appProperties = Properties() - contentResolver?.openInputStream(uri)?.use { inputStream -> - appProperties.load(inputStream) - } - PreferencesUtil.setAppProperties(this, appProperties) - - // Restart the current activity - reloadActivity() - Toast.makeText(this, R.string.success_import_app_properties, Toast.LENGTH_LONG).show() - } - } - } catch (e: Exception) { - Toast.makeText(this, R.string.error_import_app_properties, Toast.LENGTH_LONG).show() - Log.e(TAG, "Unable to import app properties", e) - } - - // Export app properties result - try { - if (requestCode == appPropertiesFileCreationRequestCode) { - mExternalFileHelper?.onCreateDocumentResult(requestCode, resultCode, data) { createdFileUri -> - createdFileUri?.let { uri -> - contentResolver?.openOutputStream(uri)?.use { outputStream -> - PreferencesUtil - .getAppProperties(this) - .store(outputStream, getString(R.string.description_app_properties)) - } - Toast.makeText(this, R.string.success_export_app_properties, Toast.LENGTH_LONG).show() - } - } - appPropertiesFileCreationRequestCode = null - } - } catch (e: Exception) { - Toast.makeText(this, R.string.error_export_app_properties, Toast.LENGTH_LONG).show() - Log.e(DatabaseLockActivity.TAG, "Unable to export app properties", e) - } - } - override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) diff --git a/app/src/main/java/com/kunzisoft/keepass/view/KeyFileSelectionView.kt b/app/src/main/java/com/kunzisoft/keepass/view/KeyFileSelectionView.kt index 3bf85978a..182623ac7 100644 --- a/app/src/main/java/com/kunzisoft/keepass/view/KeyFileSelectionView.kt +++ b/app/src/main/java/com/kunzisoft/keepass/view/KeyFileSelectionView.kt @@ -2,6 +2,7 @@ package com.kunzisoft.keepass.view import android.content.Context import android.net.Uri +import android.os.Parcelable import android.util.AttributeSet import android.view.LayoutInflater import android.widget.TextView @@ -9,6 +10,9 @@ import androidx.constraintlayout.widget.ConstraintLayout import com.google.android.material.textfield.TextInputLayout import com.kunzisoft.keepass.R import com.kunzisoft.keepass.utils.UriUtil +import android.os.Parcel +import android.os.Parcelable.Creator + class KeyFileSelectionView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, @@ -54,4 +58,45 @@ class KeyFileSelectionView @JvmOverloads constructor(context: Context, UriUtil.getFileData(context, value)?.name ?: value.path } ?: "" } + + override fun onSaveInstanceState(): Parcelable { + val superState = super.onSaveInstanceState() + val saveState = SavedState(superState) + saveState.mUri = this.mUri + return saveState + } + + override fun onRestoreInstanceState(state: Parcelable?) { + if (state !is SavedState) { + super.onRestoreInstanceState(state) + return + } + super.onRestoreInstanceState(state.superState) + this.mUri = state.mUri + } + + internal class SavedState : BaseSavedState { + var mUri: Uri? = null + + constructor(superState: Parcelable?) : super(superState) {} + + private constructor(parcel: Parcel) : super(parcel) { + mUri = parcel.readParcelable(Uri::class.java.classLoader) + } + + override fun writeToParcel(out: Parcel, flags: Int) { + super.writeToParcel(out, flags) + out.writeParcelable(mUri, flags) + } + + companion object CREATOR : Creator { + override fun createFromParcel(parcel: Parcel): SavedState { + return SavedState(parcel) + } + + override fun newArray(size: Int): Array { + return arrayOfNulls(size) + } + } + } } \ No newline at end of file From 8f5439b9580e0955bee0d0aaaba4787d9a4c72eb Mon Sep 17 00:00:00 2001 From: J-Jamet Date: Tue, 30 Nov 2021 11:20:09 +0100 Subject: [PATCH 03/10] Icon selection with activity result launcher --- .../keepass/activities/EntryEditActivity.kt | 14 ++++----- .../keepass/activities/GroupActivity.kt | 12 ++++---- .../keepass/activities/IconPickerActivity.kt | 29 ++++++++++--------- 3 files changed, 26 insertions(+), 29 deletions(-) diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/EntryEditActivity.kt b/app/src/main/java/com/kunzisoft/keepass/activities/EntryEditActivity.kt index be3825349..9be7edf03 100644 --- a/app/src/main/java/com/kunzisoft/keepass/activities/EntryEditActivity.kt +++ b/app/src/main/java/com/kunzisoft/keepass/activities/EntryEditActivity.kt @@ -109,6 +109,10 @@ class EntryEditActivity : DatabaseLockActivity(), // Education private var entryEditActivityEducation: EntryEditActivityEducation? = null + private var mIconSelectionActivityResultLauncher = IconPickerActivity.registerIconSelectionForResult(this) { icon -> + mEntryEditViewModel.selectIcon(icon) + } + // To ask data lost only one time private var backPressedAlreadyApproved = false @@ -233,7 +237,7 @@ class EntryEditActivity : DatabaseLockActivity(), // View model listeners mEntryEditViewModel.requestIconSelection.observe(this) { iconImage -> - IconPickerActivity.launch(this@EntryEditActivity, iconImage) + IconPickerActivity.launch(this@EntryEditActivity, iconImage, mIconSelectionActivityResultLauncher) } mEntryEditViewModel.requestDateTimeSelection.observe(this) { dateInstant -> @@ -496,14 +500,6 @@ class EntryEditActivity : DatabaseLockActivity(), } } - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - super.onActivityResult(requestCode, resultCode, data) - - IconPickerActivity.onActivityResult(requestCode, resultCode, data) { icon -> - mEntryEditViewModel.selectIcon(icon) - } - } - /** * Set up OTP (HOTP or TOTP) and add it as extra field */ diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/GroupActivity.kt b/app/src/main/java/com/kunzisoft/keepass/activities/GroupActivity.kt index 661ed1122..e5fadd613 100644 --- a/app/src/main/java/com/kunzisoft/keepass/activities/GroupActivity.kt +++ b/app/src/main/java/com/kunzisoft/keepass/activities/GroupActivity.kt @@ -113,6 +113,11 @@ class GroupActivity : DatabaseLockActivity(), private var mSearchSuggestionAdapter: SearchEntryCursorAdapter? = null private var mOnSuggestionListener: SearchView.OnSuggestionListener? = null + private var mIconSelectionActivityResultLauncher = IconPickerActivity.registerIconSelectionForResult(this) { icon -> + // To create tree dialog for icon + mGroupEditViewModel.selectIcon(icon) + } + private var mAutofillActivityResultLauncher: ActivityResultLauncher? = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) AutofillHelper.buildActivityResultLauncher(this) @@ -285,7 +290,7 @@ class GroupActivity : DatabaseLockActivity(), } mGroupEditViewModel.requestIconSelection.observe(this) { iconImage -> - IconPickerActivity.launch(this@GroupActivity, iconImage) + IconPickerActivity.launch(this@GroupActivity, iconImage, mIconSelectionActivityResultLauncher) } mGroupEditViewModel.requestDateTimeSelection.observe(this) { dateInstant -> @@ -1083,11 +1088,6 @@ class GroupActivity : DatabaseLockActivity(), override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) - // To create tree dialog for icon - IconPickerActivity.onActivityResult(requestCode, resultCode, data) { icon -> - mGroupEditViewModel.selectIcon(icon) - } - // Directly used the onActivityResult in fragment mGroupFragment?.onActivityResult(requestCode, resultCode, data) } diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/IconPickerActivity.kt b/app/src/main/java/com/kunzisoft/keepass/activities/IconPickerActivity.kt index 583c9bcac..93cf4b9a9 100644 --- a/app/src/main/java/com/kunzisoft/keepass/activities/IconPickerActivity.kt +++ b/app/src/main/java/com/kunzisoft/keepass/activities/IconPickerActivity.kt @@ -27,9 +27,12 @@ import android.view.Menu import android.view.MenuItem import android.view.View import android.view.ViewGroup +import androidx.activity.result.ActivityResultLauncher +import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.viewModels import androidx.appcompat.widget.Toolbar import androidx.coordinatorlayout.widget.CoordinatorLayout +import androidx.fragment.app.FragmentActivity import androidx.fragment.app.commit import com.google.android.material.snackbar.Snackbar import com.kunzisoft.keepass.R @@ -326,30 +329,28 @@ class IconPickerActivity : DatabaseLockActivity() { companion object { private const val ICON_PICKER_FRAGMENT_TAG = "ICON_PICKER_FRAGMENT_TAG" - - private const val ICON_SELECTED_REQUEST = 15861 private const val EXTRA_ICON = "EXTRA_ICON" - private const val MAX_ICON_SIZE = 5242880 - fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?, listener: (icon: IconImage) -> Unit) { - if (requestCode == ICON_SELECTED_REQUEST) { - if (resultCode == Activity.RESULT_OK) { - listener.invoke(data?.getParcelableExtra(EXTRA_ICON) ?: IconImage()) + fun registerIconSelectionForResult(context: FragmentActivity, + listener: (icon: IconImage) -> Unit): ActivityResultLauncher { + return context.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> + if (result.resultCode == Activity.RESULT_OK) { + listener.invoke(result.data?.getParcelableExtra(EXTRA_ICON) ?: IconImage()) } } } - fun launch(context: Activity, - previousIcon: IconImage?) { + fun launch(context: FragmentActivity, + previousIcon: IconImage?, + resultLauncher: ActivityResultLauncher) { // Create an instance to return the picker icon - context.startActivityForResult( - Intent(context, - IconPickerActivity::class.java).apply { + resultLauncher.launch( + Intent(context, IconPickerActivity::class.java).apply { if (previousIcon != null) putExtra(EXTRA_ICON, previousIcon) - }, - ICON_SELECTED_REQUEST) + } + ) } } } From 40a063e94fd6f0027364be68e24f4ec0b168e054 Mon Sep 17 00:00:00 2001 From: J-Jamet Date: Tue, 30 Nov 2021 11:50:07 +0100 Subject: [PATCH 04/10] Fix result exit lock --- .../keepass/activities/PasswordActivity.kt | 12 ------------ .../activities/legacy/DatabaseLockActivity.kt | 13 +------------ 2 files changed, 1 insertion(+), 24 deletions(-) diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/PasswordActivity.kt b/app/src/main/java/com/kunzisoft/keepass/activities/PasswordActivity.kt index 07be10b5a..3c776f0f0 100644 --- a/app/src/main/java/com/kunzisoft/keepass/activities/PasswordActivity.kt +++ b/app/src/main/java/com/kunzisoft/keepass/activities/PasswordActivity.kt @@ -732,18 +732,6 @@ class PasswordActivity : DatabaseModeActivity(), AdvancedUnlockFragment.BuilderL // To get device credential unlock result advancedUnlockFragment?.onActivityResult(requestCode, resultCode, data) - - // this block if not a key file response - // TODO advance unlock response - when (resultCode) { - DatabaseLockActivity.RESULT_EXIT_LOCK -> { - clearCredentialsViews() - closeDatabase() - } - Activity.RESULT_CANCELED -> { - clearCredentialsViews() - } - } } companion object { diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/legacy/DatabaseLockActivity.kt b/app/src/main/java/com/kunzisoft/keepass/activities/legacy/DatabaseLockActivity.kt index c3bc937b6..f5de6dd2e 100644 --- a/app/src/main/java/com/kunzisoft/keepass/activities/legacy/DatabaseLockActivity.kt +++ b/app/src/main/java/com/kunzisoft/keepass/activities/legacy/DatabaseLockActivity.kt @@ -180,8 +180,7 @@ abstract class DatabaseLockActivity : DatabaseModeActivity(), closeDatabase(database) if (LOCKING_ACTIVITY_UI_VISIBLE_DURING_LOCK == null) LOCKING_ACTIVITY_UI_VISIBLE_DURING_LOCK = LOCKING_ACTIVITY_UI_VISIBLE - // Add onActivityForResult response - setResult(RESULT_EXIT_LOCK) + mExitLock = true closeOptionsMenu() finish() } @@ -353,14 +352,6 @@ abstract class DatabaseLockActivity : DatabaseModeActivity(), mDatabaseTaskProvider?.startDatabaseDeleteEntryHistory(mainEntryId, entryHistoryPosition, mAutoSaveEnable) } - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - super.onActivityResult(requestCode, resultCode, data) - if (resultCode == RESULT_EXIT_LOCK) { - mExitLock = true - lockAndExit() - } - } - private fun checkRegister() { // If in ave or registration mode, don't allow read only if ((mSpecialMode == SpecialMode.SAVE @@ -440,8 +431,6 @@ abstract class DatabaseLockActivity : DatabaseModeActivity(), const val TAG = "LockingActivity" - const val RESULT_EXIT_LOCK = 1450 - const val TIMEOUT_ENABLE_KEY = "TIMEOUT_ENABLE_KEY" const val TIMEOUT_ENABLE_KEY_DEFAULT = true From 5f8746ced3c65680c83212735bdb5f7209570e11 Mon Sep 17 00:00:00 2001 From: J-Jamet Date: Wed, 1 Dec 2021 17:16:19 +0100 Subject: [PATCH 05/10] Fix result with entry edit --- .../keepass/activities/EntryActivity.kt | 41 +++++++---------- .../keepass/activities/EntryEditActivity.kt | 45 +++++++++++++++---- .../keepass/activities/GroupActivity.kt | 41 ++++++++++------- .../activities/fragments/GroupFragment.kt | 34 ++++++-------- 4 files changed, 92 insertions(+), 69 deletions(-) diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/EntryActivity.kt b/app/src/main/java/com/kunzisoft/keepass/activities/EntryActivity.kt index 29c58e8d3..5995a0638 100644 --- a/app/src/main/java/com/kunzisoft/keepass/activities/EntryActivity.kt +++ b/app/src/main/java/com/kunzisoft/keepass/activities/EntryActivity.kt @@ -32,6 +32,7 @@ import android.view.MenuItem import android.view.View import android.widget.ImageView import android.widget.ProgressBar +import androidx.activity.result.ActivityResultLauncher import androidx.activity.viewModels import androidx.appcompat.widget.Toolbar import androidx.coordinatorlayout.widget.CoordinatorLayout @@ -86,6 +87,11 @@ class EntryActivity : DatabaseLockActivity() { private var mExternalFileHelper: ExternalFileHelper? = null private var mAttachmentSelected: Attachment? = null + private var mEntryActivityResultLauncher = EntryEditActivity.registerForEntryResult(this) { + // Reload the current id from database + mEntryViewModel.loadDatabase(mDatabase) + } + private var mIcon: IconImage? = null private var mIconColor: Int = 0 @@ -227,7 +233,8 @@ class EntryActivity : DatabaseLockActivity() { this, database, historySelected.nodeId, - historySelected.historyPosition + historySelected.historyPosition, + mEntryActivityResultLauncher ) } } @@ -297,17 +304,6 @@ class EntryActivity : DatabaseLockActivity() { super.onPause() } - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - super.onActivityResult(requestCode, resultCode, data) - - when (requestCode) { - EntryEditActivity.ADD_OR_UPDATE_ENTRY_REQUEST_CODE -> { - // Reload the current id from database - mEntryViewModel.loadDatabase(mDatabase) - } - } - } - override fun onCreateOptionsMenu(menu: Menu): Boolean { super.onCreateOptionsMenu(menu) if (mEntryLoaded) { @@ -389,7 +385,8 @@ class EntryActivity : DatabaseLockActivity() { EntryEditActivity.launchToUpdate( this, database, - entryId + entryId, + mEntryActivityResultLauncher ) } } @@ -430,7 +427,7 @@ class EntryActivity : DatabaseLockActivity() { // Transit data in previous Activity after an update Intent().apply { putExtra(EntryEditActivity.ADD_OR_UPDATE_ENTRY_KEY, mMainEntryId) - setResult(EntryEditActivity.ADD_OR_UPDATE_ENTRY_RESULT_CODE, this) + setResult(Activity.RESULT_OK, this) } super.finish() } @@ -448,15 +445,13 @@ class EntryActivity : DatabaseLockActivity() { */ fun launch(activity: Activity, database: Database, - entryId: NodeId) { + entryId: NodeId, + activityResultLauncher: ActivityResultLauncher) { if (database.loaded) { if (TimeoutHelper.checkTimeAndLockIfTimeout(activity)) { val intent = Intent(activity, EntryActivity::class.java) intent.putExtra(KEY_ENTRY, entryId) - activity.startActivityForResult( - intent, - EntryEditActivity.ADD_OR_UPDATE_ENTRY_REQUEST_CODE - ) + activityResultLauncher.launch(intent) } } } @@ -467,16 +462,14 @@ class EntryActivity : DatabaseLockActivity() { fun launch(activity: Activity, database: Database, entryId: NodeId, - historyPosition: Int) { + historyPosition: Int, + activityResultLauncher: ActivityResultLauncher) { if (database.loaded) { if (TimeoutHelper.checkTimeAndLockIfTimeout(activity)) { val intent = Intent(activity, EntryActivity::class.java) intent.putExtra(KEY_ENTRY, entryId) intent.putExtra(KEY_ENTRY_HISTORY_POSITION, historyPosition) - activity.startActivityForResult( - intent, - EntryEditActivity.ADD_OR_UPDATE_ENTRY_REQUEST_CODE - ) + activityResultLauncher.launch(intent) } } } diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/EntryEditActivity.kt b/app/src/main/java/com/kunzisoft/keepass/activities/EntryEditActivity.kt index 9be7edf03..db73bf28d 100644 --- a/app/src/main/java/com/kunzisoft/keepass/activities/EntryEditActivity.kt +++ b/app/src/main/java/com/kunzisoft/keepass/activities/EntryEditActivity.kt @@ -34,6 +34,7 @@ import android.view.MenuItem import android.view.View import android.widget.* import androidx.activity.result.ActivityResultLauncher +import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.viewModels import androidx.annotation.RequiresApi import androidx.appcompat.app.AlertDialog @@ -41,6 +42,8 @@ import androidx.appcompat.app.AppCompatActivity import androidx.coordinatorlayout.widget.CoordinatorLayout import androidx.core.view.isVisible import androidx.core.widget.NestedScrollView +import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentActivity import com.google.android.material.snackbar.Snackbar import com.kunzisoft.keepass.R import com.kunzisoft.keepass.activities.dialogs.* @@ -691,7 +694,7 @@ class EntryEditActivity : DatabaseLockActivity(), val intentEntry = Intent() bundle.putParcelable(ADD_OR_UPDATE_ENTRY_KEY, entry.nodeId) intentEntry.putExtras(bundle) - setResult(ADD_OR_UPDATE_ENTRY_RESULT_CODE, intentEntry) + setResult(Activity.RESULT_OK, intentEntry) super.finish() } catch (e: Exception) { // Exception when parcelable can't be done @@ -706,23 +709,46 @@ class EntryEditActivity : DatabaseLockActivity(), // Keys for current Activity const val KEY_ENTRY = "entry" const val KEY_PARENT = "parent" - - // Keys for callback - const val ADD_OR_UPDATE_ENTRY_RESULT_CODE = 31 - const val ADD_OR_UPDATE_ENTRY_REQUEST_CODE = 7129 const val ADD_OR_UPDATE_ENTRY_KEY = "ADD_OR_UPDATE_ENTRY_KEY" + fun registerForEntryResult(fragment: Fragment, + entryAddedOrUpdatedListener: (NodeId?) -> Unit): ActivityResultLauncher { + return fragment.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> + if (result.resultCode == Activity.RESULT_OK) { + entryAddedOrUpdatedListener.invoke( + result.data?.getParcelableExtra(ADD_OR_UPDATE_ENTRY_KEY) + ) + } else { + entryAddedOrUpdatedListener.invoke(null) + } + } + } + + fun registerForEntryResult(activity: FragmentActivity, + entryAddedOrUpdatedListener: (NodeId?) -> Unit): ActivityResultLauncher { + return activity.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> + if (result.resultCode == Activity.RESULT_OK) { + entryAddedOrUpdatedListener.invoke( + result.data?.getParcelableExtra(ADD_OR_UPDATE_ENTRY_KEY) + ) + } else { + entryAddedOrUpdatedListener.invoke(null) + } + } + } + /** * Launch EntryEditActivity to update an existing entry by his [entryId] */ fun launchToUpdate(activity: Activity, database: Database, - entryId: NodeId) { + entryId: NodeId, + activityResultLauncher: ActivityResultLauncher) { if (database.loaded && !database.isReadOnly) { if (TimeoutHelper.checkTimeAndLockIfTimeout(activity)) { val intent = Intent(activity, EntryEditActivity::class.java) intent.putExtra(KEY_ENTRY, entryId) - activity.startActivityForResult(intent, ADD_OR_UPDATE_ENTRY_REQUEST_CODE) + activityResultLauncher.launch(intent) } } } @@ -732,12 +758,13 @@ class EntryEditActivity : DatabaseLockActivity(), */ fun launchToCreate(activity: Activity, database: Database, - groupId: NodeId<*>) { + groupId: NodeId<*>, + activityResultLauncher: ActivityResultLauncher) { if (database.loaded && !database.isReadOnly) { if (TimeoutHelper.checkTimeAndLockIfTimeout(activity)) { val intent = Intent(activity, EntryEditActivity::class.java) intent.putExtra(KEY_PARENT, groupId) - activity.startActivityForResult(intent, ADD_OR_UPDATE_ENTRY_REQUEST_CODE) + activityResultLauncher.launch(intent) } } } diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/GroupActivity.kt b/app/src/main/java/com/kunzisoft/keepass/activities/GroupActivity.kt index e5fadd613..19fda9e07 100644 --- a/app/src/main/java/com/kunzisoft/keepass/activities/GroupActivity.kt +++ b/app/src/main/java/com/kunzisoft/keepass/activities/GroupActivity.kt @@ -223,11 +223,14 @@ class GroupActivity : DatabaseLockActivity(), mDatabase?.let { database -> EntrySelectionHelper.doSpecialAction(intent, { - EntryEditActivity.launchToCreate( - this@GroupActivity, - database, - currentGroup.nodeId - ) + mGroupFragment?.mEntryActivityResultLauncher?.let { resultLauncher -> + EntryEditActivity.launchToCreate( + this@GroupActivity, + database, + currentGroup.nodeId, + resultLauncher + ) + } }, { // Search not used @@ -607,11 +610,14 @@ class GroupActivity : DatabaseLockActivity(), val entryVersioned = node as Entry EntrySelectionHelper.doSpecialAction(intent, { - EntryActivity.launch( - this@GroupActivity, - database, - entryVersioned.nodeId - ) + mGroupFragment?.mEntryActivityResultLauncher?.let { resultLauncher -> + EntryActivity.launch( + this@GroupActivity, + database, + entryVersioned.nodeId, + resultLauncher + ) + } }, { // Nothing here, a search is simply performed @@ -808,11 +814,16 @@ class GroupActivity : DatabaseLockActivity(), GroupEditDialogFragment.TAG_CREATE_GROUP ) } - Type.ENTRY -> EntryEditActivity.launchToUpdate( - this@GroupActivity, - database, - (node as Entry).nodeId - ) + Type.ENTRY -> { + mGroupFragment?.mEntryActivityResultLauncher?.let { resultLauncher -> + EntryEditActivity.launchToUpdate( + this@GroupActivity, + database, + (node as Entry).nodeId, + resultLauncher + ) + } + } } reloadGroupIfSearch() return true diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/fragments/GroupFragment.kt b/app/src/main/java/com/kunzisoft/keepass/activities/fragments/GroupFragment.kt index 39d5b3b86..432cd70a4 100644 --- a/app/src/main/java/com/kunzisoft/keepass/activities/fragments/GroupFragment.kt +++ b/app/src/main/java/com/kunzisoft/keepass/activities/fragments/GroupFragment.kt @@ -74,6 +74,19 @@ class GroupFragment : DatabaseFragment(), SortDialogFragment.SortSelectionListen private var mRecycleBinEnable: Boolean = false private var mRecycleBin: Group? = null + var mEntryActivityResultLauncher = EntryEditActivity.registerForEntryResult(this) { entryId -> + entryId?.let { + // Simply refresh the list + rebuildList() + // Scroll to the new entry + mDatabase?.getEntryById(it)?.let { entry -> + mAdapter?.indexOf(entry)?.let { position -> + mNodesRecyclerView?.scrollToPosition(position) + } + } + } ?: Log.e(this.javaClass.name, "Entry cannot be retrieved in Activity Result") + } + private var mRecycleViewScrollListener = object : RecyclerView.OnScrollListener() { override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) { super.onScrollStateChanged(recyclerView, newState) @@ -399,27 +412,6 @@ class GroupFragment : DatabaseFragment(), SortDialogFragment.SortSelectionListen } } - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - super.onActivityResult(requestCode, resultCode, data) - - when (requestCode) { - EntryEditActivity.ADD_OR_UPDATE_ENTRY_REQUEST_CODE -> { - if (resultCode == EntryEditActivity.ADD_OR_UPDATE_ENTRY_RESULT_CODE) { - data?.getParcelableExtra>(EntryEditActivity.ADD_OR_UPDATE_ENTRY_KEY)?.let { - // Simply refresh the list - rebuildList() - // Scroll to the new entry - mDatabase?.getEntryById(it)?.let { entry -> - mAdapter?.indexOf(entry)?.let { position -> - mNodesRecyclerView?.scrollToPosition(position) - } - } - } ?: Log.e(this.javaClass.name, "Entry cannot be retrieved in Activity Result") - } - } - } - } - /** * Callback listener to redefine to do an action when a node is click */ From d4ef1a26178a31b499d111d507e58cc06b59e8b1 Mon Sep 17 00:00:00 2001 From: J-Jamet Date: Thu, 2 Dec 2021 11:39:42 +0100 Subject: [PATCH 06/10] Fix small warnings --- .../keepass/activities/dialogs/DatabaseDialogFragment.kt | 1 + .../kunzisoft/keepass/activities/helpers/ExternalFileHelper.kt | 3 +-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/dialogs/DatabaseDialogFragment.kt b/app/src/main/java/com/kunzisoft/keepass/activities/dialogs/DatabaseDialogFragment.kt index 1cacb3d3c..fc3708205 100644 --- a/app/src/main/java/com/kunzisoft/keepass/activities/dialogs/DatabaseDialogFragment.kt +++ b/app/src/main/java/com/kunzisoft/keepass/activities/dialogs/DatabaseDialogFragment.kt @@ -29,6 +29,7 @@ abstract class DatabaseDialogFragment : DialogFragment(), DatabaseRetrieval { } } + @Suppress("DEPRECATION") override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/helpers/ExternalFileHelper.kt b/app/src/main/java/com/kunzisoft/keepass/activities/helpers/ExternalFileHelper.kt index d2467cda5..cc12c2347 100644 --- a/app/src/main/java/com/kunzisoft/keepass/activities/helpers/ExternalFileHelper.kt +++ b/app/src/main/java/com/kunzisoft/keepass/activities/helpers/ExternalFileHelper.kt @@ -54,8 +54,7 @@ class ExternalFileHelper { this.fragment = context } - fun buildOpenDocument(typeString: String = "*/*", - onFileSelected: ((uri: Uri?) -> Unit)?) { + fun buildOpenDocument(onFileSelected: ((uri: Uri?) -> Unit)?) { val resultCallback = ActivityResultCallback { result -> result?.let { uri -> From e9392781937e4361fb660e07a9ab2ce87a47f684 Mon Sep 17 00:00:00 2001 From: J-Jamet Date: Thu, 2 Dec 2021 13:21:25 +0100 Subject: [PATCH 07/10] Suppress deprecation with setTargetFragment --- .../com/kunzisoft/keepass/settings/AutofillSettingsFragment.kt | 1 + .../kunzisoft/keepass/settings/MagikeyboardSettingsFragment.kt | 1 + .../com/kunzisoft/keepass/settings/NestedAppSettingsFragment.kt | 1 + .../kunzisoft/keepass/settings/NestedDatabaseSettingsFragment.kt | 1 + 4 files changed, 4 insertions(+) diff --git a/app/src/main/java/com/kunzisoft/keepass/settings/AutofillSettingsFragment.kt b/app/src/main/java/com/kunzisoft/keepass/settings/AutofillSettingsFragment.kt index f3462752b..35dab43ae 100644 --- a/app/src/main/java/com/kunzisoft/keepass/settings/AutofillSettingsFragment.kt +++ b/app/src/main/java/com/kunzisoft/keepass/settings/AutofillSettingsFragment.kt @@ -57,6 +57,7 @@ class AutofillSettingsFragment : PreferenceFragmentCompat() { } if (dialogFragment != null) { + @Suppress("DEPRECATION") dialogFragment.setTargetFragment(this, 0) dialogFragment.show(parentFragmentManager, TAG_AUTOFILL_PREF_FRAGMENT) } diff --git a/app/src/main/java/com/kunzisoft/keepass/settings/MagikeyboardSettingsFragment.kt b/app/src/main/java/com/kunzisoft/keepass/settings/MagikeyboardSettingsFragment.kt index 3d7e6c9e6..c1af92d98 100644 --- a/app/src/main/java/com/kunzisoft/keepass/settings/MagikeyboardSettingsFragment.kt +++ b/app/src/main/java/com/kunzisoft/keepass/settings/MagikeyboardSettingsFragment.kt @@ -48,6 +48,7 @@ class MagikeyboardSettingsFragment : PreferenceFragmentCompat() { } if (dialogFragment != null) { + @Suppress("DEPRECATION") dialogFragment.setTargetFragment(this, 0) dialogFragment.show(parentFragmentManager, TAG_PREF_FRAGMENT) } diff --git a/app/src/main/java/com/kunzisoft/keepass/settings/NestedAppSettingsFragment.kt b/app/src/main/java/com/kunzisoft/keepass/settings/NestedAppSettingsFragment.kt index 4774bdd8c..815f2f99e 100644 --- a/app/src/main/java/com/kunzisoft/keepass/settings/NestedAppSettingsFragment.kt +++ b/app/src/main/java/com/kunzisoft/keepass/settings/NestedAppSettingsFragment.kt @@ -474,6 +474,7 @@ class NestedAppSettingsFragment : NestedSettingsFragment() { } if (dialogFragment != null) { + @Suppress("DEPRECATION") dialogFragment.setTargetFragment(this, 0) dialogFragment.show(parentFragmentManager, TAG_PREF_FRAGMENT) } diff --git a/app/src/main/java/com/kunzisoft/keepass/settings/NestedDatabaseSettingsFragment.kt b/app/src/main/java/com/kunzisoft/keepass/settings/NestedDatabaseSettingsFragment.kt index a4f7447a7..bbe8b8b81 100644 --- a/app/src/main/java/com/kunzisoft/keepass/settings/NestedDatabaseSettingsFragment.kt +++ b/app/src/main/java/com/kunzisoft/keepass/settings/NestedDatabaseSettingsFragment.kt @@ -632,6 +632,7 @@ class NestedDatabaseSettingsFragment : NestedSettingsFragment(), DatabaseRetriev } if (dialogFragment != null && !mDatabaseReadOnly) { + @Suppress("DEPRECATION") dialogFragment.setTargetFragment(this, 0) dialogFragment.show(parentFragmentManager, TAG_PREF_FRAGMENT) } From 9feb96b5413f243264eb35fddc795eeba347ba8e Mon Sep 17 00:00:00 2001 From: J-Jamet Date: Thu, 2 Dec 2021 13:30:02 +0100 Subject: [PATCH 08/10] Fix start autofill service --- .../kunzisoft/keepass/settings/NestedAppSettingsFragment.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/src/main/java/com/kunzisoft/keepass/settings/NestedAppSettingsFragment.kt b/app/src/main/java/com/kunzisoft/keepass/settings/NestedAppSettingsFragment.kt index 815f2f99e..291d12379 100644 --- a/app/src/main/java/com/kunzisoft/keepass/settings/NestedAppSettingsFragment.kt +++ b/app/src/main/java/com/kunzisoft/keepass/settings/NestedAppSettingsFragment.kt @@ -156,7 +156,7 @@ class NestedAppSettingsFragment : NestedSettingsFragment() { val intent = Intent(Settings.ACTION_REQUEST_SET_AUTOFILL_SERVICE) intent.data = Uri.parse("package:com.kunzisoft.keepass.autofill.KeeAutofillService") Log.d(javaClass.name, "Autofill enable service: intent=$intent") - startActivityForResult(intent, REQUEST_CODE_AUTOFILL) + startActivity(intent) } else { Log.d(javaClass.name, "Autofill service already enabled.") } @@ -514,7 +514,6 @@ class NestedAppSettingsFragment : NestedSettingsFragment() { } companion object { - private const val REQUEST_CODE_AUTOFILL = 5201 private const val TAG_PREF_FRAGMENT = "TAG_PREF_FRAGMENT" var DATABASE_APPEARANCE_PREFERENCE_CHANGED = false From d4655d70340d89fc528e943ec18f02f695783c41 Mon Sep 17 00:00:00 2001 From: J-Jamet Date: Thu, 2 Dec 2021 13:31:41 +0100 Subject: [PATCH 09/10] Fix search in activity --- .../com/kunzisoft/keepass/activities/GroupActivity.kt | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/GroupActivity.kt b/app/src/main/java/com/kunzisoft/keepass/activities/GroupActivity.kt index 19fda9e07..6afb48a03 100644 --- a/app/src/main/java/com/kunzisoft/keepass/activities/GroupActivity.kt +++ b/app/src/main/java/com/kunzisoft/keepass/activities/GroupActivity.kt @@ -1081,6 +1081,7 @@ class GroupActivity : DatabaseLockActivity(), } } + @Suppress("DEPRECATION") override fun startActivityForResult(intent: Intent, requestCode: Int, options: Bundle?) { /* * ACTION_SEARCH automatically forces a new task. This occurs when you open a kdb file in @@ -1096,13 +1097,6 @@ class GroupActivity : DatabaseLockActivity(), super.startActivityForResult(intent, requestCode, options) } - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - super.onActivityResult(requestCode, resultCode, data) - - // Directly used the onActivityResult in fragment - mGroupFragment?.onActivityResult(requestCode, resultCode, data) - } - private fun removeSearch() { intent.removeExtra(AUTO_SEARCH_KEY) if (Intent.ACTION_SEARCH == intent.action) { From 7a398e545387e051746dd626703de01dbf1f0a37 Mon Sep 17 00:00:00 2001 From: J-Jamet Date: Tue, 7 Dec 2021 16:20:57 +0100 Subject: [PATCH 10/10] Fix activity result for advanced unlocking --- .../keepass/activities/PasswordActivity.kt | 50 ++++--------- .../activities/stylish/StylishFragment.kt | 8 +-- .../biometric/AdvancedUnlockFragment.kt | 72 ++++++++++++------- .../biometric/AdvancedUnlockManager.kt | 29 +++----- .../viewmodels/AdvancedUnlockViewModel.kt | 32 +++++++++ app/src/main/res/layout/activity_password.xml | 1 + 6 files changed, 109 insertions(+), 83 deletions(-) create mode 100644 app/src/main/java/com/kunzisoft/keepass/viewmodels/AdvancedUnlockViewModel.kt diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/PasswordActivity.kt b/app/src/main/java/com/kunzisoft/keepass/activities/PasswordActivity.kt index 3c776f0f0..b2dbe45af 100644 --- a/app/src/main/java/com/kunzisoft/keepass/activities/PasswordActivity.kt +++ b/app/src/main/java/com/kunzisoft/keepass/activities/PasswordActivity.kt @@ -72,6 +72,7 @@ import com.kunzisoft.keepass.utils.MenuUtil import com.kunzisoft.keepass.utils.UriUtil import com.kunzisoft.keepass.view.KeyFileSelectionView import com.kunzisoft.keepass.view.asError +import com.kunzisoft.keepass.viewmodels.AdvancedUnlockViewModel import com.kunzisoft.keepass.viewmodels.DatabaseFileViewModel import java.io.FileNotFoundException @@ -90,7 +91,8 @@ class PasswordActivity : DatabaseModeActivity(), AdvancedUnlockFragment.BuilderL private lateinit var coordinatorLayout: CoordinatorLayout private var advancedUnlockFragment: AdvancedUnlockFragment? = null - private val databaseFileViewModel: DatabaseFileViewModel by viewModels() + private val mDatabaseFileViewModel: DatabaseFileViewModel by viewModels() + private val mAdvancedUnlockViewModel: AdvancedUnlockViewModel by viewModels() private var mDefaultDatabase: Boolean = false private var mDatabaseFileUri: Uri? = null @@ -112,8 +114,6 @@ class PasswordActivity : DatabaseModeActivity(), AdvancedUnlockFragment.BuilderL field = value } - private var mAllowAutoOpenBiometricPrompt: Boolean = true - private var mAutofillActivityResultLauncher: ActivityResultLauncher? = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) AutofillHelper.buildActivityResultLauncher(this) @@ -182,9 +182,6 @@ class PasswordActivity : DatabaseModeActivity(), AdvancedUnlockFragment.BuilderL if (savedInstanceState?.containsKey(KEY_KEYFILE) == true) { mDatabaseKeyFileUri = UriUtil.parse(savedInstanceState.getString(KEY_KEYFILE)) } - if (savedInstanceState?.containsKey(ALLOW_AUTO_OPEN_BIOMETRIC_PROMPT) == true) { - mAllowAutoOpenBiometricPrompt = savedInstanceState.getBoolean(ALLOW_AUTO_OPEN_BIOMETRIC_PROMPT) - } // Init Biometric elements advancedUnlockFragment = supportFragmentManager @@ -200,17 +197,17 @@ class PasswordActivity : DatabaseModeActivity(), AdvancedUnlockFragment.BuilderL // Listen password checkbox to init advanced unlock and confirmation button checkboxPasswordView?.setOnCheckedChangeListener { _, _ -> - advancedUnlockFragment?.checkUnlockAvailability() + mAdvancedUnlockViewModel.checkUnlockAvailability() enableOrNotTheConfirmationButton() } // Observe if default database - databaseFileViewModel.isDefaultDatabase.observe(this) { isDefaultDatabase -> + mDatabaseFileViewModel.isDefaultDatabase.observe(this) { isDefaultDatabase -> mDefaultDatabase = isDefaultDatabase } // Observe database file change - databaseFileViewModel.databaseFileLoaded.observe(this) { databaseFile -> + mDatabaseFileViewModel.databaseFileLoaded.observe(this) { databaseFile -> // Force read only if the file does not exists mForceReadOnly = databaseFile?.let { !it.databaseFileExists @@ -244,12 +241,12 @@ class PasswordActivity : DatabaseModeActivity(), AdvancedUnlockFragment.BuilderL } // Don't allow auto open prompt if lock become when UI visible - mAllowAutoOpenBiometricPrompt = if (DatabaseLockActivity.LOCKING_ACTIVITY_UI_VISIBLE_DURING_LOCK == true) - false - else - mAllowAutoOpenBiometricPrompt + if (DatabaseLockActivity.LOCKING_ACTIVITY_UI_VISIBLE_DURING_LOCK == true) { + mAdvancedUnlockViewModel.allowAutoOpenBiometricPrompt = false + } + mDatabaseFileUri?.let { databaseFileUri -> - databaseFileViewModel.loadDatabaseFile(databaseFileUri) + mDatabaseFileViewModel.loadDatabaseFile(databaseFileUri) } checkPermission() @@ -275,7 +272,7 @@ class PasswordActivity : DatabaseModeActivity(), AdvancedUnlockFragment.BuilderL when (actionTask) { ACTION_DATABASE_LOAD_TASK -> { // Recheck advanced unlock if error - advancedUnlockFragment?.initAdvancedUnlockMode() + mAdvancedUnlockViewModel.initAdvancedUnlockMode() if (result.isSuccess) { launchGroupActivityIfLoaded(database) @@ -323,7 +320,7 @@ class PasswordActivity : DatabaseModeActivity(), AdvancedUnlockFragment.BuilderL is FileNotFoundDatabaseException -> { // Remove this default database inaccessible if (mDefaultDatabase) { - databaseFileViewModel.removeDefaultDatabase() + mDatabaseFileViewModel.removeDefaultDatabase() } } } @@ -356,7 +353,7 @@ class PasswordActivity : DatabaseModeActivity(), AdvancedUnlockFragment.BuilderL mDatabaseKeyFileUri = intent?.getParcelableExtra(KEY_KEYFILE) } mDatabaseFileUri?.let { - databaseFileViewModel.checkIfIsDefaultDatabase(it) + mDatabaseFileViewModel.checkIfIsDefaultDatabase(it) } } @@ -448,8 +445,7 @@ class PasswordActivity : DatabaseModeActivity(), AdvancedUnlockFragment.BuilderL verifyCheckboxesAndLoadDatabase(password, keyFileUri) } else { // Init Biometric elements - advancedUnlockFragment?.loadDatabase(databaseFileUri, - mAllowAutoOpenBiometricPrompt) + mAdvancedUnlockViewModel.databaseFileLoaded(databaseFileUri) } enableOrNotTheConfirmationButton() @@ -509,7 +505,6 @@ class PasswordActivity : DatabaseModeActivity(), AdvancedUnlockFragment.BuilderL override fun onPause() { // Reinit locking activity UI variable DatabaseLockActivity.LOCKING_ACTIVITY_UI_VISIBLE_DURING_LOCK = null - mAllowAutoOpenBiometricPrompt = true super.onPause() } @@ -520,7 +515,6 @@ class PasswordActivity : DatabaseModeActivity(), AdvancedUnlockFragment.BuilderL outState.putString(KEY_KEYFILE, it.toString()) } outState.putBoolean(KEY_READ_ONLY, mReadOnly) - outState.putBoolean(ALLOW_AUTO_OPEN_BIOMETRIC_PROMPT, false) super.onSaveInstanceState(outState) } @@ -722,18 +716,6 @@ class PasswordActivity : DatabaseModeActivity(), AdvancedUnlockFragment.BuilderL return super.onOptionsItemSelected(item) } - override fun onActivityResult( - requestCode: Int, - resultCode: Int, - data: Intent?) { - super.onActivityResult(requestCode, resultCode, data) - - mAllowAutoOpenBiometricPrompt = false - - // To get device credential unlock result - advancedUnlockFragment?.onActivityResult(requestCode, resultCode, data) - } - companion object { private val TAG = PasswordActivity::class.java.name @@ -750,8 +732,6 @@ class PasswordActivity : DatabaseModeActivity(), AdvancedUnlockFragment.BuilderL private const val KEY_PERMISSION_ASKED = "KEY_PERMISSION_ASKED" private const val WRITE_EXTERNAL_STORAGE_REQUEST = 647 - private const val ALLOW_AUTO_OPEN_BIOMETRIC_PROMPT = "ALLOW_AUTO_OPEN_BIOMETRIC_PROMPT" - private fun buildAndLaunchIntent(activity: Activity, databaseFile: Uri, keyFile: Uri?, intentBuildLauncher: (Intent) -> Unit) { val intent = Intent(activity, PasswordActivity::class.java) diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/stylish/StylishFragment.kt b/app/src/main/java/com/kunzisoft/keepass/activities/stylish/StylishFragment.kt index 8d9aef137..ddb69d1c6 100644 --- a/app/src/main/java/com/kunzisoft/keepass/activities/stylish/StylishFragment.kt +++ b/app/src/main/java/com/kunzisoft/keepass/activities/stylish/StylishFragment.kt @@ -23,12 +23,12 @@ import android.content.Context import android.graphics.Color import android.os.Build import android.os.Bundle -import androidx.annotation.StyleRes -import androidx.fragment.app.Fragment -import androidx.appcompat.view.ContextThemeWrapper import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.annotation.StyleRes +import androidx.appcompat.view.ContextThemeWrapper +import androidx.fragment.app.Fragment abstract class StylishFragment : Fragment() { @@ -42,7 +42,6 @@ abstract class StylishFragment : Fragment() { contextThemed = ContextThemeWrapper(context, themeId) } - @Suppress("DEPRECATION") override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { // To fix status bar color if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { @@ -58,6 +57,7 @@ abstract class StylishFragment : Fragment() { try { val taWindowStatusLight = contextThemed?.theme?.obtainStyledAttributes(intArrayOf(android.R.attr.windowLightStatusBar)) if (taWindowStatusLight?.getBoolean(0, false) == true) { + @Suppress("DEPRECATION") window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR } taWindowStatusLight?.recycle() diff --git a/app/src/main/java/com/kunzisoft/keepass/biometric/AdvancedUnlockFragment.kt b/app/src/main/java/com/kunzisoft/keepass/biometric/AdvancedUnlockFragment.kt index d7fc08436..c933ca3da 100644 --- a/app/src/main/java/com/kunzisoft/keepass/biometric/AdvancedUnlockFragment.kt +++ b/app/src/main/java/com/kunzisoft/keepass/biometric/AdvancedUnlockFragment.kt @@ -19,6 +19,7 @@ */ package com.kunzisoft.keepass.biometric +import android.app.Activity import android.content.Context import android.content.Intent import android.net.Uri @@ -27,9 +28,11 @@ import android.os.Bundle import android.provider.Settings import android.util.Log import android.view.* +import androidx.activity.result.contract.ActivityResultContracts import androidx.annotation.RequiresApi import androidx.biometric.BiometricManager import androidx.biometric.BiometricPrompt +import androidx.fragment.app.activityViewModels import androidx.lifecycle.lifecycleScope import com.getkeepsafe.taptargetview.TapTargetView import com.kunzisoft.keepass.R @@ -39,6 +42,7 @@ import com.kunzisoft.keepass.database.exception.IODatabaseException import com.kunzisoft.keepass.education.PasswordActivityEducation import com.kunzisoft.keepass.settings.PreferencesUtil import com.kunzisoft.keepass.view.AdvancedUnlockInfoView +import com.kunzisoft.keepass.viewmodels.AdvancedUnlockViewModel import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -59,9 +63,12 @@ class AdvancedUnlockFragment: StylishFragment(), AdvancedUnlockManager.AdvancedU /** * Manage setting to auto open biometric prompt */ - private var mAutoOpenPrompt: Boolean = false + private var mAutoOpenPrompt: Boolean get() { - return field && mAutoOpenPromptEnabled + return mAdvancedUnlockViewModel.allowAutoOpenBiometricPrompt && mAutoOpenPromptEnabled + } + set(value) { + mAdvancedUnlockViewModel.allowAutoOpenBiometricPrompt = value } // Variable to check if the prompt can be open (if the right activity is currently shown) @@ -72,6 +79,8 @@ class AdvancedUnlockFragment: StylishFragment(), AdvancedUnlockManager.AdvancedU private var cipherDatabaseListener: CipherDatabaseAction.CipherDatabaseListener? = null + private val mAdvancedUnlockViewModel: AdvancedUnlockViewModel by activityViewModels() + // Only to fix multiple fingerprint menu #332 private var mAllowAdvancedUnlockMenu = false private var mAddBiometricMenuInProgress = false @@ -79,6 +88,15 @@ class AdvancedUnlockFragment: StylishFragment(), AdvancedUnlockManager.AdvancedU // Only keep connection when we request a device credential activity private var keepConnection = false + private var mDeviceCredentialResultLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> + mAdvancedUnlockViewModel.allowAutoOpenBiometricPrompt = false + // To wait resume + if (keepConnection) { + mAdvancedUnlockViewModel.deviceCredentialAuthSucceeded = result.resultCode == Activity.RESULT_OK + } + keepConnection = false + } + override fun onAttach(context: Context) { super.onAttach(context) @@ -97,10 +115,21 @@ class AdvancedUnlockFragment: StylishFragment(), AdvancedUnlockManager.AdvancedU override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - retainInstance = true setHasOptionsMenu(true) cipherDatabaseAction = CipherDatabaseAction.getInstance(requireContext().applicationContext) + + mAdvancedUnlockViewModel.onInitAdvancedUnlockModeRequested.observe(this) { + initAdvancedUnlockMode() + } + + mAdvancedUnlockViewModel.onUnlockAvailabilityCheckRequested.observe(this) { + checkUnlockAvailability() + } + + mAdvancedUnlockViewModel.onDatabaseFileLoaded.observe(this) { + onDatabaseLoaded(it) + } } override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { @@ -114,17 +143,6 @@ class AdvancedUnlockFragment: StylishFragment(), AdvancedUnlockManager.AdvancedU return rootView } - private data class ActivityResult(var requestCode: Int, var resultCode: Int, var data: Intent?) - private var activityResult: ActivityResult? = null - - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - // To wait resume - if (keepConnection) { - activityResult = ActivityResult(requestCode, resultCode, data) - } - keepConnection = false - } - override fun onResume() { super.onResume() context?.let { @@ -154,32 +172,38 @@ class AdvancedUnlockFragment: StylishFragment(), AdvancedUnlockManager.AdvancedU return super.onOptionsItemSelected(item) } - fun loadDatabase(databaseUri: Uri?, autoOpenPrompt: Boolean) { + private fun onDatabaseLoaded(databaseUri: Uri?) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { // To get device credential unlock result, only if same database uri if (databaseUri != null && mAdvancedUnlockEnabled) { - activityResult?.let { + val deviceCredentialAuthSucceeded = mAdvancedUnlockViewModel.deviceCredentialAuthSucceeded + deviceCredentialAuthSucceeded?.let { if (databaseUri == databaseFileUri) { - advancedUnlockManager?.onActivityResult(it.requestCode, it.resultCode) + if (deviceCredentialAuthSucceeded == true) { + advancedUnlockManager?.advancedUnlockCallback?.onAuthenticationSucceeded() + } else { + advancedUnlockManager?.advancedUnlockCallback?.onAuthenticationFailed() + } } else { disconnect() } } ?: run { - this.mAutoOpenPrompt = autoOpenPrompt - connect(databaseUri) + if (databaseUri != databaseFileUri) { + connect(databaseUri) + } } } else { disconnect() } - activityResult = null + mAdvancedUnlockViewModel.deviceCredentialAuthSucceeded = null } } /** * Check unlock availability and change the current mode depending of device's state */ - fun checkUnlockAvailability() { + private fun checkUnlockAvailability() { context?.let { context -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { allowOpenBiometricPrompt = true @@ -317,7 +341,8 @@ class AdvancedUnlockFragment: StylishFragment(), AdvancedUnlockManager.AdvancedU if (cryptoPrompt.isDeviceCredentialOperation) keepConnection = true try { - advancedUnlockManager?.openAdvancedUnlockPrompt(cryptoPrompt) + advancedUnlockManager?.openAdvancedUnlockPrompt(cryptoPrompt, + mDeviceCredentialResultLauncher) } catch (e: Exception) { Log.e(TAG, "Unable to open advanced unlock prompt", e) setAdvancedUnlockedTitleView(R.string.advanced_unlock_prompt_not_initialized) @@ -369,8 +394,7 @@ class AdvancedUnlockFragment: StylishFragment(), AdvancedUnlockManager.AdvancedU } ?: throw Exception("AdvancedUnlockManager not initialized") } - @Synchronized - fun initAdvancedUnlockMode() { + private fun initAdvancedUnlockMode() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { mAllowAdvancedUnlockMenu = false try { diff --git a/app/src/main/java/com/kunzisoft/keepass/biometric/AdvancedUnlockManager.kt b/app/src/main/java/com/kunzisoft/keepass/biometric/AdvancedUnlockManager.kt index 8fd0a136e..a771c39de 100644 --- a/app/src/main/java/com/kunzisoft/keepass/biometric/AdvancedUnlockManager.kt +++ b/app/src/main/java/com/kunzisoft/keepass/biometric/AdvancedUnlockManager.kt @@ -19,9 +19,9 @@ */ package com.kunzisoft.keepass.biometric -import android.app.Activity import android.app.KeyguardManager import android.content.Context +import android.content.Intent import android.content.pm.PackageManager import android.os.Build import android.security.keystore.KeyGenParameterSpec @@ -30,6 +30,7 @@ import android.security.keystore.KeyProperties import android.util.Base64 import android.util.Log import android.widget.Toast +import androidx.activity.result.ActivityResultLauncher import androidx.annotation.RequiresApi import androidx.biometric.BiometricManager import androidx.biometric.BiometricManager.Authenticators.* @@ -312,9 +313,9 @@ class AdvancedUnlockManager(private var retrieveContext: () -> FragmentActivity) } } - @Suppress("DEPRECATION") - @Synchronized - fun openAdvancedUnlockPrompt(cryptoPrompt: AdvancedUnlockCryptoPrompt) { + fun openAdvancedUnlockPrompt(cryptoPrompt: AdvancedUnlockCryptoPrompt, + deviceCredentialResultLauncher: ActivityResultLauncher + ) { // Init advanced unlock prompt if (biometricPrompt == null) { biometricPrompt = BiometricPrompt(retrieveContext(), @@ -345,20 +346,10 @@ class AdvancedUnlockManager(private var retrieveContext: () -> FragmentActivity) } else if (cryptoPrompt.isDeviceCredentialOperation) { val keyGuardManager = ContextCompat.getSystemService(retrieveContext(), KeyguardManager::class.java) - retrieveContext().startActivityForResult( - keyGuardManager?.createConfirmDeviceCredentialIntent(promptTitle, promptDescription), - REQUEST_DEVICE_CREDENTIAL) - } - } - - @Synchronized - fun onActivityResult(requestCode: Int, resultCode: Int) { - if (requestCode == REQUEST_DEVICE_CREDENTIAL) { - if (resultCode == Activity.RESULT_OK) { - advancedUnlockCallback?.onAuthenticationSucceeded() - } else { - advancedUnlockCallback?.onAuthenticationFailed() - } + @Suppress("DEPRECATION") + deviceCredentialResultLauncher.launch( + keyGuardManager?.createConfirmDeviceCredentialIntent(promptTitle, promptDescription) + ) } } @@ -390,8 +381,6 @@ class AdvancedUnlockManager(private var retrieveContext: () -> FragmentActivity) private const val ADVANCED_UNLOCK_BLOCKS_MODES = KeyProperties.BLOCK_MODE_CBC private const val ADVANCED_UNLOCK_ENCRYPTION_PADDING = KeyProperties.ENCRYPTION_PADDING_PKCS7 - private const val REQUEST_DEVICE_CREDENTIAL = 556 - @RequiresApi(api = Build.VERSION_CODES.M) fun canAuthenticate(context: Context): Int { return try { diff --git a/app/src/main/java/com/kunzisoft/keepass/viewmodels/AdvancedUnlockViewModel.kt b/app/src/main/java/com/kunzisoft/keepass/viewmodels/AdvancedUnlockViewModel.kt new file mode 100644 index 000000000..1c5c0a140 --- /dev/null +++ b/app/src/main/java/com/kunzisoft/keepass/viewmodels/AdvancedUnlockViewModel.kt @@ -0,0 +1,32 @@ +package com.kunzisoft.keepass.viewmodels + +import android.net.Uri +import androidx.lifecycle.LiveData +import androidx.lifecycle.ViewModel + +class AdvancedUnlockViewModel : ViewModel() { + + var allowAutoOpenBiometricPrompt : Boolean = true + var deviceCredentialAuthSucceeded: Boolean? = null + + val onInitAdvancedUnlockModeRequested : LiveData get() = _onInitAdvancedUnlockModeRequested + private val _onInitAdvancedUnlockModeRequested = SingleLiveEvent() + + val onUnlockAvailabilityCheckRequested : LiveData get() = _onUnlockAvailabilityCheckRequested + private val _onUnlockAvailabilityCheckRequested = SingleLiveEvent() + + val onDatabaseFileLoaded : LiveData get() = _onDatabaseFileLoaded + private val _onDatabaseFileLoaded = SingleLiveEvent() + + fun initAdvancedUnlockMode() { + _onInitAdvancedUnlockModeRequested.call() + } + + fun checkUnlockAvailability() { + _onUnlockAvailabilityCheckRequested.call() + } + + fun databaseFileLoaded(databaseUri: Uri?) { + _onDatabaseFileLoaded.value = databaseUri + } +} \ No newline at end of file diff --git a/app/src/main/res/layout/activity_password.xml b/app/src/main/res/layout/activity_password.xml index e9d84a479..b23de89af 100644 --- a/app/src/main/res/layout/activity_password.xml +++ b/app/src/main/res/layout/activity_password.xml @@ -23,6 +23,7 @@ xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" + android:background="?android:attr/windowBackground" tools:targetApi="o">