Loading packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java +18 −3 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; } Loading packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java +15 −5 Original line number Diff line number Diff line Loading @@ -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); } Loading Loading @@ -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; Loading packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt +36 −5 Original line number Diff line number Diff line Loading @@ -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. */ Loading Loading @@ -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 Loading Loading @@ -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] Loading Loading @@ -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) Loading @@ -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. Loading packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/SwipeStatusBarAwayGestureHandler.kt 0 → 100644 +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) packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt +24 −4 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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 Loading Loading @@ -112,6 +112,7 @@ class OngoingCallControllerTest : SysuiTestCase() { OngoingCallLogger(uiEventLoggerFake), DumpManager(), Optional.of(mockStatusBarWindowController), Optional.of(mockSwipeStatusBarAwayGestureHandler), ) controller.init() controller.addCallback(mockOngoingCallListener) Loading Loading @@ -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 Loading Loading @@ -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. */ Loading Loading
packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java +18 −3 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; } Loading
packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java +15 −5 Original line number Diff line number Diff line Loading @@ -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); } Loading Loading @@ -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; Loading
packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt +36 −5 Original line number Diff line number Diff line Loading @@ -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. */ Loading Loading @@ -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 Loading Loading @@ -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] Loading Loading @@ -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) Loading @@ -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. Loading
packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/SwipeStatusBarAwayGestureHandler.kt 0 → 100644 +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)
packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt +24 −4 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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 Loading Loading @@ -112,6 +112,7 @@ class OngoingCallControllerTest : SysuiTestCase() { OngoingCallLogger(uiEventLoggerFake), DumpManager(), Optional.of(mockStatusBarWindowController), Optional.of(mockSwipeStatusBarAwayGestureHandler), ) controller.init() controller.addCallback(mockOngoingCallListener) Loading Loading @@ -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 Loading Loading @@ -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. */ Loading