Implement some control over ALLOW_WHILE_IDLE alarms.

Since these alarms allow you to bypass the idle restrictions,
we don't want them to be so open-ended like other alarms.  This
implements a policy where the alarm manager will only deliver these
types of alarms every X minutes to each application.  For this
initial implementation, X is 1 minute under normal operation and
15 minutes when in idle mode.

To do this, I needed to introduce a new internal allow-while-idle
flag for system alarms, which applications can't get, and doesn't
have these new restrictions.

Also tweaked how the alarm manager handles the alarm window, so it
doesn't change if the alarm gets rescheduld; the window is now always
what as computed based on the time when the alarm was first
given to it.

Finally, fix TimeUtils to be able to correctly print times that
are > 999 days.

Change-Id: Ibad8c6a7c14b0624b54e82267be23224b4c31e84
diff --git a/core/java/android/app/AlarmManager.java b/core/java/android/app/AlarmManager.java
index 5e7bd0d..9ea1606 100644
--- a/core/java/android/app/AlarmManager.java
+++ b/core/java/android/app/AlarmManager.java
@@ -142,13 +142,22 @@
     public static final int FLAG_ALLOW_WHILE_IDLE = 1<<2;
 
     /**
+     * Flag for alarms: same as {@link #FLAG_ALLOW_WHILE_IDLE}, but doesn't have restrictions
+     * on how frequently it can be scheduled.  Only available (and automatically applied) to
+     * system alarms.
+     *
+     * @hide
+     */
+    public static final int FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED = 1<<3;
+
+    /**
      * Flag for alarms: this alarm marks the point where we would like to come out of idle
      * mode.  It may be moved by the alarm manager to match the first wake-from-idle alarm.
      * Scheduling an alarm with this flag puts the alarm manager in to idle mode, where it
      * avoids scheduling any further alarms until the marker alarm is executed.
      * @hide
      */
-    public static final int FLAG_IDLE_UNTIL = 1<<3;
+    public static final int FLAG_IDLE_UNTIL = 1<<4;
 
     private final IAlarmManager mService;
     private final boolean mAlwaysExact;
@@ -565,6 +574,12 @@
      * of the device when idle (and thus cause significant battery blame to the app scheduling
      * them), so they should be used with care.
      *
+     * <p>To reduce abuse, there are restrictions on how frequently these alarms will go off
+     * for a particular application.  Under normal system operation, it will not dispatch these
+     * alarms more than about every minute (at which point every such pending alarm is
+     * dispatched); when in low-power idle modes this duration may be significantly longer,
+     * such as 15 minutes.</p>
+     *
      * <p>Unlike other alarms, the system is free to reschedule this type of alarm to happen
      * out of order with any other alarms, even those from the same app.  This will clearly happen
      * when the device is idle (since this alarm can go off while idle, when any other alarms
@@ -608,6 +623,12 @@
      * of the device when idle (and thus cause significant battery blame to the app scheduling
      * them), so they should be used with care.
      *
+     * <p>To reduce abuse, there are restrictions on how frequently these alarms will go off
+     * for a particular application.  Under normal system operation, it will not dispatch these
+     * alarms more than about every minute (at which point every such pending alarm is
+     * dispatched); when in low-power idle modes this duration may be significantly longer,
+     * such as 15 minutes.</p>
+     *
      * <p>Unlike other alarms, the system is free to reschedule this type of alarm to happen
      * out of order with any other alarms, even those from the same app.  This will clearly happen
      * when the device is idle (since this alarm can go off while idle, when any other alarms
diff --git a/core/java/android/util/TimeUtils.java b/core/java/android/util/TimeUtils.java
index f7d2821..353388d 100644
--- a/core/java/android/util/TimeUtils.java
+++ b/core/java/android/util/TimeUtils.java
@@ -246,41 +246,65 @@
     public static final long NANOS_PER_MS = 1000000;
 
     private static final Object sFormatSync = new Object();
-    private static char[] sFormatStr = new char[HUNDRED_DAY_FIELD_LEN+5];
-
-    private static final long LARGEST_DURATION = (1000 * DateUtils.DAY_IN_MILLIS) - 1;
+    private static char[] sFormatStr = new char[HUNDRED_DAY_FIELD_LEN+10];
+    private static char[] sTmpFormatStr = new char[HUNDRED_DAY_FIELD_LEN+10];
 
     static private int accumField(int amt, int suffix, boolean always, int zeropad) {
-        if (amt > 99 || (always && zeropad >= 3)) {
-            return 3+suffix;
-        }
-        if (amt > 9 || (always && zeropad >= 2)) {
-            return 2+suffix;
-        }
-        if (always || amt > 0) {
-            return 1+suffix;
+        if (amt > 999) {
+            int num = 0;
+            while (amt != 0) {
+                num++;
+                amt /= 10;
+            }
+            return num + suffix;
+        } else {
+            if (amt > 99 || (always && zeropad >= 3)) {
+                return 3+suffix;
+            }
+            if (amt > 9 || (always && zeropad >= 2)) {
+                return 2+suffix;
+            }
+            if (always || amt > 0) {
+                return 1+suffix;
+            }
         }
         return 0;
     }
 
-    static private int printField(char[] formatStr, int amt, char suffix, int pos,
+    static private int printFieldLocked(char[] formatStr, int amt, char suffix, int pos,
             boolean always, int zeropad) {
         if (always || amt > 0) {
             final int startPos = pos;
-            if ((always && zeropad >= 3) || amt > 99) {
-                int dig = amt/100;
-                formatStr[pos] = (char)(dig + '0');
+            if (amt > 999) {
+                int tmp = 0;
+                while (amt != 0 && tmp < sTmpFormatStr.length) {
+                    int dig = amt % 10;
+                    sTmpFormatStr[tmp] = (char)(dig + '0');
+                    tmp++;
+                    amt /= 10;
+                }
+                tmp--;
+                while (tmp >= 0) {
+                    formatStr[pos] = sTmpFormatStr[tmp];
+                    pos++;
+                    tmp--;
+                }
+            } else {
+                if ((always && zeropad >= 3) || amt > 99) {
+                    int dig = amt/100;
+                    formatStr[pos] = (char)(dig + '0');
+                    pos++;
+                    amt -= (dig*100);
+                }
+                if ((always && zeropad >= 2) || amt > 9 || startPos != pos) {
+                    int dig = amt/10;
+                    formatStr[pos] = (char)(dig + '0');
+                    pos++;
+                    amt -= (dig*10);
+                }
+                formatStr[pos] = (char)(amt + '0');
                 pos++;
-                amt -= (dig*100);
             }
-            if ((always && zeropad >= 2) || amt > 9 || startPos != pos) {
-                int dig = amt/10;
-                formatStr[pos] = (char)(dig + '0');
-                pos++;
-                amt -= (dig*10);
-            }
-            formatStr[pos] = (char)(amt + '0');
-            pos++;
             formatStr[pos] = suffix;
             pos++;
         }
@@ -312,10 +336,6 @@
             duration = -duration;
         }
 
-        if (duration > LARGEST_DURATION) {
-            duration = LARGEST_DURATION;
-        }
-
         int millis = (int)(duration%1000);
         int seconds = (int) Math.floor(duration / 1000);
         int days = 0, hours = 0, minutes = 0;
@@ -353,11 +373,11 @@
 
         int start = pos;
         boolean zeropad = fieldLen != 0;
-        pos = printField(formatStr, days, 'd', pos, false, 0);
-        pos = printField(formatStr, hours, 'h', pos, pos != start, zeropad ? 2 : 0);
-        pos = printField(formatStr, minutes, 'm', pos, pos != start, zeropad ? 2 : 0);
-        pos = printField(formatStr, seconds, 's', pos, pos != start, zeropad ? 2 : 0);
-        pos = printField(formatStr, millis, 'm', pos, true, (zeropad && pos != start) ? 3 : 0);
+        pos = printFieldLocked(formatStr, days, 'd', pos, false, 0);
+        pos = printFieldLocked(formatStr, hours, 'h', pos, pos != start, zeropad ? 2 : 0);
+        pos = printFieldLocked(formatStr, minutes, 'm', pos, pos != start, zeropad ? 2 : 0);
+        pos = printFieldLocked(formatStr, seconds, 's', pos, pos != start, zeropad ? 2 : 0);
+        pos = printFieldLocked(formatStr, millis, 'm', pos, true, (zeropad && pos != start) ? 3 : 0);
         formatStr[pos] = 's';
         return pos + 1;
     }
diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java
index 742f570..26ece72 100644
--- a/services/core/java/com/android/server/AlarmManagerService.java
+++ b/services/core/java/com/android/server/AlarmManagerService.java
@@ -47,6 +47,7 @@
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.SparseBooleanArray;
+import android.util.SparseLongArray;
 import android.util.TimeUtils;
 
 import java.io.ByteArrayOutputStream;
@@ -84,6 +85,12 @@
     // Minimum alarm recurrence interval
     private static final long MIN_INTERVAL = 60 * 1000;  // one minute, in millis
 
+    // Minimum time between ALLOW_WHILE_IDLE alarms when system is not idle.
+    private static final long ALLOW_WHILE_IDLE_SHORT_TIME = 60*1000;
+
+    // Minimum time between ALLOW_WHILE_IDLE alarms when system is idling.
+    private static final long ALLOW_WHILE_IDLE_LONG_TIME = 15*60*1000;
+
     private static final int RTC_WAKEUP_MASK = 1 << RTC_WAKEUP;
     private static final int RTC_MASK = 1 << RTC;
     private static final int ELAPSED_REALTIME_WAKEUP_MASK = 1 << ELAPSED_REALTIME_WAKEUP;
@@ -123,8 +130,8 @@
     int mBroadcastRefCount = 0;
     PowerManager.WakeLock mWakeLock;
     boolean mLastWakeLockUnimportantForLogging;
-    ArrayList<Alarm> mPendingNonWakeupAlarms = new ArrayList<Alarm>();
-    ArrayList<InFlight> mInFlight = new ArrayList<InFlight>();
+    ArrayList<Alarm> mPendingNonWakeupAlarms = new ArrayList<>();
+    ArrayList<InFlight> mInFlight = new ArrayList<>();
     final AlarmHandler mHandler = new AlarmHandler();
     ClockReceiver mClockReceiver;
     InteractiveStateReceiver mInteractiveStateReceiver;
@@ -141,8 +148,15 @@
     long mNextNonWakeupDeliveryTime;
     long mLastTimeChangeClockTime;
     long mLastTimeChangeRealtime;
+    long mAllowWhileIdleMinTime = ALLOW_WHILE_IDLE_SHORT_TIME;
     int mNumTimeChanged;
 
+    /**
+     * For each uid, this is the last time we dispatched an "allow while idle" alarm,
+     * used to determine the earliest we can dispatch the next such alarm.
+     */
+    final SparseLongArray mLastAllowWhileIdleDispatch = new SparseLongArray();
+
     private final SparseArray<AlarmManager.AlarmClockInfo> mNextAlarmClockForUser =
             new SparseArray<>();
     private final SparseArray<AlarmManager.AlarmClockInfo> mTmpSparseAlarmClockArray =
@@ -552,7 +566,7 @@
         a.when = a.origWhen;
         long whenElapsed = convertToElapsed(a.when, a.type);
         final long maxElapsed;
-        if (a.whenElapsed == a.maxWhenElapsed) {
+        if (a.windowLength == AlarmManager.WINDOW_EXACT) {
             // Exact
             maxElapsed = whenElapsed;
         } else {
@@ -580,6 +594,9 @@
             }
         }
 
+        // Make sure we are using the correct ALLOW_WHILE_IDLE min time.
+        mAllowWhileIdleMinTime = ALLOW_WHILE_IDLE_SHORT_TIME;
+
         // Reschedule everything.
         rescheduleKernelAlarmsLocked();
         updateNextAlarmClockLocked();
@@ -632,7 +649,7 @@
             mTag = tag;
         }
     }
-    
+
     static final class BroadcastStats {
         final int mUid;
         final String mPackageName;
@@ -649,7 +666,7 @@
             mPackageName = packageName;
         }
     }
-    
+
     final SparseArray<ArrayMap<String, BroadcastStats>> mBroadcastStats
             = new SparseArray<ArrayMap<String, BroadcastStats>>();
 
@@ -751,7 +768,7 @@
 
     void setImpl(int type, long triggerAtTime, long windowLength, long interval,
             PendingIntent operation, int flags, WorkSource workSource,
-            AlarmManager.AlarmClockInfo alarmClock) {
+            AlarmManager.AlarmClockInfo alarmClock, int callingUid) {
         if (operation == null) {
             Slog.w(TAG, "set/setRepeating ignored because there is no intent");
             return;
@@ -779,9 +796,8 @@
         }
 
         if (triggerAtTime < 0) {
-            final long who = Binder.getCallingUid();
             final long what = Binder.getCallingPid();
-            Slog.w(TAG, "Invalid alarm trigger time! " + triggerAtTime + " from uid=" + who
+            Slog.w(TAG, "Invalid alarm trigger time! " + triggerAtTime + " from uid=" + callingUid
                     + " pid=" + what);
             triggerAtTime = 0;
         }
@@ -797,12 +813,12 @@
             maxElapsed = triggerElapsed;
         } else if (windowLength < 0) {
             maxElapsed = maxTriggerTime(nowElapsed, triggerElapsed, interval);
+            // Fix this window in place, so that as time approaches we don't collapse it.
+            windowLength = maxElapsed - triggerElapsed;
         } else {
             maxElapsed = triggerElapsed + windowLength;
         }
 
-        final int userId = UserHandle.getCallingUserId();
-
         synchronized (mLock) {
             if (DEBUG_BATCH) {
                 Slog.v(TAG, "set(" + operation + ") : type=" + type
@@ -811,26 +827,20 @@
                         + " interval=" + interval + " flags=0x" + Integer.toHexString(flags));
             }
             setImplLocked(type, triggerAtTime, triggerElapsed, windowLength, maxElapsed,
-                    interval, operation, flags, true, workSource, alarmClock, userId);
+                    interval, operation, flags, true, workSource, alarmClock, callingUid);
         }
     }
 
     private void setImplLocked(int type, long when, long whenElapsed, long windowLength,
             long maxWhen, long interval, PendingIntent operation, int flags,
             boolean doValidate, WorkSource workSource, AlarmManager.AlarmClockInfo alarmClock,
-            int userId) {
+            int uid) {
         Alarm a = new Alarm(type, when, whenElapsed, windowLength, maxWhen, interval,
-                operation, workSource, flags, alarmClock, userId);
+                operation, workSource, flags, alarmClock, uid);
         removeLocked(operation);
         setImplLocked(a, false, doValidate);
     }
 
-    private void updateNextWakeFromIdleFuzzLocked() {
-        if (mNextWakeFromIdle != null) {
-
-        }
-    }
-
     private void setImplLocked(Alarm a, boolean rebatching, boolean doValidate) {
         if ((a.flags&AlarmManager.FLAG_IDLE_UNTIL) != 0) {
             // This is a special alarm that will put the system into idle until it goes off.
@@ -862,7 +872,9 @@
         } else if (mPendingIdleUntil != null) {
             // We currently have an idle until alarm scheduled; if the new alarm has
             // not explicitly stated it wants to run while idle, then put it on hold.
-            if ((a.flags&(AlarmManager.FLAG_ALLOW_WHILE_IDLE|AlarmManager.FLAG_WAKE_FROM_IDLE))
+            if ((a.flags&(AlarmManager.FLAG_ALLOW_WHILE_IDLE
+                    | AlarmManager.FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED
+                    | AlarmManager.FLAG_WAKE_FROM_IDLE))
                     == 0) {
                 mPendingWhileIdleAlarms.add(a);
                 return;
@@ -892,6 +904,7 @@
 
         if ((a.flags&AlarmManager.FLAG_IDLE_UNTIL) != 0) {
             mPendingIdleUntil = a;
+            mAllowWhileIdleMinTime = ALLOW_WHILE_IDLE_LONG_TIME;
             needRebatch = true;
         } else if ((a.flags&AlarmManager.FLAG_WAKE_FROM_IDLE) != 0) {
             if (mNextWakeFromIdle == null || mNextWakeFromIdle.whenElapsed > a.whenElapsed) {
@@ -933,23 +946,45 @@
         public void set(int type, long triggerAtTime, long windowLength, long interval, int flags,
                 PendingIntent operation, WorkSource workSource,
                 AlarmManager.AlarmClockInfo alarmClock) {
+            final int callingUid = Binder.getCallingUid();
             if (workSource != null) {
-                getContext().enforceCallingPermission(
+                getContext().enforcePermission(
                         android.Manifest.permission.UPDATE_DEVICE_STATS,
-                        "AlarmManager.set");
+                        Binder.getCallingPid(), callingUid, "AlarmManager.set");
             }
 
+            // No incoming callers can request either WAKE_FROM_IDLE or
+            // ALLOW_WHILE_IDLE_UNRESTRICTED -- we will apply those later as appropriate.
+            flags &= ~(AlarmManager.FLAG_WAKE_FROM_IDLE
+                    | AlarmManager.FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED);
+
+            // Only the system can use FLAG_IDLE_UNTIL -- this is used to tell the alarm
+            // manager when to come out of idle mode, which is only for DeviceIdleController.
+            if (callingUid != Process.SYSTEM_UID) {
+                flags &= ~AlarmManager.FLAG_IDLE_UNTIL;
+            }
+
+            // If the caller is a core system component, and not calling to do work on behalf
+            // of someone else, then always set ALLOW_WHILE_IDLE_UNRESTRICTED.  This means we
+            // will allow these alarms to go off as normal even while idle, with no timing
+            // restrictions.
+            if (callingUid < Process.FIRST_APPLICATION_UID && workSource == null) {
+                flags |= AlarmManager.FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED;
+            }
+
+            // If this is an exact time alarm, then it can't be batched with other alarms.
             if (windowLength == AlarmManager.WINDOW_EXACT) {
                 flags |= AlarmManager.FLAG_STANDALONE;
             }
+
+            // If this alarm is for an alarm clock, then it must be standalone and we will
+            // use it to wake early from idle if needed.
             if (alarmClock != null) {
                 flags |= AlarmManager.FLAG_WAKE_FROM_IDLE | AlarmManager.FLAG_STANDALONE;
             }
-            if (Binder.getCallingUid() < Process.FIRST_APPLICATION_UID) {
-                flags |= AlarmManager.FLAG_ALLOW_WHILE_IDLE;
-            }
+
             setImpl(type, triggerAtTime, windowLength, interval, operation,
-                    flags, workSource, alarmClock);
+                    flags, workSource, alarmClock, callingUid);
         }
 
         @Override
@@ -1126,6 +1161,22 @@
             pw.print("  Broadcast ref count: "); pw.println(mBroadcastRefCount);
             pw.println();
 
+            pw.print("mAllowWhileIdleMinTime=");
+            TimeUtils.formatDuration(mAllowWhileIdleMinTime, pw);
+            pw.println();
+            if (mLastAllowWhileIdleDispatch.size() > 0) {
+                pw.println("Last allow while idle dispatch times:");
+                for (int i=0; i<mLastAllowWhileIdleDispatch.size(); i++) {
+                    pw.print("  UID ");
+                    UserHandle.formatUid(pw, mLastAllowWhileIdleDispatch.keyAt(i));
+                    pw.print(": ");
+                    TimeUtils.formatDuration(mLastAllowWhileIdleDispatch.valueAt(i),
+                            nowELAPSED, pw);
+                    pw.println();
+                }
+            }
+            pw.println();
+
             if (mLog.dump(pw, "  Recent problems", "    ")) {
                 pw.println();
             }
@@ -1322,7 +1373,7 @@
             for (int j = 0; j < M; j++) {
                 Alarm a = alarms.get(j);
                 if (a.alarmClock != null) {
-                    final int userId = a.userId;
+                    final int userId = UserHandle.getUserId(a.uid);
 
                     if (DEBUG_ALARM_CLOCK) {
                         Log.v(TAG, "Found AlarmClockInfo at " +
@@ -1531,6 +1582,11 @@
                 mPendingWhileIdleAlarms.remove(i);
             }
         }
+        for (int i = mLastAllowWhileIdleDispatch.size() - 1; i >= 0; i--) {
+            if (UserHandle.getUserId(mLastAllowWhileIdleDispatch.keyAt(i)) == userHandle) {
+                mLastAllowWhileIdleDispatch.removeAt(i);
+            }
+        }
 
         if (didRemove) {
             if (DEBUG_BATCH) {
@@ -1666,6 +1722,25 @@
             final int N = batch.size();
             for (int i = 0; i < N; i++) {
                 Alarm alarm = batch.get(i);
+
+                if ((alarm.flags&AlarmManager.FLAG_ALLOW_WHILE_IDLE) != 0) {
+                    // If this is an ALLOW_WHILE_IDLE alarm, we constrain how frequently the app can
+                    // schedule such alarms.
+                    long lastTime = mLastAllowWhileIdleDispatch.get(alarm.uid, 0);
+                    long minTime = lastTime + mAllowWhileIdleMinTime;
+                    if (nowELAPSED < minTime) {
+                        // Whoops, it hasn't been long enough since the last ALLOW_WHILE_IDLE
+                        // alarm went off for this app.  Reschedule the alarm to be in the
+                        // correct time period.
+                        alarm.whenElapsed = minTime;
+                        if (alarm.maxWhenElapsed < minTime) {
+                            alarm.maxWhenElapsed = minTime;
+                        }
+                        setImplLocked(alarm, true, false);
+                        continue;
+                    }
+                }
+
                 alarm.count = 1;
                 triggerList.add(alarm);
                 if ((alarm.flags&AlarmManager.FLAG_WAKE_FROM_IDLE) != 0) {
@@ -1695,7 +1770,7 @@
                     setImplLocked(alarm.type, alarm.when + delta, nextElapsed, alarm.windowLength,
                             maxTriggerTime(nowELAPSED, nextElapsed, alarm.repeatInterval),
                             alarm.repeatInterval, alarm.operation, alarm.flags, true,
-                            alarm.workSource, alarm.alarmClock, alarm.userId);
+                            alarm.workSource, alarm.alarmClock, alarm.uid);
                 }
 
                 if (alarm.wakeup) {
@@ -1749,19 +1824,19 @@
         public final String tag;
         public final WorkSource workSource;
         public final int flags;
+        public final AlarmManager.AlarmClockInfo alarmClock;
+        public final int uid;
         public int count;
         public long when;
         public long windowLength;
         public long whenElapsed;    // 'when' in the elapsed time base
         public long maxWhenElapsed; // also in the elapsed time base
         public long repeatInterval;
-        public final AlarmManager.AlarmClockInfo alarmClock;
-        public final int userId;
         public PriorityClass priorityClass;
 
         public Alarm(int _type, long _when, long _whenElapsed, long _windowLength, long _maxWhen,
                 long _interval, PendingIntent _op, WorkSource _ws, int _flags,
-                AlarmManager.AlarmClockInfo _info, int _userId) {
+                AlarmManager.AlarmClockInfo _info, int _uid) {
             type = _type;
             origWhen = _when;
             wakeup = _type == AlarmManager.ELAPSED_REALTIME_WAKEUP
@@ -1776,7 +1851,7 @@
             workSource = _ws;
             flags = _flags;
             alarmClock = _info;
-            userId = _userId;
+            uid = _uid;
         }
 
         public static String makeTag(PendingIntent pi, int type) {
@@ -1812,7 +1887,7 @@
                         pw.print(" when="); TimeUtils.formatDuration(when, nowELAPSED, pw);
                     }
                     pw.println();
-            pw.print(prefix); pw.print("window="); pw.print(windowLength);
+            pw.print(prefix); pw.print("window="); TimeUtils.formatDuration(windowLength, pw);
                     pw.print(" repeatInterval="); pw.print(repeatInterval);
                     pw.print(" count="); pw.print(count);
                     pw.print(" flags=0x"); pw.println(Integer.toHexString(flags));
@@ -1925,6 +2000,11 @@
                 mInFlight.add(inflight);
                 mBroadcastRefCount++;
 
+                if ((alarm.flags&AlarmManager.FLAG_ALLOW_WHILE_IDLE) != 0) {
+                    // Record the last time this uid handled an ALLOW_WHILE_IDLE alarm.
+                    mLastAllowWhileIdleDispatch.put(alarm.uid, nowELAPSED);
+                }
+
                 final BroadcastStats bs = inflight.mBroadcastStats;
                 bs.count++;
                 if (bs.nesting == 0) {
@@ -2196,7 +2276,8 @@
 
             final WorkSource workSource = null; // Let system take blame for time tick events.
             setImpl(ELAPSED_REALTIME, SystemClock.elapsedRealtime() + tickEventDelay, 0,
-                    0, mTimeTickSender, AlarmManager.FLAG_STANDALONE, workSource, null);
+                    0, mTimeTickSender, AlarmManager.FLAG_STANDALONE, workSource, null,
+                    Process.myUid());
         }
 
         public void scheduleDateChangedEvent() {
@@ -2210,7 +2291,7 @@
 
             final WorkSource workSource = null; // Let system take blame for date change events.
             setImpl(RTC, calendar.getTimeInMillis(), 0, 0, mDateChangeSender,
-                    AlarmManager.FLAG_STANDALONE, workSource, null);
+                    AlarmManager.FLAG_STANDALONE, workSource, null, Process.myUid());
         }
     }
     
@@ -2243,6 +2324,7 @@
             IntentFilter sdFilter = new IntentFilter();
             sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
             sdFilter.addAction(Intent.ACTION_USER_STOPPED);
+            sdFilter.addAction(Intent.ACTION_UID_REMOVED);
             getContext().registerReceiver(this, sdFilter);
         }
         
@@ -2267,6 +2349,11 @@
                     if (userHandle >= 0) {
                         removeUserLocked(userHandle);
                     }
+                } else if (Intent.ACTION_UID_REMOVED.equals(action)) {
+                    int uid = intent.getIntExtra(Intent.EXTRA_UID, -1);
+                    if (uid >= 0) {
+                        mLastAllowWhileIdleDispatch.delete(uid);
+                    }
                 } else {
                     if (Intent.ACTION_PACKAGE_REMOVED.equals(action)
                             && intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
diff --git a/tests/ActivityTests/AndroidManifest.xml b/tests/ActivityTests/AndroidManifest.xml
index c105491..dae7cc5 100644
--- a/tests/ActivityTests/AndroidManifest.xml
+++ b/tests/ActivityTests/AndroidManifest.xml
@@ -76,5 +76,6 @@
             android:authorities="com.google.android.test.activity.single_user"
             android:singleUser="true" android:exported="true" />
         <receiver android:name="TrackTimeReceiver" />
+        <receiver android:name="AlarmSpamReceiver" />
     </application>
 </manifest>
diff --git a/tests/ActivityTests/src/com/google/android/test/activity/ActivityTestMain.java b/tests/ActivityTests/src/com/google/android/test/activity/ActivityTestMain.java
index ddcfd9e..94cbabf 100644
--- a/tests/ActivityTests/src/com/google/android/test/activity/ActivityTestMain.java
+++ b/tests/ActivityTests/src/com/google/android/test/activity/ActivityTestMain.java
@@ -22,6 +22,7 @@
 import android.app.Activity;
 import android.app.ActivityManager;
 import android.app.ActivityOptions;
+import android.app.AlarmManager;
 import android.app.AlertDialog;
 import android.app.PendingIntent;
 import android.content.ActivityNotFoundException;
@@ -36,6 +37,7 @@
 import android.os.IBinder;
 import android.os.Message;
 import android.os.RemoteException;
+import android.os.SystemClock;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.graphics.Bitmap;
@@ -58,6 +60,7 @@
     static final String KEY_CONFIGURATION = "configuration";
 
     ActivityManager mAm;
+    AlarmManager mAlarm;
     Configuration mOverrideConfig;
     int mSecondUser;
 
@@ -66,6 +69,7 @@
     ServiceConnection mIsolatedConnection;
 
     static final int MSG_SPAM = 1;
+    static final int MSG_SPAM_ALARM = 2;
 
     final Handler mHandler = new Handler() {
         @Override
@@ -82,6 +86,15 @@
                     startActivity(intent, options);
                     scheduleSpam(!fg);
                 } break;
+                case MSG_SPAM_ALARM: {
+                    long when = SystemClock.elapsedRealtime();
+                    Intent intent = new Intent(ActivityTestMain.this, AlarmSpamReceiver.class);
+                    intent.setAction("com.example.SPAM_ALARM=" + when);
+                    PendingIntent pi = PendingIntent.getBroadcast(ActivityTestMain.this,
+                            0, intent, 0);
+                    mAlarm.setAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME, when+(30*1000), pi);
+                    scheduleSpamAlarm(30*1000);
+                } break;
             }
             super.handleMessage(msg);
         }
@@ -146,6 +159,7 @@
         Log.i(TAG, "Referrer: " + getReferrer());
 
         mAm = (ActivityManager)getSystemService(ACTIVITY_SERVICE);
+        mAlarm = (AlarmManager)getSystemService(ALARM_SERVICE);
         if (savedInstanceState != null) {
             mOverrideConfig = savedInstanceState.getParcelable(KEY_CONFIGURATION);
             if (mOverrideConfig != null) {
@@ -436,6 +450,13 @@
                 return true;
             }
         });
+        menu.add("Spam idle alarm").setOnMenuItemClickListener(
+                new MenuItem.OnMenuItemClickListener() {
+            @Override public boolean onMenuItemClick(MenuItem item) {
+                scheduleSpamAlarm(0);
+                return true;
+            }
+        });
         return true;
     }
 
@@ -467,6 +488,7 @@
     @Override
     protected void onStop() {
         super.onStop();
+        mHandler.removeMessages(MSG_SPAM_ALARM);
         for (ServiceConnection conn : mConnections) {
             unbindService(conn);
         }
@@ -536,6 +558,12 @@
         mHandler.sendMessageDelayed(msg, 500);
     }
 
+    void scheduleSpamAlarm(long delay) {
+        mHandler.removeMessages(MSG_SPAM_ALARM);
+        Message msg = mHandler.obtainMessage(MSG_SPAM_ALARM);
+        mHandler.sendMessageDelayed(msg, delay);
+    }
+
     private View scrollWrap(View view) {
         ScrollView scroller = new ScrollView(this);
         scroller.addView(view, new ScrollView.LayoutParams(ScrollView.LayoutParams.MATCH_PARENT,
diff --git a/tests/ActivityTests/src/com/google/android/test/activity/AlarmSpamReceiver.java b/tests/ActivityTests/src/com/google/android/test/activity/AlarmSpamReceiver.java
new file mode 100644
index 0000000..0cb1ffb
--- /dev/null
+++ b/tests/ActivityTests/src/com/google/android/test/activity/AlarmSpamReceiver.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2015 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.google.android.test.activity;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.util.Log;
+
+public class AlarmSpamReceiver extends BroadcastReceiver {
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        Log.i("AlarmSpamReceiver", "Received spam = " + intent);
+    }
+}