Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 14a7bb0d authored by Christopher Tate's avatar Christopher Tate
Browse files

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
parent 2d3c59c3
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -68,6 +68,8 @@ LOCAL_SRC_FILES += \
	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 \
+8 −0
Original line number Diff line number Diff line
@@ -3764,17 +3764,21 @@ package android.app {
  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 @@ package android.app {
    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);
+8 −0
Original line number Diff line number Diff line
@@ -3873,18 +3873,22 @@ package android.app {
  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 @@ package android.app {
    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);
+4 −3
Original line number Diff line number Diff line
@@ -116,21 +116,22 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
    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) {
        }
    }
+242 −17
Original line number Diff line number Diff line
@@ -21,15 +21,21 @@ import android.annotation.SystemApi;
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 @@ import java.io.IOException;
 * 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 class AlarmManager {
    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 @@ public class AlarmManager {
    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 @@ public class AlarmManager {
     * @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 class AlarmManager {
     */
    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 class AlarmManager {
     */
    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 @@ public class AlarmManager {
     * @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 @@ public class AlarmManager {
     * @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 @@ public class AlarmManager {
     * @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 @@ public class AlarmManager {
            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 class AlarmManager {
     */
    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 @@ public class AlarmManager {
     * @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 class AlarmManager {
     */
    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,12 +874,43 @@ public class AlarmManager {
     * @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.
Loading