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) {