diff --git a/CHANGELOG b/CHANGELOG index 003bc9d4f..526efba83 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -3,7 +3,7 @@ KeePassDX(4.0.3) * Fix username autofill #1665 #530 #1572 #1426 #1523 #1556 #1653 #1658 #1508 #1667 * Fix regex OTP recognition #1596 * Change password color dynamically #1490 - * Small fixes #1641 #1656 + * Small fixes #1641 #1656 #1649 KeePassDX(4.0.2) * Fix Autofill with API 33 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 2e3f06492..4c9d84574 100644 --- a/app/src/main/java/com/kunzisoft/keepass/activities/AutofillLauncherActivity.kt +++ b/app/src/main/java/com/kunzisoft/keepass/activities/AutofillLauncherActivity.kt @@ -25,6 +25,7 @@ import android.content.Context import android.content.Intent import android.os.Build import android.os.Bundle +import android.util.Log import android.widget.Toast import androidx.activity.result.ActivityResultLauncher import androidx.annotation.RequiresApi @@ -44,6 +45,7 @@ import com.kunzisoft.keepass.settings.PreferencesUtil import com.kunzisoft.keepass.utils.getParcelableCompat import com.kunzisoft.keepass.utils.getParcelableExtraCompat import com.kunzisoft.keepass.utils.WebDomain +import java.lang.RuntimeException @RequiresApi(api = Build.VERSION_CODES.O) class AutofillLauncherActivity : DatabaseModeActivity() { @@ -216,6 +218,8 @@ class AutofillLauncherActivity : DatabaseModeActivity() { companion object { + private val TAG = AutofillLauncherActivity::class.java.name + private const val KEY_SELECTION_BUNDLE = "KEY_SELECTION_BUNDLE" private const val KEY_SEARCH_INFO = "KEY_SEARCH_INFO" private const val KEY_INLINE_SUGGESTION = "KEY_INLINE_SUGGESTION" @@ -224,37 +228,51 @@ class AutofillLauncherActivity : DatabaseModeActivity() { fun getPendingIntentForSelection(context: Context, searchInfo: SearchInfo? = null, - compatInlineSuggestionsRequest: CompatInlineSuggestionsRequest? = null): PendingIntent { - return PendingIntent.getActivity(context, 0, - // Doesn't work with direct extra Parcelable (don't know why?) - // Wrap into a bundle to bypass the problem - Intent(context, AutofillLauncherActivity::class.java).apply { - putExtra(KEY_SELECTION_BUNDLE, Bundle().apply { - putParcelable(KEY_SEARCH_INFO, searchInfo) - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { - putParcelable(KEY_INLINE_SUGGESTION, compatInlineSuggestionsRequest) - } - }) - }, - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_CANCEL_CURRENT - } else { - PendingIntent.FLAG_CANCEL_CURRENT - }) + compatInlineSuggestionsRequest: CompatInlineSuggestionsRequest? = null): PendingIntent? { + try { + return PendingIntent.getActivity( + context, 0, + // Doesn't work with direct extra Parcelable (don't know why?) + // Wrap into a bundle to bypass the problem + Intent(context, AutofillLauncherActivity::class.java).apply { + putExtra(KEY_SELECTION_BUNDLE, Bundle().apply { + putParcelable(KEY_SEARCH_INFO, searchInfo) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + putParcelable(KEY_INLINE_SUGGESTION, compatInlineSuggestionsRequest) + } + }) + }, + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_CANCEL_CURRENT + } else { + PendingIntent.FLAG_CANCEL_CURRENT + } + ) + } catch (e: RuntimeException) { + Log.e(TAG, "Unable to create pending intent for selection", e) + return null + } } fun getPendingIntentForRegistration(context: Context, - registerInfo: RegisterInfo): PendingIntent { - return PendingIntent.getActivity(context, 0, - Intent(context, AutofillLauncherActivity::class.java).apply { - EntrySelectionHelper.addSpecialModeInIntent(this, SpecialMode.REGISTRATION) - putExtra(KEY_REGISTER_INFO, registerInfo) - }, - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_CANCEL_CURRENT - } else { - PendingIntent.FLAG_CANCEL_CURRENT - }) + registerInfo: RegisterInfo): PendingIntent? { + try { + return PendingIntent.getActivity( + context, 0, + Intent(context, AutofillLauncherActivity::class.java).apply { + EntrySelectionHelper.addSpecialModeInIntent(this, SpecialMode.REGISTRATION) + putExtra(KEY_REGISTER_INFO, registerInfo) + }, + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_CANCEL_CURRENT + } else { + PendingIntent.FLAG_CANCEL_CURRENT + } + ) + } catch (e: RuntimeException) { + Log.e(TAG, "Unable to create pending intent for registration", e) + return null + } } fun launchForRegistration(context: Context, 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 4fbc925cd..501043c7a 100644 --- a/app/src/main/java/com/kunzisoft/keepass/autofill/AutofillHelper.kt +++ b/app/src/main/java/com/kunzisoft/keepass/autofill/AutofillHelper.kt @@ -452,46 +452,52 @@ object AutofillHelper { manualSelection = true } val manualSelectionView = RemoteViews(context.packageName, R.layout.item_autofill_select_entry) - val pendingIntent = AutofillLauncherActivity.getPendingIntentForSelection(context, - searchInfo, compatInlineSuggestionsRequest) + AutofillLauncherActivity.getPendingIntentForSelection(context, + searchInfo, compatInlineSuggestionsRequest)?.let { pendingIntent -> - var inlinePresentation: InlinePresentation? = null - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { - compatInlineSuggestionsRequest?.inlineSuggestionsRequest?.let { inlineSuggestionsRequest -> - val inlinePresentationSpec = inlineSuggestionsRequest.inlinePresentationSpecs[0] - inlinePresentation = buildInlinePresentationForManualSelection(context, inlinePresentationSpec, pendingIntent) + var inlinePresentation: InlinePresentation? = null + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + compatInlineSuggestionsRequest?.inlineSuggestionsRequest?.let { inlineSuggestionsRequest -> + val inlinePresentationSpec = + inlineSuggestionsRequest.inlinePresentationSpecs[0] + inlinePresentation = buildInlinePresentationForManualSelection( + context, + inlinePresentationSpec, + pendingIntent + ) + } } - } - val datasetBuilder = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - Dataset.Builder(Presentations.Builder() - .apply { + val datasetBuilder = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + Dataset.Builder(Presentations.Builder() + .apply { + inlinePresentation?.let { + setInlinePresentation(it) + } + } + .setDialogPresentation(manualSelectionView) + .setMenuPresentation(manualSelectionView) + .build()) + } else { + @Suppress("DEPRECATION") + Dataset.Builder(manualSelectionView).apply { inlinePresentation?.let { - setInlinePresentation(it) - } - } - .setDialogPresentation(manualSelectionView) - .setMenuPresentation(manualSelectionView) - .build()) - } else { - @Suppress("DEPRECATION") - Dataset.Builder(manualSelectionView).apply { - inlinePresentation?.let { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { - setInlinePresentation(it) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + setInlinePresentation(it) + } } } } - } - parseResult.allAutofillIds().let { autofillIds -> - autofillIds.forEach { id -> - datasetBuilder.addValueToDatasetBuilder(id, null) - datasetBuilder.setAuthentication(pendingIntent.intentSender) + parseResult.allAutofillIds().let { autofillIds -> + autofillIds.forEach { id -> + datasetBuilder.addValueToDatasetBuilder(id, null) + datasetBuilder.setAuthentication(pendingIntent.intentSender) + } + val dataset = datasetBuilder.build() + Log.d(TAG, "Autofill Dataset for manual selection $dataset created") + responseBuilder.addDataset(dataset) } - val dataset = datasetBuilder.build() - Log.d(TAG, "Autofill Dataset for manual selection $dataset created") - responseBuilder.addDataset(dataset) } } diff --git a/app/src/main/java/com/kunzisoft/keepass/autofill/KeeAutofillService.kt b/app/src/main/java/com/kunzisoft/keepass/autofill/KeeAutofillService.kt index e2422373d..f669c2574 100644 --- a/app/src/main/java/com/kunzisoft/keepass/autofill/KeeAutofillService.kt +++ b/app/src/main/java/com/kunzisoft/keepass/autofill/KeeAutofillService.kt @@ -162,154 +162,184 @@ class KeeAutofillService : AutofillService() { if (autofillIds.isNotEmpty()) { // If the entire Autofill Response is authenticated, AuthActivity is used // to generate Response. - val intentSender = AutofillLauncherActivity.getPendingIntentForSelection(this, - searchInfo, inlineSuggestionsRequest).intentSender - val responseBuilder = FillResponse.Builder() - val remoteViewsUnlock: RemoteViews = if (database == null) { - if (!parseResult.webDomain.isNullOrEmpty()) { - RemoteViews( - packageName, - R.layout.item_autofill_unlock_web_domain - ).apply { - setTextViewText( - R.id.autofill_web_domain_text, - parseResult.webDomain - ) - } - } else if (!parseResult.applicationId.isNullOrEmpty()) { - RemoteViews(packageName, R.layout.item_autofill_unlock_app_id).apply { - setTextViewText( - R.id.autofill_app_id_text, - parseResult.applicationId - ) + AutofillLauncherActivity.getPendingIntentForSelection(this, + searchInfo, inlineSuggestionsRequest)?.intentSender?.let { intentSender -> + val responseBuilder = FillResponse.Builder() + val remoteViewsUnlock: RemoteViews = if (database == null) { + if (!parseResult.webDomain.isNullOrEmpty()) { + RemoteViews( + packageName, + R.layout.item_autofill_unlock_web_domain + ).apply { + setTextViewText( + R.id.autofill_web_domain_text, + parseResult.webDomain + ) + } + } else if (!parseResult.applicationId.isNullOrEmpty()) { + RemoteViews(packageName, R.layout.item_autofill_unlock_app_id).apply { + setTextViewText( + R.id.autofill_app_id_text, + parseResult.applicationId + ) + } + } else { + RemoteViews(packageName, R.layout.item_autofill_unlock) } } else { - RemoteViews(packageName, R.layout.item_autofill_unlock) - } - } else { - if (!parseResult.webDomain.isNullOrEmpty()) { - RemoteViews( - packageName, - R.layout.item_autofill_select_entry_web_domain - ).apply { - setTextViewText( - R.id.autofill_web_domain_text, - parseResult.webDomain - ) + if (!parseResult.webDomain.isNullOrEmpty()) { + RemoteViews( + packageName, + R.layout.item_autofill_select_entry_web_domain + ).apply { + setTextViewText( + R.id.autofill_web_domain_text, + parseResult.webDomain + ) + } + } else if (!parseResult.applicationId.isNullOrEmpty()) { + RemoteViews( + packageName, + R.layout.item_autofill_select_entry_app_id + ).apply { + setTextViewText( + R.id.autofill_app_id_text, + parseResult.applicationId + ) + } + } else { + RemoteViews(packageName, R.layout.item_autofill_select_entry) } - } else if (!parseResult.applicationId.isNullOrEmpty()) { - RemoteViews(packageName, R.layout.item_autofill_select_entry_app_id).apply { - setTextViewText( - R.id.autofill_app_id_text, - parseResult.applicationId - ) - } - } else { - RemoteViews(packageName, R.layout.item_autofill_select_entry) } - } - // Tell the autofill framework the interest to save credentials - if (askToSaveData) { - var types: Int = SaveInfo.SAVE_DATA_TYPE_GENERIC - val requiredIds = ArrayList() - val optionalIds = ArrayList() + // Tell the autofill framework the interest to save credentials + if (askToSaveData) { + var types: Int = SaveInfo.SAVE_DATA_TYPE_GENERIC + val requiredIds = ArrayList() + val optionalIds = ArrayList() - // Only if at least a password - parseResult.passwordId?.let { passwordInfo -> - parseResult.usernameId?.let { usernameInfo -> - types = types or SaveInfo.SAVE_DATA_TYPE_USERNAME - requiredIds.add(usernameInfo) + // Only if at least a password + parseResult.passwordId?.let { passwordInfo -> + parseResult.usernameId?.let { usernameInfo -> + types = types or SaveInfo.SAVE_DATA_TYPE_USERNAME + requiredIds.add(usernameInfo) + } + types = types or SaveInfo.SAVE_DATA_TYPE_PASSWORD + requiredIds.add(passwordInfo) } - types = types or SaveInfo.SAVE_DATA_TYPE_PASSWORD - requiredIds.add(passwordInfo) - } - // or a credit card form - if (requiredIds.isEmpty()) { - parseResult.creditCardNumberId?.let { numberId -> - types = types or SaveInfo.SAVE_DATA_TYPE_CREDIT_CARD - requiredIds.add(numberId) - Log.d(TAG, "Asking to save credit card number") + // or a credit card form + if (requiredIds.isEmpty()) { + parseResult.creditCardNumberId?.let { numberId -> + types = types or SaveInfo.SAVE_DATA_TYPE_CREDIT_CARD + requiredIds.add(numberId) + Log.d(TAG, "Asking to save credit card number") + } + parseResult.creditCardExpirationDateId?.let { id -> optionalIds.add(id) } + parseResult.creditCardExpirationYearId?.let { id -> optionalIds.add(id) } + parseResult.creditCardExpirationMonthId?.let { id -> optionalIds.add(id) } + parseResult.creditCardHolderId?.let { id -> optionalIds.add(id) } + parseResult.cardVerificationValueId?.let { id -> optionalIds.add(id) } } - parseResult.creditCardExpirationDateId?.let { id -> optionalIds.add(id) } - parseResult.creditCardExpirationYearId?.let { id -> optionalIds.add(id) } - parseResult.creditCardExpirationMonthId?.let { id -> optionalIds.add(id) } - parseResult.creditCardHolderId?.let { id -> optionalIds.add(id) } - parseResult.cardVerificationValueId?.let { id -> optionalIds.add(id) } - } - if (requiredIds.isNotEmpty()) { - val builder = SaveInfo.Builder(types, requiredIds.toTypedArray()) - if (optionalIds.isNotEmpty()) { - builder.setOptionalIds(optionalIds.toTypedArray()) + if (requiredIds.isNotEmpty()) { + val builder = SaveInfo.Builder(types, requiredIds.toTypedArray()) + if (optionalIds.isNotEmpty()) { + builder.setOptionalIds(optionalIds.toTypedArray()) + } + responseBuilder.setSaveInfo(builder.build()) } - responseBuilder.setSaveInfo(builder.build()) } - } - // Build inline presentation - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R - && autofillInlineSuggestionsEnabled) { - var inlinePresentation: InlinePresentation? = null - inlineSuggestionsRequest?.inlineSuggestionsRequest?.let { inlineSuggestionsRequest -> - val inlinePresentationSpecs = inlineSuggestionsRequest.inlinePresentationSpecs - if (inlineSuggestionsRequest.maxSuggestionCount > 0 - && inlinePresentationSpecs.size > 0) { - val inlinePresentationSpec = inlinePresentationSpecs[0] + // Build inline presentation + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R + && autofillInlineSuggestionsEnabled + ) { + var inlinePresentation: InlinePresentation? = null + inlineSuggestionsRequest?.inlineSuggestionsRequest?.let { inlineSuggestionsRequest -> + val inlinePresentationSpecs = + inlineSuggestionsRequest.inlinePresentationSpecs + if (inlineSuggestionsRequest.maxSuggestionCount > 0 + && inlinePresentationSpecs.size > 0 + ) { + val inlinePresentationSpec = inlinePresentationSpecs[0] - // Make sure that the IME spec claims support for v1 UI template. - val imeStyle = inlinePresentationSpec.style - if (UiVersions.getVersions(imeStyle).contains(UiVersions.INLINE_UI_VERSION_1)) { - // Build the content for IME UI - inlinePresentation = InlinePresentation( + // Make sure that the IME spec claims support for v1 UI template. + val imeStyle = inlinePresentationSpec.style + if (UiVersions.getVersions(imeStyle) + .contains(UiVersions.INLINE_UI_VERSION_1) + ) { + // Build the content for IME UI + inlinePresentation = InlinePresentation( InlineSuggestionUi.newContentBuilder( - PendingIntent.getActivity(this, - 0, - Intent(this, AutofillSettingsActivity::class.java), - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - PendingIntent.FLAG_IMMUTABLE - } else { - 0 - }) + PendingIntent.getActivity( + this, + 0, + Intent(this, AutofillSettingsActivity::class.java), + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + PendingIntent.FLAG_IMMUTABLE + } else { + 0 + } + ) ).apply { setContentDescription(getString(R.string.autofill_sign_in_prompt)) setTitle(getString(R.string.autofill_sign_in_prompt)) - setStartIcon(Icon.createWithResource(this@KeeAutofillService, R.mipmap.ic_launcher_round).apply { - setTintBlendMode(BlendMode.DST) - }) - }.build().slice, inlinePresentationSpec, false) + setStartIcon( + Icon.createWithResource( + this@KeeAutofillService, + R.mipmap.ic_launcher_round + ).apply { + setTintBlendMode(BlendMode.DST) + }) + }.build().slice, inlinePresentationSpec, false + ) + } } } - } - // Build response - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - try { - // Buggy method on some API 33 devices + // Build response + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + try { + // Buggy method on some API 33 devices + responseBuilder.setAuthentication( + autofillIds, + intentSender, + Presentations.Builder().apply { + inlinePresentation?.let { + setInlinePresentation(it) + } + setDialogPresentation(remoteViewsUnlock) + }.build() + ) + } catch (e: Exception) { + Log.e(TAG, "Unable to use the new setAuthentication method.", e) + @Suppress("DEPRECATION") + responseBuilder.setAuthentication( + autofillIds, + intentSender, + remoteViewsUnlock, + inlinePresentation + ) + } + } else { + @Suppress("DEPRECATION") responseBuilder.setAuthentication( autofillIds, intentSender, - Presentations.Builder().apply { - inlinePresentation?.let { - setInlinePresentation(it) - } - setDialogPresentation(remoteViewsUnlock) - }.build() + remoteViewsUnlock, + inlinePresentation ) - } catch (e: Exception) { - Log.e(TAG, "Unable to use the new setAuthentication method.", e) - @Suppress("DEPRECATION") - responseBuilder.setAuthentication(autofillIds, intentSender, remoteViewsUnlock, inlinePresentation) } } else { @Suppress("DEPRECATION") - responseBuilder.setAuthentication(autofillIds, intentSender, remoteViewsUnlock, inlinePresentation) + responseBuilder.setAuthentication( + autofillIds, + intentSender, + remoteViewsUnlock + ) } - } else { - @Suppress("DEPRECATION") - responseBuilder.setAuthentication(autofillIds, intentSender, remoteViewsUnlock) + success = true + callback.onSuccess(responseBuilder.build()) } - success = true - callback.onSuccess(responseBuilder.build()) } } if (!success) diff --git a/fastlane/metadata/android/en-US/changelogs/126.txt b/fastlane/metadata/android/en-US/changelogs/126.txt index ba029fb53..add7108dd 100644 --- a/fastlane/metadata/android/en-US/changelogs/126.txt +++ b/fastlane/metadata/android/en-US/changelogs/126.txt @@ -2,4 +2,4 @@ * Fix username autofill #1665 #530 #1572 #1426 #1523 #1556 #1653 #1658 #1508 #1667 * Fix regex OTP recognition #1596 * Change password color dynamically #1490 - * Small fixes #1641 #1656 \ No newline at end of file + * Small fixes #1641 #1656 #1649 \ No newline at end of file diff --git a/fastlane/metadata/android/fr-FR/changelogs/126.txt b/fastlane/metadata/android/fr-FR/changelogs/126.txt index 1211b0c18..9f7451ef6 100644 --- a/fastlane/metadata/android/fr-FR/changelogs/126.txt +++ b/fastlane/metadata/android/fr-FR/changelogs/126.txt @@ -2,4 +2,4 @@ * Correction du nom d'utilisateur dans la reconnaissance automatique #1665 #530 #1572 #1426 #1523 #1556 #1653 #1658 #1508 #1667 * Correction de la regex de reconnaissance OTP #1596 * Changement de couleur de mot passe dynamique #1490 - * Petites corrections #1641 #1656 + * Petites corrections #1641 #1656 #1649