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
This commit is contained in:
Santos Cordon
2021-10-14 21:42:41 +01:00
parent e43cf93ec4
commit 4f7ca41f6f
8 changed files with 143 additions and 33 deletions

View File

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

View File

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

View File

@@ -654,10 +654,23 @@
-->
</integer-array>
<!-- When entering this device state (defined in device_state_configuration.xml),
we should wake the device. -1 to disable the feature (do not wake on any device-state
transition). -->
<integer name="config_deviceStateOnWhichToWakeUp">-1</integer>
<!-- When a device enters any of these states, it should be woken up. States are defined in
device_state_configuration.xml. -->
<integer-array name="config_deviceStatesOnWhichToWakeUp">
<!-- Example:
<item>0</item>
<item>1</item>
-->
</integer-array>
<!-- When a device enters any of these states, it should go to sleep. States are defined in
device_state_configuration.xml. -->
<integer-array name="config_deviceStatesOnWhichToSleep">
<!-- Example:
<item>0</item>
<item>1</item>
-->
</integer-array>
<!-- Indicate the display area rect for foldable devices in folded state. -->
<string name="config_foldedArea"></string>

View File

@@ -3856,7 +3856,8 @@
<!-- For Foldables -->
<java-symbol type="array" name="config_foldedDeviceStates" />
<java-symbol type="integer" name="config_deviceStateOnWhichToWakeUp" />
<java-symbol type="array" name="config_deviceStatesOnWhichToWakeUp" />
<java-symbol type="array" name="config_deviceStatesOnWhichToSleep" />
<java-symbol type="string" name="config_foldedArea" />
<java-symbol type="bool" name="config_supportsConcurrentInternalDisplays" />
<java-symbol type="bool" name="config_unfoldTransitionEnabled" />

View File

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

View File

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

View File

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

View File

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