tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit 24b753daa968aa6898a4ca47385eaa965b91e09b
parent c92639c1c2870b857efee15502ec2e6c9e5571ed
Author: clairehurst <clairehurst@torproject.org>
Date:   Thu,  4 Sep 2025 15:20:20 -0600

TB 44027 [android]: Update PBM lockscreen

tor-browser#44027 Update PBM lockscreen

Diffstat:
Mmobile/android/android-components/components/compose/base/src/main/java/mozilla/components/compose/base/annotation/FlexibleWindowLightDarkPreview.kt | 4++++
Amobile/android/fenix/app/src/beta/res/drawable/tor_browser_app_icon.png | 0
Mmobile/android/fenix/app/src/main/java/org/mozilla/fenix/pbmlock/PrivateBrowsingLockFeature.kt | 3++-
Mmobile/android/fenix/app/src/main/java/org/mozilla/fenix/pbmlock/UnlockPrivateTabsFragment.kt | 8++++----
Mmobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/TabsSettingsFragment.kt | 96+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mmobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/biometric/BiometricUtils.kt | 6++++--
Mmobile/android/fenix/app/src/main/java/org/mozilla/fenix/tabstray/TabsTrayBanner.kt | 2+-
Mmobile/android/fenix/app/src/main/java/org/mozilla/fenix/tabstray/TabsTrayFragment.kt | 5+++--
Mmobile/android/fenix/app/src/main/java/org/mozilla/fenix/utils/Settings.kt | 2+-
Amobile/android/fenix/app/src/main/res/drawable/tor_browser_app_icon.png | 0
Mmobile/android/fenix/app/src/main/res/xml/tabs_preferences.xml | 10++++++++++
Amobile/android/fenix/app/src/nightly/res/drawable/tor_browser_app_icon.png | 0
Amobile/android/fenix/app/src/release/res/drawable/tor_browser_app_icon.png | 0
13 files changed, 125 insertions(+), 11 deletions(-)

diff --git a/mobile/android/android-components/components/compose/base/src/main/java/mozilla/components/compose/base/annotation/FlexibleWindowLightDarkPreview.kt b/mobile/android/android-components/components/compose/base/src/main/java/mozilla/components/compose/base/annotation/FlexibleWindowLightDarkPreview.kt @@ -61,4 +61,8 @@ import mozilla.components.compose.base.theme.layout.AcornWindowSize device = Devices.PIXEL_TABLET, uiMode = Configuration.UI_MODE_NIGHT_YES, ) +@Preview( + name = "Large text", + fontScale = 2.5f, +) annotation class FlexibleWindowLightDarkPreview diff --git a/mobile/android/fenix/app/src/beta/res/drawable/tor_browser_app_icon.png b/mobile/android/fenix/app/src/beta/res/drawable/tor_browser_app_icon.png Binary files differ. diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/pbmlock/PrivateBrowsingLockFeature.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/pbmlock/PrivateBrowsingLockFeature.kt @@ -332,7 +332,8 @@ fun Fragment.verifyUser( onVerified: (() -> Unit)? = null, ) { biometricUtils.bindBiometricsCredentialsPromptOrShowWarning( - titleRes = R.string.pbm_authentication_unlock_private_tabs, + titleRes = R.string.tor_authentication_unlock_private_tabs, + titleRes2 = R.string.app_name, view = requireView(), onShowPinVerification = { intent -> fallbackVerification.launch(intent) }, onAuthSuccess = { handleVerificationSuccess(requireContext(), onVerified) }, diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/pbmlock/UnlockPrivateTabsFragment.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/pbmlock/UnlockPrivateTabsFragment.kt @@ -71,8 +71,7 @@ class UnlockPrivateTabsFragment : Fragment(), UserInteractionHandler { UnlockPrivateTabsScreen( onUnlockClicked = { requestPrompt() }, onLeaveClicked = { - PrivateBrowsingLocked.seeOtherTabsClicked.record() - closeFragment() + requireActivity().moveTaskToBack(true) }, showNegativeButton = !isCustomPrivateTab, ) @@ -89,13 +88,14 @@ class UnlockPrivateTabsFragment : Fragment(), UserInteractionHandler { } override fun onBackPressed(): Boolean { - closeFragment() + requireActivity().moveTaskToBack(true) return true } private fun requestPrompt() { DefaultBiometricUtils.bindBiometricsCredentialsPromptOrShowWarning( - titleRes = R.string.pbm_authentication_unlock_private_tabs, + titleRes = R.string.tor_authentication_unlock_private_tabs, + titleRes2 = R.string.app_name, view = requireView(), onShowPinVerification = { intent -> startForResult.launch(intent) }, onAuthSuccess = ::onAuthSuccess, diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/TabsSettingsFragment.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/TabsSettingsFragment.kt @@ -18,6 +18,16 @@ import org.mozilla.fenix.ext.settings import org.mozilla.fenix.ext.showToolbar import org.mozilla.fenix.utils.view.addToRadioGroup +import android.content.Intent +import android.provider.Settings +import androidx.activity.result.ActivityResultLauncher +import androidx.biometric.BiometricManager +import androidx.preference.Preference +import org.mozilla.fenix.ext.registerForActivityResult +import org.mozilla.fenix.settings.biometric.DefaultBiometricUtils +import org.mozilla.fenix.settings.biometric.ext.isAuthenticatorAvailable +import org.mozilla.fenix.settings.biometric.ext.isHardwareAvailable + /** * Lets the user customize auto closing tabs. */ @@ -42,6 +52,31 @@ class TabsSettingsFragment : PreferenceFragmentCompat() { findPreference<PreferenceCategory>(getString(R.string.pref_key_inactive_tabs_category))?.apply { isVisible = !context.settings().shouldDisableNormalMode } + + startForResult = registerForActivityResult( + onFailure = { }, + onSuccess = { onSuccessfulAuthenticationUsingFallbackPrompt() }, + ) + } + + private lateinit var startForResult: ActivityResultLauncher<Intent> + + private fun onSuccessfulAuthenticationUsingFallbackPrompt() { + val newValue = !requireContext().settings().privateBrowsingLockedFeatureEnabled + requireContext().settings().privateBrowsingLockedFeatureEnabled = newValue + // Update switch state manually + requirePreference<SwitchPreference>(R.string.pref_key_private_browsing_locked_enabled).apply { + isChecked = !isChecked + } + } + + private fun onSuccessfulAuthenticationUsingPrimaryPrompt( + pbmLockEnabled: Boolean, + preference: Preference, + ) { + requireContext().settings().privateBrowsingLockedFeatureEnabled = pbmLockEnabled + // Update switch state manually + (preference as? SwitchPreference)?.isChecked = pbmLockEnabled } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { @@ -91,6 +126,67 @@ class TabsSettingsFragment : PreferenceFragmentCompat() { radioOneMonth.onClickListener(::enableInactiveTabsSetting) setupRadioGroups() + /** + * Changes in this file for "tor-browser#44027 Update PBM lockscreen" were copied from + * [PrivateBrowsingFragment] and changed to make sense and work for TBA such as removing + * any use of nimbus/glean that was being used for business logic which was making the + * release build variant not work. We should check [PrivateBrowsingFragment] for updates + * when we rebase + * */ + setUpHideBrowsingSessionPreference() + } + + private fun setUpHideBrowsingSessionPreference() { + val biometricManager = BiometricManager.from(requireContext()) + val deviceCapable = biometricManager.isHardwareAvailable() + val userHasEnabledCapability = biometricManager.isAuthenticatorAvailable() + + requirePreference<SwitchPreference>(R.string.pref_key_private_browsing_locked_enabled).apply { + title = getString(R.string.preferences_tor_lock_screen_title, getString(R.string.app_name)) + summary = getString(R.string.preferences_tor_lock_screen_summary, getString(R.string.app_name)) + isChecked = context.settings().privateBrowsingLockedFeatureEnabled && + biometricManager.isAuthenticatorAvailable() + isVisible = deviceCapable + isEnabled = userHasEnabledCapability + + setOnPreferenceChangeListener { preference, newValue -> + val pbmLockEnabled = newValue as? Boolean + ?: return@setOnPreferenceChangeListener false + + val titleRes = if (pbmLockEnabled) { + R.string.tor_authentication_enable_lock + } else { + R.string.tor_authentication_disable_lock + } + + DefaultBiometricUtils.bindBiometricsCredentialsPromptOrShowWarning( + titleRes = titleRes, + titleRes2 = R.string.app_name, + view = requireView(), + onShowPinVerification = { intent -> startForResult.launch(intent) }, + onAuthSuccess = { + onSuccessfulAuthenticationUsingPrimaryPrompt( + pbmLockEnabled = pbmLockEnabled, + preference = preference, + ) + }, + onAuthFailure = { }, + ) + + // Cancel toggle change until biometric is successful + false + } + } + + requirePreference<Preference>(R.string.pref_key_private_browsing_lock_device_feature_enabled).apply { + title = getString(R.string.tor_authentication_lock_device_feature_disabled, getString(R.string.app_name)) + isVisible = deviceCapable && !userHasEnabledCapability + + setOnPreferenceClickListener { + context.startActivity(Intent(Settings.ACTION_SECURITY_SETTINGS)) + true + } + } } private fun setupRadioGroups() { diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/biometric/BiometricUtils.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/biometric/BiometricUtils.kt @@ -42,6 +42,7 @@ interface BiometricUtils { */ fun bindBiometricsCredentialsPromptOrShowWarning( @StringRes titleRes: Int = R.string.logins_biometric_prompt_message_2, + @StringRes titleRes2: Int = R.string.empty_string, view: View, onShowPinVerification: (Intent) -> Unit, onAuthSuccess: () -> Unit, @@ -56,6 +57,7 @@ object DefaultBiometricUtils : BiometricUtils { @Suppress("Deprecation") override fun bindBiometricsCredentialsPromptOrShowWarning( @StringRes titleRes: Int, + @StringRes titleRes2: Int, view: View, onShowPinVerification: (Intent) -> Unit, onAuthSuccess: () -> Unit, @@ -90,7 +92,7 @@ object DefaultBiometricUtils : BiometricUtils { // Use the BiometricPrompt first if (BiometricPromptFeature.canUseFeature(BiometricManager.from(context))) { biometricPromptFeature.get() - ?.requestAuthentication(context.resources.getString(titleRes)) + ?.requestAuthentication(context.resources.getString(titleRes, context.resources.getString(titleRes2))) return } @@ -99,7 +101,7 @@ object DefaultBiometricUtils : BiometricUtils { if (manager?.isKeyguardSecure == true) { val confirmDeviceCredentialIntent = manager.createConfirmDeviceCredentialIntent( context.resources.getString(R.string.logins_biometric_prompt_message_pin), - context.resources.getString(titleRes), + context.resources.getString(titleRes, context.resources.getString(titleRes2)), ) onShowPinVerification(confirmDeviceCredentialIntent) } else { diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tabstray/TabsTrayBanner.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tabstray/TabsTrayBanner.kt @@ -221,7 +221,7 @@ fun TabsTrayBanner( // After this bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1965545 // is resolved, we should swap the button 1 and button 2 click actions. Banner( - message = stringResource(id = R.string.private_tab_cfr_title), + message = stringResource(id = R.string.tor_tab_cfr_title, stringResource(R.string.app_name)), button1Text = stringResource(id = R.string.private_tab_cfr_negative), button2Text = stringResource(id = R.string.private_tab_cfr_positive), onButton1Click = { diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tabstray/TabsTrayFragment.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tabstray/TabsTrayFragment.kt @@ -795,10 +795,11 @@ class TabsTrayFragment : AppCompatDialogFragment() { BiometricManager.from(requireContext()).isAuthenticatorAvailable() if (!isAuthenticatorAvailable) { navControllerProvider.getNavController(this) - .navigate(TabsTrayFragmentDirections.actionGlobalPrivateBrowsingFragment()) + .navigate(TabsTrayFragmentDirections.actionGlobalTabSettingsFragment()) } else { DefaultBiometricUtils.bindBiometricsCredentialsPromptOrShowWarning( - titleRes = R.string.pbm_authentication_enable_lock, + titleRes = R.string.tor_authentication_enable_lock, + titleRes2 = R.string.app_name, view = requireView(), onShowPinVerification = { intent -> enablePbmPinLauncher.launch(intent) }, onAuthSuccess = { diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/utils/Settings.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/utils/Settings.kt @@ -509,7 +509,7 @@ class Settings( var privateBrowsingLockedFeatureEnabled by booleanPreference( key = appContext.getPreferenceKey(R.string.pref_key_private_browsing_locked_enabled), - default = { FxNimbus.features.privateBrowsingLock.value().enabled }, + default = { false }, ) var privateBrowsingModeLocked by booleanPreference( diff --git a/mobile/android/fenix/app/src/main/res/drawable/tor_browser_app_icon.png b/mobile/android/fenix/app/src/main/res/drawable/tor_browser_app_icon.png Binary files differ. diff --git a/mobile/android/fenix/app/src/main/res/xml/tabs_preferences.xml b/mobile/android/fenix/app/src/main/res/xml/tabs_preferences.xml @@ -57,4 +57,14 @@ android:key="@string/pref_key_inactive_tabs" android:title="@string/preferences_inactive_tabs_title"/> </androidx.preference.PreferenceCategory> + + <PreferenceCategory + android:key="@string/pref_key_pbm_lock_category_divider" + android:layout="@xml/preference_category_divider" + android:selectable="false" /> + <SwitchPreference + android:defaultValue="false" + android:key="pref_key_private_browsing_locked_enabled" /> + <Preference + android:key="@string/pref_key_private_browsing_lock_device_feature_enabled" /> </androidx.preference.PreferenceScreen> diff --git a/mobile/android/fenix/app/src/nightly/res/drawable/tor_browser_app_icon.png b/mobile/android/fenix/app/src/nightly/res/drawable/tor_browser_app_icon.png Binary files differ. diff --git a/mobile/android/fenix/app/src/release/res/drawable/tor_browser_app_icon.png b/mobile/android/fenix/app/src/release/res/drawable/tor_browser_app_icon.png Binary files differ.