summaryrefslogtreecommitdiffstats
path: root/services
diff options
context:
space:
mode:
authorAdrian Roos <roosa@google.com>2014-07-07 23:35:53 +0200
committerAdrian Roos <roosa@google.com>2014-07-10 17:07:36 +0000
commitc42a1e1071937ae48b7aa5d6291a32c29078b74b (patch)
treeb5806ebfdc7738b325b5e5488ae79fd81f507367 /services
parentcafd15216ee66a302ee69c0ea0c6c63b07a2f68c (diff)
downloadframeworks_base-c42a1e1071937ae48b7aa5d6291a32c29078b74b.zip
frameworks_base-c42a1e1071937ae48b7aa5d6291a32c29078b74b.tar.gz
frameworks_base-c42a1e1071937ae48b7aa5d6291a32c29078b74b.tar.bz2
Add AlarmClock API to AlarmManager
Adds a new kind of alarm that represents an alarm clock and a way to query the next scheduled alarm clock. Deprecates Settings.System.NEXT_ALARM_FORMATTED. Bug: 14589952 Change-Id: I297eeeff36d07adcda010afac183d0f5ee37dc99
Diffstat (limited to 'services')
-rw-r--r--services/core/java/com/android/server/AlarmManagerService.java222
-rw-r--r--services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java3
2 files changed, 210 insertions, 15 deletions
diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java
index 4ab8a01..21e56702 100644
--- a/services/core/java/com/android/server/AlarmManagerService.java
+++ b/services/core/java/com/android/server/AlarmManagerService.java
@@ -17,7 +17,9 @@
package com.android.server;
import android.app.Activity;
+import android.app.ActivityManager;
import android.app.ActivityManagerNative;
+import android.app.AlarmClockInfo;
import android.app.AlarmManager;
import android.app.IAlarmManager;
import android.app.PendingIntent;
@@ -37,10 +39,14 @@ import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.WorkSource;
+import android.provider.Settings;
import android.text.TextUtils;
+import android.text.format.DateFormat;
import android.util.ArrayMap;
+import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;
+import android.util.SparseBooleanArray;
import android.util.TimeUtils;
import java.io.ByteArrayOutputStream;
@@ -54,6 +60,7 @@ import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.LinkedList;
+import java.util.Locale;
import java.util.TimeZone;
import static android.app.AlarmManager.RTC_WAKEUP;
@@ -83,15 +90,19 @@ class AlarmManagerService extends SystemService {
static final boolean localLOGV = false;
static final boolean DEBUG_BATCH = localLOGV || false;
static final boolean DEBUG_VALIDATE = localLOGV || false;
+ static final boolean DEBUG_ALARM_CLOCK = localLOGV || false;
static final int ALARM_EVENT = 1;
static final String TIMEZONE_PROPERTY = "persist.sys.timezone";
-
+
static final Intent mBackgroundIntent
= new Intent().addFlags(Intent.FLAG_FROM_BACKGROUND);
static final IncreasingTimeOrder sIncreasingTimeOrder = new IncreasingTimeOrder();
static final boolean WAKEUP_STATS = false;
+ private static final Intent NEXT_ALARM_CLOCK_CHANGED_INTENT = new Intent(
+ AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED);
+
final LocalLog mLog = new LocalLog(TAG);
final Object mLock = new Object();
@@ -118,6 +129,15 @@ class AlarmManagerService extends SystemService {
long mStartCurrentDelayTime;
long mNextNonWakeupDeliveryTime;
+ private final SparseArray<AlarmClockInfo> mNextAlarmClockForUser = new SparseArray<>();
+ private final SparseArray<AlarmClockInfo> mTmpSparseAlarmClockArray = new SparseArray<>();
+ private final SparseBooleanArray mPendingSendNextAlarmClockChangedForUser =
+ new SparseBooleanArray();
+ private boolean mNextAlarmClockMayChange;
+
+ // May only use on mHandler's thread, locking not required.
+ private final SparseArray<AlarmClockInfo> mHandlerSparseAlarmClockArray = new SparseArray<>();
+
class WakeupEvent {
public long when;
public int uid;
@@ -133,7 +153,7 @@ class AlarmManagerService extends SystemService {
final LinkedList<WakeupEvent> mRecentWakeups = new LinkedList<WakeupEvent>();
final long RECENT_WAKEUP_PERIOD = 1000L * 60 * 60 * 24; // one day
- static final class Batch {
+ final class Batch {
long start; // These endpoints are always in ELAPSED
long end;
boolean standalone; // certain "batches" don't participate in coalescing
@@ -197,6 +217,9 @@ class AlarmManagerService extends SystemService {
if (alarm.operation.equals(operation)) {
alarms.remove(i);
didRemove = true;
+ if (alarm.alarmClock != null) {
+ mNextAlarmClockMayChange = true;
+ }
} else {
if (alarm.whenElapsed > newStart) {
newStart = alarm.whenElapsed;
@@ -224,6 +247,9 @@ class AlarmManagerService extends SystemService {
if (alarm.operation.getTargetPackage().equals(packageName)) {
alarms.remove(i);
didRemove = true;
+ if (alarm.alarmClock != null) {
+ mNextAlarmClockMayChange = true;
+ }
} else {
if (alarm.whenElapsed > newStart) {
newStart = alarm.whenElapsed;
@@ -251,6 +277,9 @@ class AlarmManagerService extends SystemService {
if (UserHandle.getUserId(alarm.operation.getCreatorUid()) == userHandle) {
alarms.remove(i);
didRemove = true;
+ if (alarm.alarmClock != null) {
+ mNextAlarmClockMayChange = true;
+ }
} else {
if (alarm.whenElapsed > newStart) {
newStart = alarm.whenElapsed;
@@ -426,7 +455,8 @@ class AlarmManagerService extends SystemService {
: maxTriggerTime(nowElapsed, whenElapsed, a.repeatInterval);
}
setImplLocked(a.type, a.when, whenElapsed, a.windowLength, maxElapsed,
- a.repeatInterval, a.operation, batch.standalone, doValidate, a.workSource);
+ a.repeatInterval, a.operation, batch.standalone, doValidate, a.workSource,
+ a.alarmClock, a.userId);
}
}
}
@@ -588,7 +618,8 @@ class AlarmManagerService extends SystemService {
}
void setImpl(int type, long triggerAtTime, long windowLength, long interval,
- PendingIntent operation, boolean isStandalone, WorkSource workSource) {
+ PendingIntent operation, boolean isStandalone, WorkSource workSource,
+ AlarmClockInfo alarmClock) {
if (operation == null) {
Slog.w(TAG, "set/setRepeating ignored because there is no intent");
return;
@@ -625,6 +656,8 @@ class AlarmManagerService extends SystemService {
maxElapsed = triggerElapsed + windowLength;
}
+ final int userId = UserHandle.getCallingUserId();
+
synchronized (mLock) {
if (DEBUG_BATCH) {
Slog.v(TAG, "set(" + operation + ") : type=" + type
@@ -633,15 +666,15 @@ class AlarmManagerService extends SystemService {
+ " interval=" + interval + " standalone=" + isStandalone);
}
setImplLocked(type, triggerAtTime, triggerElapsed, windowLength, maxElapsed,
- interval, operation, isStandalone, true, workSource);
+ interval, operation, isStandalone, true, workSource, alarmClock, userId);
}
}
private void setImplLocked(int type, long when, long whenElapsed, long windowLength,
long maxWhen, long interval, PendingIntent operation, boolean isStandalone,
- boolean doValidate, WorkSource workSource) {
+ boolean doValidate, WorkSource workSource, AlarmClockInfo alarmClock, int userId) {
Alarm a = new Alarm(type, when, whenElapsed, windowLength, maxWhen, interval,
- operation, workSource);
+ operation, workSource, alarmClock, userId);
removeLocked(operation);
int whichBatch = (isStandalone) ? -1 : attemptCoalesceLocked(whenElapsed, maxWhen);
@@ -659,6 +692,11 @@ class AlarmManagerService extends SystemService {
}
}
+ if (alarmClock != null) {
+ mNextAlarmClockMayChange = true;
+ updateNextAlarmClockLocked();
+ }
+
if (DEBUG_VALIDATE) {
if (doValidate && !validateConsistencyLocked()) {
Slog.v(TAG, "Tipping-point operation: type=" + type + " when=" + when
@@ -676,7 +714,7 @@ class AlarmManagerService extends SystemService {
private final IBinder mService = new IAlarmManager.Stub() {
@Override
public void set(int type, long triggerAtTime, long windowLength, long interval,
- PendingIntent operation, WorkSource workSource) {
+ PendingIntent operation, WorkSource workSource, AlarmClockInfo alarmClock) {
if (workSource != null) {
getContext().enforceCallingPermission(
android.Manifest.permission.UPDATE_DEVICE_STATS,
@@ -684,7 +722,7 @@ class AlarmManagerService extends SystemService {
}
setImpl(type, triggerAtTime, windowLength, interval, operation,
- false, workSource);
+ false, workSource, alarmClock);
}
@Override
@@ -724,6 +762,15 @@ class AlarmManagerService extends SystemService {
}
@Override
+ public AlarmClockInfo getNextAlarmClock(int userId) {
+ userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
+ Binder.getCallingUid(), userId, false /* allowAll */, false /* requireFull */,
+ "getNextAlarmClock", null);
+
+ return getNextAlarmClockImpl(userId);
+ }
+
+ @Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (getContext().checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
!= PackageManager.PERMISSION_GRANTED) {
@@ -962,6 +1009,133 @@ class AlarmManagerService extends SystemService {
return null;
}
+ private AlarmClockInfo getNextAlarmClockImpl(int userId) {
+ synchronized (mLock) {
+ return mNextAlarmClockForUser.get(userId);
+ }
+ }
+
+ /**
+ * Recomputes the next alarm clock for all users.
+ */
+ private void updateNextAlarmClockLocked() {
+ if (!mNextAlarmClockMayChange) {
+ return;
+ }
+ mNextAlarmClockMayChange = false;
+
+ SparseArray<AlarmClockInfo> nextForUser = mTmpSparseAlarmClockArray;
+ nextForUser.clear();
+
+ final int N = mAlarmBatches.size();
+ for (int i = 0; i < N; i++) {
+ ArrayList<Alarm> alarms = mAlarmBatches.get(i).alarms;
+ final int M = alarms.size();
+
+ for (int j = 0; j < M; j++) {
+ Alarm a = alarms.get(j);
+ if (a.alarmClock != null) {
+ final int userId = a.userId;
+
+ if (DEBUG_ALARM_CLOCK) {
+ Log.v(TAG, "Found AlarmClockInfo at " +
+ formatNextAlarm(getContext(), a.alarmClock) +
+ " for user " + userId);
+ }
+
+ // Alarms and batches are sorted by time, no need to compare times here.
+ if (nextForUser.get(userId) == null) {
+ nextForUser.put(userId, a.alarmClock);
+ }
+ }
+ }
+ }
+
+ // Update mNextAlarmForUser with new values.
+ final int NN = nextForUser.size();
+ for (int i = 0; i < NN; i++) {
+ AlarmClockInfo newAlarm = nextForUser.valueAt(i);
+ int userId = nextForUser.keyAt(i);
+ AlarmClockInfo currentAlarm = mNextAlarmClockForUser.get(userId);
+ if (!newAlarm.equals(currentAlarm)) {
+ updateNextAlarmInfoForUserLocked(userId, newAlarm);
+ }
+ }
+
+ // Remove users without any alarm clocks scheduled.
+ final int NNN = mNextAlarmClockForUser.size();
+ for (int i = NNN - 1; i >= 0; i--) {
+ int userId = mNextAlarmClockForUser.keyAt(i);
+ if (nextForUser.get(userId) == null) {
+ updateNextAlarmInfoForUserLocked(userId, null);
+ }
+ }
+ }
+
+ private void updateNextAlarmInfoForUserLocked(int userId, AlarmClockInfo alarmClock) {
+ if (alarmClock != null) {
+ if (DEBUG_ALARM_CLOCK) {
+ Log.v(TAG, "Next AlarmClockInfoForUser(" + userId + "): " +
+ formatNextAlarm(getContext(), alarmClock));
+ }
+ mNextAlarmClockForUser.put(userId, alarmClock);
+ } else {
+ if (DEBUG_ALARM_CLOCK) {
+ Log.v(TAG, "Next AlarmClockInfoForUser(" + userId + "): None");
+ }
+ mNextAlarmClockForUser.remove(userId);
+ }
+
+ mPendingSendNextAlarmClockChangedForUser.put(userId, true);
+ mHandler.removeMessages(AlarmHandler.SEND_NEXT_ALARM_CLOCK_CHANGED);
+ mHandler.sendEmptyMessage(AlarmHandler.SEND_NEXT_ALARM_CLOCK_CHANGED);
+ }
+
+ /**
+ * Updates NEXT_ALARM_FORMATTED and sends NEXT_ALARM_CLOCK_CHANGED_INTENT for all users
+ * for which alarm clocks have changed since the last call to this.
+ *
+ * Do not call with a lock held. Only call from mHandler's thread.
+ *
+ * @see AlarmHandler#SEND_NEXT_ALARM_CLOCK_CHANGED
+ */
+ private void sendNextAlarmClockChanged() {
+ SparseArray<AlarmClockInfo> pendingUsers = mHandlerSparseAlarmClockArray;
+ pendingUsers.clear();
+
+ synchronized (mLock) {
+ final int N = mPendingSendNextAlarmClockChangedForUser.size();
+ for (int i = 0; i < N; i++) {
+ int userId = mPendingSendNextAlarmClockChangedForUser.keyAt(i);
+ pendingUsers.append(userId, mNextAlarmClockForUser.get(userId));
+ }
+ mPendingSendNextAlarmClockChangedForUser.clear();
+ }
+
+ final int N = pendingUsers.size();
+ for (int i = 0; i < N; i++) {
+ int userId = pendingUsers.keyAt(i);
+ AlarmClockInfo alarmClock = pendingUsers.valueAt(i);
+ Settings.System.putStringForUser(getContext().getContentResolver(),
+ Settings.System.NEXT_ALARM_FORMATTED,
+ formatNextAlarm(getContext(), alarmClock),
+ userId);
+
+ getContext().sendBroadcastAsUser(NEXT_ALARM_CLOCK_CHANGED_INTENT,
+ new UserHandle(userId));
+ }
+ }
+
+ /**
+ * Formats an alarm like platform/packages/apps/DeskClock used to.
+ */
+ private static String formatNextAlarm(final Context context, AlarmClockInfo info) {
+ String skeleton = DateFormat.is24HourFormat(context) ? "EHm" : "Ehma";
+ String pattern = DateFormat.getBestDateTimePattern(Locale.getDefault(), skeleton);
+ return (info == null) ? "" :
+ DateFormat.format(pattern, info.getTriggerTime()).toString();
+ }
+
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.
@@ -1004,6 +1178,7 @@ class AlarmManagerService extends SystemService {
}
rebatchAllAlarmsLocked(true);
rescheduleKernelAlarmsLocked();
+ updateNextAlarmClockLocked();
}
}
@@ -1023,6 +1198,7 @@ class AlarmManagerService extends SystemService {
}
rebatchAllAlarmsLocked(true);
rescheduleKernelAlarmsLocked();
+ updateNextAlarmClockLocked();
}
}
@@ -1042,6 +1218,7 @@ class AlarmManagerService extends SystemService {
}
rebatchAllAlarmsLocked(true);
rescheduleKernelAlarmsLocked();
+ updateNextAlarmClockLocked();
}
}
@@ -1180,7 +1357,7 @@ class AlarmManagerService extends SystemService {
setImplLocked(alarm.type, alarm.when + delta, nextElapsed, alarm.windowLength,
maxTriggerTime(nowELAPSED, nextElapsed, alarm.repeatInterval),
alarm.repeatInterval, alarm.operation, batch.standalone, true,
- alarm.workSource);
+ alarm.workSource, alarm.alarmClock, alarm.userId);
// For now we count this as a wakeup alarm, meaning it needs to be
// delivered immediately. In the future we should change this, but
@@ -1189,6 +1366,11 @@ class AlarmManagerService extends SystemService {
} else if (alarm.wakeup) {
hasWakeup = true;
}
+
+ // We removed an alarm clock. Let the caller recompute the next alarm clock.
+ if (alarm.alarmClock != null) {
+ mNextAlarmClockMayChange = true;
+ }
}
}
@@ -1232,9 +1414,12 @@ class AlarmManagerService extends SystemService {
public long whenElapsed; // 'when' in the elapsed time base
public long maxWhen; // also in the elapsed time base
public long repeatInterval;
+ public final AlarmClockInfo alarmClock;
+ public final int userId;
public Alarm(int _type, long _when, long _whenElapsed, long _windowLength, long _maxWhen,
- long _interval, PendingIntent _op, WorkSource _ws) {
+ long _interval, PendingIntent _op, WorkSource _ws, AlarmClockInfo _info,
+ int _userId) {
type = _type;
wakeup = _type == AlarmManager.ELAPSED_REALTIME_WAKEUP
|| _type == AlarmManager.RTC_WAKEUP;
@@ -1246,6 +1431,8 @@ class AlarmManagerService extends SystemService {
operation = _op;
tag = makeTag(_op, _type);
workSource = _ws;
+ alarmClock = _info;
+ userId = _userId;
}
public static String makeTag(PendingIntent pi, int type) {
@@ -1468,12 +1655,14 @@ class AlarmManagerService extends SystemService {
mPendingNonWakeupAlarms.addAll(triggerList);
mNumDelayedAlarms += triggerList.size();
rescheduleKernelAlarmsLocked();
+ updateNextAlarmClockLocked();
} else {
// now deliver the alarm intents; if there are pending non-wakeup
// alarms, we need to merge them in to the list. note we don't
// just deliver them first because we generally want non-wakeup
// alarms delivered after wakeup alarms.
rescheduleKernelAlarmsLocked();
+ updateNextAlarmClockLocked();
if (mPendingNonWakeupAlarms.size() > 0) {
triggerList.addAll(mPendingNonWakeupAlarms);
Collections.sort(triggerList, mAlarmDispatchComparator);
@@ -1529,6 +1718,7 @@ class AlarmManagerService extends SystemService {
public static final int ALARM_EVENT = 1;
public static final int MINUTE_CHANGE_EVENT = 2;
public static final int DATE_CHANGE_EVENT = 3;
+ public static final int SEND_NEXT_ALARM_CLOCK_CHANGED = 4;
public AlarmHandler() {
}
@@ -1540,8 +1730,9 @@ class AlarmManagerService extends SystemService {
final long nowRTC = System.currentTimeMillis();
final long nowELAPSED = SystemClock.elapsedRealtime();
triggerAlarmsLocked(triggerList, nowELAPSED, nowRTC);
+ updateNextAlarmClockLocked();
}
-
+
// now trigger the alarms without the lock held
for (int i=0; i<triggerList.size(); i++) {
Alarm alarm = triggerList.get(i);
@@ -1555,6 +1746,8 @@ class AlarmManagerService extends SystemService {
}
}
}
+ } else if (msg.what == SEND_NEXT_ALARM_CLOCK_CHANGED) {
+ sendNextAlarmClockChanged();
}
}
}
@@ -1596,7 +1789,7 @@ 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, true, workSource);
+ 0, mTimeTickSender, true, workSource, null);
}
public void scheduleDateChangedEvent() {
@@ -1609,7 +1802,8 @@ class AlarmManagerService extends SystemService {
calendar.add(Calendar.DAY_OF_MONTH, 1);
final WorkSource workSource = null; // Let system take blame for date change events.
- setImpl(RTC, calendar.getTimeInMillis(), 0, 0, mDateChangeSender, true, workSource);
+ setImpl(RTC, calendar.getTimeInMillis(), 0, 0, mDateChangeSender, true, workSource,
+ null);
}
}
diff --git a/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java
index a1af8cb..8aae695 100644
--- a/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java
@@ -47,6 +47,7 @@ import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.expectLastCall;
import static org.easymock.EasyMock.isA;
+import android.app.AlarmClockInfo;
import android.app.AlarmManager;
import android.app.IAlarmManager;
import android.app.PendingIntent;
@@ -879,7 +880,7 @@ public class NetworkStatsServiceTest extends AndroidTestCase {
expectLastCall().anyTimes();
mAlarmManager.set(eq(AlarmManager.ELAPSED_REALTIME), anyLong(), anyLong(), anyLong(),
- isA(PendingIntent.class), isA(WorkSource.class));
+ isA(PendingIntent.class), isA(WorkSource.class), isA(AlarmClockInfo.class));
expectLastCall().atLeastOnce();
mNetManager.setGlobalAlert(anyLong());