Deterministic PowerManagerServiceTest

To make PowerManagerServiceTest deterministic,
remove dependency on real time clock by mocking it out.
Use TestLooper, which allows making Handler behaviour deterministic as
well.

Test: atest PowerManagerServiceTest
Bug: 152193749
Change-Id: I4d9cd686ac672261bbb038249c0358e451b58710
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index b1c40cc..5025835 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -241,7 +241,7 @@
 
     private final Context mContext;
     private final ServiceThread mHandlerThread;
-    private final PowerManagerHandler mHandler;
+    private final Handler mHandler;
     private final AmbientDisplayConfiguration mAmbientDisplayConfiguration;
     private final BatterySaverController mBatterySaverController;
     private final BatterySaverPolicy mBatterySaverPolicy;
@@ -252,6 +252,7 @@
     private final LocalService mLocalService;
     private final NativeWrapper mNativeWrapper;
     private final SystemPropertiesWrapper mSystemProperties;
+    private final Clock mClock;
     private final Injector mInjector;
 
     private LightsManager mLightsManager;
@@ -597,7 +598,7 @@
 
         @Override
         public void onForegroundProfileSwitch(@UserIdInt int newProfileId) throws RemoteException {
-            final long now = SystemClock.uptimeMillis();
+            final long now = mClock.uptimeMillis();
             synchronized (mLock) {
                 mForegroundProfile = newProfileId;
                 maybeUpdateForegroundProfileLastActivityLocked(now);
@@ -625,11 +626,11 @@
         // Whether profile has been locked last time it timed out.
         boolean mLockingNotified;
 
-        public ProfilePowerState(@UserIdInt int userId, long screenOffTimeout) {
+        public ProfilePowerState(@UserIdInt int userId, long screenOffTimeout, long now) {
             mUserId = userId;
             mScreenOffTimeout = screenOffTimeout;
             // Not accurate but at least won't cause immediate locking of the profile.
-            mLastUserActivityTime = SystemClock.uptimeMillis();
+            mLastUserActivityTime = now;
         }
     }
 
@@ -756,6 +757,15 @@
         }
     }
 
+    /** Functional interface for providing time. */
+    @VisibleForTesting
+    interface Clock {
+        /**
+         * Returns current time in milliseconds since boot, not counting time spent in deep sleep.
+         */
+        long uptimeMillis();
+    }
+
     @VisibleForTesting
     static class Injector {
         Notifier createNotifier(Looper looper, Context context, IBatteryStats batteryStats,
@@ -822,6 +832,17 @@
             };
         }
 
+        Clock createClock() {
+            return SystemClock::uptimeMillis;
+        }
+
+        /**
+         * Handler for asynchronous operations performed by the power manager.
+         */
+        Handler createHandler(Looper looper, Handler.Callback callback) {
+            return new Handler(looper, callback, true /*async*/);
+        }
+
         void invalidateIsInteractiveCaches() {
             PowerManager.invalidateIsInteractiveCaches();
         }
@@ -853,12 +874,14 @@
         mLocalService = new LocalService();
         mNativeWrapper = injector.createNativeWrapper();
         mSystemProperties = injector.createSystemPropertiesWrapper();
+        mClock = injector.createClock();
         mInjector = injector;
 
         mHandlerThread = new ServiceThread(TAG,
                 Process.THREAD_PRIORITY_DISPLAY, false /*allowIo*/);
         mHandlerThread.start();
-        mHandler = new PowerManagerHandler(mHandlerThread.getLooper());
+        mHandler = injector.createHandler(mHandlerThread.getLooper(),
+                new PowerManagerHandlerCallback());
         mConstants = new Constants(mHandler);
         mAmbientDisplayConfiguration = mInjector.createAmbientDisplayConfiguration(context);
         mAmbientDisplaySuppressionController =
@@ -1002,7 +1025,7 @@
                 incrementBootCount();
 
             } else if (phase == PHASE_BOOT_COMPLETED) {
-                final long now = SystemClock.uptimeMillis();
+                final long now = mClock.uptimeMillis();
                 mBootCompleted = true;
                 mDirty |= DIRTY_BOOT_COMPLETED;
 
@@ -1011,7 +1034,7 @@
                         now, PowerManager.USER_ACTIVITY_EVENT_OTHER, 0, Process.SYSTEM_UID);
 
                 if (sQuiescent) {
-                    goToSleepNoUpdateLocked(SystemClock.uptimeMillis(),
+                    goToSleepNoUpdateLocked(mClock.uptimeMillis(),
                             PowerManager.GO_TO_SLEEP_REASON_QUIESCENT,
                             PowerManager.GO_TO_SLEEP_FLAG_NO_DOZE, Process.SYSTEM_UID);
                 }
@@ -1354,7 +1377,7 @@
                 opPackageName = wakeLock.mPackageName;
                 opUid = wakeLock.mOwnerUid;
             }
-            wakeUpNoUpdateLocked(SystemClock.uptimeMillis(),
+            wakeUpNoUpdateLocked(mClock.uptimeMillis(),
                     PowerManager.WAKE_REASON_APPLICATION, wakeLock.mTag,
                     opUid, opPackageName, opUid);
         }
@@ -1420,7 +1443,7 @@
     private void applyWakeLockFlagsOnReleaseLocked(WakeLock wakeLock) {
         if ((wakeLock.mFlags & PowerManager.ON_AFTER_RELEASE) != 0
                 && isScreenLock(wakeLock)) {
-            userActivityNoUpdateLocked(SystemClock.uptimeMillis(),
+            userActivityNoUpdateLocked(mClock.uptimeMillis(),
                     PowerManager.USER_ACTIVITY_EVENT_OTHER,
                     PowerManager.USER_ACTIVITY_FLAG_NO_CHANGE_LIGHTS,
                     wakeLock.mOwnerUid);
@@ -1484,7 +1507,7 @@
     }
 
     private void restartNofifyLongTimerLocked(WakeLock wakeLock) {
-        wakeLock.mAcquireTime = SystemClock.uptimeMillis();
+        wakeLock.mAcquireTime = mClock.uptimeMillis();
         if ((wakeLock.mFlags & PowerManager.WAKE_LOCK_LEVEL_MASK)
                 == PowerManager.PARTIAL_WAKE_LOCK && mNotifyLongScheduled == 0) {
             enqueueNotifyLongMsgLocked(wakeLock.mAcquireTime + MIN_LONG_WAKE_CHECK_INTERVAL);
@@ -1569,7 +1592,7 @@
 
     private void onUserAttention() {
         synchronized (mLock) {
-            if (userActivityNoUpdateLocked(SystemClock.uptimeMillis(),
+            if (userActivityNoUpdateLocked(mClock.uptimeMillis(),
                     PowerManager.USER_ACTIVITY_EVENT_ATTENTION, 0 /* flags */,
                     Process.SYSTEM_UID)) {
                 updatePowerStateLocked();
@@ -1844,7 +1867,7 @@
      * had the system not been told the user was inactive.
      */
     private void logSleepTimeoutRecapturedLocked() {
-        final long now = SystemClock.uptimeMillis();
+        final long now = mClock.uptimeMillis();
         final long savedWakeTimeMs = mOverriddenTimeout - now;
         if (savedWakeTimeMs >= 0) {
             EventLogTags.writePowerSoftSleepRequested(savedWakeTimeMs);
@@ -1867,7 +1890,7 @@
             }
             if (getWakefulnessLocked() == WAKEFULNESS_AWAKE) {
                 Trace.asyncTraceEnd(Trace.TRACE_TAG_POWER, TRACE_SCREEN_ON, 0);
-                final int latencyMs = (int) (SystemClock.uptimeMillis() - mLastWakeTime);
+                final int latencyMs = (int) (mClock.uptimeMillis() - mLastWakeTime);
                 if (latencyMs >= SCREEN_ON_LATENCY_WARNING_MS) {
                     Slog.w(TAG, "Screen on took " + latencyMs + " ms");
                 }
@@ -1903,7 +1926,7 @@
             // Phase 1: Update wakefulness.
             // Loop because the wake lock and user activity computations are influenced
             // by changes in wakefulness.
-            final long now = SystemClock.uptimeMillis();
+            final long now = mClock.uptimeMillis();
             int dirtyPhase2 = 0;
             for (;;) {
                 int dirtyPhase1 = mDirty;
@@ -1996,7 +2019,7 @@
                 // and it shuts off right away.
                 // Some devices also wake the device when plugged or unplugged because
                 // they don't have a charging LED.
-                final long now = SystemClock.uptimeMillis();
+                final long now = mClock.uptimeMillis();
                 if (shouldWakeUpWhenPluggedOrUnpluggedLocked(wasPowered, oldPlugType,
                         dockedOnWirelessCharger)) {
                     wakeUpNoUpdateLocked(now, PowerManager.WAKE_REASON_PLUGGED_IN,
@@ -2204,7 +2227,7 @@
 
     void checkForLongWakeLocks() {
         synchronized (mLock) {
-            final long now = SystemClock.uptimeMillis();
+            final long now = mClock.uptimeMillis();
             mNotifyLongDispatched = now;
             final long when = now - MIN_LONG_WAKE_CHECK_INTERVAL;
             long nextCheckTime = Long.MAX_VALUE;
@@ -2533,7 +2556,7 @@
                 if (DEBUG_SPEW) {
                     Slog.d(TAG, "updateWakefulnessLocked: Bed time...");
                 }
-                final long time = SystemClock.uptimeMillis();
+                final long time = mClock.uptimeMillis();
                 if (isAttentiveTimeoutExpired(time)) {
                     changed = goToSleepNoUpdateLocked(time, PowerManager.GO_TO_SLEEP_REASON_TIMEOUT,
                             PowerManager.GO_TO_SLEEP_FLAG_NO_DOZE, Process.SYSTEM_UID);
@@ -2568,7 +2591,7 @@
             return false;
         }
 
-        long now = SystemClock.uptimeMillis();
+        long now = mClock.uptimeMillis();
         if (isAttentiveTimeoutExpired(now)) {
             return !isBeingKeptFromInattentiveSleepLocked();
         } else {
@@ -2699,7 +2722,7 @@
             }
 
             // Determine whether the dream should continue.
-            long now = SystemClock.uptimeMillis();
+            long now = mClock.uptimeMillis();
             if (wakefulness == WAKEFULNESS_DREAMING) {
                 if (isDreaming && canDreamLocked()) {
                     if (mDreamsBatteryLevelDrainCutoffConfig >= 0
@@ -2881,7 +2904,7 @@
     private void updateScreenBrightnessBoostLocked(int dirty) {
         if ((dirty & DIRTY_SCREEN_BRIGHTNESS_BOOST) != 0) {
             if (mScreenBrightnessBoostInProgress) {
-                final long now = SystemClock.uptimeMillis();
+                final long now = mClock.uptimeMillis();
                 mHandler.removeMessages(MSG_SCREEN_BRIGHTNESS_BOOST_TIMEOUT);
                 if (mLastScreenBrightnessBoostTime > mLastSleepTime) {
                     final long boostTimeout = mLastScreenBrightnessBoostTime +
@@ -2968,7 +2991,7 @@
             synchronized (mLock) {
                 mProximityPositive = false;
                 mDirty |= DIRTY_PROXIMITY_POSITIVE;
-                userActivityNoUpdateLocked(SystemClock.uptimeMillis(),
+                userActivityNoUpdateLocked(mClock.uptimeMillis(),
                         PowerManager.USER_ACTIVITY_EVENT_OTHER, 0, Process.SYSTEM_UID);
                 updatePowerStateLocked();
             }
@@ -3289,7 +3312,8 @@
                 if (profile != null) {
                     profile.mScreenOffTimeout = timeMs;
                 } else {
-                    mProfilePowerState.put(userId, new ProfilePowerState(userId, timeMs));
+                    mProfilePowerState.put(userId, new ProfilePowerState(userId, timeMs,
+                            mClock.uptimeMillis()));
                     // We need to recalculate wake locks for the new profile state.
                     mDirty |= DIRTY_WAKE_LOCKS;
                 }
@@ -3646,14 +3670,14 @@
     @VisibleForTesting
     boolean wasDeviceIdleForInternal(long ms) {
         synchronized (mLock) {
-            return mLastUserActivityTime + ms < SystemClock.uptimeMillis();
+            return mLastUserActivityTime + ms < mClock.uptimeMillis();
         }
     }
 
     @VisibleForTesting
     void onUserActivity() {
         synchronized (mLock) {
-            mLastUserActivityTime = SystemClock.uptimeMillis();
+            mLastUserActivityTime = mClock.uptimeMillis();
         }
     }
 
@@ -3662,7 +3686,7 @@
             synchronized (mLock) {
                 mForceSuspendActive = true;
                 // Place the system in an non-interactive state
-                goToSleepInternal(SystemClock.uptimeMillis(),
+                goToSleepInternal(mClock.uptimeMillis(),
                         PowerManager.GO_TO_SLEEP_REASON_FORCE_SUSPEND,
                         PowerManager.GO_TO_SLEEP_FLAG_NO_DOZE, uid);
 
@@ -3777,21 +3801,21 @@
             if (mNotifyLongScheduled == 0) {
                 pw.print("(none)");
             } else {
-                TimeUtils.formatDuration(mNotifyLongScheduled, SystemClock.uptimeMillis(), pw);
+                TimeUtils.formatDuration(mNotifyLongScheduled, mClock.uptimeMillis(), pw);
             }
             pw.println();
             pw.print("  mNotifyLongDispatched=");
             if (mNotifyLongDispatched == 0) {
                 pw.print("(none)");
             } else {
-                TimeUtils.formatDuration(mNotifyLongDispatched, SystemClock.uptimeMillis(), pw);
+                TimeUtils.formatDuration(mNotifyLongDispatched, mClock.uptimeMillis(), pw);
             }
             pw.println();
             pw.print("  mNotifyLongNextCheck=");
             if (mNotifyLongNextCheck == 0) {
                 pw.print("(none)");
             } else {
-                TimeUtils.formatDuration(mNotifyLongNextCheck, SystemClock.uptimeMillis(), pw);
+                TimeUtils.formatDuration(mNotifyLongNextCheck, mClock.uptimeMillis(), pw);
             }
             pw.println();
             pw.println("  mUserActivitySummary=0x" + Integer.toHexString(mUserActivitySummary));
@@ -4389,15 +4413,11 @@
     };
 
     /**
-     * Handler for asynchronous operations performed by the power manager.
+     * Callback for asynchronous operations performed by the power manager.
      */
-    private final class PowerManagerHandler extends Handler {
-        public PowerManagerHandler(Looper looper) {
-            super(looper, null, true /*async*/);
-        }
-
+    private final class PowerManagerHandlerCallback implements Handler.Callback {
         @Override
-        public void handleMessage(Message msg) {
+        public boolean handleMessage(Message msg) {
             switch (msg.what) {
                 case MSG_USER_ACTIVITY_TIMEOUT:
                     handleUserActivityTimeout();
@@ -4415,6 +4435,8 @@
                     handleAttentiveTimeout();
                     break;
             }
+
+            return true;
         }
     }
 
@@ -4505,7 +4527,7 @@
             }
             if (mNotifiedAcquired) {
                 sb.append(" ACQ=");
-                TimeUtils.formatDuration(mAcquireTime-SystemClock.uptimeMillis(), sb);
+                TimeUtils.formatDuration(mAcquireTime-mClock.uptimeMillis(), sb);
             }
             if (mNotifiedLong) {
                 sb.append(" LONG");
@@ -4817,7 +4839,7 @@
 
         @Override // Binder call
         public void userActivity(long eventTime, int event, int flags) {
-            final long now = SystemClock.uptimeMillis();
+            final long now = mClock.uptimeMillis();
             if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER)
                     != PackageManager.PERMISSION_GRANTED
                     && mContext.checkCallingOrSelfPermission(
@@ -4855,7 +4877,7 @@
         @Override // Binder call
         public void wakeUp(long eventTime, @WakeReason int reason, String details,
                 String opPackageName) {
-            if (eventTime > SystemClock.uptimeMillis()) {
+            if (eventTime > mClock.uptimeMillis()) {
                 throw new IllegalArgumentException("event time must not be in the future");
             }
 
@@ -4873,7 +4895,7 @@
 
         @Override // Binder call
         public void goToSleep(long eventTime, int reason, int flags) {
-            if (eventTime > SystemClock.uptimeMillis()) {
+            if (eventTime > mClock.uptimeMillis()) {
                 throw new IllegalArgumentException("event time must not be in the future");
             }
 
@@ -4891,7 +4913,7 @@
 
         @Override // Binder call
         public void nap(long eventTime) {
-            if (eventTime > SystemClock.uptimeMillis()) {
+            if (eventTime > mClock.uptimeMillis()) {
                 throw new IllegalArgumentException("event time must not be in the future");
             }
 
@@ -5286,7 +5308,7 @@
 
         @Override // Binder call
         public void boostScreenBrightness(long eventTime) {
-            if (eventTime > SystemClock.uptimeMillis()) {
+            if (eventTime > mClock.uptimeMillis()) {
                 throw new IllegalArgumentException("event time must not be in the future");
             }
 
diff --git a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
index bb51471..d244e68 100644
--- a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
@@ -61,8 +61,8 @@
 import android.os.Looper;
 import android.os.PowerManager;
 import android.os.PowerSaveState;
-import android.os.SystemClock;
 import android.os.UserHandle;
+import android.os.test.TestLooper;
 import android.provider.Settings;
 import android.service.dreams.DreamManagerInternal;
 import android.test.mock.MockContentResolver;
@@ -84,6 +84,7 @@
 import com.android.server.power.batterysaver.BatterySaverPolicy;
 import com.android.server.power.batterysaver.BatterySaverStateMachine;
 import com.android.server.power.batterysaver.BatterySavingStats;
+import com.android.server.testutils.OffsettableClock;
 
 import org.junit.After;
 import org.junit.Before;
@@ -132,6 +133,8 @@
     private BatteryReceiver mBatteryReceiver;
     private UserSwitchedReceiver mUserSwitchedReceiver;
     private Resources mResourcesSpy;
+    private OffsettableClock mClock;
+    private TestLooper mTestLooper;
 
     private class IntentFilterMatcher implements ArgumentMatcher<IntentFilter> {
         private final IntentFilter mFilter;
@@ -189,6 +192,9 @@
 
         Settings.Global.putInt(mContextSpy.getContentResolver(),
                 Settings.Global.STAY_ON_WHILE_PLUGGED_IN, 0);
+
+        mClock = new OffsettableClock.Stopped();
+        mTestLooper = new TestLooper(mClock::now);
     }
 
     private PowerManagerService createService() {
@@ -250,6 +256,16 @@
             }
 
             @Override
+            PowerManagerService.Clock createClock() {
+                return () -> mClock.now();
+            }
+
+            @Override
+            Handler createHandler(Looper looper, Handler.Callback callback) {
+                return new Handler(mTestLooper.getLooper(), callback);
+            }
+
+            @Override
             void invalidateIsInteractiveCaches() {
                 // Avoids an SELinux failure.
             }
@@ -297,21 +313,21 @@
     }
 
     private void forceSleep() {
-        mService.getBinderServiceInstance().goToSleep(SystemClock.uptimeMillis(),
+        mService.getBinderServiceInstance().goToSleep(mClock.now(),
                 PowerManager.GO_TO_SLEEP_REASON_APPLICATION, PowerManager.GO_TO_SLEEP_FLAG_NO_DOZE);
     }
 
     private void forceDream() {
-        mService.getBinderServiceInstance().nap(SystemClock.uptimeMillis());
+        mService.getBinderServiceInstance().nap(mClock.now());
     }
 
     private void forceAwake() {
-        mService.getBinderServiceInstance().wakeUp(SystemClock.uptimeMillis(),
+        mService.getBinderServiceInstance().wakeUp(mClock.now(),
                 PowerManager.WAKE_REASON_UNKNOWN, "testing IPowerManager.wakeUp()", "pkg.name");
     }
 
     private void forceDozing() {
-        mService.getBinderServiceInstance().goToSleep(SystemClock.uptimeMillis(),
+        mService.getBinderServiceInstance().goToSleep(mClock.now(),
                 PowerManager.GO_TO_SLEEP_REASON_APPLICATION, 0);
     }
 
@@ -341,6 +357,11 @@
                 .thenReturn(minimumScreenOffTimeoutConfigMillis);
     }
 
+    private void advanceTime(long timeMs) {
+        mClock.fastForward(timeMs);
+        mTestLooper.dispatchAll();
+    }
+
     @Test
     public void testUpdatePowerScreenPolicy_UpdateDisplayPowerRequest() {
         createService();
@@ -403,7 +424,7 @@
         assertThat(mService.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
 
         // Take a nap and verify.
-        mService.getBinderServiceInstance().goToSleep(SystemClock.uptimeMillis(),
+        mService.getBinderServiceInstance().goToSleep(mClock.now(),
                 PowerManager.GO_TO_SLEEP_REASON_APPLICATION, PowerManager.GO_TO_SLEEP_FLAG_NO_DOZE);
         assertThat(mService.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
     }
@@ -445,7 +466,7 @@
         createService();
         startSystem();
         forceSleep();
-        mService.getBinderServiceInstance().wakeUp(SystemClock.uptimeMillis(),
+        mService.getBinderServiceInstance().wakeUp(mClock.now(),
                 PowerManager.WAKE_REASON_UNKNOWN, "testing IPowerManager.wakeUp()", "pkg.name");
         assertThat(mService.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
     }
@@ -540,7 +561,7 @@
         assertThat(mService.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
 
         // Take a nap and verify.
-        mService.getBinderServiceInstance().goToSleep(SystemClock.uptimeMillis(),
+        mService.getBinderServiceInstance().goToSleep(mClock.now(),
                 PowerManager.GO_TO_SLEEP_REASON_APPLICATION, 0);
         assertThat(mService.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_DOZING);
     }
@@ -550,7 +571,7 @@
         int interval = 1000;
         createService();
         mService.onUserActivity();
-        SystemClock.sleep(interval + 1 /* just a little more */);
+        advanceTime(interval + 1 /* just a little more */);
         assertThat(mService.wasDeviceIdleForInternal(interval)).isTrue();
     }
 
@@ -678,7 +699,7 @@
         mService.getBinderServiceInstance().acquireWakeLock(token, flags, tag, packageName,
                 null /* workSource */, null /* historyTag */);
         when(mDreamManagerInternalMock.isDreaming()).thenReturn(true);
-        mService.getBinderServiceInstance().goToSleep(SystemClock.uptimeMillis(),
+        mService.getBinderServiceInstance().goToSleep(mClock.now(),
                 PowerManager.GO_TO_SLEEP_REASON_APPLICATION, 0);
         assertThat(mService.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_DOZING);
         assertFalse(isAcquired[0]);
@@ -718,16 +739,16 @@
         createService();
         startSystem();
 
-        mService.getBinderServiceInstance().userActivity(SystemClock.uptimeMillis(),
+        mService.getBinderServiceInstance().userActivity(mClock.now(),
                 PowerManager.USER_ACTIVITY_EVENT_TOUCH, 0);
         verify(mInattentiveSleepWarningControllerMock, never()).show();
 
-        SystemClock.sleep(150);
+        advanceTime(150);
         verify(mInattentiveSleepWarningControllerMock, times(1)).show();
         verify(mInattentiveSleepWarningControllerMock, never()).dismiss(anyBoolean());
         when(mInattentiveSleepWarningControllerMock.isShown()).thenReturn(true);
 
-        mService.getBinderServiceInstance().userActivity(SystemClock.uptimeMillis(),
+        mService.getBinderServiceInstance().userActivity(mClock.now(),
                 PowerManager.USER_ACTIVITY_EVENT_TOUCH, 0);
         verify(mInattentiveSleepWarningControllerMock, times(1)).dismiss(true);
     }
@@ -740,10 +761,10 @@
 
         createService();
         startSystem();
-        SystemClock.sleep(50);
+        advanceTime(50);
         verify(mInattentiveSleepWarningControllerMock, atLeastOnce()).show();
         when(mInattentiveSleepWarningControllerMock.isShown()).thenReturn(true);
-        SystemClock.sleep(70);
+        advanceTime(70);
         assertThat(mService.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
         forceAwake();
         assertThat(mService.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
@@ -764,7 +785,7 @@
         setAttentiveTimeout(5);
         createService();
         startSystem();
-        SystemClock.sleep(20);
+        advanceTime(20);
         assertThat(mService.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
     }
 
@@ -772,7 +793,7 @@
     public void testInattentiveSleep_goesToSleepWithWakeLock() throws Exception {
         final String pkg = mContextSpy.getOpPackageName();
         final Binder token = new Binder();
-        final String tag = "sleep_testWithWakeLock";
+        final String tag = "testInattentiveSleep_goesToSleepWithWakeLock";
 
         setMinimumScreenOffTimeoutConfig(5);
         setAttentiveTimeout(30);
@@ -783,7 +804,7 @@
                 PowerManager.SCREEN_BRIGHT_WAKE_LOCK, tag, pkg,
                 null /* workSource */, null /* historyTag */);
 
-        SystemClock.sleep(60);
+        advanceTime(60);
         assertThat(mService.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
     }
 
diff --git a/tests/utils/testutils/java/android/os/test/TestLooper.java b/tests/utils/testutils/java/android/os/test/TestLooper.java
index 01bd47b..a826646 100644
--- a/tests/utils/testutils/java/android/os/test/TestLooper.java
+++ b/tests/utils/testutils/java/android/os/test/TestLooper.java
@@ -48,6 +48,8 @@
     private static final Method MESSAGE_MARK_IN_USE_METHOD;
     private static final String TAG = "TestLooper";
 
+    private final Clock mClock;
+
     private AutoDispatchThread mAutoDispatchThread;
 
     static {
@@ -69,8 +71,25 @@
         }
     }
 
-
+    /**
+     * Creates a TestLooper and installs it as the looper for the current thread.
+     */
     public TestLooper() {
+        this(SystemClock::uptimeMillis);
+    }
+
+    /**
+     * Creates a TestLooper with a custom clock and installs it as the looper for the current
+     * thread.
+     *
+     * Messages are dispatched when their {@link Message#when} is before or at {@link
+     * Clock#uptimeMillis()}.
+     * Use a custom clock with care. When using an offsettable clock like {@link
+     * com.android.server.testutils.OffsettableClock} be sure not to double offset messages by
+     * offsetting the clock and calling {@link #moveTimeForward(long)}. Instead, offset the clock
+     * and call {@link #dispatchAll()}.
+     */
+    public TestLooper(Clock clock) {
         try {
             mLooper = LOOPER_CONSTRUCTOR.newInstance(false);
 
@@ -80,6 +99,8 @@
         } catch (IllegalAccessException | InstantiationException | InvocationTargetException e) {
             throw new RuntimeException("Reflection error constructing or accessing looper", e);
         }
+
+        mClock = clock;
     }
 
     public Looper getLooper() {
@@ -116,9 +137,13 @@
         }
     }
 
+    private long currentTime() {
+        return mClock.uptimeMillis();
+    }
+
     private Message messageQueueNext() {
         try {
-            long now = SystemClock.uptimeMillis();
+            long now = currentTime();
 
             Message prevMsg = null;
             Message msg = getMessageLinkedList();
@@ -157,7 +182,7 @@
     public synchronized boolean isIdle() {
         Message messageList = getMessageLinkedList();
 
-        return messageList != null && SystemClock.uptimeMillis() >= messageList.getWhen();
+        return messageList != null && currentTime() >= messageList.getWhen();
     }
 
     /**
@@ -187,6 +212,7 @@
     /**
      * Dispatch all messages currently in the queue
      * Will not fail if there are no messages pending
+     *
      * @return the number of messages dispatched
      */
     public synchronized int dispatchAll() {
@@ -198,6 +224,10 @@
         return count;
     }
 
+    public interface Clock {
+        long uptimeMillis();
+    }
+
     /**
      * Thread used to dispatch messages when the main thread is blocked waiting for a response.
      */