Merge "Preserve shade state on fold/unfold."
This commit is contained in:
committed by
Android (Google) Code Review
commit
51a1afe4e6
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -147,6 +147,13 @@ public class ShadeControllerImpl implements ShadeController {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isShadeOpen() {
|
||||
NotificationPanelViewController controller =
|
||||
getNotificationPanelViewController();
|
||||
return controller.isExpanding() || controller.isFullyExpanded();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postOnShadeExpanded(Runnable executable) {
|
||||
getNotificationPanelViewController().addOnGlobalLayoutListener(
|
||||
|
||||
@@ -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
|
||||
// ================================================================================
|
||||
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
|
||||
Reference in New Issue
Block a user