From 4f7ca41f6f2d9b2fc8a306e4a28da25ecfbec8d9 Mon Sep 17 00:00:00 2001 From: Santos Cordon Date: Thu, 14 Oct 2021 21:42:41 +0100 Subject: [PATCH] Go to sleep on fold for foldable devices. Allow configuration of foldable states to indicate the device should sleep when entering certain states. Test: Check that device still wakes up on unfold, and sleeps on fold. Bug: 180154690 Change-Id: I179d93ec506deee7e9da7d9190934c52a0bc188f --- .../display/DisplayManagerInternal.java | 7 ++ core/java/android/os/PowerManager.java | 12 ++- core/res/res/values/config.xml | 21 +++- core/res/res/values/symbols.xml | 3 +- .../server/display/DisplayManagerService.java | 6 ++ .../server/display/LogicalDisplayMapper.java | 102 ++++++++++++++---- .../com/android/server/power/Notifier.java | 13 ++- .../display/LogicalDisplayMapperTest.java | 12 ++- 8 files changed, 143 insertions(+), 33 deletions(-) diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java index 5bb51c19342da..fd34fa4c9c7f9 100644 --- a/core/java/android/hardware/display/DisplayManagerInternal.java +++ b/core/java/android/hardware/display/DisplayManagerInternal.java @@ -371,6 +371,13 @@ public abstract class DisplayManagerInternal { */ public abstract Point getDisplaySurfaceDefaultSize(int displayId); + /** + * Receives early interactivity changes from power manager. + * + * @param interactive The interactive state that the device is moving into. + */ + public abstract void onEarlyInteractivityChange(boolean interactive); + /** * Describes the requested power state of the display. * diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java index 753f3498a209b..74fffd0ae10dd 100644 --- a/core/java/android/os/PowerManager.java +++ b/core/java/android/os/PowerManager.java @@ -440,9 +440,15 @@ public final class PowerManager { public static final int GO_TO_SLEEP_REASON_DISPLAY_GROUPS_TURNED_OFF = 12; /** + * Go to sleep reason code: A foldable device has been folded. * @hide */ - public static final int GO_TO_SLEEP_REASON_MAX = GO_TO_SLEEP_REASON_DISPLAY_GROUPS_TURNED_OFF; + public static final int GO_TO_SLEEP_REASON_DEVICE_FOLD = 13; + + /** + * @hide + */ + public static final int GO_TO_SLEEP_REASON_MAX = GO_TO_SLEEP_REASON_DEVICE_FOLD; /** * @hide @@ -461,6 +467,7 @@ public final class PowerManager { case GO_TO_SLEEP_REASON_INATTENTIVE: return "inattentive"; case GO_TO_SLEEP_REASON_DISPLAY_GROUP_REMOVED: return "display_group_removed"; case GO_TO_SLEEP_REASON_DISPLAY_GROUPS_TURNED_OFF: return "display_groups_turned_off"; + case GO_TO_SLEEP_REASON_DEVICE_FOLD: return "device_folded"; default: return Integer.toString(sleepReason); } } @@ -568,7 +575,8 @@ public final class PowerManager { GO_TO_SLEEP_REASON_ACCESSIBILITY, GO_TO_SLEEP_REASON_FORCE_SUSPEND, GO_TO_SLEEP_REASON_INATTENTIVE, - GO_TO_SLEEP_REASON_QUIESCENT + GO_TO_SLEEP_REASON_QUIESCENT, + GO_TO_SLEEP_REASON_DEVICE_FOLD }) @Retention(RetentionPolicy.SOURCE) public @interface GoToSleepReason{} diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index e1b1e6129477a..4a8462dd12ed8 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -654,10 +654,23 @@ --> - - -1 + + + + + + + + + diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 5208c4a9230db..b0e4b1ff93d19 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -3856,7 +3856,8 @@ - + + diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index d35fc19385047..6e3078de224b1 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -524,6 +524,7 @@ public final class DisplayManagerService extends SystemService { } } else if (phase == PHASE_BOOT_COMPLETED) { mDisplayModeDirector.onBootCompleted(); + mLogicalDisplayMapper.onBootCompleted(); } } @@ -3601,6 +3602,11 @@ public final class DisplayManagerService extends SystemService { } return device.getDisplaySurfaceDefaultSize(); } + + @Override + public void onEarlyInteractivityChange(boolean interactive) { + mLogicalDisplayMapper.onEarlyInteractivityChange(interactive); + } } class DesiredDisplayModeSpecsObserver diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java index 0fbc3e8fb89a6..7719dfec928bd 100644 --- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java +++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java @@ -30,6 +30,7 @@ import android.util.ArraySet; import android.util.IndentingPrintWriter; import android.util.Slog; import android.util.SparseArray; +import android.util.SparseBooleanArray; import android.util.SparseIntArray; import android.view.Display; import android.view.DisplayAddress; @@ -71,7 +72,7 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { public static final int DISPLAY_GROUP_EVENT_CHANGED = 2; public static final int DISPLAY_GROUP_EVENT_REMOVED = 3; - private static final int TIMEOUT_STATE_TRANSITION_MILLIS = 300; + private static final int TIMEOUT_STATE_TRANSITION_MILLIS = 500; private static final int MSG_TRANSITION_TO_PENDING_DEVICE_STATE = 1; @@ -100,9 +101,14 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { private final boolean mSupportsConcurrentInternalDisplays; /** - * Wake the device when transitioning into this device state. + * Wake the device when transitioning into these device state. */ - private final int mDeviceStateOnWhichToWakeUp; + private final SparseBooleanArray mDeviceStatesOnWhichToWakeUp; + + /** + * Sleep the device when transitioning into these device state. + */ + private final SparseBooleanArray mDeviceStatesOnWhichToSleep; /** * Map of all logical displays indexed by logical display id. @@ -153,20 +159,25 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { private Layout mCurrentLayout = null; private int mDeviceState = DeviceStateManager.INVALID_DEVICE_STATE; private int mPendingDeviceState = DeviceStateManager.INVALID_DEVICE_STATE; + private boolean mBootCompleted = false; + private boolean mInteractive; LogicalDisplayMapper(@NonNull Context context, @NonNull DisplayDeviceRepository repo, @NonNull Listener listener, @NonNull DisplayManagerService.SyncRoot syncRoot, @NonNull Handler handler) { mSyncRoot = syncRoot; mPowerManager = context.getSystemService(PowerManager.class); + mInteractive = mPowerManager.isInteractive(); mHandler = new LogicalDisplayMapperHandler(handler.getLooper()); mDisplayDeviceRepo = repo; mListener = listener; mSingleDisplayDemoMode = SystemProperties.getBoolean("persist.demo.singledisplay", false); mSupportsConcurrentInternalDisplays = context.getResources().getBoolean( com.android.internal.R.bool.config_supportsConcurrentInternalDisplays); - mDeviceStateOnWhichToWakeUp = context.getResources().getInteger( - com.android.internal.R.integer.config_deviceStateOnWhichToWakeUp); + mDeviceStatesOnWhichToWakeUp = toSparseBooleanArray(context.getResources().getIntArray( + com.android.internal.R.array.config_deviceStatesOnWhichToWakeUp)); + mDeviceStatesOnWhichToSleep = toSparseBooleanArray(context.getResources().getIntArray( + com.android.internal.R.array.config_deviceStatesOnWhichToSleep)); mDisplayDeviceRepo.addListener(this); mDeviceStateToLayoutMap = new DeviceStateToLayoutMap(); } @@ -329,7 +340,9 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { ipw.println("mSingleDisplayDemoMode=" + mSingleDisplayDemoMode); ipw.println("mCurrentLayout=" + mCurrentLayout); - ipw.println("mDeviceStateOnWhichToWakeUp=" + mDeviceStateOnWhichToWakeUp); + ipw.println("mDeviceStatesOnWhichToWakeUp=" + mDeviceStatesOnWhichToWakeUp); + ipw.println("mDeviceStatesOnWhichToSleep=" + mDeviceStatesOnWhichToSleep); + ipw.println("mInteractive=" + mInteractive); final int logicalDisplayCount = mLogicalDisplays.size(); ipw.println(); @@ -347,9 +360,8 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { } void setDeviceStateLocked(int state) { - final boolean isInteractive = mPowerManager.isInteractive(); Slog.i(TAG, "Requesting Transition to state: " + state + ", from state=" + mDeviceState - + ", interactive=" + isInteractive); + + ", interactive=" + mInteractive); // As part of a state transition, we may need to turn off some displays temporarily so that // the transition is smooth. Plus, on some devices, only one internal displays can be // on at a time. We use DISPLAY_PHASE_LAYOUT_TRANSITION to mark a display that needs to be @@ -358,13 +370,17 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { resetLayoutLocked(mDeviceState, state, LogicalDisplay.DISPLAY_PHASE_LAYOUT_TRANSITION); } mPendingDeviceState = state; - final boolean wakeDevice = mPendingDeviceState == mDeviceStateOnWhichToWakeUp - && !isInteractive; + final boolean wakeDevice = mDeviceStatesOnWhichToWakeUp.get(mPendingDeviceState) + && !mDeviceStatesOnWhichToWakeUp.get(mDeviceState) + && !mInteractive && mBootCompleted; + final boolean sleepDevice = mDeviceStatesOnWhichToSleep.get(mPendingDeviceState) + && !mDeviceStatesOnWhichToSleep.get(mDeviceState) + && mInteractive && mBootCompleted; - // If all displays are off already, we can just transition here, unless the device is asleep - // and we plan on waking it up. In that case, fall through to the call to wakeUp, and defer - // the final transition until later once the device is awake. - if (areAllTransitioningDisplaysOffLocked() && !wakeDevice) { + // If all displays are off already, we can just transition here, unless we are trying to + // wake or sleep the device as part of this transition. In that case defer the final + // transition until later once the device is awake/asleep. + if (areAllTransitioningDisplaysOffLocked() && !wakeDevice && !sleepDevice) { transitionToPendingStateLocked(); return; } @@ -376,17 +392,39 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { // start turning OFF in preparation for the new layout. updateLogicalDisplaysLocked(); - if (wakeDevice) { - // We already told the displays to turn off, now we need to wake the device as - // we transition to this new state. We do it here so that the waking happens between the - // transition from one layout to another. - mPowerManager.wakeUp(SystemClock.uptimeMillis(), PowerManager.WAKE_REASON_UNFOLD_DEVICE, - "server.display:unfold"); + if (wakeDevice || sleepDevice) { + if (wakeDevice) { + // We already told the displays to turn off, now we need to wake the device as + // we transition to this new state. We do it here so that the waking happens + // between the transition from one layout to another. + mPowerManager.wakeUp(SystemClock.uptimeMillis(), + PowerManager.WAKE_REASON_UNFOLD_DEVICE, "server.display:unfold"); + } else if (sleepDevice) { + // Send the device to sleep when required. + mPowerManager.goToSleep(SystemClock.uptimeMillis(), + PowerManager.GO_TO_SLEEP_REASON_DEVICE_FOLD, 0); + } } + mHandler.sendEmptyMessageDelayed(MSG_TRANSITION_TO_PENDING_DEVICE_STATE, TIMEOUT_STATE_TRANSITION_MILLIS); } + void onBootCompleted() { + synchronized (mSyncRoot) { + mBootCompleted = true; + } + } + + void onEarlyInteractivityChange(boolean interactive) { + synchronized (mSyncRoot) { + if (mInteractive != interactive) { + mInteractive = interactive; + finishStateTransitionLocked(false /*force*/); + } + } + } + private boolean areAllTransitioningDisplaysOffLocked() { final int count = mLogicalDisplays.size(); for (int i = 0; i < count; i++) { @@ -419,13 +457,24 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { return; } + final boolean waitingToWakeDevice = mDeviceStatesOnWhichToWakeUp.get(mPendingDeviceState) + && !mDeviceStatesOnWhichToWakeUp.get(mDeviceState) + && !mInteractive && mBootCompleted; + final boolean waitingToSleepDevice = mDeviceStatesOnWhichToSleep.get(mPendingDeviceState) + && !mDeviceStatesOnWhichToSleep.get(mDeviceState) + && mInteractive && mBootCompleted; + final boolean displaysOff = areAllTransitioningDisplaysOffLocked(); - if (displaysOff || force) { + final boolean isReadyToTransition = displaysOff && !waitingToWakeDevice + && !waitingToSleepDevice; + + if (isReadyToTransition || force) { transitionToPendingStateLocked(); mHandler.removeMessages(MSG_TRANSITION_TO_PENDING_DEVICE_STATE); } else if (DEBUG) { Slog.d(TAG, "Not yet ready to transition to state=" + mPendingDeviceState - + " with displays-off=" + displaysOff + " and force=" + force); + + " with displays-off=" + displaysOff + ", force=" + force + + ", mInteractive=" + mInteractive + ", isReady=" + isReadyToTransition); } } @@ -821,6 +870,14 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { return displayId; } + private SparseBooleanArray toSparseBooleanArray(int[] input) { + final SparseBooleanArray retval = new SparseBooleanArray(2); + for (int i = 0; input != null && i < input.length; i++) { + retval.put(input[i], true); + } + return retval; + } + private String displayEventToString(int msg) { switch(msg) { case LOGICAL_DISPLAY_EVENT_ADDED: @@ -861,5 +918,4 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { } } } - } diff --git a/services/core/java/com/android/server/power/Notifier.java b/services/core/java/com/android/server/power/Notifier.java index c91d8dedc41b3..14b2eab046d42 100644 --- a/services/core/java/com/android/server/power/Notifier.java +++ b/services/core/java/com/android/server/power/Notifier.java @@ -24,6 +24,7 @@ import android.app.trust.TrustManager; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; +import android.hardware.display.DisplayManagerInternal; import android.hardware.input.InputManagerInternal; import android.media.AudioAttributes; import android.media.AudioManager; @@ -129,6 +130,7 @@ public class Notifier { private final TrustManager mTrustManager; private final Vibrator mVibrator; private final WakeLockLog mWakeLockLog; + private final DisplayManagerInternal mDisplayManagerInternal; private final NotifierHandler mHandler; private final Intent mScreenOnIntent; @@ -181,6 +183,7 @@ public class Notifier { mInputManagerInternal = LocalServices.getService(InputManagerInternal.class); mInputMethodManagerInternal = LocalServices.getService(InputMethodManagerInternal.class); mStatusBarManagerInternal = LocalServices.getService(StatusBarManagerInternal.class); + mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class); mTrustManager = mContext.getSystemService(TrustManager.class); mVibrator = mContext.getSystemService(Vibrator.class); @@ -460,7 +463,10 @@ public class Notifier { synchronized (mLock) { if (mInteractive) { // Waking up... - mHandler.post(() -> mPolicy.startedWakingUp(mInteractiveChangeReason)); + mHandler.post(() -> { + mPolicy.startedWakingUp(mInteractiveChangeReason); + mDisplayManagerInternal.onEarlyInteractivityChange(true /*isInteractive*/); + }); // Send interactive broadcast. mPendingInteractiveState = INTERACTIVE_STATE_AWAKE; @@ -469,7 +475,10 @@ public class Notifier { } else { // Going to sleep... // Tell the policy that we started going to sleep. - mHandler.post(() -> mPolicy.startedGoingToSleep(mInteractiveChangeReason)); + mHandler.post(() -> { + mPolicy.startedGoingToSleep(mInteractiveChangeReason); + mDisplayManagerInternal.onEarlyInteractivityChange(false /*isInteractive*/); + }); } } } diff --git a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java index fbc1952b0fafc..fefc425496884 100644 --- a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java +++ b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java @@ -38,7 +38,10 @@ import android.app.PropertyInvalidatedCache; import android.content.Context; import android.content.res.Resources; import android.os.Handler; +import android.os.IPowerManager; +import android.os.IThermalService; import android.os.Parcel; +import android.os.PowerManager; import android.os.Process; import android.os.test.TestLooper; import android.platform.test.annotations.Presubmit; @@ -72,10 +75,13 @@ public class LogicalDisplayMapperTest { private LogicalDisplayMapper mLogicalDisplayMapper; private TestLooper mLooper; private Handler mHandler; + private PowerManager mPowerManager; @Mock LogicalDisplayMapper.Listener mListenerMock; @Mock Context mContextMock; @Mock Resources mResourcesMock; + @Mock IPowerManager mIPowerManagerMock; + @Mock IThermalService mIThermalServiceMock; @Captor ArgumentCaptor mDisplayCaptor; @@ -105,6 +111,11 @@ public class LogicalDisplayMapperTest { // Disable binder caches in this process. PropertyInvalidatedCache.disableForTestMode(); + mPowerManager = new PowerManager(mContextMock, mIPowerManagerMock, mIThermalServiceMock, + null); + when(mContextMock.getSystemServiceName(PowerManager.class)) + .thenReturn(Context.POWER_SERVICE); + when(mContextMock.getSystemService(PowerManager.class)).thenReturn(mPowerManager); when(mContextMock.getResources()).thenReturn(mResourcesMock); when(mResourcesMock.getBoolean( com.android.internal.R.bool.config_supportsConcurrentInternalDisplays)) @@ -301,7 +312,6 @@ public class LogicalDisplayMapperTest { mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(id(display3))); } - ///////////////// // Helper Methods /////////////////