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
diff --git a/api/current.txt b/api/current.txt
index 8642eee..fd2a739 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -3661,15 +3661,27 @@
     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 @@
     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 0000000..58a3644
--- /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 0000000..0ccaf02
--- /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 0cf7ad0..fa2d64c 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 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 @@
      * @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 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 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 @@
      * @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 @@
 
         try {
             mService.set(type, triggerAtMillis, windowMillis, intervalMillis, operation,
-                    workSource);
+                    workSource, alarmClock);
         } catch (RemoteException ex) {
         }
     }
@@ -461,7 +502,7 @@
      */
     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 @@
         } 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 ef9f26e..fb33706 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 @@
 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 99e108f..f48855a 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -1832,7 +1832,10 @@
         /**
          * 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 e38fce9..c34a971 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 fa8e1a6..1bb36dd 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.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 @@
         @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 @@
             else if (action.equals(TtyIntent.TTY_ENABLED_CHANGE_ACTION)) {
                 updateTTY(intent);
             }
+            else if (action.equals(Intent.ACTION_USER_SWITCHED)) {
+                updateAlarm();
+            }
         }
     };
 
@@ -99,13 +104,14 @@
 
         // 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 @@
         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 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.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.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 @@
     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 @@
     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 @@
     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 @@
                 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 @@
                 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 @@
                 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 @@
                             : 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 @@
     }
 
     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 @@
             maxElapsed = triggerElapsed + windowLength;
         }
 
+        final int userId = UserHandle.getCallingUserId();
+
         synchronized (mLock) {
             if (DEBUG_BATCH) {
                 Slog.v(TAG, "set(" + operation + ") : type=" + type
@@ -633,15 +666,15 @@
                         + " 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 @@
             }
         }
 
+        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 @@
     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 @@
             }
 
             setImpl(type, triggerAtTime, windowLength, interval, operation,
-                    false, workSource);
+                    false, workSource, alarmClock);
         }
 
         @Override
@@ -724,6 +762,15 @@
         }
 
         @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 @@
         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 @@
             }
             rebatchAllAlarmsLocked(true);
             rescheduleKernelAlarmsLocked();
+            updateNextAlarmClockLocked();
         }
     }
 
@@ -1023,6 +1198,7 @@
             }
             rebatchAllAlarmsLocked(true);
             rescheduleKernelAlarmsLocked();
+            updateNextAlarmClockLocked();
         }
     }
 
@@ -1042,6 +1218,7 @@
             }
             rebatchAllAlarmsLocked(true);
             rescheduleKernelAlarmsLocked();
+            updateNextAlarmClockLocked();
         }
     }
 
@@ -1180,7 +1357,7 @@
                     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 @@
                 } 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 @@
         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 @@
             operation = _op;
             tag = makeTag(_op, _type);
             workSource = _ws;
+            alarmClock = _info;
+            userId = _userId;
         }
 
         public static String makeTag(PendingIntent pi, int type) {
@@ -1468,12 +1655,14 @@
                         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 @@
         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 @@
                     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 @@
                         }
                     }
                 }
+            } else if (msg.what == SEND_NEXT_ALARM_CLOCK_CHANGED) {
+                sendNextAlarmClockChanged();
             }
         }
     }
@@ -1596,7 +1789,7 @@
 
             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 @@
             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.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 @@
         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());