diff options
Diffstat (limited to 'app/src')
4 files changed, 175 insertions, 236 deletions
diff --git a/app/src/main/java/app/passwordstore/ui/autofill/AutofillPublisherChangedActivity.kt b/app/src/main/java/app/passwordstore/ui/autofill/AutofillPublisherChangedActivity.kt index 5b55143a..803896a5 100644 --- a/app/src/main/java/app/passwordstore/ui/autofill/AutofillPublisherChangedActivity.kt +++ b/app/src/main/java/app/passwordstore/ui/autofill/AutofillPublisherChangedActivity.kt @@ -13,21 +13,20 @@ import android.os.Build import android.os.Bundle import android.service.autofill.FillResponse import android.text.format.DateUtils -import android.view.View import android.view.autofill.AutofillManager +import androidx.activity.compose.setContent import androidx.appcompat.app.AppCompatActivity import androidx.core.content.IntentCompat import app.passwordstore.R -import app.passwordstore.databinding.ActivityOreoAutofillPublisherChangedBinding +import app.passwordstore.ui.compose.theme.APSTheme import app.passwordstore.util.autofill.AutofillMatcher import app.passwordstore.util.autofill.AutofillPublisherChangedException import app.passwordstore.util.extensions.asLog import app.passwordstore.util.extensions.getApplicationInfoCompat import app.passwordstore.util.extensions.getPackageInfoCompat -import app.passwordstore.util.extensions.viewBinding import com.github.androidpasswordstore.autofillparser.FormOrigin import com.github.androidpasswordstore.autofillparser.computeCertificatesHash -import com.github.michaelbull.result.onFailure +import com.github.michaelbull.result.getOrElse import com.github.michaelbull.result.runCatching import logcat.LogPriority.ERROR import logcat.logcat @@ -65,15 +64,15 @@ class AutofillPublisherChangedActivity : AppCompatActivity() { } } - private lateinit var appPackage: String - private val binding by viewBinding(ActivityOreoAutofillPublisherChangedBinding::inflate) - override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - setContentView(binding.root) setFinishOnTouchOutside(true) + // Tapjacking guard: a touch that reaches this window through another app's window (an overlay + // drawn to trick the user into re-enabling) is dropped. ViewGroup.dispatchTouchEvent checks the + // decor view's own flag before dispatching to any child, so the ComposeView is covered. + window.decorView.filterTouchesWhenObscured = true - appPackage = + val appPackage = intent.getStringExtra(EXTRA_APP_PACKAGE) ?: run { logcat(ERROR) { "AutofillPublisherChangedActivity started without EXTRA_PACKAGE_NAME" } @@ -81,63 +80,57 @@ class AutofillPublisherChangedActivity : AppCompatActivity() { return } supportActionBar?.hide() - showPackageInfo() - with(binding) { - okButton.setOnClickListener { finish() } - advancedButton.setOnClickListener { - advancedButton.visibility = View.GONE - warningAppAdvancedInfo.visibility = View.VISIBLE - resetButton.visibility = View.VISIBLE - } - resetButton.setOnClickListener { - AutofillMatcher.clearMatchesFor( - this@AutofillPublisherChangedActivity, - FormOrigin.App(appPackage), - ) - val fillResponse = - IntentCompat.getParcelableExtra( - intent, - EXTRA_FILL_RESPONSE_AFTER_RESET, - FillResponse::class.java, + val (packageInfo, appInfo, hash) = + runCatching { + Triple( + packageManager.getPackageInfoCompat(appPackage, PackageManager.GET_META_DATA), + packageManager.getApplicationInfoCompat(appPackage, PackageManager.GET_META_DATA), + computeCertificatesHash(this, appPackage), ) - setResult( - RESULT_OK, - Intent().apply { putExtra(AutofillManager.EXTRA_AUTHENTICATION_RESULT, fillResponse) }, + } + .getOrElse { e -> + logcat(ERROR) { e.asLog("Failed to retrieve package info for $appPackage") } + finish() + return + } + val appName = + getString( + R.string.oreo_autofill_warning_publisher_app_name, + packageManager.getApplicationLabel(appInfo), + ) + val installed = + getString( + R.string.oreo_autofill_warning_publisher_install_time, + DateUtils.getRelativeTimeSpanString(packageInfo.firstInstallTime), + ) + val details = + getString(R.string.oreo_autofill_warning_publisher_advanced_info_template, appPackage, hash) + setContent { + APSTheme { + PublisherChangedScreen( + appName = appName, + installed = installed, + details = details, + onKeepDisabled = { finish() }, + onReset = { reset(appPackage) }, ) - finish() } } } - private fun showPackageInfo() { - runCatching { - with(binding) { - val packageInfo = - packageManager.getPackageInfoCompat(appPackage, PackageManager.GET_META_DATA) - val installTime = DateUtils.getRelativeTimeSpanString(packageInfo.firstInstallTime) - warningAppInstallDate.text = - getString(R.string.oreo_autofill_warning_publisher_install_time, installTime) - val appInfo = - packageManager.getApplicationInfoCompat(appPackage, PackageManager.GET_META_DATA) - warningAppName.text = - getString( - R.string.oreo_autofill_warning_publisher_app_name, - packageManager.getApplicationLabel(appInfo), - ) - - val currentHash = - computeCertificatesHash(this@AutofillPublisherChangedActivity, appPackage) - warningAppAdvancedInfo.text = - getString( - R.string.oreo_autofill_warning_publisher_advanced_info_template, - appPackage, - currentHash, - ) - } - } - .onFailure { e -> - logcat(ERROR) { e.asLog("Failed to retrieve package info for $appPackage") } - finish() - } + /** Forgets the app's matches and fills with the response the service prepared for this case. */ + private fun reset(appPackage: String) { + AutofillMatcher.clearMatchesFor(this, FormOrigin.App(appPackage)) + val fillResponse = + IntentCompat.getParcelableExtra( + intent, + EXTRA_FILL_RESPONSE_AFTER_RESET, + FillResponse::class.java, + ) + setResult( + RESULT_OK, + Intent().apply { putExtra(AutofillManager.EXTRA_AUTHENTICATION_RESULT, fillResponse) }, + ) + finish() } } diff --git a/app/src/main/java/app/passwordstore/ui/autofill/PublisherChangedScreen.kt b/app/src/main/java/app/passwordstore/ui/autofill/PublisherChangedScreen.kt new file mode 100644 index 00000000..8c210b79 --- /dev/null +++ b/app/src/main/java/app/passwordstore/ui/autofill/PublisherChangedScreen.kt @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2026 Danilo M. <danix@danix.xyz> + * SPDX-License-Identifier: GPL-3.0-only + */ + +package app.passwordstore.ui.autofill + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.text.selection.SelectionContainer +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.Button +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedButton +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import app.passwordstore.R +import app.passwordstore.ui.compose.theme.SpacingLarge +import app.passwordstore.ui.compose.theme.SpacingLargest + +/** + * Warns that the app being filled ([appName], installed [installed]) is signed by another publisher + * than when an entry was first matched to it. [onKeepDisabled] fills nothing. [onReset] forgets the + * old matches and fills; it is offered only after "Advanced information", which shows [details] + * (package and certificate hash), so re-enabling always takes two deliberate taps. + */ +@Composable +fun PublisherChangedScreen( + appName: String, + installed: String, + details: String, + onKeepDisabled: () -> Unit, + onReset: () -> Unit, + modifier: Modifier = Modifier, +) { + var advanced by rememberSaveable { mutableStateOf(false) } + val center = Modifier.fillMaxWidth() + Surface(modifier) { + Column( + Modifier.verticalScroll(rememberScrollState()).padding(SpacingLargest), + verticalArrangement = Arrangement.spacedBy(SpacingLarge), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Icon( + painterResource(R.drawable.ic_warning_red_24dp), + contentDescription = + stringResource(R.string.oreo_autofill_warning_publisher_warning_sign_description), + modifier = Modifier.size(40.dp), + tint = MaterialTheme.colorScheme.error, + ) + // ponytail: stringResource drops the footer's <b> in every locale but Italian (whose footer + // has none); the red title carries the emphasis. Keep spans with getText if it is missed. + Text( + stringResource(R.string.oreo_autofill_warning_publisher_dataset_title), + modifier = center, + color = MaterialTheme.colorScheme.error, + textAlign = TextAlign.Center, + style = MaterialTheme.typography.titleLarge, + ) + Text( + stringResource(R.string.oreo_autofill_warning_publisher_header), + style = MaterialTheme.typography.bodyMedium, + ) + Text( + appName, + modifier = center, + textAlign = TextAlign.Center, + style = MaterialTheme.typography.titleMedium, + ) + Text( + installed, + modifier = center, + textAlign = TextAlign.Center, + style = MaterialTheme.typography.bodySmall, + ) + Text( + stringResource(R.string.oreo_autofill_warning_publisher_footer), + style = MaterialTheme.typography.bodyMedium, + ) + Button(onClick = onKeepDisabled, modifier = center) { + Text( + stringResource(R.string.oreo_autofill_warning_publisher_changed_disable_autofill_button) + ) + } + if (!advanced) { + OutlinedButton(onClick = { advanced = true }, modifier = center) { + Text(stringResource(R.string.oreo_autofill_warning_publisher_advanced_info_button)) + } + } else { + OutlinedButton(onClick = onReset, modifier = center) { + Text(stringResource(R.string.oreo_autofill_warning_publisher_reenable_button)) + } + SelectionContainer { + Text( + details, + modifier = center, + textAlign = TextAlign.Center, + style = MaterialTheme.typography.bodySmall.copy(fontFamily = FontFamily.Monospace), + ) + } + } + } + } +} diff --git a/app/src/main/res/layout/activity_oreo_autofill_publisher_changed.xml b/app/src/main/res/layout/activity_oreo_autofill_publisher_changed.xml deleted file mode 100644 index 89d64fe4..00000000 --- a/app/src/main/res/layout/activity_oreo_autofill_publisher_changed.xml +++ /dev/null @@ -1,174 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?><!-- - ~ Copyright © 2014-2024 The Android Password Store Authors. All Rights Reserved. - ~ SPDX-License-Identifier: GPL-3.0-only - --> - -<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:app="http://schemas.android.com/apk/res-auto" - xmlns:tools="http://schemas.android.com/tools" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:animateLayoutChanges="true" - android:elevation="2dp" - android:paddingBottom="16dp" - android:scrollbars="vertical" - tools:context="app.passwordstore.ui.autofill.AutofillPublisherChangedActivity"> - - <ImageView - android:id="@+id/cover" - android:layout_width="60dp" - android:layout_height="60dp" - android:background="@android:color/transparent" - android:contentDescription="@string/app_name" - android:src="@drawable/ic_launcher_foreground" - app:layout_constraintBottom_toTopOf="@id/warningSign" - app:layout_constraintEnd_toStartOf="@id/app_name" - app:layout_constraintHorizontal_chainStyle="packed" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toTopOf="parent" /> - - <TextView - android:id="@+id/app_name" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="@string/app_name" - android:textSize="18sp" - android:textStyle="bold" - app:layout_constraintBottom_toBottomOf="@id/cover" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toEndOf="@id/cover" - app:layout_constraintTop_toTopOf="@id/cover" /> - - <ImageView - android:id="@+id/warningSign" - android:layout_width="40dp" - android:layout_height="40dp" - android:layout_marginStart="@dimen/activity_horizontal_margin" - android:contentDescription="@string/oreo_autofill_warning_publisher_warning_sign_description" - android:src="@drawable/ic_warning_red_24dp" - app:layout_constraintBottom_toBottomOf="@id/warningHeader" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toTopOf="@id/warningHeader" /> - - <TextView - android:id="@+id/warningHeader" - android:layout_width="0dp" - android:layout_height="wrap_content" - android:layout_marginStart="8dp" - android:layout_marginTop="@dimen/activity_vertical_margin" - android:layout_marginEnd="@dimen/activity_horizontal_margin" - android:text="@string/oreo_autofill_warning_publisher_header" - android:textSize="12sp" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toEndOf="@id/warningSign" - app:layout_constraintTop_toBottomOf="@id/cover" /> - - <TextView - android:id="@+id/warningAppName" - android:layout_width="0dp" - android:layout_height="wrap_content" - android:layout_marginStart="@dimen/activity_horizontal_margin" - android:layout_marginTop="@dimen/activity_vertical_margin" - android:layout_marginEnd="@dimen/activity_horizontal_margin" - android:gravity="center_horizontal" - android:textSize="12sp" - app:layout_constraintBottom_toTopOf="@id/warningAppInstallDate" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@id/warningHeader" - tools:text="Example Banking" /> - - <TextView - android:id="@+id/warningAppInstallDate" - android:layout_width="0dp" - android:layout_height="wrap_content" - android:layout_marginStart="@dimen/activity_horizontal_margin" - android:layout_marginEnd="@dimen/activity_horizontal_margin" - android:gravity="center_horizontal" - android:textSize="12sp" - app:layout_constraintBottom_toTopOf="@id/warningAppFooter" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@id/warningAppName" - tools:text="Installed: 5 days ago" /> - - <TextView - android:id="@+id/warningAppFooter" - android:layout_width="0dp" - android:layout_height="wrap_content" - android:layout_marginStart="@dimen/activity_horizontal_margin" - android:layout_marginTop="@dimen/activity_vertical_margin" - android:layout_marginEnd="@dimen/activity_horizontal_margin" - android:text="@string/oreo_autofill_warning_publisher_footer" - android:textSize="12sp" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@id/warningAppInstallDate" /> - - <com.google.android.material.button.MaterialButton - android:id="@+id/okButton" - style="?attr/materialButtonOutlinedStyle" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginStart="@dimen/activity_horizontal_margin" - android:layout_marginTop="@dimen/activity_vertical_margin" - android:layout_marginEnd="@dimen/activity_horizontal_margin" - android:minWidth="240dp" - android:text="@string/oreo_autofill_warning_publisher_changed_disable_autofill_button" - android:textSize="12sp" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@id/warningAppFooter" /> - - <com.google.android.material.button.MaterialButton - android:id="@+id/advancedButton" - style="?attr/materialButtonOutlinedStyle" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginStart="@dimen/activity_horizontal_margin" - android:layout_marginEnd="@dimen/activity_horizontal_margin" - android:minWidth="240dp" - android:text="@string/oreo_autofill_warning_publisher_advanced_info_button" - android:textSize="12sp" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@id/okButton" - tools:visibility="visible" /> - - <com.google.android.material.button.MaterialButton - android:id="@+id/resetButton" - style="?attr/materialButtonOutlinedStyle" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginStart="@dimen/activity_horizontal_margin" - android:layout_marginEnd="@dimen/activity_horizontal_margin" - android:minWidth="240dp" - android:text="@string/oreo_autofill_warning_publisher_reenable_button" - android:textSize="12sp" - android:visibility="gone" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@id/advancedButton" - tools:visibility="visible" /> - - <TextView - android:id="@+id/warningAppAdvancedInfo" - android:layout_width="0dp" - android:layout_height="wrap_content" - android:layout_marginStart="@dimen/activity_horizontal_margin" - android:layout_marginTop="16dp" - android:layout_marginEnd="@dimen/activity_horizontal_margin" - android:fontFamily="@font/jetbrainsmono_nl_regular" - android:gravity="center_horizontal" - android:textIsSelectable="true" - android:textSize="10sp" - android:visibility="gone" - app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@id/resetButton" - tools:ignore="SmallSp" - tools:text="Package: com.example.banking\n\nHash:\n8P1sW0EPJcslw7UzRsiXL64w+O50Ed+RBICtay1g24M=" - tools:visibility="visible" /> - -</androidx.constraintlayout.widget.ConstraintLayout> diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index 44d4f581..de2ac854 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -6,5 +6,4 @@ <resources> <!-- Default screen margins, per the Android Design guidelines. --> <dimen name="activity_horizontal_margin">16dp</dimen> - <dimen name="activity_vertical_margin">16dp</dimen> </resources> |
