Introduce direct listener API for alarm delivery

The Alarm Manager now supports a set() variant that takes a listener
callback to invoke at alarm trigger time rather than a PendingIntent.
This is much lower overhead and has guaranteed low delivery latency
from the trigger time.  The tradeoff is that the app must be running
*continuously* from the time the alarm is set to the time it is
delivered.  If the app exits for any reason before the alarm fires,
the listener becomes invalid and the alarm will be dropped.  This is
more or less equivalent to setting an alarm with a broadcast
PendingIntent that matches only a runtime-registered receiver.

The app's alarm listener can be any object that implements the new
AlarmManager.OnAlarmListener interface and implements its onAlarm()
method.  There is no data delivered at alarm trigger time: whatever
state needs to be associated with the specific alarm instance should
simply be packaged inside the OnAlarmListener instance.

An alarm using OnAlarmListener can request that the onAlarm() method
be called on an arbitrary handler.  If the program passes 'null' for
this parameter when setting the alarm, the callback occurs on the
application's main Looper thread.

Bug 20157436

Change-Id: I2eb030a24efdd466a2eee1666c5231201b43684b
diff --git a/Android.mk b/Android.mk
index 5713f3e..f0c0117 100644
--- a/Android.mk
+++ b/Android.mk
@@ -68,6 +68,8 @@
 	core/java/android/app/IActivityContainerCallback.aidl \
 	core/java/android/app/IActivityController.aidl \
 	core/java/android/app/IActivityPendingResult.aidl \
+	core/java/android/app/IAlarmCompleteListener.aidl \
+	core/java/android/app/IAlarmListener.aidl \
 	core/java/android/app/IAlarmManager.aidl \
 	core/java/android/app/IAppTask.aidl \
 	core/java/android/app/ITaskStackListener.aidl \
diff --git a/api/current.txt b/api/current.txt
index 70e1547..0bbf2f9 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -3764,17 +3764,21 @@
 
   public class AlarmManager {
     method public void cancel(android.app.PendingIntent);
+    method public void cancel(android.app.AlarmManager.OnAlarmListener);
     method public android.app.AlarmManager.AlarmClockInfo getNextAlarmClock();
     method public void set(int, long, android.app.PendingIntent);
+    method public void set(int, long, java.lang.String, android.app.AlarmManager.OnAlarmListener, android.os.Handler);
     method public void setAlarmClock(android.app.AlarmManager.AlarmClockInfo, android.app.PendingIntent);
     method public void setAndAllowWhileIdle(int, long, android.app.PendingIntent);
     method public void setExact(int, long, android.app.PendingIntent);
+    method public void setExact(int, long, java.lang.String, android.app.AlarmManager.OnAlarmListener, android.os.Handler);
     method public void setExactAndAllowWhileIdle(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);
+    method public void setWindow(int, long, long, java.lang.String, android.app.AlarmManager.OnAlarmListener, android.os.Handler);
     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
@@ -3796,6 +3800,10 @@
     field public static final android.os.Parcelable.Creator<android.app.AlarmManager.AlarmClockInfo> CREATOR;
   }
 
+  public static abstract interface AlarmManager.OnAlarmListener {
+    method public abstract void onAlarm();
+  }
+
   public class AlertDialog extends android.app.Dialog implements android.content.DialogInterface {
     ctor protected AlertDialog(android.content.Context);
     ctor protected AlertDialog(android.content.Context, boolean, android.content.DialogInterface.OnCancelListener);
diff --git a/api/system-current.txt b/api/system-current.txt
index 0f42e66..c5ad20d 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -3873,18 +3873,22 @@
 
   public class AlarmManager {
     method public void cancel(android.app.PendingIntent);
+    method public void cancel(android.app.AlarmManager.OnAlarmListener);
     method public android.app.AlarmManager.AlarmClockInfo getNextAlarmClock();
     method public void set(int, long, android.app.PendingIntent);
+    method public void set(int, long, java.lang.String, android.app.AlarmManager.OnAlarmListener, android.os.Handler);
     method public void set(int, long, long, long, android.app.PendingIntent, android.os.WorkSource);
     method public void setAlarmClock(android.app.AlarmManager.AlarmClockInfo, android.app.PendingIntent);
     method public void setAndAllowWhileIdle(int, long, android.app.PendingIntent);
     method public void setExact(int, long, android.app.PendingIntent);
+    method public void setExact(int, long, java.lang.String, android.app.AlarmManager.OnAlarmListener, android.os.Handler);
     method public void setExactAndAllowWhileIdle(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);
+    method public void setWindow(int, long, long, java.lang.String, android.app.AlarmManager.OnAlarmListener, android.os.Handler);
     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
@@ -3906,6 +3910,10 @@
     field public static final android.os.Parcelable.Creator<android.app.AlarmManager.AlarmClockInfo> CREATOR;
   }
 
+  public static abstract interface AlarmManager.OnAlarmListener {
+    method public abstract void onAlarm();
+  }
+
   public class AlertDialog extends android.app.Dialog implements android.content.DialogInterface {
     ctor protected AlertDialog(android.content.Context);
     ctor protected AlertDialog(android.content.Context, boolean, android.content.DialogInterface.OnCancelListener);
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index 70f1bcc..a24b1ec 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -116,21 +116,22 @@
     static public void noteWakeupAlarm(PendingIntent ps, int sourceUid, String sourcePkg,
             String tag) {
         try {
-            getDefault().noteWakeupAlarm(ps.getTarget(), sourceUid, sourcePkg, tag);
+            getDefault().noteWakeupAlarm((ps != null) ? ps.getTarget() : null,
+                    sourceUid, sourcePkg, tag);
         } catch (RemoteException ex) {
         }
     }
 
     static public void noteAlarmStart(PendingIntent ps, int sourceUid, String tag) {
         try {
-            getDefault().noteAlarmStart(ps.getTarget(), sourceUid, tag);
+            getDefault().noteAlarmStart((ps != null) ? ps.getTarget() : null, sourceUid, tag);
         } catch (RemoteException ex) {
         }
     }
 
     static public void noteAlarmFinish(PendingIntent ps, int sourceUid, String tag) {
         try {
-            getDefault().noteAlarmFinish(ps.getTarget(), sourceUid, tag);
+            getDefault().noteAlarmFinish((ps != null) ? ps.getTarget() : null, sourceUid, tag);
         } catch (RemoteException ex) {
         }
     }
diff --git a/core/java/android/app/AlarmManager.java b/core/java/android/app/AlarmManager.java
index fb03b62..3dc9582 100644
--- a/core/java/android/app/AlarmManager.java
+++ b/core/java/android/app/AlarmManager.java
@@ -21,15 +21,21 @@
 import android.content.Context;
 import android.content.Intent;
 import android.os.Build;
+import android.os.Handler;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.RemoteException;
+import android.os.SystemClock;
 import android.os.UserHandle;
 import android.os.WorkSource;
 import android.text.TextUtils;
+import android.util.Log;
+
 import libcore.util.ZoneInfoDB;
 
 import java.io.IOException;
+import java.lang.ref.WeakReference;
+import java.util.WeakHashMap;
 
 /**
  * This class provides access to the system alarm services.  These allow you
@@ -72,6 +78,8 @@
  * Context.getSystemService(Context.ALARM_SERVICE)}.
  */
 public class AlarmManager {
+    private static final String TAG = "AlarmManager";
+
     /**
      * Alarm time in {@link System#currentTimeMillis System.currentTimeMillis()}
      * (wall clock time in UTC), which will wake up the device when
@@ -160,9 +168,89 @@
     public static final int FLAG_IDLE_UNTIL = 1<<4;
 
     private final IAlarmManager mService;
+    private final String mPackageName;
     private final boolean mAlwaysExact;
     private final int mTargetSdkVersion;
+    private final Handler mMainThreadHandler;
 
+    /**
+     * Direct-notification alarms: the requester must be running continuously from the
+     * time the alarm is set to the time it is delivered, or delivery will fail.  Only
+     * one-shot alarms can be set using this mechanism, not repeating alarms.
+     */
+    public interface OnAlarmListener {
+        /**
+         * Callback method that is invoked by the system when the alarm time is reached.
+         */
+        public void onAlarm();
+    }
+
+    final class ListenerWrapper extends IAlarmListener.Stub implements Runnable {
+        final OnAlarmListener mListener;
+        Handler mHandler;
+        IAlarmCompleteListener mCompletion;
+
+        public ListenerWrapper(OnAlarmListener listener) {
+            mListener = listener;
+        }
+
+        public void setHandler(Handler h) {
+           mHandler = h;
+        }
+
+        public void cancel() {
+            try {
+                mService.remove(null, this);
+            } catch (RemoteException ex) {
+            }
+
+            synchronized (AlarmManager.class) {
+                if (sWrappers != null) {
+                    sWrappers.remove(mListener);
+                }
+            }
+        }
+
+        @Override
+        public void doAlarm(IAlarmCompleteListener alarmManager) {
+            mCompletion = alarmManager;
+            mHandler.post(this);
+        }
+
+        @Override
+        public void run() {
+            // Remove this listener from the wrapper cache first; the server side
+            // already considers it gone
+            synchronized (AlarmManager.class) {
+                if (sWrappers != null) {
+                    sWrappers.remove(mListener);
+                }
+            }
+
+            // Now deliver it to the app
+            try {
+                mListener.onAlarm();
+            } finally {
+                // No catch -- make sure to report completion to the system process,
+                // but continue to allow the exception to crash the app.
+
+                try {
+                    mCompletion.alarmComplete(this);
+                } catch (Exception e) {
+                    Log.e(TAG, "Unable to report completion to Alarm Manager!", e);
+                }
+            }
+        }
+    }
+
+    // Tracking of the OnAlarmListener -> wrapper mapping, for cancel() support.
+    // Access is synchronized on the AlarmManager class object.
+    //
+    // These are weak references so that we don't leak listener references if, for
+    // example, the pending-alarm messages are posted to a HandlerThread that is
+    // disposed of prior to alarm delivery.  The underlying messages will be GC'd
+    // but this static reference would still persist, orphaned, never deallocated.
+    private static WeakHashMap<OnAlarmListener, WeakReference<ListenerWrapper>> sWrappers;
 
     /**
      * package private on purpose
@@ -170,8 +258,10 @@
     AlarmManager(IAlarmManager service, Context ctx) {
         mService = service;
 
+        mPackageName = ctx.getPackageName();
         mTargetSdkVersion = ctx.getApplicationInfo().targetSdkVersion;
         mAlwaysExact = (mTargetSdkVersion < Build.VERSION_CODES.KITKAT);
+        mMainThreadHandler = new Handler(ctx.getMainLooper());
     }
 
     private long legacyExactLength() {
@@ -249,7 +339,37 @@
      * @see #RTC_WAKEUP
      */
     public void set(int type, long triggerAtMillis, PendingIntent operation) {
-        setImpl(type, triggerAtMillis, legacyExactLength(), 0, 0, operation, null, null);
+        setImpl(type, triggerAtMillis, legacyExactLength(), 0, 0, operation, null, null,
+                null, null, null);
+    }
+
+    /**
+     * Direct callback version of {@link #set(int, long, PendingIntent)}.  Rather than
+     * supplying a PendingIntent to be sent when the alarm time is reached, this variant
+     * supplies an {@link OnAlarmListener} instance that will be invoked at that time.
+     * <p>
+     * The OnAlarmListener's {@link OnAlarmListener#onAlarm() onAlarm()} method will be
+     * invoked via the specified target Handler, or on the application's main looper
+     * if {@code null} is passed as the {@code targetHandler} parameter.
+     *
+     * @param type One of {@link #ELAPSED_REALTIME}, {@link #ELAPSED_REALTIME_WAKEUP},
+     *         {@link #RTC}, or {@link #RTC_WAKEUP}.
+     * @param triggerAtMillis time in milliseconds that the alarm should go
+     *         off, using the appropriate clock (depending on the alarm type).
+     * @param tag string describing the alarm, used for logging and battery-use
+     *         attribution
+     * @param listener {@link OnAlarmListener} instance whose
+     *         {@link OnAlarmListener#onAlarm() onAlarm()} method will be
+     *         called when the alarm time is reached.  A given OnAlarmListener instance can
+     *         only be the target of a single pending alarm, just as a given PendingIntent
+     *         can only be used with one alarm at a time.
+     * @param targetHandler {@link Handler} on which to execute the listener's onAlarm()
+     *         callback, or {@code null} to run that callback on the main looper.
+     */
+    public void set(int type, long triggerAtMillis, String tag, OnAlarmListener listener,
+            Handler targetHandler) {
+        setImpl(type, triggerAtMillis, legacyExactLength(), 0, 0, null, listener, tag,
+                targetHandler, null, null);
     }
 
     /**
@@ -310,8 +430,8 @@
      */
     public void setRepeating(int type, long triggerAtMillis,
             long intervalMillis, PendingIntent operation) {
-        setImpl(type, triggerAtMillis, legacyExactLength(), intervalMillis, 0, operation, null,
-                null);
+        setImpl(type, triggerAtMillis, legacyExactLength(), intervalMillis, 0, operation,
+                null, null, null, null, null);
     }
 
     /**
@@ -361,7 +481,23 @@
      */
     public void setWindow(int type, long windowStartMillis, long windowLengthMillis,
             PendingIntent operation) {
-        setImpl(type, windowStartMillis, windowLengthMillis, 0, 0, operation, null, null);
+        setImpl(type, windowStartMillis, windowLengthMillis, 0, 0, operation,
+                null, null, null, null, null);
+    }
+
+    /**
+     * Direct callback version of {@link #setWindow(int, long, long, PendingIntent)}.  Rather
+     * than supplying a PendingIntent to be sent when the alarm time is reached, this variant
+     * supplies an {@link OnAlarmListener} instance that will be invoked at that time.
+     * <p>
+     * The OnAlarmListener {@link OnAlarmListener#onAlarm() onAlarm()} method will be
+     * invoked via the specified target Handler, or on the application's main looper
+     * if {@code null} is passed as the {@code targetHandler} parameter.
+     */
+    public void setWindow(int type, long windowStartMillis, long windowLengthMillis,
+            String tag, OnAlarmListener listener, Handler targetHandler) {
+        setImpl(type, windowStartMillis, windowLengthMillis, 0, 0, null, listener, tag,
+                targetHandler, null, null);
     }
 
     /**
@@ -399,7 +535,23 @@
      * @see #RTC_WAKEUP
      */
     public void setExact(int type, long triggerAtMillis, PendingIntent operation) {
-        setImpl(type, triggerAtMillis, WINDOW_EXACT, 0, 0, operation, null, null);
+        setImpl(type, triggerAtMillis, WINDOW_EXACT, 0, 0, operation, null, null, null,
+                null, null);
+    }
+
+    /**
+     * Direct callback version of {@link #setExact(int, long, PendingIntent)}.  Rather
+     * than supplying a PendingIntent to be sent when the alarm time is reached, this variant
+     * supplies an {@link OnAlarmListener} instance that will be invoked at that time.
+     * <p>
+     * The OnAlarmListener's {@link OnAlarmListener#onAlarm() onAlarm()} method will be
+     * invoked via the specified target Handler, or on the application's main looper
+     * if {@code null} is passed as the {@code targetHandler} parameter.
+     */
+    public void setExact(int type, long triggerAtMillis, String tag, OnAlarmListener listener,
+            Handler targetHandler) {
+        setImpl(type, triggerAtMillis, WINDOW_EXACT, 0, 0, null, listener, tag,
+                targetHandler, null, null);
     }
 
     /**
@@ -408,7 +560,8 @@
      * @hide
      */
     public void setIdleUntil(int type, long triggerAtMillis, PendingIntent operation) {
-        setImpl(type, triggerAtMillis, WINDOW_EXACT, 0, FLAG_IDLE_UNTIL, operation, null, null);
+        setImpl(type, triggerAtMillis, WINDOW_EXACT, 0, FLAG_IDLE_UNTIL, operation,
+                null, null, null, null, null);
     }
 
     /**
@@ -436,19 +589,38 @@
      * @see android.content.Intent#filterEquals
      */
     public void setAlarmClock(AlarmClockInfo info, PendingIntent operation) {
-        setImpl(RTC_WAKEUP, info.getTriggerTime(), WINDOW_EXACT, 0, 0, operation, null, info);
+        setImpl(RTC_WAKEUP, info.getTriggerTime(), WINDOW_EXACT, 0, 0, operation,
+                null, null, null, null, info);
     }
 
     /** @hide */
     @SystemApi
     public void set(int type, long triggerAtMillis, long windowMillis, long intervalMillis,
             PendingIntent operation, WorkSource workSource) {
-        setImpl(type, triggerAtMillis, windowMillis, intervalMillis, 0, operation, workSource,
-                null);
+        setImpl(type, triggerAtMillis, windowMillis, intervalMillis, 0, operation, null, null,
+                null, workSource, null);
+    }
+
+    /**
+     * Direct callback version of {@link #set(int, long, long, long, PendingIntent, WorkSource)}.
+     * Note that repeating alarms must use the PendingIntent variant, not an OnAlarmListener.
+     * <p>
+     * The OnAlarmListener's {@link OnAlarmListener#onAlarm() onAlarm()} method will be
+     * invoked via the specified target Handler, or on the application's main looper
+     * if {@code null} is passed as the {@code targetHandler} parameter.
+     *
+     * @hide
+     */
+    //@SystemApi
+    public void set(int type, long triggerAtMillis, long windowMillis, long intervalMillis,
+            OnAlarmListener listener, Handler targetHandler, WorkSource workSource) {
+        setImpl(type, triggerAtMillis, windowMillis, intervalMillis, 0, null, listener, null,
+                targetHandler, workSource, null);
     }
 
     private void setImpl(int type, long triggerAtMillis, long windowMillis, long intervalMillis,
-            int flags, PendingIntent operation, WorkSource workSource, AlarmClockInfo alarmClock) {
+            int flags, PendingIntent operation, final OnAlarmListener listener, String listenerTag,
+            Handler targetHandler, WorkSource workSource, AlarmClockInfo alarmClock) {
         if (triggerAtMillis < 0) {
             /* NOTYET
             if (mAlwaysExact) {
@@ -460,9 +632,30 @@
             triggerAtMillis = 0;
         }
 
+        ListenerWrapper recipientWrapper = null;
+        if (listener != null) {
+            synchronized (AlarmManager.class) {
+                if (sWrappers == null) {
+                    sWrappers = new WeakHashMap<OnAlarmListener, WeakReference<ListenerWrapper>>();
+                }
+
+                WeakReference<ListenerWrapper> wrapperRef = sWrappers.get(listener);
+                // no existing wrapper *or* we've lost our weak ref to it => build a new one
+                if (wrapperRef == null ||
+                        (recipientWrapper = wrapperRef.get()) == null) {
+                    recipientWrapper = new ListenerWrapper(listener);
+                    wrapperRef = new WeakReference<ListenerWrapper>(recipientWrapper);
+                    sWrappers.put(listener, wrapperRef);
+                }
+            }
+
+            final Handler handler = (targetHandler != null) ? targetHandler : mMainThreadHandler;
+            recipientWrapper.setHandler(handler);
+        }
+
         try {
-            mService.set(type, triggerAtMillis, windowMillis, intervalMillis, flags, operation,
-                    workSource, alarmClock);
+            mService.set(mPackageName, type, triggerAtMillis, windowMillis, intervalMillis, flags,
+                    operation, recipientWrapper, listenerTag, workSource, alarmClock);
         } catch (RemoteException ex) {
         }
     }
@@ -562,7 +755,8 @@
      */
     public void setInexactRepeating(int type, long triggerAtMillis,
             long intervalMillis, PendingIntent operation) {
-        setImpl(type, triggerAtMillis, WINDOW_HEURISTIC, intervalMillis, 0, operation, null, null);
+        setImpl(type, triggerAtMillis, WINDOW_HEURISTIC, intervalMillis, 0, operation, null,
+                null, null, null, null);
     }
 
     /**
@@ -611,8 +805,8 @@
      * @see #RTC_WAKEUP
      */
     public void setAndAllowWhileIdle(int type, long triggerAtMillis, PendingIntent operation) {
-        setImpl(type, triggerAtMillis, WINDOW_HEURISTIC, 0, FLAG_ALLOW_WHILE_IDLE, operation,
-                null, null);
+        setImpl(type, triggerAtMillis, WINDOW_HEURISTIC, 0, FLAG_ALLOW_WHILE_IDLE,
+                operation, null, null, null, null, null);
     }
 
     /**
@@ -666,7 +860,7 @@
      */
     public void setExactAndAllowWhileIdle(int type, long triggerAtMillis, PendingIntent operation) {
         setImpl(type, triggerAtMillis, WINDOW_EXACT, 0, FLAG_ALLOW_WHILE_IDLE, operation,
-                null, null);
+                null, null, null, null, null);
     }
 
     /**
@@ -680,13 +874,44 @@
      * @see #set
      */
     public void cancel(PendingIntent operation) {
+        if (operation == null) {
+            throw new NullPointerException("operation");
+        }
+
         try {
-            mService.remove(operation);
+            mService.remove(operation, null);
         } catch (RemoteException ex) {
         }
     }
 
     /**
+     * Remove any alarm scheduled to be delivered to the given {@link OnAlarmListener}.
+     *
+     * @param listener OnAlarmListener instance that is the target of a currently-set alarm.
+     */
+    public void cancel(OnAlarmListener listener) {
+        if (listener == null) {
+            throw new NullPointerException("listener");
+        }
+
+        ListenerWrapper wrapper = null;
+        synchronized (AlarmManager.class) {
+            final WeakReference<ListenerWrapper> wrapperRef;
+            wrapperRef = sWrappers.get(listener);
+            if (wrapperRef != null) {
+                wrapper = wrapperRef.get();
+            }
+        }
+
+        if (wrapper == null) {
+            Log.w(TAG, "Unrecognized alarm listener " + listener);
+            return;
+        }
+
+        wrapper.cancel();
+    }
+
+    /**
      * Set the system wall clock time.
      * Requires the permission android.permission.SET_TIME.
      *
diff --git a/core/java/android/app/IAlarmCompleteListener.aidl b/core/java/android/app/IAlarmCompleteListener.aidl
new file mode 100644
index 0000000..9f9ee40
--- /dev/null
+++ b/core/java/android/app/IAlarmCompleteListener.aidl
@@ -0,0 +1,27 @@
+/*
+** Copyright 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 android.app;
+
+import android.os.IBinder;
+
+/**
+ * Callback from app into system process to indicate that processing of
+ * a direct-call alarm has completed.
+ * {@hide}
+ */
+interface IAlarmCompleteListener {
+    void alarmComplete(in IBinder who);
+}
diff --git a/core/java/android/app/IAlarmListener.aidl b/core/java/android/app/IAlarmListener.aidl
new file mode 100644
index 0000000..a110d4d
--- /dev/null
+++ b/core/java/android/app/IAlarmListener.aidl
@@ -0,0 +1,29 @@
+/*
+** Copyright 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 android.app;
+
+import android.app.IAlarmCompleteListener;
+
+/**
+ * System private API for direct alarm callbacks (non-broadcast deliver).  See the
+ * AlarmManager#set() variants that take an AlarmReceiver callback object
+ * rather than a PendingIntent.
+ *
+ * {@hide}
+ */
+oneway interface IAlarmListener {
+    void doAlarm(in IAlarmCompleteListener callback);
+}
diff --git a/core/java/android/app/IAlarmManager.aidl b/core/java/android/app/IAlarmManager.aidl
index 327c00b..7b05b49 100644
--- a/core/java/android/app/IAlarmManager.aidl
+++ b/core/java/android/app/IAlarmManager.aidl
@@ -17,7 +17,9 @@
 package android.app;
 
 import android.app.AlarmManager;
+import android.app.IAlarmListener;
 import android.app.PendingIntent;
+import android.content.Intent;
 import android.os.WorkSource;
 
 /**
@@ -27,14 +29,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, int flags, in PendingIntent operation, in WorkSource workSource,
-            in AlarmManager.AlarmClockInfo alarmClock);
+    void set(String callingPackage, int type, long triggerAtTime, long windowLength,
+            long interval, int flags, in PendingIntent operation, in IAlarmListener listener,
+            String listenerTag, in WorkSource workSource, in AlarmManager.AlarmClockInfo alarmClock);
     boolean setTime(long millis);
     void setTimeZone(String zone);
-    void remove(in PendingIntent operation);
+    void remove(in PendingIntent operation, in IAlarmListener listener);
     long getNextWakeFromIdleTime();
     AlarmManager.AlarmClockInfo getNextAlarmClock(int userId);
 }
-
-
diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java
index ed6fc00..a5ddc12 100644
--- a/services/core/java/com/android/server/AlarmManagerService.java
+++ b/services/core/java/com/android/server/AlarmManagerService.java
@@ -20,7 +20,10 @@
 import android.app.ActivityManager;
 import android.app.ActivityManagerNative;
 import android.app.AlarmManager;
+import android.app.AppOpsManager;
 import android.app.BroadcastOptions;
+import android.app.IAlarmCompleteListener;
+import android.app.IAlarmListener;
 import android.app.IAlarmManager;
 import android.app.PendingIntent;
 import android.content.BroadcastReceiver;
@@ -94,12 +97,13 @@
     static final boolean DEBUG_BATCH = localLOGV || false;
     static final boolean DEBUG_VALIDATE = localLOGV || false;
     static final boolean DEBUG_ALARM_CLOCK = localLOGV || false;
+    static final boolean DEBUG_LISTENER_CALLBACK = localLOGV || false;
     static final boolean RECORD_ALARMS_IN_HISTORY = true;
     static final boolean RECORD_DEVICE_IDLE_ALARMS = false;
     static final int ALARM_EVENT = 1;
     static final String TIMEZONE_PROPERTY = "persist.sys.timezone";
 
-    static final Intent mBackgroundIntent
+    private final Intent mBackgroundIntent
             = new Intent().addFlags(Intent.FLAG_FROM_BACKGROUND);
     static final IncreasingTimeOrder sIncreasingTimeOrder = new IncreasingTimeOrder();
     
@@ -110,6 +114,8 @@
 
     final LocalLog mLog = new LocalLog(TAG);
 
+    AppOpsManager mAppOps;
+
     final Object mLock = new Object();
 
     long mNativeData;
@@ -124,7 +130,7 @@
     ClockReceiver mClockReceiver;
     InteractiveStateReceiver mInteractiveStateReceiver;
     private UninstallReceiver mUninstallReceiver;
-    final ResultReceiver mResultReceiver = new ResultReceiver();
+    final DeliveryTracker mDeliveryTracker = new DeliveryTracker();
     PendingIntent mTimeTickSender;
     PendingIntent mDateChangeSender;
     Random mRandom;
@@ -185,6 +191,7 @@
         private static final String KEY_ALLOW_WHILE_IDLE_LONG_TIME = "allow_while_idle_long_time";
         private static final String KEY_ALLOW_WHILE_IDLE_WHITELIST_DURATION
                 = "allow_while_idle_whitelist_duration";
+        private static final String KEY_LISTENER_TIMEOUT = "listener_timeout";
 
         private static final long DEFAULT_MIN_FUTURITY = 5 * 1000;
         private static final long DEFAULT_MIN_INTERVAL = 60 * 1000;
@@ -192,6 +199,8 @@
         private static final long DEFAULT_ALLOW_WHILE_IDLE_LONG_TIME = 9*60*1000;
         private static final long DEFAULT_ALLOW_WHILE_IDLE_WHITELIST_DURATION = 10*1000;
 
+        private static final long DEFAULT_LISTENER_TIMEOUT = 5 * 1000;
+
         // Minimum futurity of a new alarm
         public long MIN_FUTURITY = DEFAULT_MIN_FUTURITY;
 
@@ -208,6 +217,9 @@
         public long ALLOW_WHILE_IDLE_WHITELIST_DURATION
                 = DEFAULT_ALLOW_WHILE_IDLE_WHITELIST_DURATION;
 
+        // Direct alarm listener callback timeout
+        public long LISTENER_TIMEOUT = DEFAULT_LISTENER_TIMEOUT;
+
         private ContentResolver mResolver;
         private final KeyValueListParser mParser = new KeyValueListParser(',');
         private long mLastAllowWhileIdleWhitelistDuration = -1;
@@ -264,6 +276,8 @@
                 ALLOW_WHILE_IDLE_WHITELIST_DURATION = mParser.getLong(
                         KEY_ALLOW_WHILE_IDLE_WHITELIST_DURATION,
                         DEFAULT_ALLOW_WHILE_IDLE_WHITELIST_DURATION);
+                LISTENER_TIMEOUT = mParser.getLong(KEY_LISTENER_TIMEOUT,
+                        DEFAULT_LISTENER_TIMEOUT);
 
                 updateAllowWhileIdleMinTimeLocked();
                 updateAllowWhileIdleWhitelistDurationLocked();
@@ -281,6 +295,10 @@
             TimeUtils.formatDuration(MIN_INTERVAL, pw);
             pw.println();
 
+            pw.print("    "); pw.print(KEY_LISTENER_TIMEOUT); pw.print("=");
+            TimeUtils.formatDuration(LISTENER_TIMEOUT, pw);
+            pw.println();
+
             pw.print("    "); pw.print(KEY_ALLOW_WHILE_IDLE_SHORT_TIME); pw.print("=");
             TimeUtils.formatDuration(ALLOW_WHILE_IDLE_SHORT_TIME, pw);
             pw.println();
@@ -388,14 +406,21 @@
             return newStart;
         }
 
-        boolean remove(final PendingIntent operation) {
+        boolean remove(final PendingIntent operation, final IAlarmListener listener) {
+            if (operation == null && listener == null) {
+                if (localLOGV) {
+                    Slog.w(TAG, "requested remove() of null operation",
+                            new RuntimeException("here"));
+                }
+                return false;
+            }
             boolean didRemove = false;
             long newStart = 0;  // recalculate endpoints as we go
             long newEnd = Long.MAX_VALUE;
             int newFlags = 0;
             for (int i = 0; i < alarms.size(); ) {
                 Alarm alarm = alarms.get(i);
-                if (alarm.operation.equals(operation)) {
+                if (alarm.matches(operation, listener)) {
                     alarms.remove(i);
                     didRemove = true;
                     if (alarm.alarmClock != null) {
@@ -422,13 +447,20 @@
         }
 
         boolean remove(final String packageName) {
+            if (packageName == null) {
+                if (localLOGV) {
+                    Slog.w(TAG, "requested remove() of null packageName",
+                            new RuntimeException("here"));
+                }
+                return false;
+            }
             boolean didRemove = false;
             long newStart = 0;  // recalculate endpoints as we go
             long newEnd = Long.MAX_VALUE;
             int newFlags = 0;
             for (int i = 0; i < alarms.size(); ) {
                 Alarm alarm = alarms.get(i);
-                if (alarm.operation.getTargetPackage().equals(packageName)) {
+                if (alarm.matches(packageName)) {
                     alarms.remove(i);
                     didRemove = true;
                     if (alarm.alarmClock != null) {
@@ -460,7 +492,7 @@
             long newEnd = Long.MAX_VALUE;
             for (int i = 0; i < alarms.size(); ) {
                 Alarm alarm = alarms.get(i);
-                if (UserHandle.getUserId(alarm.operation.getCreatorUid()) == userHandle) {
+                if (UserHandle.getUserId(alarm.creatorUid) == userHandle) {
                     alarms.remove(i);
                     didRemove = true;
                     if (alarm.alarmClock != null) {
@@ -488,7 +520,7 @@
             final int N = alarms.size();
             for (int i = 0; i < N; i++) {
                 Alarm a = alarms.get(i);
-                if (a.operation.getTargetPackage().equals(packageName)) {
+                if (a.matches(packageName)) {
                     return true;
                 }
             }
@@ -565,7 +597,8 @@
             Alarm a = alarms.get(i);
 
             final int alarmPrio;
-            if (Intent.ACTION_TIME_TICK.equals(a.operation.getIntent().getAction())) {
+            if (a.operation != null
+                    && Intent.ACTION_TIME_TICK.equals(a.operation.getIntent().getAction())) {
                 alarmPrio = PRIO_TICK;
             } else if (a.wakeup) {
                 alarmPrio = PRIO_WAKEUP;
@@ -574,10 +607,13 @@
             }
 
             PriorityClass packagePrio = a.priorityClass;
-            if (packagePrio == null) packagePrio = mPriorities.get(a.operation.getCreatorPackage());
+            String alarmPackage = (a.operation != null)
+                    ? a.operation.getCreatorPackage()
+                    : a.packageName;
+            if (packagePrio == null) packagePrio = mPriorities.get(alarmPackage);
             if (packagePrio == null) {
                 packagePrio = a.priorityClass = new PriorityClass(); // lowest prio & stale sequence
-                mPriorities.put(a.operation.getCreatorPackage(), packagePrio);
+                mPriorities.put(alarmPackage, packagePrio);
             }
             a.priorityClass = packagePrio;
 
@@ -744,20 +780,27 @@
         }
     }
 
-    static final class InFlight extends Intent {
+    static final class InFlight {
         final PendingIntent mPendingIntent;
+        final IBinder mListener;
         final WorkSource mWorkSource;
+        final int mUid;
         final String mTag;
         final BroadcastStats mBroadcastStats;
         final FilterStats mFilterStats;
         final int mAlarmType;
 
-        InFlight(AlarmManagerService service, PendingIntent pendingIntent, WorkSource workSource,
-                int alarmType, String tag, long nowELAPSED) {
+        InFlight(AlarmManagerService service, PendingIntent pendingIntent, IAlarmListener listener,
+                WorkSource workSource, int uid, String alarmPkg, int alarmType, String tag,
+                long nowELAPSED) {
             mPendingIntent = pendingIntent;
+            mListener = listener != null ? listener.asBinder() : null;
             mWorkSource = workSource;
+            mUid = uid;
             mTag = tag;
-            mBroadcastStats = service.getStatsLocked(pendingIntent);
+            mBroadcastStats = (pendingIntent != null)
+                    ? service.getStatsLocked(pendingIntent)
+                    : service.getStatsLocked(uid, alarmPkg);
             FilterStats fs = mBroadcastStats.filterStats.get(mTag);
             if (fs == null) {
                 fs = new FilterStats(mBroadcastStats, mTag);
@@ -853,6 +896,7 @@
     public void onBootPhase(int phase) {
         if (phase == PHASE_SYSTEM_SERVICES_READY) {
             mConstants.start(getContext().getContentResolver());
+            mAppOps = (AppOpsManager) getContext().getSystemService(Context.APP_OPS_SERVICE);
         }
     }
 
@@ -905,15 +949,20 @@
             return;
         }
         synchronized (mLock) {
-            removeLocked(operation);
+            removeLocked(operation, null);
         }
     }
 
     void setImpl(int type, long triggerAtTime, long windowLength, long interval,
-            PendingIntent operation, int flags, WorkSource workSource,
-            AlarmManager.AlarmClockInfo alarmClock, int callingUid) {
-        if (operation == null) {
-            Slog.w(TAG, "set/setRepeating ignored because there is no intent");
+            PendingIntent operation, IAlarmListener directReceiver, String listenerTag,
+            int flags, WorkSource workSource, AlarmManager.AlarmClockInfo alarmClock,
+            int callingUid, String callingPackage) {
+        // must be *either* PendingIntent or AlarmReceiver, but not both
+        if ((operation == null && directReceiver == null)
+                || (operation != null && directReceiver != null)) {
+            Slog.w(TAG, "Alarms must either supply a PendingIntent or an AlarmReceiver");
+            // NB: previous releases failed silently here, so we are continuing to do the same
+            // rather than throw an IllegalArgumentException.
             return;
         }
 
@@ -971,17 +1020,19 @@
                         + " interval=" + interval + " flags=0x" + Integer.toHexString(flags));
             }
             setImplLocked(type, triggerAtTime, triggerElapsed, windowLength, maxElapsed,
-                    interval, operation, flags, true, workSource, alarmClock, callingUid);
+                    interval, operation, directReceiver, listenerTag, flags, true, workSource,
+                    alarmClock, callingUid, callingPackage);
         }
     }
 
     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 uid) {
+            long maxWhen, long interval, PendingIntent operation, IAlarmListener directReceiver,
+            String listenerTag, int flags, boolean doValidate, WorkSource workSource,
+            AlarmManager.AlarmClockInfo alarmClock, int callingUid, String callingPackage) {
         Alarm a = new Alarm(type, when, whenElapsed, windowLength, maxWhen, interval,
-                operation, workSource, flags, alarmClock, uid);
-        removeLocked(operation);
+                operation, directReceiver, listenerTag, workSource, flags, alarmClock,
+                callingUid, callingPackage);
+        removeLocked(operation, directReceiver);
         setImplLocked(a, false, doValidate);
     }
 
@@ -1109,10 +1160,31 @@
 
     private final IBinder mService = new IAlarmManager.Stub() {
         @Override
-        public void set(int type, long triggerAtTime, long windowLength, long interval, int flags,
-                PendingIntent operation, WorkSource workSource,
-                AlarmManager.AlarmClockInfo alarmClock) {
+        public void set(String callingPackage,
+                int type, long triggerAtTime, long windowLength, long interval, int flags,
+                PendingIntent operation, IAlarmListener directReceiver, String listenerTag,
+                WorkSource workSource, AlarmManager.AlarmClockInfo alarmClock) {
             final int callingUid = Binder.getCallingUid();
+
+            // make sure the caller is not lying about which package should be blamed for
+            // wakelock time spent in alarm delivery
+            mAppOps.checkPackage(callingUid, callingPackage);
+
+            // Repeating alarms must use PendingIntent, not direct listener
+            if (interval != 0) {
+                if (directReceiver != null) {
+                    throw new IllegalArgumentException("Repeating alarms cannot use AlarmReceivers");
+                }
+            }
+
+            // direct-callback alarms must be wakeup alarms (otherwise they should just be
+            // posting work to a Handler)
+            if (directReceiver != null) {
+                if (type != RTC_WAKEUP && type != ELAPSED_REALTIME_WAKEUP) {
+                    throw new IllegalArgumentException("Only wakeup alarms can use AlarmReceivers");
+                }
+            }
+
             if (workSource != null) {
                 getContext().enforcePermission(
                         android.Manifest.permission.UPDATE_DEVICE_STATS,
@@ -1149,8 +1221,8 @@
                 flags |= AlarmManager.FLAG_WAKE_FROM_IDLE | AlarmManager.FLAG_STANDALONE;
             }
 
-            setImpl(type, triggerAtTime, windowLength, interval, operation,
-                    flags, workSource, alarmClock, callingUid);
+            setImpl(type, triggerAtTime, windowLength, interval, operation, directReceiver,
+                    listenerTag, flags, workSource, alarmClock, callingUid, callingPackage);
         }
 
         @Override
@@ -1184,9 +1256,15 @@
         }
 
         @Override
-        public void remove(PendingIntent operation) {
-            removeImpl(operation);
+        public void remove(PendingIntent operation, IAlarmListener listener) {
+            if (operation == null && listener == null) {
+                Slog.w(TAG, "remove() with no intent or listener");
+                return;
+            }
 
+            synchronized (mLock) {
+                removeLocked(operation, listener);
+            }
         }
 
         @Override
@@ -1697,17 +1775,17 @@
         }
     }
 
-    private void removeLocked(PendingIntent operation) {
+    private void removeLocked(PendingIntent operation, IAlarmListener directReceiver) {
         boolean didRemove = false;
         for (int i = mAlarmBatches.size() - 1; i >= 0; i--) {
             Batch b = mAlarmBatches.get(i);
-            didRemove |= b.remove(operation);
+            didRemove |= b.remove(operation, directReceiver);
             if (b.size() == 0) {
                 mAlarmBatches.remove(i);
             }
         }
         for (int i = mPendingWhileIdleAlarms.size() - 1; i >= 0; i--) {
-            if (mPendingWhileIdleAlarms.get(i).operation.equals(operation)) {
+            if (mPendingWhileIdleAlarms.get(i).matches(operation, directReceiver)) {
                 // Don't set didRemove, since this doesn't impact the scheduled alarms.
                 mPendingWhileIdleAlarms.remove(i);
             }
@@ -1718,11 +1796,11 @@
                 Slog.v(TAG, "remove(operation) changed bounds; rebatching");
             }
             boolean restorePending = false;
-            if (mPendingIdleUntil != null && mPendingIdleUntil.operation.equals(operation)) {
+            if (mPendingIdleUntil != null && mPendingIdleUntil.matches(operation, directReceiver)) {
                 mPendingIdleUntil = null;
                 restorePending = true;
             }
-            if (mNextWakeFromIdle != null && mNextWakeFromIdle.operation.equals(operation)) {
+            if (mNextWakeFromIdle != null && mNextWakeFromIdle.matches(operation, directReceiver)) {
                 mNextWakeFromIdle = null;
             }
             rebatchAllAlarmsLocked(true);
@@ -1743,7 +1821,8 @@
             }
         }
         for (int i = mPendingWhileIdleAlarms.size() - 1; i >= 0; i--) {
-            if (mPendingWhileIdleAlarms.get(i).operation.getTargetPackage().equals(packageName)) {
+            final Alarm a = mPendingWhileIdleAlarms.get(i);
+            if (a.matches(packageName)) {
                 // Don't set didRemove, since this doesn't impact the scheduled alarms.
                 mPendingWhileIdleAlarms.remove(i);
             }
@@ -1769,7 +1848,7 @@
             }
         }
         for (int i = mPendingWhileIdleAlarms.size() - 1; i >= 0; i--) {
-            if (UserHandle.getUserId(mPendingWhileIdleAlarms.get(i).operation.getCreatorUid())
+            if (UserHandle.getUserId(mPendingWhileIdleAlarms.get(i).creatorUid)
                     == userHandle) {
                 // Don't set didRemove, since this doesn't impact the scheduled alarms.
                 mPendingWhileIdleAlarms.remove(i);
@@ -1825,7 +1904,8 @@
             }
         }
         for (int i = 0; i < mPendingWhileIdleAlarms.size(); i++) {
-            if (mPendingWhileIdleAlarms.get(i).operation.getTargetPackage().equals(packageName)) {
+            final Alarm a = mPendingWhileIdleAlarms.get(i);
+            if (a.matches(packageName)) {
                 return true;
             }
         }
@@ -1948,7 +2028,7 @@
                 triggerList.add(alarm);
                 if ((alarm.flags&AlarmManager.FLAG_WAKE_FROM_IDLE) != 0) {
                     EventLogTags.writeDeviceIdleWakeFromIdle(mPendingIdleUntil != null ? 1 : 0,
-                            alarm.tag);
+                            alarm.statsTag);
                 }
                 if (mPendingIdleUntil == alarm) {
                     mPendingIdleUntil = null;
@@ -1972,8 +2052,8 @@
                     final long nextElapsed = alarm.whenElapsed + delta;
                     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.uid);
+                            alarm.repeatInterval, alarm.operation, null, null, alarm.flags, true,
+                            alarm.workSource, alarm.alarmClock, alarm.uid, alarm.packageName);
                 }
 
                 if (alarm.wakeup) {
@@ -2024,11 +2104,15 @@
         public final long origWhen;
         public final boolean wakeup;
         public final PendingIntent operation;
-        public final String tag;
+        public final IAlarmListener listener;
+        public final String listenerTag;
+        public final String statsTag;
         public final WorkSource workSource;
         public final int flags;
         public final AlarmManager.AlarmClockInfo alarmClock;
         public final int uid;
+        public final int creatorUid;
+        public final String packageName;
         public int count;
         public long when;
         public long windowLength;
@@ -2038,8 +2122,9 @@
         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 _uid) {
+                long _interval, PendingIntent _op, IAlarmListener _rec, String _listenerTag,
+                WorkSource _ws, int _flags, AlarmManager.AlarmClockInfo _info,
+                int _uid, String _pkgName) {
             type = _type;
             origWhen = _when;
             wakeup = _type == AlarmManager.ELAPSED_REALTIME_WAKEUP
@@ -2050,16 +2135,42 @@
             maxWhenElapsed = _maxWhen;
             repeatInterval = _interval;
             operation = _op;
-            tag = makeTag(_op, _type);
+            listener = _rec;
+            listenerTag = _listenerTag;
+            statsTag = makeTag(_op, _listenerTag, _type);
             workSource = _ws;
             flags = _flags;
             alarmClock = _info;
             uid = _uid;
+            packageName = _pkgName;
+
+            creatorUid = (operation != null) ? operation.getCreatorUid() : uid;
         }
 
-        public static String makeTag(PendingIntent pi, int type) {
-            return pi.getTag(type == ELAPSED_REALTIME_WAKEUP || type == RTC_WAKEUP
-                    ? "*walarm*:" : "*alarm*:");
+        public static String makeTag(PendingIntent pi, String tag, int type) {
+            final String alarmString = type == ELAPSED_REALTIME_WAKEUP || type == RTC_WAKEUP
+                    ? "*walarm*:" : "*alarm*:";
+            return (pi != null) ? pi.getTag(alarmString) : (alarmString + tag);
+        }
+
+        public WakeupEvent makeWakeupEvent(long nowRTC) {
+            return new WakeupEvent(nowRTC, creatorUid,
+                    (operation != null)
+                        ? operation.getIntent().getAction()
+                        : ("<listener>:" + listenerTag));
+        }
+
+        // Returns true if either matches
+        public boolean matches(PendingIntent pi, IAlarmListener rec) {
+            return (operation != null)
+                    ? operation.equals(pi)
+                    : listener.asBinder().equals(rec.asBinder());
+        }
+
+        public boolean matches(String packageName) {
+            return (operation != null)
+                    ? packageName.equals(operation.getTargetPackage())
+                    : packageName.equals(this.packageName);
         }
 
         @Override
@@ -2072,7 +2183,11 @@
             sb.append(" when ");
             sb.append(when);
             sb.append(" ");
-            sb.append(operation.getTargetPackage());
+            if (operation != null) {
+                sb.append(operation.getTargetPackage());
+            } else {
+                sb.append(listener.asBinder().toString());
+            }
             sb.append('}');
             return sb.toString();
         }
@@ -2080,14 +2195,15 @@
         public void dump(PrintWriter pw, String prefix, long nowRTC, long nowELAPSED,
                 SimpleDateFormat sdf) {
             final boolean isRtc = (type == RTC || type == RTC_WAKEUP);
-            pw.print(prefix); pw.print("tag="); pw.println(tag);
+            pw.print(prefix); pw.print("tag="); pw.println(statsTag);
             pw.print(prefix); pw.print("type="); pw.print(type);
                     pw.print(" whenElapsed="); TimeUtils.formatDuration(whenElapsed,
                             nowELAPSED, pw);
+                    pw.print(" when=");
                     if (isRtc) {
-                        pw.print(" when="); pw.print(sdf.format(new Date(when)));
+                        pw.print(sdf.format(new Date(when)));
                     } else {
-                        pw.print(" when="); TimeUtils.formatDuration(when, nowELAPSED, pw);
+                        TimeUtils.formatDuration(when, nowELAPSED, pw);
                     }
                     pw.println();
             pw.print(prefix); pw.print("window="); TimeUtils.formatDuration(windowLength, pw);
@@ -2101,6 +2217,9 @@
                 pw.print(prefix); pw.print("  showIntent="); pw.println(alarmClock.getShowIntent());
             }
             pw.print(prefix); pw.print("operation="); pw.println(operation);
+            if (listener != null) {
+                pw.print(prefix); pw.print("listener="); pw.println(listener.asBinder());
+            }
         }
     }
 
@@ -2115,10 +2234,7 @@
             final int numAlarms = b.alarms.size();
             for (int nextAlarm = 0; nextAlarm < numAlarms; nextAlarm++) {
                 Alarm a = b.alarms.get(nextAlarm);
-                WakeupEvent e = new WakeupEvent(nowRTC,
-                        a.operation.getCreatorUid(),
-                        a.operation.getIntent().getAction());
-                mRecentWakeups.add(e);
+                mRecentWakeups.add(a.makeWakeupEvent(nowRTC));
             }
         }
     }
@@ -2181,80 +2297,14 @@
                     if (alarm.workSource != null && alarm.workSource.size() > 0) {
                         for (int wi=0; wi<alarm.workSource.size(); wi++) {
                             ActivityManagerNative.noteAlarmStart(
-                                    alarm.operation, alarm.workSource.get(wi), alarm.tag);
+                                    alarm.operation, alarm.workSource.get(wi), alarm.statsTag);
                         }
                     } else {
                         ActivityManagerNative.noteAlarmStart(
-                                alarm.operation, -1, alarm.tag);
+                                alarm.operation, alarm.uid, alarm.statsTag);
                     }
                 }
-                alarm.operation.send(getContext(), 0,
-                        mBackgroundIntent.putExtra(
-                                Intent.EXTRA_ALARM_COUNT, alarm.count),
-                        mResultReceiver, mHandler, null, allowWhileIdle ? mIdleOptions : null);
-
-                // we have an active broadcast so stay awake.
-                if (mBroadcastRefCount == 0) {
-                    setWakelockWorkSource(alarm.operation, alarm.workSource,
-                            alarm.type, alarm.tag, true);
-                    mWakeLock.acquire();
-                }
-                final InFlight inflight = new InFlight(AlarmManagerService.this,
-                        alarm.operation, alarm.workSource, alarm.type, alarm.tag, nowELAPSED);
-                mInFlight.add(inflight);
-                mBroadcastRefCount++;
-
-                if (allowWhileIdle) {
-                    // Record the last time this uid handled an ALLOW_WHILE_IDLE alarm.
-                    mLastAllowWhileIdleDispatch.put(alarm.uid, nowELAPSED);
-                    if (RECORD_DEVICE_IDLE_ALARMS) {
-                        IdleDispatchEntry ent = new IdleDispatchEntry();
-                        ent.uid = alarm.uid;
-                        ent.pkg = alarm.operation.getCreatorPackage();
-                        ent.tag = alarm.operation.getTag("");
-                        ent.op = "DELIVER";
-                        ent.elapsedRealtime = nowELAPSED;
-                        mAllowWhileIdleDispatches.add(ent);
-                    }
-                }
-
-                final BroadcastStats bs = inflight.mBroadcastStats;
-                bs.count++;
-                if (bs.nesting == 0) {
-                    bs.nesting = 1;
-                    bs.startTime = nowELAPSED;
-                } else {
-                    bs.nesting++;
-                }
-                final FilterStats fs = inflight.mFilterStats;
-                fs.count++;
-                if (fs.nesting == 0) {
-                    fs.nesting = 1;
-                    fs.startTime = nowELAPSED;
-                } else {
-                    fs.nesting++;
-                }
-                if (alarm.type == ELAPSED_REALTIME_WAKEUP
-                        || alarm.type == RTC_WAKEUP) {
-                    bs.numWakeup++;
-                    fs.numWakeup++;
-                    if (alarm.workSource != null && alarm.workSource.size() > 0) {
-                        for (int wi=0; wi<alarm.workSource.size(); wi++) {
-                            ActivityManagerNative.noteWakeupAlarm(
-                                    alarm.operation, alarm.workSource.get(wi),
-                                    alarm.workSource.getName(wi), alarm.tag);
-                        }
-                    } else {
-                        ActivityManagerNative.noteWakeupAlarm(
-                                alarm.operation, -1, null, alarm.tag);
-                    }
-                }
-            } catch (PendingIntent.CanceledException e) {
-                if (alarm.repeatInterval > 0) {
-                    // This IntentSender is no longer valid, but this
-                    // is a repeating alarm, so toss the hoser.
-                    removeImpl(alarm.operation);
-                }
+                mDeliveryTracker.deliverLocked(alarm, nowELAPSED, allowWhileIdle);
             } catch (RuntimeException e) {
                 Slog.w(TAG, "Failure sending alarm.", e);
             }
@@ -2384,9 +2434,10 @@
      * Attribute blame for a WakeLock.
      * @param pi PendingIntent to attribute blame to if ws is null.
      * @param ws WorkSource to attribute blame.
+     * @param knownUid attribution uid; < 0 if we need to derive it from the PendingIntent sender
      */
     void setWakelockWorkSource(PendingIntent pi, WorkSource ws, int type, String tag,
-            boolean first) {
+            int knownUid, boolean first) {
         try {
             final boolean unimportant = pi == mTimeTickSender;
             mWakeLock.setUnimportantForLogging(unimportant);
@@ -2401,8 +2452,9 @@
                 return;
             }
 
-            final int uid = ActivityManagerNative.getDefault()
-                    .getUidForIntentSender(pi.getTarget());
+            final int uid = (knownUid >= 0)
+                    ? knownUid
+                    : ActivityManagerNative.getDefault().getUidForIntentSender(pi.getTarget());
             if (uid >= 0) {
                 mWakeLock.setWorkSource(new WorkSource(uid));
                 return;
@@ -2419,35 +2471,49 @@
         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 static final int LISTENER_TIMEOUT = 5;
         
         public AlarmHandler() {
         }
         
         public void handleMessage(Message msg) {
-            if (msg.what == ALARM_EVENT) {
-                ArrayList<Alarm> triggerList = new ArrayList<Alarm>();
-                synchronized (mLock) {
-                    final long nowRTC = System.currentTimeMillis();
-                    final long nowELAPSED = SystemClock.elapsedRealtime();
-                    triggerAlarmsLocked(triggerList, nowELAPSED, nowRTC);
-                    updateNextAlarmClockLocked();
-                }
+            switch (msg.what) {
+                case ALARM_EVENT: {
+                    ArrayList<Alarm> triggerList = new ArrayList<Alarm>();
+                    synchronized (mLock) {
+                        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);
-                    try {
-                        alarm.operation.send();
-                    } catch (PendingIntent.CanceledException e) {
-                        if (alarm.repeatInterval > 0) {
-                            // This IntentSender is no longer valid, but this
-                            // is a repeating alarm, so toss the hoser.
-                            removeImpl(alarm.operation);
+                    // now trigger the alarms without the lock held
+                    for (int i=0; i<triggerList.size(); i++) {
+                        Alarm alarm = triggerList.get(i);
+                        try {
+                            alarm.operation.send();
+                        } catch (PendingIntent.CanceledException e) {
+                            if (alarm.repeatInterval > 0) {
+                                // This IntentSender is no longer valid, but this
+                                // is a repeating alarm, so toss the hoser.
+                                removeImpl(alarm.operation);
+                            }
                         }
                     }
+                    break;
                 }
-            } else if (msg.what == SEND_NEXT_ALARM_CLOCK_CHANGED) {
-                sendNextAlarmClockChanged();
+
+                case SEND_NEXT_ALARM_CLOCK_CHANGED:
+                    sendNextAlarmClockChanged();
+                    break;
+
+                case LISTENER_TIMEOUT:
+                    mDeliveryTracker.alarmTimedOut((IBinder) msg.obj);
+                    break;
+
+                default:
+                    // nope, just ignore it
+                    break;
             }
         }
     }
@@ -2489,8 +2555,8 @@
 
             final WorkSource workSource = null; // Let system take blame for time tick events.
             setImpl(ELAPSED_REALTIME, SystemClock.elapsedRealtime() + tickEventDelay, 0,
-                    0, mTimeTickSender, AlarmManager.FLAG_STANDALONE, workSource, null,
-                    Process.myUid());
+                    0, mTimeTickSender, null, null, AlarmManager.FLAG_STANDALONE, workSource,
+                    null, Process.myUid(), "android");
         }
 
         public void scheduleDateChangedEvent() {
@@ -2503,8 +2569,9 @@
             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,
-                    AlarmManager.FLAG_STANDALONE, workSource, null, Process.myUid());
+            setImpl(RTC, calendar.getTimeInMillis(), 0, 0, mDateChangeSender, null, null,
+                    AlarmManager.FLAG_STANDALONE, workSource, null,
+                    Process.myUid(), "android");
         }
     }
     
@@ -2602,80 +2669,260 @@
     private final BroadcastStats getStatsLocked(PendingIntent pi) {
         String pkg = pi.getCreatorPackage();
         int uid = pi.getCreatorUid();
+        return getStatsLocked(uid, pkg);
+    }
+
+    private final BroadcastStats getStatsLocked(int uid, String pkgName) {
         ArrayMap<String, BroadcastStats> uidStats = mBroadcastStats.get(uid);
         if (uidStats == null) {
             uidStats = new ArrayMap<String, BroadcastStats>();
             mBroadcastStats.put(uid, uidStats);
         }
-        BroadcastStats bs = uidStats.get(pkg);
+        BroadcastStats bs = uidStats.get(pkgName);
         if (bs == null) {
-            bs = new BroadcastStats(uid, pkg);
-            uidStats.put(pkg, bs);
+            bs = new BroadcastStats(uid, pkgName);
+            uidStats.put(pkgName, bs);
         }
         return bs;
     }
 
-    class ResultReceiver implements PendingIntent.OnFinished {
+    class DeliveryTracker extends IAlarmCompleteListener.Stub implements PendingIntent.OnFinished {
+        private InFlight removeLocked(PendingIntent pi, Intent intent) {
+            for (int i = 0; i < mInFlight.size(); i++) {
+                if (mInFlight.get(i).mPendingIntent == pi) {
+                    return mInFlight.remove(i);
+                }
+            }
+            mLog.w("No in-flight alarm for " + pi + " " + intent);
+            return null;
+        }
+
+        private InFlight removeLocked(IBinder listener) {
+            for (int i = 0; i < mInFlight.size(); i++) {
+                if (mInFlight.get(i).mListener == listener) {
+                    return mInFlight.remove(i);
+                }
+            }
+            mLog.w("No in-flight alarm for listener " + listener);
+            return null;
+        }
+
+        private void updateStatsLocked(InFlight inflight) {
+            final long nowELAPSED = SystemClock.elapsedRealtime();
+            BroadcastStats bs = inflight.mBroadcastStats;
+            bs.nesting--;
+            if (bs.nesting <= 0) {
+                bs.nesting = 0;
+                bs.aggregateTime += nowELAPSED - bs.startTime;
+            }
+            FilterStats fs = inflight.mFilterStats;
+            fs.nesting--;
+            if (fs.nesting <= 0) {
+                fs.nesting = 0;
+                fs.aggregateTime += nowELAPSED - fs.startTime;
+            }
+            if (RECORD_ALARMS_IN_HISTORY) {
+                if (inflight.mWorkSource != null && inflight.mWorkSource.size() > 0) {
+                    for (int wi=0; wi<inflight.mWorkSource.size(); wi++) {
+                        ActivityManagerNative.noteAlarmFinish(
+                                inflight.mPendingIntent, inflight.mWorkSource.get(wi), inflight.mTag);
+                    }
+                } else {
+                    ActivityManagerNative.noteAlarmFinish(
+                            inflight.mPendingIntent, inflight.mUid, inflight.mTag);
+                }
+            }
+        }
+
+        private void updateTrackingLocked(InFlight inflight) {
+            if (inflight != null) {
+                updateStatsLocked(inflight);
+            }
+            mBroadcastRefCount--;
+            if (mBroadcastRefCount == 0) {
+                mWakeLock.release();
+                if (mInFlight.size() > 0) {
+                    mLog.w("Finished all dispatches with " + mInFlight.size()
+                            + " remaining inflights");
+                    for (int i=0; i<mInFlight.size(); i++) {
+                        mLog.w("  Remaining #" + i + ": " + mInFlight.get(i));
+                    }
+                    mInFlight.clear();
+                }
+            } else {
+                // the next of our alarms is now in flight.  reattribute the wakelock.
+                if (mInFlight.size() > 0) {
+                    InFlight inFlight = mInFlight.get(0);
+                    setWakelockWorkSource(inFlight.mPendingIntent, inFlight.mWorkSource,
+                            inFlight.mAlarmType, inFlight.mTag, -1, false);
+                } else {
+                    // should never happen
+                    mLog.w("Alarm wakelock still held but sent queue empty");
+                    mWakeLock.setWorkSource(null);
+                }
+            }
+        }
+
+        /**
+         * Callback that arrives when a direct-call alarm reports that delivery has finished
+         */
+        @Override
+        public void alarmComplete(IBinder who) {
+            if (who == null) {
+                Slog.w(TAG, "Invalid alarmComplete: uid=" + Binder.getCallingUid()
+                        + " pid=" + Binder.getCallingPid());
+                return;
+            }
+
+            final long ident = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    mHandler.removeMessages(AlarmHandler.LISTENER_TIMEOUT, who);
+                    InFlight inflight = removeLocked(who);
+                    if (inflight != null) {
+                        if (DEBUG_LISTENER_CALLBACK) {
+                            Slog.i(TAG, "alarmComplete() from " + who);
+                        }
+                        updateTrackingLocked(inflight);
+                    } else {
+                        // Delivery timed out, and the timeout handling already took care of
+                        // updating our tracking here, so we needn't do anything further.
+                        if (DEBUG_LISTENER_CALLBACK) {
+                            Slog.i(TAG, "Late alarmComplete() from " + who);
+                        }
+                    }
+                }
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+        }
+
+        /**
+         * Callback that arrives when a PendingIntent alarm has finished delivery
+         */
+        @Override
         public void onSendFinished(PendingIntent pi, Intent intent, int resultCode,
                 String resultData, Bundle resultExtras) {
             synchronized (mLock) {
-                InFlight inflight = null;
-                for (int i=0; i<mInFlight.size(); i++) {
-                    if (mInFlight.get(i).mPendingIntent == pi) {
-                        inflight = mInFlight.remove(i);
-                        break;
-                    }
-                }
+                updateTrackingLocked(removeLocked(pi, intent));
+            }
+        }
+
+        /**
+         * Timeout of a direct-call alarm delivery
+         */
+        public void alarmTimedOut(IBinder who) {
+            synchronized (mLock) {
+                InFlight inflight = removeLocked(who);
                 if (inflight != null) {
-                    final long nowELAPSED = SystemClock.elapsedRealtime();
-                    BroadcastStats bs = inflight.mBroadcastStats;
-                    bs.nesting--;
-                    if (bs.nesting <= 0) {
-                        bs.nesting = 0;
-                        bs.aggregateTime += nowELAPSED - bs.startTime;
+                    // TODO: implement ANR policy for the target
+                    if (DEBUG_LISTENER_CALLBACK) {
+                        Slog.i(TAG, "Alarm listener " + who + " timed out in delivery");
                     }
-                    FilterStats fs = inflight.mFilterStats;
-                    fs.nesting--;
-                    if (fs.nesting <= 0) {
-                        fs.nesting = 0;
-                        fs.aggregateTime += nowELAPSED - fs.startTime;
-                    }
-                    if (RECORD_ALARMS_IN_HISTORY) {
-                        if (inflight.mWorkSource != null && inflight.mWorkSource.size() > 0) {
-                            for (int wi=0; wi<inflight.mWorkSource.size(); wi++) {
-                                ActivityManagerNative.noteAlarmFinish(
-                                        pi, inflight.mWorkSource.get(wi), inflight.mTag);
-                            }
-                        } else {
-                            ActivityManagerNative.noteAlarmFinish(
-                                    pi, -1, inflight.mTag);
-                        }
-                    }
+                    updateTrackingLocked(inflight);
                 } else {
-                    mLog.w("No in-flight alarm for " + pi + " " + intent);
+                    if (DEBUG_LISTENER_CALLBACK) {
+                        Slog.i(TAG, "Spurious timeout of listener " + who);
+                    }
                 }
-                mBroadcastRefCount--;
-                if (mBroadcastRefCount == 0) {
-                    mWakeLock.release();
-                    if (mInFlight.size() > 0) {
-                        mLog.w("Finished all broadcasts with " + mInFlight.size()
-                                + " remaining inflights");
-                        for (int i=0; i<mInFlight.size(); i++) {
-                            mLog.w("  Remaining #" + i + ": " + mInFlight.get(i));
-                        }
-                        mInFlight.clear();
+            }
+        }
+
+        /**
+         * Deliver an alarm and set up the post-delivery handling appropriately
+         */
+        public void deliverLocked(Alarm alarm, long nowELAPSED, boolean allowWhileIdle) {
+            if (alarm.operation != null) {
+                // PendingIntent alarm
+                try {
+                    alarm.operation.send(getContext(), 0,
+                            mBackgroundIntent.putExtra(
+                                    Intent.EXTRA_ALARM_COUNT, alarm.count),
+                                    mDeliveryTracker, mHandler, null,
+                                    allowWhileIdle ? mIdleOptions : null);
+                } catch (PendingIntent.CanceledException e) {
+                    if (alarm.repeatInterval > 0) {
+                        // This IntentSender is no longer valid, but this
+                        // is a repeating alarm, so toss it
+                        removeImpl(alarm.operation);
+                    }
+                }
+            } else {
+                // Direct listener callback alarm
+                try {
+                    if (DEBUG_LISTENER_CALLBACK) {
+                        Slog.v(TAG, "Alarm to uid=" + alarm.uid
+                                + " listener=" + alarm.listener.asBinder());
+                    }
+                    alarm.listener.doAlarm(this);
+                    mHandler.sendMessageDelayed(
+                            mHandler.obtainMessage(AlarmHandler.LISTENER_TIMEOUT,
+                                    alarm.listener.asBinder()),
+                            mConstants.LISTENER_TIMEOUT);
+                } catch (Exception e) {
+                    if (DEBUG_LISTENER_CALLBACK) {
+                        Slog.i(TAG, "Alarm undeliverable to listener "
+                                + alarm.listener.asBinder(), e);
+                    }
+                }
+            }
+
+            // The alarm is now in flight; now arrange wakelock and stats tracking
+            if (mBroadcastRefCount == 0) {
+                setWakelockWorkSource(alarm.operation, alarm.workSource,
+                        alarm.type, alarm.statsTag, (alarm.operation == null) ? alarm.uid : -1,
+                        true);
+                mWakeLock.acquire();
+            }
+            final InFlight inflight = new InFlight(AlarmManagerService.this,
+                    alarm.operation, alarm.listener, alarm.workSource, alarm.uid,
+                    alarm.packageName, alarm.type, alarm.statsTag, nowELAPSED);
+            mInFlight.add(inflight);
+            mBroadcastRefCount++;
+
+            if (allowWhileIdle) {
+                // Record the last time this uid handled an ALLOW_WHILE_IDLE alarm.
+                mLastAllowWhileIdleDispatch.put(alarm.uid, nowELAPSED);
+                if (RECORD_DEVICE_IDLE_ALARMS) {
+                    IdleDispatchEntry ent = new IdleDispatchEntry();
+                    ent.uid = alarm.uid;
+                    ent.pkg = alarm.packageName;
+                    ent.tag = alarm.statsTag;
+                    ent.op = "DELIVER";
+                    ent.elapsedRealtime = nowELAPSED;
+                    mAllowWhileIdleDispatches.add(ent);
+                }
+            }
+
+            final BroadcastStats bs = inflight.mBroadcastStats;
+            bs.count++;
+            if (bs.nesting == 0) {
+                bs.nesting = 1;
+                bs.startTime = nowELAPSED;
+            } else {
+                bs.nesting++;
+            }
+            final FilterStats fs = inflight.mFilterStats;
+            fs.count++;
+            if (fs.nesting == 0) {
+                fs.nesting = 1;
+                fs.startTime = nowELAPSED;
+            } else {
+                fs.nesting++;
+            }
+            if (alarm.type == ELAPSED_REALTIME_WAKEUP
+                    || alarm.type == RTC_WAKEUP) {
+                bs.numWakeup++;
+                fs.numWakeup++;
+                if (alarm.workSource != null && alarm.workSource.size() > 0) {
+                    for (int wi=0; wi<alarm.workSource.size(); wi++) {
+                        ActivityManagerNative.noteWakeupAlarm(
+                                alarm.operation, alarm.workSource.get(wi),
+                                alarm.workSource.getName(wi), alarm.statsTag);
                     }
                 } else {
-                    // the next of our alarms is now in flight.  reattribute the wakelock.
-                    if (mInFlight.size() > 0) {
-                        InFlight inFlight = mInFlight.get(0);
-                        setWakelockWorkSource(inFlight.mPendingIntent, inFlight.mWorkSource,
-                                inFlight.mAlarmType, inFlight.mTag, false);
-                    } else {
-                        // should never happen
-                        mLog.w("Alarm wakelock still held but sent queue empty");
-                        mWakeLock.setWorkSource(null);
-                    }
+                    ActivityManagerNative.noteWakeupAlarm(
+                            alarm.operation, -1, alarm.packageName, alarm.statsTag);
                 }
             }
         }
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index d5ee5e8..90a119d 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -11364,7 +11364,7 @@
     }
 
     public void noteWakeupAlarm(IIntentSender sender, int sourceUid, String sourcePkg, String tag) {
-        if (!(sender instanceof PendingIntentRecord)) {
+        if (sender != null && !(sender instanceof PendingIntentRecord)) {
             return;
         }
         final PendingIntentRecord rec = (PendingIntentRecord)sender;
@@ -11373,7 +11373,12 @@
             if (mBatteryStatsService.isOnBattery()) {
                 mBatteryStatsService.enforceCallingPermission();
                 int MY_UID = Binder.getCallingUid();
-                int uid = rec.uid == MY_UID ? Process.SYSTEM_UID : rec.uid;
+                final int uid;
+                if (sender == null) {
+                    uid = sourceUid;
+                } else {
+                    uid = rec.uid == MY_UID ? Process.SYSTEM_UID : rec.uid;
+                }
                 BatteryStatsImpl.Uid.Pkg pkg =
                     stats.getPackageStatsLocked(sourceUid >= 0 ? sourceUid : uid,
                             sourcePkg != null ? sourcePkg : rec.key.packageName);
@@ -11383,7 +11388,7 @@
     }
 
     public void noteAlarmStart(IIntentSender sender, int sourceUid, String tag) {
-        if (!(sender instanceof PendingIntentRecord)) {
+        if (sender != null && !(sender instanceof PendingIntentRecord)) {
             return;
         }
         final PendingIntentRecord rec = (PendingIntentRecord)sender;
@@ -11391,13 +11396,18 @@
         synchronized (stats) {
             mBatteryStatsService.enforceCallingPermission();
             int MY_UID = Binder.getCallingUid();
-            int uid = rec.uid == MY_UID ? Process.SYSTEM_UID : rec.uid;
+            final int uid;
+            if (sender == null) {
+                uid = sourceUid;
+            } else {
+                uid = rec.uid == MY_UID ? Process.SYSTEM_UID : rec.uid;
+            }
             mBatteryStatsService.noteAlarmStart(tag, sourceUid >= 0 ? sourceUid : uid);
         }
     }
 
     public void noteAlarmFinish(IIntentSender sender, int sourceUid, String tag) {
-        if (!(sender instanceof PendingIntentRecord)) {
+        if (sender != null && !(sender instanceof PendingIntentRecord)) {
             return;
         }
         final PendingIntentRecord rec = (PendingIntentRecord)sender;
@@ -11405,7 +11415,12 @@
         synchronized (stats) {
             mBatteryStatsService.enforceCallingPermission();
             int MY_UID = Binder.getCallingUid();
-            int uid = rec.uid == MY_UID ? Process.SYSTEM_UID : rec.uid;
+            final int uid;
+            if (sender == null) {
+                uid = sourceUid;
+            } else {
+                uid = rec.uid == MY_UID ? Process.SYSTEM_UID : rec.uid;
+            }
             mBatteryStatsService.noteAlarmFinish(tag, sourceUid >= 0 ? sourceUid : uid);
         }
     }
diff --git a/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java
index 90b4f43..c12f978 100644
--- a/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java
@@ -876,11 +876,12 @@
     }
 
     private void expectSystemReady() throws Exception {
-        mAlarmManager.remove(isA(PendingIntent.class));
+        mAlarmManager.remove(isA(PendingIntent.class), null);
         expectLastCall().anyTimes();
 
-        mAlarmManager.set(eq(AlarmManager.ELAPSED_REALTIME), anyLong(), anyLong(), anyLong(),
-                anyInt(), isA(PendingIntent.class), isA(WorkSource.class),
+        mAlarmManager.set(getContext().getPackageName(),
+                eq(AlarmManager.ELAPSED_REALTIME), anyLong(), anyLong(), anyLong(),
+                anyInt(), isA(PendingIntent.class), null, null, isA(WorkSource.class),
                 isA(AlarmManager.AlarmClockInfo.class));
         expectLastCall().atLeastOnce();