diff options
7 files changed, 278 insertions, 4 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/FoldStateListener.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/FoldStateListener.kt new file mode 100644 index 000000000000..56b0d598a512 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/FoldStateListener.kt @@ -0,0 +1,57 @@ +/* + * 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 + +import android.content.Context +import android.hardware.devicestate.DeviceStateManager.DeviceStateCallback +import com.android.internal.R + +/** + * Listens for fold state changes and reports the new folded state together with other properties + * associated with that state. + */ +internal class FoldStateListener( + context: Context, + private val listener: OnFoldStateChangeListener +) : DeviceStateCallback { + + internal interface OnFoldStateChangeListener { + /** + * Reports that the device either became folded or unfolded. + * + * @param folded whether the device is folded. + * @param willGoToSleep whether the device will go to sleep and keep the screen off. + */ + fun onFoldStateChanged(folded: Boolean, willGoToSleep: Boolean) + } + + private val foldedDeviceStates: IntArray = + context.resources.getIntArray(R.array.config_foldedDeviceStates) + private val goToSleepDeviceStates: IntArray = + context.resources.getIntArray(R.array.config_deviceStatesOnWhichToSleep) + + private var wasFolded: Boolean? = null + + override fun onStateChanged(state: Int) { + val isFolded = foldedDeviceStates.contains(state) + if (wasFolded == isFolded) { + return + } + wasFolded = isFolded + val willGoToSleep = goToSleepDeviceStates.contains(state) + listener.onFoldStateChanged(isFolded, willGoToSleep) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeController.java index 2d41e5e9be2c..24bb7f25f2eb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeController.java @@ -55,6 +55,9 @@ public interface ShadeController { */ boolean closeShadeIfOpen(); + /** Returns whether the shade is currently open or opening. */ + boolean isShadeOpen(); + /** * Add a runnable for NotificationPanelView to post when the panel is expanded. * diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeControllerImpl.java index b4fed2ba8624..f8b0535b7ec7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeControllerImpl.java @@ -148,6 +148,13 @@ public class ShadeControllerImpl implements ShadeController { } @Override + public boolean isShadeOpen() { + NotificationPanelViewController controller = + getNotificationPanelViewController(); + return controller.isExpanding() || controller.isFullyExpanded(); + } + + @Override public void postOnShadeExpanded(Runnable executable) { getNotificationPanelViewController().addOnGlobalLayoutListener( new ViewTreeObserver.OnGlobalLayoutListener() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java index 3671bb99b451..1cc5e12a4102 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -70,6 +70,7 @@ import android.content.pm.ResolveInfo; import android.content.res.Configuration; import android.graphics.Point; import android.graphics.PointF; +import android.hardware.devicestate.DeviceStateManager; import android.metrics.LogMaker; import android.net.Uri; import android.os.Bundle; @@ -779,7 +780,8 @@ public class StatusBar extends CoreStartable implements Optional<StartingSurface> startingSurfaceOptional, ActivityLaunchAnimator activityLaunchAnimator, NotifPipelineFlags notifPipelineFlags, - InteractionJankMonitor jankMonitor) { + InteractionJankMonitor jankMonitor, + DeviceStateManager deviceStateManager) { super(context); mNotificationsController = notificationsController; mFragmentService = fragmentService; @@ -902,6 +904,9 @@ public class StatusBar extends CoreStartable implements data -> mCommandQueueCallbacks.animateExpandSettingsPanel(data.mSubpanel)); mMessageRouter.subscribeTo(MSG_LAUNCH_TRANSITION_TIMEOUT, id -> onLaunchTransitionTimeout()); + + deviceStateManager.registerCallback(mMainExecutor, + new FoldStateListener(mContext, this::onFoldedStateChanged)); } @Override @@ -1100,6 +1105,27 @@ public class StatusBar extends CoreStartable implements requestTopUi, componentTag)))); } + private void onFoldedStateChanged(boolean isFolded, boolean willGoToSleep) { + // Folded state changes are followed by a screen off event. + // By default turning off the screen also closes the shade. + // We want to make sure that the shade status is kept after + // folding/unfolding. + boolean isShadeOpen = mShadeController.isShadeOpen(); + boolean leaveOpen = isShadeOpen && !willGoToSleep; + if (DEBUG) { + Log.d(TAG, String.format( + "#onFoldedStateChanged(): " + + "isFolded=%s, " + + "willGoToSleep=%s, " + + "isShadeOpen=%s, " + + "leaveOpen=%s", + isFolded, willGoToSleep, isShadeOpen, leaveOpen)); + } + if (leaveOpen) { + mStatusBarStateController.setLeaveOpenOnKeyguardHide(true); + } + } + // ================================================================================ // Constructing the view // ================================================================================ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java index 50176fec4ac8..977fe9c2d201 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java @@ -20,6 +20,7 @@ import static com.android.systemui.Dependency.TIME_TICK_HANDLER_NAME; import android.app.WallpaperManager; import android.content.Context; +import android.hardware.devicestate.DeviceStateManager; import android.os.Handler; import android.os.PowerManager; import android.util.DisplayMetrics; @@ -231,7 +232,8 @@ public interface StatusBarPhoneModule { Optional<StartingSurface> startingSurfaceOptional, ActivityLaunchAnimator activityLaunchAnimator, NotifPipelineFlags notifPipelineFlags, - InteractionJankMonitor jankMonitor) { + InteractionJankMonitor jankMonitor, + DeviceStateManager deviceStateManager) { return new StatusBar( context, notificationsController, @@ -327,7 +329,8 @@ public interface StatusBarPhoneModule { startingSurfaceOptional, activityLaunchAnimator, notifPipelineFlags, - jankMonitor + jankMonitor, + deviceStateManager ); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/FoldStateListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/FoldStateListenerTest.kt new file mode 100644 index 000000000000..649dc235f398 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/FoldStateListenerTest.kt @@ -0,0 +1,130 @@ +/* + * 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 + +import android.testing.AndroidTestingRunner +import androidx.test.filters.SmallTest +import com.android.internal.R +import com.android.systemui.SysuiTestCase +import com.android.systemui.statusbar.phone.FoldStateListener.OnFoldStateChangeListener +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito +import org.mockito.Mockito.times +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations.initMocks + +@RunWith(AndroidTestingRunner::class) +@SmallTest +class FoldStateListenerTest : SysuiTestCase() { + + @Mock + private lateinit var listener: OnFoldStateChangeListener + private lateinit var sut: FoldStateListener + + @Before + fun setUp() { + initMocks(this) + setFoldedStates(DEVICE_STATE_FOLDED) + setGoToSleepStates(DEVICE_STATE_FOLDED) + sut = FoldStateListener(mContext, listener) + } + + @Test + fun onStateChanged_stateFolded_notifiesWithFoldedAndGoingToSleep() { + sut.onStateChanged(DEVICE_STATE_FOLDED) + + verify(listener).onFoldStateChanged(FOLDED, WILL_GO_TO_SLEEP) + } + + @Test + fun onStateChanged_stateHalfFolded_notifiesWithNotFoldedAndNotGoingToSleep() { + sut.onStateChanged(DEVICE_STATE_HALF_FOLDED) + + verify(listener).onFoldStateChanged(NOT_FOLDED, WILL_NOT_SLEEP) + } + + @Test + fun onStateChanged_stateUnfolded_notifiesWithNotFoldedAndNotGoingToSleep() { + sut.onStateChanged(DEVICE_STATE_UNFOLDED) + + verify(listener).onFoldStateChanged(NOT_FOLDED, WILL_NOT_SLEEP) + } + + @Test + fun onStateChanged_stateUnfoldedThenHalfFolded_notifiesOnce() { + sut.onStateChanged(DEVICE_STATE_UNFOLDED) + sut.onStateChanged(DEVICE_STATE_HALF_FOLDED) + + verify(listener, times(1)).onFoldStateChanged(NOT_FOLDED, WILL_NOT_SLEEP) + } + + @Test + fun onStateChanged_stateHalfFoldedThenUnfolded_notifiesOnce() { + sut.onStateChanged(DEVICE_STATE_HALF_FOLDED) + sut.onStateChanged(DEVICE_STATE_UNFOLDED) + + verify(listener, times(1)).onFoldStateChanged(NOT_FOLDED, WILL_NOT_SLEEP) + } + + @Test + fun onStateChanged_stateHalfFoldedThenFolded_notifiesTwice() { + sut.onStateChanged(DEVICE_STATE_HALF_FOLDED) + sut.onStateChanged(DEVICE_STATE_FOLDED) + + val inOrder = Mockito.inOrder(listener) + inOrder.verify(listener).onFoldStateChanged(NOT_FOLDED, WILL_NOT_SLEEP) + inOrder.verify(listener).onFoldStateChanged(FOLDED, WILL_GO_TO_SLEEP) + } + + @Test + fun onStateChanged_stateFoldedThenHalfFolded_notifiesTwice() { + sut.onStateChanged(DEVICE_STATE_FOLDED) + sut.onStateChanged(DEVICE_STATE_HALF_FOLDED) + + val inOrder = Mockito.inOrder(listener) + inOrder.verify(listener).onFoldStateChanged(FOLDED, WILL_GO_TO_SLEEP) + inOrder.verify(listener).onFoldStateChanged(NOT_FOLDED, WILL_NOT_SLEEP) + } + + private fun setGoToSleepStates(vararg states: Int) { + mContext.orCreateTestableResources.addOverride( + R.array.config_deviceStatesOnWhichToSleep, + states + ) + } + + private fun setFoldedStates(vararg states: Int) { + mContext.orCreateTestableResources.addOverride( + R.array.config_foldedDeviceStates, + states + ) + } + + companion object { + private const val DEVICE_STATE_FOLDED = 123 + private const val DEVICE_STATE_HALF_FOLDED = 456 + private const val DEVICE_STATE_UNFOLDED = 789 + + private const val FOLDED = true + private const val NOT_FOLDED = false + + private const val WILL_GO_TO_SLEEP = true + private const val WILL_NOT_SLEEP = false + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java index 69f2bd788e03..bb8bad39ab31 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java @@ -44,6 +44,7 @@ import android.app.trust.TrustManager; import android.content.BroadcastReceiver; import android.content.ContentResolver; import android.content.IntentFilter; +import android.hardware.devicestate.DeviceStateManager; import android.hardware.display.AmbientDisplayConfiguration; import android.hardware.fingerprint.FingerprintManager; import android.metrics.LogMaker; @@ -162,6 +163,7 @@ import com.android.wm.shell.startingsurface.StartingSurface; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; @@ -176,6 +178,9 @@ import dagger.Lazy; @RunWithLooper(setAsMainLooper = true) public class StatusBarTest extends SysuiTestCase { + private static final int FOLD_STATE_FOLDED = 0; + private static final int FOLD_STATE_UNFOLDED = 1; + private StatusBar mStatusBar; private FakeMetricsLogger mMetricsLogger; private PowerManager mPowerManager; @@ -281,6 +286,7 @@ public class StatusBarTest extends SysuiTestCase { @Mock private NotifPipelineFlags mNotifPipelineFlags; @Mock private NotifLiveDataStore mNotifLiveDataStore; @Mock private InteractionJankMonitor mJankMonitor; + @Mock private DeviceStateManager mDeviceStateManager; private ShadeController mShadeController; private final FakeSystemClock mFakeSystemClock = new FakeSystemClock(); private FakeExecutor mMainExecutor = new FakeExecutor(mFakeSystemClock); @@ -466,7 +472,8 @@ public class StatusBarTest extends SysuiTestCase { Optional.of(mStartingSurface), mActivityLaunchAnimator, mNotifPipelineFlags, - mJankMonitor); + mJankMonitor, + mDeviceStateManager); when(mKeyguardViewMediator.registerStatusBar( any(StatusBar.class), any(NotificationPanelViewController.class), @@ -949,6 +956,47 @@ public class StatusBarTest extends SysuiTestCase { verify(mStatusBarKeyguardViewManager).updateResources(); } + @Test + public void deviceStateChange_unfolded_shadeOpen_setsLeaveOpenOnKeyguardHide() { + setFoldedStates(FOLD_STATE_FOLDED); + setGoToSleepStates(FOLD_STATE_FOLDED); + when(mNotificationPanelViewController.isFullyExpanded()).thenReturn(true); + + setDeviceState(FOLD_STATE_UNFOLDED); + + verify(mStatusBarStateController).setLeaveOpenOnKeyguardHide(true); + } + + @Test + public void deviceStateChange_unfolded_shadeClose_doesNotSetLeaveOpenOnKeyguardHide() { + setFoldedStates(FOLD_STATE_FOLDED); + setGoToSleepStates(FOLD_STATE_FOLDED); + when(mNotificationPanelViewController.isFullyExpanded()).thenReturn(false); + + setDeviceState(FOLD_STATE_UNFOLDED); + + verify(mStatusBarStateController, never()).setLeaveOpenOnKeyguardHide(true); + } + + private void setDeviceState(int state) { + ArgumentCaptor<DeviceStateManager.DeviceStateCallback> callbackCaptor = + ArgumentCaptor.forClass(DeviceStateManager.DeviceStateCallback.class); + verify(mDeviceStateManager).registerCallback(any(), callbackCaptor.capture()); + callbackCaptor.getValue().onStateChanged(state); + } + + private void setGoToSleepStates(int... states) { + mContext.getOrCreateTestableResources().addOverride( + com.android.internal.R.array.config_deviceStatesOnWhichToSleep, + states); + } + + private void setFoldedStates(int... states) { + mContext.getOrCreateTestableResources().addOverride( + com.android.internal.R.array.config_foldedDeviceStates, + states); + } + public static class TestableNotificationInterruptStateProviderImpl extends NotificationInterruptStateProviderImpl { |