richard pushed to branch firefox-android-115.2.1-13.0-1 at The Tor Project / Applications / firefox-android
Commits: a0805d02 by t-p-white at 2023-12-14T15:36:13+01:00 Bug 1823316 - Use 'Snackbar' themed Dialog to notify on making app full-screen
- - - - - 8ff2dd1b by Tarik Eshaq at 2023-12-14T15:36:22+01:00 Bug 1865488: Adds server parameter to push subscription
(cherry picked from commit f66bc9d4981d9bba7091389d9f0a6864291d38fe)
- - - - -
9 changed files:
- + android-components/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/dialog/FullScreenNotificationDialog.kt - android-components/components/feature/sitepermissions/src/main/java/mozilla/components/feature/sitepermissions/SitePermissionsFeature.kt - fenix/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt - fenix/app/src/main/java/org/mozilla/fenix/push/WebPushEngineIntegration.kt - + fenix/app/src/main/res/layout/full_screen_notification_dialog.xml - focus-android/app/src/main/java/org/mozilla/focus/browser/integration/FullScreenIntegration.kt - focus-android/app/src/main/java/org/mozilla/focus/fragment/BrowserFragment.kt - + focus-android/app/src/main/res/layout/dialog_full_screen_notification.xml - focus-android/app/src/test/java/org/mozilla/focus/browser/integration/FullScreenIntegrationTest.kt
Changes:
===================================== android-components/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/dialog/FullScreenNotificationDialog.kt ===================================== @@ -0,0 +1,69 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package mozilla.components.feature.prompts.dialog + +import android.app.Dialog +import android.os.Bundle +import android.view.Gravity +import android.view.WindowManager +import androidx.annotation.LayoutRes +import androidx.appcompat.app.AlertDialog +import androidx.fragment.app.DialogFragment +import androidx.fragment.app.FragmentManager +import androidx.lifecycle.lifecycleScope +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch + +private const val TAG = "mozac_feature_prompts_full_screen_notification_dialog" +private const val SNACKBAR_DURATION_LONG_MS = 3000L + +/** + * UI to show a 'full screen mode' notification. + */ +interface FullScreenNotification { + /** + * Show the notification. + * + * @param fragmentManager the [FragmentManager] to add this notification to. + */ + fun show(fragmentManager: FragmentManager) +} + +/** + * [DialogFragment] that is configured to match the style and behaviour of a Snackbar. + * + * @property layout the layout to use for the dialog. + */ +class FullScreenNotificationDialog(@LayoutRes val layout: Int) : + DialogFragment(), FullScreenNotification { + override fun show(fragmentManager: FragmentManager) = super.show(fragmentManager, TAG) + + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog = requireActivity().let { + val view = layoutInflater.inflate(layout, null) + AlertDialog.Builder(it).setView(view).create() + } + + override fun onStart() { + super.onStart() + + dialog?.let { dialog -> + dialog.window?.let { window -> + // Prevent any user input from key or other button events to it. + window.setFlags( + WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, + WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, + ) + + window.setGravity(Gravity.BOTTOM) + window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND) + } + + lifecycleScope.launch { + delay(SNACKBAR_DURATION_LONG_MS) + dismiss() + } + } + } +}
===================================== android-components/components/feature/sitepermissions/src/main/java/mozilla/components/feature/sitepermissions/SitePermissionsFeature.kt ===================================== @@ -70,7 +70,9 @@ import mozilla.components.support.ktx.kotlinx.coroutines.flow.ifChanged import java.security.InvalidParameterException import mozilla.components.ui.icons.R as iconsR
-internal const val FRAGMENT_TAG = "mozac_feature_sitepermissions_prompt_dialog" +internal const val PROMPT_FRAGMENT_TAG = "mozac_feature_sitepermissions_prompt_dialog" + +private const val FULL_SCREEN_NOTIFICATION_TAG = "mozac_feature_prompts_full_screen_notification_dialog"
@VisibleForTesting internal const val STORAGE_ACCESS_DOCUMENTATION_URL = @@ -124,7 +126,7 @@ class SitePermissionsFeature( private var loadingScope: CoroutineScope? = null
override fun start() { - fragmentManager.findFragmentByTag(FRAGMENT_TAG)?.let { fragment -> + fragmentManager.findFragmentByTag(PROMPT_FRAGMENT_TAG)?.let { fragment -> // There's still a [SitePermissionsDialogFragment] visible from the last time. Re-attach // this feature so that the fragment can invoke the callback on this feature once the user // makes a selection. This can happen when the app was in the background and on resume @@ -439,8 +441,16 @@ class SitePermissionsFeature( } else { handleNoRuledFlow(permissionFromStorage, permissionRequest, origin) } - prompt?.show(fragmentManager, FRAGMENT_TAG) - return prompt + + val fullScreenNotificationDisplayed = + fragmentManager.fragments.any { fragment -> fragment.tag == FULL_SCREEN_NOTIFICATION_TAG } + + return if (fullScreenNotificationDisplayed || prompt == null) { + null + } else { + prompt.show(fragmentManager, PROMPT_FRAGMENT_TAG) + prompt + } }
@VisibleForTesting
===================================== fenix/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt ===================================== @@ -17,7 +17,6 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.view.accessibility.AccessibilityManager -import android.widget.Toast import androidx.activity.result.ActivityResultLauncher import androidx.annotation.CallSuper import androidx.annotation.VisibleForTesting @@ -74,6 +73,7 @@ import mozilla.components.feature.prompts.PromptFeature import mozilla.components.feature.prompts.PromptFeature.Companion.PIN_REQUEST import mozilla.components.feature.prompts.address.AddressDelegate import mozilla.components.feature.prompts.creditcard.CreditCardDelegate +import mozilla.components.feature.prompts.dialog.FullScreenNotificationDialog import mozilla.components.feature.prompts.login.LoginDelegate import mozilla.components.feature.prompts.share.ShareDelegate import mozilla.components.feature.readerview.ReaderViewFeature @@ -1465,10 +1465,11 @@ abstract class BaseBrowserFragment : if (inFullScreen) { // Close find in page bar if opened findInPageIntegration.onBackPressed() - Toast - .makeText(requireContext(), R.string.full_screen_notification, Toast.LENGTH_SHORT) - .show() - activity?.enterToImmersiveMode() + + FullScreenNotificationDialog(R.layout.full_screen_notification_dialog).show( + parentFragmentManager, + ) + (view as? SwipeGestureLayout)?.isSwipeEnabled = false browserToolbarView.collapse() browserToolbarView.view.isVisible = false
===================================== fenix/app/src/main/java/org/mozilla/fenix/push/WebPushEngineIntegration.kt ===================================== @@ -58,8 +58,6 @@ internal class WebPushEngineDelegate( private val logger = Logger("WebPushEngineDelegate")
override fun onGetSubscription(scope: String, onSubscription: (WebPushSubscription?) -> Unit) { - // We don't have the appServerKey unless an app is creating a new subscription so we - // allow the key to be null since it won't be overridden from a previous subscription. pushFeature.getSubscription(scope) { onSubscription(it?.toEnginePushSubscription()) } @@ -72,9 +70,7 @@ internal class WebPushEngineDelegate( ) { pushFeature.subscribe( scope = scope, - // See the full note at the implementation of `toEnginePushSubscription`. - // Issue: https://github.com/mozilla/application-services/issues/2698 - appServerKey = null, + appServerKey = serverKey?.toEncodedBase64String(), onSubscribeError = { logger.error("Error on push onSubscribe.") onSubscribe(null) @@ -104,13 +100,12 @@ internal fun AutoPushSubscription.toEnginePushSubscription() = WebPushSubscripti publicKey = this.publicKey.toDecodedByteArray(), endpoint = this.endpoint, authSecret = this.authKey.toDecodedByteArray(), - // We don't send the `serverKey` because the code path from that will query - // the push database for this key, which leads to an exception thrown. - // Our workaround for now is to not put the server key in to begin with (which - // will probably break a lot of sites). - // See: https://github.com/mozilla/application-services/issues/2698 + // We don't have the appServerKey unless an app is creating a new subscription so we + // allow the key to be null since it won't be overridden from a previous subscription. appServerKey = null, )
private fun String.toDecodedByteArray() = Base64.decode(this.toByteArray(), Base64.URL_SAFE or Base64.NO_PADDING or Base64.NO_WRAP) +private fun ByteArray.toEncodedBase64String() = + Base64.encodeToString(this, Base64.URL_SAFE or Base64.NO_PADDING or Base64.NO_WRAP)
===================================== fenix/app/src/main/res/layout/full_screen_notification_dialog.xml ===================================== @@ -0,0 +1,40 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> + +<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:id="@+id/full_screen_notification" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_margin="8dp" + android:background="@drawable/fenix_snackbar_background" + android:elevation="4dp" + android:minHeight="48dp" + android:orientation="horizontal" + android:paddingStart="16dp" + android:paddingEnd="16dp"> + + <TextView + android:id="@+id/full_screen_notification_text" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:ellipsize="end" + android:letterSpacing="0.05" + android:maxLines="2" + android:minHeight="46dp" + android:paddingTop="8dp" + android:paddingBottom="8dp" + android:text="@string/full_screen_notification" + android:textAlignment="textStart" + android:textColor="@color/photonWhite" + android:textSize="18sp" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" + tools:text="@string/full_screen_notification" /> + +</androidx.constraintlayout.widget.ConstraintLayout>
===================================== focus-android/app/src/main/java/org/mozilla/focus/browser/integration/FullScreenIntegration.kt ===================================== @@ -7,12 +7,14 @@ package org.mozilla.focus.browser.integration import android.app.Activity import android.os.Build import android.view.View -import android.widget.Toast import androidx.annotation.VisibleForTesting import androidx.core.view.isVisible +import androidx.fragment.app.FragmentManager import mozilla.components.browser.state.store.BrowserStore import mozilla.components.browser.toolbar.BrowserToolbar import mozilla.components.concept.engine.EngineView +import mozilla.components.feature.prompts.dialog.FullScreenNotification +import mozilla.components.feature.prompts.dialog.FullScreenNotificationDialog import mozilla.components.feature.session.FullScreenFeature import mozilla.components.feature.session.SessionUseCases import mozilla.components.support.base.feature.LifecycleAwareFeature @@ -26,6 +28,7 @@ import org.mozilla.focus.ext.hide import org.mozilla.focus.ext.showAsFixed import org.mozilla.focus.utils.Settings
+@Suppress("LongParameterList") class FullScreenIntegration( val activity: Activity, val store: BrowserStore, @@ -35,6 +38,7 @@ class FullScreenIntegration( private val toolbarView: BrowserToolbar, private val statusBar: View, private val engineView: EngineView, + private val parentFragmentManager: FragmentManager, ) : LifecycleAwareFeature, UserInteractionHandler { @VisibleForTesting internal var feature = FullScreenFeature( @@ -54,14 +58,16 @@ class FullScreenIntegration( }
@VisibleForTesting - internal fun fullScreenChanged(enabled: Boolean) { + internal fun fullScreenChanged( + enabled: Boolean, + fullScreenNotification: FullScreenNotification = + FullScreenNotificationDialog(R.layout.dialog_full_screen_notification), + ) { if (enabled) { enterBrowserFullscreen() statusBar.isVisible = false
- Toast - .makeText(activity, R.string.full_screen_notification, Toast.LENGTH_SHORT) - .show() + fullScreenNotification.show(parentFragmentManager)
switchToImmersiveMode() } else {
===================================== focus-android/app/src/main/java/org/mozilla/focus/fragment/BrowserFragment.kt ===================================== @@ -272,6 +272,7 @@ class BrowserFragment : binding.browserToolbar, binding.statusBarBackground, binding.engineView, + parentFragmentManager, ), this, view,
===================================== focus-android/app/src/main/res/layout/dialog_full_screen_notification.xml ===================================== @@ -0,0 +1,38 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> + +<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:id="@+id/full_screen_notification_layout" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_margin="8dp" + android:background="@drawable/focus_snackbar_background" + android:elevation="4dp" + android:minHeight="48dp" + android:orientation="horizontal" + android:paddingStart="16dp" + android:paddingEnd="16dp"> + + <TextView + android:id="@+id/full_screen_notification_text" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:ellipsize="end" + android:letterSpacing="0.05" + android:maxLines="2" + android:paddingTop="8dp" + android:paddingBottom="8dp" + android:text="@string/full_screen_notification" + android:textAlignment="textStart" + android:textColor="@color/snackbarTextColor" + android:textSize="14sp" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" + tools:text="@string/full_screen_notification" /> +</androidx.constraintlayout.widget.ConstraintLayout>
===================================== focus-android/app/src/test/java/org/mozilla/focus/browser/integration/FullScreenIntegrationTest.kt ===================================== @@ -12,11 +12,11 @@ import android.view.WindowManager import androidx.core.view.isVisible import mozilla.components.browser.engine.gecko.GeckoEngineView import mozilla.components.browser.toolbar.BrowserToolbar +import mozilla.components.feature.prompts.dialog.FullScreenNotification import mozilla.components.feature.session.FullScreenFeature import mozilla.components.support.test.any import mozilla.components.support.test.mock import mozilla.components.support.test.robolectric.testContext -import org.junit.Assert.assertNotNull import org.junit.Test import org.junit.jupiter.api.Assertions.assertEquals import org.junit.runner.RunWith @@ -26,7 +26,6 @@ import org.mockito.Mockito.never import org.mockito.Mockito.spy import org.mockito.Mockito.times import org.mockito.Mockito.verify -import org.mozilla.focus.R import org.mozilla.focus.ext.disableDynamicBehavior import org.mozilla.focus.ext.enableDynamicBehavior import org.mozilla.focus.ext.hide @@ -34,7 +33,6 @@ import org.mozilla.focus.ext.showAsFixed import org.mozilla.focus.utils.Settings import org.robolectric.Robolectric import org.robolectric.RobolectricTestRunner -import org.robolectric.shadows.ShadowToast
@RunWith(RobolectricTestRunner::class) internal class FullScreenIntegrationTest { @@ -50,6 +48,7 @@ internal class FullScreenIntegrationTest { mock(), mock(), mock(), + mock(), ).apply { this.feature = feature } @@ -71,6 +70,7 @@ internal class FullScreenIntegrationTest { mock(), mock(), mock(), + mock(), ).apply { this.feature = feature } @@ -92,6 +92,7 @@ internal class FullScreenIntegrationTest { mock(), mock(), mock(), + mock(), ).apply { this.feature = feature } @@ -117,6 +118,7 @@ internal class FullScreenIntegrationTest { mock(), mock(), mock(), + mock(), )
integration.viewportFitChanged(33) @@ -141,6 +143,7 @@ internal class FullScreenIntegrationTest { mock(), mock(), mock(), + mock(), )
integration.switchToImmersiveMode() @@ -169,6 +172,7 @@ internal class FullScreenIntegrationTest { mock(), mock(), mock(), + mock(), )
integration.exitImmersiveMode() @@ -195,6 +199,7 @@ internal class FullScreenIntegrationTest { toolbar, mock(), engineView, + mock(), )
integration.enterBrowserFullscreen() @@ -220,6 +225,7 @@ internal class FullScreenIntegrationTest { toolbar, mock(), engineView, + mock(), )
integration.enterBrowserFullscreen() @@ -250,6 +256,7 @@ internal class FullScreenIntegrationTest { toolbar, mock(), engineView, + mock(), )
integration.exitBrowserFullscreen() @@ -278,6 +285,7 @@ internal class FullScreenIntegrationTest { toolbar, mock(), engineView, + mock(), )
integration.exitBrowserFullscreen() @@ -308,21 +316,17 @@ internal class FullScreenIntegrationTest { toolbar, statusBar, engineView, + mock(), ), )
- integration.fullScreenChanged(true) + val fullScreenNotification = mock<FullScreenNotification>() + integration.fullScreenChanged(true, fullScreenNotification)
verify(integration).enterBrowserFullscreen() - verify(integration).switchToImmersiveMode() verify(statusBar).isVisible = false - - val toast = ShadowToast.getTextOfLatestToast() - assertNotNull(toast) - assertEquals( - testContext.getString(R.string.full_screen_notification), - toast, - ) + verify(fullScreenNotification).show(any()) + verify(integration).switchToImmersiveMode() }
@Test @@ -352,6 +356,7 @@ internal class FullScreenIntegrationTest { toolbar, statusBar, engineView, + mock(), ), )
View it on GitLab: https://gitlab.torproject.org/tpo/applications/firefox-android/-/compare/332...
tor-commits@lists.torproject.org