Injecting system calls into alarm manager

Injecting and mocking dependencies to make the service unit testable.
Shuffled around initialization to make that possible.

Also added the first batch of unit tests. The idea is to have extensive
coverage of minute pieces of alarm manager logic, which should help us
catch any regressions caused by future changes to the code.

Test: atest com.android.server.AlarmManagerServiceTest
atest CtsAlarmManagerTestCases

Bug: 111454659
Change-Id: Ifa1c09646f688cf5c41abe17d98dbc19978eac70
diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java
index 26ef42f..499c03d 100644
--- a/services/core/java/com/android/server/AlarmManagerService.java
+++ b/services/core/java/com/android/server/AlarmManagerService.java
@@ -52,6 +52,7 @@
 import android.os.Environment;
 import android.os.Handler;
 import android.os.IBinder;
+import android.os.Looper;
 import android.os.Message;
 import android.os.ParcelableException;
 import android.os.PowerManager;
@@ -111,7 +112,7 @@
 import java.util.function.Predicate;
 
 /**
- * Alarm manager implementaion.
+ * Alarm manager implementation.
  *
  * Unit test:
  atest $ANDROID_BUILD_TOP/frameworks/base/services/tests/servicestests/src/com/android/server/AlarmManagerServiceTest.java
@@ -138,7 +139,6 @@
     static final boolean DEBUG_STANDBY = localLOGV || false;
     static final boolean RECORD_ALARMS_IN_HISTORY = true;
     static final boolean RECORD_DEVICE_IDLE_ALARMS = false;
-    static final int ALARM_EVENT = 1;
     static final String TIMEZONE_PROPERTY = "persist.sys.timezone";
 
     // Indices into the APP_STANDBY_MIN_DELAYS and KEYS_APP_STANDBY_DELAY arrays
@@ -169,7 +169,6 @@
 
     // List of alarms per uid deferred due to user applied background restrictions on the source app
     SparseArray<ArrayList<Alarm>> mPendingBackgroundAlarms = new SparseArray<>();
-    long mNativeData;
     private long mNextWakeup;
     private long mNextNonWakeup;
     private long mNextWakeUpSetAt;
@@ -181,15 +180,14 @@
     private long mLastTickReceived;
     private long mLastTickAdded;
     private long mLastTickRemoved;
+    private final Injector mInjector;
     int mBroadcastRefCount = 0;
     PowerManager.WakeLock mWakeLock;
     boolean mLastWakeLockUnimportantForLogging;
     ArrayList<Alarm> mPendingNonWakeupAlarms = new ArrayList<>();
     ArrayList<InFlight> mInFlight = new ArrayList<>();
-    final AlarmHandler mHandler = new AlarmHandler();
+    AlarmHandler mHandler;
     ClockReceiver mClockReceiver;
-    InteractiveStateReceiver mInteractiveStateReceiver;
-    private UninstallReceiver mUninstallReceiver;
     final DeliveryTracker mDeliveryTracker = new DeliveryTracker();
     PendingIntent mTimeTickSender;
     PendingIntent mDateChangeSender;
@@ -277,7 +275,8 @@
      * global Settings. Any access to this class or its fields should be done while
      * holding the AlarmManagerService.mLock lock.
      */
-    private final class Constants extends ContentObserver {
+    @VisibleForTesting
+    final class Constants extends ContentObserver {
         // Key names stored in the settings value.
         private static final String KEY_MIN_FUTURITY = "min_futurity";
         private static final String KEY_MIN_INTERVAL = "min_interval";
@@ -456,7 +455,7 @@
         }
     }
 
-    final Constants mConstants;
+    Constants mConstants;
 
     // Alarm delivery ordering bookkeeping
     static final int PRIO_TICK = 0;
@@ -510,7 +509,7 @@
             flags = seed.flags;
             alarms.add(seed);
             if (seed.operation == mTimeTickSender) {
-                mLastTickAdded = System.currentTimeMillis();
+                mLastTickAdded = mInjector.getCurrentTimeMillis();
             }
         }
 
@@ -535,7 +534,7 @@
             }
             alarms.add(index, alarm);
             if (alarm.operation == mTimeTickSender) {
-                mLastTickAdded = System.currentTimeMillis();
+                mLastTickAdded = mInjector.getCurrentTimeMillis();
             }
             if (DEBUG_BATCH) {
                 Slog.v(TAG, "Adding " + alarm + " to " + this);
@@ -573,7 +572,7 @@
                         mNextAlarmClockMayChange = true;
                     }
                     if (alarm.operation == mTimeTickSender) {
-                        mLastTickRemoved = System.currentTimeMillis();
+                        mLastTickRemoved = mInjector.getCurrentTimeMillis();
                     }
                 } else {
                     if (alarm.whenElapsed > newStart) {
@@ -734,17 +733,20 @@
     Alarm mNextWakeFromIdle = null;
     ArrayList<Alarm> mPendingWhileIdleAlarms = new ArrayList<>();
 
-    public AlarmManagerService(Context context) {
+    @VisibleForTesting
+    AlarmManagerService(Context context, Injector injector) {
         super(context);
-        mConstants = new Constants(mHandler);
-
-        publishLocalService(AlarmManagerInternal.class, new LocalService());
+        mInjector = injector;
     }
 
-    static long convertToElapsed(long when, int type) {
+    AlarmManagerService(Context context) {
+        this(context, new Injector(context));
+    }
+
+    private long convertToElapsed(long when, int type) {
         final boolean isRtc = (type == RTC || type == RTC_WAKEUP);
         if (isRtc) {
-            when -= System.currentTimeMillis() - SystemClock.elapsedRealtime();
+            when -= mInjector.getCurrentTimeMillis() - mInjector.getElapsedRealtime();
         }
         return when;
     }
@@ -854,7 +856,7 @@
         ArrayList<Batch> oldSet = (ArrayList<Batch>) mAlarmBatches.clone();
         mAlarmBatches.clear();
         Alarm oldPendingIdleUntil = mPendingIdleUntil;
-        final long nowElapsed = SystemClock.elapsedRealtime();
+        final long nowElapsed = mInjector.getElapsedRealtime();
         final int oldBatches = oldSet.size();
         for (int batchNum = 0; batchNum < oldBatches; batchNum++) {
             Batch batch = oldSet.get(batchNum);
@@ -984,7 +986,7 @@
             alarmsToDeliver = alarmsForUid;
             mPendingBackgroundAlarms.remove(uid);
         }
-        deliverPendingBackgroundAlarmsLocked(alarmsToDeliver, SystemClock.elapsedRealtime());
+        deliverPendingBackgroundAlarmsLocked(alarmsToDeliver, mInjector.getElapsedRealtime());
     }
 
     /**
@@ -1001,7 +1003,7 @@
                 mPendingBackgroundAlarms, alarmsToDeliver, this::isBackgroundRestricted);
 
         if (alarmsToDeliver.size() > 0) {
-            deliverPendingBackgroundAlarmsLocked(alarmsToDeliver, SystemClock.elapsedRealtime());
+            deliverPendingBackgroundAlarmsLocked(alarmsToDeliver, mInjector.getElapsedRealtime());
         }
     }
 
@@ -1088,7 +1090,7 @@
             IdleDispatchEntry ent = new IdleDispatchEntry();
             ent.uid = 0;
             ent.pkg = "FINISH IDLE";
-            ent.elapsedRealtime = SystemClock.elapsedRealtime();
+            ent.elapsedRealtime = mInjector.getElapsedRealtime();
             mAllowWhileIdleDispatches.add(ent);
         }
 
@@ -1096,7 +1098,7 @@
         if (mPendingWhileIdleAlarms.size() > 0) {
             ArrayList<Alarm> alarms = mPendingWhileIdleAlarms;
             mPendingWhileIdleAlarms = new ArrayList<>();
-            final long nowElapsed = SystemClock.elapsedRealtime();
+            final long nowElapsed = mInjector.getElapsedRealtime();
             for (int i=alarms.size() - 1; i >= 0; i--) {
                 Alarm a = alarms.get(i);
                 reAddAlarmLocked(a, nowElapsed, false);
@@ -1282,121 +1284,114 @@
 
     @Override
     public void onStart() {
-        mNativeData = init();
-        mNextWakeup = mNextNonWakeup = 0;
+        mInjector.init();
 
-        // We have to set current TimeZone info to kernel
-        // because kernel doesn't keep this after reboot
-        setTimeZoneImpl(SystemProperties.get(TIMEZONE_PROPERTY));
+        synchronized (mLock) {
+            mHandler = new AlarmHandler(Looper.myLooper());
+            mConstants = new Constants(mHandler);
 
-        // Also sure that we're booting with a halfway sensible current time
-        if (mNativeData != 0) {
+            mNextWakeup = mNextNonWakeup = 0;
+
+            // We have to set current TimeZone info to kernel
+            // because kernel doesn't keep this after reboot
+            setTimeZoneImpl(SystemProperties.get(TIMEZONE_PROPERTY));
+
+            // Also sure that we're booting with a halfway sensible current time
             final long systemBuildTime = Environment.getRootDirectory().lastModified();
-            if (System.currentTimeMillis() < systemBuildTime) {
-                Slog.i(TAG, "Current time only " + System.currentTimeMillis()
+            if (mInjector.getCurrentTimeMillis() < systemBuildTime) {
+                Slog.i(TAG, "Current time only " + mInjector.getCurrentTimeMillis()
                         + ", advancing to build time " + systemBuildTime);
-                setKernelTime(mNativeData, systemBuildTime);
+                mInjector.setKernelTime(systemBuildTime);
             }
-        }
 
-        // Determine SysUI's uid
-        final PackageManager packMan = getContext().getPackageManager();
-        try {
-            PermissionInfo sysUiPerm = packMan.getPermissionInfo(SYSTEM_UI_SELF_PERMISSION, 0);
-            ApplicationInfo sysUi = packMan.getApplicationInfo(sysUiPerm.packageName, 0);
-            if ((sysUi.privateFlags&ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0) {
-                mSystemUiUid = sysUi.uid;
+            // Determine SysUI's uid
+            mSystemUiUid = mInjector.getSystemUiUid();
+            if (mSystemUiUid <= 0) {
+                Slog.wtf(TAG, "SysUI package not found!");
+            }
+            mWakeLock = mInjector.getAlarmWakeLock();
+
+            mTimeTickSender = PendingIntent.getBroadcastAsUser(getContext(), 0,
+                    new Intent(Intent.ACTION_TIME_TICK).addFlags(
+                            Intent.FLAG_RECEIVER_REGISTERED_ONLY
+                                    | Intent.FLAG_RECEIVER_FOREGROUND
+                                    | Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS), 0,
+                    UserHandle.ALL);
+            Intent intent = new Intent(Intent.ACTION_DATE_CHANGED);
+            intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING
+                    | Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS);
+            mDateChangeSender = PendingIntent.getBroadcastAsUser(getContext(), 0, intent,
+                    Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT, UserHandle.ALL);
+
+            mClockReceiver = mInjector.getClockReceiver(this);
+            new InteractiveStateReceiver();
+            new UninstallReceiver();
+
+            if (mInjector.isAlarmDriverPresent()) {
+                AlarmThread waitThread = new AlarmThread();
+                waitThread.start();
             } else {
-                Slog.e(TAG, "SysUI permission " + SYSTEM_UI_SELF_PERMISSION
-                        + " defined by non-privileged app " + sysUi.packageName
-                        + " - ignoring");
+                Slog.w(TAG, "Failed to open alarm driver. Falling back to a handler.");
             }
-        } catch (NameNotFoundException e) {
+
+            try {
+                ActivityManager.getService().registerUidObserver(new UidObserver(),
+                        ActivityManager.UID_OBSERVER_GONE | ActivityManager.UID_OBSERVER_IDLE
+                                | ActivityManager.UID_OBSERVER_ACTIVE,
+                        ActivityManager.PROCESS_STATE_UNKNOWN, null);
+            } catch (RemoteException e) {
+                // ignored; both services live in system_server
+            }
         }
-
-        if (mSystemUiUid <= 0) {
-            Slog.wtf(TAG, "SysUI package not found!");
-        }
-
-        PowerManager pm = (PowerManager) getContext().getSystemService(Context.POWER_SERVICE);
-        mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "*alarm*");
-
-        mTimeTickSender = PendingIntent.getBroadcastAsUser(getContext(), 0,
-                new Intent(Intent.ACTION_TIME_TICK).addFlags(
-                        Intent.FLAG_RECEIVER_REGISTERED_ONLY
-                        | Intent.FLAG_RECEIVER_FOREGROUND
-                        | Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS), 0,
-                        UserHandle.ALL);
-        Intent intent = new Intent(Intent.ACTION_DATE_CHANGED);
-        intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING
-                | Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS);
-        mDateChangeSender = PendingIntent.getBroadcastAsUser(getContext(), 0, intent,
-                Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT, UserHandle.ALL);
-
-        // now that we have initied the driver schedule the alarm
-        mClockReceiver = new ClockReceiver();
-        mClockReceiver.scheduleTimeTickEvent();
-        mClockReceiver.scheduleDateChangedEvent();
-        mInteractiveStateReceiver = new InteractiveStateReceiver();
-        mUninstallReceiver = new UninstallReceiver();
-
-        if (mNativeData != 0) {
-            AlarmThread waitThread = new AlarmThread();
-            waitThread.start();
-        } else {
-            Slog.w(TAG, "Failed to open alarm driver. Falling back to a handler.");
-        }
-
-        try {
-            ActivityManager.getService().registerUidObserver(new UidObserver(),
-                    ActivityManager.UID_OBSERVER_GONE | ActivityManager.UID_OBSERVER_IDLE
-                            | ActivityManager.UID_OBSERVER_ACTIVE,
-                    ActivityManager.PROCESS_STATE_UNKNOWN, null);
-        } catch (RemoteException e) {
-            // ignored; both services live in system_server
-        }
+        publishLocalService(AlarmManagerInternal.class, new LocalService());
         publishBinderService(Context.ALARM_SERVICE, mService);
     }
 
     @Override
     public void onBootPhase(int phase) {
         if (phase == PHASE_SYSTEM_SERVICES_READY) {
-            mConstants.start(getContext().getContentResolver());
-            mAppOps = (AppOpsManager) getContext().getSystemService(Context.APP_OPS_SERVICE);
-            mLocalDeviceIdleController
-                    = LocalServices.getService(DeviceIdleController.LocalService.class);
-            mUsageStatsManagerInternal = LocalServices.getService(UsageStatsManagerInternal.class);
-            mUsageStatsManagerInternal.addAppIdleStateChangeListener(new AppStandbyTracker());
+            synchronized (mLock) {
+                mConstants.start(getContext().getContentResolver());
+                mAppOps = (AppOpsManager) getContext().getSystemService(Context.APP_OPS_SERVICE);
+                mLocalDeviceIdleController =
+                        LocalServices.getService(DeviceIdleController.LocalService.class);
+                mUsageStatsManagerInternal =
+                        LocalServices.getService(UsageStatsManagerInternal.class);
+                mUsageStatsManagerInternal.addAppIdleStateChangeListener(new AppStandbyTracker());
 
-            mAppStateTracker = LocalServices.getService(AppStateTracker.class);
-            mAppStateTracker.addListener(mForceAppStandbyListener);
+                mAppStateTracker = LocalServices.getService(AppStateTracker.class);
+                mAppStateTracker.addListener(mForceAppStandbyListener);
+
+                mClockReceiver.scheduleTimeTickEvent();
+                mClockReceiver.scheduleDateChangedEvent();
+            }
         }
     }
 
     @Override
     protected void finalize() throws Throwable {
         try {
-            close(mNativeData);
+            mInjector.close();
         } finally {
             super.finalize();
         }
     }
 
     boolean setTimeImpl(long millis) {
-        if (mNativeData == 0) {
+        if (!mInjector.isAlarmDriverPresent()) {
             Slog.w(TAG, "Not setting time since no alarm driver is available.");
             return false;
         }
 
         synchronized (mLock) {
-            final long currentTimeMillis = System.currentTimeMillis();
-            setKernelTime(mNativeData, millis);
+            final long currentTimeMillis = mInjector.getCurrentTimeMillis();
+            mInjector.setKernelTime(millis);
             final TimeZone timeZone = TimeZone.getDefault();
             final int currentTzOffset = timeZone.getOffset(currentTimeMillis);
             final int newTzOffset = timeZone.getOffset(millis);
             if (currentTzOffset != newTzOffset) {
                 Slog.i(TAG, "Timezone offset has changed, updating kernel timezone");
-                setKernelTimezone(mNativeData, -(newTzOffset / 60000));
+                mInjector.setKernelTimezone(-(newTzOffset / 60000));
             }
             // The native implementation of setKernelTime can return -1 even when the kernel
             // time was set correctly, so assume setting kernel time was successful and always
@@ -1426,8 +1421,8 @@
 
             // Update the kernel timezone information
             // Kernel tracks time offsets as 'minutes west of GMT'
-            int gmtOffset = zone.getOffset(System.currentTimeMillis());
-            setKernelTimezone(mNativeData, -(gmtOffset / 60000));
+            int gmtOffset = zone.getOffset(mInjector.getCurrentTimeMillis());
+            mInjector.setKernelTimezone(-(gmtOffset / 60000));
         }
 
         TimeZone.setDefault(null);
@@ -1498,7 +1493,7 @@
             triggerAtTime = 0;
         }
 
-        final long nowElapsed = SystemClock.elapsedRealtime();
+        final long nowElapsed = mInjector.getElapsedRealtime();
         final long nominalTrigger = convertToElapsed(triggerAtTime, type);
         // Try to prevent spamming by making sure we aren't firing alarms in the immediate future
         final long minTrigger = nowElapsed + mConstants.MIN_FUTURITY;
@@ -1551,7 +1546,8 @@
      * Return the minimum time that should elapse before an app in the specified bucket
      * can receive alarms again
      */
-    private long getMinDelayForBucketLocked(int bucket) {
+    @VisibleForTesting
+    long getMinDelayForBucketLocked(int bucket) {
         // UsageStats bucket values are treated as floors of their behavioral range.
         // In other words, a bucket value between WORKING and ACTIVE is treated as
         // WORKING, not as ACTIVE.  The ACTIVE and NEVER bucket apply only at specific
@@ -1591,7 +1587,7 @@
         final String sourcePackage = alarm.sourcePackage;
         final int sourceUserId = UserHandle.getUserId(alarm.creatorUid);
         final int standbyBucket = mUsageStatsManagerInternal.getAppStandbyBucket(
-                sourcePackage, sourceUserId, SystemClock.elapsedRealtime());
+                sourcePackage, sourceUserId, mInjector.getElapsedRealtime());
 
         final Pair<String, Integer> packageUser = Pair.create(sourcePackage, sourceUserId);
         final long lastElapsed = mLastAlarmDeliveredForPackage.getOrDefault(packageUser, 0L);
@@ -1619,7 +1615,7 @@
                 a.when = a.whenElapsed = a.maxWhenElapsed = mNextWakeFromIdle.whenElapsed;
             }
             // Add fuzz to make the alarm go off some time before the actual desired time.
-            final long nowElapsed = SystemClock.elapsedRealtime();
+            final long nowElapsed = mInjector.getElapsedRealtime();
             final int fuzz = fuzzForDuration(a.whenElapsed-nowElapsed);
             if (fuzz > 0) {
                 if (mRandom == null) {
@@ -1655,7 +1651,7 @@
                 ent.pkg = a.operation.getCreatorPackage();
                 ent.tag = a.operation.getTag("");
                 ent.op = "SET";
-                ent.elapsedRealtime = SystemClock.elapsedRealtime();
+                ent.elapsedRealtime = mInjector.getElapsedRealtime();
                 ent.argRealtime = a.whenElapsed;
                 mAllowWhileIdleDispatches.add(ent);
             }
@@ -1675,7 +1671,7 @@
                     IdleDispatchEntry ent = new IdleDispatchEntry();
                     ent.uid = 0;
                     ent.pkg = "START IDLE";
-                    ent.elapsedRealtime = SystemClock.elapsedRealtime();
+                    ent.elapsedRealtime = mInjector.getElapsedRealtime();
                     mAllowWhileIdleDispatches.add(ent);
                 }
             }
@@ -1890,8 +1886,8 @@
             pw.println("  App Standby Parole: " + mAppStandbyParole);
             pw.println();
 
-            final long nowRTC = System.currentTimeMillis();
-            final long nowELAPSED = SystemClock.elapsedRealtime();
+            final long nowRTC = mInjector.getCurrentTimeMillis();
+            final long nowELAPSED = mInjector.getElapsedRealtime();
             final long nowUPTIME = SystemClock.uptimeMillis();
             SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
 
@@ -1958,10 +1954,10 @@
                     pw.println();
 
             pw.print("  Next kernel non-wakeup alarm: ");
-            TimeUtils.formatDuration(getNextAlarm(mNativeData, ELAPSED_REALTIME), pw);
+            TimeUtils.formatDuration(mInjector.getNextAlarm(ELAPSED_REALTIME), pw);
             pw.println();
             pw.print("  Next kernel wakeup alarm: ");
-            TimeUtils.formatDuration(getNextAlarm(mNativeData, ELAPSED_REALTIME_WAKEUP), pw);
+            TimeUtils.formatDuration(mInjector.getNextAlarm(ELAPSED_REALTIME_WAKEUP), pw);
             pw.println();
 
             pw.print("  Last wakeup: "); TimeUtils.formatDuration(mLastWakeup, nowELAPSED, pw);
@@ -2257,8 +2253,8 @@
         final ProtoOutputStream proto = new ProtoOutputStream(fd);
 
         synchronized (mLock) {
-            final long nowRTC = System.currentTimeMillis();
-            final long nowElapsed = SystemClock.elapsedRealtime();
+            final long nowRTC = mInjector.getCurrentTimeMillis();
+            final long nowElapsed = mInjector.getElapsedRealtime();
             proto.write(AlarmManagerServiceDumpProto.CURRENT_TIME, nowRTC);
             proto.write(AlarmManagerServiceDumpProto.ELAPSED_REALTIME, nowElapsed);
             proto.write(AlarmManagerServiceDumpProto.LAST_TIME_CHANGE_CLOCK_TIME,
@@ -2494,8 +2490,8 @@
     private void logBatchesLocked(SimpleDateFormat sdf) {
         ByteArrayOutputStream bs = new ByteArrayOutputStream(2048);
         PrintWriter pw = new PrintWriter(bs);
-        final long nowRTC = System.currentTimeMillis();
-        final long nowELAPSED = SystemClock.elapsedRealtime();
+        final long nowRTC = mInjector.getCurrentTimeMillis();
+        final long nowELAPSED = mInjector.getElapsedRealtime();
         final int NZ = mAlarmBatches.size();
         for (int iz = 0; iz < NZ; iz++) {
             Batch bz = mAlarmBatches.get(iz);
@@ -2693,7 +2689,7 @@
             TimeUtils.formatDuration(mNextNonWakeUpSetAt - nowElapsed, errorMsg);
             errorMsg.append(", mLastWakeup=");
             TimeUtils.formatDuration(mLastWakeup - nowElapsed, errorMsg);
-            errorMsg.append(", timerfd_gettime=" + getNextAlarm(mNativeData, ELAPSED_REALTIME));
+            errorMsg.append(", timerfd_gettime=" + mInjector.getNextAlarm(ELAPSED_REALTIME));
             errorMsg.append("];");
         }
         if (mNextWakeup < (nowElapsed - 10_000) && mLastWakeup < mNextWakeup) {
@@ -2705,7 +2701,7 @@
             errorMsg.append(", mLastWakeup=");
             TimeUtils.formatDuration(mLastWakeup - nowElapsed, errorMsg);
             errorMsg.append(", timerfd_gettime="
-                    + getNextAlarm(mNativeData, ELAPSED_REALTIME_WAKEUP));
+                    + mInjector.getNextAlarm(ELAPSED_REALTIME_WAKEUP));
             errorMsg.append("];");
         }
         if (stuck) {
@@ -2716,7 +2712,7 @@
     void rescheduleKernelAlarmsLocked() {
         // Schedule the next upcoming wakeup alarm.  If there is a deliverable batch
         // prior to that which contains no wakeups, we schedule that as well.
-        final long nowElapsed = SystemClock.elapsedRealtime();
+        final long nowElapsed = mInjector.getElapsedRealtime();
         validateLastAlarmExpiredLocked(nowElapsed);
         long nextNonWakeup = 0;
         if (mAlarmBatches.size() > 0) {
@@ -2743,7 +2739,7 @@
         }
     }
 
-    private void removeLocked(PendingIntent operation, IAlarmListener directReceiver) {
+    void removeLocked(PendingIntent operation, IAlarmListener directReceiver) {
         if (operation == null && directReceiver == null) {
             if (localLOGV) {
                 Slog.w(TAG, "requested remove() of null operation",
@@ -2983,7 +2979,7 @@
     void interactiveStateChangedLocked(boolean interactive) {
         if (mInteractive != interactive) {
             mInteractive = interactive;
-            final long nowELAPSED = SystemClock.elapsedRealtime();
+            final long nowELAPSED = mInjector.getElapsedRealtime();
             if (interactive) {
                 if (mPendingNonWakeupAlarms.size() > 0) {
                     final long thisDelayTime = nowELAPSED - mStartCurrentDelayTime;
@@ -3023,31 +3019,13 @@
     }
 
     private void setLocked(int type, long when) {
-        if (mNativeData != 0) {
-            // The kernel never triggers alarms with negative wakeup times
-            // so we ensure they are positive.
-            long alarmSeconds, alarmNanoseconds;
-            if (when < 0) {
-                alarmSeconds = 0;
-                alarmNanoseconds = 0;
-            } else {
-                alarmSeconds = when / 1000;
-                alarmNanoseconds = (when % 1000) * 1000 * 1000;
-            }
-
-            final int result = set(mNativeData, type, alarmSeconds, alarmNanoseconds);
-            if (result != 0) {
-                final long nowElapsed = SystemClock.elapsedRealtime();
-                Slog.wtf(TAG, "Unable to set kernel alarm, now=" + nowElapsed
-                        + " type=" + type + " when=" + when
-                        + " @ (" + alarmSeconds + "," + alarmNanoseconds
-                        + "), ret = " + result + " = " + Os.strerror(result));
-            }
+        if (mInjector.isAlarmDriverPresent()) {
+            mInjector.setAlarm(type, when);
         } else {
             Message msg = Message.obtain();
-            msg.what = ALARM_EVENT;
+            msg.what = AlarmHandler.ALARM_EVENT;
 
-            mHandler.removeMessages(ALARM_EVENT);
+            mHandler.removeMessages(msg.what);
             mHandler.sendMessageAtTime(msg, when);
         }
     }
@@ -3106,13 +3084,13 @@
                         exemptOnBatterySaver);
     }
 
-    private native long init();
-    private native void close(long nativeData);
-    private native int set(long nativeData, int type, long seconds, long nanoseconds);
-    private native int waitForAlarm(long nativeData);
-    private native int setKernelTime(long nativeData, long millis);
-    private native int setKernelTimezone(long nativeData, int minuteswest);
-    private native long getNextAlarm(long nativeData, int type);
+    private static native long init();
+    private static native void close(long nativeData);
+    private static native int set(long nativeData, int type, long seconds, long nanoseconds);
+    private static native int waitForAlarm(long nativeData);
+    private static native int setKernelTime(long nativeData, long millis);
+    private static native int setKernelTimezone(long nativeData, int minuteswest);
+    private static native long getNextAlarm(long nativeData, int type);
 
     private long getWhileIdleMinIntervalLocked(int uid) {
         final boolean dozing = mPendingIdleUntil != null;
@@ -3516,6 +3494,103 @@
                 || (a.flags & FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED) != 0;
     }
 
+    @VisibleForTesting
+    static class Injector {
+        private long mNativeData;
+        private Context mContext;
+
+        Injector(Context context) {
+            mContext = context;
+        }
+
+        void init() {
+            mNativeData = AlarmManagerService.init();
+        }
+
+        int waitForAlarm() {
+            return AlarmManagerService.waitForAlarm(mNativeData);
+        }
+
+        boolean isAlarmDriverPresent() {
+            return mNativeData != 0;
+        }
+
+        void setAlarm(int type, long millis) {
+            // The kernel never triggers alarms with negative wakeup times
+            // so we ensure they are positive.
+            final long alarmSeconds, alarmNanoseconds;
+            if (millis < 0) {
+                alarmSeconds = 0;
+                alarmNanoseconds = 0;
+            } else {
+                alarmSeconds = millis / 1000;
+                alarmNanoseconds = (millis % 1000) * 1000 * 1000;
+            }
+
+            final int result = AlarmManagerService.set(mNativeData, type, alarmSeconds,
+                    alarmNanoseconds);
+            if (result != 0) {
+                final long nowElapsed = SystemClock.elapsedRealtime();
+                Slog.wtf(TAG, "Unable to set kernel alarm, now=" + nowElapsed
+                        + " type=" + type + " @ (" + alarmSeconds + "," + alarmNanoseconds
+                        + "), ret = " + result + " = " + Os.strerror(result));
+            }
+        }
+
+        long getNextAlarm(int type) {
+            return AlarmManagerService.getNextAlarm(mNativeData, type);
+        }
+
+        void setKernelTimezone(int minutesWest) {
+            AlarmManagerService.setKernelTimezone(mNativeData, minutesWest);
+        }
+
+        void setKernelTime(long millis) {
+            if (mNativeData != 0) {
+                AlarmManagerService.setKernelTime(mNativeData, millis);
+            }
+        }
+
+        void close() {
+            AlarmManagerService.close(mNativeData);
+        }
+
+        long getElapsedRealtime() {
+            return SystemClock.elapsedRealtime();
+        }
+
+        long getCurrentTimeMillis() {
+            return System.currentTimeMillis();
+        }
+
+        PowerManager.WakeLock getAlarmWakeLock() {
+            final PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
+            return pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "*alarm*");
+        }
+
+        int getSystemUiUid() {
+            int sysUiUid = -1;
+            final PackageManager pm = mContext.getPackageManager();
+            try {
+                PermissionInfo sysUiPerm = pm.getPermissionInfo(SYSTEM_UI_SELF_PERMISSION, 0);
+                ApplicationInfo sysUi = pm.getApplicationInfo(sysUiPerm.packageName, 0);
+                if ((sysUi.privateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0) {
+                    sysUiUid = sysUi.uid;
+                } else {
+                    Slog.e(TAG, "SysUI permission " + SYSTEM_UI_SELF_PERMISSION
+                            + " defined by non-privileged app " + sysUi.packageName
+                            + " - ignoring");
+                }
+            } catch (NameNotFoundException e) {
+            }
+            return sysUiUid;
+        }
+
+        ClockReceiver getClockReceiver(AlarmManagerService service) {
+            return service.new ClockReceiver();
+        }
+    }
+
     private class AlarmThread extends Thread
     {
         private int mFalseWakeups;
@@ -3524,7 +3599,7 @@
         {
             super("AlarmManager");
             mFalseWakeups = 0;
-            mWtfThreshold = 10;
+            mWtfThreshold = 100;
         }
 
         public void run()
@@ -3533,14 +3608,16 @@
 
             while (true)
             {
-                int result = waitForAlarm(mNativeData);
-
-                final long nowRTC = System.currentTimeMillis();
-                final long nowELAPSED = SystemClock.elapsedRealtime();
+                int result = mInjector.waitForAlarm();
+                final long nowRTC = mInjector.getCurrentTimeMillis();
+                final long nowELAPSED = mInjector.getElapsedRealtime();
                 synchronized (mLock) {
                     mLastWakeup = nowELAPSED;
                 }
-
+                if (result == 0) {
+                    Slog.wtf(TAG, "waitForAlarm returned 0, nowRTC = " + nowRTC
+                            + ", nowElapsed = " + nowELAPSED);
+                }
                 triggerList.clear();
 
                 if ((result & TIME_CHANGED_MASK) != 0) {
@@ -3720,7 +3797,8 @@
         public static final int APP_STANDBY_PAROLE_CHANGED = 6;
         public static final int REMOVE_FOR_STOPPED = 7;
 
-        public AlarmHandler() {
+        AlarmHandler(Looper looper) {
+            super(looper);
         }
 
         public void postRemoveForStopped(int uid) {
@@ -3732,8 +3810,8 @@
                 case ALARM_EVENT: {
                     ArrayList<Alarm> triggerList = new ArrayList<Alarm>();
                     synchronized (mLock) {
-                        final long nowRTC = System.currentTimeMillis();
-                        final long nowELAPSED = SystemClock.elapsedRealtime();
+                        final long nowRTC = mInjector.getCurrentTimeMillis();
+                        final long nowELAPSED = mInjector.getElapsedRealtime();
                         triggerAlarmsLocked(triggerList, nowELAPSED, nowRTC);
                         updateNextAlarmClockLocked();
                     }
@@ -3817,7 +3895,7 @@
                     Slog.v(TAG, "Received TIME_TICK alarm; rescheduling");
                 }
                 synchronized (mLock) {
-                    mLastTickReceived = System.currentTimeMillis();
+                    mLastTickReceived = mInjector.getCurrentTimeMillis();
                 }
                 scheduleTimeTickEvent();
             } else if (intent.getAction().equals(Intent.ACTION_DATE_CHANGED)) {
@@ -3826,14 +3904,14 @@
                 // based off of the current Zone gmt offset + userspace tracked
                 // daylight savings information.
                 TimeZone zone = TimeZone.getTimeZone(SystemProperties.get(TIMEZONE_PROPERTY));
-                int gmtOffset = zone.getOffset(System.currentTimeMillis());
-                setKernelTimezone(mNativeData, -(gmtOffset / 60000));
+                int gmtOffset = zone.getOffset(mInjector.getCurrentTimeMillis());
+                mInjector.setKernelTimezone(-(gmtOffset / 60000));
                 scheduleDateChangedEvent();
             }
         }
 
         public void scheduleTimeTickEvent() {
-            final long currentTime = System.currentTimeMillis();
+            final long currentTime = mInjector.getCurrentTimeMillis();
             final long nextTime = 60000 * ((currentTime / 60000) + 1);
 
             // Schedule this event for the amount of time that it would take to get to
@@ -3841,7 +3919,7 @@
             final long tickEventDelay = nextTime - currentTime;
 
             final WorkSource workSource = null; // Let system take blame for time tick events.
-            setImpl(ELAPSED_REALTIME, SystemClock.elapsedRealtime() + tickEventDelay, 0,
+            setImpl(ELAPSED_REALTIME, mInjector.getElapsedRealtime() + tickEventDelay, 0,
                     0, mTimeTickSender, null, null, AlarmManager.FLAG_STANDALONE, workSource,
                     null, Process.myUid(), "android");
 
@@ -3853,7 +3931,7 @@
 
         public void scheduleDateChangedEvent() {
             Calendar calendar = Calendar.getInstance();
-            calendar.setTimeInMillis(System.currentTimeMillis());
+            calendar.setTimeInMillis(mInjector.getCurrentTimeMillis());
             calendar.set(Calendar.HOUR_OF_DAY, 0);
             calendar.set(Calendar.MINUTE, 0);
             calendar.set(Calendar.SECOND, 0);
@@ -4122,7 +4200,7 @@
         }
 
         private void updateStatsLocked(InFlight inflight) {
-            final long nowELAPSED = SystemClock.elapsedRealtime();
+            final long nowELAPSED = mInjector.getElapsedRealtime();
             BroadcastStats bs = inflight.mBroadcastStats;
             bs.nesting--;
             if (bs.nesting <= 0) {
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 6431344..4664601 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -878,7 +878,8 @@
             }
 
             traceBeginAndSlog("StartAlarmManagerService");
-            mSystemServiceManager.startService(AlarmManagerService.class);
+            mSystemServiceManager.startService(new AlarmManagerService(context));
+
             traceEnd();
 
             traceBeginAndSlog("InitWatchdog");
diff --git a/services/tests/mockingservicestests/Android.mk b/services/tests/mockingservicestests/Android.mk
index 34de9df..8c02833 100644
--- a/services/tests/mockingservicestests/Android.mk
+++ b/services/tests/mockingservicestests/Android.mk
@@ -20,22 +20,21 @@
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
 LOCAL_STATIC_JAVA_LIBRARIES := \
-    services.core \
-    services.devicepolicy \
     frameworks-base-testutils \
+    services.core \
     androidx-test \
     mockito-target-extended-minus-junit4 \
+    platform-test-annotations \
     ShortcutManagerTestUtils \
-    compatibility-device-util \
-    truth-prebuilt
+    truth-prebuilt \
 
-LOCAL_JAVA_LIBRARIES := \
-    android.test.mock
+LOCAL_JAVA_LIBRARIES := android.test.mock android.test.base android.test.runner
 
 LOCAL_JNI_SHARED_LIBRARIES := \
     libdexmakerjvmtiagent \
-    libstaticjvmtiagent
+    libstaticjvmtiagent \
 
+LOCAL_CERTIFICATE := platform
 LOCAL_PACKAGE_NAME := FrameworksMockingServicesTests
 LOCAL_PRIVATE_PLATFORM_APIS := true
 LOCAL_COMPATIBILITY_SUITE := device-tests
diff --git a/services/tests/mockingservicestests/AndroidManifest.xml b/services/tests/mockingservicestests/AndroidManifest.xml
index 247e446..c9aa631 100644
--- a/services/tests/mockingservicestests/AndroidManifest.xml
+++ b/services/tests/mockingservicestests/AndroidManifest.xml
@@ -17,7 +17,10 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
         package="com.android.frameworks.mockingservicestests">
 
-    <application android:debuggable="true">
+    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
+
+    <application android:testOnly="true"
+                 android:debuggable="true">
         <uses-library android:name="android.test.runner" />
     </application>
 
diff --git a/services/tests/mockingservicestests/AndroidTest.xml b/services/tests/mockingservicestests/AndroidTest.xml
index adfee96..7782d57 100644
--- a/services/tests/mockingservicestests/AndroidTest.xml
+++ b/services/tests/mockingservicestests/AndroidTest.xml
@@ -19,6 +19,7 @@
 
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
+        <option name="install-arg" value="-t" />
         <option name="test-file-name" value="FrameworksMockingServicesTests.apk" />
     </target_preparer>
 
diff --git a/services/tests/mockingservicestests/src/com/android/server/AlarmManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/AlarmManagerServiceTest.java
new file mode 100644
index 0000000..de3d285
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/AlarmManagerServiceTest.java
@@ -0,0 +1,374 @@
+/*
+ * 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;
+
+import static android.app.AlarmManager.ELAPSED_REALTIME_WAKEUP;
+import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_ACTIVE;
+import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_FREQUENT;
+import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_RARE;
+import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_WORKING_SET;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.timeout;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isNull;
+
+import android.app.ActivityManager;
+import android.app.AlarmManager;
+import android.app.IActivityManager;
+import android.app.IUidObserver;
+import android.app.PendingIntent;
+import android.app.usage.UsageStatsManagerInternal;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.PowerManager;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Answers;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoSession;
+
+import javax.annotation.concurrent.GuardedBy;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class AlarmManagerServiceTest {
+    private static final String TAG = AlarmManagerServiceTest.class.getSimpleName();
+    private static final String TEST_CALLING_PACKAGE = "com.android.framework.test-package";
+    private static final int SYSTEM_UI_UID = 123456789;
+    private static final int TEST_CALLING_UID = 12345;
+    private static final long DEFAULT_TIMEOUT = 5_000;
+
+    private AlarmManagerService mService;
+    @Mock
+    private IActivityManager mIActivityManager;
+    @Mock
+    private UsageStatsManagerInternal mUsageStatsManagerInternal;
+    @Mock
+    private AppStateTracker mAppStateTracker;
+    @Mock
+    private AlarmManagerService.ClockReceiver mClockReceiver;
+    @Mock
+    private PowerManager.WakeLock mWakeLock;
+
+    private MockitoSession mMockingSession;
+    private Injector mInjector;
+    private volatile long mNowElapsedTest;
+    @GuardedBy("mTestTimer")
+    private TestTimer mTestTimer = new TestTimer();
+
+    static class TestTimer {
+        private long mElapsed;
+        boolean mExpired;
+
+        synchronized long getElapsed() {
+            return mElapsed;
+        }
+
+        synchronized void set(long millisElapsed) {
+            mElapsed = millisElapsed;
+        }
+
+        synchronized long expire() {
+            mExpired = true;
+            notify();
+            return mElapsed;
+        }
+    }
+
+    public class Injector extends AlarmManagerService.Injector {
+        Injector(Context context) {
+            super(context);
+        }
+
+        @Override
+        void init() {
+            // Do nothing.
+        }
+
+        @Override
+        int waitForAlarm() {
+            synchronized (mTestTimer) {
+                if (!mTestTimer.mExpired) {
+                    try {
+                        mTestTimer.wait();
+                    } catch (InterruptedException ie) {
+                        Log.e(TAG, "Wait interrupted!", ie);
+                        return 0;
+                    }
+                }
+                mTestTimer.mExpired = false;
+            }
+            return AlarmManagerService.IS_WAKEUP_MASK; // Doesn't matter, just evaluate.
+        }
+
+        @Override
+        void setKernelTimezone(int minutesWest) {
+            // Do nothing.
+        }
+
+        @Override
+        void setAlarm(int type, long millis) {
+            mTestTimer.set(millis);
+        }
+
+        @Override
+        void setKernelTime(long millis) {
+        }
+
+        @Override
+        int getSystemUiUid() {
+            return SYSTEM_UI_UID;
+        }
+
+        @Override
+        boolean isAlarmDriverPresent() {
+            // Pretend the driver is present, so code does not fall back to handler
+            return true;
+        }
+
+        @Override
+        long getElapsedRealtime() {
+            return mNowElapsedTest;
+        }
+
+        @Override
+        AlarmManagerService.ClockReceiver getClockReceiver(AlarmManagerService service) {
+            return mClockReceiver;
+        }
+
+        @Override
+        PowerManager.WakeLock getAlarmWakeLock() {
+            return mWakeLock;
+        }
+    }
+
+    @Before
+    public final void setUp() throws Exception {
+        mMockingSession = mockitoSession()
+                .initMocks(this)
+                .mockStatic(ActivityManager.class, Answers.CALLS_REAL_METHODS)
+                .mockStatic(LocalServices.class)
+                .mockStatic(Looper.class, Answers.CALLS_REAL_METHODS)
+                .startMocking();
+        doReturn(mIActivityManager).when(ActivityManager::getService);
+        doReturn(mAppStateTracker).when(() -> LocalServices.getService(AppStateTracker.class));
+        doReturn(null)
+                .when(() -> LocalServices.getService(DeviceIdleController.LocalService.class));
+        doReturn(mUsageStatsManagerInternal).when(
+                () -> LocalServices.getService(UsageStatsManagerInternal.class));
+        when(mUsageStatsManagerInternal.getAppStandbyBucket(eq(TEST_CALLING_PACKAGE),
+                eq(UserHandle.getUserId(TEST_CALLING_UID)), anyLong()))
+                .thenReturn(STANDBY_BUCKET_ACTIVE);
+        doReturn(Looper.getMainLooper()).when(Looper::myLooper);
+
+        final Context context = InstrumentationRegistry.getTargetContext();
+        mInjector = spy(new Injector(context));
+        mService = new AlarmManagerService(context, mInjector);
+        spyOn(mService);
+        doNothing().when(mService).publishBinderService(any(), any());
+        mService.onStart();
+        mService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY);
+        mService.mConstants.MIN_FUTURITY = 0;
+
+        assertEquals(mService.mSystemUiUid, SYSTEM_UI_UID);
+        assertEquals(mService.mClockReceiver, mClockReceiver);
+        assertEquals(mService.mWakeLock, mWakeLock);
+        verify(mIActivityManager).registerUidObserver(any(IUidObserver.class), anyInt(), anyInt(),
+                isNull());
+    }
+
+    private void setTestAlarm(int type, long triggerTime, PendingIntent operation) {
+        mService.setImpl(type, triggerTime, AlarmManager.WINDOW_EXACT, 0,
+                operation, null, "test", AlarmManager.FLAG_STANDALONE, null, null,
+                TEST_CALLING_UID, TEST_CALLING_PACKAGE);
+    }
+
+    private PendingIntent getNewMockPendingIntent() {
+        final PendingIntent mockPi = mock(PendingIntent.class, Answers.RETURNS_DEEP_STUBS);
+        when(mockPi.getCreatorUid()).thenReturn(TEST_CALLING_UID);
+        when(mockPi.getCreatorPackage()).thenReturn(TEST_CALLING_PACKAGE);
+        return mockPi;
+    }
+
+    @Test
+    public void testSingleAlarmSet() {
+        final long triggerTime = mNowElapsedTest + 5000;
+        final PendingIntent alarmPi = getNewMockPendingIntent();
+        setTestAlarm(ELAPSED_REALTIME_WAKEUP, triggerTime, alarmPi);
+        verify(mInjector).setAlarm(ELAPSED_REALTIME_WAKEUP, triggerTime);
+        assertEquals(triggerTime, mTestTimer.getElapsed());
+    }
+
+    @Test
+    public void testSingleAlarmExpiration() throws Exception {
+        final long triggerTime = mNowElapsedTest + 5000;
+        final PendingIntent alarmPi = getNewMockPendingIntent();
+        setTestAlarm(ELAPSED_REALTIME_WAKEUP, triggerTime, alarmPi);
+
+        mNowElapsedTest = mTestTimer.expire();
+
+        final ArgumentCaptor<PendingIntent.OnFinished> onFinishedCaptor =
+                ArgumentCaptor.forClass(PendingIntent.OnFinished.class);
+        verify(alarmPi, timeout(DEFAULT_TIMEOUT)).send(any(Context.class), eq(0),
+                any(Intent.class), onFinishedCaptor.capture(), any(Handler.class), isNull(), any());
+        verify(mWakeLock, timeout(DEFAULT_TIMEOUT)).acquire();
+        onFinishedCaptor.getValue().onSendFinished(alarmPi, null, 0, null, null);
+        verify(mWakeLock, timeout(DEFAULT_TIMEOUT)).release();
+    }
+
+    @Test
+    public void testMinFuturity() {
+        mService.mConstants.MIN_FUTURITY = 10;
+        final long triggerTime = mNowElapsedTest + 1;
+        final long expectedTriggerTime = mNowElapsedTest + mService.mConstants.MIN_FUTURITY;
+        setTestAlarm(ELAPSED_REALTIME_WAKEUP, triggerTime, getNewMockPendingIntent());
+        verify(mInjector).setAlarm(ELAPSED_REALTIME_WAKEUP, expectedTriggerTime);
+    }
+
+    @Test
+    public void testEarliestAlarmSet() {
+        final PendingIntent pi6 = getNewMockPendingIntent();
+        final PendingIntent pi8 = getNewMockPendingIntent();
+        final PendingIntent pi9 = getNewMockPendingIntent();
+
+        setTestAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 8, pi8);
+        assertEquals(mNowElapsedTest + 8, mTestTimer.getElapsed());
+
+        setTestAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 9, pi9);
+        assertEquals(mNowElapsedTest + 8, mTestTimer.getElapsed());
+
+        setTestAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 6, pi6);
+        assertEquals(mNowElapsedTest + 6, mTestTimer.getElapsed());
+
+        mService.removeLocked(pi6, null);
+        assertEquals(mNowElapsedTest + 8, mTestTimer.getElapsed());
+
+        mService.removeLocked(pi8, null);
+        assertEquals(mNowElapsedTest + 9, mTestTimer.getElapsed());
+    }
+
+    @Test
+    public void testStandbyBucketDelay_workingSet() throws Exception {
+        setTestAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 5, getNewMockPendingIntent());
+        setTestAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 6, getNewMockPendingIntent());
+        assertEquals(mNowElapsedTest + 5, mTestTimer.getElapsed());
+
+        when(mUsageStatsManagerInternal.getAppStandbyBucket(eq(TEST_CALLING_PACKAGE), anyInt(),
+                anyLong())).thenReturn(STANDBY_BUCKET_WORKING_SET);
+        mNowElapsedTest = mTestTimer.expire();
+        verify(mUsageStatsManagerInternal, timeout(DEFAULT_TIMEOUT).atLeastOnce())
+                .getAppStandbyBucket(eq(TEST_CALLING_PACKAGE),
+                        eq(UserHandle.getUserId(TEST_CALLING_UID)), anyLong());
+        final long expectedNextTrigger = mNowElapsedTest
+                + mService.getMinDelayForBucketLocked(STANDBY_BUCKET_WORKING_SET);
+        assertTrue("Incorrect next alarm trigger. Expected " + expectedNextTrigger + " found: "
+                + mTestTimer.getElapsed(), pollingCheck(DEFAULT_TIMEOUT,
+                () -> (mTestTimer.getElapsed() == expectedNextTrigger)));
+    }
+
+    @Test
+    public void testStandbyBucketDelay_frequent() {
+        setTestAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 5, getNewMockPendingIntent());
+        setTestAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 6, getNewMockPendingIntent());
+        assertEquals(mNowElapsedTest + 5, mTestTimer.getElapsed());
+
+        when(mUsageStatsManagerInternal.getAppStandbyBucket(eq(TEST_CALLING_PACKAGE), anyInt(),
+                anyLong())).thenReturn(STANDBY_BUCKET_FREQUENT);
+        mNowElapsedTest = mTestTimer.expire();
+        verify(mUsageStatsManagerInternal, timeout(DEFAULT_TIMEOUT).atLeastOnce())
+                .getAppStandbyBucket(eq(TEST_CALLING_PACKAGE),
+                        eq(UserHandle.getUserId(TEST_CALLING_UID)), anyLong());
+        final long expectedNextTrigger = mNowElapsedTest
+                + mService.getMinDelayForBucketLocked(STANDBY_BUCKET_FREQUENT);
+        assertTrue("Incorrect next alarm trigger. Expected " + expectedNextTrigger + " found: "
+                + mTestTimer.getElapsed(), pollingCheck(DEFAULT_TIMEOUT,
+                () -> (mTestTimer.getElapsed() == expectedNextTrigger)));
+    }
+
+    @Test
+    public void testStandbyBucketDelay_rare() {
+        setTestAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 5, getNewMockPendingIntent());
+        setTestAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 6, getNewMockPendingIntent());
+        assertEquals(mNowElapsedTest + 5, mTestTimer.getElapsed());
+
+        when(mUsageStatsManagerInternal.getAppStandbyBucket(eq(TEST_CALLING_PACKAGE), anyInt(),
+                anyLong())).thenReturn(STANDBY_BUCKET_RARE);
+        mNowElapsedTest = mTestTimer.expire();
+        verify(mUsageStatsManagerInternal, timeout(DEFAULT_TIMEOUT).atLeastOnce())
+                .getAppStandbyBucket(eq(TEST_CALLING_PACKAGE),
+                        eq(UserHandle.getUserId(TEST_CALLING_UID)), anyLong());
+        final long expectedNextTrigger = mNowElapsedTest
+                + mService.getMinDelayForBucketLocked(STANDBY_BUCKET_RARE);
+        assertTrue("Incorrect next alarm trigger. Expected " + expectedNextTrigger + " found: "
+                + mTestTimer.getElapsed(), pollingCheck(DEFAULT_TIMEOUT,
+                () -> (mTestTimer.getElapsed() == expectedNextTrigger)));
+    }
+
+    @After
+    public void tearDown() {
+        if (mMockingSession != null) {
+            mMockingSession.finishMocking();
+        }
+    }
+
+    private boolean pollingCheck(long timeout, Condition condition) {
+        final long deadline = SystemClock.uptimeMillis() + timeout;
+        boolean interrupted = false;
+        while (!condition.check() && SystemClock.uptimeMillis() < deadline) {
+            try {
+                Thread.sleep(500);
+            } catch (InterruptedException ie) {
+                interrupted = true;
+            }
+        }
+        if (interrupted) {
+            Thread.currentThread().interrupt();
+        }
+        return condition.check();
+    }
+
+    @FunctionalInterface
+    interface Condition {
+        boolean check();
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/AlarmManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/BackgroundRestrictedAlarmsTest.java
similarity index 98%
rename from services/tests/servicestests/src/com/android/server/AlarmManagerServiceTest.java
rename to services/tests/servicestests/src/com/android/server/BackgroundRestrictedAlarmsTest.java
index 1f63d61..d248b89 100644
--- a/services/tests/servicestests/src/com/android/server/AlarmManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/BackgroundRestrictedAlarmsTest.java
@@ -35,14 +35,13 @@
 
 @SmallTest
 @RunWith(AndroidJUnit4.class)
-public class AlarmManagerServiceTest {
+public class BackgroundRestrictedAlarmsTest {
     private SparseArray<ArrayList<Alarm>> addPendingAlarm(
             SparseArray<ArrayList<Alarm>> all, int uid, String name, boolean removeIt) {
         ArrayList<Alarm> uidAlarms = all.get(uid);
         if (uidAlarms == null) {
             all.put(uid, uidAlarms = new ArrayList<>());
         }
-        // Details don't matter.
         uidAlarms.add(new Alarm(
                 removeIt ? RTC : RTC_WAKEUP,
                 0, 0, 0, 0, 0, null, null, null, null, 0, null, uid, name));