diff options
| -rw-r--r-- | api/current.txt | 14 | ||||
| -rw-r--r-- | core/java/android/app/AlarmClockInfo.aidl | 19 | ||||
| -rw-r--r-- | core/java/android/app/AlarmClockInfo.java | 101 | ||||
| -rw-r--r-- | core/java/android/app/AlarmManager.java | 89 | ||||
| -rw-r--r-- | core/java/android/app/IAlarmManager.aidl | 5 | ||||
| -rw-r--r-- | core/java/android/provider/Settings.java | 3 | ||||
| -rw-r--r-- | core/res/AndroidManifest.xml | 1 | ||||
| -rw-r--r-- | packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java | 17 | ||||
| -rw-r--r-- | services/core/java/com/android/server/AlarmManagerService.java | 222 | ||||
| -rw-r--r-- | services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java | 3 |
10 files changed, 444 insertions, 30 deletions
diff --git a/api/current.txt b/api/current.txt index 01ed08dcfbb4..d235fa4571bd 100644 --- a/api/current.txt +++ b/api/current.txt @@ -3661,15 +3661,27 @@ package android.app { method public void update(android.app.ActivityOptions); } + public class AlarmClockInfo implements android.os.Parcelable { + ctor public AlarmClockInfo(long, android.app.PendingIntent); + method public int describeContents(); + method public android.app.PendingIntent getShowIntent(); + method public long getTriggerTime(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator CREATOR; + } + public class AlarmManager { method public void cancel(android.app.PendingIntent); + method public android.app.AlarmClockInfo getNextAlarmClock(); method public void set(int, long, android.app.PendingIntent); + method public void setAlarmClock(android.app.AlarmClockInfo, android.app.PendingIntent); method public void setExact(int, long, android.app.PendingIntent); method public void setInexactRepeating(int, long, long, android.app.PendingIntent); method public void setRepeating(int, long, long, android.app.PendingIntent); method public void setTime(long); method public void setTimeZone(java.lang.String); method public void setWindow(int, long, long, android.app.PendingIntent); + field public static final java.lang.String ACTION_NEXT_ALARM_CLOCK_CHANGED = "android.app.action.NEXT_ALARM_CLOCK_CHANGED"; field public static final int ELAPSED_REALTIME = 3; // 0x3 field public static final int ELAPSED_REALTIME_WAKEUP = 2; // 0x2 field public static final long INTERVAL_DAY = 86400000L; // 0x5265c00L @@ -24745,7 +24757,7 @@ package android.provider { field public static final java.lang.String MODE_RINGER_STREAMS_AFFECTED = "mode_ringer_streams_affected"; field public static final java.lang.String MUTE_STREAMS_AFFECTED = "mute_streams_affected"; field public static final deprecated java.lang.String NETWORK_PREFERENCE = "network_preference"; - field public static final java.lang.String NEXT_ALARM_FORMATTED = "next_alarm_formatted"; + field public static final deprecated java.lang.String NEXT_ALARM_FORMATTED = "next_alarm_formatted"; field public static final java.lang.String NOTIFICATION_SOUND = "notification_sound"; field public static final deprecated java.lang.String PARENTAL_CONTROL_ENABLED = "parental_control_enabled"; field public static final deprecated java.lang.String PARENTAL_CONTROL_LAST_UPDATE = "parental_control_last_update"; diff --git a/core/java/android/app/AlarmClockInfo.aidl b/core/java/android/app/AlarmClockInfo.aidl new file mode 100644 index 000000000000..58a3644d8928 --- /dev/null +++ b/core/java/android/app/AlarmClockInfo.aidl @@ -0,0 +1,19 @@ +/** + * Copyright (c) 2014, 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 android.app; + +parcelable AlarmClockInfo; diff --git a/core/java/android/app/AlarmClockInfo.java b/core/java/android/app/AlarmClockInfo.java new file mode 100644 index 000000000000..0ccaf02995ae --- /dev/null +++ b/core/java/android/app/AlarmClockInfo.java @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2014 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 android.app; + +import android.os.Parcel; +import android.os.Parcelable; + +/** + * An immutable description of an alarm clock. + * + * @see AlarmManager#setAlarmClock + * @see AlarmManager#getNextAlarmClock + */ +public class AlarmClockInfo implements Parcelable { + + private final long mTriggerTime; + private final PendingIntent mShowIntent; + + /** + * Creates a new alarm clock description. + * + * @param triggerTime time at which the underlying alarm is triggered in wall time milliseconds + * since the epoch + * @param showIntent an intent that can be used to show or edit details of + * the alarm clock. + */ + public AlarmClockInfo(long triggerTime, PendingIntent showIntent) { + mTriggerTime = triggerTime; + mShowIntent = showIntent; + } + + /** + * Use the {@link #CREATOR} + * @hide + */ + AlarmClockInfo(Parcel in) { + mTriggerTime = in.readLong(); + mShowIntent = in.readParcelable(PendingIntent.class.getClassLoader()); + } + + /** + * Returns the time at which the alarm is going to trigger. + * + * This value is UTC wall clock time in milliseconds, as returned by + * {@link System#currentTimeMillis()} for example. + */ + public long getTriggerTime() { + return mTriggerTime; + } + + /** + * Returns an intent intent that can be used to show or edit details of the alarm clock in + * the application that scheduled it. + * + * <p class="note">Beware that any application can retrieve and send this intent, potentially + * with additional fields filled in. See + * {@link PendingIntent#send(android.content.Context, int, android.content.Intent) + * PendingIntent.send()} and {@link android.content.Intent#fillIn Intent.fillIn()} + * for details. + */ + public PendingIntent getShowIntent() { + return mShowIntent; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeLong(mTriggerTime); + dest.writeParcelable(mShowIntent, flags); + } + + public static final Creator<AlarmClockInfo> CREATOR = new Creator<AlarmClockInfo>() { + @Override + public AlarmClockInfo createFromParcel(Parcel in) { + return new AlarmClockInfo(in); + } + + @Override + public AlarmClockInfo[] newArray(int size) { + return new AlarmClockInfo[size]; + } + }; +} diff --git a/core/java/android/app/AlarmManager.java b/core/java/android/app/AlarmManager.java index 0cf7ad06a291..fa2d64c9c1a5 100644 --- a/core/java/android/app/AlarmManager.java +++ b/core/java/android/app/AlarmManager.java @@ -16,10 +16,12 @@ package android.app; +import android.annotation.SdkConstant; import android.content.Context; import android.content.Intent; import android.os.Build; import android.os.RemoteException; +import android.os.UserHandle; import android.os.WorkSource; /** @@ -94,6 +96,17 @@ public class AlarmManager */ public static final int ELAPSED_REALTIME = 3; + /** + * Broadcast Action: Sent after the value returned by + * {@link #getNextAlarmClock()} has changed. + * + * <p class="note">This is a protected intent that can only be sent by the system. + * It is only sent to registered receivers.</p> + */ + @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_NEXT_ALARM_CLOCK_CHANGED = + "android.app.action.NEXT_ALARM_CLOCK_CHANGED"; + /** @hide */ public static final long WINDOW_EXACT = 0; /** @hide */ @@ -188,7 +201,7 @@ public class AlarmManager * @see #RTC_WAKEUP */ public void set(int type, long triggerAtMillis, PendingIntent operation) { - setImpl(type, triggerAtMillis, legacyExactLength(), 0, operation, null); + setImpl(type, triggerAtMillis, legacyExactLength(), 0, operation, null, null); } /** @@ -249,7 +262,7 @@ public class AlarmManager */ public void setRepeating(int type, long triggerAtMillis, long intervalMillis, PendingIntent operation) { - setImpl(type, triggerAtMillis, legacyExactLength(), intervalMillis, operation, null); + setImpl(type, triggerAtMillis, legacyExactLength(), intervalMillis, operation, null, null); } /** @@ -299,7 +312,7 @@ public class AlarmManager */ public void setWindow(int type, long windowStartMillis, long windowLengthMillis, PendingIntent operation) { - setImpl(type, windowStartMillis, windowLengthMillis, 0, operation, null); + setImpl(type, windowStartMillis, windowLengthMillis, 0, operation, null, null); } /** @@ -337,17 +350,45 @@ public class AlarmManager * @see #RTC_WAKEUP */ public void setExact(int type, long triggerAtMillis, PendingIntent operation) { - setImpl(type, triggerAtMillis, WINDOW_EXACT, 0, operation, null); + setImpl(type, triggerAtMillis, WINDOW_EXACT, 0, operation, null, null); + } + + /** + * Schedule an alarm that represents an alarm clock. + * + * The system may choose to display information about this alarm to the user. + * + * <p> + * This method is like {@link #setExact(int, long, PendingIntent)}, but implies + * {@link #RTC_WAKEUP}. + * + * @param info + * @param operation Action to perform when the alarm goes off; + * typically comes from {@link PendingIntent#getBroadcast + * IntentSender.getBroadcast()}. + * + * @see #set + * @see #setRepeating + * @see #setWindow + * @see #setExact + * @see #cancel + * @see #getNextAlarmClock() + * @see android.content.Context#sendBroadcast + * @see android.content.Context#registerReceiver + * @see android.content.Intent#filterEquals + */ + public void setAlarmClock(AlarmClockInfo info, PendingIntent operation) { + setImpl(RTC_WAKEUP, info.getTriggerTime(), WINDOW_EXACT, 0, operation, null, info); } /** @hide */ public void set(int type, long triggerAtMillis, long windowMillis, long intervalMillis, PendingIntent operation, WorkSource workSource) { - setImpl(type, triggerAtMillis, windowMillis, intervalMillis, operation, workSource); + setImpl(type, triggerAtMillis, windowMillis, intervalMillis, operation, workSource, null); } private void setImpl(int type, long triggerAtMillis, long windowMillis, long intervalMillis, - PendingIntent operation, WorkSource workSource) { + PendingIntent operation, WorkSource workSource, AlarmClockInfo alarmClock) { if (triggerAtMillis < 0) { /* NOTYET if (mAlwaysExact) { @@ -361,7 +402,7 @@ public class AlarmManager try { mService.set(type, triggerAtMillis, windowMillis, intervalMillis, operation, - workSource); + workSource, alarmClock); } catch (RemoteException ex) { } } @@ -461,7 +502,7 @@ public class AlarmManager */ public void setInexactRepeating(int type, long triggerAtMillis, long intervalMillis, PendingIntent operation) { - setImpl(type, triggerAtMillis, WINDOW_HEURISTIC, intervalMillis, operation, null); + setImpl(type, triggerAtMillis, WINDOW_HEURISTIC, intervalMillis, operation, null, null); } /** @@ -506,4 +547,36 @@ public class AlarmManager } catch (RemoteException ex) { } } + + /** + * Gets information about the next alarm clock currently scheduled. + * + * The alarm clocks considered are those scheduled by {@link #setAlarmClock} + * from any package of the calling user. + * + * @see #setAlarmClock + * @see AlarmClockInfo + */ + public AlarmClockInfo getNextAlarmClock() { + return getNextAlarmClock(UserHandle.myUserId()); + } + + /** + * Gets information about the next alarm clock currently scheduled. + * + * The alarm clocks considered are those scheduled by {@link #setAlarmClock} + * from any package of the given {@parm userId}. + * + * @see #setAlarmClock + * @see AlarmClockInfo + * + * @hide + */ + public AlarmClockInfo getNextAlarmClock(int userId) { + try { + return mService.getNextAlarmClock(userId); + } catch (RemoteException ex) { + return null; + } + } } diff --git a/core/java/android/app/IAlarmManager.aidl b/core/java/android/app/IAlarmManager.aidl index ef9f26eeb6d4..fb33706dde34 100644 --- a/core/java/android/app/IAlarmManager.aidl +++ b/core/java/android/app/IAlarmManager.aidl @@ -16,6 +16,7 @@ */ package android.app; +import android.app.AlarmClockInfo; import android.app.PendingIntent; import android.os.WorkSource; @@ -27,10 +28,12 @@ import android.os.WorkSource; interface IAlarmManager { /** windowLength == 0 means exact; windowLength < 0 means the let the OS decide */ void set(int type, long triggerAtTime, long windowLength, - long interval, in PendingIntent operation, in WorkSource workSource); + long interval, in PendingIntent operation, in WorkSource workSource, + in AlarmClockInfo alarmClock); boolean setTime(long millis); void setTimeZone(String zone); void remove(in PendingIntent operation); + AlarmClockInfo getNextAlarmClock(int userId); } diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 99e108fa2ebe..f48855a891ca 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -1832,7 +1832,10 @@ public final class Settings { /** * A formatted string of the next alarm that is set, or the empty string * if there is no alarm set. + * + * @deprecated Use {@link android.app.AlarmManager#getNextAlarmClock()}. */ + @Deprecated public static final String NEXT_ALARM_FORMATTED = "next_alarm_formatted"; /** diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index e38fce943b40..c34a971722cf 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -80,6 +80,7 @@ <protected-broadcast android:name="android.app.action.EXIT_CAR_MODE" /> <protected-broadcast android:name="android.app.action.ENTER_DESK_MODE" /> <protected-broadcast android:name="android.app.action.EXIT_DESK_MODE" /> + <protected-broadcast android:name="android.app.action.NEXT_ALARM_CLOCK_CHANGED" /> <protected-broadcast android:name="android.appwidget.action.APPWIDGET_UPDATE_OPTIONS" /> <protected-broadcast android:name="android.appwidget.action.APPWIDGET_DELETED" /> diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java index fa8e1a6b9243..1bb36dd796d7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.phone; +import android.app.AlarmManager; import android.app.StatusBarManager; import android.bluetooth.BluetoothAdapter; import android.content.BroadcastReceiver; @@ -24,6 +25,7 @@ import android.content.Intent; import android.content.IntentFilter; import android.media.AudioManager; import android.os.Handler; +import android.os.UserHandle; import android.provider.Settings.Global; import android.util.Log; @@ -71,8 +73,8 @@ public class PhoneStatusBarPolicy { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); - if (action.equals(Intent.ACTION_ALARM_CHANGED)) { - updateAlarm(intent); + if (action.equals(AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED)) { + updateAlarm(); } else if (action.equals(Intent.ACTION_SYNC_STATE_CHANGED)) { updateSyncState(intent); @@ -90,6 +92,9 @@ public class PhoneStatusBarPolicy { else if (action.equals(TtyIntent.TTY_ENABLED_CHANGE_ACTION)) { updateTTY(intent); } + else if (action.equals(Intent.ACTION_USER_SWITCHED)) { + updateAlarm(); + } } }; @@ -99,13 +104,14 @@ public class PhoneStatusBarPolicy { // listen for broadcasts IntentFilter filter = new IntentFilter(); - filter.addAction(Intent.ACTION_ALARM_CHANGED); + filter.addAction(AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED); filter.addAction(Intent.ACTION_SYNC_STATE_CHANGED); filter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION); filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED); filter.addAction(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED); filter.addAction(TelephonyIntents.ACTION_SIM_STATE_CHANGED); filter.addAction(TtyIntent.TTY_ENABLED_CHANGE_ACTION); + filter.addAction(Intent.ACTION_USER_SWITCHED); mContext.registerReceiver(mIntentReceiver, filter, null, mHandler); // TTY status @@ -152,8 +158,9 @@ public class PhoneStatusBarPolicy { updateVolumeZen(); } - private final void updateAlarm(Intent intent) { - boolean alarmSet = intent.getBooleanExtra("alarmSet", false); + private void updateAlarm() { + AlarmManager alarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE); + boolean alarmSet = alarmManager.getNextAlarmClock(UserHandle.USER_CURRENT) != null; mService.setIconVisibility(SLOT_ALARM_CLOCK, alarmSet); } diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java index 4ab8a011c995..21e56702b6ad 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 a1af8cb79f43..8aae69554ad7 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()); |