Merge "Preserve shade state on fold/unfold."

This commit is contained in:
Christian Göllner
2022-01-06 16:52:59 +00:00
committed by Android (Google) Code Review
7 changed files with 278 additions and 4 deletions

View File

@@ -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)
}
}

View File

@@ -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.
*

View File

@@ -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(

View File

@@ -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
// ================================================================================

View File

@@ -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
);
}
}

View File

@@ -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
}
}

View File

@@ -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 {