Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 099977ee authored by Caitlin Cassidy's avatar Caitlin Cassidy Committed by Automerger Merge Worker
Browse files

Merge "[Ongoing Call] Hide the status bar in immersive mode if the user has...

Merge "[Ongoing Call] Hide the status bar in immersive mode if the user has swiped it away." into sc-v2-dev am: acf2a8d3 am: a8ba7e00

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/16010040

Change-Id: I5a25eed6f3d65f0dc47d893ffc3572fbc31c9697
parents fb58e975 a8ba7e00
Loading
Loading
Loading
Loading
+18 −3
Original line number Diff line number Diff line
@@ -69,6 +69,7 @@ import com.android.systemui.statusbar.phone.StatusBarWindowController;
import com.android.systemui.statusbar.phone.SystemUIHostDialogProvider;
import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController;
import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallLogger;
import com.android.systemui.statusbar.phone.ongoingcall.SwipeStatusBarAwayGestureHandler;
import com.android.systemui.statusbar.policy.RemoteInputUriController;
import com.android.systemui.tracing.ProtoTracer;
import com.android.systemui.util.concurrency.DelayableExecutor;
@@ -255,15 +256,29 @@ public interface StatusBarDependenciesModule {
            IActivityManager iActivityManager,
            OngoingCallLogger logger,
            DumpManager dumpManager,
            StatusBarWindowController statusBarWindowController) {
            StatusBarWindowController statusBarWindowController,
            SwipeStatusBarAwayGestureHandler swipeStatusBarAwayGestureHandler) {
        Optional<StatusBarWindowController> windowController =
                featureFlags.isOngoingCallInImmersiveEnabled()
                        ? Optional.of(statusBarWindowController)
                        : Optional.empty();
        Optional<SwipeStatusBarAwayGestureHandler> gestureHandler =
                featureFlags.isOngoingCallInImmersiveEnabled()
                        ? Optional.of(swipeStatusBarAwayGestureHandler)
                        : Optional.empty();
        OngoingCallController ongoingCallController =
                new OngoingCallController(
                        notifCollection, featureFlags, systemClock, activityStarter, mainExecutor,
                        iActivityManager, logger, dumpManager, windowController);
                        notifCollection,
                        featureFlags,
                        systemClock,
                        activityStarter,
                        mainExecutor,
                        iActivityManager,
                        logger,
                        dumpManager,
                        windowController,
                        gestureHandler
                );
        ongoingCallController.init();
        return ongoingCallController;
    }
+15 −5
Original line number Diff line number Diff line
@@ -208,9 +208,18 @@ public class StatusBarWindowController {
        apply(mCurrentState);
    }

    /** Sets whether there is currently an ongoing call. */
    public void setIsCallOngoing(boolean isCallOngoing) {
        mCurrentState.mIsCallOngoing = isCallOngoing;
    /**
     * Sets whether an ongoing process requires the status bar to be forced visible.
     *
     * This method is separate from {@link this#setForceStatusBarVisible} because the ongoing
     * process **takes priority**. For example, if {@link this#setForceStatusBarVisible} is set to
     * false but this method is set to true, then the status bar **will** be visible.
     *
     * TODO(b/195839150): We should likely merge this method and
     * {@link this#setForceStatusBarVisible} together and use some sort of ranking system instead.
     */
    public void setOngoingProcessRequiresStatusBarVisible(boolean visible) {
        mCurrentState.mOngoingProcessRequiresStatusBarVisible = visible;
        apply(mCurrentState);
    }

@@ -254,13 +263,14 @@ public class StatusBarWindowController {
    private static class State {
        boolean mForceStatusBarVisible;
        boolean mIsLaunchAnimationRunning;
        boolean mIsCallOngoing;
        boolean mOngoingProcessRequiresStatusBarVisible;
    }

    private void applyForceStatusBarVisibleFlag(State state) {
        if (state.mForceStatusBarVisible
                || state.mIsLaunchAnimationRunning
                || state.mIsCallOngoing) {
                // Don't force-show the status bar if the user has already dismissed it.
                || state.mOngoingProcessRequiresStatusBarVisible) {
            mLpChanged.privateFlags |= PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR;
        } else {
            mLpChanged.privateFlags &= ~PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR;
+36 −5
Original line number Diff line number Diff line
@@ -60,6 +60,7 @@ class OngoingCallController @Inject constructor(
    private val logger: OngoingCallLogger,
    private val dumpManager: DumpManager,
    private val statusBarWindowController: Optional<StatusBarWindowController>,
    private val swipeStatusBarAwayGestureHandler: Optional<SwipeStatusBarAwayGestureHandler>,
) : CallbackController<OngoingCallListener>, Dumpable {

    /** Non-null if there's an active call notification. */
@@ -96,7 +97,8 @@ class OngoingCallController @Inject constructor(
                        entry.sbn.notification.contentIntent?.intent,
                        entry.sbn.uid,
                        entry.sbn.notification.extras.getInt(
                                Notification.EXTRA_CALL_TYPE, -1) == CALL_TYPE_ONGOING
                                Notification.EXTRA_CALL_TYPE, -1) == CALL_TYPE_ONGOING,
                        statusBarSwipedAway = callNotificationInfo?.statusBarSwipedAway ?: false
                )
                if (newOngoingCallInfo == callNotificationInfo) {
                    return
@@ -202,9 +204,16 @@ class OngoingCallController @Inject constructor(
                    )
                }
            }

            setUpUidObserver(currentCallNotificationInfo)
            statusBarWindowController.ifPresent { it.setIsCallOngoing(true) }
            if (!currentCallNotificationInfo.statusBarSwipedAway) {
                statusBarWindowController.ifPresent {
                    it.setOngoingProcessRequiresStatusBarVisible(true)
                }
                // TODO(b/195839150): Only listen for the gesture when in immersive mode.
                swipeStatusBarAwayGestureHandler.ifPresent {
                    it.addOnGestureDetectedCallback(TAG, this::onSwipeAwayGestureDetected)
                }
            }
            mListeners.forEach { l -> l.onOngoingCallStateChanged(animate = true) }
        } else {
            // If we failed to update the chip, don't store the call info. Then [hasOngoingCall]
@@ -271,7 +280,8 @@ class OngoingCallController @Inject constructor(
    private fun removeChip() {
        callNotificationInfo = null
        tearDownChipView()
        statusBarWindowController.ifPresent { it.setIsCallOngoing(false) }
        statusBarWindowController.ifPresent { it.setOngoingProcessRequiresStatusBarVisible(false) }
        swipeStatusBarAwayGestureHandler.ifPresent { it.removeOnGestureDetectedCallback(TAG) }
        mListeners.forEach { l -> l.onOngoingCallStateChanged(animate = true) }
        if (uidObserver != null) {
            iActivityManager.unregisterUidObserver(uidObserver)
@@ -286,13 +296,34 @@ class OngoingCallController @Inject constructor(
        return this.findViewById(R.id.ongoing_call_chip_time)
    }

   /**
    * If there's an active ongoing call, then we will force the status bar to always show, even if
    * the user is in immersive mode. However, we also want to give users the ability to swipe away
    * the status bar if they need to access the area under the status bar.
    *
    * This method updates the status bar window appropriately when the swipe away gesture is
    * detected.
    */
    private fun onSwipeAwayGestureDetected() {
        if (DEBUG) { Log.d(TAG, "Swipe away gesture detected") }
        callNotificationInfo = callNotificationInfo?.copy(statusBarSwipedAway = true)
        statusBarWindowController.ifPresent {
            it.setOngoingProcessRequiresStatusBarVisible(false)
        }
        swipeStatusBarAwayGestureHandler.ifPresent {
            it.removeOnGestureDetectedCallback(TAG)
        }
    }

    private data class CallNotificationInfo(
        val key: String,
        val callStartTime: Long,
        val intent: Intent?,
        val uid: Int,
        /** True if the call is currently ongoing (as opposed to incoming, screening, etc.). */
        val isOngoing: Boolean
        val isOngoing: Boolean,
        /** True if the user has swiped away the status bar while in this phone call. */
        val statusBarSwipedAway: Boolean
    ) {
        /**
         * Returns true if the notification information has a valid call start time.
+153 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.systemui.statusbar.phone.ongoingcall

import android.content.Context
import android.os.Looper
import android.util.Log
import android.view.Choreographer
import android.view.Display
import android.view.InputEvent
import android.view.MotionEvent
import android.view.MotionEvent.*
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.shared.system.InputChannelCompat
import com.android.systemui.shared.system.InputMonitorCompat
import com.android.systemui.statusbar.phone.StatusBarWindowController
import javax.inject.Inject

/**
 * A class to detect when a user swipes away the status bar. To be notified when the swipe away
 * gesture is detected, add a callback via [addOnGestureDetectedCallback].
 */
@SysUISingleton
open class SwipeStatusBarAwayGestureHandler @Inject constructor(
    context: Context,
    private val statusBarWindowController: StatusBarWindowController,
) {

    /**
     * Active callbacks, each associated with a tag. Gestures will only be monitored if
     * [callbacks.size] > 0.
     */
    private val callbacks: MutableMap<String, () -> Unit> = mutableMapOf()

    private var startY: Float = 0f
    private var startTime: Long = 0L
    private var monitoringCurrentTouch: Boolean = false

    private var inputMonitor: InputMonitorCompat? = null
    private var inputReceiver: InputChannelCompat.InputEventReceiver? = null

    // TODO(b/195839150): Update this threshold when the config changes?
    private var swipeDistanceThreshold: Int = context.resources.getDimensionPixelSize(
        com.android.internal.R.dimen.system_gestures_start_threshold
    )

    /** Adds a callback that will be triggered when the swipe away gesture is detected. */
    fun addOnGestureDetectedCallback(tag: String, callback: () -> Unit) {
        val callbacksWasEmpty = callbacks.isEmpty()
        callbacks[tag] = callback
        if (callbacksWasEmpty) {
            startGestureListening()
        }
    }

    /** Removes the callback. */
    fun removeOnGestureDetectedCallback(tag: String) {
        callbacks.remove(tag)
        if (callbacks.isEmpty()) {
             stopGestureListening()
        }
    }

    private fun onInputEvent(ev: InputEvent) {
        if (ev !is MotionEvent) {
            return
        }

        when (ev.actionMasked) {
            ACTION_DOWN -> {
                if (
                    // Gesture starts just below the status bar
                    // TODO(b/195839150): Is [statusBarHeight] the correct dimension to use for
                    //   determining which down touches are valid?
                    ev.y >= statusBarWindowController.statusBarHeight
                    && ev.y <= 3 * statusBarWindowController.statusBarHeight
                ) {
                    Log.d(TAG, "Beginning gesture detection, y=${ev.y}")
                    startY = ev.y
                    startTime = ev.eventTime
                    monitoringCurrentTouch = true
                } else {
                    monitoringCurrentTouch = false
                }
            }
            ACTION_MOVE -> {
                if (!monitoringCurrentTouch) {
                    return
                }
                if (
                    // Gesture is up
                    ev.y < startY
                    // Gesture went far enough
                    && (startY - ev.y) >= swipeDistanceThreshold
                    // Gesture completed quickly enough
                    && (ev.eventTime - startTime) < SWIPE_TIMEOUT_MS
                ) {
                    Log.i(TAG, "Gesture detected; notifying callbacks")
                    callbacks.values.forEach { it.invoke() }
                    monitoringCurrentTouch = false
                }
            }
            ACTION_CANCEL, ACTION_UP -> {
                monitoringCurrentTouch = false
            }
        }
    }

    /** Start listening for the swipe gesture. */
    private fun startGestureListening() {
        stopGestureListening()

        if (DEBUG) { Log.d(TAG, "Input listening started") }
        inputMonitor = InputMonitorCompat(TAG, Display.DEFAULT_DISPLAY).also {
            inputReceiver = it.getInputReceiver(
                Looper.getMainLooper(),
                Choreographer.getInstance(),
                this::onInputEvent
            )
        }
    }

    /** Stop listening for the swipe gesture. */
    private fun stopGestureListening() {
        inputMonitor?.let {
            if (DEBUG) { Log.d(TAG, "Input listening stopped") }
            inputMonitor = null
            it.dispose()
        }
        inputReceiver?.let {
            inputReceiver = null
            it.dispose()
        }
    }
}

private const val SWIPE_TIMEOUT_MS: Long = 500
private val TAG = SwipeStatusBarAwayGestureHandler::class.simpleName
private val DEBUG = Log.isLoggable(TAG, Log.DEBUG)
+24 −4
Original line number Diff line number Diff line
@@ -50,8 +50,7 @@ import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers.anyBoolean
import org.mockito.ArgumentMatchers.nullable
import org.mockito.ArgumentMatchers.*
import org.mockito.Mock
import org.mockito.Mockito.`when`
import org.mockito.Mockito.eq
@@ -83,6 +82,7 @@ class OngoingCallControllerTest : SysuiTestCase() {
    private lateinit var controller: OngoingCallController
    private lateinit var notifCollectionListener: NotifCollectionListener

    @Mock private lateinit var mockSwipeStatusBarAwayGestureHandler: SwipeStatusBarAwayGestureHandler
    @Mock private lateinit var mockOngoingCallListener: OngoingCallListener
    @Mock private lateinit var mockActivityStarter: ActivityStarter
    @Mock private lateinit var mockIActivityManager: IActivityManager
@@ -112,6 +112,7 @@ class OngoingCallControllerTest : SysuiTestCase() {
                OngoingCallLogger(uiEventLoggerFake),
                DumpManager(),
                Optional.of(mockStatusBarWindowController),
                Optional.of(mockSwipeStatusBarAwayGestureHandler),
        )
        controller.init()
        controller.addCallback(mockOngoingCallListener)
@@ -141,7 +142,15 @@ class OngoingCallControllerTest : SysuiTestCase() {
    fun onEntryUpdated_isOngoingCallNotif_windowControllerUpdated() {
        notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())

        verify(mockStatusBarWindowController).setIsCallOngoing(true)
        verify(mockStatusBarWindowController).setOngoingProcessRequiresStatusBarVisible(true)
    }

    @Test
    fun onEntryUpdated_isOngoingCallNotif_swipeGestureCallbackAdded() {
        notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())

        verify(mockSwipeStatusBarAwayGestureHandler)
            .addOnGestureDetectedCallback(anyString(), any())
    }

    @Test
@@ -242,7 +251,18 @@ class OngoingCallControllerTest : SysuiTestCase() {

        notifCollectionListener.onEntryRemoved(ongoingCallNotifEntry, REASON_USER_STOPPED)

        verify(mockStatusBarWindowController).setIsCallOngoing(false)
        verify(mockStatusBarWindowController).setOngoingProcessRequiresStatusBarVisible(false)
    }

    @Test
    fun onEntryUpdated_callNotifAddedThenRemoved_swipeGestureCallbackRemoved() {
        val ongoingCallNotifEntry = createOngoingCallNotifEntry()
        notifCollectionListener.onEntryAdded(ongoingCallNotifEntry)

        notifCollectionListener.onEntryRemoved(ongoingCallNotifEntry, REASON_USER_STOPPED)

        verify(mockSwipeStatusBarAwayGestureHandler)
            .removeOnGestureDetectedCallback(anyString())
    }

    /** Regression test for b/188491504. */