All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.robolectric.shadows.ShadowAlarmManager Maven / Gradle / Ivy

package org.robolectric.shadows;

import static android.app.AlarmManager.RTC_WAKEUP;
import static org.robolectric.util.reflector.Reflector.reflector;

import android.app.AlarmManager;
import android.app.AlarmManager.AlarmClockInfo;
import android.app.AlarmManager.OnAlarmListener;
import android.app.PendingIntent;
import android.app.PendingIntent.CanceledException;
import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;
import android.os.Handler;
import android.os.Looper;
import android.os.SystemClock;
import android.os.WorkSource;
import androidx.annotation.GuardedBy;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import com.google.common.collect.Iterables;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.PriorityQueue;
import java.util.TimeZone;
import java.util.concurrent.Executor;
import java.util.concurrent.RejectedExecutionException;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.annotation.RealObject;
import org.robolectric.annotation.Resetter;
import org.robolectric.util.reflector.Direct;
import org.robolectric.util.reflector.ForType;

/** Shadow for {@link android.app.AlarmManager}. */
@Implements(AlarmManager.class)
public class ShadowAlarmManager {

  public static final long WINDOW_EXACT = 0;
  public static final long WINDOW_HEURISTIC = -1;

  private static final TimeZone DEFAULT_TIMEZONE = TimeZone.getDefault();

  private static boolean canScheduleExactAlarms;
  private static boolean autoSchedule;

  private final Handler schedulingHandler = new Handler(Looper.getMainLooper());

  @GuardedBy("scheduledAlarms")
  private final PriorityQueue scheduledAlarms = new PriorityQueue<>();

  @RealObject private AlarmManager realObject;

  @Resetter
  public static void reset() {
    TimeZone.setDefault(DEFAULT_TIMEZONE);
    canScheduleExactAlarms = false;
    autoSchedule = false;
  }

  /**
   * When set to true, automatically schedules alarms to fire at the appropriate time (with respect
   * to the main Looper time) when they are set. This means that a test as below could be expected
   * to pass:
   *
   * 
{@code
   * shadowOf(alarmManager).setAutoSchedule(true);
   * AlarmManager.OnAlarmListener listener = mock(AlarmManager.OnAlarmListener.class);
   * alarmManager.setExact(
   *   ELAPSED_REALTIME_WAKEUP,
   *   SystemClock.elapsedRealtime() + 10,
   *   "tag",
   *   listener,
   *   new Handler(Looper.getMainLooper()));
   * shadowOf(Looper.getMainLooper()).idleFor(Duration.ofMillis(10));
   * verify(listener).onAlarm();
   * }
* *

Alarms are always scheduled with respect to the trigger/window start time - there is no * emulation of alarms being reordered, rescheduled, or delayed, as might happen on a real device. * If emulating this is necessary, see {@link #fireAlarm(ScheduledAlarm)}. * *

{@link AlarmManager.OnAlarmListener} alarms will be run on the correct Handler/Executor as * specified when the alarm is set. */ public static void setAutoSchedule(boolean autoSchedule) { ShadowAlarmManager.autoSchedule = autoSchedule; } @Implementation protected void set(int type, long triggerAtMs, PendingIntent operation) { setImpl(type, triggerAtMs, WINDOW_HEURISTIC, 0L, operation, null, null, false); } @Implementation(minSdk = VERSION_CODES.N) protected void set( int type, long triggerAtMs, @Nullable String tag, OnAlarmListener listener, @Nullable Handler handler) { setImpl( type, triggerAtMs, WINDOW_HEURISTIC, 0L, tag, listener, new HandlerExecutor(handler), null, false); } @Implementation protected void setRepeating( int type, long triggerAtMs, long intervalMs, PendingIntent operation) { setImpl(type, triggerAtMs, WINDOW_HEURISTIC, intervalMs, operation, null, null, false); } @Implementation(minSdk = VERSION_CODES.KITKAT) protected void setWindow( int type, long windowStartMs, long windowLengthMs, PendingIntent operation) { setImpl(type, windowStartMs, windowLengthMs, 0L, operation, null, null, false); } @Implementation(minSdk = VERSION_CODES.N) protected void setWindow( int type, long windowStartMs, long windowLengthMs, @Nullable String tag, OnAlarmListener listener, @Nullable Handler handler) { setImpl( type, windowStartMs, windowLengthMs, 0L, tag, listener, new HandlerExecutor(handler), null, false); } @Implementation(minSdk = 34) protected void setWindow( int type, long windowStartMs, long windowLengthMs, @Nullable String tag, Executor executor, OnAlarmListener listener) { setImpl(type, windowStartMs, windowLengthMs, 0L, tag, listener, executor, null, false); } @Implementation(minSdk = 34) protected void setWindow( int type, long windowStartMs, long windowLengthMs, @Nullable String tag, Executor executor, WorkSource workSource, OnAlarmListener listener) { setImpl(type, windowStartMs, windowLengthMs, 0L, tag, listener, executor, workSource, false); } @Implementation(minSdk = VERSION_CODES.S) protected void setPrioritized( int type, long windowStartMs, long windowLengthMs, @Nullable String tag, Executor executor, OnAlarmListener listener) { Objects.requireNonNull(executor); Objects.requireNonNull(listener); setImpl(type, windowStartMs, windowLengthMs, 0L, tag, listener, executor, null, true); } @Implementation(minSdk = VERSION_CODES.KITKAT) protected void setExact(int type, long triggerAtMs, PendingIntent operation) { setImpl(type, triggerAtMs, WINDOW_EXACT, 0L, operation, null, null, false); } @Implementation(minSdk = VERSION_CODES.N) protected void setExact( int type, long triggerAtTime, @Nullable String tag, OnAlarmListener listener, @Nullable Handler targetHandler) { setImpl( type, triggerAtTime, WINDOW_EXACT, 0L, tag, listener, new HandlerExecutor(targetHandler), null, false); } @RequiresApi(VERSION_CODES.LOLLIPOP) @Implementation(minSdk = VERSION_CODES.LOLLIPOP) protected void setAlarmClock(AlarmClockInfo info, PendingIntent operation) { setImpl(RTC_WAKEUP, info.getTriggerTime(), WINDOW_EXACT, 0L, operation, null, info, true); } @Implementation(minSdk = VERSION_CODES.KITKAT) protected void set( int type, long triggerAtMs, long windowLengthMs, long intervalMs, PendingIntent operation, @Nullable WorkSource workSource) { setImpl(type, triggerAtMs, windowLengthMs, intervalMs, operation, workSource, null, false); } @Implementation(minSdk = VERSION_CODES.N) protected void set( int type, long triggerAtMs, long windowLengthMs, long intervalMs, @Nullable String tag, OnAlarmListener listener, @Nullable Handler targetHandler, @Nullable WorkSource workSource) { setImpl( type, triggerAtMs, windowLengthMs, intervalMs, tag, listener, new HandlerExecutor(targetHandler), workSource, false); } @Implementation(minSdk = VERSION_CODES.N) protected void set( int type, long triggerAtMs, long windowLengthMs, long intervalMs, OnAlarmListener listener, @Nullable Handler targetHandler, @Nullable WorkSource workSource) { setImpl( type, triggerAtMs, windowLengthMs, intervalMs, null, listener, new HandlerExecutor(targetHandler), workSource, false); } @Implementation(minSdk = VERSION_CODES.S) protected void setExact( int type, long triggerAtMs, @Nullable String tag, Executor executor, WorkSource workSource, OnAlarmListener listener) { Objects.requireNonNull(workSource); setImpl(type, triggerAtMs, WINDOW_EXACT, 0L, tag, listener, executor, workSource, false); } @Implementation protected void setInexactRepeating( int type, long triggerAtMs, long intervalMillis, PendingIntent operation) { setImpl(type, triggerAtMs, WINDOW_HEURISTIC, intervalMillis, operation, null, null, false); } @Implementation(minSdk = VERSION_CODES.M) protected void setAndAllowWhileIdle(int type, long triggerAtMs, PendingIntent operation) { setImpl(type, triggerAtMs, WINDOW_HEURISTIC, 0L, operation, null, null, true); } @Implementation(minSdk = VERSION_CODES.M) protected void setExactAndAllowWhileIdle(int type, long triggerAtMs, PendingIntent operation) { setImpl(type, triggerAtMs, WINDOW_EXACT, 0L, operation, null, null, true); } @Implementation(minSdk = 34) protected void setExactAndAllowWhileIdle( int type, long triggerAtMs, @Nullable String tag, Executor executor, @Nullable WorkSource workSource, OnAlarmListener listener) { setImpl(type, triggerAtMs, WINDOW_EXACT, 0L, tag, listener, executor, workSource, true); } @Implementation protected void cancel(PendingIntent operation) { synchronized (scheduledAlarms) { Iterables.removeIf( scheduledAlarms, alarm -> { if (operation.equals(alarm.operation)) { alarm.deschedule(); return true; } return false; }); } } @Implementation(minSdk = VERSION_CODES.N) protected void cancel(OnAlarmListener listener) { synchronized (scheduledAlarms) { Iterables.removeIf( scheduledAlarms, alarm -> { if (listener.equals(alarm.onAlarmListener)) { alarm.deschedule(); return true; } return false; }); } } @Implementation(minSdk = 34) protected void cancelAll() { synchronized (scheduledAlarms) { for (InternalScheduledAlarm alarm : scheduledAlarms) { alarm.deschedule(); } scheduledAlarms.clear(); } } @Implementation protected void setTimeZone(String timeZone) { // Do the real check first reflector(AlarmManagerReflector.class, realObject).setTimeZone(timeZone); // Then do the right side effect TimeZone.setDefault(TimeZone.getTimeZone(timeZone)); } @Implementation(minSdk = VERSION_CODES.S) protected boolean canScheduleExactAlarms() { return canScheduleExactAlarms; } @RequiresApi(VERSION_CODES.LOLLIPOP) @Implementation(minSdk = VERSION_CODES.LOLLIPOP) @Nullable protected AlarmClockInfo getNextAlarmClock() { synchronized (scheduledAlarms) { for (ScheduledAlarm scheduledAlarm : scheduledAlarms) { AlarmClockInfo alarmClockInfo = scheduledAlarm.getAlarmClockInfo(); if (alarmClockInfo != null) { return alarmClockInfo; } } return null; } } private void setImpl( int type, long triggerAtMs, long windowLengthMs, long intervalMs, PendingIntent operation, @Nullable WorkSource workSource, @Nullable Object alarmClockInfo, boolean allowWhileIdle) { synchronized (scheduledAlarms) { cancel(operation); scheduledAlarms.add( new InternalScheduledAlarm( type, triggerAtMs, windowLengthMs, intervalMs, operation, workSource, alarmClockInfo, allowWhileIdle) .schedule()); } } private void setImpl( int type, long triggerAtMs, long windowLengthMs, long intervalMs, @Nullable String tag, OnAlarmListener listener, Executor executor, @Nullable WorkSource workSource, boolean allowWhileIdle) { synchronized (scheduledAlarms) { cancel(listener); scheduledAlarms.add( new InternalScheduledAlarm( type, triggerAtMs, windowLengthMs, intervalMs, tag, listener, executor, workSource, null, allowWhileIdle) .schedule()); } } /** * Returns the earliest scheduled alarm and removes it from the list of scheduled alarms. * * @deprecated Prefer to use {@link ShadowAlarmManager#setAutoSchedule(boolean)} in combination * with incrementing time to actually run alarms and test their side-effects. */ @Deprecated @Nullable public ScheduledAlarm getNextScheduledAlarm() { synchronized (scheduledAlarms) { InternalScheduledAlarm alarm = scheduledAlarms.poll(); if (alarm != null) { alarm.deschedule(); } return alarm; } } /** Returns the earliest scheduled alarm. */ @Nullable public ScheduledAlarm peekNextScheduledAlarm() { synchronized (scheduledAlarms) { return scheduledAlarms.peek(); } } /** Returns a list of all scheduled alarms, ordered from earliest time to latest time. */ public List getScheduledAlarms() { synchronized (scheduledAlarms) { return new ArrayList<>(scheduledAlarms); } } /** * Immediately removes the given alarm from the list of scheduled alarms (and then reschedules it * in the case of a repeating alarm) and fires it. The given alarm must on the list of scheduled * alarms prior to being fired. * *

Generally prefer to use {@link ShadowAlarmManager#setAutoSchedule(boolean)} in combination * with advancing time on the main Looper in order to test alarms - however this method can be * useful to emulate rescheduled, reordered, or delayed alarms, as may happen on a real device. */ public void fireAlarm(ScheduledAlarm alarm) { synchronized (scheduledAlarms) { if (!scheduledAlarms.contains(alarm)) { throw new IllegalArgumentException(); } ((InternalScheduledAlarm) alarm).deschedule(); ((InternalScheduledAlarm) alarm).run(); } } /** * Sets the schedule exact alarm state reported by {@link AlarmManager#canScheduleExactAlarms()}, * but has no effect otherwise. */ public static void setCanScheduleExactAlarms(boolean scheduleExactAlarms) { canScheduleExactAlarms = scheduleExactAlarms; } /** Represents a set alarm. */ public static class ScheduledAlarm implements Comparable { @Deprecated public final int type; @Deprecated public final long triggerAtTime; private final long windowLengthMs; @Deprecated public final long interval; @Nullable private final String tag; @Deprecated @Nullable public final PendingIntent operation; @Deprecated @Nullable public final OnAlarmListener onAlarmListener; @Deprecated @Nullable public final Executor executor; @Nullable private final WorkSource workSource; @Nullable private final Object alarmClockInfo; @Deprecated public final boolean allowWhileIdle; @Deprecated @Nullable public final PendingIntent showIntent; @Deprecated @Nullable public final Handler handler; @Deprecated public ScheduledAlarm( int type, long triggerAtMs, PendingIntent operation, PendingIntent showIntent) { this(type, triggerAtMs, 0, operation, showIntent); } @Deprecated public ScheduledAlarm( int type, long triggerAtMs, long intervalMs, PendingIntent operation, PendingIntent showIntent) { this(type, triggerAtMs, intervalMs, operation, showIntent, false); } @Deprecated public ScheduledAlarm( int type, long triggerAtMs, long intervalMs, PendingIntent operation, PendingIntent showIntent, boolean allowWhileIdle) { this( type, triggerAtMs, intervalMs, WINDOW_HEURISTIC, operation, null, VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP && showIntent != null ? new AlarmClockInfo(triggerAtMs, showIntent) : null, allowWhileIdle); } protected ScheduledAlarm( int type, long triggerAtMs, long windowLengthMs, long intervalMs, PendingIntent operation, @Nullable WorkSource workSource, @Nullable Object alarmClockInfo, boolean allowWhileIdle) { this.type = type; this.triggerAtTime = triggerAtMs; this.windowLengthMs = windowLengthMs; this.interval = intervalMs; this.tag = null; this.operation = Objects.requireNonNull(operation); this.onAlarmListener = null; this.executor = null; this.workSource = workSource; this.alarmClockInfo = alarmClockInfo; this.allowWhileIdle = allowWhileIdle; this.handler = null; if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP && alarmClockInfo != null) { this.showIntent = ((AlarmClockInfo) alarmClockInfo).getShowIntent(); } else { this.showIntent = null; } } protected ScheduledAlarm( int type, long triggerAtMs, long windowLengthMs, long intervalMs, @Nullable String tag, OnAlarmListener listener, Executor executor, @Nullable WorkSource workSource, @Nullable Object alarmClockInfo, boolean allowWhileIdle) { this.type = type; this.triggerAtTime = triggerAtMs; this.windowLengthMs = windowLengthMs; this.interval = intervalMs; this.tag = tag; this.operation = null; this.onAlarmListener = Objects.requireNonNull(listener); this.executor = Objects.requireNonNull(executor); this.workSource = workSource; this.alarmClockInfo = alarmClockInfo; this.allowWhileIdle = allowWhileIdle; if (executor instanceof HandlerExecutor) { this.handler = ((HandlerExecutor) executor).handler; } else { this.handler = null; } if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP && alarmClockInfo != null) { this.showIntent = ((AlarmClockInfo) alarmClockInfo).getShowIntent(); } else { this.showIntent = null; } } protected ScheduledAlarm(long triggerAtMs, ScheduledAlarm alarm) { this.type = alarm.type; this.triggerAtTime = triggerAtMs; this.windowLengthMs = alarm.windowLengthMs; this.interval = alarm.interval; this.tag = alarm.tag; this.operation = alarm.operation; this.onAlarmListener = alarm.onAlarmListener; this.executor = alarm.executor; this.workSource = alarm.workSource; this.alarmClockInfo = alarm.alarmClockInfo; this.allowWhileIdle = alarm.allowWhileIdle; this.handler = alarm.handler; this.showIntent = alarm.showIntent; } public int getType() { return type; } public long getTriggerAtMs() { return triggerAtTime; } public long getWindowLengthMs() { return windowLengthMs; } public long getIntervalMs() { return interval; } @Nullable public String getTag() { return tag; } @Nullable public WorkSource getWorkSource() { return workSource; } @RequiresApi(VERSION_CODES.LOLLIPOP) @Nullable public AlarmClockInfo getAlarmClockInfo() { return (AlarmClockInfo) alarmClockInfo; } public boolean isAllowWhileIdle() { return allowWhileIdle; } @Override public int compareTo(ScheduledAlarm scheduledAlarm) { return Long.compare(triggerAtTime, scheduledAlarm.triggerAtTime); } } // wrapper class created because we can't modify ScheduledAlarm without breaking compatibility private class InternalScheduledAlarm extends ScheduledAlarm implements Runnable { InternalScheduledAlarm( int type, long triggerAtMs, long windowLengthMs, long intervalMs, PendingIntent operation, @Nullable WorkSource workSource, @Nullable Object alarmClockInfo, boolean allowWhileIdle) { super( type, triggerAtMs, windowLengthMs, intervalMs, operation, workSource, alarmClockInfo, allowWhileIdle); } InternalScheduledAlarm( int type, long triggerAtMs, long windowLengthMs, long intervalMs, @Nullable String tag, OnAlarmListener listener, Executor executor, @Nullable WorkSource workSource, @Nullable Object alarmClockInfo, boolean allowWhileIdle) { super( type, triggerAtMs, windowLengthMs, intervalMs, tag, listener, executor, workSource, alarmClockInfo, allowWhileIdle); } InternalScheduledAlarm(long triggerAtMs, InternalScheduledAlarm alarm) { super(triggerAtMs, alarm); } InternalScheduledAlarm schedule() { if (autoSchedule) { schedulingHandler.postDelayed(this, triggerAtTime - SystemClock.elapsedRealtime()); } return this; } void deschedule() { schedulingHandler.removeCallbacks(this); } @Override public void run() { Executor executor; if (operation != null) { executor = Runnable::run; } else { executor = Objects.requireNonNull(this.executor); } executor.execute( () -> { synchronized (scheduledAlarms) { if (!scheduledAlarms.remove(this)) { return; } if (interval > 0) { scheduledAlarms.add( new InternalScheduledAlarm(triggerAtTime + interval, this).schedule()); } } if (operation != null) { try { operation.send(); } catch (CanceledException e) { // only necessary in case this is a repeated alarm and we've already rescheduled cancel(operation); } } else if (VERSION.SDK_INT >= VERSION_CODES.N) { Objects.requireNonNull(onAlarmListener).onAlarm(); } else { throw new IllegalStateException(); } }); } } private static final class HandlerExecutor implements Executor { private final Handler handler; HandlerExecutor(@Nullable Handler handler) { this.handler = handler != null ? handler : new Handler(Looper.getMainLooper()); } @Override public void execute(Runnable command) { if (!handler.post(command)) { throw new RejectedExecutionException(handler + " is shutting down"); } } } @ForType(AlarmManager.class) interface AlarmManagerReflector { @Direct void setTimeZone(String timeZone); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy