Verified Commit 270554ff authored by pollymce's avatar pollymce Committed by Pier Angelo Vendrame
Browse files

Bug 1902996 - Improve messaging for fullscreen notifications. r=android-reviewers,gl

Instead of explaining to users that they are in fullscreen mode, which may be obvious, we explain how to get out of it :)
Also use a Toast rather than a custom Dialog.
Update Focus too.
Fix lint errors.

Differential Revision: https://phabricator.services.mozilla.com/D215782
parent 4a4f94a3
Loading
Loading
Loading
Loading
+42 −0
Original line number Diff line number Diff line
/* 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.Activity
import android.widget.Toast

/**
 * UI to show a 'full screen mode' notification.
 */
interface FullScreenNotification {
    /**
     * Show the notification.
     */
    fun show()
}

/**
 * A [Toast] to show a full screen notification message
 * @property activity The activity to show the toast on.
 * @property gestureNavString The string to show when in gesture navigation mode.
 * @property backButtonString The string to show when in 3-button navigation mode.
 * @property gestureNavUtils Utility to detect gesture navigation.
 */
class FullScreenNotificationToast(
    private val activity: Activity,
    private val gestureNavString: String,
    private val backButtonString: String,
    private val gestureNavUtils: GestureNavUtils,
) : FullScreenNotification {
    override fun show() {
        val toastString =
            if (gestureNavUtils.isInGestureNavigationMode(activity.window)) {
                gestureNavString
            } else {
                backButtonString
            }
        Toast.makeText(activity, toastString, Toast.LENGTH_LONG).show()
    }
}
+0 −70
Original line number Diff line number Diff line
/* 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)
            }
        }

        // Attempt to automatically dismiss the dialog after the given duration.
        lifecycleScope.launch {
            delay(SNACKBAR_DURATION_LONG_MS)
            dialog?.dismiss()
        }
    }
}
+42 −0
Original line number Diff line number Diff line
/* 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.os.Build
import android.view.Window
import androidx.core.view.WindowInsetsCompat

/**
 * A place to keep utility functions for gesture navigation.
 */
object GestureNavUtils {
    /**
     * Find out if the device supports gesture navigation and has gestures enabled.
     *
     * Gesture nav support was added in Android Q, but on recent devices it is possible to
     * switch navigation mode between gesture nav and 3-button navigation.
     * The system gesture inset needs to be greater than 0 at the left edge of the screen
     * for the back gesture to be picked up, so this seems to be the most reliable way of
     * detecting gesture navigation use.
     *
     * * See also: [Gesture Navigation: handling visual overlaps (II)](https://medium.com/androiddevelopers/gesture-navigation-handling-visual-overlaps-4aed565c134c)
     * * See also: [How to detect full screen gesture mode](https://stackoverflow.com/a/70514883)
     *
     * @param window The containing [Window] of the current `Activity`.
     * @return true if the device supports gesture navigation and gestures are enabled.
     */
    fun isInGestureNavigationMode(window: Window): Boolean =
        (
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
                WindowInsetsCompat
                    .toWindowInsetsCompat(
                        window.decorView.rootWindowInsets,
                    ).getInsets(WindowInsetsCompat.Type.systemGestures())
                    .left
            } else {
                0
            }
            ) > 0
}
+67 −0
Original line number Diff line number Diff line
/* 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.Activity
import androidx.test.core.app.ActivityScenario
import androidx.test.ext.junit.runners.AndroidJUnit4
import mozilla.components.support.test.any
import mozilla.components.support.test.whenever
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.MockitoAnnotations
import org.robolectric.Robolectric
import org.robolectric.shadows.ShadowToast

@RunWith(AndroidJUnit4::class)
class FullScreenNotificationTest {

    @Mock
    lateinit var mockGestureNavUtils: GestureNavUtils

    @Before
    fun setup() {
        MockitoAnnotations.openMocks(this)
        val activity = Robolectric.buildActivity(EmptyActivity::class.java)
        activity.create()
    }

    @Test
    fun `when in 3 button navigation mode, full screen notification shows exit instructions for back button`() {
        whenever(mockGestureNavUtils.isInGestureNavigationMode(any())).thenReturn(false)
        ActivityScenario.launch(EmptyActivity::class.java).use { scenario ->
            scenario.onActivity { activity: EmptyActivity ->
                FullScreenNotificationToast(
                    activity,
                    "gesture",
                    "button",
                    mockGestureNavUtils,
                ).show()
                assertTrue(ShadowToast.showedToast("button"))
            }
        }
    }

    @Test
    fun `when in gesture navigation mode, full screen notification shows exit instructions for gesture nav`() {
        whenever(mockGestureNavUtils.isInGestureNavigationMode(any())).thenReturn(true)
        ActivityScenario.launch(EmptyActivity::class.java).use { scenario ->
            scenario.onActivity { activity: EmptyActivity ->
                FullScreenNotificationToast(
                    activity,
                    "gesture",
                    "button",
                    mockGestureNavUtils,
                ).show()
                assertTrue(ShadowToast.showedToast("gesture"))
            }
        }
    }
}

internal class EmptyActivity : Activity()
+2 −0
Original line number Diff line number Diff line
@@ -5,6 +5,8 @@ permalink: /changelog/
---

# 128.0 (In Development)
* **feature-prompts**:
  * ⚠️ **Breaking change**: `FullScreenNotification` interface is now implemented using a `FullScreenNotificationToast`. `FullScreenNotificationDialog` has been removed, see [Bug 1902996](https://bugzilla.mozilla.org/show_bug.cgi?id=1902996).

* **browser-toolbar**
  * Added new data classes `CustomTabsToolbarButtonConfig` and `CustomTabsToolbarListeners` to `CustomTabsToolbarFeature`, see [Bug 1897811](https://bugzilla.mozilla.org/show_bug.cgi?id=1897811).
Loading