clairehurst pushed to branch tor-browser-140.3.0esr-15.0-1 at The Tor Project / Applications / Tor Browser
Commits:
-
9538f500
by clairehurst at 2025-09-24T19:03:40-06:00
14 changed files:
- + mobile/android/fenix/app/src/beta/res/drawable/tor_browser_app_icon.png
- mobile/android/fenix/app/src/main/java/org/mozilla/fenix/biometricauthentication/UnlockPrivateTabsFragment.kt
- mobile/android/fenix/app/src/main/java/org/mozilla/fenix/biometricauthentication/UnlockPrivateTabsScreen.kt
- mobile/android/fenix/app/src/main/java/org/mozilla/fenix/lifecycle/PrivateBrowsingLockFeature.kt
- mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/TabsSettingsFragment.kt
- mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/biometric/BiometricUtils.kt
- mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tabstray/TabsTrayBanner.kt
- mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tabstray/TabsTrayFragment.kt
- mobile/android/fenix/app/src/main/java/org/mozilla/fenix/utils/Settings.kt
- + mobile/android/fenix/app/src/main/res/drawable/tor_browser_app_icon.png
- mobile/android/fenix/app/src/main/res/values/torbrowser_strings.xml
- mobile/android/fenix/app/src/main/res/xml/tabs_preferences.xml
- + mobile/android/fenix/app/src/nightly/res/drawable/tor_browser_app_icon.png
- + mobile/android/fenix/app/src/release/res/drawable/tor_browser_app_icon.png
Changes:
... | ... | @@ -56,8 +56,7 @@ class UnlockPrivateTabsFragment : Fragment(), UserInteractionHandler { |
56 | 56 | UnlockPrivateTabsScreen(
|
57 | 57 | onUnlockClicked = { requestPrompt() },
|
58 | 58 | onLeaveClicked = {
|
59 | - PrivateBrowsingLocked.seeOtherTabsClicked.record()
|
|
60 | - closeFragment()
|
|
59 | + requireActivity().moveTaskToBack(true)
|
|
61 | 60 | },
|
62 | 61 | )
|
63 | 62 | |
... | ... | @@ -67,13 +66,14 @@ class UnlockPrivateTabsFragment : Fragment(), UserInteractionHandler { |
67 | 66 | }
|
68 | 67 | |
69 | 68 | override fun onBackPressed(): Boolean {
|
70 | - closeFragment()
|
|
69 | + requireActivity().moveTaskToBack(true)
|
|
71 | 70 | return true
|
72 | 71 | }
|
73 | 72 | |
74 | 73 | private fun requestPrompt() {
|
75 | 74 | DefaultBiometricUtils.bindBiometricsCredentialsPromptOrShowWarning(
|
76 | - titleRes = R.string.pbm_authentication_unlock_private_tabs,
|
|
75 | + titleRes = R.string.tor_authentication_unlock_private_tabs,
|
|
76 | + titleRes2 = R.string.app_name,
|
|
77 | 77 | view = requireView(),
|
78 | 78 | onShowPinVerification = { intent -> startForResult.launch(intent) },
|
79 | 79 | onAuthSuccess = ::onAuthSuccess,
|
... | ... | @@ -31,6 +31,14 @@ import org.mozilla.fenix.ext.isLargeWindow |
31 | 31 | import org.mozilla.fenix.theme.FirefoxTheme
|
32 | 32 | import org.mozilla.fenix.theme.Theme
|
33 | 33 | |
34 | +import androidx.compose.foundation.layout.Row
|
|
35 | +import androidx.compose.foundation.layout.size
|
|
36 | +import androidx.compose.ui.text.AnnotatedString
|
|
37 | +import androidx.compose.ui.text.SpanStyle
|
|
38 | +import androidx.compose.ui.text.font.FontWeight
|
|
39 | +import androidx.compose.ui.unit.sp
|
|
40 | +import org.mozilla.fenix.compose.parseHtml
|
|
41 | + |
|
34 | 42 | private const val FILL_WIDTH_LARGE_WINDOW = 0.5f
|
35 | 43 | private const val FILL_WIDTH_DEFAULT = 1.0f
|
36 | 44 | |
... | ... | @@ -72,16 +80,30 @@ private fun Header() { |
72 | 80 | modifier = Modifier.padding(horizontal = 16.dp),
|
73 | 81 | horizontalAlignment = Alignment.CenterHorizontally,
|
74 | 82 | ) {
|
75 | - Image(
|
|
76 | - painter = painterResource(id = R.drawable.ic_pbm_firefox_logo),
|
|
77 | - contentDescription = null, // decorative only.
|
|
78 | - modifier = Modifier.padding(32.dp),
|
|
79 | - )
|
|
83 | + Row (
|
|
84 | + verticalAlignment = Alignment.CenterVertically
|
|
85 | + ) {
|
|
86 | + Image(
|
|
87 | + painter = painterResource(id = R.drawable.tor_browser_app_icon),
|
|
88 | + contentDescription = null, // decorative only.
|
|
89 | + Modifier
|
|
90 | + .size(62.dp)
|
|
91 | + .padding(end = 10.dp),
|
|
92 | + )
|
|
93 | + Text(
|
|
94 | + text = stringResource(R.string.app_name),
|
|
95 | + color = FirefoxTheme.colors.textPrimary,
|
|
96 | + fontWeight = FontWeight.Bold,
|
|
97 | + fontSize = 29.sp,
|
|
98 | + letterSpacing = 0.18.sp,
|
|
99 | + lineHeight = 52.sp,
|
|
100 | + )
|
|
101 | + }
|
|
80 | 102 | |
81 | 103 | Spacer(modifier = Modifier.height(24.dp))
|
82 | 104 | |
83 | 105 | Text(
|
84 | - text = stringResource(id = R.string.pbm_authentication_unlock_private_tabs),
|
|
106 | + text = stringResource(id = R.string.tor_authentication_unlock_private_tabs, stringResource(R.string.app_name)),
|
|
85 | 107 | color = FirefoxTheme.colors.textPrimary,
|
86 | 108 | textAlign = TextAlign.Center,
|
87 | 109 | style = FirefoxTheme.typography.headline6,
|
... | ... | @@ -109,15 +131,6 @@ private fun Footer(onUnlockClicked: () -> Unit, onLeaveClicked: () -> Unit) { |
109 | 131 | modifier = Modifier.fillMaxWidth(),
|
110 | 132 | onClick = onUnlockClicked,
|
111 | 133 | )
|
112 | - |
|
113 | - Spacer(modifier = Modifier.height(8.dp))
|
|
114 | - |
|
115 | - TextButton(
|
|
116 | - text = stringResource(R.string.pbm_authentication_leave_private_tabs),
|
|
117 | - onClick = onLeaveClicked,
|
|
118 | - textColor = FirefoxTheme.colors.textActionPrimary,
|
|
119 | - upperCaseText = false,
|
|
120 | - )
|
|
121 | 134 | }
|
122 | 135 | }
|
123 | 136 |
... | ... | @@ -312,7 +312,8 @@ fun Fragment.verifyUser( |
312 | 312 | onVerified: (() -> Unit)? = null,
|
313 | 313 | ) {
|
314 | 314 | biometricUtils.bindBiometricsCredentialsPromptOrShowWarning(
|
315 | - titleRes = R.string.pbm_authentication_unlock_private_tabs,
|
|
315 | + titleRes = R.string.tor_authentication_unlock_private_tabs,
|
|
316 | + titleRes2 = R.string.app_name,
|
|
316 | 317 | view = requireView(),
|
317 | 318 | onShowPinVerification = { intent -> fallbackVerification.launch(intent) },
|
318 | 319 | onAuthSuccess = { handleVerificationSuccess(requireContext(), onVerified) },
|
... | ... | @@ -17,6 +17,16 @@ import org.mozilla.fenix.ext.settings |
17 | 17 | import org.mozilla.fenix.ext.showToolbar
|
18 | 18 | import org.mozilla.fenix.utils.view.addToRadioGroup
|
19 | 19 | |
20 | +import android.content.Intent
|
|
21 | +import android.provider.Settings
|
|
22 | +import androidx.activity.result.ActivityResultLauncher
|
|
23 | +import androidx.biometric.BiometricManager
|
|
24 | +import androidx.preference.Preference
|
|
25 | +import org.mozilla.fenix.ext.registerForActivityResult
|
|
26 | +import org.mozilla.fenix.settings.biometric.DefaultBiometricUtils
|
|
27 | +import org.mozilla.fenix.settings.biometric.ext.isAuthenticatorAvailable
|
|
28 | +import org.mozilla.fenix.settings.biometric.ext.isHardwareAvailable
|
|
29 | + |
|
20 | 30 | /**
|
21 | 31 | * Lets the user customize auto closing tabs.
|
22 | 32 | */
|
... | ... | @@ -40,6 +50,31 @@ class TabsSettingsFragment : PreferenceFragmentCompat() { |
40 | 50 | findPreference<PreferenceCategory>(getString(R.string.pref_key_inactive_tabs_category))?.apply {
|
41 | 51 | isVisible = !context.settings().shouldDisableNormalMode
|
42 | 52 | }
|
53 | + |
|
54 | + startForResult = registerForActivityResult(
|
|
55 | + onFailure = { },
|
|
56 | + onSuccess = { onSuccessfulAuthenticationUsingFallbackPrompt() },
|
|
57 | + )
|
|
58 | + }
|
|
59 | + |
|
60 | + private lateinit var startForResult: ActivityResultLauncher<Intent>
|
|
61 | + |
|
62 | + private fun onSuccessfulAuthenticationUsingFallbackPrompt() {
|
|
63 | + val newValue = !requireContext().settings().privateBrowsingLockedEnabled
|
|
64 | + requireContext().settings().privateBrowsingLockedEnabled = newValue
|
|
65 | + // Update switch state manually
|
|
66 | + requirePreference<SwitchPreference>(R.string.pref_key_private_browsing_locked_enabled).apply {
|
|
67 | + isChecked = !isChecked
|
|
68 | + }
|
|
69 | + }
|
|
70 | + |
|
71 | + private fun onSuccessfulAuthenticationUsingPrimaryPrompt(
|
|
72 | + pbmLockEnabled: Boolean,
|
|
73 | + preference: Preference,
|
|
74 | + ) {
|
|
75 | + requireContext().settings().privateBrowsingLockedEnabled = pbmLockEnabled
|
|
76 | + // Update switch state manually
|
|
77 | + (preference as? SwitchPreference)?.isChecked = pbmLockEnabled
|
|
43 | 78 | }
|
44 | 79 | |
45 | 80 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
... | ... | @@ -86,6 +121,67 @@ class TabsSettingsFragment : PreferenceFragmentCompat() { |
86 | 121 | radioOneMonth.onClickListener(::enableInactiveTabsSetting)
|
87 | 122 | |
88 | 123 | setupRadioGroups()
|
124 | + /**
|
|
125 | + * Changes in this file for "tor-browser#44027 Update PBM lockscreen" were copied from
|
|
126 | + * [PrivateBrowsingFragment] and changed to make sense and work for TBA such as removing
|
|
127 | + * any use of nimbus/glean that was being used for business logic which was making the
|
|
128 | + * release build variant not work. We should check [PrivateBrowsingFragment] for updates
|
|
129 | + * when we rebase
|
|
130 | + * */
|
|
131 | + setUpHideBrowsingSessionPreference()
|
|
132 | + }
|
|
133 | + |
|
134 | + private fun setUpHideBrowsingSessionPreference() {
|
|
135 | + val biometricManager = BiometricManager.from(requireContext())
|
|
136 | + val deviceCapable = biometricManager.isHardwareAvailable()
|
|
137 | + val userHasEnabledCapability = biometricManager.isAuthenticatorAvailable()
|
|
138 | + |
|
139 | + requirePreference<SwitchPreference>(R.string.pref_key_private_browsing_locked_enabled).apply {
|
|
140 | + title = getString(R.string.preferences_tor_lock_screen_title, getString(R.string.app_name))
|
|
141 | + summary = getString(R.string.preferences_tor_lock_screen_summary, getString(R.string.app_name))
|
|
142 | + isChecked = context.settings().privateBrowsingLockedEnabled &&
|
|
143 | + biometricManager.isAuthenticatorAvailable()
|
|
144 | + isVisible = deviceCapable
|
|
145 | + isEnabled = userHasEnabledCapability
|
|
146 | + |
|
147 | + setOnPreferenceChangeListener { preference, newValue ->
|
|
148 | + val pbmLockEnabled = newValue as? Boolean
|
|
149 | + ?: return@setOnPreferenceChangeListener false
|
|
150 | + |
|
151 | + val titleRes = if (pbmLockEnabled) {
|
|
152 | + R.string.tor_authentication_enable_lock
|
|
153 | + } else {
|
|
154 | + R.string.tor_authentication_disable_lock
|
|
155 | + }
|
|
156 | + |
|
157 | + DefaultBiometricUtils.bindBiometricsCredentialsPromptOrShowWarning(
|
|
158 | + titleRes = titleRes,
|
|
159 | + titleRes2 = R.string.app_name,
|
|
160 | + view = requireView(),
|
|
161 | + onShowPinVerification = { intent -> startForResult.launch(intent) },
|
|
162 | + onAuthSuccess = {
|
|
163 | + onSuccessfulAuthenticationUsingPrimaryPrompt(
|
|
164 | + pbmLockEnabled = pbmLockEnabled,
|
|
165 | + preference = preference,
|
|
166 | + )
|
|
167 | + },
|
|
168 | + onAuthFailure = { },
|
|
169 | + )
|
|
170 | + |
|
171 | + // Cancel toggle change until biometric is successful
|
|
172 | + false
|
|
173 | + }
|
|
174 | + }
|
|
175 | + |
|
176 | + requirePreference<Preference>(R.string.pref_key_private_browsing_lock_device_feature_enabled).apply {
|
|
177 | + title = getString(R.string.tor_authentication_lock_device_feature_disabled, getString(R.string.app_name))
|
|
178 | + isVisible = deviceCapable && !userHasEnabledCapability
|
|
179 | + |
|
180 | + setOnPreferenceClickListener {
|
|
181 | + context.startActivity(Intent(Settings.ACTION_SECURITY_SETTINGS))
|
|
182 | + true
|
|
183 | + }
|
|
184 | + }
|
|
89 | 185 | }
|
90 | 186 | |
91 | 187 | private fun setupRadioGroups() {
|
... | ... | @@ -42,6 +42,7 @@ interface BiometricUtils { |
42 | 42 | */
|
43 | 43 | fun bindBiometricsCredentialsPromptOrShowWarning(
|
44 | 44 | @StringRes titleRes: Int = R.string.logins_biometric_prompt_message_2,
|
45 | + @StringRes titleRes2: Int = R.string.empty_string,
|
|
45 | 46 | view: View,
|
46 | 47 | onShowPinVerification: (Intent) -> Unit,
|
47 | 48 | onAuthSuccess: () -> Unit,
|
... | ... | @@ -56,6 +57,7 @@ object DefaultBiometricUtils : BiometricUtils { |
56 | 57 | @Suppress("Deprecation")
|
57 | 58 | override fun bindBiometricsCredentialsPromptOrShowWarning(
|
58 | 59 | @StringRes titleRes: Int,
|
60 | + @StringRes titleRes2: Int,
|
|
59 | 61 | view: View,
|
60 | 62 | onShowPinVerification: (Intent) -> Unit,
|
61 | 63 | onAuthSuccess: () -> Unit,
|
... | ... | @@ -90,7 +92,7 @@ object DefaultBiometricUtils : BiometricUtils { |
90 | 92 | // Use the BiometricPrompt first
|
91 | 93 | if (BiometricPromptFeature.canUseFeature(BiometricManager.from(context))) {
|
92 | 94 | biometricPromptFeature.get()
|
93 | - ?.requestAuthentication(context.resources.getString(titleRes))
|
|
95 | + ?.requestAuthentication(context.resources.getString(titleRes, context.resources.getString(titleRes2)))
|
|
94 | 96 | return
|
95 | 97 | }
|
96 | 98 | |
... | ... | @@ -99,7 +101,7 @@ object DefaultBiometricUtils : BiometricUtils { |
99 | 101 | if (manager?.isKeyguardSecure == true) {
|
100 | 102 | val confirmDeviceCredentialIntent = manager.createConfirmDeviceCredentialIntent(
|
101 | 103 | context.resources.getString(R.string.logins_biometric_prompt_message_pin),
|
102 | - context.resources.getString(titleRes),
|
|
104 | + context.resources.getString(titleRes, context.resources.getString(titleRes2)),
|
|
103 | 105 | )
|
104 | 106 | onShowPinVerification(confirmDeviceCredentialIntent)
|
105 | 107 | } else {
|
... | ... | @@ -205,7 +205,7 @@ fun TabsTrayBanner( |
205 | 205 | // After this bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1965545
|
206 | 206 | // is resolved, we should swap the button 1 and button 2 click actions.
|
207 | 207 | Banner(
|
208 | - message = stringResource(id = R.string.private_tab_cfr_title),
|
|
208 | + message = stringResource(id = R.string.tor_tab_cfr_title, stringResource(R.string.app_name)),
|
|
209 | 209 | button1Text = stringResource(id = R.string.private_tab_cfr_negative),
|
210 | 210 | button2Text = stringResource(id = R.string.private_tab_cfr_positive),
|
211 | 211 | onButton1Click = {
|
... | ... | @@ -265,7 +265,7 @@ class TabsTrayFragment : AppCompatDialogFragment() { |
265 | 265 | shouldShowTabAutoCloseBanner = requireContext().settings().shouldShowAutoCloseTabsBanner &&
|
266 | 266 | requireContext().settings().canShowCfr,
|
267 | 267 | shouldShowLockPbmBanner =
|
268 | - if (FxNimbus.features.privateBrowsingLock.value().enabled) {
|
|
268 | + if (true) {
|
|
269 | 269 | shouldShowLockPbmBanner(
|
270 | 270 | isPrivateMode = (activity as HomeActivity).browsingModeManager.mode.isPrivate,
|
271 | 271 | hasPrivateTabs = requireComponents.core.store.state.privateTabs.isNotEmpty(),
|
... | ... | @@ -775,10 +775,11 @@ class TabsTrayFragment : AppCompatDialogFragment() { |
775 | 775 | val isAuthenticatorAvailable =
|
776 | 776 | BiometricManager.from(requireContext()).isAuthenticatorAvailable()
|
777 | 777 | if (!isAuthenticatorAvailable) {
|
778 | - findNavController().navigate(TabsTrayFragmentDirections.actionGlobalPrivateBrowsingFragment())
|
|
778 | + findNavController().navigate(TabsTrayFragmentDirections.actionGlobalTabSettingsFragment())
|
|
779 | 779 | } else {
|
780 | 780 | DefaultBiometricUtils.bindBiometricsCredentialsPromptOrShowWarning(
|
781 | - titleRes = R.string.pbm_authentication_enable_lock,
|
|
781 | + titleRes = R.string.tor_authentication_enable_lock,
|
|
782 | + titleRes2 = R.string.app_name,
|
|
782 | 783 | view = requireView(),
|
783 | 784 | onShowPinVerification = { intent -> enablePbmPinLauncher.launch(intent) },
|
784 | 785 | onAuthSuccess = {
|
... | ... | @@ -334,10 +334,9 @@ class Settings(private val appContext: Context) : PreferencesHolder { |
334 | 334 | default = false,
|
335 | 335 | )
|
336 | 336 | |
337 | - var privateBrowsingLockedEnabled by lazyFeatureFlagPreference(
|
|
337 | + var privateBrowsingLockedEnabled by booleanPreference(
|
|
338 | 338 | appContext.getPreferenceKey(R.string.pref_key_private_browsing_locked_enabled),
|
339 | - featureFlag = FxNimbus.features.privateBrowsingLock.value().enabled,
|
|
340 | - default = { false },
|
|
339 | + default = false,
|
|
341 | 340 | )
|
342 | 341 | |
343 | 342 | var shouldReturnToBrowser by booleanPreference(
|
... | ... | @@ -142,4 +142,21 @@ |
142 | 142 | <!-- Notification body for closing browser tabs. "%s" will be replaced with the localised application name, such as "Tor Browser". -->
|
143 | 143 | <string name="notification_close_tor_browser_tabs_long">Tap or swipe this notification to close %s’s tabs</string>
|
144 | 144 | |
145 | + <!-- Hide Tor Browser tabs authentication screen -->
|
|
146 | + <!-- Label for the primary action button to unlock private tabs. "%s" will be replaced with the localised application name, such as "Tor Browser" -->
|
|
147 | + <string name="tor_authentication_unlock_private_tabs">Unlock %s tabs</string>
|
|
148 | + <!-- Authentication prompt title to enable the private tabs mode. "%s" will be replaced with the localised application name, such as "Tor Browser" -->
|
|
149 | + <string name="tor_authentication_enable_lock">Enable %s tabs screen lock</string>
|
|
150 | + <!-- Authentication prompt title to disable the private tabs mode. "%s" will be replaced with the localised application name, such as "Tor Browser" -->
|
|
151 | + <string name="tor_authentication_disable_lock">Disable %s tabs screen lock</string>
|
|
152 | + <!-- Label for a preference shown when the device supports screen lock (e.g., PIN, pattern, or password) but the user has not set one up. "%s" will be replaced with the localised application name, such as "Tor Browser" -->
|
|
153 | + <string name="tor_authentication_lock_device_feature_disabled">Set up screen lock to hide %s tabs</string>
|
|
154 | + <!-- Title text for the contextual feature recommendation (CFR) suggesting the user enable a screen lock to protect Tor Browser tabs. "%s" will be replaced with the localised application name, such as "Tor Browser" -->
|
|
155 | + <string name="tor_tab_cfr_title">Use screen lock to hide %s tabs?</string>
|
|
156 | + |
|
157 | + <!-- Preference title for using the screen lock to hide Tor Browser tabs. "%s" will be replaced with the localised application name, such as "Tor Browser" -->
|
|
158 | + <string name="preferences_tor_lock_screen_title">Use screen lock to hide %s tabs</string>
|
|
159 | + <!-- Informs the user how to access the tabs when "Use screen lock to hide Tor Browser tabs" is enabled. "%s" will be replaced with the localised application name, such as "Tor Browser" -->
|
|
160 | + <string name="preferences_tor_lock_screen_summary">View %s tabs with your fingerprint, PIN, or face unlock.</string>
|
|
161 | + |
|
145 | 162 | </resources> |
... | ... | @@ -57,4 +57,14 @@ |
57 | 57 | android:key="@string/pref_key_inactive_tabs"
|
58 | 58 | android:title="@string/preferences_inactive_tabs_title"/>
|
59 | 59 | </androidx.preference.PreferenceCategory>
|
60 | + |
|
61 | + <PreferenceCategory
|
|
62 | + android:key="@string/pref_key_pbm_lock_category_divider"
|
|
63 | + android:layout="@xml/preference_category_divider"
|
|
64 | + android:selectable="false" />
|
|
65 | + <SwitchPreference
|
|
66 | + android:defaultValue="false"
|
|
67 | + android:key="pref_key_private_browsing_locked_enabled" />
|
|
68 | + <Preference
|
|
69 | + android:key="@string/pref_key_private_browsing_lock_device_feature_enabled" />
|
|
60 | 70 | </androidx.preference.PreferenceScreen> |