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 81fada3f17c8e..53ed0ec496d46 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 79b773eb6b77b..e4bed3d067143 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();
}
}
@@ -3618,6 +3619,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
/////////////////