diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 15a800263cf5c..3017bbd7defc0 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -2444,6 +2444,8 @@ 900000 false + + false diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 153e499852ebc..6a9f7e84b2f5a 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -2235,6 +2235,7 @@ + diff --git a/services/core/java/com/android/server/power/AmbientDisplaySuppressionController.java b/services/core/java/com/android/server/power/AmbientDisplaySuppressionController.java index aad7b1457b3c2..7440fc75bf511 100644 --- a/services/core/java/com/android/server/power/AmbientDisplaySuppressionController.java +++ b/services/core/java/com/android/server/power/AmbientDisplaySuppressionController.java @@ -40,13 +40,24 @@ import java.util.Set; public class AmbientDisplaySuppressionController { private static final String TAG = "AmbientDisplaySuppressionController"; - private final Context mContext; private final Set> mSuppressionTokens; + private final AmbientDisplaySuppressionChangedCallback mCallback; private IStatusBarService mStatusBarService; - AmbientDisplaySuppressionController(Context context) { - mContext = requireNonNull(context); + /** Interface to get a list of available logical devices. */ + interface AmbientDisplaySuppressionChangedCallback { + /** + * Called when the suppression state changes. + * + * @param isSuppressed Whether ambient is suppressed. + */ + void onSuppressionChanged(boolean isSuppressed); + } + + AmbientDisplaySuppressionController( + @NonNull AmbientDisplaySuppressionChangedCallback callback) { mSuppressionTokens = Collections.synchronizedSet(new ArraySet<>()); + mCallback = requireNonNull(callback); } /** @@ -58,6 +69,7 @@ public class AmbientDisplaySuppressionController { */ public void suppress(@NonNull String token, int callingUid, boolean suppress) { Pair suppressionToken = Pair.create(requireNonNull(token), callingUid); + final boolean wasSuppressed = isSuppressed(); if (suppress) { mSuppressionTokens.add(suppressionToken); @@ -65,9 +77,14 @@ public class AmbientDisplaySuppressionController { mSuppressionTokens.remove(suppressionToken); } + final boolean isSuppressed = isSuppressed(); + if (isSuppressed != wasSuppressed) { + mCallback.onSuppressionChanged(isSuppressed); + } + try { synchronized (mSuppressionTokens) { - getStatusBar().suppressAmbientDisplay(isSuppressed()); + getStatusBar().suppressAmbientDisplay(isSuppressed); } } catch (RemoteException e) { Slog.e(TAG, "Failed to suppress ambient display", e); diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java index 772a94ab877f7..5abc875a697ff 100644 --- a/services/core/java/com/android/server/power/PowerManagerService.java +++ b/services/core/java/com/android/server/power/PowerManagerService.java @@ -131,6 +131,7 @@ import com.android.server.compat.PlatformCompat; import com.android.server.lights.LightsManager; import com.android.server.lights.LogicalLight; import com.android.server.policy.WindowManagerPolicy; +import com.android.server.power.AmbientDisplaySuppressionController.AmbientDisplaySuppressionChangedCallback; import com.android.server.power.batterysaver.BatterySaverController; import com.android.server.power.batterysaver.BatterySaverPolicy; import com.android.server.power.batterysaver.BatterySaverStateMachine; @@ -527,6 +528,9 @@ public final class PowerManagerService extends SystemService // effectively and terminate the dream. Use -1 to disable this safety feature. private int mDreamsBatteryLevelDrainCutoffConfig; + // Whether dreams should be disabled when ambient mode is suppressed. + private boolean mDreamsDisabledByAmbientModeSuppressionConfig; + // True if dreams are enabled by the user. private boolean mDreamsEnabledSetting; @@ -989,8 +993,8 @@ public final class PowerManagerService extends SystemService } AmbientDisplaySuppressionController createAmbientDisplaySuppressionController( - Context context) { - return new AmbientDisplaySuppressionController(context); + @NonNull AmbientDisplaySuppressionChangedCallback callback) { + return new AmbientDisplaySuppressionController(callback); } InattentiveSleepWarningController createInattentiveSleepWarningController() { @@ -1089,7 +1093,8 @@ public final class PowerManagerService extends SystemService mConstants = new Constants(mHandler); mAmbientDisplayConfiguration = mInjector.createAmbientDisplayConfiguration(context); mAmbientDisplaySuppressionController = - mInjector.createAmbientDisplaySuppressionController(context); + mInjector.createAmbientDisplaySuppressionController( + mAmbientSuppressionChangedCallback); mAttentionDetector = new AttentionDetector(this::onUserAttention, mLock); mFaceDownDetector = new FaceDownDetector(this::onFlip); mScreenUndimDetector = new ScreenUndimDetector(); @@ -1450,6 +1455,8 @@ public final class PowerManagerService extends SystemService com.android.internal.R.integer.config_dreamsBatteryLevelMinimumWhenNotPowered); mDreamsBatteryLevelDrainCutoffConfig = resources.getInteger( com.android.internal.R.integer.config_dreamsBatteryLevelDrainCutoff); + mDreamsDisabledByAmbientModeSuppressionConfig = resources.getBoolean( + com.android.internal.R.bool.config_dreamsDisabledByAmbientModeSuppressionConfig); mDozeAfterScreenOff = resources.getBoolean( com.android.internal.R.bool.config_dozeAfterScreenOffByDefault); mMinimumScreenOffTimeoutConfig = resources.getInteger( @@ -3381,7 +3388,7 @@ public final class PowerManagerService extends SystemService } // Doze has ended or will be stopped. Update the power state. - sleepPowerGroupLocked(powerGroup, now, PowerManager.GO_TO_SLEEP_REASON_TIMEOUT, + sleepPowerGroupLocked(powerGroup, now, PowerManager.GO_TO_SLEEP_REASON_TIMEOUT, Process.SYSTEM_UID); } } @@ -3392,12 +3399,32 @@ public final class PowerManagerService extends SystemService } } + @GuardedBy("mLock") + private void onDreamSuppressionChangedLocked(final boolean isSuppressed) { + if (!mDreamsDisabledByAmbientModeSuppressionConfig) { + return; + } + if (!isSuppressed && mIsPowered && mDreamsSupportedConfig && mDreamsEnabledSetting + && shouldNapAtBedTimeLocked() && isItBedTimeYetLocked( + mPowerGroups.get(Display.DEFAULT_DISPLAY_GROUP))) { + napInternal(SystemClock.uptimeMillis(), Process.SYSTEM_UID, /* allowWake= */ true); + } else if (isSuppressed) { + mDirty |= DIRTY_SETTINGS; + updatePowerStateLocked(); + } + } + + /** * Returns true if the {@code groupId} is allowed to dream in its current state. */ @GuardedBy("mLock") private boolean canDreamLocked(final PowerGroup powerGroup) { + final boolean dreamsSuppressed = mDreamsDisabledByAmbientModeSuppressionConfig + && mAmbientDisplaySuppressionController.isSuppressed(); + if (!mBootCompleted + || dreamsSuppressed || getGlobalWakefulnessLocked() != WAKEFULNESS_DREAMING || !mDreamsSupportedConfig || !mDreamsEnabledSetting @@ -5093,6 +5120,16 @@ public final class PowerManagerService extends SystemService } }; + private final AmbientDisplaySuppressionChangedCallback mAmbientSuppressionChangedCallback = + new AmbientDisplaySuppressionChangedCallback() { + @Override + public void onSuppressionChanged(boolean isSuppressed) { + synchronized (mLock) { + onDreamSuppressionChangedLocked(isSuppressed); + } + } + }; + /** * Callback for asynchronous operations performed by the power manager. */ diff --git a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java index f5905a756fc0e..fe4db3a758e3f 100644 --- a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java @@ -414,6 +414,12 @@ public class PowerManagerServiceTest { .thenReturn(minimumScreenOffTimeoutConfigMillis); } + private void setDreamsDisabledByAmbientModeSuppressionConfig(boolean disable) { + when(mResourcesSpy.getBoolean( + com.android.internal.R.bool.config_dreamsDisabledByAmbientModeSuppressionConfig)) + .thenReturn(disable); + } + private void advanceTime(long timeMs) { mClock.fastForward(timeMs); mTestLooper.dispatchAll(); @@ -859,6 +865,91 @@ public class PowerManagerServiceTest { assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DREAMING); } + @SuppressWarnings("GuardedBy") + @Test + public void testAmbientSuppression_disablesDreamingAndWakesDevice() { + Settings.Secure.putInt(mContextSpy.getContentResolver(), + Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP, 1); + Settings.Secure.putInt(mContextSpy.getContentResolver(), + Settings.Secure.SCREENSAVER_ENABLED, 1); + + setDreamsDisabledByAmbientModeSuppressionConfig(true); + setMinimumScreenOffTimeoutConfig(10000); + createService(); + startSystem(); + + doAnswer(inv -> { + when(mDreamManagerInternalMock.isDreaming()).thenReturn(true); + return null; + }).when(mDreamManagerInternalMock).startDream(anyBoolean(), anyString()); + + setPluggedIn(true); + // Allow asynchronous sandman calls to execute. + advanceTime(10000); + + forceDream(); + assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DREAMING); + mService.getBinderServiceInstance().suppressAmbientDisplay("test", true); + advanceTime(50); + assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE); + } + + @SuppressWarnings("GuardedBy") + @Test + public void testAmbientSuppressionDisabled_shouldNotWakeDevice() { + Settings.Secure.putInt(mContextSpy.getContentResolver(), + Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP, 1); + Settings.Secure.putInt(mContextSpy.getContentResolver(), + Settings.Secure.SCREENSAVER_ENABLED, 1); + + setDreamsDisabledByAmbientModeSuppressionConfig(false); + setMinimumScreenOffTimeoutConfig(10000); + createService(); + startSystem(); + + doAnswer(inv -> { + when(mDreamManagerInternalMock.isDreaming()).thenReturn(true); + return null; + }).when(mDreamManagerInternalMock).startDream(anyBoolean(), anyString()); + + setPluggedIn(true); + // Allow asynchronous sandman calls to execute. + advanceTime(10000); + + forceDream(); + assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DREAMING); + mService.getBinderServiceInstance().suppressAmbientDisplay("test", true); + advanceTime(50); + assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DREAMING); + } + + @Test + public void testAmbientSuppression_doesNotAffectDreamForcing() { + Settings.Secure.putInt(mContextSpy.getContentResolver(), + Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP, 1); + Settings.Secure.putInt(mContextSpy.getContentResolver(), + Settings.Secure.SCREENSAVER_ENABLED, 1); + + setDreamsDisabledByAmbientModeSuppressionConfig(true); + setMinimumScreenOffTimeoutConfig(10000); + createService(); + startSystem(); + + doAnswer(inv -> { + when(mDreamManagerInternalMock.isDreaming()).thenReturn(true); + return null; + }).when(mDreamManagerInternalMock).startDream(anyBoolean(), anyString()); + + mService.getBinderServiceInstance().suppressAmbientDisplay("test", true); + setPluggedIn(true); + // Allow asynchronous sandman calls to execute. + advanceTime(10000); + + // Verify that forcing dream still works even though ambient display is suppressed + forceDream(); + assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DREAMING); + } + @Test public void testSetDozeOverrideFromDreamManager_triggersSuspendBlocker() { final String suspendBlockerName = "PowerManagerService.Display";