aboutsummaryrefslogtreecommitdiffstats
path: root/app/src/main/java
diff options
context:
space:
mode:
Diffstat (limited to 'app/src/main/java')
-rw-r--r--app/src/main/java/app/passwordstore/ui/autofill/AutofillPublisherChangedActivity.kt115
-rw-r--r--app/src/main/java/app/passwordstore/ui/autofill/PublisherChangedScreen.kt121
2 files changed, 175 insertions, 61 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),
+ )
+ }
+ }
+ }
+ }
+}