summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--core/java/android/app/AlarmManager.java23
-rw-r--r--core/java/android/util/TimeUtils.java86
-rw-r--r--services/core/java/com/android/server/AlarmManagerService.java157
-rw-r--r--tests/ActivityTests/AndroidManifest.xml1
-rw-r--r--tests/ActivityTests/src/com/google/android/test/activity/ActivityTestMain.java28
-rw-r--r--tests/ActivityTests/src/com/google/android/test/activity/AlarmSpamReceiver.java31
6 files changed, 257 insertions, 69 deletions
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 class AlarmManager {
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 @@ public class AlarmManager {
* 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 @@ public class AlarmManager {
* 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 class TimeUtils {
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');
- pos++;
- amt -= (dig*100);
- }
- if ((always && zeropad >= 2) || amt > 9 || startPos != pos) {
- int dig = amt/10;
- 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*10);
}
- formatStr[pos] = (char)(amt + '0');
- pos++;
formatStr[pos] = suffix;
pos++;
}
@@ -312,10 +336,6 @@ public class TimeUtils {
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 @@ public class TimeUtils {
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.Log;
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 @@ class AlarmManagerService extends SystemService {
// 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 @@ class AlarmManagerService extends SystemService {
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 @@ class AlarmManagerService extends SystemService {
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 @@ class AlarmManagerService extends SystemService {
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 @@ class AlarmManagerService extends SystemService {
}
}
+ // 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 @@ class AlarmManagerService extends SystemService {
mTag = tag;
}
}
-
+
static final class BroadcastStats {
final int mUid;
final String mPackageName;
@@ -649,7 +666,7 @@ class AlarmManagerService extends SystemService {
mPackageName = packageName;
}
}
-
+
final SparseArray<ArrayMap<String, BroadcastStats>> mBroadcastStats
= new SparseArray<ArrayMap<String, BroadcastStats>>();
@@ -751,7 +768,7 @@ class AlarmManagerService extends SystemService {
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 @@ class AlarmManagerService extends SystemService {
}
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 @@ class AlarmManagerService extends SystemService {
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 @@ class AlarmManagerService extends SystemService {
+ " 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 @@ class AlarmManagerService extends SystemService {
} 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 @@ class AlarmManagerService extends SystemService {
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 @@ class AlarmManagerService extends SystemService {
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 @@ class AlarmManagerService extends SystemService {
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 @@ class AlarmManagerService extends SystemService {
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 @@ class AlarmManagerService extends SystemService {
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 @@ class AlarmManagerService extends SystemService {
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 @@ class AlarmManagerService extends SystemService {
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 @@ class AlarmManagerService extends SystemService {
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 @@ class AlarmManagerService extends SystemService {
workSource = _ws;
flags = _flags;
alarmClock = _info;
- userId = _userId;
+ uid = _uid;
}
public static String makeTag(PendingIntent pi, int type) {
@@ -1812,7 +1887,7 @@ class AlarmManagerService extends SystemService {
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 @@ class AlarmManagerService extends SystemService {
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 @@ class AlarmManagerService extends SystemService {
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 @@ class AlarmManagerService extends SystemService {
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 @@ class AlarmManagerService extends SystemService {
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 @@ class AlarmManagerService extends SystemService {
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 java.util.List;
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.Handler;
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 @@ public class ActivityTestMain extends Activity {
static final String KEY_CONFIGURATION = "configuration";
ActivityManager mAm;
+ AlarmManager mAlarm;
Configuration mOverrideConfig;
int mSecondUser;
@@ -66,6 +69,7 @@ public class ActivityTestMain extends Activity {
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 @@ public class ActivityTestMain extends Activity {
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 @@ public class ActivityTestMain extends Activity {
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 @@ public class ActivityTestMain extends Activity {
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 @@ public class ActivityTestMain extends Activity {
@Override
protected void onStop() {
super.onStop();
+ mHandler.removeMessages(MSG_SPAM_ALARM);
for (ServiceConnection conn : mConnections) {
unbindService(conn);
}
@@ -536,6 +558,12 @@ public class ActivityTestMain extends Activity {
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);
+ }
+}