Migrate PowerManagerTests to PowerServiceTests

- Migrate power tests within servicestests/ to correct module
- Add permissions and libraries needed for these tests.

Bug: 290764236
Test: atest PowerServiceTests
Change-Id: Ib63768225b39024153497d11cd4cb9bfef46617e
diff --git a/services/tests/powerservicetests/Android.bp b/services/tests/powerservicetests/Android.bp
index 236f90c..f6714a8 100644
--- a/services/tests/powerservicetests/Android.bp
+++ b/services/tests/powerservicetests/Android.bp
@@ -12,9 +12,15 @@
 
     static_libs: [
         "frameworks-base-testutils",
+        "platform-compat-test-rules",
         "platform-test-annotations",
         "services.core",
         "servicestests-utils",
+        "testables",
+    ],
+
+    libs: [
+        "android.test.mock",
     ],
 
     platform_apis: true,
diff --git a/services/tests/powerservicetests/AndroidManifest.xml b/services/tests/powerservicetests/AndroidManifest.xml
index 3ace9d5..26d9eec 100644
--- a/services/tests/powerservicetests/AndroidManifest.xml
+++ b/services/tests/powerservicetests/AndroidManifest.xml
@@ -17,10 +17,19 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="com.android.frameworks.powerservicetests">
 
-    <!--
-    Insert permissions here. eg:
-    <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
-    -->
+    <!-- Permissions -->
+    <uses-permission android:name="android.permission.DEVICE_POWER"/>
+    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS"/>
+    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL"/>
+    <uses-permission android:name="android.permission.READ_DEVICE_CONFIG"/>
+    <uses-permission android:name="android.permission.READ_DREAM_STATE"/>
+    <uses-permission android:name="android.permission.READ_DREAM_SUPPRESSION"/>
+    <uses-permission android:name="android.permission.STATUS_BAR_SERVICE"/>
+    <uses-permission android:name="android.permission.UPDATE_APP_OPS_STATS"/>
+    <uses-permission android:name="android.permission.UPDATE_DEVICE_STATS"/>
+    <uses-permission android:name="android.permission.WAKE_LOCK"/>
+    <uses-permission android:name="android.permission.WRITE_DREAM_STATE"/>
+
     <application android:debuggable="true"
                  android:testOnly="true">
         <uses-library android:name="android.test.mock" android:required="true" />
diff --git a/services/tests/powerservicetests/src/com/android/server/power/LowPowerStandbyControllerTest.java b/services/tests/powerservicetests/src/com/android/server/power/LowPowerStandbyControllerTest.java
new file mode 100644
index 0000000..d3c0e35
--- /dev/null
+++ b/services/tests/powerservicetests/src/com/android/server/power/LowPowerStandbyControllerTest.java
@@ -0,0 +1,899 @@
+/*
+ * Copyright (C) 2021 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 android.os.PowerManager.FEATURE_WAKE_ON_LAN_IN_LOW_POWER_STANDBY;
+import static android.os.PowerManager.LOW_POWER_STANDBY_ALLOWED_REASON_ONGOING_CALL;
+import static android.os.PowerManager.LOW_POWER_STANDBY_ALLOWED_REASON_TEMP_POWER_SAVE_ALLOWLIST;
+import static android.os.PowerManager.LOW_POWER_STANDBY_ALLOWED_REASON_VOICE_INTERACTION;
+import static android.os.PowerManager.LowPowerStandbyPortDescription.MATCH_PORT_LOCAL;
+import static android.os.PowerManager.LowPowerStandbyPortDescription.PROTOCOL_TCP;
+import static android.os.PowerManager.LowPowerStandbyPortDescription.PROTOCOL_UDP;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import android.app.ActivityManagerInternal;
+import android.app.AlarmManager;
+import android.app.IActivityManager;
+import android.app.IForegroundServiceObserver;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.content.pm.ServiceInfo;
+import android.content.res.Resources;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.IPowerManager;
+import android.os.PowerManager;
+import android.os.PowerManager.LowPowerStandbyPolicy;
+import android.os.PowerManager.LowPowerStandbyPortDescription;
+import android.os.PowerManagerInternal;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.os.test.TestLooper;
+import android.provider.Settings;
+import android.test.mock.MockContentResolver;
+import android.util.ArraySet;
+
+import androidx.test.InstrumentationRegistry;
+
+import com.android.internal.util.test.BroadcastInterceptingContext;
+import com.android.internal.util.test.FakeSettingsProvider;
+import com.android.server.LocalServices;
+import com.android.server.PowerAllowlistInternal;
+import com.android.server.PowerAllowlistInternal.TempAllowlistChangeListener;
+import com.android.server.net.NetworkPolicyManagerInternal;
+import com.android.server.power.LowPowerStandbyController.DeviceConfigWrapper;
+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.InOrder;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.io.File;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Tests for {@link com.android.server.power.LowPowerStandbyController}.
+ *
+ * Build/Install/Run:
+ * atest LowPowerStandbyControllerTest
+ */
+public class LowPowerStandbyControllerTest {
+    private static final int STANDBY_TIMEOUT = 5000;
+    private static final String TEST_PKG1 = "PKG1";
+    private static final String TEST_PKG2 = "PKG2";
+    private static final int TEST_PKG1_APP_ID = 123;
+    private static final int TEST_PKG2_APP_ID = 456;
+    private static final int USER_ID_1 = 0;
+    private static final int USER_ID_2 = 10;
+    private static final LowPowerStandbyPolicy EMPTY_POLICY = new LowPowerStandbyPolicy(
+            "Test policy", Collections.emptySet(), 0, Collections.emptySet());
+    private static final LowPowerStandbyPortDescription PORT_DESC_1 =
+            new LowPowerStandbyPortDescription(PROTOCOL_UDP, MATCH_PORT_LOCAL, 5353);
+    private static final LowPowerStandbyPortDescription PORT_DESC_2 =
+            new LowPowerStandbyPortDescription(PROTOCOL_TCP, MATCH_PORT_LOCAL, 8008);
+
+    private LowPowerStandbyController mController;
+    private BroadcastInterceptingContext mContextSpy;
+    private Resources mResourcesSpy;
+    private OffsettableClock mClock;
+    private TestLooper mTestLooper;
+    private File mTestPolicyFile;
+
+    @Mock
+    private DeviceConfigWrapper mDeviceConfigWrapperMock;
+    @Mock
+    private IActivityManager mIActivityManagerMock;
+    @Mock
+    private AlarmManager mAlarmManagerMock;
+    @Mock
+    private PackageManager mPackageManagerMock;
+    @Mock
+    private UserManager mUserManagerMock;
+    @Mock
+    private IPowerManager mIPowerManagerMock;
+    @Mock
+    private PowerManagerInternal mPowerManagerInternalMock;
+    @Mock
+    private NetworkPolicyManagerInternal mNetworkPolicyManagerInternalMock;
+    @Mock
+    private PowerAllowlistInternal mPowerAllowlistInternalMock;
+    @Mock
+    private ActivityManagerInternal mActivityManagerInternalMock;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        mContextSpy = spy(new BroadcastInterceptingContext(InstrumentationRegistry.getContext()));
+        when(mContextSpy.getPackageManager()).thenReturn(mPackageManagerMock);
+        when(mContextSpy.getSystemService(AlarmManager.class)).thenReturn(mAlarmManagerMock);
+        when(mContextSpy.getSystemService(UserManager.class)).thenReturn(mUserManagerMock);
+        PowerManager powerManager = new PowerManager(mContextSpy, mIPowerManagerMock, null, null);
+        when(mContextSpy.getSystemService(PowerManager.class)).thenReturn(powerManager);
+        addLocalServiceMock(PowerManagerInternal.class, mPowerManagerInternalMock);
+        addLocalServiceMock(NetworkPolicyManagerInternal.class, mNetworkPolicyManagerInternalMock);
+        addLocalServiceMock(PowerAllowlistInternal.class, mPowerAllowlistInternalMock);
+        addLocalServiceMock(ActivityManagerInternal.class, mActivityManagerInternalMock);
+
+        when(mIPowerManagerMock.isInteractive()).thenReturn(true);
+
+        when(mDeviceConfigWrapperMock.enableCustomPolicy()).thenReturn(true);
+        when(mDeviceConfigWrapperMock.enableStandbyPorts()).thenReturn(true);
+        mResourcesSpy = spy(mContextSpy.getResources());
+        when(mContextSpy.getResources()).thenReturn(mResourcesSpy);
+        when(mResourcesSpy.getBoolean(
+                com.android.internal.R.bool.config_lowPowerStandbySupported))
+                .thenReturn(true);
+        when(mResourcesSpy.getInteger(
+                com.android.internal.R.integer.config_lowPowerStandbyNonInteractiveTimeout))
+                .thenReturn(STANDBY_TIMEOUT);
+        when(mResourcesSpy.getBoolean(
+                com.android.internal.R.bool.config_lowPowerStandbyEnabledByDefault))
+                .thenReturn(false);
+
+        FakeSettingsProvider.clearSettingsProvider();
+        MockContentResolver cr = new MockContentResolver(mContextSpy);
+        cr.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
+        when(mContextSpy.getContentResolver()).thenReturn(cr);
+
+        when(mUserManagerMock.getUserHandles(true)).thenReturn(List.of(
+                UserHandle.of(USER_ID_1), UserHandle.of(USER_ID_2)));
+        when(mPackageManagerMock.getPackageUid(eq(TEST_PKG1), any())).thenReturn(TEST_PKG1_APP_ID);
+        when(mPackageManagerMock.getPackageUid(eq(TEST_PKG2), any())).thenReturn(TEST_PKG2_APP_ID);
+        when(mPackageManagerMock.getPackageUidAsUser(eq(TEST_PKG1), eq(USER_ID_1)))
+                .thenReturn(TEST_PKG1_APP_ID);
+        when(mPackageManagerMock.getPackageUidAsUser(eq(TEST_PKG2), eq(USER_ID_1)))
+                .thenReturn(TEST_PKG2_APP_ID);
+
+        mClock = new OffsettableClock.Stopped();
+        mTestLooper = new TestLooper(mClock::now);
+
+        mTestPolicyFile = new File(mContextSpy.getCacheDir(), "lps_policy.xml");
+        mController = new LowPowerStandbyController(mContextSpy, mTestLooper.getLooper(),
+                () -> mClock.now(), mDeviceConfigWrapperMock, () -> mIActivityManagerMock,
+                mTestPolicyFile);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        LocalServices.removeServiceForTest(PowerManagerInternal.class);
+        LocalServices.removeServiceForTest(LowPowerStandbyControllerInternal.class);
+        LocalServices.removeServiceForTest(NetworkPolicyManagerInternal.class);
+        LocalServices.removeServiceForTest(PowerAllowlistInternal.class);
+        LocalServices.removeServiceForTest(ActivityManagerInternal.class);
+
+        mTestPolicyFile.delete();
+    }
+
+    @Test
+    public void testOnSystemReady_isInactivate() {
+        setLowPowerStandbySupportedConfig(true);
+        mController.systemReady();
+
+        assertThat(mController.isActive()).isFalse();
+        verify(mPowerManagerInternalMock, never()).setLowPowerStandbyActive(anyBoolean());
+        verify(mNetworkPolicyManagerInternalMock, never()).setLowPowerStandbyActive(anyBoolean());
+    }
+
+    @Test
+    public void testActivate() throws Exception {
+        setLowPowerStandbySupportedConfig(true);
+        mController.systemReady();
+        mController.setEnabled(true);
+        setNonInteractive();
+        setDeviceIdleMode(true);
+        awaitStandbyTimeoutAlarm();
+        assertThat(mController.isActive()).isTrue();
+        verify(mPowerManagerInternalMock, times(1)).setLowPowerStandbyActive(true);
+        verify(mNetworkPolicyManagerInternalMock, times(1)).setLowPowerStandbyActive(true);
+    }
+
+    private void awaitStandbyTimeoutAlarm() {
+        ArgumentCaptor<Long> timeArg = ArgumentCaptor.forClass(Long.class);
+        ArgumentCaptor<AlarmManager.OnAlarmListener> listenerArg =
+                ArgumentCaptor.forClass(AlarmManager.OnAlarmListener.class);
+        verify(mAlarmManagerMock).setExact(
+                eq(AlarmManager.ELAPSED_REALTIME_WAKEUP),
+                timeArg.capture(), anyString(),
+                listenerArg.capture(), any());
+        mClock.reset();
+        mClock.fastForward(timeArg.getValue());
+        listenerArg.getValue().onAlarm();
+        mTestLooper.dispatchAll();
+    }
+
+    @Test
+    public void testOnNonInteractive_notImmediatelyActive() throws Exception {
+        setLowPowerStandbySupportedConfig(true);
+        mController.systemReady();
+        mController.setEnabled(true);
+
+        setNonInteractive();
+        mTestLooper.dispatchAll();
+
+        assertThat(mController.isActive()).isFalse();
+        verify(mPowerManagerInternalMock, never()).setLowPowerStandbyActive(anyBoolean());
+        verify(mNetworkPolicyManagerInternalMock, never()).setLowPowerStandbyActive(anyBoolean());
+    }
+
+    @Test
+    public void testOnNonInteractive_activateAfterStandbyTimeout() throws Exception {
+        setLowPowerStandbySupportedConfig(true);
+        mController.systemReady();
+        mController.setEnabled(true);
+
+        setNonInteractive();
+        awaitStandbyTimeoutAlarm();
+
+        assertThat(mController.isActive()).isTrue();
+        verify(mPowerManagerInternalMock, times(1)).setLowPowerStandbyActive(true);
+        verify(mNetworkPolicyManagerInternalMock, times(1)).setLowPowerStandbyActive(true);
+    }
+
+    @Test
+    public void testOnNonInteractive_doesNotActivateWhenBecomingInteractive() throws Exception {
+        setLowPowerStandbySupportedConfig(true);
+        mController.systemReady();
+        mController.setEnabled(true);
+
+        setNonInteractive();
+        advanceTime(STANDBY_TIMEOUT / 2);
+        setInteractive();
+        verifyStandbyAlarmCancelled();
+
+        assertThat(mController.isActive()).isFalse();
+        verify(mPowerManagerInternalMock, never()).setLowPowerStandbyActive(anyBoolean());
+        verify(mNetworkPolicyManagerInternalMock, never()).setLowPowerStandbyActive(anyBoolean());
+    }
+
+    private void verifyStandbyAlarmCancelled() {
+        InOrder inOrder = inOrder(mAlarmManagerMock);
+        inOrder.verify(mAlarmManagerMock, atLeast(0)).setExact(anyInt(), anyLong(), anyString(),
+                any(), any());
+        inOrder.verify(mAlarmManagerMock).cancel((AlarmManager.OnAlarmListener) any());
+        inOrder.verifyNoMoreInteractions();
+    }
+
+    @Test
+    public void testOnInteractive_deactivate() throws Exception {
+        setLowPowerStandbySupportedConfig(true);
+        mController.systemReady();
+        mController.setEnabled(true);
+        setNonInteractive();
+        setDeviceIdleMode(true);
+        awaitStandbyTimeoutAlarm();
+
+        setInteractive();
+        mTestLooper.dispatchAll();
+
+        assertThat(mController.isActive()).isFalse();
+        verify(mPowerManagerInternalMock, times(1)).setLowPowerStandbyActive(false);
+        verify(mNetworkPolicyManagerInternalMock, times(1)).setLowPowerStandbyActive(false);
+    }
+
+    @Test
+    public void testOnDozeMaintenance_deactivate() throws Exception {
+        setLowPowerStandbySupportedConfig(true);
+        mController.systemReady();
+        mController.setEnabled(true);
+        mController.setActiveDuringMaintenance(false);
+        setNonInteractive();
+        setDeviceIdleMode(true);
+        awaitStandbyTimeoutAlarm();
+
+        setDeviceIdleMode(false);
+        mTestLooper.dispatchAll();
+
+        assertThat(mController.isActive()).isFalse();
+        verify(mPowerManagerInternalMock, times(1)).setLowPowerStandbyActive(false);
+        verify(mNetworkPolicyManagerInternalMock, times(1)).setLowPowerStandbyActive(false);
+    }
+
+    @Test
+    public void testOnDozeMaintenance_activeDuringMaintenance_staysActive() throws Exception {
+        setLowPowerStandbySupportedConfig(true);
+        mController.systemReady();
+        mController.setEnabled(true);
+        mController.setActiveDuringMaintenance(true);
+        setNonInteractive();
+        setDeviceIdleMode(true);
+        awaitStandbyTimeoutAlarm();
+
+        setDeviceIdleMode(false);
+        mTestLooper.dispatchAll();
+
+        assertThat(mController.isActive()).isTrue();
+        verify(mPowerManagerInternalMock, never()).setLowPowerStandbyActive(false);
+        verify(mNetworkPolicyManagerInternalMock, never()).setLowPowerStandbyActive(false);
+    }
+
+    @Test
+    public void testOnDozeMaintenanceEnds_activate() throws Exception {
+        setLowPowerStandbySupportedConfig(true);
+        mController.systemReady();
+        mController.setEnabled(true);
+        setNonInteractive();
+        setDeviceIdleMode(true);
+        awaitStandbyTimeoutAlarm();
+
+        setDeviceIdleMode(false);
+        advanceTime(1000);
+        setDeviceIdleMode(true);
+        mTestLooper.dispatchAll();
+
+        assertThat(mController.isActive()).isTrue();
+        verify(mPowerManagerInternalMock, times(2)).setLowPowerStandbyActive(true);
+        verify(mNetworkPolicyManagerInternalMock, times(2)).setLowPowerStandbyActive(true);
+    }
+
+    @Test
+    public void testLowPowerStandbyDisabled_doesNotActivate() throws Exception {
+        setLowPowerStandbySupportedConfig(true);
+        mController.systemReady();
+        mController.setEnabled(false);
+        setNonInteractive();
+
+        assertThat(mController.isActive()).isFalse();
+        verify(mAlarmManagerMock, never()).setExact(anyInt(), anyLong(), anyString(), any(), any());
+        verify(mPowerManagerInternalMock, never()).setLowPowerStandbyActive(anyBoolean());
+        verify(mNetworkPolicyManagerInternalMock, never()).setLowPowerStandbyActive(anyBoolean());
+    }
+
+    @Test
+    public void testLowPowerStandbyEnabled_EnabledChangedBroadcastsAreSent() throws Exception {
+        setLowPowerStandbySupportedConfig(true);
+        mController.systemReady();
+
+        BroadcastInterceptingContext.FutureIntent futureIntent = mContextSpy.nextBroadcastIntent(
+                PowerManager.ACTION_LOW_POWER_STANDBY_ENABLED_CHANGED);
+        mController.setEnabled(false);
+        futureIntent.assertNotReceived();
+
+        futureIntent = mContextSpy.nextBroadcastIntent(
+                PowerManager.ACTION_LOW_POWER_STANDBY_ENABLED_CHANGED);
+        mController.setEnabled(true);
+        assertThat(futureIntent.get(1, TimeUnit.SECONDS)).isNotNull();
+
+        futureIntent = mContextSpy.nextBroadcastIntent(
+                PowerManager.ACTION_LOW_POWER_STANDBY_ENABLED_CHANGED);
+        mController.setEnabled(true);
+        futureIntent.assertNotReceived();
+
+        futureIntent = mContextSpy.nextBroadcastIntent(
+                PowerManager.ACTION_LOW_POWER_STANDBY_ENABLED_CHANGED);
+
+        mController.setEnabled(false);
+        assertThat(futureIntent.get(1, TimeUnit.SECONDS)).isNotNull();
+    }
+
+    @Test
+    public void testSetEnabled_WhenNotSupported_DoesNotEnable() throws Exception {
+        setLowPowerStandbySupportedConfig(false);
+        mController.systemReady();
+
+        mController.setEnabled(true);
+
+        assertThat(mController.isEnabled()).isFalse();
+    }
+
+    @Test
+    public void testIsSupported_WhenSupported() throws Exception {
+        setLowPowerStandbySupportedConfig(true);
+        mController.systemReady();
+
+        assertThat(mController.isSupported()).isTrue();
+    }
+
+    @Test
+    public void testIsSupported_WhenNotSupported() throws Exception {
+        setLowPowerStandbySupportedConfig(false);
+        mController.systemReady();
+
+        assertThat(mController.isSupported()).isFalse();
+    }
+
+    @Test
+    public void testForceActive() throws Exception {
+        setLowPowerStandbySupportedConfig(false);
+        mController.systemReady();
+
+        mController.forceActive(true);
+        mTestLooper.dispatchAll();
+
+        assertThat(mController.isActive()).isTrue();
+        verify(mPowerManagerInternalMock).setLowPowerStandbyActive(true);
+        verify(mNetworkPolicyManagerInternalMock).setLowPowerStandbyActive(true);
+
+        mController.forceActive(false);
+        mTestLooper.dispatchAll();
+
+        assertThat(mController.isActive()).isFalse();
+        verify(mPowerManagerInternalMock).setLowPowerStandbyActive(false);
+        verify(mNetworkPolicyManagerInternalMock).setLowPowerStandbyActive(false);
+    }
+
+    private void setLowPowerStandbySupportedConfig(boolean supported) {
+        when(mResourcesSpy.getBoolean(
+                com.android.internal.R.bool.config_lowPowerStandbySupported))
+                .thenReturn(supported);
+    }
+
+    @Test
+    public void testSetPolicy() throws Exception {
+        mController.systemReady();
+        mController.setPolicy(EMPTY_POLICY);
+        assertThat(mController.getPolicy()).isEqualTo(EMPTY_POLICY);
+    }
+
+    @Test
+    public void testSetDefaultPolicy() throws Exception {
+        mController.systemReady();
+        mController.setPolicy(EMPTY_POLICY);
+        mController.setPolicy(null);
+        assertThat(mController.getPolicy()).isNotNull();
+        assertThat(mController.getPolicy()).isEqualTo(LowPowerStandbyController.DEFAULT_POLICY);
+    }
+
+    @Test
+    public void testAddToAllowlist_ReasonIsAllowed_servicesAreNotified() throws Exception {
+        mController.systemReady();
+        mController.setPolicy(
+                policyWithAllowedReasons(LOW_POWER_STANDBY_ALLOWED_REASON_VOICE_INTERACTION));
+
+        LowPowerStandbyControllerInternal service = LocalServices.getService(
+                LowPowerStandbyControllerInternal.class);
+        service.addToAllowlist(10, LOW_POWER_STANDBY_ALLOWED_REASON_VOICE_INTERACTION);
+        mTestLooper.dispatchAll();
+        verify(mPowerManagerInternalMock).setLowPowerStandbyAllowlist(new int[]{10});
+        verify(mNetworkPolicyManagerInternalMock).setLowPowerStandbyAllowlist(new int[]{10});
+
+        service.removeFromAllowlist(10, LOW_POWER_STANDBY_ALLOWED_REASON_VOICE_INTERACTION);
+        mTestLooper.dispatchAll();
+        verify(mPowerManagerInternalMock).setLowPowerStandbyAllowlist(new int[]{});
+        verify(mNetworkPolicyManagerInternalMock).setLowPowerStandbyAllowlist(new int[]{});
+    }
+
+    @Test
+    public void testRemoveFromAllowlist_ReasonIsAllowed_servicesAreNotified() throws Exception {
+        mController.systemReady();
+        mController.setPolicy(
+                policyWithAllowedReasons(LOW_POWER_STANDBY_ALLOWED_REASON_VOICE_INTERACTION));
+
+        LowPowerStandbyControllerInternal service = LocalServices.getService(
+                LowPowerStandbyControllerInternal.class);
+        service.addToAllowlist(10, LOW_POWER_STANDBY_ALLOWED_REASON_VOICE_INTERACTION);
+        mTestLooper.dispatchAll();
+
+        service.removeFromAllowlist(10, LOW_POWER_STANDBY_ALLOWED_REASON_VOICE_INTERACTION);
+        mTestLooper.dispatchAll();
+        verify(mPowerManagerInternalMock).setLowPowerStandbyAllowlist(new int[]{});
+        verify(mNetworkPolicyManagerInternalMock).setLowPowerStandbyAllowlist(new int[]{});
+    }
+
+    @Test
+    public void testSetAllowReasons_ActiveExemptionsNoLongerAllowed_servicesAreNotified() {
+        mController.systemReady();
+        mController.setEnabled(true);
+        mController.setPolicy(
+                policyWithAllowedReasons(LOW_POWER_STANDBY_ALLOWED_REASON_VOICE_INTERACTION));
+
+        LowPowerStandbyControllerInternal service = LocalServices.getService(
+                LowPowerStandbyControllerInternal.class);
+        service.addToAllowlist(10, LOW_POWER_STANDBY_ALLOWED_REASON_VOICE_INTERACTION);
+        mTestLooper.dispatchAll();
+
+        mController.setPolicy(EMPTY_POLICY);
+        mTestLooper.dispatchAll();
+
+        verify(mPowerManagerInternalMock).setLowPowerStandbyAllowlist(new int[]{});
+        verify(mNetworkPolicyManagerInternalMock).setLowPowerStandbyAllowlist(new int[]{});
+    }
+
+    @Test
+    public void testSetAllowReasons_ReasonBecomesAllowed_servicesAreNotified() throws Exception {
+        mController.systemReady();
+        mController.setEnabled(true);
+        mController.setPolicy(EMPTY_POLICY);
+
+        LowPowerStandbyControllerInternal service = LocalServices.getService(
+                LowPowerStandbyControllerInternal.class);
+        service.addToAllowlist(10, LOW_POWER_STANDBY_ALLOWED_REASON_VOICE_INTERACTION);
+        mTestLooper.dispatchAll();
+
+        verify(mPowerManagerInternalMock, never()).setLowPowerStandbyAllowlist(any());
+        verify(mNetworkPolicyManagerInternalMock, never()).setLowPowerStandbyAllowlist(any());
+
+        mController.setPolicy(
+                policyWithAllowedReasons(LOW_POWER_STANDBY_ALLOWED_REASON_VOICE_INTERACTION));
+        mTestLooper.dispatchAll();
+
+        verify(mPowerManagerInternalMock).setLowPowerStandbyAllowlist(new int[]{10});
+        verify(mNetworkPolicyManagerInternalMock).setLowPowerStandbyAllowlist(new int[]{10});
+    }
+
+    @Test
+    public void testSetAllowReasons_NoActiveExemptions_servicesAreNotNotified() throws Exception {
+        mController.systemReady();
+        mController.setEnabled(true);
+        mController.setPolicy(
+                policyWithAllowedReasons(LOW_POWER_STANDBY_ALLOWED_REASON_VOICE_INTERACTION));
+        mController.setPolicy(EMPTY_POLICY);
+        mTestLooper.dispatchAll();
+
+        verify(mPowerManagerInternalMock, never()).setLowPowerStandbyAllowlist(any());
+        verify(mNetworkPolicyManagerInternalMock, never()).setLowPowerStandbyAllowlist(any());
+    }
+
+    @Test
+    public void testSetAllowedFeatures_isAllowedIfDisabled() throws Exception {
+        mController.systemReady();
+        mController.setEnabled(false);
+        mTestLooper.dispatchAll();
+
+        assertTrue(mController.isAllowed(FEATURE_WAKE_ON_LAN_IN_LOW_POWER_STANDBY));
+    }
+
+    @Test
+    public void testSetAllowedFeatures_isAllowedWhenEnabled() throws Exception {
+        mController.systemReady();
+        mController.setEnabled(true);
+        mController.setPolicy(policyWithAllowedFeatures(FEATURE_WAKE_ON_LAN_IN_LOW_POWER_STANDBY));
+        mTestLooper.dispatchAll();
+
+        assertTrue(mController.isAllowed(FEATURE_WAKE_ON_LAN_IN_LOW_POWER_STANDBY));
+    }
+
+    @Test
+    public void testSetAllowedFeatures_isNotAllowed() throws Exception {
+        mController.systemReady();
+        mController.setEnabled(true);
+        mTestLooper.dispatchAll();
+
+        assertFalse(mController.isAllowed(FEATURE_WAKE_ON_LAN_IN_LOW_POWER_STANDBY));
+    }
+
+    @Test
+    public void testSetExemptPackages_uidPerUserIsExempt() throws Exception {
+        mController.systemReady();
+        mController.setEnabled(true);
+        mController.setPolicy(policyWithExemptPackages(TEST_PKG1, TEST_PKG2));
+        mTestLooper.dispatchAll();
+
+        int[] expectedUidAllowlist = {
+                UserHandle.getUid(USER_ID_1, TEST_PKG1_APP_ID),
+                UserHandle.getUid(USER_ID_1, TEST_PKG2_APP_ID),
+                UserHandle.getUid(USER_ID_2, TEST_PKG1_APP_ID),
+                UserHandle.getUid(USER_ID_2, TEST_PKG2_APP_ID)
+        };
+        verify(mPowerManagerInternalMock).setLowPowerStandbyAllowlist(expectedUidAllowlist);
+        verify(mNetworkPolicyManagerInternalMock).setLowPowerStandbyAllowlist(expectedUidAllowlist);
+    }
+
+    @Test
+    public void testExemptPackageIsRemoved_servicesAreNotified() throws Exception {
+        mController.systemReady();
+        mController.setEnabled(true);
+        mController.setPolicy(policyWithExemptPackages(TEST_PKG1));
+        mTestLooper.dispatchAll();
+
+        int[] expectedUidAllowlist = {
+                UserHandle.getUid(USER_ID_1, TEST_PKG1_APP_ID),
+                UserHandle.getUid(USER_ID_2, TEST_PKG1_APP_ID),
+        };
+        verify(mPowerManagerInternalMock).setLowPowerStandbyAllowlist(expectedUidAllowlist);
+        verify(mNetworkPolicyManagerInternalMock).setLowPowerStandbyAllowlist(expectedUidAllowlist);
+        verifyNoMoreInteractions(mPowerManagerInternalMock, mNetworkPolicyManagerInternalMock);
+
+        reset(mPackageManagerMock);
+        when(mPackageManagerMock.getPackageUid(eq(TEST_PKG1), any()))
+                .thenThrow(PackageManager.NameNotFoundException.class);
+
+        Intent intent = new Intent(Intent.ACTION_PACKAGE_REMOVED);
+        intent.setData(Uri.fromParts(IntentFilter.SCHEME_PACKAGE, TEST_PKG1, null));
+        intent.putExtra(Intent.EXTRA_REPLACING, false);
+        mContextSpy.sendBroadcast(intent);
+        mTestLooper.dispatchAll();
+
+        verify(mPowerManagerInternalMock).setLowPowerStandbyAllowlist(new int[0]);
+        verify(mNetworkPolicyManagerInternalMock).setLowPowerStandbyAllowlist(new int[0]);
+    }
+
+    @Test
+    public void testUsersChanged_packagesExemptForNewUser() throws Exception {
+        mController.systemReady();
+        mController.setEnabled(true);
+        mController.setPolicy(policyWithExemptPackages(TEST_PKG1));
+        mTestLooper.dispatchAll();
+
+        InOrder inOrder = inOrder(mPowerManagerInternalMock);
+
+        inOrder.verify(mPowerManagerInternalMock).setLowPowerStandbyAllowlist(new int[]{
+                UserHandle.getUid(USER_ID_1, TEST_PKG1_APP_ID),
+                UserHandle.getUid(USER_ID_2, TEST_PKG1_APP_ID),
+        });
+        inOrder.verifyNoMoreInteractions();
+
+        when(mUserManagerMock.getUserHandles(true)).thenReturn(List.of(UserHandle.of(USER_ID_1)));
+        Intent intent = new Intent(Intent.ACTION_USER_REMOVED);
+        intent.putExtra(Intent.EXTRA_USER, UserHandle.of(USER_ID_2));
+        mContextSpy.sendBroadcast(intent);
+        mTestLooper.dispatchAll();
+
+        inOrder.verify(mPowerManagerInternalMock).setLowPowerStandbyAllowlist(new int[]{
+                UserHandle.getUid(USER_ID_1, TEST_PKG1_APP_ID)
+        });
+        inOrder.verifyNoMoreInteractions();
+
+        when(mUserManagerMock.getUserHandles(true)).thenReturn(
+                List.of(UserHandle.of(USER_ID_1), UserHandle.of(USER_ID_2)));
+        intent = new Intent(Intent.ACTION_USER_ADDED);
+        intent.putExtra(Intent.EXTRA_USER, UserHandle.of(USER_ID_2));
+        mContextSpy.sendBroadcast(intent);
+        mTestLooper.dispatchAll();
+
+        inOrder.verify(mPowerManagerInternalMock).setLowPowerStandbyAllowlist(new int[]{
+                UserHandle.getUid(USER_ID_1, TEST_PKG1_APP_ID),
+                UserHandle.getUid(USER_ID_2, TEST_PKG1_APP_ID)
+        });
+        inOrder.verifyNoMoreInteractions();
+    }
+
+    @Test
+    public void testIsExempt_exemptIfDisabled() throws Exception {
+        mController.systemReady();
+        mController.setEnabled(false);
+        mTestLooper.dispatchAll();
+
+        assertTrue(mController.isPackageExempt(TEST_PKG1_APP_ID));
+    }
+
+    @Test
+    public void testIsExempt_notExemptIfEnabled() throws Exception {
+        mController.systemReady();
+        mController.setEnabled(true);
+        mTestLooper.dispatchAll();
+
+        assertFalse(mController.isPackageExempt(TEST_PKG1_APP_ID));
+    }
+
+    @Test
+    public void testAllowReason_tempPowerSaveAllowlist() throws Exception {
+        mController.systemReady();
+        mController.setEnabled(true);
+        mController.setPolicy(policyWithAllowedReasons(
+                LOW_POWER_STANDBY_ALLOWED_REASON_TEMP_POWER_SAVE_ALLOWLIST));
+        mTestLooper.dispatchAll();
+
+        ArgumentCaptor<TempAllowlistChangeListener> tempAllowlistChangeListenerArgumentCaptor =
+                ArgumentCaptor.forClass(TempAllowlistChangeListener.class);
+        verify(mPowerAllowlistInternalMock).registerTempAllowlistChangeListener(
+                tempAllowlistChangeListenerArgumentCaptor.capture());
+        TempAllowlistChangeListener tempAllowlistChangeListener =
+                tempAllowlistChangeListenerArgumentCaptor.getValue();
+
+        tempAllowlistChangeListener.onAppAdded(TEST_PKG1_APP_ID);
+        mTestLooper.dispatchAll();
+        verify(mPowerManagerInternalMock).setLowPowerStandbyAllowlist(new int[]{TEST_PKG1_APP_ID});
+
+        tempAllowlistChangeListener.onAppAdded(TEST_PKG2_APP_ID);
+        mTestLooper.dispatchAll();
+        verify(mPowerManagerInternalMock).setLowPowerStandbyAllowlist(
+                new int[]{TEST_PKG1_APP_ID, TEST_PKG2_APP_ID});
+
+        tempAllowlistChangeListener.onAppRemoved(TEST_PKG1_APP_ID);
+        mTestLooper.dispatchAll();
+        verify(mPowerManagerInternalMock).setLowPowerStandbyAllowlist(new int[]{TEST_PKG2_APP_ID});
+
+        mController.setPolicy(EMPTY_POLICY);
+        mTestLooper.dispatchAll();
+        verify(mPowerManagerInternalMock).setLowPowerStandbyAllowlist(new int[0]);
+    }
+
+    @Test
+    public void testAllowReason_ongoingPhoneCallService() throws Exception {
+        mController.systemReady();
+        mController.setEnabled(true);
+        mController.setPolicy(policyWithAllowedReasons(
+                LOW_POWER_STANDBY_ALLOWED_REASON_ONGOING_CALL));
+        mTestLooper.dispatchAll();
+
+        ArgumentCaptor<IForegroundServiceObserver> fgsObserverCapt =
+                ArgumentCaptor.forClass(IForegroundServiceObserver.class);
+        verify(mIActivityManagerMock).registerForegroundServiceObserver(fgsObserverCapt.capture());
+        IForegroundServiceObserver fgsObserver = fgsObserverCapt.getValue();
+
+        when(mActivityManagerInternalMock.hasRunningForegroundService(eq(TEST_PKG1_APP_ID),
+                eq(ServiceInfo.FOREGROUND_SERVICE_TYPE_PHONE_CALL))).thenReturn(true);
+        fgsObserver.onForegroundStateChanged(null, TEST_PKG1, USER_ID_1, true);
+        mTestLooper.dispatchAll();
+        verify(mPowerManagerInternalMock).setLowPowerStandbyAllowlist(new int[]{TEST_PKG1_APP_ID});
+
+        when(mActivityManagerInternalMock.hasRunningForegroundService(eq(TEST_PKG2_APP_ID),
+                eq(ServiceInfo.FOREGROUND_SERVICE_TYPE_PHONE_CALL))).thenReturn(true);
+        fgsObserver.onForegroundStateChanged(null, TEST_PKG2, USER_ID_1, true);
+        mTestLooper.dispatchAll();
+        verify(mPowerManagerInternalMock).setLowPowerStandbyAllowlist(
+                new int[]{TEST_PKG1_APP_ID, TEST_PKG2_APP_ID});
+
+        when(mActivityManagerInternalMock.hasRunningForegroundService(eq(TEST_PKG1_APP_ID),
+                eq(ServiceInfo.FOREGROUND_SERVICE_TYPE_PHONE_CALL))).thenReturn(false);
+        fgsObserver.onForegroundStateChanged(null, TEST_PKG1, USER_ID_1, false);
+        mTestLooper.dispatchAll();
+        verify(mPowerManagerInternalMock).setLowPowerStandbyAllowlist(new int[]{TEST_PKG2_APP_ID});
+
+        mController.setPolicy(EMPTY_POLICY);
+        mTestLooper.dispatchAll();
+        verify(mPowerManagerInternalMock).setLowPowerStandbyAllowlist(new int[0]);
+    }
+
+    @Test
+    public void testStandbyPorts_broadcastChangedIfPackageIsExempt() throws Exception {
+        mController.systemReady();
+        mController.setEnabled(true);
+        mController.setPolicy(policyWithExemptPackages(TEST_PKG1));
+
+        Binder token = new Binder();
+        BroadcastInterceptingContext.FutureIntent futureIntent = mContextSpy.nextBroadcastIntent(
+                PowerManager.ACTION_LOW_POWER_STANDBY_PORTS_CHANGED);
+        mController.acquireStandbyPorts(token, TEST_PKG1_APP_ID, List.of(PORT_DESC_1));
+        mTestLooper.dispatchAll();
+        assertThat(futureIntent.get(1, TimeUnit.SECONDS)).isNotNull();
+
+        futureIntent = mContextSpy.nextBroadcastIntent(
+                PowerManager.ACTION_LOW_POWER_STANDBY_PORTS_CHANGED);
+        mController.releaseStandbyPorts(token);
+        mTestLooper.dispatchAll();
+        assertThat(futureIntent.get(1, TimeUnit.SECONDS)).isNotNull();
+    }
+
+    @Test
+    public void testStandbyPorts_noBroadcastChangedIfPackageIsNotExempt() throws Exception {
+        mController.systemReady();
+        mController.setEnabled(true);
+        mController.setPolicy(policyWithExemptPackages(TEST_PKG1));
+
+        BroadcastInterceptingContext.FutureIntent futureIntent = mContextSpy.nextBroadcastIntent(
+                PowerManager.ACTION_LOW_POWER_STANDBY_PORTS_CHANGED);
+        mController.acquireStandbyPorts(new Binder(), TEST_PKG2_APP_ID, List.of(PORT_DESC_1));
+        mTestLooper.dispatchAll();
+        futureIntent.assertNotReceived();
+    }
+
+    @Test
+    public void testActiveStandbyPorts_emptyIfDisabled() throws Exception {
+        mController.systemReady();
+        mController.setEnabled(false);
+        mController.setPolicy(policyWithExemptPackages(TEST_PKG1));
+
+        mController.acquireStandbyPorts(new Binder(), TEST_PKG1_APP_ID, List.of(PORT_DESC_1));
+        assertThat(mController.getActiveStandbyPorts()).isEmpty();
+    }
+
+    @Test
+    public void testActiveStandbyPorts_emptyIfPackageNotExempt() throws Exception {
+        mController.systemReady();
+        mController.setEnabled(true);
+        mController.setPolicy(policyWithExemptPackages(TEST_PKG2));
+
+        mController.acquireStandbyPorts(new Binder(), TEST_PKG1_APP_ID, List.of(PORT_DESC_1));
+        assertThat(mController.getActiveStandbyPorts()).isEmpty();
+    }
+
+    @Test
+    public void testActiveStandbyPorts_activeIfPackageExempt() throws Exception {
+        mController.systemReady();
+        mController.setEnabled(true);
+        mController.setPolicy(policyWithExemptPackages(TEST_PKG1));
+
+        mController.acquireStandbyPorts(new Binder(), TEST_PKG1_APP_ID, List.of(PORT_DESC_1));
+        mController.acquireStandbyPorts(new Binder(), TEST_PKG2_APP_ID, List.of(PORT_DESC_2));
+        assertThat(mController.getActiveStandbyPorts()).containsExactly(PORT_DESC_1);
+    }
+
+    @Test
+    public void testActiveStandbyPorts_removedAfterRelease() throws Exception {
+        mController.systemReady();
+        mController.setEnabled(true);
+        mController.setPolicy(policyWithExemptPackages(TEST_PKG1));
+        Binder token = new Binder();
+        mController.acquireStandbyPorts(token, TEST_PKG1_APP_ID, List.of(PORT_DESC_1));
+        mController.releaseStandbyPorts(token);
+        assertThat(mController.getActiveStandbyPorts()).isEmpty();
+    }
+
+    private void setInteractive() throws Exception {
+        when(mIPowerManagerMock.isInteractive()).thenReturn(true);
+        mContextSpy.sendBroadcast(new Intent(Intent.ACTION_SCREEN_ON));
+    }
+
+    private void setNonInteractive() throws Exception {
+        when(mIPowerManagerMock.isInteractive()).thenReturn(false);
+        mContextSpy.sendBroadcast(new Intent(Intent.ACTION_SCREEN_OFF));
+    }
+
+    private void setDeviceIdleMode(boolean idle) throws Exception {
+        when(mIPowerManagerMock.isDeviceIdleMode()).thenReturn(idle);
+        mContextSpy.sendBroadcast(new Intent(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED));
+    }
+
+    private LowPowerStandbyPolicy policyWithAllowedReasons(int allowedReasons) {
+        return new LowPowerStandbyPolicy(
+                "Test policy",
+                Collections.emptySet(),
+                allowedReasons,
+                Collections.emptySet()
+        );
+    }
+
+    private LowPowerStandbyPolicy policyWithAllowedFeatures(String... allowedFeatures) {
+        return new LowPowerStandbyPolicy(
+                "Test policy",
+                Collections.emptySet(),
+                0,
+                new ArraySet<>(allowedFeatures)
+        );
+    }
+
+    private LowPowerStandbyPolicy policyWithExemptPackages(String... exemptPackages) {
+        return new LowPowerStandbyPolicy(
+                "Test policy",
+                new ArraySet<>(exemptPackages),
+                0,
+                Collections.emptySet()
+        );
+    }
+
+    private void advanceTime(long timeMs) {
+        mClock.fastForward(timeMs);
+        mTestLooper.dispatchAll();
+    }
+
+    /**
+     * Creates a mock and registers it to {@link LocalServices}.
+     */
+    private static <T> void addLocalServiceMock(Class<T> clazz, T mock) {
+        LocalServices.removeServiceForTest(clazz);
+        LocalServices.addService(clazz, mock);
+    }
+}
diff --git a/services/tests/powerservicetests/src/com/android/server/power/NotifierTest.java b/services/tests/powerservicetests/src/com/android/server/power/NotifierTest.java
new file mode 100644
index 0000000..2f03965
--- /dev/null
+++ b/services/tests/powerservicetests/src/com/android/server/power/NotifierTest.java
@@ -0,0 +1,375 @@
+/*
+ * Copyright (C) 2020 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 org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.hardware.SensorManager;
+import android.hardware.display.AmbientDisplayConfiguration;
+import android.os.BatteryStats;
+import android.os.Handler;
+import android.os.IWakeLockCallback;
+import android.os.Looper;
+import android.os.PowerManager;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.VibrationAttributes;
+import android.os.Vibrator;
+import android.os.test.TestLooper;
+import android.provider.Settings;
+import android.testing.TestableContext;
+
+import androidx.test.InstrumentationRegistry;
+
+import com.android.internal.app.IBatteryStats;
+import com.android.server.LocalServices;
+import com.android.server.policy.WindowManagerPolicy;
+import com.android.server.power.batterysaver.BatterySaverController;
+import com.android.server.power.batterysaver.BatterySaverPolicy;
+import com.android.server.power.batterysaver.BatterySavingStats;
+import com.android.server.power.stats.BatteryStatsImpl;
+import com.android.server.statusbar.StatusBarManagerInternal;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Tests for {@link com.android.server.power.Notifier}
+ */
+public class NotifierTest {
+    private static final String SYSTEM_PROPERTY_QUIESCENT = "ro.boot.quiescent";
+    private static final int USER_ID = 0;
+
+    @Mock private BatterySaverController mBatterySaverControllerMock;
+    @Mock private BatterySaverPolicy mBatterySaverPolicyMock;
+    @Mock private PowerManagerService.NativeWrapper mNativeWrapperMock;
+    @Mock private Notifier mNotifierMock;
+    @Mock private WirelessChargerDetector mWirelessChargerDetectorMock;
+    @Mock private AmbientDisplayConfiguration mAmbientDisplayConfigurationMock;
+    @Mock private SystemPropertiesWrapper mSystemPropertiesMock;
+    @Mock private InattentiveSleepWarningController mInattentiveSleepWarningControllerMock;
+    @Mock private BatteryStatsImpl mBatteryStats;
+    @Mock private Vibrator mVibrator;
+    @Mock private StatusBarManagerInternal mStatusBarManagerInternal;
+
+    private PowerManagerService mService;
+    private Context mContextSpy;
+    private Resources mResourcesSpy;
+    private TestLooper mTestLooper = new TestLooper();
+    private FakeExecutor mTestExecutor = new FakeExecutor();
+    private Notifier mNotifier;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        LocalServices.removeServiceForTest(StatusBarManagerInternal.class);
+        LocalServices.addService(StatusBarManagerInternal.class, mStatusBarManagerInternal);
+
+        mContextSpy = spy(new TestableContext(InstrumentationRegistry.getContext()));
+        mResourcesSpy = spy(mContextSpy.getResources());
+        when(mContextSpy.getResources()).thenReturn(mResourcesSpy);
+        when(mSystemPropertiesMock.get(eq(SYSTEM_PROPERTY_QUIESCENT), anyString())).thenReturn("");
+        when(mContextSpy.getSystemService(Vibrator.class)).thenReturn(mVibrator);
+
+        mService = new PowerManagerService(mContextSpy, mInjector);
+    }
+
+    @Test
+    public void testVibrateEnabled_wiredCharging() {
+        createNotifier();
+
+        // GIVEN the charging vibration is enabled
+        enableChargingVibration(true);
+
+        // WHEN wired charging starts
+        mNotifier.onWiredChargingStarted(USER_ID);
+        mTestLooper.dispatchAll();
+        mTestExecutor.simulateAsyncExecutionOfLastCommand();
+
+        // THEN the device vibrates once
+        verify(mVibrator, times(1)).vibrate(any(), any(VibrationAttributes.class));
+    }
+
+    @Test
+    public void testVibrateDisabled_wiredCharging() {
+        createNotifier();
+
+        // GIVEN the charging vibration is disabled
+        enableChargingVibration(false);
+
+        // WHEN wired charging starts
+        mNotifier.onWiredChargingStarted(USER_ID);
+        mTestLooper.dispatchAll();
+        mTestExecutor.simulateAsyncExecutionOfLastCommand();
+
+        // THEN the device doesn't vibrate
+        verify(mVibrator, never()).vibrate(any(), any(VibrationAttributes.class));
+    }
+
+    @Test
+    public void testVibrateEnabled_wirelessCharging() {
+        createNotifier();
+
+        // GIVEN the charging vibration is enabled
+        enableChargingVibration(true);
+
+        // WHEN wireless charging starts
+        mNotifier.onWirelessChargingStarted(5, USER_ID);
+        mTestLooper.dispatchAll();
+        mTestExecutor.simulateAsyncExecutionOfLastCommand();
+
+        // THEN the device vibrates once
+        verify(mVibrator, times(1)).vibrate(any(), any(VibrationAttributes.class));
+    }
+
+    @Test
+    public void testVibrateDisabled_wirelessCharging() {
+        createNotifier();
+
+        // GIVEN the charging vibration is disabeld
+        enableChargingVibration(false);
+
+        // WHEN wireless charging starts
+        mNotifier.onWirelessChargingStarted(5, USER_ID);
+        mTestLooper.dispatchAll();
+        mTestExecutor.simulateAsyncExecutionOfLastCommand();
+
+        // THEN the device doesn't vibrate
+        verify(mVibrator, never()).vibrate(any(), any(VibrationAttributes.class));
+    }
+
+    @Test
+    public void testVibrateEnabled_dndOn() {
+        createNotifier();
+
+        // GIVEN the charging vibration is enabled but dnd is on
+        enableChargingVibration(true);
+        enableChargingFeedback(
+                /* chargingFeedbackEnabled */ true,
+                /* dndOn */ true);
+
+        // WHEN wired charging starts
+        mNotifier.onWiredChargingStarted(USER_ID);
+        mTestLooper.dispatchAll();
+        mTestExecutor.simulateAsyncExecutionOfLastCommand();
+
+        // THEN the device doesn't vibrate
+        verify(mVibrator, never()).vibrate(any(), any(VibrationAttributes.class));
+    }
+
+    @Test
+    public void testWirelessAnimationEnabled() {
+        // GIVEN the wireless charging animation is enabled
+        when(mResourcesSpy.getBoolean(
+                com.android.internal.R.bool.config_showBuiltinWirelessChargingAnim))
+                .thenReturn(true);
+        createNotifier();
+
+        // WHEN wireless charging starts
+        mNotifier.onWirelessChargingStarted(5, USER_ID);
+        mTestLooper.dispatchAll();
+        mTestExecutor.simulateAsyncExecutionOfLastCommand();
+
+        // THEN the charging animation is triggered
+        verify(mStatusBarManagerInternal, times(1)).showChargingAnimation(5);
+    }
+
+    @Test
+    public void testWirelessAnimationDisabled() {
+        // GIVEN the wireless charging animation is disabled
+        when(mResourcesSpy.getBoolean(
+                com.android.internal.R.bool.config_showBuiltinWirelessChargingAnim))
+                .thenReturn(false);
+        createNotifier();
+
+        // WHEN wireless charging starts
+        mNotifier.onWirelessChargingStarted(5, USER_ID);
+        mTestLooper.dispatchAll();
+        mTestExecutor.simulateAsyncExecutionOfLastCommand();
+
+        // THEN the charging animation never gets called
+        verify(mStatusBarManagerInternal, never()).showChargingAnimation(anyInt());
+    }
+
+    @Test
+    public void testOnWakeLockListener_RemoteException_NoRethrow() {
+        createNotifier();
+
+        IWakeLockCallback exceptingCallback = new IWakeLockCallback.Stub() {
+            @Override public void onStateChanged(boolean enabled) throws RemoteException {
+                throw new RemoteException("Just testing");
+            }
+        };
+
+        final int uid = 1234;
+        final int pid = 5678;
+        mNotifier.onWakeLockReleased(PowerManager.PARTIAL_WAKE_LOCK, "wakelockTag",
+                "my.package.name", uid, pid, /* workSource= */ null, /* historyTag= */ null,
+                exceptingCallback);
+        mNotifier.onWakeLockAcquired(PowerManager.PARTIAL_WAKE_LOCK, "wakelockTag",
+                "my.package.name", uid, pid, /* workSource= */ null, /* historyTag= */ null,
+                exceptingCallback);
+        mNotifier.onWakeLockChanging(PowerManager.PARTIAL_WAKE_LOCK, "wakelockTag",
+                "my.package.name", uid, pid, /* workSource= */ null, /* historyTag= */ null,
+                exceptingCallback,
+                PowerManager.SCREEN_BRIGHT_WAKE_LOCK, "wakelockTag",
+                "my.package.name", uid, pid, /* newWorkSource= */ null, /* newHistoryTag= */ null,
+                exceptingCallback);
+        mTestLooper.dispatchAll();
+        // If we didn't throw, we're good!
+    }
+
+    private final PowerManagerService.Injector mInjector = new PowerManagerService.Injector() {
+        @Override
+        Notifier createNotifier(Looper looper, Context context, IBatteryStats batteryStats,
+                SuspendBlocker suspendBlocker, WindowManagerPolicy policy,
+                FaceDownDetector faceDownDetector, ScreenUndimDetector screenUndimDetector,
+                Executor backgroundExecutor) {
+            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
+        PowerManagerService.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
+        void invalidateIsInteractiveCaches() {
+            // Avoids an SELinux denial.
+        }
+    };
+
+    private void enableChargingFeedback(boolean chargingFeedbackEnabled, boolean dndOn) {
+        // enable/disable charging feedback
+        Settings.Secure.putIntForUser(
+                mContextSpy.getContentResolver(),
+                Settings.Secure.CHARGING_SOUNDS_ENABLED,
+                chargingFeedbackEnabled ? 1 : 0,
+                USER_ID);
+
+        // toggle on/off dnd
+        Settings.Global.putInt(
+                mContextSpy.getContentResolver(),
+                Settings.Global.ZEN_MODE,
+                dndOn ? Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS
+                        : Settings.Global.ZEN_MODE_OFF);
+    }
+
+    private void enableChargingVibration(boolean enable) {
+        enableChargingFeedback(true, false);
+
+        Settings.Secure.putIntForUser(
+                mContextSpy.getContentResolver(),
+                Settings.Secure.CHARGING_VIBRATION_ENABLED,
+                enable ? 1 : 0,
+                USER_ID);
+    }
+
+    private void createNotifier() {
+        mNotifier = new Notifier(
+                mTestLooper.getLooper(),
+                mContextSpy,
+                IBatteryStats.Stub.asInterface(ServiceManager.getService(
+                        BatteryStats.SERVICE_NAME)),
+                mInjector.createSuspendBlocker(mService, "testBlocker"),
+                null,
+                null,
+                null,
+                mTestExecutor);
+    }
+
+    private static class FakeExecutor implements Executor {
+        private Runnable mLastCommand;
+
+        @Override
+        public void execute(Runnable command) {
+            assertNull(mLastCommand);
+            assertNotNull(command);
+            mLastCommand = command;
+        }
+
+        public Runnable getAndResetLastCommand() {
+            Runnable toReturn = mLastCommand;
+            mLastCommand = null;
+            return toReturn;
+        }
+
+        public void simulateAsyncExecutionOfLastCommand() {
+            Runnable toRun = getAndResetLastCommand();
+            if (toRun != null) {
+                toRun.run();
+            }
+        }
+    }
+
+}
diff --git a/services/tests/powerservicetests/src/com/android/server/power/PowerGroupTest.java b/services/tests/powerservicetests/src/com/android/server/power/PowerGroupTest.java
new file mode 100644
index 0000000..fe31b9c
--- /dev/null
+++ b/services/tests/powerservicetests/src/com/android/server/power/PowerGroupTest.java
@@ -0,0 +1,574 @@
+/*
+ * 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 android.hardware.display.DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT;
+import static android.hardware.display.DisplayManagerInternal.DisplayPowerRequest.POLICY_DIM;
+import static android.hardware.display.DisplayManagerInternal.DisplayPowerRequest.POLICY_DOZE;
+import static android.hardware.display.DisplayManagerInternal.DisplayPowerRequest.POLICY_OFF;
+import static android.os.PowerManager.GO_TO_SLEEP_REASON_APPLICATION;
+import static android.os.PowerManager.GO_TO_SLEEP_REASON_DEVICE_ADMIN;
+import static android.os.PowerManager.GO_TO_SLEEP_REASON_DEVICE_FOLD;
+import static android.os.PowerManager.GO_TO_SLEEP_REASON_TIMEOUT;
+import static android.os.PowerManager.WAKE_REASON_GESTURE;
+import static android.os.PowerManager.WAKE_REASON_PLUGGED_IN;
+import static android.os.PowerManagerInternal.WAKEFULNESS_ASLEEP;
+import static android.os.PowerManagerInternal.WAKEFULNESS_AWAKE;
+import static android.os.PowerManagerInternal.WAKEFULNESS_DOZING;
+import static android.os.PowerManagerInternal.WAKEFULNESS_DREAMING;
+
+import static com.android.server.power.PowerManagerService.USER_ACTIVITY_SCREEN_BRIGHT;
+import static com.android.server.power.PowerManagerService.WAKE_LOCK_DOZE;
+import static com.android.server.power.PowerManagerService.WAKE_LOCK_SCREEN_BRIGHT;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import android.hardware.display.DisplayManagerInternal;
+import android.os.PowerManager;
+import android.os.PowerSaveState;
+import android.view.Display;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.internal.util.LatencyTracker;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Tests for {@link com.android.server.power.PowerGroup}.
+ *
+ * Build/Install/Run:
+ *  atest FrameworksServicesTests:PowerManagerServiceTest
+ */
+public class PowerGroupTest {
+
+    private static final int GROUP_ID = 0;
+    private static final int UID = 11;
+    private static final long TIMESTAMP_CREATE = 1;
+    private static final long TIMESTAMP1 = 999;
+    private static final long TIMESTAMP2 = TIMESTAMP1 + 10;
+    private static final long TIMESTAMP3 = TIMESTAMP2 + 10;
+
+    private static final float PRECISION = 0.001f;
+
+    private static final float BRIGHTNESS = 0.99f;
+    private static final float BRIGHTNESS_DOZE = 0.5f;
+
+
+
+    private PowerGroup mPowerGroup;
+    @Mock private PowerGroup.PowerGroupListener mWakefulnessCallbackMock;
+    @Mock private Notifier mNotifier;
+    @Mock private DisplayManagerInternal mDisplayManagerInternal;
+
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mPowerGroup = new PowerGroup(GROUP_ID, mWakefulnessCallbackMock, mNotifier,
+                mDisplayManagerInternal, WAKEFULNESS_AWAKE, /* ready= */ true,
+                /* supportsSandman= */ true, TIMESTAMP_CREATE);
+    }
+
+    @Test
+    public void testWakePowerGroup() {
+        mPowerGroup.sleepLocked(TIMESTAMP1, UID, GO_TO_SLEEP_REASON_APPLICATION);
+        verify(mWakefulnessCallbackMock).onWakefulnessChangedLocked(eq(GROUP_ID),
+                eq(WAKEFULNESS_ASLEEP), eq(TIMESTAMP1), eq(GO_TO_SLEEP_REASON_APPLICATION),
+                eq(UID), /* opUid= */anyInt(), /* opPackageName= */ isNull(), /* details= */
+                isNull());
+        String details = "wake PowerGroup1";
+        LatencyTracker latencyTracker = LatencyTracker.getInstance(
+                InstrumentationRegistry.getInstrumentation().getContext());
+        mPowerGroup.wakeUpLocked(TIMESTAMP2, WAKE_REASON_PLUGGED_IN, details, UID,
+                /* opPackageName= */ null, /* opUid= */ 0, latencyTracker);
+        verify(mWakefulnessCallbackMock).onWakefulnessChangedLocked(eq(GROUP_ID),
+                eq(WAKEFULNESS_AWAKE), eq(TIMESTAMP2), eq(WAKE_REASON_PLUGGED_IN), eq(UID),
+                /* opUid= */ anyInt(), /* opPackageName= */ isNull(), eq(details));
+    }
+
+    @Test
+    public void testDreamPowerGroup() {
+        assertThat(mPowerGroup.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+        mPowerGroup.dreamLocked(TIMESTAMP1, UID, /* allowWake= */ false);
+        assertThat(mPowerGroup.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_DREAMING);
+        assertThat(mPowerGroup.isSandmanSummonedLocked()).isTrue();
+        verify(mWakefulnessCallbackMock).onWakefulnessChangedLocked(eq(GROUP_ID),
+                eq(WAKEFULNESS_DREAMING), eq(TIMESTAMP1), eq(GO_TO_SLEEP_REASON_APPLICATION),
+                eq(UID), /* opUid= */anyInt(), /* opPackageName= */ isNull(), /* details= */
+                isNull());
+    }
+
+    @Test
+    public void testDozePowerGroup() {
+        assertThat(mPowerGroup.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+        mPowerGroup.dozeLocked(TIMESTAMP1, UID, GO_TO_SLEEP_REASON_TIMEOUT);
+        assertThat(mPowerGroup.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_DOZING);
+        assertThat(mPowerGroup.isSandmanSummonedLocked()).isTrue();
+        verify(mWakefulnessCallbackMock).onWakefulnessChangedLocked(eq(GROUP_ID),
+                eq(WAKEFULNESS_DOZING), eq(TIMESTAMP1), eq(GO_TO_SLEEP_REASON_TIMEOUT),
+                eq(UID), /* opUid= */ anyInt(), /* opPackageName= */ isNull(),
+                /* details= */ isNull());
+    }
+
+    @Test
+    public void testDozePowerGroupWhenNonInteractiveHasNoEffect() {
+        mPowerGroup.sleepLocked(TIMESTAMP1, UID, GO_TO_SLEEP_REASON_TIMEOUT);
+        verify(mWakefulnessCallbackMock).onWakefulnessChangedLocked(eq(GROUP_ID),
+                eq(WAKEFULNESS_ASLEEP), eq(TIMESTAMP1), eq(GO_TO_SLEEP_REASON_TIMEOUT),
+                eq(UID), /* opUid= */ anyInt(), /* opPackageName= */ isNull(),
+                /* details= */ isNull());
+        assertThat(mPowerGroup.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
+        assertThat(mPowerGroup.dozeLocked(TIMESTAMP2, UID, GO_TO_SLEEP_REASON_TIMEOUT)).isFalse();
+        assertThat(mPowerGroup.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
+        verify(mWakefulnessCallbackMock, never()).onWakefulnessChangedLocked(
+                eq(GROUP_ID), eq(WAKEFULNESS_DOZING), eq(TIMESTAMP2), /* reason= */ anyInt(),
+                eq(UID), /* opUid= */ anyInt(), /* opPackageName= */ any(), /* details= */ any());
+    }
+
+    @Test
+    public void testSleepPowerGroup() {
+        assertThat(mPowerGroup.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+        mPowerGroup.sleepLocked(TIMESTAMP1, UID, GO_TO_SLEEP_REASON_DEVICE_FOLD);
+        assertThat(mPowerGroup.isSandmanSummonedLocked()).isTrue();
+        assertThat(mPowerGroup.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
+        verify(mWakefulnessCallbackMock).onWakefulnessChangedLocked(eq(GROUP_ID),
+                eq(WAKEFULNESS_ASLEEP), eq(TIMESTAMP1), eq(GO_TO_SLEEP_REASON_DEVICE_FOLD),
+                eq(UID), /* opUid= */ anyInt(), /* opPackageName= */ isNull(),
+                /* details= */ isNull());
+    }
+
+    @Test
+    public void testDreamPowerGroupWhenNotAwakeHasNoEffect() {
+        mPowerGroup.dozeLocked(TIMESTAMP1, UID, GO_TO_SLEEP_REASON_TIMEOUT);
+        verify(mWakefulnessCallbackMock).onWakefulnessChangedLocked(eq(GROUP_ID),
+                eq(WAKEFULNESS_DOZING), eq(TIMESTAMP1), eq(GO_TO_SLEEP_REASON_TIMEOUT),
+                eq(UID), /* opUid= */ anyInt(), /* opPackageName= */ isNull(),
+                /* details= */ isNull());
+        assertThat(mPowerGroup.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_DOZING);
+        assertThat(mPowerGroup.dreamLocked(TIMESTAMP2, UID, /* allowWake= */ false)).isFalse();
+        assertThat(mPowerGroup.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_DOZING);
+        verify(mWakefulnessCallbackMock, never()).onWakefulnessChangedLocked(
+                eq(GROUP_ID), /* wakefulness= */ eq(WAKEFULNESS_DREAMING), eq(TIMESTAMP2),
+                /* reason= */ anyInt(), eq(UID), /* opUid= */ anyInt(), /* opPackageName= */ any(),
+                /* details= */ any());
+    }
+
+    @Test
+    public void testDreamPowerGroupWhenNotAwakeShouldWake() {
+        mPowerGroup.dozeLocked(TIMESTAMP1, UID, GO_TO_SLEEP_REASON_TIMEOUT);
+        verify(mWakefulnessCallbackMock).onWakefulnessChangedLocked(eq(GROUP_ID),
+                eq(WAKEFULNESS_DOZING), eq(TIMESTAMP1), eq(GO_TO_SLEEP_REASON_TIMEOUT),
+                eq(UID), /* opUid= */ anyInt(), /* opPackageName= */ isNull(),
+                /* details= */ isNull());
+        assertThat(mPowerGroup.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_DOZING);
+        assertThat(mPowerGroup.dreamLocked(TIMESTAMP2, UID, /* allowWake= */ true)).isTrue();
+        assertThat(mPowerGroup.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_DREAMING);
+        verify(mWakefulnessCallbackMock).onWakefulnessChangedLocked(
+                eq(GROUP_ID), /* wakefulness= */ eq(WAKEFULNESS_DREAMING), eq(TIMESTAMP2),
+                /* reason= */ anyInt(), eq(UID), /* opUid= */ anyInt(), /* opPackageName= */ any(),
+                /* details= */ any());
+    }
+
+    @Test
+    public void testLastWakeAndSleepTimeIsUpdated() {
+        assertThat(mPowerGroup.getLastWakeTimeLocked()).isEqualTo(TIMESTAMP_CREATE);
+        assertThat(mPowerGroup.getLastSleepTimeLocked()).isEqualTo(TIMESTAMP_CREATE);
+
+        // Verify that the transition to WAKEFULNESS_DOZING updates the last sleep time
+        String details = "PowerGroup1 Timeout";
+        mPowerGroup.setWakefulnessLocked(WAKEFULNESS_DOZING, TIMESTAMP1, UID,
+                GO_TO_SLEEP_REASON_TIMEOUT, /* opUid= */ 0, /* opPackageName= */ null, details);
+        assertThat(mPowerGroup.getLastSleepTimeLocked()).isEqualTo(TIMESTAMP1);
+        assertThat(mPowerGroup.getLastWakeTimeLocked()).isEqualTo(TIMESTAMP_CREATE);
+        assertThat(mPowerGroup.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_DOZING);
+        verify(mWakefulnessCallbackMock).onWakefulnessChangedLocked(eq(GROUP_ID),
+                eq(WAKEFULNESS_DOZING), eq(TIMESTAMP1), eq(GO_TO_SLEEP_REASON_TIMEOUT),
+                eq(UID), /* opUid= */anyInt(), /* opPackageName= */ isNull(), eq(details));
+
+        // Verify that the transition to WAKEFULNESS_ASLEEP after dozing does not update the last
+        // wake or sleep time
+        mPowerGroup.setWakefulnessLocked(WAKEFULNESS_ASLEEP, TIMESTAMP2, UID,
+                GO_TO_SLEEP_REASON_DEVICE_ADMIN, /* opUid= */ 0, /* opPackageName= */ null,
+                details);
+        assertThat(mPowerGroup.getLastSleepTimeLocked()).isEqualTo(TIMESTAMP1);
+        assertThat(mPowerGroup.getLastWakeTimeLocked()).isEqualTo(TIMESTAMP_CREATE);
+        assertThat(mPowerGroup.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
+        verify(mWakefulnessCallbackMock).onWakefulnessChangedLocked(eq(GROUP_ID),
+                eq(WAKEFULNESS_ASLEEP), eq(TIMESTAMP2), eq(GO_TO_SLEEP_REASON_DEVICE_ADMIN),
+                eq(UID), /* opUid= */anyInt(), /* opPackageName= */ isNull(), eq(details));
+
+        // Verify that waking up the power group only updates the last wake time
+        details = "PowerGroup1 Gesture";
+        mPowerGroup.setWakefulnessLocked(WAKEFULNESS_AWAKE, TIMESTAMP2, UID,
+                WAKE_REASON_GESTURE, /* opUid= */ 0, /* opPackageName= */ null, details);
+        assertThat(mPowerGroup.getLastWakeTimeLocked()).isEqualTo(TIMESTAMP2);
+        assertThat(mPowerGroup.getLastSleepTimeLocked()).isEqualTo(TIMESTAMP1);
+        assertThat(mPowerGroup.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+        verify(mWakefulnessCallbackMock).onWakefulnessChangedLocked(eq(GROUP_ID),
+                eq(WAKEFULNESS_AWAKE), eq(TIMESTAMP2), eq(WAKE_REASON_GESTURE),
+                eq(UID), /* opUid= */ anyInt(), /* opPackageName= */ isNull(), eq(details));
+
+        // Verify that a transition to WAKEFULNESS_ASLEEP from an interactive state updates the last
+        // sleep time
+        mPowerGroup.setWakefulnessLocked(WAKEFULNESS_ASLEEP, TIMESTAMP3, UID,
+                GO_TO_SLEEP_REASON_DEVICE_ADMIN, /* opUid= */ 0, /* opPackageName= */ null,
+                details);
+        assertThat(mPowerGroup.getLastSleepTimeLocked()).isEqualTo(TIMESTAMP3);
+        assertThat(mPowerGroup.getLastWakeTimeLocked()).isEqualTo(TIMESTAMP2);
+        assertThat(mPowerGroup.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
+        verify(mWakefulnessCallbackMock).onWakefulnessChangedLocked(eq(GROUP_ID),
+                eq(WAKEFULNESS_ASLEEP), eq(TIMESTAMP3), eq(GO_TO_SLEEP_REASON_DEVICE_ADMIN),
+                eq(UID), /* opUid= */anyInt(), /* opPackageName= */ isNull(), eq(details));
+    }
+
+    @Test
+    public void testUpdateWhileAwake_UpdatesDisplayPowerRequest() {
+        final boolean batterySaverEnabled = true;
+        float brightnessFactor = 0.7f;
+        PowerSaveState powerSaveState = new PowerSaveState.Builder()
+                .setBatterySaverEnabled(batterySaverEnabled)
+                .setBrightnessFactor(brightnessFactor)
+                .build();
+
+        mPowerGroup.updateLocked(/* screenBrightnessOverride= */ BRIGHTNESS,
+                /* useProximitySensor= */ false,
+                /* boostScreenBrightness= */ false,
+                /* dozeScreenStateOverride= */ Display.STATE_ON,
+                /* dozeScreenBrightness= */ BRIGHTNESS_DOZE,
+                /* overrideDrawWakeLock= */ false,
+                powerSaveState,
+                /* quiescent= */ false,
+                /* dozeAfterScreenOff= */ false,
+                /* bootCompleted= */ true,
+                /* screenBrightnessBoostInProgress= */ false,
+                /* waitForNegativeProximity= */ false);
+        DisplayManagerInternal.DisplayPowerRequest displayPowerRequest =
+                mPowerGroup.mDisplayPowerRequest;
+        assertThat(displayPowerRequest.policy).isEqualTo(POLICY_DIM);
+        assertThat(displayPowerRequest.screenBrightnessOverride).isWithin(PRECISION).of(BRIGHTNESS);
+        assertThat(displayPowerRequest.useProximitySensor).isEqualTo(false);
+        assertThat(displayPowerRequest.boostScreenBrightness).isEqualTo(false);
+        assertThat(displayPowerRequest.dozeScreenState).isEqualTo(Display.STATE_UNKNOWN);
+        assertThat(displayPowerRequest.dozeScreenBrightness).isEqualTo(
+                PowerManager.BRIGHTNESS_INVALID_FLOAT);
+        assertThat(displayPowerRequest.lowPowerMode).isEqualTo(batterySaverEnabled);
+        assertThat(displayPowerRequest.screenLowPowerBrightnessFactor).isWithin(PRECISION).of(
+                brightnessFactor);
+    }
+
+    @Test
+    public void testUpdateWhileDozing_UpdatesDisplayPowerRequest() {
+        final boolean batterySaverEnabled = false;
+        float brightnessFactor = 0.3f;
+        PowerSaveState powerSaveState = new PowerSaveState.Builder()
+                .setBatterySaverEnabled(batterySaverEnabled)
+                .setBrightnessFactor(brightnessFactor)
+                .build();
+        mPowerGroup.dozeLocked(TIMESTAMP1, UID, GO_TO_SLEEP_REASON_APPLICATION);
+        assertThat(mPowerGroup.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_DOZING);
+        mPowerGroup.setWakeLockSummaryLocked(WAKE_LOCK_DOZE);
+
+        mPowerGroup.updateLocked(/* screenBrightnessOverride= */ BRIGHTNESS,
+                /* useProximitySensor= */ true,
+                /* boostScreenBrightness= */ true,
+                /* dozeScreenStateOverride= */ Display.STATE_ON,
+                /* dozeScreenBrightness= */ BRIGHTNESS_DOZE,
+                /* overrideDrawWakeLock= */ false,
+                powerSaveState,
+                /* quiescent= */ false,
+                /* dozeAfterScreenOff= */ false,
+                /* bootCompleted= */ true,
+                /* screenBrightnessBoostInProgress= */ false,
+                /* waitForNegativeProximity= */ false);
+        DisplayManagerInternal.DisplayPowerRequest displayPowerRequest =
+                mPowerGroup.mDisplayPowerRequest;
+        assertThat(displayPowerRequest.policy).isEqualTo(POLICY_DOZE);
+        assertThat(displayPowerRequest.screenBrightnessOverride).isWithin(PRECISION).of(BRIGHTNESS);
+        assertThat(displayPowerRequest.useProximitySensor).isEqualTo(true);
+        assertThat(displayPowerRequest.boostScreenBrightness).isEqualTo(true);
+        assertThat(displayPowerRequest.dozeScreenState).isEqualTo(Display.STATE_ON);
+        assertThat(displayPowerRequest.dozeScreenBrightness).isWithin(PRECISION).of(
+                BRIGHTNESS_DOZE);
+        assertThat(displayPowerRequest.lowPowerMode).isEqualTo(batterySaverEnabled);
+        assertThat(displayPowerRequest.screenLowPowerBrightnessFactor).isWithin(PRECISION).of(
+                brightnessFactor);
+    }
+
+    @Test
+    public void testUpdateWhileDozing_DozeAfterScreenOff() {
+        final boolean batterySaverEnabled = false;
+        float brightnessFactor = 0.3f;
+        PowerSaveState powerSaveState = new PowerSaveState.Builder()
+                .setBatterySaverEnabled(batterySaverEnabled)
+                .setBrightnessFactor(brightnessFactor)
+                .build();
+        mPowerGroup.dozeLocked(TIMESTAMP1, UID, GO_TO_SLEEP_REASON_APPLICATION);
+        assertThat(mPowerGroup.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_DOZING);
+
+        mPowerGroup.updateLocked(/* screenBrightnessOverride= */ BRIGHTNESS,
+                /* useProximitySensor= */ true,
+                /* boostScreenBrightness= */ true,
+                /* dozeScreenStateOverride= */ Display.STATE_ON,
+                /* dozeScreenBrightness= */ BRIGHTNESS_DOZE,
+                /* overrideDrawWakeLock= */ false,
+                powerSaveState,
+                /* quiescent= */ false,
+                /* dozeAfterScreenOff= */ true,
+                /* bootCompleted= */ true,
+                /* screenBrightnessBoostInProgress= */ false,
+                /* waitForNegativeProximity= */ false);
+        DisplayManagerInternal.DisplayPowerRequest displayPowerRequest =
+                mPowerGroup.mDisplayPowerRequest;
+        assertThat(displayPowerRequest.policy).isEqualTo(POLICY_OFF);
+        assertThat(displayPowerRequest.screenBrightnessOverride).isWithin(PRECISION).of(BRIGHTNESS);
+        assertThat(displayPowerRequest.useProximitySensor).isEqualTo(true);
+        assertThat(displayPowerRequest.boostScreenBrightness).isEqualTo(true);
+        assertThat(displayPowerRequest.dozeScreenState).isEqualTo(Display.STATE_UNKNOWN);
+        assertThat(displayPowerRequest.dozeScreenBrightness).isEqualTo(
+                PowerManager.BRIGHTNESS_INVALID_FLOAT);
+        assertThat(displayPowerRequest.lowPowerMode).isEqualTo(batterySaverEnabled);
+        assertThat(displayPowerRequest.screenLowPowerBrightnessFactor).isWithin(PRECISION).of(
+                brightnessFactor);
+    }
+
+    @Test
+    public void testUpdateQuiescent() {
+        final boolean batterySaverEnabled = false;
+        float brightnessFactor = 0.3f;
+        PowerSaveState powerSaveState = new PowerSaveState.Builder()
+                .setBatterySaverEnabled(batterySaverEnabled)
+                .setBrightnessFactor(brightnessFactor)
+                .build();
+        assertThat(mPowerGroup.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+
+        mPowerGroup.updateLocked(/* screenBrightnessOverride= */ BRIGHTNESS,
+                /* useProximitySensor= */ true,
+                /* boostScreenBrightness= */ true,
+                /* dozeScreenStateOverride= */ Display.STATE_ON,
+                /* dozeScreenBrightness= */ BRIGHTNESS_DOZE,
+                /* overrideDrawWakeLock= */ false,
+                powerSaveState,
+                /* quiescent= */ true,
+                /* dozeAfterScreenOff= */ true,
+                /* bootCompleted= */ true,
+                /* screenBrightnessBoostInProgress= */ false,
+                /* waitForNegativeProximity= */ false);
+        DisplayManagerInternal.DisplayPowerRequest displayPowerRequest =
+                mPowerGroup.mDisplayPowerRequest;
+        assertThat(displayPowerRequest.policy).isEqualTo(POLICY_OFF);
+        assertThat(displayPowerRequest.screenBrightnessOverride).isWithin(PRECISION).of(BRIGHTNESS);
+        assertThat(displayPowerRequest.useProximitySensor).isEqualTo(true);
+        assertThat(displayPowerRequest.boostScreenBrightness).isEqualTo(true);
+        assertThat(displayPowerRequest.dozeScreenState).isEqualTo(Display.STATE_UNKNOWN);
+        assertThat(displayPowerRequest.dozeScreenBrightness).isEqualTo(
+                PowerManager.BRIGHTNESS_INVALID_FLOAT);
+        assertThat(displayPowerRequest.lowPowerMode).isEqualTo(batterySaverEnabled);
+        assertThat(displayPowerRequest.screenLowPowerBrightnessFactor).isWithin(PRECISION).of(
+                brightnessFactor);
+    }
+
+    @Test
+    public void testUpdateWhileAsleep_UpdatesDisplayPowerRequest() {
+        final boolean batterySaverEnabled = false;
+        float brightnessFactor = 0.3f;
+        PowerSaveState powerSaveState = new PowerSaveState.Builder()
+                .setBatterySaverEnabled(batterySaverEnabled)
+                .setBrightnessFactor(brightnessFactor)
+                .build();
+        mPowerGroup.sleepLocked(TIMESTAMP1, UID, GO_TO_SLEEP_REASON_TIMEOUT);
+        assertThat(mPowerGroup.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
+        mPowerGroup.updateLocked(/* screenBrightnessOverride= */ BRIGHTNESS,
+                /* useProximitySensor= */ true,
+                /* boostScreenBrightness= */ true,
+                /* dozeScreenStateOverride= */ Display.STATE_ON,
+                /* dozeScreenBrightness= */ BRIGHTNESS_DOZE,
+                /* overrideDrawWakeLock= */ false,
+                powerSaveState,
+                /* quiescent= */ false,
+                /* dozeAfterScreenOff= */ false,
+                /* bootCompleted= */ true,
+                /* screenBrightnessBoostInProgress= */ false,
+                /* waitForNegativeProximity= */ false);
+        DisplayManagerInternal.DisplayPowerRequest displayPowerRequest =
+                mPowerGroup.mDisplayPowerRequest;
+        assertThat(displayPowerRequest.policy).isEqualTo(POLICY_OFF);
+        assertThat(displayPowerRequest.screenBrightnessOverride).isWithin(PRECISION).of(BRIGHTNESS);
+        assertThat(displayPowerRequest.useProximitySensor).isEqualTo(true);
+        assertThat(displayPowerRequest.boostScreenBrightness).isEqualTo(true);
+        assertThat(displayPowerRequest.dozeScreenState).isEqualTo(Display.STATE_UNKNOWN);
+        assertThat(displayPowerRequest.dozeScreenBrightness).isEqualTo(
+                PowerManager.BRIGHTNESS_INVALID_FLOAT);
+        assertThat(displayPowerRequest.lowPowerMode).isEqualTo(batterySaverEnabled);
+        assertThat(displayPowerRequest.screenLowPowerBrightnessFactor).isWithin(PRECISION).of(
+                brightnessFactor);
+    }
+
+    @Test
+    public void testUpdateWhileDreamingWithScreenBrightWakelock_UpdatesDisplayPowerRequest() {
+        final boolean batterySaverEnabled = false;
+        float brightnessFactor = 0.3f;
+        PowerSaveState powerSaveState = new PowerSaveState.Builder()
+                .setBatterySaverEnabled(batterySaverEnabled)
+                .setBrightnessFactor(brightnessFactor)
+                .build();
+        mPowerGroup.dreamLocked(TIMESTAMP1, UID, /* allowWake= */ false);
+        assertThat(mPowerGroup.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_DREAMING);
+        mPowerGroup.setWakeLockSummaryLocked(WAKE_LOCK_SCREEN_BRIGHT);
+        mPowerGroup.updateLocked(/* screenBrightnessOverride= */ BRIGHTNESS,
+                /* useProximitySensor= */ true,
+                /* boostScreenBrightness= */ true,
+                /* dozeScreenStateOverride= */ Display.STATE_ON,
+                /* dozeScreenBrightness= */ BRIGHTNESS_DOZE,
+                /* overrideDrawWakeLock= */ false,
+                powerSaveState,
+                /* quiescent= */ false,
+                /* dozeAfterScreenOff= */ false,
+                /* bootCompleted= */ true,
+                /* screenBrightnessBoostInProgress= */ false,
+                /* waitForNegativeProximity= */ false);
+        DisplayManagerInternal.DisplayPowerRequest displayPowerRequest =
+                mPowerGroup.mDisplayPowerRequest;
+        assertThat(displayPowerRequest.policy).isEqualTo(POLICY_BRIGHT);
+        assertThat(displayPowerRequest.screenBrightnessOverride).isWithin(PRECISION).of(BRIGHTNESS);
+        assertThat(displayPowerRequest.useProximitySensor).isEqualTo(true);
+        assertThat(displayPowerRequest.boostScreenBrightness).isEqualTo(true);
+        assertThat(displayPowerRequest.dozeScreenState).isEqualTo(Display.STATE_UNKNOWN);
+        assertThat(displayPowerRequest.dozeScreenBrightness).isEqualTo(
+                PowerManager.BRIGHTNESS_INVALID_FLOAT);
+        assertThat(displayPowerRequest.lowPowerMode).isEqualTo(batterySaverEnabled);
+        assertThat(displayPowerRequest.screenLowPowerBrightnessFactor).isWithin(PRECISION).of(
+                brightnessFactor);
+    }
+
+    @Test
+    public void testUpdateWhileAwakeBootNotComplete_UpdatesDisplayPowerRequest() {
+        final boolean batterySaverEnabled = false;
+        float brightnessFactor = 0.3f;
+        PowerSaveState powerSaveState = new PowerSaveState.Builder()
+                .setBatterySaverEnabled(batterySaverEnabled)
+                .setBrightnessFactor(brightnessFactor)
+                .build();
+        assertThat(mPowerGroup.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+        mPowerGroup.updateLocked(/* screenBrightnessOverride= */ BRIGHTNESS,
+                /* useProximitySensor= */ true,
+                /* boostScreenBrightness= */ true,
+                /* dozeScreenStateOverride= */ Display.STATE_ON,
+                /* dozeScreenBrightness= */ BRIGHTNESS_DOZE,
+                /* overrideDrawWakeLock= */ false,
+                powerSaveState,
+                /* quiescent= */ false,
+                /* dozeAfterScreenOff= */ false,
+                /* bootCompleted= */ false,
+                /* screenBrightnessBoostInProgress= */ false,
+                /* waitForNegativeProximity= */ false);
+        DisplayManagerInternal.DisplayPowerRequest displayPowerRequest =
+                mPowerGroup.mDisplayPowerRequest;
+        assertThat(displayPowerRequest.policy).isEqualTo(POLICY_BRIGHT);
+        assertThat(displayPowerRequest.screenBrightnessOverride).isWithin(PRECISION).of(BRIGHTNESS);
+        assertThat(displayPowerRequest.useProximitySensor).isEqualTo(true);
+        assertThat(displayPowerRequest.boostScreenBrightness).isEqualTo(true);
+        assertThat(displayPowerRequest.dozeScreenState).isEqualTo(Display.STATE_UNKNOWN);
+        assertThat(displayPowerRequest.dozeScreenBrightness).isEqualTo(
+                PowerManager.BRIGHTNESS_INVALID_FLOAT);
+        assertThat(displayPowerRequest.lowPowerMode).isEqualTo(batterySaverEnabled);
+        assertThat(displayPowerRequest.screenLowPowerBrightnessFactor).isWithin(PRECISION).of(
+                brightnessFactor);
+    }
+
+    @Test
+    public void testUpdateWhileAwakeUserActivityScreenBright_UpdatesDisplayPowerRequest() {
+        final boolean batterySaverEnabled = false;
+        float brightnessFactor = 0.3f;
+        PowerSaveState powerSaveState = new PowerSaveState.Builder()
+                .setBatterySaverEnabled(batterySaverEnabled)
+                .setBrightnessFactor(brightnessFactor)
+                .build();
+        assertThat(mPowerGroup.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+        mPowerGroup.setUserActivitySummaryLocked(USER_ACTIVITY_SCREEN_BRIGHT);
+        mPowerGroup.updateLocked(/* screenBrightnessOverride= */ BRIGHTNESS,
+                /* useProximitySensor= */ true,
+                /* boostScreenBrightness= */ true,
+                /* dozeScreenStateOverride= */ Display.STATE_ON,
+                /* dozeScreenBrightness= */ BRIGHTNESS_DOZE,
+                /* overrideDrawWakeLock= */ false,
+                powerSaveState,
+                /* quiescent= */ false,
+                /* dozeAfterScreenOff= */ false,
+                /* bootCompleted= */ true,
+                /* screenBrightnessBoostInProgress= */ false,
+                /* waitForNegativeProximity= */ false);
+        DisplayManagerInternal.DisplayPowerRequest displayPowerRequest =
+                mPowerGroup.mDisplayPowerRequest;
+        assertThat(displayPowerRequest.policy).isEqualTo(POLICY_BRIGHT);
+        assertThat(displayPowerRequest.screenBrightnessOverride).isWithin(PRECISION).of(BRIGHTNESS);
+        assertThat(displayPowerRequest.useProximitySensor).isEqualTo(true);
+        assertThat(displayPowerRequest.boostScreenBrightness).isEqualTo(true);
+        assertThat(displayPowerRequest.dozeScreenState).isEqualTo(Display.STATE_UNKNOWN);
+        assertThat(displayPowerRequest.dozeScreenBrightness).isEqualTo(
+                PowerManager.BRIGHTNESS_INVALID_FLOAT);
+        assertThat(displayPowerRequest.lowPowerMode).isEqualTo(batterySaverEnabled);
+        assertThat(displayPowerRequest.screenLowPowerBrightnessFactor).isWithin(PRECISION).of(
+                brightnessFactor);
+    }
+
+    @Test
+    public void testUpdateWhileAwakeScreenBrightnessBoostInProgress_UpdatesDisplayPowerRequest() {
+        final boolean batterySaverEnabled = false;
+        float brightnessFactor = 0.3f;
+        PowerSaveState powerSaveState = new PowerSaveState.Builder()
+                .setBatterySaverEnabled(batterySaverEnabled)
+                .setBrightnessFactor(brightnessFactor)
+                .build();
+        assertThat(mPowerGroup.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+        mPowerGroup.updateLocked(/* screenBrightnessOverride= */ BRIGHTNESS,
+                /* useProximitySensor= */ true,
+                /* boostScreenBrightness= */ true,
+                /* dozeScreenStateOverride= */ Display.STATE_ON,
+                /* dozeScreenBrightness= */ BRIGHTNESS_DOZE,
+                /* overrideDrawWakeLock= */ false,
+                powerSaveState,
+                /* quiescent= */ false,
+                /* dozeAfterScreenOff= */ false,
+                /* bootCompleted= */ true,
+                /* screenBrightnessBoostInProgress= */ true,
+                /* waitForNegativeProximity= */ false);
+        DisplayManagerInternal.DisplayPowerRequest displayPowerRequest =
+                mPowerGroup.mDisplayPowerRequest;
+        assertThat(displayPowerRequest.policy).isEqualTo(POLICY_BRIGHT);
+        assertThat(displayPowerRequest.screenBrightnessOverride).isWithin(PRECISION).of(BRIGHTNESS);
+        assertThat(displayPowerRequest.useProximitySensor).isEqualTo(true);
+        assertThat(displayPowerRequest.boostScreenBrightness).isEqualTo(true);
+        assertThat(displayPowerRequest.dozeScreenState).isEqualTo(Display.STATE_UNKNOWN);
+        assertThat(displayPowerRequest.dozeScreenBrightness).isEqualTo(
+                PowerManager.BRIGHTNESS_INVALID_FLOAT);
+        assertThat(displayPowerRequest.lowPowerMode).isEqualTo(batterySaverEnabled);
+        assertThat(displayPowerRequest.screenLowPowerBrightnessFactor).isWithin(PRECISION).of(
+                brightnessFactor);
+    }
+}
diff --git a/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java b/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java
new file mode 100644
index 0000000..c80ff45
--- /dev/null
+++ b/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java
@@ -0,0 +1,2713 @@
+/*
+ * Copyright (C) 2017 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 android.app.ActivityManager.PROCESS_STATE_BOUND_TOP;
+import static android.app.ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE;
+import static android.os.PowerManager.USER_ACTIVITY_EVENT_BUTTON;
+import static android.os.PowerManagerInternal.WAKEFULNESS_ASLEEP;
+import static android.os.PowerManagerInternal.WAKEFULNESS_AWAKE;
+import static android.os.PowerManagerInternal.WAKEFULNESS_DOZING;
+import static android.os.PowerManagerInternal.WAKEFULNESS_DREAMING;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isA;
+import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.ArgumentMatchers.same;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.atMost;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.ActivityManagerInternal;
+import android.attention.AttentionManagerInternal;
+import android.compat.testing.PlatformCompatChangeRule;
+import android.content.AttributionSource;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.PermissionChecker;
+import android.content.res.Resources;
+import android.hardware.SensorManager;
+import android.hardware.display.AmbientDisplayConfiguration;
+import android.hardware.display.DisplayManagerInternal;
+import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest;
+import android.hardware.power.Boost;
+import android.hardware.power.Mode;
+import android.os.BatteryManager;
+import android.os.BatteryManagerInternal;
+import android.os.BatterySaverPolicyConfig;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.IWakeLockCallback;
+import android.os.Looper;
+import android.os.PowerManager;
+import android.os.PowerManagerInternal;
+import android.os.PowerSaveState;
+import android.os.UserHandle;
+import android.os.test.TestLooper;
+import android.provider.Settings;
+import android.service.dreams.DreamManagerInternal;
+import android.sysprop.PowerProperties;
+import android.test.mock.MockContentResolver;
+import android.util.IntArray;
+import android.view.Display;
+import android.view.DisplayInfo;
+
+import androidx.test.core.app.ApplicationProvider;
+
+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.BinderService;
+import com.android.server.power.PowerManagerService.NativeWrapper;
+import com.android.server.power.PowerManagerService.UserSwitchedReceiver;
+import com.android.server.power.PowerManagerService.WakeLock;
+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 libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
+import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.ArgumentCaptor;
+import org.mockito.ArgumentMatcher;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+import org.mockito.stubbing.Answer;
+
+import java.time.Duration;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.Callable;
+import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * Tests for {@link com.android.server.power.PowerManagerService}.
+ *
+ * Build/Install/Run:
+ *  atest FrameworksServicesTests:PowerManagerServiceTest
+ */
+@SuppressWarnings("GuardedBy")
+public class PowerManagerServiceTest {
+    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 LowPowerStandbyController mLowPowerStandbyControllerMock;
+    @Mock private Callable<Void> mInvalidateInteractiveCachesMock;
+    @Mock private InattentiveSleepWarningController mInattentiveSleepWarningControllerMock;
+    @Mock private PowerManagerService.PermissionCheckerWrapper mPermissionCheckerWrapperMock;
+    @Mock private PowerManagerService.PowerPropertiesWrapper mPowerPropertiesWrapper;
+
+    @Rule public TestRule compatChangeRule = new PlatformCompatChangeRule();
+
+    private PowerManagerService mService;
+    private ContextWrapper mContextSpy;
+    private BatteryReceiver mBatteryReceiver;
+    private UserSwitchedReceiver mUserSwitchedReceiver;
+    private Resources mResourcesSpy;
+    private OffsettableClock mClock;
+    private long mLastElapsedRealtime;
+    private TestLooper mTestLooper;
+
+    private static class IntentFilterMatcher implements ArgumentMatcher<IntentFilter> {
+        private final IntentFilter mFilter;
+
+        IntentFilterMatcher(IntentFilter filter) {
+            mFilter = filter;
+        }
+
+        @Override
+        public boolean matches(IntentFilter other) {
+            if (other.countActions() != mFilter.countActions()) {
+                return false;
+            }
+            for (int i = 0; i < mFilter.countActions(); i++) {
+                if (!mFilter.getAction(i).equals(other.getAction(i))) {
+                    return false;
+                }
+            }
+            return true;
+        }
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        FakeSettingsProvider.clearSettingsProvider();
+
+        PowerSaveState powerSaveState = new PowerSaveState.Builder()
+                .setBatterySaverEnabled(BATTERY_SAVER_ENABLED)
+                .setBrightnessFactor(BRIGHTNESS_FACTOR)
+                .build();
+        when(mBatterySaverPolicyMock.getBatterySaverPolicy(
+                eq(PowerManager.ServiceType.SCREEN_BRIGHTNESS)))
+                .thenReturn(powerSaveState);
+        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(ApplicationProvider.getApplicationContext()));
+        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);
+
+        Settings.Global.putInt(mContextSpy.getContentResolver(),
+                Settings.Global.STAY_ON_WHILE_PLUGGED_IN, 0);
+        Settings.Secure.putInt(mContextSpy.getContentResolver(),
+                Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP, 0);
+
+        mClock = new OffsettableClock.Stopped();
+        mTestLooper = new TestLooper(mClock::now);
+    }
+
+    private PowerManagerService createService() {
+        mService = new PowerManagerService(mContextSpy, new PowerManagerService.Injector() {
+            @Override
+            Notifier createNotifier(Looper looper, Context context, IBatteryStats batteryStats,
+                    SuspendBlocker suspendBlocker, WindowManagerPolicy policy,
+                    FaceDownDetector faceDownDetector, ScreenUndimDetector screenUndimDetector,
+                    Executor executor) {
+                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 new PowerManagerService.Clock() {
+                    @Override
+                    public long uptimeMillis() {
+                        return mClock.now();
+                    }
+
+                    @Override
+                    public long elapsedRealtime() {
+                        mLastElapsedRealtime = mClock.now();
+                        return mLastElapsedRealtime;
+                    }
+                };
+            }
+
+            @Override
+            Handler createHandler(Looper looper, Handler.Callback callback) {
+                return new Handler(mTestLooper.getLooper(), callback);
+            }
+
+            @Override
+            void invalidateIsInteractiveCaches() {
+                try {
+                    mInvalidateInteractiveCachesMock.call();
+                } catch (Exception e) {
+                    throw new RuntimeException(e);
+                }
+            }
+
+            @Override
+            LowPowerStandbyController createLowPowerStandbyController(Context context,
+                    Looper looper) {
+                return mLowPowerStandbyControllerMock;
+            }
+
+            @Override
+            PowerManagerService.PermissionCheckerWrapper createPermissionCheckerWrapper() {
+                return mPermissionCheckerWrapperMock;
+            }
+
+            @Override
+            PowerManagerService.PowerPropertiesWrapper createPowerPropertiesWrapper() {
+                return mPowerPropertiesWrapper;
+            }
+        });
+        return mService;
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        LocalServices.removeServiceForTest(LightsManager.class);
+        LocalServices.removeServiceForTest(DisplayManagerInternal.class);
+        LocalServices.removeServiceForTest(BatteryManagerInternal.class);
+        LocalServices.removeServiceForTest(ActivityManagerInternal.class);
+        FakeSettingsProvider.clearSettingsProvider();
+    }
+
+    /**
+     * Creates a mock and registers it to {@link LocalServices}.
+     */
+    private static <T> void addLocalServiceMock(Class<T> clazz, T mock) {
+        LocalServices.removeServiceForTest(clazz);
+        LocalServices.addService(clazz, mock);
+    }
+
+    private void startSystem() {
+        mService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY);
+
+        // Grab the BatteryReceiver
+        ArgumentCaptor<BatteryReceiver> batCaptor = ArgumentCaptor.forClass(BatteryReceiver.class);
+        IntentFilter batFilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
+        batFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
+        verify(mContextSpy).registerReceiver(batCaptor.capture(),
+                argThat(new IntentFilterMatcher(batFilter)), isNull(), isA(Handler.class));
+        mBatteryReceiver = batCaptor.getValue();
+
+        // Grab the UserSwitchedReceiver
+        ArgumentCaptor<UserSwitchedReceiver> userSwitchedCaptor =
+                ArgumentCaptor.forClass(UserSwitchedReceiver.class);
+        IntentFilter usFilter = new IntentFilter(Intent.ACTION_USER_SWITCHED);
+        verify(mContextSpy).registerReceiver(userSwitchedCaptor.capture(),
+                argThat(new IntentFilterMatcher(usFilter)), isNull(), isA(Handler.class));
+        mUserSwitchedReceiver = userSwitchedCaptor.getValue();
+
+        mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
+    }
+
+    private void forceSleep() {
+        mService.getBinderServiceInstance().goToSleep(mClock.now(),
+                PowerManager.GO_TO_SLEEP_REASON_APPLICATION, PowerManager.GO_TO_SLEEP_FLAG_NO_DOZE);
+    }
+
+    private void forceDream() {
+        mService.getBinderServiceInstance().nap(mClock.now());
+    }
+
+    private void forceAwake() {
+        mService.getBinderServiceInstance().wakeUp(mClock.now(),
+                PowerManager.WAKE_REASON_UNKNOWN, "testing IPowerManager.wakeUp()", "pkg.name");
+    }
+
+    private void forceDozing() {
+        mService.getBinderServiceInstance().goToSleep(mClock.now(),
+                PowerManager.GO_TO_SLEEP_REASON_APPLICATION, 0);
+    }
+
+    private void setPluggedIn(boolean isPluggedIn) {
+        // Set the callback to return the new state
+        when(mBatteryManagerInternalMock.isPowered(anyInt()))
+                .thenReturn(isPluggedIn);
+        // Trigger PowerManager to reread the plug-in state
+        mBatteryReceiver.onReceive(mContextSpy, new Intent(Intent.ACTION_BATTERY_CHANGED));
+    }
+
+    private void setBatteryLevel(int batteryLevel) {
+        when(mBatteryManagerInternalMock.getBatteryLevel())
+                .thenReturn(batteryLevel);
+        mBatteryReceiver.onReceive(mContextSpy, new Intent(Intent.ACTION_BATTERY_CHANGED));
+    }
+
+    private void setBatteryHealth(int batteryHealth) {
+        when(mBatteryManagerInternalMock.getBatteryHealth())
+                .thenReturn(batteryHealth);
+        mBatteryReceiver.onReceive(mContextSpy, new Intent(Intent.ACTION_BATTERY_CHANGED));
+    }
+
+    private void setAttentiveTimeout(int attentiveTimeoutMillis) {
+        Settings.Secure.putInt(
+                mContextSpy.getContentResolver(), Settings.Secure.ATTENTIVE_TIMEOUT,
+                attentiveTimeoutMillis);
+    }
+
+    private void setAttentiveWarningDuration(int attentiveWarningDurationMillis) {
+        when(mResourcesSpy.getInteger(
+                com.android.internal.R.integer.config_attentiveWarningDuration))
+                .thenReturn(attentiveWarningDurationMillis);
+    }
+
+    private void setMinimumScreenOffTimeoutConfig(int minimumScreenOffTimeoutConfigMillis) {
+        when(mResourcesSpy.getInteger(
+                com.android.internal.R.integer.config_minimumScreenOffTimeout))
+                .thenReturn(minimumScreenOffTimeoutConfigMillis);
+    }
+
+    private void setDreamsDisabledByAmbientModeSuppressionConfig(boolean disable) {
+        when(mResourcesSpy.getBoolean(
+                com.android.internal.R.bool.config_dreamsDisabledByAmbientModeSuppressionConfig))
+                .thenReturn(disable);
+    }
+
+    private void setDreamsBatteryLevelDrainConfig(int threshold) {
+        when(mResourcesSpy.getInteger(
+                com.android.internal.R.integer.config_dreamsBatteryLevelDrainCutoff)).thenReturn(
+                threshold);
+    }
+
+    private void advanceTime(long timeMs) {
+        mClock.fastForward(timeMs);
+        mTestLooper.dispatchAll();
+    }
+
+    @Test
+    public void testCreateService_initializesNativeServiceAndSetsPowerModes() {
+        PowerManagerService service = createService();
+        verify(mNativeWrapperMock).nativeInit(same(service));
+        verify(mNativeWrapperMock).nativeSetPowerMode(eq(Mode.INTERACTIVE), eq(true));
+        verify(mNativeWrapperMock).nativeSetPowerMode(eq(Mode.DOUBLE_TAP_TO_WAKE), eq(false));
+    }
+
+    @Test
+    public void testGetLastShutdownReasonInternal() {
+        when(mSystemPropertiesMock.get(eq(SYSTEM_PROPERTY_REBOOT_REASON), any())).thenReturn(
+                "shutdown,thermal");
+        createService();
+        int reason = mService.getLastShutdownReasonInternal();
+        assertThat(reason).isEqualTo(PowerManager.SHUTDOWN_REASON_THERMAL_SHUTDOWN);
+    }
+
+    @Test
+    public void testWakefulnessAwake_InitialValue() {
+        createService();
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+    }
+
+    @Test
+    public void testWakefulnessSleep_NoDozeSleepFlag() {
+        createService();
+        // Start with AWAKE state
+        startSystem();
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+
+        // Take a nap and verify.
+        mService.getBinderServiceInstance().goToSleep(mClock.now(),
+                PowerManager.GO_TO_SLEEP_REASON_APPLICATION, PowerManager.GO_TO_SLEEP_FLAG_NO_DOZE);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
+    }
+
+    @Test
+    public void testWakefulnessSleep_SoftSleepFlag_NoWakelocks() {
+        createService();
+        // Start with AWAKE state
+        startSystem();
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+
+        // Take a nap and verify we go to sleep.
+        mService.getBinderServiceInstance().goToSleep(mClock.now(),
+                PowerManager.GO_TO_SLEEP_REASON_APPLICATION,
+                PowerManager.GO_TO_SLEEP_FLAG_SOFT_SLEEP);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DOZING);
+    }
+
+    @Test
+    public void testWakefulnessSleep_SoftSleepFlag_WithPartialWakelock() {
+        createService();
+        // Start with AWAKE state
+        startSystem();
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+
+        // Grab a wakelock
+        final String tag = "wakelock1";
+        final String packageName = "pkg.name";
+        final IBinder token = new Binder();
+        final int flags = PowerManager.PARTIAL_WAKE_LOCK;
+        mService.getBinderServiceInstance().acquireWakeLock(token, flags, tag, packageName,
+                null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY,
+                null /* callback */);
+
+        // Take a nap and verify we stay awake.
+        mService.getBinderServiceInstance().goToSleep(mClock.now(),
+                PowerManager.GO_TO_SLEEP_REASON_APPLICATION,
+                PowerManager.GO_TO_SLEEP_FLAG_SOFT_SLEEP);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DOZING);
+    }
+
+    @Test
+    public void testWakefulnessSleep_SoftSleepFlag_WithFullWakelock() {
+        createService();
+        // Start with AWAKE state
+        startSystem();
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+
+        // Grab a wakelock
+        final String tag = "wakelock1";
+        final String packageName = "pkg.name";
+        final IBinder token = new Binder();
+        final int flags = PowerManager.FULL_WAKE_LOCK;
+        mService.getBinderServiceInstance().acquireWakeLock(token, flags, tag, packageName,
+                null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY,
+                null /* callback */);
+
+        // Take a nap and verify we stay awake.
+        mService.getBinderServiceInstance().goToSleep(mClock.now(),
+                PowerManager.GO_TO_SLEEP_REASON_APPLICATION,
+                PowerManager.GO_TO_SLEEP_FLAG_SOFT_SLEEP);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+    }
+
+    @Test
+    public void testWakefulnessSleep_SoftSleepFlag_WithScreenBrightWakelock() {
+        createService();
+        // Start with AWAKE state
+        startSystem();
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+
+        // Grab a wakelock
+        final String tag = "wakelock1";
+        final String packageName = "pkg.name";
+        final IBinder token = new Binder();
+        final int flags = PowerManager.SCREEN_BRIGHT_WAKE_LOCK;
+        mService.getBinderServiceInstance().acquireWakeLock(token, flags, tag, packageName,
+                null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY,
+                null /* callback */);
+
+        // Take a nap and verify we stay awake.
+        mService.getBinderServiceInstance().goToSleep(mClock.now(),
+                PowerManager.GO_TO_SLEEP_REASON_APPLICATION,
+                PowerManager.GO_TO_SLEEP_FLAG_SOFT_SLEEP);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+    }
+    @Test
+    public void testWakefulnessSleep_SoftSleepFlag_WithScreenDimWakelock() {
+        createService();
+        // Start with AWAKE state
+        startSystem();
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+
+        // Grab a wakelock
+        final String tag = "wakelock1";
+        final String packageName = "pkg.name";
+        final IBinder token = new Binder();
+        final int flags = PowerManager.SCREEN_DIM_WAKE_LOCK;
+        mService.getBinderServiceInstance().acquireWakeLock(token, flags, tag, packageName,
+                null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY,
+                null /* callback */);
+
+        // Take a nap and verify we stay awake.
+        mService.getBinderServiceInstance().goToSleep(mClock.now(),
+                PowerManager.GO_TO_SLEEP_REASON_APPLICATION,
+                PowerManager.GO_TO_SLEEP_FLAG_SOFT_SLEEP);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+    }
+
+    @Test
+    @EnableCompatChanges({PowerManagerService.REQUIRE_TURN_SCREEN_ON_PERMISSION})
+    public void testWakefulnessAwake_AcquireCausesWakeup_turnScreenOnAllowed() {
+        createService();
+        startSystem();
+        forceSleep();
+
+        IBinder token = new Binder();
+        String tag = "acq_causes_wakeup";
+        String packageName = "pkg.name";
+        AttributionSource attrSrc = new AttributionSource(Binder.getCallingUid(),
+                packageName, /* attributionTag= */ null);
+
+        doReturn(PermissionChecker.PERMISSION_GRANTED).when(
+                mPermissionCheckerWrapperMock).checkPermissionForDataDelivery(any(),
+                eq(android.Manifest.permission.TURN_SCREEN_ON), anyInt(), eq(attrSrc), anyString());
+
+        // First, ensure that a normal full wake lock does not cause a wakeup
+        int flags = PowerManager.FULL_WAKE_LOCK;
+        mService.getBinderServiceInstance().acquireWakeLock(token, flags, tag, packageName,
+                null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY, null);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
+        mService.getBinderServiceInstance().releaseWakeLock(token, 0 /* flags */);
+
+        // Ensure that the flag does *NOT* work with a partial wake lock.
+        flags = PowerManager.PARTIAL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP;
+        mService.getBinderServiceInstance().acquireWakeLock(token, flags, tag, packageName,
+                null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY, null);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
+        mService.getBinderServiceInstance().releaseWakeLock(token, 0 /* flags */);
+
+        // Verify that flag forces a wakeup when paired to a FULL_WAKE_LOCK
+        flags = PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP;
+        mService.getBinderServiceInstance().acquireWakeLock(token, flags, tag, packageName,
+                null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY, null);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+        mService.getBinderServiceInstance().releaseWakeLock(token, 0 /* flags */);
+    }
+
+    @Test
+    @DisableCompatChanges({PowerManagerService.REQUIRE_TURN_SCREEN_ON_PERMISSION})
+    public void testWakefulnessAwake_AcquireCausesWakeupOldSdk_turnScreenOnAllowed() {
+        createService();
+        startSystem();
+        forceSleep();
+
+        IBinder token = new Binder();
+        String tag = "acq_causes_wakeup";
+        String packageName = "pkg.name";
+        AttributionSource attrSrc = new AttributionSource(Binder.getCallingUid(),
+                packageName, /* attributionTag= */ null);
+
+        // verify that the wakeup is allowed for apps targeting older sdks, and therefore won't have
+        // the TURN_SCREEN_ON permission granted
+        doReturn(PermissionChecker.PERMISSION_HARD_DENIED).when(
+                mPermissionCheckerWrapperMock).checkPermissionForDataDelivery(any(),
+                eq(android.Manifest.permission.TURN_SCREEN_ON), anyInt(), eq(attrSrc), anyString());
+
+        doReturn(false).when(mPowerPropertiesWrapper).waive_target_sdk_check_for_turn_screen_on();
+
+        int flags = PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP;
+        mService.getBinderServiceInstance().acquireWakeLock(token, flags, tag, packageName,
+                null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY, null);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+        mService.getBinderServiceInstance().releaseWakeLock(token, 0 /* flags */);
+    }
+
+    @Test
+    @EnableCompatChanges({PowerManagerService.REQUIRE_TURN_SCREEN_ON_PERMISSION})
+    public void testWakefulnessAwake_AcquireCausesWakeup_turnScreenOnDenied() {
+        createService();
+        startSystem();
+        forceSleep();
+
+        IBinder token = new Binder();
+        String tag = "acq_causes_wakeup";
+        String packageName = "pkg.name";
+        AttributionSource attrSrc = new AttributionSource(Binder.getCallingUid(),
+                packageName, /* attributionTag= */ null);
+        doReturn(PermissionChecker.PERMISSION_HARD_DENIED).when(
+                mPermissionCheckerWrapperMock).checkPermissionForDataDelivery(any(),
+                eq(android.Manifest.permission.TURN_SCREEN_ON), anyInt(), eq(attrSrc), anyString());
+
+        doReturn(false).when(mPowerPropertiesWrapper).waive_target_sdk_check_for_turn_screen_on();
+        doReturn(false).when(mPowerPropertiesWrapper).permissionless_turn_screen_on();
+
+        // Verify that flag has no effect when TURN_SCREEN_ON is not allowed for apps targeting U+
+        int flags = PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP;
+        mService.getBinderServiceInstance().acquireWakeLock(token, flags, tag, packageName,
+                null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY, null);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
+        mService.getBinderServiceInstance().releaseWakeLock(token, 0 /* flags */);
+    }
+
+    @Test
+    @EnableCompatChanges({PowerManagerService.REQUIRE_TURN_SCREEN_ON_PERMISSION})
+    public void testWakefulnessAwake_AcquireCausesWakeupOldSdk_turnScreenOnDenied() {
+        createService();
+        startSystem();
+        forceSleep();
+
+        IBinder token = new Binder();
+        String tag = "acq_causes_wakeup";
+        String packageName = "pkg.name";
+        AttributionSource attrSrc = new AttributionSource(Binder.getCallingUid(),
+                packageName, /* attributionTag= */ null);
+        doReturn(PermissionChecker.PERMISSION_HARD_DENIED).when(
+                mPermissionCheckerWrapperMock).checkPermissionForDataDelivery(any(),
+                eq(android.Manifest.permission.TURN_SCREEN_ON), anyInt(), eq(attrSrc), anyString());
+
+        doReturn(true).when(mPowerPropertiesWrapper).waive_target_sdk_check_for_turn_screen_on();
+
+        // Verify that flag has no effect when TURN_SCREEN_ON is not allowed for apps targeting U+
+        int flags = PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP;
+        mService.getBinderServiceInstance().acquireWakeLock(token, flags, tag, packageName,
+                null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY, null);
+        if (PowerProperties.permissionless_turn_screen_on().orElse(false)) {
+            assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+        } else {
+            assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
+        }
+        mService.getBinderServiceInstance().releaseWakeLock(token, 0 /* flags */);
+    }
+
+    @Test
+    public void testWakefulnessAwake_IPowerManagerWakeUp() {
+        createService();
+        startSystem();
+        forceSleep();
+        mService.getBinderServiceInstance().wakeUp(mClock.now(),
+                PowerManager.WAKE_REASON_UNKNOWN, "testing IPowerManager.wakeUp()", "pkg.name");
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+    }
+
+    /**
+     * Tests a series of variants that control whether a device wakes-up when it is plugged in
+     * or docked.
+     */
+    @Test
+    public void testWakefulnessAwake_ShouldWakeUpWhenPluggedIn() {
+        createService();
+        startSystem();
+        forceSleep();
+
+        // Test 1:
+        // Set config to prevent it wake up, test, verify, reset config value.
+        when(mResourcesSpy.getBoolean(com.android.internal.R.bool.config_unplugTurnsOnScreen))
+                .thenReturn(false);
+        mService.readConfigurationLocked();
+        setPluggedIn(true);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
+        when(mResourcesSpy.getBoolean(com.android.internal.R.bool.config_unplugTurnsOnScreen))
+                .thenReturn(true);
+        mService.readConfigurationLocked();
+
+        // Test 2:
+        // Turn the power off, sleep, then plug into a wireless charger.
+        // Verify that we do not wake up if the phone is being plugged into a wireless charger.
+        setPluggedIn(false);
+        forceSleep();
+        when(mBatteryManagerInternalMock.getPlugType())
+                .thenReturn(BatteryManager.BATTERY_PLUGGED_WIRELESS);
+        when(mWirelessChargerDetectorMock.update(true /* isPowered */,
+                BatteryManager.BATTERY_PLUGGED_WIRELESS)).thenReturn(false);
+        setPluggedIn(true);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
+
+        // Test 3:
+        // Do not wake up if the phone is being REMOVED from a wireless charger
+        when(mBatteryManagerInternalMock.getPlugType()).thenReturn(0);
+        setPluggedIn(false);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
+
+        // Test 4:
+        // Do not wake if we are dreaming.
+        forceAwake();  // Needs to be awake first before it can dream.
+        forceDream();
+        setPluggedIn(true);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DREAMING);
+        forceSleep();
+
+        // Test 5:
+        // Don't wake if the device is configured not to wake up in theater mode (and theater
+        // mode is enabled).
+        Settings.Global.putInt(
+                mContextSpy.getContentResolver(), Settings.Global.THEATER_MODE_ON, 1);
+        mUserSwitchedReceiver.onReceive(mContextSpy, new Intent(Intent.ACTION_USER_SWITCHED));
+        when(mResourcesSpy.getBoolean(
+                com.android.internal.R.bool.config_allowTheaterModeWakeFromUnplug))
+                .thenReturn(false);
+        setPluggedIn(false);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
+        Settings.Global.putInt(
+                mContextSpy.getContentResolver(), Settings.Global.THEATER_MODE_ON, 0);
+        mUserSwitchedReceiver.onReceive(mContextSpy, new Intent(Intent.ACTION_USER_SWITCHED));
+
+        // Test 6:
+        // Don't wake up if we are Dozing away and always-on is enabled.
+        when(mAmbientDisplayConfigurationMock.alwaysOnEnabled(UserHandle.USER_CURRENT))
+                .thenReturn(true);
+        mUserSwitchedReceiver.onReceive(mContextSpy, new Intent(Intent.ACTION_USER_SWITCHED));
+        forceAwake();
+        forceDozing();
+        setPluggedIn(true);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DOZING);
+
+        // Test 7:
+        // Finally, take away all the factors above and ensure the device wakes up!
+        forceAwake();
+        forceSleep();
+        setPluggedIn(false);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+    }
+
+    /**
+     * Tests that dreaming stops when undocking and not configured to keep dreaming.
+     */
+    @Test
+    public void testWakefulnessDream_shouldStopDreamingWhenUndocked_whenNotConfigured() {
+        // Make sure "unplug turns on screen" is configured to true.
+        when(mResourcesSpy.getBoolean(com.android.internal.R.bool.config_unplugTurnsOnScreen))
+                .thenReturn(true);
+
+        createService();
+        startSystem();
+
+        ArgumentCaptor<DreamManagerInternal.DreamManagerStateListener> dreamManagerStateListener =
+                ArgumentCaptor.forClass(DreamManagerInternal.DreamManagerStateListener.class);
+        verify(mDreamManagerInternalMock).registerDreamManagerStateListener(
+                dreamManagerStateListener.capture());
+        dreamManagerStateListener.getValue().onKeepDreamingWhenUnpluggingChanged(false);
+
+        when(mBatteryManagerInternalMock.getPlugType())
+                .thenReturn(BatteryManager.BATTERY_PLUGGED_DOCK);
+        setPluggedIn(true);
+
+        forceAwake();  // Needs to be awake first before it can dream.
+        forceDream();
+        when(mBatteryManagerInternalMock.getPlugType()).thenReturn(0);
+        setPluggedIn(false);
+
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+    }
+
+    /**
+     * Tests that dreaming continues when undocking and configured to do so.
+     */
+    @Test
+    public void testWakefulnessDream_shouldKeepDreamingWhenUndocked_whenConfigured() {
+        // Make sure "unplug turns on screen" is configured to true.
+        when(mResourcesSpy.getBoolean(com.android.internal.R.bool.config_unplugTurnsOnScreen))
+                .thenReturn(true);
+
+        createService();
+        startSystem();
+
+        ArgumentCaptor<DreamManagerInternal.DreamManagerStateListener> dreamManagerStateListener =
+                ArgumentCaptor.forClass(DreamManagerInternal.DreamManagerStateListener.class);
+        verify(mDreamManagerInternalMock).registerDreamManagerStateListener(
+                dreamManagerStateListener.capture());
+        dreamManagerStateListener.getValue().onKeepDreamingWhenUnpluggingChanged(true);
+
+        when(mBatteryManagerInternalMock.getPlugType())
+                .thenReturn(BatteryManager.BATTERY_PLUGGED_DOCK);
+        setPluggedIn(true);
+
+        forceAwake();  // Needs to be awake first before it can dream.
+        forceDream();
+        when(mBatteryManagerInternalMock.getPlugType()).thenReturn(0);
+        setPluggedIn(false);
+
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DREAMING);
+    }
+
+    /**
+     * Tests that dreaming stops when undocking while showing a dream that prevents it.
+     */
+    @Test
+    public void testWakefulnessDream_shouldStopDreamingWhenUndocked_whenDreamPrevents() {
+        // Make sure "unplug turns on screen" is configured to true.
+        when(mResourcesSpy.getBoolean(com.android.internal.R.bool.config_unplugTurnsOnScreen))
+                .thenReturn(true);
+
+        createService();
+        startSystem();
+
+        ArgumentCaptor<DreamManagerInternal.DreamManagerStateListener> dreamManagerStateListener =
+                ArgumentCaptor.forClass(DreamManagerInternal.DreamManagerStateListener.class);
+        verify(mDreamManagerInternalMock).registerDreamManagerStateListener(
+                dreamManagerStateListener.capture());
+        dreamManagerStateListener.getValue().onKeepDreamingWhenUnpluggingChanged(true);
+
+        when(mBatteryManagerInternalMock.getPlugType())
+                .thenReturn(BatteryManager.BATTERY_PLUGGED_DOCK);
+        setPluggedIn(true);
+
+        forceAwake();  // Needs to be awake first before it can dream.
+        forceDream();
+        dreamManagerStateListener.getValue().onKeepDreamingWhenUnpluggingChanged(false);
+        when(mBatteryManagerInternalMock.getPlugType()).thenReturn(0);
+        setPluggedIn(false);
+
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+    }
+
+    @Test
+    public void testWakefulnessDream_shouldStopDreamingWhenUnplugging_whenDreamPrevents() {
+        // Make sure "unplug turns on screen" is configured to true.
+        when(mResourcesSpy.getBoolean(com.android.internal.R.bool.config_unplugTurnsOnScreen))
+                .thenReturn(true);
+
+        createService();
+        startSystem();
+
+        ArgumentCaptor<DreamManagerInternal.DreamManagerStateListener> dreamManagerStateListener =
+                ArgumentCaptor.forClass(DreamManagerInternal.DreamManagerStateListener.class);
+        verify(mDreamManagerInternalMock).registerDreamManagerStateListener(
+                dreamManagerStateListener.capture());
+
+        when(mBatteryManagerInternalMock.getPlugType())
+                .thenReturn(BatteryManager.BATTERY_PLUGGED_AC);
+        setPluggedIn(true);
+
+        forceAwake();  // Needs to be awake first before it can dream.
+        forceDream();
+        dreamManagerStateListener.getValue().onKeepDreamingWhenUnpluggingChanged(false);
+        when(mBatteryManagerInternalMock.getPlugType()).thenReturn(0);
+        setPluggedIn(false);
+
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+    }
+
+
+    @Test
+    public void testWakefulnessDoze_goToSleep() {
+        createService();
+        // Start with AWAKE state
+        startSystem();
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+
+        // Take a nap and verify.
+        mService.getBinderServiceInstance().goToSleep(mClock.now(),
+                PowerManager.GO_TO_SLEEP_REASON_APPLICATION, 0);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DOZING);
+    }
+
+    @Test
+    public void testWasDeviceIdleFor_true() {
+        int interval = 1000;
+        createService();
+        startSystem();
+        mService.onUserActivity();
+        advanceTime(interval + 1 /* just a little more */);
+        assertThat(mService.wasDeviceIdleForInternal(interval)).isTrue();
+    }
+
+    @Test
+    public void testWasDeviceIdleFor_false() {
+        int interval = 1000;
+        createService();
+        startSystem();
+        mService.onUserActivity();
+        assertThat(mService.wasDeviceIdleForInternal(interval)).isFalse();
+    }
+
+    @Test
+    public void testForceSuspend_putsDeviceToSleep() {
+        createService();
+        startSystem();
+
+        // Verify that we start awake
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+
+        // Grab the wakefulness value when PowerManager finally calls into the
+        // native component to actually perform the suspend.
+        when(mNativeWrapperMock.nativeForceSuspend()).then(inv -> {
+            assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
+            return true;
+        });
+
+        boolean retval = mService.getBinderServiceInstance().forceSuspend();
+        assertThat(retval).isTrue();
+
+        // Still asleep when the function returns.
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
+    }
+
+    @Test
+    public void testForceSuspend_wakeLocksDisabled() {
+        final String tag = "TestWakelockTag_098213";
+        final int flags = PowerManager.PARTIAL_WAKE_LOCK;
+        final String pkg = mContextSpy.getOpPackageName();
+
+        createService();
+
+        // Set up the Notification mock to keep track of the wakelocks that are currently
+        // active or disabled. We'll use this to verify that wakelocks are disabled when
+        // they should be.
+        final Map<String, Integer> wakelockMap = new HashMap<>(1);
+        doAnswer(inv -> {
+            wakelockMap.put((String) inv.getArguments()[1], (int) inv.getArguments()[0]);
+            return null;
+        }).when(mNotifierMock).onWakeLockAcquired(anyInt(), anyString(), anyString(), anyInt(),
+                anyInt(), any(), any(), any());
+        doAnswer(inv -> {
+            wakelockMap.remove((String) inv.getArguments()[1]);
+            return null;
+        }).when(mNotifierMock).onWakeLockReleased(anyInt(), anyString(), anyString(), anyInt(),
+                anyInt(), any(), any(), any());
+
+        //
+        // TEST STARTS HERE
+        //
+        startSystem();
+
+        // Verify that we start awake
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+
+        // Create a wakelock
+        mService.getBinderServiceInstance().acquireWakeLock(new Binder(), flags, tag, pkg,
+                null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY, null);
+        assertThat(wakelockMap.get(tag)).isEqualTo(flags);  // Verify wakelock is active.
+
+        // Confirm that the wakelocks have been disabled when the forceSuspend is in flight.
+        when(mNativeWrapperMock.nativeForceSuspend()).then(inv -> {
+            // Verify that the wakelock is disabled by the time we get to the native force
+            // suspend call.
+            assertThat(wakelockMap.containsKey(tag)).isFalse();
+            return true;
+        });
+
+        assertThat(mService.getBinderServiceInstance().forceSuspend()).isTrue();
+        assertThat(wakelockMap.get(tag)).isEqualTo(flags);
+
+    }
+
+    @Test
+    public void testForceSuspend_forceSuspendFailurePropagated() {
+        createService();
+        startSystem();
+        when(mNativeWrapperMock.nativeForceSuspend()).thenReturn(false);
+        assertThat(mService.getBinderServiceInstance().forceSuspend()).isFalse();
+    }
+
+    @SuppressWarnings("GuardedBy")
+    @Test
+    public void testScreensaverActivateOnSleepDisabled_powered_afterTimeout_goesToDozing() {
+        when(mBatteryManagerInternalMock.isPowered(anyInt())).thenReturn(true);
+
+        doAnswer(inv -> {
+            when(mDreamManagerInternalMock.isDreaming()).thenReturn(true);
+            return null;
+        }).when(mDreamManagerInternalMock).startDream(anyBoolean(), anyString());
+
+        setMinimumScreenOffTimeoutConfig(5);
+        createService();
+        startSystem();
+
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+
+        advanceTime(15000);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DOZING);
+    }
+
+    @SuppressWarnings("GuardedBy")
+    @Test
+    public void testScreensaverActivateOnSleepEnabled_powered_afterTimeout_goesToDreaming() {
+        when(mResourcesSpy.getBoolean(com.android.internal.R.bool.config_dreamsSupported))
+                .thenReturn(true);
+        when(mBatteryManagerInternalMock.isPowered(anyInt())).thenReturn(true);
+        Settings.Secure.putInt(mContextSpy.getContentResolver(),
+                Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP, 1);
+        Settings.Secure.putInt(mContextSpy.getContentResolver(),
+                Settings.Secure.SCREENSAVER_ENABLED, 1);
+
+        doAnswer(inv -> {
+            when(mDreamManagerInternalMock.isDreaming()).thenReturn(true);
+            return null;
+        }).when(mDreamManagerInternalMock).startDream(anyBoolean(), anyString());
+
+        setMinimumScreenOffTimeoutConfig(5);
+        createService();
+        startSystem();
+
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+
+        advanceTime(15000);
+        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 testBatteryDrainDuringDream() {
+        Settings.Secure.putInt(mContextSpy.getContentResolver(),
+                Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP, 1);
+        Settings.Secure.putInt(mContextSpy.getContentResolver(),
+                Settings.Secure.SCREENSAVER_ENABLED, 1);
+
+        setMinimumScreenOffTimeoutConfig(100);
+        setDreamsBatteryLevelDrainConfig(5);
+        createService();
+        startSystem();
+
+        doAnswer(inv -> {
+            when(mDreamManagerInternalMock.isDreaming()).thenReturn(true);
+            return null;
+        }).when(mDreamManagerInternalMock).startDream(anyBoolean(), anyString());
+
+        setBatteryLevel(100);
+        setPluggedIn(true);
+
+        forceAwake();  // Needs to be awake first before it can dream.
+        forceDream();
+        advanceTime(10); // Allow async calls to happen
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DREAMING);
+        setBatteryLevel(90);
+        advanceTime(10); // Allow async calls to happen
+        assertThat(mService.getDreamsBatteryLevelDrain()).isEqualTo(10);
+
+        // If battery overheat protection is enabled, we shouldn't count battery drain
+        setBatteryHealth(BatteryManager.BATTERY_HEALTH_OVERHEAT);
+        setBatteryLevel(70);
+        advanceTime(10); // Allow async calls to happen
+        assertThat(mService.getDreamsBatteryLevelDrain()).isEqualTo(10);
+    }
+
+    @Test
+    public void testSetDozeOverrideFromDreamManager_triggersSuspendBlocker() {
+        final String suspendBlockerName = "PowerManagerService.Display";
+        final String tag = "acq_causes_wakeup";
+        final String packageName = "pkg.name";
+        final IBinder token = new Binder();
+
+        final boolean[] isAcquired = new boolean[1];
+        doAnswer(inv -> {
+            if (suspendBlockerName.equals(inv.getArguments()[0])) {
+                isAcquired[0] = false;
+            }
+            return null;
+        }).when(mNativeWrapperMock).nativeReleaseSuspendBlocker(any());
+
+        doAnswer(inv -> {
+            if (suspendBlockerName.equals(inv.getArguments()[0])) {
+                isAcquired[0] = true;
+            }
+            return null;
+        }).when(mNativeWrapperMock).nativeAcquireSuspendBlocker(any());
+
+        // Need to create the service after we stub the mocks for this test because some of the
+        // mocks are used during the constructor.
+        createService();
+
+        // Start with AWAKE state
+        startSystem();
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+        assertTrue(isAcquired[0]);
+
+        // Take a nap and verify we no longer hold the blocker
+        int flags = PowerManager.DOZE_WAKE_LOCK;
+        mService.getBinderServiceInstance().acquireWakeLock(token, flags, tag, packageName,
+                null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY, null);
+        when(mDreamManagerInternalMock.isDreaming()).thenReturn(true);
+        mService.getBinderServiceInstance().goToSleep(mClock.now(),
+                PowerManager.GO_TO_SLEEP_REASON_APPLICATION, 0);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DOZING);
+        assertFalse(isAcquired[0]);
+
+        // Override the display state by DreamManager and verify is reacquires the blocker.
+        mService.getLocalServiceInstance()
+                .setDozeOverrideFromDreamManager(Display.STATE_ON, PowerManager.BRIGHTNESS_DEFAULT);
+        assertTrue(isAcquired[0]);
+    }
+
+    @Test
+    public void testSuspendBlockerHeldDuringBoot() {
+        final String suspendBlockerName = "PowerManagerService.Booting";
+
+        final boolean[] isAcquired = new boolean[1];
+        doAnswer(inv -> {
+            isAcquired[0] = false;
+            return null;
+        }).when(mNativeWrapperMock).nativeReleaseSuspendBlocker(eq(suspendBlockerName));
+
+        doAnswer(inv -> {
+            isAcquired[0] = true;
+            return null;
+        }).when(mNativeWrapperMock).nativeAcquireSuspendBlocker(eq(suspendBlockerName));
+
+        // Need to create the service after we stub the mocks for this test because some of the
+        // mocks are used during the constructor.
+        createService();
+        assertTrue(isAcquired[0]);
+
+        mService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY);
+        assertTrue(isAcquired[0]);
+
+        mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
+        assertFalse(isAcquired[0]);
+    }
+
+    @Test
+    public void testInattentiveSleep_hideWarningIfStayOnIsEnabledAndPluggedIn() {
+        setMinimumScreenOffTimeoutConfig(5);
+        setAttentiveWarningDuration(120);
+        setAttentiveTimeout(100);
+
+        Settings.Global.putInt(mContextSpy.getContentResolver(),
+                Settings.Global.STAY_ON_WHILE_PLUGGED_IN, BatteryManager.BATTERY_PLUGGED_AC);
+
+        createService();
+        startSystem();
+
+        verify(mInattentiveSleepWarningControllerMock, times(1)).show();
+        verify(mInattentiveSleepWarningControllerMock, never()).dismiss(anyBoolean());
+        when(mInattentiveSleepWarningControllerMock.isShown()).thenReturn(true);
+
+        setPluggedIn(true);
+        verify(mInattentiveSleepWarningControllerMock, atLeastOnce()).dismiss(true);
+    }
+
+    @Test
+    public void testInattentiveSleep_hideWarningIfInattentiveSleepIsDisabled() {
+        setMinimumScreenOffTimeoutConfig(5);
+        setAttentiveWarningDuration(120);
+        setAttentiveTimeout(100);
+
+        createService();
+        startSystem();
+
+        verify(mInattentiveSleepWarningControllerMock, times(1)).show();
+        verify(mInattentiveSleepWarningControllerMock, never()).dismiss(anyBoolean());
+        when(mInattentiveSleepWarningControllerMock.isShown()).thenReturn(true);
+
+        setAttentiveTimeout(-1);
+        mService.handleSettingsChangedLocked();
+
+        verify(mInattentiveSleepWarningControllerMock, atLeastOnce()).dismiss(true);
+    }
+
+    @Test
+    public void testInattentiveSleep_userActivityDismissesWarning() {
+        final DisplayInfo info = new DisplayInfo();
+        info.displayGroupId = Display.DEFAULT_DISPLAY_GROUP;
+        when(mDisplayManagerInternalMock.getDisplayInfo(Display.DEFAULT_DISPLAY)).thenReturn(info);
+        setMinimumScreenOffTimeoutConfig(5);
+        setAttentiveWarningDuration(1900);
+        setAttentiveTimeout(2000);
+
+        createService();
+        startSystem();
+
+        mService.getBinderServiceInstance().userActivity(Display.DEFAULT_DISPLAY, mClock.now(),
+                PowerManager.USER_ACTIVITY_EVENT_TOUCH, 0);
+        verify(mInattentiveSleepWarningControllerMock, never()).show();
+
+        advanceTime(150);
+        verify(mInattentiveSleepWarningControllerMock, times(1)).show();
+        verify(mInattentiveSleepWarningControllerMock, never()).dismiss(anyBoolean());
+        when(mInattentiveSleepWarningControllerMock.isShown()).thenReturn(true);
+
+        mService.getBinderServiceInstance().userActivity(Display.DEFAULT_DISPLAY, mClock.now(),
+                PowerManager.USER_ACTIVITY_EVENT_TOUCH, 0);
+        verify(mInattentiveSleepWarningControllerMock, times(1)).dismiss(true);
+    }
+
+    @Test
+    public void testInattentiveSleep_warningHiddenAfterWakingUp() {
+        setMinimumScreenOffTimeoutConfig(5);
+        setAttentiveWarningDuration(70);
+        setAttentiveTimeout(100);
+
+        createService();
+        startSystem();
+        advanceTime(50);
+        verify(mInattentiveSleepWarningControllerMock, atLeastOnce()).show();
+        when(mInattentiveSleepWarningControllerMock.isShown()).thenReturn(true);
+        advanceTime(70);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
+        forceAwake();
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+        verify(mInattentiveSleepWarningControllerMock, atLeastOnce()).dismiss(false);
+    }
+
+    @Test
+    public void testInattentiveSleep_warningStaysWhenDreaming() {
+        setMinimumScreenOffTimeoutConfig(5);
+        setAttentiveWarningDuration(70);
+        setAttentiveTimeout(100);
+        createService();
+        startSystem();
+        advanceTime(50);
+        verify(mInattentiveSleepWarningControllerMock, atLeastOnce()).show();
+        when(mInattentiveSleepWarningControllerMock.isShown()).thenReturn(true);
+
+        forceDream();
+        when(mDreamManagerInternalMock.isDreaming()).thenReturn(true);
+
+        advanceTime(10);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DREAMING);
+        verify(mInattentiveSleepWarningControllerMock, never()).dismiss(anyBoolean());
+    }
+
+    @Test
+    public void testInattentiveSleep_warningNotShownWhenSleeping() {
+        setMinimumScreenOffTimeoutConfig(5);
+        setAttentiveWarningDuration(70);
+        setAttentiveTimeout(100);
+        createService();
+        startSystem();
+
+        advanceTime(10);
+        forceSleep();
+
+        advanceTime(50);
+        verify(mInattentiveSleepWarningControllerMock, never()).show();
+    }
+
+    @Test
+    public void testInattentiveSleep_noWarningShownIfInattentiveSleepDisabled() {
+        setAttentiveTimeout(-1);
+        createService();
+        startSystem();
+        verify(mInattentiveSleepWarningControllerMock, never()).show();
+    }
+
+    @Test
+    public void testInattentiveSleep_goesToSleepAfterTimeout() {
+        setMinimumScreenOffTimeoutConfig(5);
+        setAttentiveTimeout(5);
+        createService();
+        startSystem();
+        advanceTime(20);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
+        assertThat(mService.getBinderServiceInstance().getLastSleepReason()).isEqualTo(
+                PowerManager.GO_TO_SLEEP_REASON_INATTENTIVE);
+    }
+
+    @Test
+    public void testInattentiveSleep_goesToSleepWithWakeLock() {
+        final String pkg = mContextSpy.getOpPackageName();
+        final Binder token = new Binder();
+        final String tag = "testInattentiveSleep_goesToSleepWithWakeLock";
+
+        setMinimumScreenOffTimeoutConfig(5);
+        setAttentiveTimeout(30);
+        createService();
+        startSystem();
+
+        mService.getBinderServiceInstance().acquireWakeLock(token,
+                PowerManager.SCREEN_BRIGHT_WAKE_LOCK, tag, pkg,
+                null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY, null);
+
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+        advanceTime(60);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
+        assertThat(mService.getBinderServiceInstance().getLastSleepReason()).isEqualTo(
+                PowerManager.GO_TO_SLEEP_REASON_INATTENTIVE);
+    }
+
+    @Test
+    public void testInattentiveSleep_dreamEnds_goesToSleepAfterTimeout() {
+        setMinimumScreenOffTimeoutConfig(5);
+        setAttentiveTimeout(30000);
+        createService();
+        startSystem();
+
+        advanceTime(10000);
+        forceDream();
+        advanceTime(10000);
+        final String pkg = mContextSpy.getOpPackageName();
+        mService.getBinderServiceInstance().wakeUp(mClock.now(),
+                PowerManager.WAKE_REASON_DREAM_FINISHED, "PowerManagerServiceTest:DREAM_FINISHED",
+                pkg);
+        advanceTime(10001);
+
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
+        assertThat(mService.getBinderServiceInstance().getLastSleepReason()).isEqualTo(
+                PowerManager.GO_TO_SLEEP_REASON_INATTENTIVE);
+    }
+
+    @Test
+    public void testInattentiveSleep_wakeLockOnAfterRelease_inattentiveSleepTimeoutNotAffected() {
+        final DisplayInfo info = new DisplayInfo();
+        info.displayGroupId = Display.DEFAULT_DISPLAY_GROUP;
+        when(mDisplayManagerInternalMock.getDisplayInfo(Display.DEFAULT_DISPLAY)).thenReturn(info);
+
+        final String pkg = mContextSpy.getOpPackageName();
+        final Binder token = new Binder();
+        final String tag = "testInattentiveSleep_wakeLockOnAfterRelease";
+
+        setMinimumScreenOffTimeoutConfig(5);
+        setAttentiveTimeout(2000);
+        createService();
+        startSystem();
+
+        mService.getBinderServiceInstance().acquireWakeLock(token,
+                PowerManager.SCREEN_BRIGHT_WAKE_LOCK | PowerManager.ON_AFTER_RELEASE, tag, pkg,
+                null /* workSource */, null /* historyTag */, Display.DEFAULT_DISPLAY, null);
+
+        advanceTime(1500);
+        mService.getBinderServiceInstance().releaseWakeLock(token, 0 /* flags */);
+
+        advanceTime(520);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
+        assertThat(mService.getBinderServiceInstance().getLastSleepReason()).isEqualTo(
+                PowerManager.GO_TO_SLEEP_REASON_INATTENTIVE);
+    }
+
+    @Test
+    public void testInattentiveSleep_userActivityNoChangeLights_inattentiveSleepTimeoutNotAffected() {
+        final DisplayInfo info = new DisplayInfo();
+        info.displayGroupId = Display.DEFAULT_DISPLAY_GROUP;
+        when(mDisplayManagerInternalMock.getDisplayInfo(Display.DEFAULT_DISPLAY)).thenReturn(info);
+
+        setMinimumScreenOffTimeoutConfig(5);
+        setAttentiveTimeout(2000);
+        createService();
+        startSystem();
+
+        advanceTime(1500);
+        mService.getBinderServiceInstance().userActivity(Display.DEFAULT_DISPLAY, mClock.now(),
+                PowerManager.USER_ACTIVITY_EVENT_OTHER,
+                PowerManager.USER_ACTIVITY_FLAG_NO_CHANGE_LIGHTS);
+
+        advanceTime(520);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
+        assertThat(mService.getBinderServiceInstance().getLastSleepReason()).isEqualTo(
+                PowerManager.GO_TO_SLEEP_REASON_INATTENTIVE);
+    }
+
+    @Test
+    public void testInattentiveSleep_userActivity_inattentiveSleepTimeoutExtended() {
+        final DisplayInfo info = new DisplayInfo();
+        info.displayGroupId = Display.DEFAULT_DISPLAY_GROUP;
+        when(mDisplayManagerInternalMock.getDisplayInfo(Display.DEFAULT_DISPLAY)).thenReturn(info);
+
+        setMinimumScreenOffTimeoutConfig(5);
+        setAttentiveTimeout(2000);
+        createService();
+        startSystem();
+
+        advanceTime(1500);
+        mService.getBinderServiceInstance().userActivity(Display.DEFAULT_DISPLAY, mClock.now(),
+                PowerManager.USER_ACTIVITY_EVENT_OTHER, 0 /* flags */);
+
+        advanceTime(520);
+        assertThat(mService.getGlobalWakefulnessLocked()).isNotEqualTo(WAKEFULNESS_ASLEEP);
+    }
+
+
+    @SuppressWarnings("GuardedBy")
+    @Test
+    public void testInattentiveSleep_goesToSleepFromDream() {
+        setAttentiveTimeout(20000);
+        createService();
+        startSystem();
+        setPluggedIn(true);
+        forceAwake();
+        forceDream();
+        when(mDreamManagerInternalMock.isDreaming()).thenReturn(true);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DREAMING);
+
+        advanceTime(20500);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
+    }
+
+    @Test
+    public void testWakeLock_affectsProperDisplayGroup() {
+        final int nonDefaultDisplayGroupId = Display.DEFAULT_DISPLAY_GROUP + 1;
+        final AtomicReference<DisplayManagerInternal.DisplayGroupListener> listener =
+                new AtomicReference<>();
+        doAnswer((Answer<Void>) invocation -> {
+            listener.set(invocation.getArgument(0));
+            return null;
+        }).when(mDisplayManagerInternalMock).registerDisplayGroupListener(any());
+        final DisplayInfo info = new DisplayInfo();
+        info.displayGroupId = Display.DEFAULT_DISPLAY_GROUP;
+        when(mDisplayManagerInternalMock.getDisplayInfo(Display.DEFAULT_DISPLAY)).thenReturn(info);
+
+        final String pkg = mContextSpy.getOpPackageName();
+        final Binder token = new Binder();
+        final String tag = "testWakeLock_affectsProperDisplayGroup";
+
+        setMinimumScreenOffTimeoutConfig(5);
+        createService();
+        startSystem();
+        listener.get().onDisplayGroupAdded(nonDefaultDisplayGroupId);
+
+        mService.getBinderServiceInstance().acquireWakeLock(token,
+                PowerManager.SCREEN_BRIGHT_WAKE_LOCK, tag, pkg,
+                null /* workSource */, null /* historyTag */, Display.DEFAULT_DISPLAY, null);
+
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+        assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP)).isEqualTo(
+                WAKEFULNESS_AWAKE);
+        assertThat(mService.getWakefulnessLocked(nonDefaultDisplayGroupId)).isEqualTo(
+                WAKEFULNESS_AWAKE);
+
+        advanceTime(15000);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+        assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP)).isEqualTo(
+                WAKEFULNESS_AWAKE);
+        assertThat(mService.getWakefulnessLocked(nonDefaultDisplayGroupId)).isEqualTo(
+                WAKEFULNESS_DOZING);
+    }
+
+    @Test
+    public void testInvalidDisplayGroupWakeLock_affectsAllDisplayGroups() {
+        final int nonDefaultDisplayGroupId = Display.DEFAULT_DISPLAY_GROUP + 1;
+        final AtomicReference<DisplayManagerInternal.DisplayGroupListener> listener =
+                new AtomicReference<>();
+        doAnswer((Answer<Void>) invocation -> {
+            listener.set(invocation.getArgument(0));
+            return null;
+        }).when(mDisplayManagerInternalMock).registerDisplayGroupListener(any());
+        final DisplayInfo info = new DisplayInfo();
+        info.displayGroupId = Display.DEFAULT_DISPLAY_GROUP;
+        when(mDisplayManagerInternalMock.getDisplayInfo(Display.DEFAULT_DISPLAY)).thenReturn(info);
+
+        final String pkg = mContextSpy.getOpPackageName();
+        final Binder token = new Binder();
+        final String tag = "testInvalidDisplayGroupWakeLock_affectsAllDisplayGroups";
+
+        setMinimumScreenOffTimeoutConfig(5);
+        createService();
+        startSystem();
+        listener.get().onDisplayGroupAdded(nonDefaultDisplayGroupId);
+
+        mService.getBinderServiceInstance().acquireWakeLock(token,
+                PowerManager.SCREEN_BRIGHT_WAKE_LOCK, tag, pkg,
+                null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY, null);
+
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+        assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP)).isEqualTo(
+                WAKEFULNESS_AWAKE);
+        assertThat(mService.getWakefulnessLocked(nonDefaultDisplayGroupId)).isEqualTo(
+                WAKEFULNESS_AWAKE);
+
+        advanceTime(15000);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+        assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP)).isEqualTo(
+                WAKEFULNESS_AWAKE);
+        assertThat(mService.getWakefulnessLocked(nonDefaultDisplayGroupId)).isEqualTo(
+                WAKEFULNESS_AWAKE);
+    }
+
+    @Test
+    public void testRemovedDisplayGroupWakeLock_affectsNoDisplayGroups() {
+        final int nonDefaultDisplayGroupId = Display.DEFAULT_DISPLAY_GROUP + 1;
+        final int nonDefaultDisplay = Display.DEFAULT_DISPLAY + 1;
+        final AtomicReference<DisplayManagerInternal.DisplayGroupListener> listener =
+                new AtomicReference<>();
+        doAnswer((Answer<Void>) invocation -> {
+            listener.set(invocation.getArgument(0));
+            return null;
+        }).when(mDisplayManagerInternalMock).registerDisplayGroupListener(any());
+        final DisplayInfo info = new DisplayInfo();
+        info.displayGroupId = nonDefaultDisplayGroupId;
+        when(mDisplayManagerInternalMock.getDisplayInfo(nonDefaultDisplay)).thenReturn(info);
+
+        doAnswer(inv -> {
+            when(mDreamManagerInternalMock.isDreaming()).thenReturn(true);
+            return null;
+        }).when(mDreamManagerInternalMock).startDream(anyBoolean(), anyString());
+
+        final String pkg = mContextSpy.getOpPackageName();
+        final Binder token = new Binder();
+        final String tag = "testRemovedDisplayGroupWakeLock_affectsNoDisplayGroups";
+
+        setMinimumScreenOffTimeoutConfig(5);
+        createService();
+        startSystem();
+        listener.get().onDisplayGroupAdded(nonDefaultDisplayGroupId);
+
+        mService.getBinderServiceInstance().acquireWakeLock(token,
+                PowerManager.SCREEN_BRIGHT_WAKE_LOCK, tag, pkg,
+                null /* workSource */, null /* historyTag */, nonDefaultDisplay, null);
+
+        assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP)).isEqualTo(
+                WAKEFULNESS_AWAKE);
+        assertThat(mService.getWakefulnessLocked(nonDefaultDisplayGroupId)).isEqualTo(
+                WAKEFULNESS_AWAKE);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+
+        listener.get().onDisplayGroupRemoved(nonDefaultDisplayGroupId);
+
+        advanceTime(15000);
+        assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP)).isEqualTo(
+                WAKEFULNESS_DOZING);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DOZING);
+    }
+
+    @Test
+    public void testBoot_ShouldBeAwake() {
+        createService();
+        startSystem();
+
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+        verify(mNotifierMock, never()).onGlobalWakefulnessChangeStarted(anyInt(), anyInt(),
+                anyLong());
+    }
+
+    @Test
+    public void testBoot_DesiredScreenPolicyShouldBeBright() {
+        createService();
+        startSystem();
+
+        assertThat(mService.getDesiredScreenPolicyLocked(Display.DEFAULT_DISPLAY)).isEqualTo(
+                DisplayPowerRequest.POLICY_BRIGHT);
+    }
+
+    @Test
+    public void testQuiescentBoot_ShouldBeAsleep() {
+        when(mSystemPropertiesMock.get(eq(SYSTEM_PROPERTY_QUIESCENT), any())).thenReturn("1");
+        createService();
+        startSystem();
+
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
+        verify(mNotifierMock).onGlobalWakefulnessChangeStarted(eq(WAKEFULNESS_ASLEEP), anyInt(),
+                anyLong());
+    }
+
+    @Test
+    public void testQuiescentBoot_DesiredScreenPolicyShouldBeOff() {
+        when(mSystemPropertiesMock.get(eq(SYSTEM_PROPERTY_QUIESCENT), any())).thenReturn("1");
+        createService();
+        startSystem();
+        assertThat(mService.getDesiredScreenPolicyLocked(Display.DEFAULT_DISPLAY)).isEqualTo(
+                DisplayPowerRequest.POLICY_OFF);
+    }
+
+    @Test
+    public void testQuiescentBoot_WakeUp_DesiredScreenPolicyShouldBeBright() {
+        when(mSystemPropertiesMock.get(eq(SYSTEM_PROPERTY_QUIESCENT), any())).thenReturn("1");
+        createService();
+        startSystem();
+        forceAwake();
+        assertThat(mService.getDesiredScreenPolicyLocked(Display.DEFAULT_DISPLAY)).isEqualTo(
+                DisplayPowerRequest.POLICY_BRIGHT);
+    }
+
+    @Test
+    public void testQuiescentBoot_WakeKeyBeforeBootCompleted_AwakeAfterBootCompleted() {
+        when(mSystemPropertiesMock.get(eq(SYSTEM_PROPERTY_QUIESCENT), any())).thenReturn("1");
+        createService();
+        startSystem();
+
+        mService.getBinderServiceInstance().wakeUp(mClock.now(),
+                PowerManager.WAKE_REASON_UNKNOWN, "testing IPowerManager.wakeUp()", "pkg.name");
+
+        mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+        assertThat(mService.getDesiredScreenPolicyLocked(Display.DEFAULT_DISPLAY_GROUP)).isEqualTo(
+                DisplayPowerRequest.POLICY_BRIGHT);
+    }
+
+    @Test
+    public void testIsAmbientDisplayAvailable_available() {
+        createService();
+        when(mAmbientDisplayConfigurationMock.ambientDisplayAvailable()).thenReturn(true);
+
+        assertThat(mService.getBinderServiceInstance().isAmbientDisplayAvailable()).isTrue();
+    }
+
+    @Test
+    public void testIsAmbientDisplayAvailable_unavailable() {
+        createService();
+        when(mAmbientDisplayConfigurationMock.ambientDisplayAvailable()).thenReturn(false);
+
+        assertThat(mService.getBinderServiceInstance().isAmbientDisplayAvailable()).isFalse();
+    }
+
+    @Test
+    public void testIsAmbientDisplaySuppressed_default_notSuppressed() {
+        createService();
+
+        assertThat(mService.getBinderServiceInstance().isAmbientDisplaySuppressed()).isFalse();
+    }
+
+    @Test
+    public void testIsAmbientDisplaySuppressed_suppressed() {
+        createService();
+        mService.getBinderServiceInstance().suppressAmbientDisplay("test", true);
+
+        assertThat(mService.getBinderServiceInstance().isAmbientDisplaySuppressed()).isTrue();
+    }
+
+    @Test
+    public void testIsAmbientDisplaySuppressed_notSuppressed() {
+        createService();
+        mService.getBinderServiceInstance().suppressAmbientDisplay("test", false);
+
+        assertThat(mService.getBinderServiceInstance().isAmbientDisplaySuppressed()).isFalse();
+    }
+
+    @Test
+    public void testIsAmbientDisplaySuppressed_multipleTokens_suppressed() {
+        createService();
+        mService.getBinderServiceInstance().suppressAmbientDisplay("test1", false);
+        mService.getBinderServiceInstance().suppressAmbientDisplay("test2", true);
+
+        assertThat(mService.getBinderServiceInstance().isAmbientDisplaySuppressed()).isTrue();
+    }
+
+    @Test
+    public void testIsAmbientDisplaySuppressed_multipleTokens_notSuppressed() {
+        createService();
+        mService.getBinderServiceInstance().suppressAmbientDisplay("test1", false);
+        mService.getBinderServiceInstance().suppressAmbientDisplay("test2", false);
+
+        assertThat(mService.getBinderServiceInstance().isAmbientDisplaySuppressed()).isFalse();
+    }
+
+    @Test
+    public void testIsAmbientDisplaySuppressedForToken_default_notSuppressed() {
+        createService();
+
+        assertThat(mService.getBinderServiceInstance().isAmbientDisplaySuppressedForToken("test"))
+            .isFalse();
+    }
+
+    @Test
+    public void testIsAmbientDisplaySuppressedForToken_suppressed() {
+        createService();
+        mService.getBinderServiceInstance().suppressAmbientDisplay("test", true);
+
+        assertThat(mService.getBinderServiceInstance().isAmbientDisplaySuppressedForToken("test"))
+            .isTrue();
+    }
+
+    @Test
+    public void testIsAmbientDisplaySuppressedForToken_notSuppressed() {
+        createService();
+        mService.getBinderServiceInstance().suppressAmbientDisplay("test", false);
+
+        assertThat(mService.getBinderServiceInstance().isAmbientDisplaySuppressedForToken("test"))
+            .isFalse();
+    }
+
+    @Test
+    public void testIsAmbientDisplaySuppressedForToken_multipleTokens_suppressed() {
+        createService();
+        mService.getBinderServiceInstance().suppressAmbientDisplay("test1", true);
+        mService.getBinderServiceInstance().suppressAmbientDisplay("test2", true);
+
+        assertThat(mService.getBinderServiceInstance().isAmbientDisplaySuppressedForToken("test1"))
+            .isTrue();
+        assertThat(mService.getBinderServiceInstance().isAmbientDisplaySuppressedForToken("test2"))
+            .isTrue();
+    }
+
+    @Test
+    public void testIsAmbientDisplaySuppressedForToken_multipleTokens_notSuppressed() {
+        createService();
+        mService.getBinderServiceInstance().suppressAmbientDisplay("test1", true);
+        mService.getBinderServiceInstance().suppressAmbientDisplay("test2", false);
+
+        assertThat(mService.getBinderServiceInstance().isAmbientDisplaySuppressedForToken("test1"))
+            .isTrue();
+        assertThat(mService.getBinderServiceInstance().isAmbientDisplaySuppressedForToken("test2"))
+            .isFalse();
+    }
+
+    @Test
+    public void testIsAmbientDisplaySuppressedForTokenByApp_ambientDisplayUnavailable() {
+        createService();
+        when(mAmbientDisplayConfigurationMock.ambientDisplayAvailable()).thenReturn(false);
+
+        BinderService service = mService.getBinderServiceInstance();
+        assertThat(service.isAmbientDisplaySuppressedForTokenByApp("test", Binder.getCallingUid()))
+            .isFalse();
+    }
+
+    @Test
+    public void testIsAmbientDisplaySuppressedForTokenByApp_default() {
+        createService();
+
+        BinderService service = mService.getBinderServiceInstance();
+        assertThat(service.isAmbientDisplaySuppressedForTokenByApp("test", Binder.getCallingUid()))
+            .isFalse();
+    }
+
+    @Test
+    public void testIsAmbientDisplaySuppressedForTokenByApp_suppressedByCallingApp() {
+        createService();
+        BinderService service = mService.getBinderServiceInstance();
+        service.suppressAmbientDisplay("test", true);
+
+        assertThat(service.isAmbientDisplaySuppressedForTokenByApp("test", Binder.getCallingUid()))
+            .isTrue();
+        // Check that isAmbientDisplaySuppressedForTokenByApp doesn't return true for another app.
+        assertThat(service.isAmbientDisplaySuppressedForTokenByApp("test", /* appUid= */ 123))
+            .isFalse();
+    }
+
+    @Test
+    public void testIsAmbientDisplaySuppressedForTokenByApp_notSuppressedByCallingApp() {
+        createService();
+        BinderService service = mService.getBinderServiceInstance();
+        service.suppressAmbientDisplay("test", false);
+
+        assertThat(service.isAmbientDisplaySuppressedForTokenByApp("test", Binder.getCallingUid()))
+            .isFalse();
+        // Check that isAmbientDisplaySuppressedForTokenByApp doesn't return true for another app.
+        assertThat(service.isAmbientDisplaySuppressedForTokenByApp("test", /* appUid= */ 123))
+            .isFalse();
+    }
+
+    @Test
+    public void testIsAmbientDisplaySuppressedForTokenByApp_multipleTokensSuppressedByCallingApp() {
+        createService();
+        BinderService service = mService.getBinderServiceInstance();
+        service.suppressAmbientDisplay("test1", true);
+        service.suppressAmbientDisplay("test2", true);
+
+        assertThat(service.isAmbientDisplaySuppressedForTokenByApp("test1", Binder.getCallingUid()))
+            .isTrue();
+        assertThat(service.isAmbientDisplaySuppressedForTokenByApp("test2", Binder.getCallingUid()))
+            .isTrue();
+        // Check that isAmbientDisplaySuppressedForTokenByApp doesn't return true for another app.
+        assertThat(service.isAmbientDisplaySuppressedForTokenByApp("test1", /* appUid= */ 123))
+            .isFalse();
+        assertThat(service.isAmbientDisplaySuppressedForTokenByApp("test2", /* appUid= */ 123))
+            .isFalse();
+    }
+
+    @Test
+    public void testGetAmbientDisplaySuppressionTokens_default() {
+        createService();
+        BinderService service = mService.getBinderServiceInstance();
+
+        assertThat(service.getAmbientDisplaySuppressionTokens()).isEmpty();
+    }
+
+    @Test
+    public void testGetAmbientDisplaySuppressionTokens_singleToken() {
+        createService();
+        BinderService service = mService.getBinderServiceInstance();
+        service.suppressAmbientDisplay("test1", true);
+        service.suppressAmbientDisplay("test2", false);
+
+        assertThat(service.getAmbientDisplaySuppressionTokens()).containsExactly("test1");
+    }
+
+    @Test
+    public void testGetAmbientDisplaySuppressionTokens_multipleTokens() {
+        createService();
+        BinderService service = mService.getBinderServiceInstance();
+        service.suppressAmbientDisplay("test1", true);
+        service.suppressAmbientDisplay("test2", true);
+
+        assertThat(service.getAmbientDisplaySuppressionTokens())
+                .containsExactly("test1", "test2");
+    }
+
+    @Test
+    public void testSetPowerBoost_redirectsCallToNativeWrapper() {
+        createService();
+        startSystem();
+
+        mService.getBinderServiceInstance().setPowerBoost(Boost.INTERACTION, 1234);
+
+        verify(mNativeWrapperMock).nativeSetPowerBoost(eq(Boost.INTERACTION), eq(1234));
+    }
+
+    @Test
+    public void testSetPowerMode_redirectsCallToNativeWrapper() {
+        createService();
+        startSystem();
+
+        // Enabled launch boost in BatterySaverController to allow setting launch mode.
+        when(mBatterySaverControllerMock.isLaunchBoostDisabled()).thenReturn(false);
+        when(mNativeWrapperMock.nativeSetPowerMode(anyInt(), anyBoolean())).thenReturn(true);
+
+        mService.getBinderServiceInstance().setPowerMode(Mode.LAUNCH, true);
+
+        verify(mNativeWrapperMock).nativeSetPowerMode(eq(Mode.LAUNCH), eq(true));
+    }
+
+    @Test
+    public void testSetPowerMode_withLaunchBoostDisabledAndModeLaunch_ignoresCallToEnable() {
+        createService();
+        startSystem();
+
+        // Disables launch boost in BatterySaverController.
+        when(mBatterySaverControllerMock.isLaunchBoostDisabled()).thenReturn(true);
+        when(mNativeWrapperMock.nativeSetPowerMode(anyInt(), anyBoolean())).thenReturn(true);
+
+        mService.getBinderServiceInstance().setPowerMode(Mode.LAUNCH, true);
+        mService.getBinderServiceInstance().setPowerMode(Mode.LAUNCH, false);
+
+        verify(mNativeWrapperMock, never()).nativeSetPowerMode(eq(Mode.LAUNCH), eq(true));
+        verify(mNativeWrapperMock).nativeSetPowerMode(eq(Mode.LAUNCH), eq(false));
+    }
+
+    @Test
+    public void testSetPowerModeChecked_returnsNativeCallResult() {
+        createService();
+        startSystem();
+
+        // Disables launch boost in BatterySaverController.
+        when(mBatterySaverControllerMock.isLaunchBoostDisabled()).thenReturn(true);
+        when(mNativeWrapperMock.nativeSetPowerMode(anyInt(), anyBoolean())).thenReturn(true);
+        when(mNativeWrapperMock.nativeSetPowerMode(eq(Mode.INTERACTIVE), anyBoolean()))
+            .thenReturn(false);
+
+        // Ignored because isLaunchBoostDisabled is true. Should return false.
+        assertFalse(mService.getBinderServiceInstance().setPowerModeChecked(Mode.LAUNCH, true));
+        // Native calls return true.
+        assertTrue(mService.getBinderServiceInstance().setPowerModeChecked(Mode.LAUNCH, false));
+        assertTrue(mService.getBinderServiceInstance().setPowerModeChecked(Mode.LOW_POWER, true));
+        // Native call for interactive returns false.
+        assertFalse(
+                mService.getBinderServiceInstance().setPowerModeChecked(Mode.INTERACTIVE, false));
+    }
+
+    @Test
+    public void testMultiDisplay_wakefulnessUpdates() {
+        final int nonDefaultDisplayGroupId = Display.DEFAULT_DISPLAY_GROUP + 1;
+        final AtomicReference<DisplayManagerInternal.DisplayGroupListener> listener =
+                new AtomicReference<>();
+        doAnswer((Answer<Void>) invocation -> {
+            listener.set(invocation.getArgument(0));
+            return null;
+        }).when(mDisplayManagerInternalMock).registerDisplayGroupListener(any());
+
+        createService();
+        startSystem();
+        listener.get().onDisplayGroupAdded(nonDefaultDisplayGroupId);
+
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+
+        mService.setWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP, WAKEFULNESS_ASLEEP, 0, 0, 0, 0,
+                null, null);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+
+        mService.setWakefulnessLocked(nonDefaultDisplayGroupId, WAKEFULNESS_ASLEEP, 0, 0, 0, 0,
+                null, null);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
+
+        mService.setWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP, WAKEFULNESS_AWAKE, 0, 0, 0, 0,
+                null, null);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+    }
+
+    @Test
+    public void testMultiDisplay_addDisplayGroup_wakesDeviceUp() {
+        final int nonDefaultDisplayGroupId = Display.DEFAULT_DISPLAY_GROUP + 1;
+        final AtomicReference<DisplayManagerInternal.DisplayGroupListener> listener =
+                new AtomicReference<>();
+        doAnswer((Answer<Void>) invocation -> {
+            listener.set(invocation.getArgument(0));
+            return null;
+        }).when(mDisplayManagerInternalMock).registerDisplayGroupListener(any());
+
+        createService();
+        startSystem();
+
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+
+        mService.setWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP, WAKEFULNESS_ASLEEP, 0, 0, 0, 0,
+                null, null);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
+
+        listener.get().onDisplayGroupAdded(nonDefaultDisplayGroupId);
+
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+    }
+
+    @Test
+    public void testMultiDisplay_removeDisplayGroup_updatesWakefulness() {
+        final int nonDefaultDisplayGroupId = Display.DEFAULT_DISPLAY_GROUP + 1;
+        final AtomicReference<DisplayManagerInternal.DisplayGroupListener> listener =
+                new AtomicReference<>();
+        doAnswer((Answer<Void>) invocation -> {
+            listener.set(invocation.getArgument(0));
+            return null;
+        }).when(mDisplayManagerInternalMock).registerDisplayGroupListener(any());
+
+        createService();
+        startSystem();
+        listener.get().onDisplayGroupAdded(nonDefaultDisplayGroupId);
+
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+
+        mService.setWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP, WAKEFULNESS_ASLEEP, 0, 0, 0, 0,
+                null, null);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+
+        listener.get().onDisplayGroupRemoved(nonDefaultDisplayGroupId);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
+
+        mService.setWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP, WAKEFULNESS_AWAKE, 0, 0, 0, 0,
+                null, null);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+    }
+
+    @Test
+    public void testMultiDisplay_updatesLastGlobalWakeTime() {
+        final int nonDefaultPowerGroupId = Display.DEFAULT_DISPLAY_GROUP + 1;
+        final AtomicReference<DisplayManagerInternal.DisplayGroupListener> listener =
+                new AtomicReference<>();
+        long eventTime1 = 10;
+        long eventTime2 = eventTime1 + 1;
+        long eventTime3 = eventTime2 + 1;
+        long eventTime4 = eventTime3 + 1;
+        doAnswer((Answer<Void>) invocation -> {
+            listener.set(invocation.getArgument(0));
+            return null;
+        }).when(mDisplayManagerInternalMock).registerDisplayGroupListener(any());
+
+        createService();
+        startSystem();
+        listener.get().onDisplayGroupAdded(nonDefaultPowerGroupId);
+
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+
+        mService.setWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP, WAKEFULNESS_DOZING, eventTime1,
+                0, PowerManager.GO_TO_SLEEP_REASON_INATTENTIVE, 0, null, null);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+
+        mService.setWakefulnessLocked(nonDefaultPowerGroupId, WAKEFULNESS_DOZING, eventTime2,
+                0, PowerManager.GO_TO_SLEEP_REASON_APPLICATION, 0, null, null);
+        long eventElapsedRealtime1 = mLastElapsedRealtime;
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DOZING);
+        assertThat(mService.getBinderServiceInstance().getLastSleepReason()).isEqualTo(
+                PowerManager.GO_TO_SLEEP_REASON_APPLICATION);
+
+        mService.setWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP, WAKEFULNESS_AWAKE,
+                eventTime3, /* uid= */ 0, PowerManager.WAKE_REASON_PLUGGED_IN, /* opUid= */
+                0, /* opPackageName= */ null, /* details= */ null);
+        long eventElapsedRealtime2 = mLastElapsedRealtime;
+        PowerManager.WakeData wakeData = mService.getLocalServiceInstance().getLastWakeup();
+        assertThat(wakeData.wakeTime).isEqualTo(eventTime3);
+        assertThat(wakeData.wakeReason).isEqualTo(PowerManager.WAKE_REASON_PLUGGED_IN);
+        assertThat(wakeData.sleepDurationRealtime)
+                .isEqualTo(eventElapsedRealtime2 - eventElapsedRealtime1);
+
+        // The global wake time and reason as well as sleep duration shouldn't change when another
+        // PowerGroup wakes up.
+        mService.setWakefulnessLocked(nonDefaultPowerGroupId, WAKEFULNESS_AWAKE,
+                eventTime4, /* uid= */ 0, PowerManager.WAKE_REASON_CAMERA_LAUNCH, /* opUid= */
+                0, /* opPackageName= */ null, /* details= */ null);
+        PowerManager.WakeData wakeData2 = mService.getLocalServiceInstance().getLastWakeup();
+        assertThat(wakeData2).isEqualTo(wakeData);
+    }
+
+    @Test
+    public void testMultiDisplay_defaultDisplayCanDoze() {
+        createService();
+        startSystem();
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+        assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP)).isEqualTo(
+                WAKEFULNESS_AWAKE);
+
+        forceDozing();
+        // Allow handleSandman() to be called asynchronously
+        advanceTime(500);
+        verify(mDreamManagerInternalMock).startDream(eq(true), anyString());
+    }
+
+    @Test
+    public void testMultiDisplay_twoDisplays_defaultDisplayCanDoze() {
+        final int nonDefaultDisplayGroupId = Display.DEFAULT_DISPLAY_GROUP + 1;
+        final int nonDefaultDisplay = Display.DEFAULT_DISPLAY + 1;
+        final AtomicReference<DisplayManagerInternal.DisplayGroupListener> listener =
+                new AtomicReference<>();
+        doAnswer((Answer<Void>) invocation -> {
+            listener.set(invocation.getArgument(0));
+            return null;
+        }).when(mDisplayManagerInternalMock).registerDisplayGroupListener(any());
+        final DisplayInfo info = new DisplayInfo();
+        info.displayGroupId = nonDefaultDisplayGroupId;
+        when(mDisplayManagerInternalMock.getDisplayInfo(nonDefaultDisplay)).thenReturn(info);
+
+        createService();
+        startSystem();
+
+        listener.get().onDisplayGroupAdded(nonDefaultDisplayGroupId);
+
+        assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP)).isEqualTo(
+                WAKEFULNESS_AWAKE);
+        assertThat(mService.getWakefulnessLocked(nonDefaultDisplayGroupId)).isEqualTo(
+                WAKEFULNESS_AWAKE);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+
+        forceDozing();
+
+        assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP)).isEqualTo(
+                WAKEFULNESS_DOZING);
+        assertThat(mService.getWakefulnessLocked(nonDefaultDisplayGroupId)).isEqualTo(
+                WAKEFULNESS_AWAKE);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+
+        // Allow handleSandman() to be called asynchronously
+        advanceTime(500);
+        verify(mDreamManagerInternalMock).startDream(eq(true), anyString());
+    }
+
+    @Test
+    public void testMultiDisplay_addNewDisplay_becomeGloballyAwakeButDefaultRemainsDozing() {
+        final int nonDefaultDisplayGroupId = Display.DEFAULT_DISPLAY_GROUP + 1;
+        final int nonDefaultDisplay = Display.DEFAULT_DISPLAY + 1;
+        final AtomicReference<DisplayManagerInternal.DisplayGroupListener> listener =
+                new AtomicReference<>();
+        doAnswer((Answer<Void>) invocation -> {
+            listener.set(invocation.getArgument(0));
+            return null;
+        }).when(mDisplayManagerInternalMock).registerDisplayGroupListener(any());
+        final DisplayInfo info = new DisplayInfo();
+        info.displayGroupId = nonDefaultDisplayGroupId;
+        when(mDisplayManagerInternalMock.getDisplayInfo(nonDefaultDisplay)).thenReturn(info);
+
+        doAnswer(inv -> {
+            when(mDreamManagerInternalMock.isDreaming()).thenReturn(true);
+            return null;
+        }).when(mDreamManagerInternalMock).startDream(anyBoolean(), anyString());
+
+        createService();
+        startSystem();
+
+        assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP)).isEqualTo(
+                WAKEFULNESS_AWAKE);
+
+        forceDozing();
+        advanceTime(500);
+
+        assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP)).isEqualTo(
+                WAKEFULNESS_DOZING);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DOZING);
+        verify(mDreamManagerInternalMock).stopDream(anyBoolean(), anyString());
+        verify(mDreamManagerInternalMock).startDream(eq(true), anyString());
+
+        listener.get().onDisplayGroupAdded(nonDefaultDisplayGroupId);
+        advanceTime(500);
+
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+        assertThat(mService.getWakefulnessLocked(nonDefaultDisplayGroupId)).isEqualTo(
+                WAKEFULNESS_AWAKE);
+        assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP)).isEqualTo(
+                WAKEFULNESS_DOZING);
+
+        // Make sure there were no additional calls to stopDream or startDream
+        verify(mDreamManagerInternalMock, atMost(1)).stopDream(anyBoolean(), anyString());
+        verify(mDreamManagerInternalMock, atMost(1)).startDream(eq(true), anyString());
+    }
+
+    @Test
+    public void testLastSleepTime_notUpdatedWhenDreaming() {
+        createService();
+        startSystem();
+
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+        PowerManager.WakeData initialWakeData = mService.getLocalServiceInstance().getLastWakeup();
+
+        forceDream();
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DREAMING);
+        assertThat(mService.getLocalServiceInstance().getLastWakeup()).isEqualTo(initialWakeData);
+    }
+
+    @Test
+    public void testMultiDisplay_onlyOneDisplaySleeps_onWakefulnessChangedEventsFire() {
+        createService();
+        startSystem();
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+        forceSleep();
+        assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP)).isEqualTo(
+                WAKEFULNESS_ASLEEP);
+
+        verify(mNotifierMock).onGroupWakefulnessChangeStarted(eq(Display.DEFAULT_DISPLAY_GROUP),
+                eq(WAKEFULNESS_ASLEEP), eq(PowerManager.GO_TO_SLEEP_REASON_APPLICATION), anyLong());
+        verify(mNotifierMock).onGlobalWakefulnessChangeStarted(eq(WAKEFULNESS_ASLEEP),
+                eq(PowerManager.GO_TO_SLEEP_REASON_APPLICATION), anyLong());
+    }
+
+    @Test
+    public void testMultiDisplay_bothDisplaysSleep_onWakefulnessChangedEventsFireCorrectly() {
+        final int nonDefaultDisplayGroupId = Display.DEFAULT_DISPLAY_GROUP + 1;
+        final int nonDefaultDisplay = Display.DEFAULT_DISPLAY + 1;
+        final AtomicReference<DisplayManagerInternal.DisplayGroupListener> listener =
+                new AtomicReference<>();
+        doAnswer((Answer<Void>) invocation -> {
+            listener.set(invocation.getArgument(0));
+            return null;
+        }).when(mDisplayManagerInternalMock).registerDisplayGroupListener(any());
+        final DisplayInfo info = new DisplayInfo();
+        info.displayGroupId = nonDefaultDisplayGroupId;
+        when(mDisplayManagerInternalMock.getDisplayInfo(nonDefaultDisplay)).thenReturn(info);
+
+        createService();
+        startSystem();
+
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+        listener.get().onDisplayGroupAdded(nonDefaultDisplayGroupId);
+
+        mService.setWakefulnessLocked(nonDefaultDisplayGroupId, WAKEFULNESS_ASLEEP, 0, 0,
+                PowerManager.GO_TO_SLEEP_REASON_APPLICATION, 0, null, null);
+        mService.setWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP, WAKEFULNESS_ASLEEP, 0, 0,
+                PowerManager.GO_TO_SLEEP_REASON_APPLICATION, 0, null, null);
+
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
+        assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP)).isEqualTo(
+                WAKEFULNESS_ASLEEP);
+        assertThat(mService.getWakefulnessLocked(nonDefaultDisplayGroupId)).isEqualTo(
+                WAKEFULNESS_ASLEEP);
+
+        verify(mNotifierMock).onGroupWakefulnessChangeStarted(eq(nonDefaultDisplayGroupId),
+                eq(WAKEFULNESS_ASLEEP), eq(PowerManager.GO_TO_SLEEP_REASON_APPLICATION), anyLong());
+        verify(mNotifierMock).onGroupWakefulnessChangeStarted(eq(Display.DEFAULT_DISPLAY_GROUP),
+                eq(WAKEFULNESS_ASLEEP), eq(PowerManager.GO_TO_SLEEP_REASON_APPLICATION), anyLong());
+        verify(mNotifierMock).onGlobalWakefulnessChangeStarted(eq(WAKEFULNESS_ASLEEP),
+                eq(PowerManager.GO_TO_SLEEP_REASON_APPLICATION), anyLong());
+    }
+
+    @Test
+    public void testMultiDisplay_separateWakeStates_onWakefulnessChangedEventsFireCorrectly() {
+        final int nonDefaultDisplayGroupId = Display.DEFAULT_DISPLAY_GROUP + 1;
+        final int nonDefaultDisplay = Display.DEFAULT_DISPLAY + 1;
+        final AtomicReference<DisplayManagerInternal.DisplayGroupListener> listener =
+                new AtomicReference<>();
+        doAnswer((Answer<Void>) invocation -> {
+            listener.set(invocation.getArgument(0));
+            return null;
+        }).when(mDisplayManagerInternalMock).registerDisplayGroupListener(any());
+        final DisplayInfo info = new DisplayInfo();
+        info.displayGroupId = nonDefaultDisplayGroupId;
+        when(mDisplayManagerInternalMock.getDisplayInfo(nonDefaultDisplay)).thenReturn(info);
+
+        createService();
+        startSystem();
+
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+        listener.get().onDisplayGroupAdded(nonDefaultDisplayGroupId);
+
+        final String pkg = mContextSpy.getOpPackageName();
+        final Binder token = new Binder();
+        final String tag =
+                "testMultiDisplay_separateWakeStates_onWakefulnessChangedEventFiresCorrectly";
+        mService.getBinderServiceInstance().acquireWakeLock(token,
+                PowerManager.SCREEN_BRIGHT_WAKE_LOCK, tag, pkg,
+                null /* workSource */, null /* historyTag */, nonDefaultDisplay, null);
+
+        forceSleep();
+
+        // The wakelock should have kept the second display awake, and we should notify that the
+        // default display went to sleep.
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+        assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP)).isEqualTo(
+                WAKEFULNESS_ASLEEP);
+        assertThat(mService.getWakefulnessLocked(nonDefaultDisplayGroupId)).isEqualTo(
+                WAKEFULNESS_AWAKE);
+        verify(mNotifierMock).onGroupWakefulnessChangeStarted(eq(nonDefaultDisplayGroupId),
+                eq(WAKEFULNESS_AWAKE), eq(PowerManager.WAKE_REASON_DISPLAY_GROUP_ADDED), anyLong());
+        verify(mNotifierMock).onGroupWakefulnessChangeStarted(eq(Display.DEFAULT_DISPLAY_GROUP),
+                eq(WAKEFULNESS_ASLEEP), eq(PowerManager.GO_TO_SLEEP_REASON_APPLICATION), anyLong());
+        verify(mNotifierMock, never()).onGlobalWakefulnessChangeStarted(eq(WAKEFULNESS_ASLEEP),
+                anyInt(), anyLong());
+    }
+
+    @Test
+    public void testMultiDisplay_oneDisplayGroupChanges_globalDoesNotChange() {
+        final int nonDefaultDisplayGroupId = Display.DEFAULT_DISPLAY_GROUP + 1;
+        final int nonDefaultDisplay = Display.DEFAULT_DISPLAY + 1;
+        final AtomicReference<DisplayManagerInternal.DisplayGroupListener> listener =
+                new AtomicReference<>();
+        doAnswer((Answer<Void>) invocation -> {
+            listener.set(invocation.getArgument(0));
+            return null;
+        }).when(mDisplayManagerInternalMock).registerDisplayGroupListener(any());
+        final DisplayInfo info = new DisplayInfo();
+        info.displayGroupId = nonDefaultDisplayGroupId;
+        when(mDisplayManagerInternalMock.getDisplayInfo(nonDefaultDisplay)).thenReturn(info);
+
+        createService();
+        startSystem();
+
+        listener.get().onDisplayGroupAdded(nonDefaultDisplayGroupId);
+
+        assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP)).isEqualTo(
+                WAKEFULNESS_AWAKE);
+        assertThat(mService.getWakefulnessLocked(nonDefaultDisplayGroupId)).isEqualTo(
+                WAKEFULNESS_AWAKE);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+
+        long eventTime = mClock.now();
+        mService.setWakefulnessLocked(nonDefaultDisplayGroupId, WAKEFULNESS_ASLEEP, eventTime, 0,
+                PowerManager.GO_TO_SLEEP_REASON_APPLICATION, 0, null, null);
+
+        assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP)).isEqualTo(
+                WAKEFULNESS_AWAKE);
+        assertThat(mService.getWakefulnessLocked(nonDefaultDisplayGroupId)).isEqualTo(
+                WAKEFULNESS_ASLEEP);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+        verify(mNotifierMock, never()).onGlobalWakefulnessChangeStarted(anyInt(), anyInt(),
+                anyLong());
+        verify(mNotifierMock, atMost(1)).onGroupWakefulnessChangeStarted(
+                eq(nonDefaultDisplayGroupId), eq(WAKEFULNESS_ASLEEP),
+                eq(PowerManager.GO_TO_SLEEP_REASON_APPLICATION), eq(eventTime));
+    }
+
+    @Test
+    public void testMultiDisplay_isInteractive_nonExistentGroup() {
+        createService();
+        startSystem();
+
+        int nonExistentDisplayGroup = 999;
+        BinderService binderService = mService.getBinderServiceInstance();
+        assertThat(binderService.isDisplayInteractive(nonExistentDisplayGroup)).isFalse();
+    }
+
+    private void testMultiDisplay_isInteractive_returnsCorrectValue(
+            boolean defaultDisplayAwake, boolean secondGroupDisplayAwake) {
+        final int nonDefaultDisplayGroupId = Display.DEFAULT_DISPLAY_GROUP + 1;
+        // We use a display id that does not match the group id, to make sure we aren't accidentally
+        // confusing display id's and display group id's in the implementation.
+        final int nonDefaultDisplay = Display.DEFAULT_DISPLAY + 17;
+        final AtomicReference<DisplayManagerInternal.DisplayGroupListener> listener =
+                new AtomicReference<>();
+        doAnswer((Answer<Void>) invocation -> {
+            listener.set(invocation.getArgument(0));
+            return null;
+        }).when(mDisplayManagerInternalMock).registerDisplayGroupListener(any());
+
+        final DisplayInfo defaultDisplayInfo = new DisplayInfo();
+        defaultDisplayInfo.displayGroupId = Display.DEFAULT_DISPLAY_GROUP;
+        when(mDisplayManagerInternalMock.getDisplayInfo(Display.DEFAULT_DISPLAY)).thenReturn(
+                defaultDisplayInfo);
+
+        final DisplayInfo secondDisplayInfo = new DisplayInfo();
+        secondDisplayInfo.displayGroupId = nonDefaultDisplayGroupId;
+        when(mDisplayManagerInternalMock.getDisplayInfo(nonDefaultDisplay)).thenReturn(
+                secondDisplayInfo);
+
+        createService();
+        startSystem();
+        listener.get().onDisplayGroupAdded(nonDefaultDisplayGroupId);
+
+        if (!defaultDisplayAwake) {
+            mService.setWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP, WAKEFULNESS_ASLEEP,
+                    mClock.now(), 0, PowerManager.GO_TO_SLEEP_REASON_APPLICATION, 0, null, null);
+        }
+        if (!secondGroupDisplayAwake) {
+            mService.setWakefulnessLocked(nonDefaultDisplayGroupId, WAKEFULNESS_ASLEEP,
+                    mClock.now(), 0,
+                    PowerManager.GO_TO_SLEEP_REASON_APPLICATION, 0, null, null);
+        }
+        assertThat(PowerManagerInternal.isInteractive(
+                mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP))).isEqualTo(
+                defaultDisplayAwake);
+        assertThat(PowerManagerInternal.isInteractive(
+                mService.getWakefulnessLocked(nonDefaultDisplayGroupId))).isEqualTo(
+                secondGroupDisplayAwake);
+
+        BinderService binderService = mService.getBinderServiceInstance();
+        assertThat(binderService.isInteractive()).isEqualTo(
+                defaultDisplayAwake || secondGroupDisplayAwake);
+        assertThat(binderService.isDisplayInteractive(Display.DEFAULT_DISPLAY)).isEqualTo(
+                defaultDisplayAwake);
+        assertThat(binderService.isDisplayInteractive(nonDefaultDisplay)).isEqualTo(
+                secondGroupDisplayAwake);
+    }
+
+    @Test
+    public void testMultiDisplay_isInteractive_defaultGroupIsAwakeSecondGroupIsAwake() {
+        testMultiDisplay_isInteractive_returnsCorrectValue(true, true);
+    }
+
+    @Test
+    public void testMultiDisplay_isInteractive_defaultGroupIsAwakeSecondGroupIsAsleep() {
+        testMultiDisplay_isInteractive_returnsCorrectValue(true, false);
+    }
+
+    @Test
+    public void testMultiDisplay_isInteractive_defaultGroupIsAsleepSecondGroupIsAwake() {
+        testMultiDisplay_isInteractive_returnsCorrectValue(false, true);
+    }
+
+    @Test
+    public void testMultiDisplay_isInteractive_bothGroupsAreAsleep() {
+        testMultiDisplay_isInteractive_returnsCorrectValue(false, false);
+    }
+
+    @Test
+    public void testMultiDisplay_defaultGroupWakefulnessChange_causesIsInteractiveInvalidate()
+            throws Exception {
+        final int nonDefaultDisplayGroupId = Display.DEFAULT_DISPLAY_GROUP + 1;
+        final int nonDefaultDisplay = Display.DEFAULT_DISPLAY + 1;
+        final AtomicReference<DisplayManagerInternal.DisplayGroupListener> listener =
+                new AtomicReference<>();
+        doAnswer((Answer<Void>) invocation -> {
+            listener.set(invocation.getArgument(0));
+            return null;
+        }).when(mDisplayManagerInternalMock).registerDisplayGroupListener(any());
+        final DisplayInfo info = new DisplayInfo();
+        info.displayGroupId = nonDefaultDisplayGroupId;
+        when(mDisplayManagerInternalMock.getDisplayInfo(nonDefaultDisplay)).thenReturn(info);
+        createService();
+        startSystem();
+        listener.get().onDisplayGroupAdded(nonDefaultDisplayGroupId);
+
+        verify(mInvalidateInteractiveCachesMock).call();
+
+        mService.setWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP, WAKEFULNESS_ASLEEP,
+                mClock.now(), 0, PowerManager.GO_TO_SLEEP_REASON_APPLICATION, 0, null, null);
+
+        verify(mInvalidateInteractiveCachesMock, times(2)).call();
+    }
+
+    @Test
+    public void testMultiDisplay_secondGroupWakefulness_causesIsInteractiveInvalidate()
+            throws Exception {
+        final int nonDefaultDisplayGroupId = Display.DEFAULT_DISPLAY_GROUP + 1;
+        final int nonDefaultDisplay = Display.DEFAULT_DISPLAY + 1;
+        final AtomicReference<DisplayManagerInternal.DisplayGroupListener> listener =
+                new AtomicReference<>();
+        doAnswer((Answer<Void>) invocation -> {
+            listener.set(invocation.getArgument(0));
+            return null;
+        }).when(mDisplayManagerInternalMock).registerDisplayGroupListener(any());
+        final DisplayInfo info = new DisplayInfo();
+        info.displayGroupId = nonDefaultDisplayGroupId;
+        when(mDisplayManagerInternalMock.getDisplayInfo(nonDefaultDisplay)).thenReturn(info);
+        createService();
+        startSystem();
+        listener.get().onDisplayGroupAdded(nonDefaultDisplayGroupId);
+
+        verify(mInvalidateInteractiveCachesMock).call();
+
+        mService.setWakefulnessLocked(nonDefaultDisplayGroupId, WAKEFULNESS_ASLEEP, mClock.now(),
+                0, PowerManager.GO_TO_SLEEP_REASON_APPLICATION, 0, null, null);
+
+        verify(mInvalidateInteractiveCachesMock, times(2)).call();
+    }
+
+    @Test
+    public void testGetFullPowerSavePolicy_returnsStateMachineResult() {
+        createService();
+        startSystem();
+        BatterySaverPolicyConfig mockReturnConfig = new BatterySaverPolicyConfig.Builder().build();
+        when(mBatterySaverStateMachineMock.getFullBatterySaverPolicy())
+                .thenReturn(mockReturnConfig);
+
+        mService.getBinderServiceInstance().setPowerSaveModeEnabled(true);
+        BatterySaverPolicyConfig policyConfig =
+                mService.getBinderServiceInstance().getFullPowerSavePolicy();
+        assertThat(mockReturnConfig).isEqualTo(policyConfig);
+        verify(mBatterySaverStateMachineMock).getFullBatterySaverPolicy();
+    }
+
+    @Test
+    public void testSetFullPowerSavePolicy_callsStateMachine() {
+        createService();
+        startSystem();
+        BatterySaverPolicyConfig mockSetPolicyConfig =
+                new BatterySaverPolicyConfig.Builder().build();
+        when(mBatterySaverStateMachineMock.setFullBatterySaverPolicy(any())).thenReturn(true);
+
+        mService.getBinderServiceInstance().setPowerSaveModeEnabled(true);
+        assertThat(mService.getBinderServiceInstance()
+                .setFullPowerSavePolicy(mockSetPolicyConfig)).isTrue();
+        verify(mBatterySaverStateMachineMock).setFullBatterySaverPolicy(eq(mockSetPolicyConfig));
+    }
+
+    @Test
+    public void testLowPowerStandby_whenInactive_FgsWakeLockEnabled() {
+        createService();
+        startSystem();
+        WakeLock wakeLock = acquireWakeLock("fgsWakeLock", PowerManager.PARTIAL_WAKE_LOCK);
+        mService.updateUidProcStateInternal(wakeLock.mOwnerUid, PROCESS_STATE_FOREGROUND_SERVICE);
+        mService.setDeviceIdleModeInternal(true);
+
+        assertThat(wakeLock.mDisabled).isFalse();
+    }
+
+    @Test
+    public void testLowPowerStandby_whenActive_FgsWakeLockDisabled() {
+        createService();
+        startSystem();
+        WakeLock wakeLock = acquireWakeLock("fgsWakeLock", PowerManager.PARTIAL_WAKE_LOCK);
+        mService.updateUidProcStateInternal(wakeLock.mOwnerUid, PROCESS_STATE_FOREGROUND_SERVICE);
+        mService.setDeviceIdleModeInternal(true);
+        mService.setLowPowerStandbyActiveInternal(true);
+
+        assertThat(wakeLock.mDisabled).isTrue();
+    }
+
+    @Test
+    public void testLowPowerStandby_whenActive_FgsWakeLockEnabledIfAllowlisted() {
+        createService();
+        startSystem();
+        WakeLock wakeLock = acquireWakeLock("fgsWakeLock", PowerManager.PARTIAL_WAKE_LOCK);
+        mService.updateUidProcStateInternal(wakeLock.mOwnerUid, PROCESS_STATE_FOREGROUND_SERVICE);
+        mService.setDeviceIdleModeInternal(true);
+        mService.setLowPowerStandbyActiveInternal(true);
+        mService.setLowPowerStandbyAllowlistInternal(new int[]{wakeLock.mOwnerUid});
+
+        assertThat(wakeLock.mDisabled).isFalse();
+    }
+
+    @Test
+    public void testLowPowerStandby_whenActive_BoundTopWakeLockDisabled() {
+        createService();
+        startSystem();
+        WakeLock wakeLock = acquireWakeLock("BoundTopWakeLock", PowerManager.PARTIAL_WAKE_LOCK);
+        mService.updateUidProcStateInternal(wakeLock.mOwnerUid, PROCESS_STATE_BOUND_TOP);
+        mService.setDeviceIdleModeInternal(true);
+        mService.setLowPowerStandbyActiveInternal(true);
+
+        assertThat(wakeLock.mDisabled).isFalse();
+    }
+
+    @Test
+    public void testSetLowPowerStandbyActiveDuringMaintenance_redirectsCallToNativeWrapper() {
+        createService();
+        startSystem();
+
+        mService.getBinderServiceInstance().setLowPowerStandbyActiveDuringMaintenance(true);
+        verify(mLowPowerStandbyControllerMock).setActiveDuringMaintenance(true);
+
+        mService.getBinderServiceInstance().setLowPowerStandbyActiveDuringMaintenance(false);
+        verify(mLowPowerStandbyControllerMock).setActiveDuringMaintenance(false);
+    }
+
+    @Test
+    public void testPowerGroupInitialization_multipleDisplayGroups() {
+        IntArray displayGroupIds = IntArray.wrap(new int[]{1, 2, 3});
+        when(mDisplayManagerInternalMock.getDisplayGroupIds()).thenReturn(displayGroupIds);
+
+        createService();
+        startSystem();
+
+        // Power group for DEFAULT_DISPLAY_GROUP is added by default.
+        assertThat(mService.getPowerGroupSize()).isEqualTo(4);
+    }
+
+    @Test
+    public void testPowerGroupInitialization_multipleDisplayGroupsWithDefaultGroup() {
+        IntArray displayGroupIds = IntArray.wrap(new int[]{Display.DEFAULT_DISPLAY_GROUP, 1, 2, 3});
+        when(mDisplayManagerInternalMock.getDisplayGroupIds()).thenReturn(displayGroupIds);
+
+        createService();
+        startSystem();
+
+        // Power group for DEFAULT_DISPLAY_GROUP is added once even if getDisplayGroupIds() return
+        // an array including DEFAULT_DESIPLAY_GROUP.
+        assertThat(mService.getPowerGroupSize()).isEqualTo(4);
+    }
+
+    private WakeLock acquireWakeLock(String tag, int flags) {
+        IBinder token = new Binder();
+        String packageName = "pkg.name";
+        mService.getBinderServiceInstance().acquireWakeLock(token, flags, tag, packageName,
+                null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY,
+                null /* callback */);
+        return mService.findWakeLockLocked(token);
+    }
+
+    /**
+     * Test IPowerManager.acquireWakeLock() with a IWakeLockCallback.
+     */
+    @Test
+    public void testNotifyWakeLockCallback() {
+        createService();
+        startSystem();
+        final String tag = "wakelock1";
+        final String packageName = "pkg.name";
+        final IBinder token = new Binder();
+        final int flags = PowerManager.PARTIAL_WAKE_LOCK;
+        final IWakeLockCallback callback = Mockito.mock(IWakeLockCallback.class);
+        final IBinder callbackBinder = Mockito.mock(Binder.class);
+        when(callback.asBinder()).thenReturn(callbackBinder);
+        mService.getBinderServiceInstance().acquireWakeLock(token, flags, tag, packageName,
+                null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY, callback);
+        verify(mNotifierMock).onWakeLockAcquired(anyInt(), eq(tag), eq(packageName), anyInt(),
+                anyInt(), any(), any(), same(callback));
+
+        mService.getBinderServiceInstance().releaseWakeLock(token, 0);
+        verify(mNotifierMock).onWakeLockReleased(anyInt(), eq(tag), eq(packageName), anyInt(),
+                anyInt(), any(), any(), same(callback));
+    }
+
+    /**
+     * Test IPowerManager.updateWakeLockCallback() with a new IWakeLockCallback.
+     */
+    @Test
+    public void testNotifyWakeLockCallbackChange() {
+        createService();
+        startSystem();
+        final String tag = "wakelock1";
+        final String packageName = "pkg.name";
+        final IBinder token = new Binder();
+        int flags = PowerManager.PARTIAL_WAKE_LOCK;
+        final IWakeLockCallback callback1 = Mockito.mock(IWakeLockCallback.class);
+        final IBinder callbackBinder1 = Mockito.mock(Binder.class);
+        when(callback1.asBinder()).thenReturn(callbackBinder1);
+        mService.getBinderServiceInstance().acquireWakeLock(token, flags, tag, packageName,
+                null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY, callback1);
+        verify(mNotifierMock).onWakeLockAcquired(anyInt(), eq(tag), eq(packageName), anyInt(),
+                anyInt(), any(), any(), same(callback1));
+
+        final IWakeLockCallback callback2 = Mockito.mock(IWakeLockCallback.class);
+        final IBinder callbackBinder2 = Mockito.mock(Binder.class);
+        when(callback2.asBinder()).thenReturn(callbackBinder2);
+        mService.getBinderServiceInstance().updateWakeLockCallback(token, callback2);
+        verify(mNotifierMock).onWakeLockChanging(anyInt(), eq(tag), eq(packageName),
+                anyInt(), anyInt(), any(), any(), same(callback1),
+                anyInt(), eq(tag), eq(packageName), anyInt(), anyInt(), any(), any(),
+                same(callback2));
+
+        mService.getBinderServiceInstance().releaseWakeLock(token, 0);
+        verify(mNotifierMock).onWakeLockReleased(anyInt(), eq(tag), eq(packageName), anyInt(),
+                anyInt(), any(), any(), same(callback2));
+    }
+
+    @Test
+    public void testUserActivity_futureEventsAreIgnored() {
+        createService();
+        startSystem();
+        // Starting the system triggers a user activity event, so clear that before calling
+        // userActivity() directly.
+        clearInvocations(mNotifierMock);
+        final long eventTime = mClock.now() + Duration.ofHours(10).toMillis();
+        mService.getBinderServiceInstance().userActivity(Display.DEFAULT_DISPLAY, eventTime,
+                USER_ACTIVITY_EVENT_BUTTON, /* flags= */ 0);
+        verify(mNotifierMock, never()).onUserActivity(anyInt(),  anyInt(), anyInt());
+    }
+
+}
diff --git a/services/tests/powerservicetests/src/com/android/server/power/ShutdownCheckPointsTest.java b/services/tests/powerservicetests/src/com/android/server/power/ShutdownCheckPointsTest.java
new file mode 100644
index 0000000..fe6cc28
--- /dev/null
+++ b/services/tests/powerservicetests/src/com/android/server/power/ShutdownCheckPointsTest.java
@@ -0,0 +1,414 @@
+/*
+ * Copyright (C) 2020 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 org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.when;
+
+import android.app.ActivityManager;
+import android.app.IActivityManager;
+import android.os.Process;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Locale;
+import java.util.TimeZone;
+
+/**
+ * Run: atest FrameworksServicesTests:ShutdownCheckPointsTest
+ */
+@Presubmit
+public class ShutdownCheckPointsTest {
+
+    @Rule
+    public MockitoRule rule = MockitoJUnit.rule();
+
+    @Mock
+    private IActivityManager mActivityManager;
+
+    private TestInjector mTestInjector;
+    private ShutdownCheckPoints mInstance;
+
+    @Before
+    public void setUp() {
+        Locale.setDefault(Locale.UK);
+        TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
+        mTestInjector = new TestInjector(mActivityManager);
+        mInstance = new ShutdownCheckPoints(mTestInjector);
+    }
+
+    @Test
+    public void testSystemServerEntry() {
+        mTestInjector.setCurrentTime(1000);
+        mInstance.recordCheckPointInternal("reason1");
+
+        assertTrue(dumpToString().startsWith(
+                "Shutdown request from SYSTEM for reason reason1 "
+                        + "at 1970-01-01 00:00:01.000 UTC (epoch=1000)\n"
+                        + "com.android.server.power.ShutdownCheckPointsTest"
+                        + ".testSystemServerEntry\n at "));
+    }
+
+    @Test
+    public void testSystemServerEntryWithoutReason() {
+        mTestInjector.setCurrentTime(1000);
+        mInstance.recordCheckPointInternal(null);
+
+        assertTrue(dumpToString().startsWith(
+                "Shutdown request from SYSTEM at 1970-01-01 00:00:01.000 UTC (epoch=1000)\n"));
+    }
+
+    @Test
+    public void testSystemServiceBinderEntry() {
+        mTestInjector.setCurrentTime(1000);
+        mInstance.recordCheckPointInternal(Process.myPid(), "reason1");
+
+        assertTrue(dumpToString().startsWith(
+                "Shutdown request from SYSTEM for reason reason1 "
+                        + "at 1970-01-01 00:00:01.000 UTC (epoch=1000)\n"
+                        + "com.android.server.power.ShutdownCheckPointsTest"
+                        + ".testSystemServiceBinderEntry\n at "));
+    }
+
+    @Test
+    public void testCallerProcessBinderEntries() throws RemoteException {
+        List<ActivityManager.RunningAppProcessInfo> runningAppProcessInfos = new ArrayList<>();
+        runningAppProcessInfos.add(
+                new ActivityManager.RunningAppProcessInfo("process_name", 1, new String[0]));
+        when(mActivityManager.getRunningAppProcesses()).thenReturn(runningAppProcessInfos);
+
+        mTestInjector.setCurrentTime(1000);
+        // Matching pid in getRunningAppProcesses
+        mInstance.recordCheckPointInternal(1, "reason1");
+        // Missing pid in getRunningAppProcesses
+        mInstance.recordCheckPointInternal(2, "reason2");
+
+        assertEquals(
+                "Shutdown request from BINDER for reason reason1 "
+                        + "at 1970-01-01 00:00:01.000 UTC (epoch=1000)\n"
+                        + "com.android.server.power.ShutdownCheckPointsTest"
+                        + ".testCallerProcessBinderEntries\n"
+                        + "From process process_name (pid=1)\n\n"
+                        + "Shutdown request from BINDER for reason reason2 "
+                        + "at 1970-01-01 00:00:01.000 UTC (epoch=1000)\n"
+                        + "com.android.server.power.ShutdownCheckPointsTest"
+                        + ".testCallerProcessBinderEntries\n"
+                        + "From process ? (pid=2)\n\n",
+                dumpToString());
+    }
+
+    @Test
+    public void testNullCallerProcessBinderEntries() throws RemoteException {
+        when(mActivityManager.getRunningAppProcesses()).thenReturn(null);
+
+        mTestInjector.setCurrentTime(1000);
+        mInstance.recordCheckPointInternal(1, "reason1");
+
+        assertEquals(
+                "Shutdown request from BINDER for reason reason1 "
+                        + "at 1970-01-01 00:00:01.000 UTC (epoch=1000)\n"
+                        + "com.android.server.power.ShutdownCheckPointsTest"
+                        + ".testNullCallerProcessBinderEntries\n"
+                        + "From process ? (pid=1)\n\n",
+                dumpToString());
+    }
+
+    @Test
+    public void testRemoteExceptionOnBinderEntry() throws RemoteException {
+        when(mActivityManager.getRunningAppProcesses()).thenThrow(new RemoteException("Error"));
+
+        mTestInjector.setCurrentTime(1000);
+        mInstance.recordCheckPointInternal(1, "reason1");
+
+        assertEquals(
+                "Shutdown request from BINDER for reason reason1 "
+                        + "at 1970-01-01 00:00:01.000 UTC (epoch=1000)\n"
+                        + "com.android.server.power.ShutdownCheckPointsTest"
+                        + ".testRemoteExceptionOnBinderEntry\n"
+                        + "From process ? (pid=1)\n\n",
+                dumpToString());
+    }
+
+    @Test
+    public void testUnknownProcessBinderEntry() {
+        mTestInjector.setCurrentTime(1000);
+        mInstance.recordCheckPointInternal(1, "reason1");
+
+        assertEquals(
+                "Shutdown request from BINDER for reason reason1 "
+                        + "at 1970-01-01 00:00:01.000 UTC (epoch=1000)\n"
+                        + "com.android.server.power.ShutdownCheckPointsTest"
+                        + ".testUnknownProcessBinderEntry\n"
+                        + "From process ? (pid=1)\n\n",
+                dumpToString());
+    }
+
+    @Test
+    public void testBinderEntryWithoutReason() throws RemoteException {
+        mTestInjector.setCurrentTime(1000);
+        mInstance.recordCheckPointInternal(1, null);
+
+        assertTrue(dumpToString().startsWith(
+                "Shutdown request from BINDER at 1970-01-01 00:00:01.000 UTC (epoch=1000)\n"));
+    }
+
+    @Test
+    public void testSystemServiceIntentEntry() {
+        mTestInjector.setCurrentTime(1000);
+        mInstance.recordCheckPointInternal("some.intent", "android", "reason1");
+
+        assertTrue(dumpToString().startsWith(
+                "Shutdown request from SYSTEM for reason reason1 "
+                        + "at 1970-01-01 00:00:01.000 UTC (epoch=1000)\n"
+                        + "com.android.server.power.ShutdownCheckPointsTest"
+                        + ".testSystemServiceIntentEntry\n at "));
+    }
+
+    @Test
+    public void testIntentEntry() {
+        mTestInjector.setCurrentTime(1000);
+        mInstance.recordCheckPointInternal("some.intent", "some.app", "reason1");
+
+        assertEquals(
+                "Shutdown request from INTENT for reason reason1 "
+                        + "at 1970-01-01 00:00:01.000 UTC (epoch=1000)\n"
+                        + "Intent: some.intent\n"
+                        + "Package: some.app\n\n",
+                dumpToString());
+    }
+
+    @Test
+    public void testIntentEntryWithoutReason() {
+        mTestInjector.setCurrentTime(1000);
+        mInstance.recordCheckPointInternal("some.intent", "some.app", null);
+
+        assertTrue(dumpToString().startsWith(
+                "Shutdown request from INTENT at 1970-01-01 00:00:01.000 UTC (epoch=1000)\n"));
+    }
+
+    @Test
+    public void testMultipleEntries() {
+        mTestInjector.setCurrentTime(1000);
+        mInstance.recordCheckPointInternal(1, "reason1");
+        mTestInjector.setCurrentTime(2000);
+        mInstance.recordCheckPointInternal(2, "reason2");
+        mTestInjector.setCurrentTime(3000);
+        mInstance.recordCheckPointInternal("intent", "app", "reason3");
+
+        assertEquals(
+                "Shutdown request from BINDER for reason reason1 "
+                        + "at 1970-01-01 00:00:01.000 UTC (epoch=1000)\n"
+                        + "com.android.server.power.ShutdownCheckPointsTest.testMultipleEntries\n"
+                        + "From process ? (pid=1)\n\n"
+                        + "Shutdown request from BINDER for reason reason2 "
+                        + "at 1970-01-01 00:00:02.000 UTC (epoch=2000)\n"
+                        + "com.android.server.power.ShutdownCheckPointsTest.testMultipleEntries\n"
+                        + "From process ? (pid=2)\n\n"
+                        + "Shutdown request from INTENT for reason reason3 "
+                        + "at 1970-01-01 00:00:03.000 UTC (epoch=3000)\n"
+                        + "Intent: intent\n"
+                        + "Package: app\n\n",
+                dumpToString());
+    }
+
+    @Test
+    public void testTooManyEntriesDropsOlderOnes() {
+        mTestInjector.setCheckPointsLimit(2);
+        ShutdownCheckPoints limitedInstance = new ShutdownCheckPoints(mTestInjector);
+
+        mTestInjector.setCurrentTime(1000);
+        limitedInstance.recordCheckPointInternal("intent.1", "app.1", "reason1");
+        mTestInjector.setCurrentTime(2000);
+        limitedInstance.recordCheckPointInternal("intent.2", "app.2", "reason2");
+        mTestInjector.setCurrentTime(3000);
+        limitedInstance.recordCheckPointInternal("intent.3", "app.3", "reason3");
+
+        // Drops first intent.
+        assertEquals(
+                "Shutdown request from INTENT for reason reason2 "
+                        + "at 1970-01-01 00:00:02.000 UTC (epoch=2000)\n"
+                        + "Intent: intent.2\n"
+                        + "Package: app.2\n\n"
+                        + "Shutdown request from INTENT for reason reason3 "
+                        + "at 1970-01-01 00:00:03.000 UTC (epoch=3000)\n"
+                        + "Intent: intent.3\n"
+                        + "Package: app.3\n\n",
+                dumpToString(limitedInstance));
+    }
+
+    @Test
+    public void testDumpToFile() throws Exception {
+        File tempDir = createTempDir();
+        File baseFile = new File(tempDir, "checkpoints");
+
+        mTestInjector.setCurrentTime(1000);
+        mInstance.recordCheckPointInternal("first.intent", "first.app", "reason1");
+        dumpToFile(baseFile);
+
+        mTestInjector.setCurrentTime(2000);
+        mInstance.recordCheckPointInternal("second.intent", "second.app", "reason2");
+        dumpToFile(baseFile);
+
+        File[] dumpFiles = tempDir.listFiles();
+        Arrays.sort(dumpFiles);
+
+        assertEquals(2, dumpFiles.length);
+        assertEquals(
+                "Shutdown request from INTENT for reason reason1 "
+                        + "at 1970-01-01 00:00:01.000 UTC (epoch=1000)\n"
+                        + "Intent: first.intent\n"
+                        + "Package: first.app\n\n",
+                readFileAsString(dumpFiles[0].getAbsolutePath()));
+        assertEquals(
+                "Shutdown request from INTENT for reason reason1 "
+                        + "at 1970-01-01 00:00:01.000 UTC (epoch=1000)\n"
+                        + "Intent: first.intent\n"
+                        + "Package: first.app\n\n"
+                        + "Shutdown request from INTENT for reason reason2 "
+                        + "at 1970-01-01 00:00:02.000 UTC (epoch=2000)\n"
+                        + "Intent: second.intent\n"
+                        + "Package: second.app\n\n",
+                readFileAsString(dumpFiles[1].getAbsolutePath()));
+    }
+
+    @Test
+    public void testTooManyFilesDropsOlderOnes() throws Exception {
+        mTestInjector.setDumpFilesLimit(1);
+        ShutdownCheckPoints instance = new ShutdownCheckPoints(mTestInjector);
+        File tempDir = createTempDir();
+        File baseFile = new File(tempDir, "checkpoints");
+
+        mTestInjector.setCurrentTime(1000);
+        instance.recordCheckPointInternal("first.intent", "first.app", "reason1");
+        dumpToFile(instance, baseFile);
+
+        mTestInjector.setCurrentTime(2000);
+        instance.recordCheckPointInternal("second.intent", "second.app", "reason2");
+        dumpToFile(instance, baseFile);
+
+        File[] dumpFiles = tempDir.listFiles();
+        assertEquals(1, dumpFiles.length);
+        assertEquals(
+                "Shutdown request from INTENT for reason reason1 "
+                        + "at 1970-01-01 00:00:01.000 UTC (epoch=1000)\n"
+                        + "Intent: first.intent\n"
+                        + "Package: first.app\n\n"
+                        + "Shutdown request from INTENT for reason reason2 "
+                        + "at 1970-01-01 00:00:02.000 UTC (epoch=2000)\n"
+                        + "Intent: second.intent\n"
+                        + "Package: second.app\n\n",
+                readFileAsString(dumpFiles[0].getAbsolutePath()));
+    }
+
+    private String dumpToString() {
+        return dumpToString(mInstance);
+    }
+
+    private String dumpToString(ShutdownCheckPoints instance) {
+        StringWriter sw = new StringWriter();
+        PrintWriter pw = new PrintWriter(sw);
+        instance.dumpInternal(pw);
+        return sw.toString();
+    }
+
+    private void dumpToFile(File baseFile) throws InterruptedException {
+        dumpToFile(mInstance, baseFile);
+    }
+
+    private void dumpToFile(ShutdownCheckPoints instance, File baseFile)
+            throws InterruptedException {
+        Thread dumpThread = instance.newDumpThreadInternal(baseFile);
+        dumpThread.start();
+        dumpThread.join();
+    }
+
+    private String readFileAsString(String absolutePath) throws IOException {
+        return new String(Files.readAllBytes(Paths.get(absolutePath)), StandardCharsets.UTF_8);
+    }
+
+    private File createTempDir() throws IOException {
+        File tempDir = File.createTempFile("checkpoints", "out");
+        tempDir.delete();
+        tempDir.mkdir();
+        return tempDir;
+    }
+
+    /** Fake system dependencies for testing. */
+    private final class TestInjector implements ShutdownCheckPoints.Injector {
+        private long mNow;
+        private int mCheckPointsLimit;
+        private int mDumpFilesLimit;
+        private IActivityManager mActivityManager;
+
+        TestInjector(IActivityManager activityManager) {
+            mNow = 0;
+            mCheckPointsLimit = 100;
+            mDumpFilesLimit = 2;
+            mActivityManager = activityManager;
+        }
+
+        @Override
+        public long currentTimeMillis() {
+            return mNow;
+        }
+
+        @Override
+        public int maxCheckPoints() {
+            return mCheckPointsLimit;
+        }
+
+        @Override
+        public int maxDumpFiles() {
+            return mDumpFilesLimit;
+        }
+
+        @Override
+        public IActivityManager activityManager() {
+            return mActivityManager;
+        }
+
+        void setCurrentTime(long time) {
+            mNow = time;
+        }
+
+        void setCheckPointsLimit(int limit) {
+            mCheckPointsLimit = limit;
+        }
+
+        void setDumpFilesLimit(int dumpFilesLimit) {
+            mDumpFilesLimit = dumpFilesLimit;
+        }
+    }
+}
diff --git a/services/tests/powerservicetests/src/com/android/server/power/ShutdownThreadTest.java b/services/tests/powerservicetests/src/com/android/server/power/ShutdownThreadTest.java
new file mode 100644
index 0000000..6041e91
--- /dev/null
+++ b/services/tests/powerservicetests/src/com/android/server/power/ShutdownThreadTest.java
@@ -0,0 +1,215 @@
+/*
+ * Copyright (C) 2020 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.android.server.power.ShutdownThread.DEFAULT_SHUTDOWN_VIBRATE_MS;
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.os.VibrationAttributes;
+import android.os.VibrationEffect;
+import android.os.Vibrator;
+import android.util.AtomicFile;
+
+import androidx.test.InstrumentationRegistry;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+/**
+ * Tests for {@link com.android.server.power.ShutdownThread}
+ */
+public class ShutdownThreadTest {
+
+    private static final String WAVEFORM_VIB_10MS_SERIALIZATION =
+            """
+            <vibration>
+                <waveform-effect>
+                    <waveform-entry durationMs="10" amplitude="100"/>
+                </waveform-effect>
+            </vibration>
+            """;
+
+    private static final VibrationEffect WAVEFORM_VIB_10MS = VibrationEffect.createOneShot(10, 100);
+
+    private static final String REPEATING_VIB_SERIALIZATION =
+            """
+            <vibration>
+                <waveform-effect>
+                    <repeating>
+                        <waveform-entry durationMs="10" amplitude="100"/>
+                    </repeating>
+                </waveform-effect>
+            </vibration>
+            """;
+
+    private static final String CLICK_VIB_SERIALIZATION =
+            """
+            <vibration>
+                <predefined-effect name="click"/>
+            </vibration>
+            """;
+
+    private static final VibrationEffect CLILCK_VIB =
+            VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK);
+
+    private static final String BAD_VIB_SERIALIZATION = "BAD SERIALIZATION";
+
+    @Mock private Context mContextMock;
+    @Mock private Vibrator mVibratorMock;
+
+    private String mDefaultShutdownVibrationFilePath;
+    private long mLastSleepDurationMs;
+
+    private ShutdownThread mShutdownThread;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        when(mVibratorMock.hasVibrator()).thenReturn(true);
+
+        when(mVibratorMock.areVibrationFeaturesSupported(any())).thenReturn(true);
+
+        mShutdownThread = new ShutdownThread(new TestInjector());
+    }
+
+    @Test
+    public void testSuccessfulShutdownVibrationFromFile() throws Exception {
+        setShutdownVibrationFileContent(WAVEFORM_VIB_10MS_SERIALIZATION);
+
+        mShutdownThread.playShutdownVibration(mContextMock);
+
+        assertShutdownVibration(WAVEFORM_VIB_10MS, /* vibrationSleepDuration= */ 10);
+    }
+
+    @Test
+    public void testIOExceptionWhenParsingShutdownVibration() throws Exception {
+        mDefaultShutdownVibrationFilePath = "non/existent/file_path";
+
+        mShutdownThread.playShutdownVibration(mContextMock);
+
+        assertDefaultShutdownVibration();
+    }
+
+    @Test
+    public void testMalformedShutdownVibrationFileContent() throws Exception {
+        setShutdownVibrationFileContent(BAD_VIB_SERIALIZATION);
+
+        mShutdownThread.playShutdownVibration(mContextMock);
+
+        assertDefaultShutdownVibration();
+    }
+
+    @Test
+    public void testVibratorUnsupportedShutdownVibrationEffect() throws Exception {
+        setShutdownVibrationFileContent(WAVEFORM_VIB_10MS_SERIALIZATION);
+        when(mVibratorMock.areVibrationFeaturesSupported(any())).thenReturn(false);
+
+        mShutdownThread.playShutdownVibration(mContextMock);
+
+        assertDefaultShutdownVibration();
+    }
+
+    @Test
+    public void testRepeatinghutdownVibrationEffect() throws Exception {
+        setShutdownVibrationFileContent(REPEATING_VIB_SERIALIZATION);
+
+        mShutdownThread.playShutdownVibration(mContextMock);
+
+        assertDefaultShutdownVibration();
+    }
+
+    @Test
+    public void testVibrationEffectWithUnknownDuration() throws Exception {
+        setShutdownVibrationFileContent(CLICK_VIB_SERIALIZATION);
+
+        mShutdownThread.playShutdownVibration(mContextMock);
+
+        assertShutdownVibration(CLILCK_VIB, DEFAULT_SHUTDOWN_VIBRATE_MS);
+    }
+
+    @Test
+    public void testNoVibrator() {
+        when(mVibratorMock.hasVibrator()).thenReturn(false);
+
+        mShutdownThread.playShutdownVibration(mContextMock);
+
+        verify(mVibratorMock, never())
+                .vibrate(any(VibrationEffect.class), any(VibrationAttributes.class));
+    }
+
+    private void assertShutdownVibration(VibrationEffect effect, long vibrationSleepDuration)
+            throws Exception {
+        verify(mVibratorMock).vibrate(
+                eq(effect),
+                eq(VibrationAttributes.createForUsage(VibrationAttributes.USAGE_TOUCH)));
+        assertEquals(vibrationSleepDuration, mLastSleepDurationMs);
+    }
+
+    private void assertDefaultShutdownVibration() throws Exception {
+        assertShutdownVibration(
+                VibrationEffect.createOneShot(
+                        DEFAULT_SHUTDOWN_VIBRATE_MS, VibrationEffect.DEFAULT_AMPLITUDE),
+                DEFAULT_SHUTDOWN_VIBRATE_MS);
+    }
+
+    private void setShutdownVibrationFileContent(String content) throws Exception {
+        mDefaultShutdownVibrationFilePath = createFileForContent(content).getAbsolutePath();
+    }
+
+    private static File createFileForContent(String content) throws Exception {
+        File file = new File(InstrumentationRegistry.getContext().getCacheDir(), "test.xml");
+        file.createNewFile();
+
+        AtomicFile atomicFile = new AtomicFile(file);
+        FileOutputStream fos = atomicFile.startWrite();
+        fos.write(content.getBytes());
+        atomicFile.finishWrite(fos);
+
+        return file;
+    }
+
+    private class TestInjector extends ShutdownThread.Injector {
+        @Override
+        public Vibrator getVibrator(Context context) {
+            return mVibratorMock;
+        }
+
+        @Override
+        public String getDefaultShutdownVibrationEffectFilePath(Context context) {
+            return mDefaultShutdownVibrationFilePath;
+        }
+
+        @Override
+        public void sleep(long durationMs) {
+            mLastSleepDurationMs = durationMs;
+        }
+    }
+}
diff --git a/services/tests/powerservicetests/src/com/android/server/power/WakeLockLogTest.java b/services/tests/powerservicetests/src/com/android/server/power/WakeLockLogTest.java
new file mode 100644
index 0000000..7af4b3d
--- /dev/null
+++ b/services/tests/powerservicetests/src/com/android/server/power/WakeLockLogTest.java
@@ -0,0 +1,453 @@
+/*
+ * Copyright (C) 2019 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 org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.PowerManager;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.text.SimpleDateFormat;
+import java.util.TimeZone;
+
+/**
+ * Tests for {@link WakeLockLog}.
+ */
+public class WakeLockLogTest {
+
+    @Mock
+    private Context mContext;
+
+    @Mock
+    private PackageManager mPackageManager;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        when(mContext.getPackageManager()).thenReturn(mPackageManager);
+
+        when(mPackageManager.getPackagesForUid(101)).thenReturn(new String[]{ "some.package1" });
+        when(mPackageManager.getPackagesForUid(102)).thenReturn(new String[]{ "some.package2" });
+    }
+
+    @Test
+    public void testAddTwoItems() {
+        final int tagDatabaseSize = 128;
+        final int logSize = 20;
+        TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, logSize));
+        WakeLockLog log = new WakeLockLog(injectorSpy, mContext);
+
+        when(injectorSpy.currentTimeMillis()).thenReturn(1000L);
+        log.onWakeLockAcquired("TagPartial", 101,
+                PowerManager.PARTIAL_WAKE_LOCK | PowerManager.ON_AFTER_RELEASE);
+
+        when(injectorSpy.currentTimeMillis()).thenReturn(1150L);
+        log.onWakeLockAcquired("TagFull", 102,
+                PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP);
+
+        assertEquals("Wake Lock Log\n"
+                + "  01-01 00:00:01.000 - 101 (some.package1) - ACQ TagPartial "
+                        + "(partial,on-after-release)\n"
+                + "  01-01 00:00:01.150 - 102 (some.package2) - ACQ TagFull "
+                        + "(full,acq-causes-wake)\n"
+                + "  -\n"
+                + "  Events: 2, Time-Resets: 0\n"
+                + "  Buffer, Bytes used: 6\n",
+                dumpLog(log, false));
+    }
+
+    @Test
+    public void testAddTwoItemsWithTimeReset() {
+        final int tagDatabaseSize = 128;
+        final int logSize = 20;
+        TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, logSize));
+        WakeLockLog log = new WakeLockLog(injectorSpy, mContext);
+
+        when(injectorSpy.currentTimeMillis()).thenReturn(1000L);
+        log.onWakeLockAcquired("TagPartial", 101, PowerManager.PARTIAL_WAKE_LOCK);
+
+        when(injectorSpy.currentTimeMillis()).thenReturn(1350L);
+        log.onWakeLockAcquired("TagFull", 102, PowerManager.FULL_WAKE_LOCK);
+
+        assertEquals("Wake Lock Log\n"
+                + "  01-01 00:00:01.000 - 101 (some.package1) - ACQ TagPartial (partial)\n"
+                + "  01-01 00:00:01.350 - 102 (some.package2) - ACQ TagFull (full)\n"
+                + "  -\n"
+                + "  Events: 2, Time-Resets: 1\n"
+                + "  Buffer, Bytes used: 15\n",
+                dumpLog(log, false));
+    }
+
+    @Test
+    public void testAddTwoItemsWithTagOverwrite() {
+        final int tagDatabaseSize = 2;
+        final int logSize = 20;
+        TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, logSize));
+        WakeLockLog log = new WakeLockLog(injectorSpy, mContext);
+
+        when(injectorSpy.currentTimeMillis()).thenReturn(1000L);
+        log.onWakeLockAcquired("TagPartial", 101, PowerManager.PARTIAL_WAKE_LOCK);
+
+        when(injectorSpy.currentTimeMillis()).thenReturn(1150L);
+        log.onWakeLockAcquired("TagFull", 102, PowerManager.FULL_WAKE_LOCK);
+
+        assertEquals("Wake Lock Log\n"
+                + "  01-01 00:00:01.000 - --- - ACQ UNKNOWN (partial)\n"
+                + "  01-01 00:00:01.150 - 102 (some.package2) - ACQ TagFull (full)\n"
+                + "  -\n"
+                + "  Events: 2, Time-Resets: 0\n"
+                + "  Buffer, Bytes used: 6\n",
+                dumpLog(log, false));
+    }
+
+    @Test
+    public void testAddFourItemsWithRingBufferOverflow() {
+        final int tagDatabaseSize = 6;
+        final int logSize = 10;
+        TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, logSize));
+        WakeLockLog log = new WakeLockLog(injectorSpy, mContext);
+
+        // Wake lock 1 acquired - log size = 3
+        when(injectorSpy.currentTimeMillis()).thenReturn(1000L);
+        log.onWakeLockAcquired("TagPartial", 101, PowerManager.PARTIAL_WAKE_LOCK);
+
+        // Wake lock 2 acquired - log size = 3 + 3 = 6
+        when(injectorSpy.currentTimeMillis()).thenReturn(1150L);
+        log.onWakeLockAcquired("TagFull", 102, PowerManager.FULL_WAKE_LOCK);
+
+        // Wake lock 3 acquired - log size = 6 + 3 = 9
+        when(injectorSpy.currentTimeMillis()).thenReturn(1151L);
+        log.onWakeLockAcquired("TagThree", 101, PowerManager.PARTIAL_WAKE_LOCK);
+
+        // We need more space - wake lock 1 acquisition is removed from the log and saved in the
+        // list. Log size = 9 - 3 + 2 = 8
+        when(injectorSpy.currentTimeMillis()).thenReturn(1152L);
+        log.onWakeLockReleased("TagThree", 101);
+
+        // We need more space - wake lock 2 acquisition is removed from the log and saved in the
+        // list. Log size = 8 - 3 + 2 = 7
+        when(injectorSpy.currentTimeMillis()).thenReturn(1153L);
+        log.onWakeLockReleased("TagPartial", 101);
+
+        // We need more space - wake lock 3 acquisition is removed from the log and saved in the
+        // list. Log size = 7 - 3 + 3 = 7
+        when(injectorSpy.currentTimeMillis()).thenReturn(1154L);
+        log.onWakeLockAcquired("TagFour", 101, PowerManager.PARTIAL_WAKE_LOCK);
+
+        // We need more space - wake lock 3 release is removed from the log and wake lock 3
+        // acquisition is removed from the list. Log size = 7 - 2 + 3 = 8
+        when(injectorSpy.currentTimeMillis()).thenReturn(1155L);
+        log.onWakeLockAcquired("TagFive", 101, PowerManager.PARTIAL_WAKE_LOCK);
+
+        // We need more space - wake lock 1 release is removed from the log and wake lock 1
+        // acquisition is removed from the list. Log size = 8 - 2 + 2 = 8
+        when(injectorSpy.currentTimeMillis()).thenReturn(1156L);
+        log.onWakeLockReleased("TagFull", 102);
+
+        // Wake lock 2 acquisition is still printed because its release have not rolled off the log
+        // yet.
+        assertEquals("Wake Lock Log\n"
+                + "  01-01 00:00:01.150 - 102 (some.package2) - ACQ TagFull (full)\n"
+                + "  01-01 00:00:01.154 - 101 (some.package1) - ACQ TagFour (partial)\n"
+                + "  01-01 00:00:01.155 - 101 (some.package1) - ACQ TagFive (partial)\n"
+                + "  01-01 00:00:01.156 - 102 (some.package2) - REL TagFull\n"
+                + "  -\n"
+                + "  Events: 4, Time-Resets: 0\n"
+                + "  Buffer, Bytes used: 8\n",
+                dumpLog(log, false));
+    }
+
+    @Test
+    public void testAddItemWithBadTag() {
+        final int tagDatabaseSize = 6;
+        final int logSize = 10;
+        TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, logSize));
+        WakeLockLog log = new WakeLockLog(injectorSpy, mContext);
+
+        // Bad tag means it wont get written
+        when(injectorSpy.currentTimeMillis()).thenReturn(1000L);
+        log.onWakeLockAcquired(null /* tag */, 0 /* ownerUid */, PowerManager.PARTIAL_WAKE_LOCK);
+
+        assertEquals("Wake Lock Log\n"
+                + "  -\n"
+                + "  Events: 0, Time-Resets: 0\n"
+                + "  Buffer, Bytes used: 0\n",
+                dumpLog(log, false));
+    }
+
+    @Test
+    public void testAddItemWithReducedTagName() {
+        final int tagDatabaseSize = 6;
+        final int logSize = 10;
+        TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, logSize));
+        WakeLockLog log = new WakeLockLog(injectorSpy, mContext);
+
+        when(injectorSpy.currentTimeMillis()).thenReturn(1000L);
+        log.onWakeLockAcquired("*job*/com.one.two.3hree/.one..Last", 101,
+                PowerManager.PARTIAL_WAKE_LOCK);
+
+        assertEquals("Wake Lock Log\n"
+                + "  01-01 00:00:01.000 - 101 (some.package1) - ACQ "
+                        + "*job*/c.o.t.3/.o..Last (partial)\n"
+                + "  -\n"
+                + "  Events: 1, Time-Resets: 0\n"
+                + "  Buffer, Bytes used: 3\n",
+                dumpLog(log, false));
+    }
+
+    @Test
+    public void testAddAcquireAndReleaseWithRepeatTagName() {
+        final int tagDatabaseSize = 6;
+        final int logSize = 10;
+        TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, logSize));
+        WakeLockLog log = new WakeLockLog(injectorSpy, mContext);
+
+        when(injectorSpy.currentTimeMillis()).thenReturn(1000L);
+        log.onWakeLockAcquired("HowdyTag", 101, PowerManager.PARTIAL_WAKE_LOCK);
+        when(injectorSpy.currentTimeMillis()).thenReturn(1001L);
+        log.onWakeLockReleased("HowdyTag", 101);
+
+        assertEquals("Wake Lock Log\n"
+                + "  01-01 00:00:01.000 - 101 (some.package1) - ACQ HowdyTag (partial)\n"
+                + "  01-01 00:00:01.001 - 101 (some.package1) - REL HowdyTag\n"
+                + "  -\n"
+                + "  Events: 2, Time-Resets: 0\n"
+                + "  Buffer, Bytes used: 5\n"
+                + "  Tag Database: size(5), entries: 1, Bytes used: 80\n",
+                dumpLog(log, true));
+    }
+
+    @Test
+    public void testAddAcquireAndReleaseWithTimeTravel() {
+        final int tagDatabaseSize = 6;
+        final int logSize = 10;
+        TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, logSize));
+        WakeLockLog log = new WakeLockLog(injectorSpy, mContext);
+
+        when(injectorSpy.currentTimeMillis()).thenReturn(1100L);
+        log.onWakeLockAcquired("HowdyTag", 101, PowerManager.PARTIAL_WAKE_LOCK);
+
+        // New element goes back in time...should not be written to log.
+        when(injectorSpy.currentTimeMillis()).thenReturn(1000L);
+        log.onWakeLockReleased("HowdyTag", 101);
+
+        assertEquals("Wake Lock Log\n"
+                + "  01-01 00:00:01.100 - 101 (some.package1) - ACQ HowdyTag (partial)\n"
+                + "  -\n"
+                + "  Events: 1, Time-Resets: 0\n"
+                + "  Buffer, Bytes used: 3\n",
+                dumpLog(log, false));
+    }
+
+    @Test
+    public void testAddSystemWakelock() {
+        final int tagDatabaseSize = 6;
+        final int logSize = 10;
+        TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, logSize));
+        WakeLockLog log = new WakeLockLog(injectorSpy, mContext);
+
+        when(injectorSpy.currentTimeMillis()).thenReturn(1000L);
+        log.onWakeLockAcquired("TagPartial", 101,
+                PowerManager.PARTIAL_WAKE_LOCK | PowerManager.SYSTEM_WAKELOCK);
+
+        assertEquals("Wake Lock Log\n"
+                        + "  01-01 00:00:01.000 - 101 (some.package1) - ACQ TagPartial "
+                                + "(partial,system-wakelock)\n"
+                        + "  -\n"
+                        + "  Events: 1, Time-Resets: 0\n"
+                        + "  Buffer, Bytes used: 3\n",
+                dumpLog(log, false));
+    }
+
+    @Test
+    public void testAddItemWithNoPackageName() {
+        final int tagDatabaseSize = 128;
+        final int logSize = 20;
+        TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, logSize));
+        WakeLockLog log = new WakeLockLog(injectorSpy, mContext);
+
+        when(mPackageManager.getPackagesForUid(101)).thenReturn(null);
+        when(injectorSpy.currentTimeMillis()).thenReturn(1000L);
+        log.onWakeLockAcquired("TagPartial", 101,
+                PowerManager.PARTIAL_WAKE_LOCK | PowerManager.ON_AFTER_RELEASE);
+
+        assertEquals("Wake Lock Log\n"
+                        + "  01-01 00:00:01.000 - 101 - ACQ TagPartial "
+                                + "(partial,on-after-release)\n"
+                        + "  -\n"
+                        + "  Events: 1, Time-Resets: 0\n"
+                        + "  Buffer, Bytes used: 3\n",
+                dumpLog(log, false));
+    }
+
+    @Test
+    public void testAddItemWithMultiplePackageNames() {
+        final int tagDatabaseSize = 128;
+        final int logSize = 20;
+        TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, logSize));
+        WakeLockLog log = new WakeLockLog(injectorSpy, mContext);
+
+        when(mPackageManager.getPackagesForUid(101)).thenReturn(
+                new String[]{ "some.package1", "some.package2", "some.package3" });
+
+        when(injectorSpy.currentTimeMillis()).thenReturn(1000L);
+        log.onWakeLockAcquired("TagPartial", 101,
+                PowerManager.PARTIAL_WAKE_LOCK | PowerManager.ON_AFTER_RELEASE);
+
+        assertEquals("Wake Lock Log\n"
+                        + "  01-01 00:00:01.000 - 101 (some.package1,...) - ACQ TagPartial "
+                                + "(partial,on-after-release)\n"
+                        + "  -\n"
+                        + "  Events: 1, Time-Resets: 0\n"
+                        + "  Buffer, Bytes used: 3\n",
+                dumpLog(log, false));
+    }
+
+    @Test
+    public void testAddItemsWithRepeatOwnerUid_UsesCache() {
+        final int tagDatabaseSize = 128;
+        final int logSize = 20;
+        TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, logSize));
+        WakeLockLog log = new WakeLockLog(injectorSpy, mContext);
+
+        when(injectorSpy.currentTimeMillis()).thenReturn(1000L);
+        log.onWakeLockAcquired("TagPartial", 101,
+                PowerManager.PARTIAL_WAKE_LOCK | PowerManager.ON_AFTER_RELEASE);
+
+        when(injectorSpy.currentTimeMillis()).thenReturn(1150L);
+        log.onWakeLockAcquired("TagFull", 101,
+                PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP);
+
+        when(injectorSpy.currentTimeMillis()).thenReturn(1151L);
+        log.onWakeLockAcquired("TagFull2", 101,
+                PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP);
+
+        assertEquals("Wake Lock Log\n"
+                        + "  01-01 00:00:01.000 - 101 (some.package1) - ACQ TagPartial "
+                        + "(partial,on-after-release)\n"
+                        + "  01-01 00:00:01.150 - 101 (some.package1) - ACQ TagFull "
+                        + "(full,acq-causes-wake)\n"
+                        + "  01-01 00:00:01.151 - 101 (some.package1) - ACQ TagFull2 "
+                        + "(full,acq-causes-wake)\n"
+                        + "  -\n"
+                        + "  Events: 3, Time-Resets: 0\n"
+                        + "  Buffer, Bytes used: 9\n",
+                dumpLog(log, false));
+
+        verify(mPackageManager, times(1)).getPackagesForUid(101);
+    }
+
+    @Test
+    public void testAddItemsWithRepeatOwnerUid_SavedAcquisitions_UsesCache() {
+        final int tagDatabaseSize = 128;
+        final int logSize = 10;
+        TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, logSize));
+        WakeLockLog log = new WakeLockLog(injectorSpy, mContext);
+
+        when(injectorSpy.currentTimeMillis()).thenReturn(1000L);
+        log.onWakeLockAcquired("TagPartial", 101,
+                PowerManager.PARTIAL_WAKE_LOCK | PowerManager.ON_AFTER_RELEASE);
+
+        when(injectorSpy.currentTimeMillis()).thenReturn(1150L);
+        log.onWakeLockAcquired("TagFull", 101,
+                PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP);
+
+        when(injectorSpy.currentTimeMillis()).thenReturn(1151L);
+        log.onWakeLockAcquired("TagFull2", 101,
+                PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP);
+
+        when(injectorSpy.currentTimeMillis()).thenReturn(1152L);
+        log.onWakeLockAcquired("TagFull3", 101,
+                PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP);
+
+        when(injectorSpy.currentTimeMillis()).thenReturn(1153L);
+        log.onWakeLockAcquired("TagFull4", 101,
+                PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP);
+
+        when(injectorSpy.currentTimeMillis()).thenReturn(1154L);
+        log.onWakeLockAcquired("TagFull5", 101,
+                PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP);
+
+        // The first 3 events have been removed from the log and they exist in the saved
+        // acquisitions list. They should also use the cache when fetching the package names.
+        assertEquals("Wake Lock Log\n"
+                        + "  01-01 00:00:01.000 - 101 (some.package1) - ACQ TagPartial "
+                        + "(partial,on-after-release)\n"
+                        + "  01-01 00:00:01.150 - 101 (some.package1) - ACQ TagFull "
+                        + "(full,acq-causes-wake)\n"
+                        + "  01-01 00:00:01.151 - 101 (some.package1) - ACQ TagFull2 "
+                        + "(full,acq-causes-wake)\n"
+                        + "  01-01 00:00:01.152 - 101 (some.package1) - ACQ TagFull3 "
+                        + "(full,acq-causes-wake)\n"
+                        + "  01-01 00:00:01.153 - 101 (some.package1) - ACQ TagFull4 "
+                        + "(full,acq-causes-wake)\n"
+                        + "  01-01 00:00:01.154 - 101 (some.package1) - ACQ TagFull5 "
+                        + "(full,acq-causes-wake)\n"
+                        + "  -\n"
+                        + "  Events: 6, Time-Resets: 0\n"
+                        + "  Buffer, Bytes used: 9\n",
+                dumpLog(log, false));
+
+        verify(mPackageManager, times(1)).getPackagesForUid(101);
+    }
+
+    private String dumpLog(WakeLockLog log, boolean includeTagDb) {
+        StringWriter sw = new StringWriter();
+        PrintWriter pw = new PrintWriter(sw);
+        log.dump(pw, includeTagDb);
+        return sw.toString();
+    }
+
+    public class TestInjector extends WakeLockLog.Injector {
+        private final int mTagDatabaseSize;
+        private final int mLogSize;
+
+        public TestInjector(int tagDatabaseSize, int logSize) {
+            mTagDatabaseSize = tagDatabaseSize;
+            mLogSize = logSize;
+        }
+
+        @Override
+        public int getTagDatabaseSize() {
+            return mTagDatabaseSize;
+        }
+
+        @Override
+        public int getLogSize() {
+            return mLogSize;
+        }
+
+        @Override
+        public SimpleDateFormat getDateFormat() {
+            SimpleDateFormat format = new SimpleDateFormat(super.getDateFormat().toPattern());
+            format.setTimeZone(TimeZone.getTimeZone("UTC"));
+            return format;
+        }
+    }
+}