diff options
11 files changed, 722 insertions, 3 deletions
diff --git a/core/java/Android.bp b/core/java/Android.bp index ce767f46dd50..1e97d4f12836 100644 --- a/core/java/Android.bp +++ b/core/java/Android.bp @@ -223,6 +223,7 @@ filegroup { "android/os/IThermalService.aidl", "android/os/IPowerManager.aidl", "android/os/IWakeLockCallback.aidl", + "android/os/IScreenTimeoutPolicyListener.aidl", ], } diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java index 68b6cfc012fc..d273ddb15cc4 100644 --- a/core/java/android/hardware/display/DisplayManagerInternal.java +++ b/core/java/android/hardware/display/DisplayManagerInternal.java @@ -449,6 +449,11 @@ public abstract class DisplayManagerInternal { public abstract IntArray getDisplayIds(); /** + * Get group id for given display id + */ + public abstract int getGroupIdForDisplay(int displayId); + + /** * Called upon presentation started/ended on the display. * @param displayId the id of the display where presentation started. * @param isShown whether presentation is shown. diff --git a/core/java/android/os/IPowerManager.aidl b/core/java/android/os/IPowerManager.aidl index 4cac4dee0bea..17697e67c757 100644 --- a/core/java/android/os/IPowerManager.aidl +++ b/core/java/android/os/IPowerManager.aidl @@ -22,6 +22,7 @@ import android.os.ParcelDuration; import android.os.PowerSaveState; import android.os.WorkSource; import android.os.IWakeLockCallback; +import android.os.IScreenTimeoutPolicyListener; /** @hide */ @@ -45,6 +46,10 @@ interface IPowerManager @UnsupportedAppUsage boolean isWakeLockLevelSupported(int level); boolean isWakeLockLevelSupportedWithDisplayId(int level, int displayId); + oneway void addScreenTimeoutPolicyListener(int displayId, + IScreenTimeoutPolicyListener listener); + oneway void removeScreenTimeoutPolicyListener(int displayId, + IScreenTimeoutPolicyListener listener); void userActivity(int displayId, long time, int event, int flags); void wakeUp(long time, int reason, String details, String opPackageName); diff --git a/core/java/android/os/IScreenTimeoutPolicyListener.aidl b/core/java/android/os/IScreenTimeoutPolicyListener.aidl new file mode 100644 index 000000000000..62811a0cb436 --- /dev/null +++ b/core/java/android/os/IScreenTimeoutPolicyListener.aidl @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2024 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 android.os; + +/** + * Listener for screen timeout policy changes + * @see PowerManager#addScreenTimeoutPolicyListener(int, IScreenTimeoutPolicyListener) + * @hide + */ +oneway interface IScreenTimeoutPolicyListener { + /** + * @see PowerManager#addScreenTimeoutPolicyListener + */ + oneway void onScreenTimeoutPolicyChanged(int screenTimeoutPolicy); +} diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java index cd48f0847f8d..1801df048b3e 100644 --- a/core/java/android/os/PowerManager.java +++ b/core/java/android/os/PowerManager.java @@ -34,6 +34,7 @@ import android.annotation.TestApi; import android.app.PropertyInvalidatedCache; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; +import android.os.IScreenTimeoutPolicyListener; import android.service.dreams.Sandman; import android.util.ArrayMap; import android.util.ArraySet; @@ -1049,6 +1050,29 @@ public final class PowerManager { } /** + * Screen timeout policy type: the screen turns off after a timeout + * @hide + */ + public static final int SCREEN_TIMEOUT_ACTIVE = 0; + + /** + * Screen timeout policy type: the screen is kept 'on' (no timeout) + * @hide + */ + public static final int SCREEN_TIMEOUT_KEEP_DISPLAY_ON = 1; + + /** + * @hide + */ + @IntDef(prefix = { "SCREEN_TIMEOUT_" }, value = { + SCREEN_TIMEOUT_ACTIVE, + SCREEN_TIMEOUT_KEEP_DISPLAY_ON + }) + @Retention(RetentionPolicy.SOURCE) + public @interface ScreenTimeoutPolicy{} + + + /** * Either the location providers shouldn't be affected by battery saver, * or battery saver is off. */ @@ -1208,6 +1232,9 @@ public final class PowerManager { private final ArrayMap<OnThermalHeadroomChangedListener, IThermalHeadroomListener> mThermalHeadroomListenerMap = new ArrayMap<>(); + private final ArrayMap<ScreenTimeoutPolicyListener, IScreenTimeoutPolicyListener> + mScreenTimeoutPolicyListeners = new ArrayMap<>(); + /** * {@hide} */ @@ -1749,6 +1776,77 @@ public final class PowerManager { } } + /** + * Adds a listener to be notified about changes in screen timeout policy. + * + * <p>The screen timeout policy determines the behavior of the device's screen + * after a period of inactivity. It can be used to understand if the display is going + * to be turned off after a timeout to conserve power, or if it will be kept on indefinitely. + * For example, it might be useful for adjusting display switch conditions on foldable + * devices based on the current timeout policy. + * + * <p>See {@link ScreenTimeoutPolicy} for possible values. + * + * <p>The listener will be fired with the initial state upon subscribing. + * + * <p>IScreenTimeoutPolicyListener is called on either system server's main thread or + * on a binder thread if subscribed outside the system service process. + * + * @param displayId display id for which to be notified about screen timeout policy changes + * @param executor executor on which to execute ScreenTimeoutPolicyListener methods + * @param listener listener that will be fired on screem timeout policy updates + * @hide + */ + @RequiresPermission(android.Manifest.permission.DEVICE_POWER) + public void addScreenTimeoutPolicyListener(int displayId, + @NonNull @CallbackExecutor Executor executor, + @NonNull ScreenTimeoutPolicyListener listener) { + Objects.requireNonNull(listener, "listener cannot be null"); + Objects.requireNonNull(executor, "executor cannot be null"); + Preconditions.checkArgument(!mScreenTimeoutPolicyListeners.containsKey(listener), + "Listener already registered: %s", listener); + + final IScreenTimeoutPolicyListener stub = new IScreenTimeoutPolicyListener.Stub() { + public void onScreenTimeoutPolicyChanged(int screenTimeoutPolicy) { + final long token = Binder.clearCallingIdentity(); + try { + executor.execute(() -> + listener.onScreenTimeoutPolicyChanged(screenTimeoutPolicy)); + } finally { + Binder.restoreCallingIdentity(token); + } + } + }; + + try { + mService.addScreenTimeoutPolicyListener(displayId, stub); + mScreenTimeoutPolicyListeners.put(listener, stub); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Removes a listener that is used to listen for screen timeout policy changes. + * @see PowerManager#addScreenTimeoutPolicyListener(int, ScreenTimeoutPolicyListener) + * @param displayId display id for which to be notified about screen timeout changes + * @hide + */ + @RequiresPermission(android.Manifest.permission.DEVICE_POWER) + public void removeScreenTimeoutPolicyListener(int displayId, + @NonNull ScreenTimeoutPolicyListener listener) { + Objects.requireNonNull(listener, "listener cannot be null"); + IScreenTimeoutPolicyListener internalListener = mScreenTimeoutPolicyListeners.get(listener); + Preconditions.checkArgument(internalListener != null, "Listener was not added"); + + try { + mService.removeScreenTimeoutPolicyListener(displayId, internalListener); + mScreenTimeoutPolicyListeners.remove(listener); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + /** * Returns true if the specified wake lock level is supported. * @@ -3825,6 +3923,21 @@ public final class PowerManager { } /** + * Listener for screen timeout policy changes + * @see PowerManager#addScreenTimeoutPolicyListener(int, ScreenTimeoutPolicyListener) + * @hide + */ + public interface ScreenTimeoutPolicyListener { + /** + * Invoked on changes in screen timeout policy. + * + * @param screenTimeoutPolicy Screen timeout policy, one of {@link ScreenTimeoutPolicy} + * @see PowerManager#addScreenTimeoutPolicyListener + */ + void onScreenTimeoutPolicyChanged(@ScreenTimeoutPolicy int screenTimeoutPolicy); + } + + /** * A wake lock is a mechanism to indicate that your application needs * to have the device stay on. * <p> diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index f145a47eb935..c8192e534f5c 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -5845,6 +5845,15 @@ public final class DisplayManagerService extends SystemService { } @Override + public int getGroupIdForDisplay(int displayId) { + synchronized (mSyncRoot) { + final LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked(displayId); + if (display == null) return Display.INVALID_DISPLAY_GROUP; + return display.getDisplayInfoLocked().displayGroupId; + } + } + + @Override public DisplayManagerInternal.DisplayOffloadSession registerDisplayOffloader( int displayId, @NonNull DisplayManagerInternal.DisplayOffloader displayOffloader) { if (!mFlags.isDisplayOffloadEnabled()) { diff --git a/services/core/java/com/android/server/power/Notifier.java b/services/core/java/com/android/server/power/Notifier.java index 7f88e7463208..102dc071c7b6 100644 --- a/services/core/java/com/android/server/power/Notifier.java +++ b/services/core/java/com/android/server/power/Notifier.java @@ -37,11 +37,14 @@ import android.os.BatteryStatsInternal; import android.os.Bundle; import android.os.Handler; import android.os.IWakeLockCallback; +import android.os.IScreenTimeoutPolicyListener; import android.os.Looper; import android.os.Message; import android.os.PowerManager; +import android.os.PowerManager.ScreenTimeoutPolicy; import android.os.PowerManagerInternal; import android.os.Process; +import android.os.RemoteCallbackList; import android.os.RemoteException; import android.os.SystemClock; import android.os.UserHandle; @@ -52,6 +55,7 @@ import android.os.WorkSource; import android.os.WorkSource.WorkChain; import android.provider.Settings; import android.telephony.TelephonyManager; +import android.util.ArrayMap; import android.util.EventLog; import android.util.Slog; import android.util.SparseArray; @@ -152,6 +156,11 @@ public class Notifier { private final Intent mScreenOffIntent; private final Bundle mScreenOnOffOptions; + // Display id -> ScreenTimeoutPolicyListenersContainer that contains list of screen + // wake lock listeners + private final SparseArray<ScreenTimeoutPolicyListenersContainer> mScreenTimeoutPolicyListeners + = new SparseArray<>(); + // True if the device should suspend when the screen is off due to proximity. private final boolean mSuspendWhenScreenOffDueToProximityConfig; @@ -1270,6 +1279,146 @@ public class Notifier { } } + /** + * Adds a listener for the screen timeout policy + * @param displayId display ID + * @param screenTimeoutPolicy initial state of the timeout policy + * @param listener callback to receive screen timeout policy updates + */ + void addScreenTimeoutPolicyListener(int displayId, @ScreenTimeoutPolicy int screenTimeoutPolicy, + IScreenTimeoutPolicyListener listener) { + synchronized (mLock) { + ScreenTimeoutPolicyListenersContainer listenersContainer = + mScreenTimeoutPolicyListeners.get(displayId); + if (listenersContainer == null) { + listenersContainer = new ScreenTimeoutPolicyListenersContainer( + screenTimeoutPolicy); + mScreenTimeoutPolicyListeners.set(displayId, listenersContainer); + } + + listenersContainer.addListener(listener); + } + } + + /** + * Removes a listener for the screen timeout policy + * @param displayId display id from which the listener should be removed + * @param listener the instance of the listener + */ + void removeScreenTimeoutPolicyListener(int displayId, + IScreenTimeoutPolicyListener listener) { + synchronized (mLock) { + ScreenTimeoutPolicyListenersContainer listenersContainer = + mScreenTimeoutPolicyListeners.get(displayId); + if (listenersContainer == null) { + return; + } + + listenersContainer.removeListener(listener); + + if (listenersContainer.isEmpty()) { + mScreenTimeoutPolicyListeners.remove(displayId); + } + } + } + + /** + * Clears all screen timeout policy listeners for the specified display id + * @param displayId display id from which the listeners should be cleared + */ + void clearScreenTimeoutPolicyListeners(int displayId) { + synchronized (mLock) { + mScreenTimeoutPolicyListeners.remove(displayId); + } + } + + /** + * Notifies about screen timeout policy changes of the corresponding display group if + * it has changed + * @param displayGroupId the id of the display group to report + * @param screenTimeoutPolicy screen timeout policy + */ + void notifyScreenTimeoutPolicyChanges(int displayGroupId, + @ScreenTimeoutPolicy int screenTimeoutPolicy) { + synchronized (mLock) { + for (int idx = 0; idx < mScreenTimeoutPolicyListeners.size(); idx++) { + final int displayId = mScreenTimeoutPolicyListeners.keyAt(idx); + if (mDisplayManagerInternal.getGroupIdForDisplay(displayId) == displayGroupId) { + final ScreenTimeoutPolicyListenersContainer container = + mScreenTimeoutPolicyListeners.valueAt(idx); + container.updateScreenTimeoutPolicyAndNotifyIfNeeded(screenTimeoutPolicy); + } + } + } + } + + private final class ScreenTimeoutPolicyListenersContainer { + private final RemoteCallbackList<IScreenTimeoutPolicyListener> mListeners; + private final ArrayMap<IScreenTimeoutPolicyListener, Integer> mLastReportedState = + new ArrayMap<>(); + + @ScreenTimeoutPolicy + private volatile int mScreenTimeoutPolicy; + + ScreenTimeoutPolicyListenersContainer(int screenTimeoutPolicy) { + mScreenTimeoutPolicy = screenTimeoutPolicy; + mListeners = new RemoteCallbackList<IScreenTimeoutPolicyListener>() { + @Override + public void onCallbackDied(IScreenTimeoutPolicyListener callbackInterface) { + mLastReportedState.remove(callbackInterface); + } + }; + } + + void updateScreenTimeoutPolicyAndNotifyIfNeeded( + @ScreenTimeoutPolicy int screenTimeoutPolicy) { + mScreenTimeoutPolicy = screenTimeoutPolicy; + + mHandler.post(() -> { + for (int i = mListeners.beginBroadcast() - 1; i >= 0; i--) { + final IScreenTimeoutPolicyListener listener = mListeners.getBroadcastItem(i); + notifyListenerIfNeeded(listener); + } + mListeners.finishBroadcast(); + }); + } + + void addListener(IScreenTimeoutPolicyListener listener) { + mListeners.register(listener); + mHandler.post(() -> notifyListenerIfNeeded(listener)); + } + + void removeListener(IScreenTimeoutPolicyListener listener) { + mListeners.unregister(listener); + mLastReportedState.remove(listener); + } + + boolean isEmpty() { + return mListeners.getRegisteredCallbackCount() == 0; + } + + private void notifyListenerIfNeeded(IScreenTimeoutPolicyListener listener) { + final int currentScreenTimeoutPolicy = mScreenTimeoutPolicy; + final Integer reportedScreenTimeoutPolicy = mLastReportedState.get(listener); + final boolean needsReporting = reportedScreenTimeoutPolicy == null + || !reportedScreenTimeoutPolicy.equals(currentScreenTimeoutPolicy); + + if (!needsReporting) return; + + try { + listener.onScreenTimeoutPolicyChanged(currentScreenTimeoutPolicy); + mLastReportedState.put(listener, currentScreenTimeoutPolicy); + } catch (RemoteException e) { + // The RemoteCallbackList will take care of removing + // the dead object for us. + Slog.e(TAG, "Remote exception when notifying screen timeout policy change", e); + } catch (Throwable e) { + Slog.e(TAG, "Exception when notifying screen timeout policy change", e); + removeListener(listener); + } + } + } + private final class NotifierHandler extends Handler { public NotifierHandler(Looper looper) { diff --git a/services/core/java/com/android/server/power/PowerGroup.java b/services/core/java/com/android/server/power/PowerGroup.java index 01a2045df426..86eb34cead15 100644 --- a/services/core/java/com/android/server/power/PowerGroup.java +++ b/services/core/java/com/android/server/power/PowerGroup.java @@ -16,6 +16,8 @@ package com.android.server.power; +import static android.os.PowerManager.SCREEN_TIMEOUT_KEEP_DISPLAY_ON; +import static android.os.PowerManager.SCREEN_TIMEOUT_ACTIVE; import static android.os.PowerManagerInternal.WAKEFULNESS_ASLEEP; import static android.os.PowerManagerInternal.WAKEFULNESS_AWAKE; import static android.os.PowerManagerInternal.WAKEFULNESS_DOZING; @@ -34,6 +36,7 @@ import static com.android.server.power.PowerManagerService.WAKE_LOCK_STAY_AWAKE; import android.hardware.display.DisplayManagerInternal; import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest; import android.os.PowerManager; +import android.os.PowerManager.ScreenTimeoutPolicy; import android.os.PowerManagerInternal; import android.os.PowerSaveState; import android.os.Trace; @@ -415,6 +418,12 @@ public class PowerGroup { return (mWakeLockSummary & (screenOnWakeLockMask)) != 0; } + @ScreenTimeoutPolicy + public int getScreenTimeoutPolicy() { + return hasWakeLockKeepingScreenOnLocked() ? SCREEN_TIMEOUT_KEEP_DISPLAY_ON + : SCREEN_TIMEOUT_ACTIVE; + } + public void setWakeLockSummaryLocked(int summary) { mWakeLockSummary = summary; } diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java index ce8dc69e4b26..23383a9c55c0 100644 --- a/services/core/java/com/android/server/power/PowerManagerService.java +++ b/services/core/java/com/android/server/power/PowerManagerService.java @@ -63,6 +63,7 @@ import android.hardware.SystemSensorManager; import android.hardware.devicestate.DeviceState; import android.hardware.devicestate.DeviceStateManager; import android.hardware.display.AmbientDisplayConfiguration; +import android.hardware.display.DisplayManager; import android.hardware.display.DisplayManagerInternal; import android.hardware.power.Boost; import android.hardware.power.Mode; @@ -76,6 +77,7 @@ import android.os.Handler; import android.os.HandlerExecutor; import android.os.IBinder; import android.os.IPowerManager; +import android.os.IScreenTimeoutPolicyListener; import android.os.IWakeLockCallback; import android.os.Looper; import android.os.Message; @@ -341,6 +343,7 @@ public final class PowerManagerService extends SystemService private LightsManager mLightsManager; private BatteryManagerInternal mBatteryManagerInternal; private DisplayManagerInternal mDisplayManagerInternal; + private DisplayManager mDisplayManager; private IBatteryStats mBatteryStats; private WindowManagerPolicy mPolicy; private Notifier mNotifier; @@ -758,6 +761,24 @@ public final class PowerManagerService extends SystemService } } + private final class DisplayListener implements DisplayManager.DisplayListener { + + @Override + public void onDisplayAdded(int displayId) { + + } + + @Override + public void onDisplayRemoved(int displayId) { + mNotifier.clearScreenTimeoutPolicyListeners(displayId); + } + + @Override + public void onDisplayChanged(int displayId) { + + } + } + private final class DisplayGroupPowerChangeListener implements DisplayManagerInternal.DisplayGroupListener { @@ -1354,6 +1375,7 @@ public final class PowerManagerService extends SystemService mDisplayManagerInternal = getLocalService(DisplayManagerInternal.class); mPolicy = getLocalService(WindowManagerPolicy.class); mBatteryManagerInternal = getLocalService(BatteryManagerInternal.class); + mDisplayManager = mContext.getSystemService(DisplayManager.class); mAttentionDetector.systemReady(mContext); SensorManager sensorManager = new SystemSensorManager(mContext, mHandler.getLooper()); @@ -1373,6 +1395,7 @@ public final class PowerManagerService extends SystemService DisplayGroupPowerChangeListener displayGroupPowerChangeListener = new DisplayGroupPowerChangeListener(); mDisplayManagerInternal.registerDisplayGroupListener(displayGroupPowerChangeListener); + mDisplayManager.registerDisplayListener(new DisplayListener(), mHandler); if(mDreamManager != null){ // This DreamManager method does not acquire a lock, so it should be safe to call. @@ -2571,7 +2594,10 @@ public final class PowerManagerService extends SystemService // Phase 5: Send notifications, if needed. finishWakefulnessChangeIfNeededLocked(); - // Phase 6: Update suspend blocker. + // Phase 6: Notify screen timeout policy changes if needed + notifyScreenTimeoutPolicyChangesLocked(); + + // Phase 7: Update suspend blocker. // Because we might release the last suspend blocker here, we need to make sure // we finished everything else first! updateSuspendBlockerLocked(); @@ -3824,6 +3850,16 @@ public final class PowerManagerService extends SystemService & WAKE_LOCK_PROXIMITY_SCREEN_OFF) != 0; } + @GuardedBy("mLock") + private void notifyScreenTimeoutPolicyChangesLocked() { + for (int idx = 0; idx < mPowerGroups.size(); idx++) { + final int powerGroupId = mPowerGroups.keyAt(idx); + final PowerGroup powerGroup = mPowerGroups.valueAt(idx); + final int screenTimeoutPolicy = powerGroup.getScreenTimeoutPolicy(); + mNotifier.notifyScreenTimeoutPolicyChanges(powerGroupId, screenTimeoutPolicy); + } + } + /** * Updates the suspend blocker that keeps the CPU alive. * @@ -5973,6 +6009,55 @@ public final class PowerManagerService extends SystemService } @Override // Binder call + public void addScreenTimeoutPolicyListener(int displayId, + IScreenTimeoutPolicyListener listener) { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER, + null); + + if (displayId == Display.INVALID_DISPLAY) { + throw new IllegalArgumentException("Valid display id is expected"); + } + + final long ident = Binder.clearCallingIdentity(); + try { + int initialTimeoutPolicy; + final int displayGroupId = mDisplayManagerInternal.getGroupIdForDisplay(displayId); + synchronized (mLock) { + final PowerGroup powerGroup = mPowerGroups.get(displayGroupId); + if (powerGroup != null) { + initialTimeoutPolicy = powerGroup.getScreenTimeoutPolicy(); + } else { + throw new IllegalArgumentException("No display found for the specified " + + "display id " + displayId); + } + } + + mNotifier.addScreenTimeoutPolicyListener(displayId, initialTimeoutPolicy, + listener); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + @Override // Binder call + public void removeScreenTimeoutPolicyListener(int displayId, + IScreenTimeoutPolicyListener listener) { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER, + null); + + if (displayId == Display.INVALID_DISPLAY) { + throw new IllegalArgumentException("Valid display id is expected"); + } + + final long ident = Binder.clearCallingIdentity(); + try { + mNotifier.removeScreenTimeoutPolicyListener(displayId, listener); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + @Override // Binder call public void userActivity(int displayId, long eventTime, @PowerManager.UserActivityEvent int event, int flags) { final long now = mClock.uptimeMillis(); diff --git a/services/tests/powerservicetests/src/com/android/server/power/NotifierTest.java b/services/tests/powerservicetests/src/com/android/server/power/NotifierTest.java index 469bd66b7e7b..83a390d7f70b 100644 --- a/services/tests/powerservicetests/src/com/android/server/power/NotifierTest.java +++ b/services/tests/powerservicetests/src/com/android/server/power/NotifierTest.java @@ -16,6 +16,8 @@ package com.android.server.power; +import static android.os.PowerManager.SCREEN_TIMEOUT_KEEP_DISPLAY_ON; +import static android.os.PowerManager.SCREEN_TIMEOUT_ACTIVE; import static android.os.PowerManagerInternal.WAKEFULNESS_ASLEEP; import static android.os.PowerManagerInternal.WAKEFULNESS_AWAKE; @@ -30,6 +32,7 @@ import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.atLeast; import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; @@ -49,7 +52,9 @@ import android.hardware.display.DisplayManagerInternal; import android.os.BatteryStats; import android.os.BatteryStatsInternal; import android.os.Handler; +import android.os.IBinder; import android.os.IWakeLockCallback; +import android.os.IScreenTimeoutPolicyListener; import android.os.Looper; import android.os.PowerManager; import android.os.RemoteException; @@ -84,6 +89,7 @@ import org.junit.Test; import org.mockito.ArgumentCaptor; import org.mockito.InOrder; import org.mockito.Mock; +import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import java.util.concurrent.Executor; @@ -916,6 +922,201 @@ public class NotifierTest { PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK), -1); } + @Test + public void testScreenTimeoutListener_reportsScreenTimeoutPolicyChange() throws Exception { + createNotifier(); + final IScreenTimeoutPolicyListener listener = Mockito.mock( + IScreenTimeoutPolicyListener.class); + final IBinder listenerBinder = Mockito.mock(IBinder.class); + when(listener.asBinder()).thenReturn(listenerBinder); + mNotifier.addScreenTimeoutPolicyListener(Display.DEFAULT_DISPLAY, + SCREEN_TIMEOUT_ACTIVE, listener); + mTestLooper.dispatchAll(); + clearInvocations(listener); + + mNotifier.notifyScreenTimeoutPolicyChanges(Display.DEFAULT_DISPLAY_GROUP, + /* hasScreenWakeLock= */ SCREEN_TIMEOUT_KEEP_DISPLAY_ON); + + // Verify that the event is sent asynchronously on a handler + verify(listener, never()).onScreenTimeoutPolicyChanged(anyInt()); + mTestLooper.dispatchAll(); + verify(listener).onScreenTimeoutPolicyChanged(SCREEN_TIMEOUT_KEEP_DISPLAY_ON); + } + + @Test + public void testScreenTimeoutListener_addAndRemoveListener_doesNotInvokeListener() + throws Exception { + createNotifier(); + final IScreenTimeoutPolicyListener listener = Mockito.mock( + IScreenTimeoutPolicyListener.class); + final IBinder listenerBinder = Mockito.mock(IBinder.class); + when(listener.asBinder()).thenReturn(listenerBinder); + mNotifier.addScreenTimeoutPolicyListener(Display.DEFAULT_DISPLAY, + SCREEN_TIMEOUT_ACTIVE, listener); + mTestLooper.dispatchAll(); + clearInvocations(listener); + mNotifier.removeScreenTimeoutPolicyListener(Display.DEFAULT_DISPLAY, listener); + + mNotifier.notifyScreenTimeoutPolicyChanges(Display.DEFAULT_DISPLAY_GROUP, + SCREEN_TIMEOUT_KEEP_DISPLAY_ON); + mTestLooper.dispatchAll(); + + // Callback should not be fired as listener is removed + verify(listener, never()).onScreenTimeoutPolicyChanged(anyInt()); + } + + @Test + public void testScreenTimeoutListener_addAndClearListeners_doesNotInvokeListener() + throws Exception { + createNotifier(); + final IScreenTimeoutPolicyListener listener = Mockito.mock( + IScreenTimeoutPolicyListener.class); + final IBinder listenerBinder = Mockito.mock(IBinder.class); + when(listener.asBinder()).thenReturn(listenerBinder); + mNotifier.addScreenTimeoutPolicyListener(Display.DEFAULT_DISPLAY, + SCREEN_TIMEOUT_ACTIVE, listener); + mTestLooper.dispatchAll(); + clearInvocations(listener); + mNotifier.clearScreenTimeoutPolicyListeners(Display.DEFAULT_DISPLAY); + + mNotifier.notifyScreenTimeoutPolicyChanges(Display.DEFAULT_DISPLAY_GROUP, + SCREEN_TIMEOUT_KEEP_DISPLAY_ON); + mTestLooper.dispatchAll(); + + // Callback should not be fired as listener is removed + verify(listener, never()).onScreenTimeoutPolicyChanged(anyInt()); + } + + @Test + public void testScreenTimeoutListener_subscribedToAnotherDisplay_listenerNotFired() + throws Exception { + createNotifier(); + + final IScreenTimeoutPolicyListener listener = Mockito.mock( + IScreenTimeoutPolicyListener.class); + final IBinder listenerBinder = Mockito.mock(IBinder.class); + when(listener.asBinder()).thenReturn(listenerBinder); + mNotifier.addScreenTimeoutPolicyListener(Display.DEFAULT_DISPLAY, + SCREEN_TIMEOUT_ACTIVE, listener); + mTestLooper.dispatchAll(); + clearInvocations(listener); + + mNotifier.notifyScreenTimeoutPolicyChanges(/* displayGroupId= */ 123, + SCREEN_TIMEOUT_KEEP_DISPLAY_ON); + mTestLooper.dispatchAll(); + + // Callback should not be fired as we subscribed only to the DEFAULT_DISPLAY + verify(listener, never()).onScreenTimeoutPolicyChanged(anyInt()); + } + + @Test + public void testScreenTimeoutListener_listenerDied_listenerNotFired() + throws Exception { + createNotifier(); + + final IScreenTimeoutPolicyListener listener = Mockito.mock( + IScreenTimeoutPolicyListener.class); + final IBinder listenerBinder = Mockito.mock(IBinder.class); + when(listener.asBinder()).thenReturn(listenerBinder); + + mNotifier.addScreenTimeoutPolicyListener(Display.DEFAULT_DISPLAY, + SCREEN_TIMEOUT_ACTIVE, listener); + mTestLooper.dispatchAll(); + + ArgumentCaptor<IBinder.DeathRecipient> captor = + ArgumentCaptor.forClass(IBinder.DeathRecipient.class); + verify(listenerBinder).linkToDeath(captor.capture(), anyInt()); + mTestLooper.dispatchAll(); + captor.getValue().binderDied(); + clearInvocations(listener); + + mNotifier.notifyScreenTimeoutPolicyChanges(Display.DEFAULT_DISPLAY, + SCREEN_TIMEOUT_KEEP_DISPLAY_ON); + mTestLooper.dispatchAll(); + + // Callback should not be fired as binder died + verify(listener, never()).onScreenTimeoutPolicyChanged(anyInt()); + } + + @Test + public void testScreenTimeoutListener_listenerThrowsException_listenerNotFiredSecondTime() + throws Exception { + createNotifier(); + + final IScreenTimeoutPolicyListener listener = Mockito.mock( + IScreenTimeoutPolicyListener.class); + final IBinder listenerBinder = Mockito.mock(IBinder.class); + when(listener.asBinder()).thenReturn(listenerBinder); + doThrow(RuntimeException.class).when(listener).onScreenTimeoutPolicyChanged(anyInt()); + mNotifier.addScreenTimeoutPolicyListener(Display.DEFAULT_DISPLAY_GROUP, + SCREEN_TIMEOUT_ACTIVE, listener); + mTestLooper.dispatchAll(); + clearInvocations(listener); + + mNotifier.notifyScreenTimeoutPolicyChanges(Display.DEFAULT_DISPLAY, + SCREEN_TIMEOUT_KEEP_DISPLAY_ON); + mTestLooper.dispatchAll(); + + // Callback should not be fired as it has thrown an exception once + verify(listener, never()).onScreenTimeoutPolicyChanged(anyInt()); + } + + @Test + public void testScreenTimeoutListener_nonDefaultDisplay_stillReportsPolicyCorrectly() + throws Exception { + createNotifier(); + final int otherDisplayId = 123; + final int otherDisplayGroupId = 123_00; + when(mDisplayManagerInternal.getGroupIdForDisplay(otherDisplayId)).thenReturn( + otherDisplayGroupId); + final IScreenTimeoutPolicyListener listener = Mockito.mock( + IScreenTimeoutPolicyListener.class); + final IBinder listenerBinder = Mockito.mock(IBinder.class); + when(listener.asBinder()).thenReturn(listenerBinder); + mNotifier.addScreenTimeoutPolicyListener(otherDisplayId, + SCREEN_TIMEOUT_ACTIVE, listener); + mTestLooper.dispatchAll(); + clearInvocations(listener); + + mNotifier.notifyScreenTimeoutPolicyChanges(otherDisplayGroupId, + SCREEN_TIMEOUT_KEEP_DISPLAY_ON); + mTestLooper.dispatchAll(); + + verify(listener).onScreenTimeoutPolicyChanged(SCREEN_TIMEOUT_KEEP_DISPLAY_ON); + } + + @Test + public void testScreenTimeoutListener_timeoutPolicyTimeout_reportsTimeoutOnSubscription() + throws Exception { + createNotifier(); + final IScreenTimeoutPolicyListener listener = Mockito.mock( + IScreenTimeoutPolicyListener.class); + final IBinder listenerBinder = Mockito.mock(IBinder.class); + when(listener.asBinder()).thenReturn(listenerBinder); + + mNotifier.addScreenTimeoutPolicyListener(Display.DEFAULT_DISPLAY, + SCREEN_TIMEOUT_ACTIVE, listener); + mTestLooper.dispatchAll(); + + verify(listener).onScreenTimeoutPolicyChanged(SCREEN_TIMEOUT_ACTIVE); + } + + @Test + public void testScreenTimeoutListener_policyHeld_reportsHeldOnSubscription() + throws Exception { + createNotifier(); + final IScreenTimeoutPolicyListener listener = Mockito.mock( + IScreenTimeoutPolicyListener.class); + final IBinder listenerBinder = Mockito.mock(IBinder.class); + when(listener.asBinder()).thenReturn(listenerBinder); + + mNotifier.addScreenTimeoutPolicyListener(Display.DEFAULT_DISPLAY, + SCREEN_TIMEOUT_KEEP_DISPLAY_ON, listener); + mTestLooper.dispatchAll(); + + verify(listener).onScreenTimeoutPolicyChanged(SCREEN_TIMEOUT_KEEP_DISPLAY_ON); + } + private final PowerManagerService.Injector mInjector = new PowerManagerService.Injector() { @Override Notifier createNotifier(Looper looper, Context context, IBatteryStats batteryStats, diff --git a/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java b/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java index 376091e4a241..3cb27451bd57 100644 --- a/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java +++ b/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java @@ -20,6 +20,8 @@ import static android.app.ActivityManager.PROCESS_STATE_BOUND_TOP; import static android.app.ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE; import static android.app.ActivityManager.PROCESS_STATE_RECEIVER; import static android.app.ActivityManager.PROCESS_STATE_TOP_SLEEPING; +import static android.os.PowerManager.SCREEN_TIMEOUT_KEEP_DISPLAY_ON; +import static android.os.PowerManager.SCREEN_TIMEOUT_ACTIVE; import static android.os.PowerManager.USER_ACTIVITY_EVENT_BUTTON; import static android.os.PowerManagerInternal.WAKEFULNESS_ASLEEP; import static android.os.PowerManagerInternal.WAKEFULNESS_AWAKE; @@ -69,6 +71,7 @@ import android.hardware.devicestate.DeviceState; import android.hardware.devicestate.DeviceStateManager; import android.hardware.devicestate.DeviceStateManager.DeviceStateCallback; import android.hardware.display.AmbientDisplayConfiguration; +import android.hardware.display.DisplayManager; import android.hardware.display.DisplayManagerInternal; import android.hardware.power.Boost; import android.hardware.power.Mode; @@ -79,6 +82,7 @@ import android.os.Binder; import android.os.Handler; import android.os.IBinder; import android.os.IWakeLockCallback; +import android.os.IScreenTimeoutPolicyListener; import android.os.Looper; import android.os.PowerManager; import android.os.PowerManagerInternal; @@ -132,6 +136,7 @@ import org.junit.rules.TestRule; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.ArgumentMatcher; +import org.mockito.Captor; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; @@ -166,6 +171,7 @@ public class PowerManagerServiceTest { @Mock private BatterySaverStateMachine mBatterySaverStateMachineMock; @Mock private LightsManager mLightsManagerMock; @Mock private DisplayManagerInternal mDisplayManagerInternalMock; + @Mock private DisplayManager mDisplayManagerMock; @Mock private BatteryManagerInternal mBatteryManagerInternalMock; @Mock private ActivityManagerInternal mActivityManagerInternalMock; @Mock private AttentionManagerInternal mAttentionManagerInternalMock; @@ -185,6 +191,8 @@ public class PowerManagerServiceTest { @Mock private DeviceStateManager mDeviceStateManagerMock; @Mock private DeviceConfigParameterProvider mDeviceParameterProvider; + @Captor private ArgumentCaptor<DisplayManager.DisplayListener> mDisplayListenerArgumentCaptor; + @Rule public TestRule compatChangeRule = new PlatformCompatChangeRule(); @Rule public SetFlagsRule mSetFlagsRule = new SetFlagsRule(); @@ -254,6 +262,7 @@ public class PowerManagerServiceTest { mContextSpy = spy(new ContextWrapper(ApplicationProvider.getApplicationContext())); mResourcesSpy = spy(mContextSpy.getResources()); when(mContextSpy.getResources()).thenReturn(mResourcesSpy); + when(mContextSpy.getSystemService(DisplayManager.class)).thenReturn(mDisplayManagerMock); setBatterySaverSupported(); MockContentResolver cr = new MockContentResolver(mContextSpy); @@ -2947,15 +2956,19 @@ public class PowerManagerServiceTest { assertThat(mService.getPowerGroupSize()).isEqualTo(4); } - private WakeLock acquireWakeLock(String tag, int flags) { + private WakeLock acquireWakeLock(String tag, int flags, int displayId) { IBinder token = new Binder(); String packageName = "pkg.name"; mService.getBinderServiceInstance().acquireWakeLock(token, flags, tag, packageName, - null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY, + null /* workSource */, null /* historyTag */, displayId, null /* callback */); return mService.findWakeLockLocked(token); } + private WakeLock acquireWakeLock(String tag, int flags) { + return acquireWakeLock(tag, flags, Display.INVALID_DISPLAY); + } + /** * Test IPowerManager.acquireWakeLock() with a IWakeLockCallback. */ @@ -3443,6 +3456,106 @@ public class PowerManagerServiceTest { } @Test + public void testAddWakeLockKeepingScreenOn_addsToNotifierAndReportsTimeoutPolicyChange() { + IntArray displayGroupIds = IntArray.wrap(new int[]{Display.DEFAULT_DISPLAY_GROUP}); + when(mDisplayManagerInternalMock.getDisplayGroupIds()).thenReturn(displayGroupIds); + + final DisplayInfo displayInfo = new DisplayInfo(); + displayInfo.displayGroupId = Display.DEFAULT_DISPLAY_GROUP; + when(mDisplayManagerInternalMock.getDisplayInfo(Display.DEFAULT_DISPLAY)) + .thenReturn(displayInfo); + + createService(); + startSystem(); + + final IScreenTimeoutPolicyListener listener = Mockito.mock( + IScreenTimeoutPolicyListener.class); + mService.getBinderServiceInstance().addScreenTimeoutPolicyListener( + Display.DEFAULT_DISPLAY_GROUP, listener); + verify(mNotifierMock).addScreenTimeoutPolicyListener(Display.DEFAULT_DISPLAY_GROUP, + SCREEN_TIMEOUT_ACTIVE, listener); + clearInvocations(mNotifierMock); + + acquireWakeLock("screenBright", PowerManager.SCREEN_BRIGHT_WAKE_LOCK, + Display.DEFAULT_DISPLAY); + verify(mNotifierMock).notifyScreenTimeoutPolicyChanges(Display.DEFAULT_DISPLAY_GROUP, + SCREEN_TIMEOUT_KEEP_DISPLAY_ON); + } + + @Test + public void test_addAndRemoveScreenTimeoutListener_propagatesToNotifier() + throws Exception { + IntArray displayGroupIds = IntArray.wrap(new int[]{Display.DEFAULT_DISPLAY_GROUP}); + when(mDisplayManagerInternalMock.getDisplayGroupIds()).thenReturn(displayGroupIds); + + final DisplayInfo displayInfo = new DisplayInfo(); + displayInfo.displayGroupId = Display.DEFAULT_DISPLAY_GROUP; + when(mDisplayManagerInternalMock.getDisplayInfo(Display.DEFAULT_DISPLAY)) + .thenReturn(displayInfo); + + createService(); + startSystem(); + + final IScreenTimeoutPolicyListener listener = Mockito.mock( + IScreenTimeoutPolicyListener.class); + mService.getBinderServiceInstance().addScreenTimeoutPolicyListener( + Display.DEFAULT_DISPLAY, listener); + + clearInvocations(mNotifierMock); + mService.getBinderServiceInstance().removeScreenTimeoutPolicyListener( + Display.DEFAULT_DISPLAY, listener); + verify(mNotifierMock).removeScreenTimeoutPolicyListener(Display.DEFAULT_DISPLAY, + listener); + } + + @Test + public void test_displayIsRemoved_clearsScreenTimeoutListeners() + throws Exception { + IntArray displayGroupIds = IntArray.wrap(new int[]{Display.DEFAULT_DISPLAY_GROUP}); + when(mDisplayManagerInternalMock.getDisplayGroupIds()).thenReturn(displayGroupIds); + + final DisplayInfo displayInfo = new DisplayInfo(); + displayInfo.displayGroupId = Display.DEFAULT_DISPLAY_GROUP; + when(mDisplayManagerInternalMock.getDisplayInfo(Display.DEFAULT_DISPLAY)) + .thenReturn(displayInfo); + + createService(); + startSystem(); + verify(mDisplayManagerMock).registerDisplayListener( + mDisplayListenerArgumentCaptor.capture(), any()); + clearInvocations(mNotifierMock); + + mDisplayListenerArgumentCaptor.getValue().onDisplayRemoved(Display.DEFAULT_DISPLAY); + + verify(mNotifierMock).clearScreenTimeoutPolicyListeners(Display.DEFAULT_DISPLAY); + } + + @Test + public void testScreenWakeLockListener_screenHasWakelocks_addsWithHeldTimeoutPolicyToNotifier() + throws Exception { + IntArray displayGroupIds = IntArray.wrap(new int[]{Display.DEFAULT_DISPLAY_GROUP}); + when(mDisplayManagerInternalMock.getDisplayGroupIds()).thenReturn(displayGroupIds); + + final DisplayInfo displayInfo = new DisplayInfo(); + displayInfo.displayGroupId = Display.DEFAULT_DISPLAY_GROUP; + when(mDisplayManagerInternalMock.getDisplayInfo(Display.DEFAULT_DISPLAY)) + .thenReturn(displayInfo); + + createService(); + startSystem(); + + acquireWakeLock("screenBright", PowerManager.SCREEN_BRIGHT_WAKE_LOCK, + Display.DEFAULT_DISPLAY); + + final IScreenTimeoutPolicyListener listener = Mockito.mock( + IScreenTimeoutPolicyListener.class); + mService.getBinderServiceInstance().addScreenTimeoutPolicyListener( + Display.DEFAULT_DISPLAY_GROUP, listener); + verify(mNotifierMock).notifyScreenTimeoutPolicyChanges(Display.DEFAULT_DISPLAY_GROUP, + SCREEN_TIMEOUT_KEEP_DISPLAY_ON); + } + + @Test public void testHalAutoSuspendMode_enabledByConfiguration() { AtomicReference<DisplayManagerInternal.DisplayPowerCallbacks> callback = new AtomicReference<>(); |