diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java index 92d652df35d9b..d4a338bef02b0 100644 --- a/core/java/android/os/PowerManager.java +++ b/core/java/android/os/PowerManager.java @@ -334,6 +334,12 @@ public final class PowerManager { */ public static final int USER_ACTIVITY_EVENT_FACE_DOWN = 5; + /** + * User activity event type: There is a change in the device state. + * @hide + */ + public static final int USER_ACTIVITY_EVENT_DEVICE_STATE = 6; + /** * User activity flag: If already dimmed, extend the dim timeout * but do not brighten. This flag is useful for keeping the screen on diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java index 1e7410451021c..4b2770cef4765 100644 --- a/services/core/java/com/android/server/power/PowerManagerService.java +++ b/services/core/java/com/android/server/power/PowerManagerService.java @@ -49,6 +49,7 @@ import android.content.res.Resources; import android.database.ContentObserver; import android.hardware.SensorManager; import android.hardware.SystemSensorManager; +import android.hardware.devicestate.DeviceStateManager; import android.hardware.display.AmbientDisplayConfiguration; import android.hardware.display.DisplayManagerInternal; import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest; @@ -60,6 +61,7 @@ import android.os.BatteryManagerInternal; import android.os.BatterySaverPolicyConfig; import android.os.Binder; import android.os.Handler; +import android.os.HandlerExecutor; import android.os.IBinder; import android.os.IPowerManager; import android.os.Looper; @@ -1151,6 +1153,9 @@ public final class PowerManagerService extends SystemService PowerManager.GO_TO_SLEEP_REASON_QUIESCENT, PowerManager.GO_TO_SLEEP_FLAG_NO_DOZE, Process.SYSTEM_UID); } + + mContext.getSystemService(DeviceStateManager.class).registerCallback( + new HandlerExecutor(mHandler), new DeviceStateListener()); } } } @@ -6349,4 +6354,25 @@ public final class PowerManagerService extends SystemService return interceptPowerKeyDownInternal(event); } } + + /** + * Listens to changes in device state and updates the interactivity time. + * Any changes to the device state are treated as user interactions. + */ + class DeviceStateListener implements DeviceStateManager.DeviceStateCallback { + private int mDeviceState = DeviceStateManager.INVALID_DEVICE_STATE; + + @Override + public void onStateChanged(int deviceState) { + if (mDeviceState != deviceState) { + mDeviceState = deviceState; + // Device-state interactions are applied to the default display so that they + // are reflected only with the default power group. + userActivityInternal(Display.DEFAULT_DISPLAY, mClock.uptimeMillis(), + PowerManager.USER_ACTIVITY_EVENT_DEVICE_STATE, /* flags= */0, + Process.SYSTEM_UID); + } + } + }; + } diff --git a/services/tests/mockingservicestests/Android.bp b/services/tests/mockingservicestests/Android.bp index 8538603d41807..635f1360ff734 100644 --- a/services/tests/mockingservicestests/Android.bp +++ b/services/tests/mockingservicestests/Android.bp @@ -40,26 +40,26 @@ android_test { ], static_libs: [ + "androidx.test.core", + "androidx.test.runner", + "androidx.test.ext.truth", "frameworks-base-testutils", + "hamcrest-library", + "kotlin-test", + "mockingservicestests-utils-mockito", + "mockito-target-extended-minus-junit4", + "platform-test-annotations", + "service-blobstore", + "service-jobscheduler", + "service-permission.impl", "services.core", "services.devicepolicy", "services.net", "services.usage", - "service-jobscheduler", - "service-permission.impl", - "service-blobstore", - "androidx.test.core", - "androidx.test.runner", - "androidx.test.ext.truth", - "mockito-target-extended-minus-junit4", - "platform-test-annotations", - "truth-prebuilt", - "hamcrest-library", - "servicestests-utils-mockito-extended", - "mockingservicestests-utils-mockito", "servicestests-core-utils", + "servicestests-utils-mockito-extended", "testables", - "kotlin-test", + "truth-prebuilt", // TODO: remove once Android migrates to JUnit 4.12, which provides assertThrows "testng", ], diff --git a/services/tests/mockingservicestests/src/com/android/server/power/PowerManagerServiceMockingTest.java b/services/tests/mockingservicestests/src/com/android/server/power/PowerManagerServiceMockingTest.java new file mode 100644 index 0000000000000..b65c3e9399548 --- /dev/null +++ b/services/tests/mockingservicestests/src/com/android/server/power/PowerManagerServiceMockingTest.java @@ -0,0 +1,303 @@ +/* + * Copyright (C) 2022 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 com.android.server.power; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.ActivityManagerInternal; +import android.attention.AttentionManagerInternal; +import android.content.Context; +import android.content.ContextWrapper; +import android.content.res.Resources; +import android.hardware.SensorManager; +import android.hardware.devicestate.DeviceStateManager; +import android.hardware.devicestate.DeviceStateManager.DeviceStateCallback; +import android.hardware.display.AmbientDisplayConfiguration; +import android.hardware.display.DisplayManagerInternal; +import android.os.BatteryManagerInternal; +import android.os.Handler; +import android.os.Looper; +import android.os.PowerManager; +import android.os.PowerSaveState; +import android.os.test.TestLooper; +import android.provider.Settings; +import android.service.dreams.DreamManagerInternal; +import android.test.mock.MockContentResolver; +import android.view.Display; +import android.view.DisplayInfo; + +import androidx.test.InstrumentationRegistry; + +import com.android.internal.app.IBatteryStats; +import com.android.internal.util.test.FakeSettingsProvider; +import com.android.server.LocalServices; +import com.android.server.SystemService; +import com.android.server.lights.LightsManager; +import com.android.server.policy.WindowManagerPolicy; +import com.android.server.power.PowerManagerService.BatteryReceiver; +import com.android.server.power.PowerManagerService.Injector; +import com.android.server.power.PowerManagerService.NativeWrapper; +import com.android.server.power.PowerManagerService.UserSwitchedReceiver; +import com.android.server.power.batterysaver.BatterySaverController; +import com.android.server.power.batterysaver.BatterySaverPolicy; +import com.android.server.power.batterysaver.BatterySaverStateMachine; +import com.android.server.power.batterysaver.BatterySavingStats; +import com.android.server.testutils.OffsettableClock; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** + * Tests for {@link com.android.server.power.PowerManagerService}. + * + * Build/Install/Run: + * atest FrameworksServicesTests:PowerManagerServiceMockingTest + */ +public class PowerManagerServiceMockingTest { + private static final String SYSTEM_PROPERTY_QUIESCENT = "ro.boot.quiescent"; + private static final String SYSTEM_PROPERTY_REBOOT_REASON = "sys.boot.reason"; + + private static final float BRIGHTNESS_FACTOR = 0.7f; + private static final boolean BATTERY_SAVER_ENABLED = true; + + @Mock private BatterySaverController mBatterySaverControllerMock; + @Mock private BatterySaverPolicy mBatterySaverPolicyMock; + @Mock private BatterySaverStateMachine mBatterySaverStateMachineMock; + @Mock private LightsManager mLightsManagerMock; + @Mock private DisplayManagerInternal mDisplayManagerInternalMock; + @Mock private BatteryManagerInternal mBatteryManagerInternalMock; + @Mock private ActivityManagerInternal mActivityManagerInternalMock; + @Mock private AttentionManagerInternal mAttentionManagerInternalMock; + @Mock private DreamManagerInternal mDreamManagerInternalMock; + @Mock private PowerManagerService.NativeWrapper mNativeWrapperMock; + @Mock private Notifier mNotifierMock; + @Mock private WirelessChargerDetector mWirelessChargerDetectorMock; + @Mock private AmbientDisplayConfiguration mAmbientDisplayConfigurationMock; + @Mock private SystemPropertiesWrapper mSystemPropertiesMock; + @Mock private DeviceStateManager mDeviceStateManagerMock; + + @Mock + private InattentiveSleepWarningController mInattentiveSleepWarningControllerMock; + + private PowerManagerService mService; + private PowerSaveState mPowerSaveState; + private ContextWrapper mContextSpy; + private BatteryReceiver mBatteryReceiver; + private UserSwitchedReceiver mUserSwitchedReceiver; + private Resources mResourcesSpy; + private OffsettableClock mClock; + private TestLooper mTestLooper; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + FakeSettingsProvider.clearSettingsProvider(); + + mPowerSaveState = new PowerSaveState.Builder() + .setBatterySaverEnabled(BATTERY_SAVER_ENABLED) + .setBrightnessFactor(BRIGHTNESS_FACTOR) + .build(); + when(mBatterySaverPolicyMock.getBatterySaverPolicy( + eq(PowerManager.ServiceType.SCREEN_BRIGHTNESS))) + .thenReturn(mPowerSaveState); + when(mBatteryManagerInternalMock.isPowered(anyInt())).thenReturn(false); + when(mInattentiveSleepWarningControllerMock.isShown()).thenReturn(false); + when(mDisplayManagerInternalMock.requestPowerState(anyInt(), any(), anyBoolean())) + .thenReturn(true); + when(mSystemPropertiesMock.get(eq(SYSTEM_PROPERTY_QUIESCENT), anyString())).thenReturn(""); + when(mAmbientDisplayConfigurationMock.ambientDisplayAvailable()).thenReturn(true); + + addLocalServiceMock(LightsManager.class, mLightsManagerMock); + addLocalServiceMock(DisplayManagerInternal.class, mDisplayManagerInternalMock); + addLocalServiceMock(BatteryManagerInternal.class, mBatteryManagerInternalMock); + addLocalServiceMock(ActivityManagerInternal.class, mActivityManagerInternalMock); + addLocalServiceMock(AttentionManagerInternal.class, mAttentionManagerInternalMock); + addLocalServiceMock(DreamManagerInternal.class, mDreamManagerInternalMock); + + mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getContext())); + mResourcesSpy = spy(mContextSpy.getResources()); + when(mContextSpy.getResources()).thenReturn(mResourcesSpy); + + MockContentResolver cr = new MockContentResolver(mContextSpy); + cr.addProvider(Settings.AUTHORITY, new FakeSettingsProvider()); + when(mContextSpy.getContentResolver()).thenReturn(cr); + + when(mContextSpy.getSystemService(DeviceStateManager.class)) + .thenReturn(mDeviceStateManagerMock); + + Settings.Global.putInt(mContextSpy.getContentResolver(), + Settings.Global.STAY_ON_WHILE_PLUGGED_IN, 0); + + mClock = new OffsettableClock.Stopped(); + mTestLooper = new TestLooper(mClock::now); + } + + private PowerManagerService createService() { + mService = new PowerManagerService(mContextSpy, new Injector() { + @Override + Notifier createNotifier(Looper looper, Context context, IBatteryStats batteryStats, + SuspendBlocker suspendBlocker, WindowManagerPolicy policy, + FaceDownDetector faceDownDetector, ScreenUndimDetector screenUndimDetector) { + return mNotifierMock; + } + + @Override + SuspendBlocker createSuspendBlocker(PowerManagerService service, String name) { + return super.createSuspendBlocker(service, name); + } + + @Override + BatterySaverPolicy createBatterySaverPolicy( + Object lock, Context context, BatterySavingStats batterySavingStats) { + return mBatterySaverPolicyMock; + } + + @Override + BatterySaverController createBatterySaverController( + Object lock, Context context, BatterySaverPolicy batterySaverPolicy, + BatterySavingStats batterySavingStats) { + return mBatterySaverControllerMock; + } + + @Override + BatterySaverStateMachine createBatterySaverStateMachine(Object lock, Context context, + BatterySaverController batterySaverController) { + return mBatterySaverStateMachineMock; + } + + @Override + NativeWrapper createNativeWrapper() { + return mNativeWrapperMock; + } + + @Override + WirelessChargerDetector createWirelessChargerDetector( + SensorManager sensorManager, SuspendBlocker suspendBlocker, Handler handler) { + return mWirelessChargerDetectorMock; + } + + @Override + AmbientDisplayConfiguration createAmbientDisplayConfiguration(Context context) { + return mAmbientDisplayConfigurationMock; + } + + @Override + InattentiveSleepWarningController createInattentiveSleepWarningController() { + return mInattentiveSleepWarningControllerMock; + } + + @Override + public SystemPropertiesWrapper createSystemPropertiesWrapper() { + return mSystemPropertiesMock; + } + + @Override + PowerManagerService.Clock createClock() { + return () -> mClock.now(); + } + + @Override + Handler createHandler(Looper looper, Handler.Callback callback) { + return new Handler(mTestLooper.getLooper(), callback); + } + + @Override + void invalidateIsInteractiveCaches() { + // Avoids an SELinux failure. + } + }); + return mService; + } + + @After + public void tearDown() throws Exception { + LocalServices.removeServiceForTest(LightsManager.class); + LocalServices.removeServiceForTest(DisplayManagerInternal.class); + LocalServices.removeServiceForTest(BatteryManagerInternal.class); + LocalServices.removeServiceForTest(ActivityManagerInternal.class); + LocalServices.removeServiceForTest(AttentionManagerInternal.class); + LocalServices.removeServiceForTest(DreamManagerInternal.class); + FakeSettingsProvider.clearSettingsProvider(); + } + + /** + * Creates a mock and registers it to {@link LocalServices}. + */ + private static void addLocalServiceMock(Class clazz, T mock) { + LocalServices.removeServiceForTest(clazz); + LocalServices.addService(clazz, mock); + } + + private void advanceTime(long timeMs) { + mClock.fastForward(timeMs); + mTestLooper.dispatchAll(); + } + + @Test + public void testUserActivityOnDeviceStateChange() { + createService(); + mService.systemReady(null); + mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED); + + final DisplayInfo info = new DisplayInfo(); + info.displayGroupId = Display.DEFAULT_DISPLAY_GROUP; + when(mDisplayManagerInternalMock.getDisplayInfo(Display.DEFAULT_DISPLAY)).thenReturn(info); + + final ArgumentCaptor deviceStateCallbackCaptor = + ArgumentCaptor.forClass(DeviceStateCallback.class); + verify(mDeviceStateManagerMock).registerCallback(any(), + deviceStateCallbackCaptor.capture()); + + // Advance the time 10001 and verify that the device thinks it has been idle + // for just less than that. + mService.onUserActivity(); + advanceTime(10001); + assertThat(mService.wasDeviceIdleForInternal(10000)).isTrue(); + + // Send a display state change event and advance the clock 10. + final DeviceStateCallback deviceStateCallback = deviceStateCallbackCaptor.getValue(); + deviceStateCallback.onStateChanged(1); + final long timeToAdvance = 10; + advanceTime(timeToAdvance); + + // Ensure that the device has been idle for only 10 (doesn't include the idle time + // before the display state event). + assertThat(mService.wasDeviceIdleForInternal(timeToAdvance - 1)).isTrue(); + assertThat(mService.wasDeviceIdleForInternal(timeToAdvance)).isFalse(); + + // Send the same state and ensure that does not trigger an update. + deviceStateCallback.onStateChanged(1); + advanceTime(timeToAdvance); + final long newTime = timeToAdvance * 2; + + assertThat(mService.wasDeviceIdleForInternal(newTime - 1)).isTrue(); + assertThat(mService.wasDeviceIdleForInternal(newTime)).isFalse(); + } +}