1 package org.robolectric.shadows; 2 3 import static android.app.AlarmManager.RTC_WAKEUP; 4 import static android.os.Build.VERSION_CODES.KITKAT; 5 import static android.os.Build.VERSION_CODES.LOLLIPOP; 6 import static android.os.Build.VERSION_CODES.M; 7 import static android.os.Build.VERSION_CODES.N; 8 9 import android.annotation.TargetApi; 10 import android.app.AlarmManager; 11 import android.app.AlarmManager.AlarmClockInfo; 12 import android.app.AlarmManager.OnAlarmListener; 13 import android.app.PendingIntent; 14 import android.content.Intent; 15 import android.os.Handler; 16 import java.util.ArrayList; 17 import java.util.Collections; 18 import java.util.List; 19 import java.util.TimeZone; 20 import org.robolectric.annotation.Implementation; 21 import org.robolectric.annotation.Implements; 22 import org.robolectric.annotation.RealObject; 23 import org.robolectric.annotation.Resetter; 24 import org.robolectric.shadow.api.Shadow; 25 26 @SuppressWarnings({"UnusedDeclaration"}) 27 @Implements(AlarmManager.class) 28 public class ShadowAlarmManager { 29 30 private static final TimeZone DEFAULT_TIMEZONE = TimeZone.getDefault(); 31 32 private final List<ScheduledAlarm> scheduledAlarms = new ArrayList<>(); 33 34 @RealObject private AlarmManager realObject; 35 36 @Resetter reset()37 public static void reset() { 38 TimeZone.setDefault(DEFAULT_TIMEZONE); 39 } 40 41 @Implementation setTimeZone(String timeZone)42 protected void setTimeZone(String timeZone) { 43 // Do the real check first 44 Shadow.directlyOn(realObject, AlarmManager.class).setTimeZone(timeZone); 45 // Then do the right side effect 46 TimeZone.setDefault(TimeZone.getTimeZone(timeZone)); 47 } 48 49 @Implementation set(int type, long triggerAtTime, PendingIntent operation)50 protected void set(int type, long triggerAtTime, PendingIntent operation) { 51 internalSet(type, triggerAtTime, 0L, operation, null); 52 } 53 54 @Implementation(minSdk = N) set( int type, long triggerAtTime, String tag, OnAlarmListener listener, Handler targetHandler)55 protected void set( 56 int type, long triggerAtTime, String tag, OnAlarmListener listener, Handler targetHandler) { 57 internalSet(type, triggerAtTime, listener, targetHandler); 58 } 59 60 @Implementation(minSdk = KITKAT) setExact(int type, long triggerAtTime, PendingIntent operation)61 protected void setExact(int type, long triggerAtTime, PendingIntent operation) { 62 internalSet(type, triggerAtTime, 0L, operation, null); 63 } 64 65 @Implementation(minSdk = N) setExact( int type, long triggerAtTime, String tag, OnAlarmListener listener, Handler targetHandler)66 protected void setExact( 67 int type, long triggerAtTime, String tag, OnAlarmListener listener, Handler targetHandler) { 68 internalSet(type, triggerAtTime, listener, targetHandler); 69 } 70 71 @Implementation(minSdk = KITKAT) setWindow( int type, long windowStartMillis, long windowLengthMillis, PendingIntent operation)72 protected void setWindow( 73 int type, long windowStartMillis, long windowLengthMillis, PendingIntent operation) { 74 internalSet(type, windowStartMillis, 0L, operation, null); 75 } 76 77 @Implementation(minSdk = N) setWindow( int type, long windowStartMillis, long windowLengthMillis, String tag, OnAlarmListener listener, Handler targetHandler)78 protected void setWindow( 79 int type, 80 long windowStartMillis, 81 long windowLengthMillis, 82 String tag, 83 OnAlarmListener listener, 84 Handler targetHandler) { 85 internalSet(type, windowStartMillis, listener, targetHandler); 86 } 87 88 @Implementation(minSdk = M) setAndAllowWhileIdle(int type, long triggerAtTime, PendingIntent operation)89 protected void setAndAllowWhileIdle(int type, long triggerAtTime, PendingIntent operation) { 90 internalSet(type, triggerAtTime, 0L, operation, null); 91 } 92 93 @Implementation(minSdk = M) setExactAndAllowWhileIdle(int type, long triggerAtTime, PendingIntent operation)94 protected void setExactAndAllowWhileIdle(int type, long triggerAtTime, PendingIntent operation) { 95 internalSet(type, triggerAtTime, 0L, operation, null); 96 } 97 98 @Implementation setRepeating( int type, long triggerAtTime, long interval, PendingIntent operation)99 protected void setRepeating( 100 int type, long triggerAtTime, long interval, PendingIntent operation) { 101 internalSet(type, triggerAtTime, interval, operation, null); 102 } 103 104 @Implementation setInexactRepeating( int type, long triggerAtMillis, long intervalMillis, PendingIntent operation)105 protected void setInexactRepeating( 106 int type, long triggerAtMillis, long intervalMillis, PendingIntent operation) { 107 internalSet(type, triggerAtMillis, intervalMillis, operation, null); 108 } 109 110 @Implementation(minSdk = LOLLIPOP) setAlarmClock(AlarmClockInfo info, PendingIntent operation)111 protected void setAlarmClock(AlarmClockInfo info, PendingIntent operation) { 112 internalSet(RTC_WAKEUP, info.getTriggerTime(), 0L, operation, info.getShowIntent()); 113 } 114 115 @Implementation(minSdk = LOLLIPOP) getNextAlarmClock()116 protected AlarmClockInfo getNextAlarmClock() { 117 for (ScheduledAlarm scheduledAlarm : scheduledAlarms) { 118 AlarmClockInfo alarmClockInfo = scheduledAlarm.getAlarmClockInfo(); 119 if (alarmClockInfo != null) { 120 return alarmClockInfo; 121 } 122 } 123 return null; 124 } 125 internalSet(int type, long triggerAtTime, long interval, PendingIntent operation, PendingIntent showIntent)126 private void internalSet(int type, long triggerAtTime, long interval, PendingIntent operation, 127 PendingIntent showIntent) { 128 cancel(operation); 129 scheduledAlarms.add(new ScheduledAlarm(type, triggerAtTime, interval, operation, showIntent)); 130 Collections.sort(scheduledAlarms); 131 } 132 internalSet( int type, long triggerAtTime, OnAlarmListener listener, Handler handler)133 private void internalSet( 134 int type, long triggerAtTime, OnAlarmListener listener, Handler handler) { 135 cancel(listener); 136 scheduledAlarms.add(new ScheduledAlarm(type, triggerAtTime, 0L, listener, handler)); 137 Collections.sort(scheduledAlarms); 138 } 139 140 /** 141 * @return the next scheduled alarm after consuming it 142 */ getNextScheduledAlarm()143 public ScheduledAlarm getNextScheduledAlarm() { 144 if (scheduledAlarms.isEmpty()) { 145 return null; 146 } else { 147 return scheduledAlarms.remove(0); 148 } 149 } 150 151 /** 152 * @return the most recently scheduled alarm without consuming it 153 */ peekNextScheduledAlarm()154 public ScheduledAlarm peekNextScheduledAlarm() { 155 if (scheduledAlarms.isEmpty()) { 156 return null; 157 } else { 158 return scheduledAlarms.get(0); 159 } 160 } 161 162 /** 163 * @return all scheduled alarms 164 */ getScheduledAlarms()165 public List<ScheduledAlarm> getScheduledAlarms() { 166 return scheduledAlarms; 167 } 168 169 @Implementation cancel(PendingIntent operation)170 protected void cancel(PendingIntent operation) { 171 ShadowPendingIntent shadowPendingIntent = Shadow.extract(operation); 172 final Intent toRemove = shadowPendingIntent.getSavedIntent(); 173 final int requestCode = shadowPendingIntent.getRequestCode(); 174 for (ScheduledAlarm scheduledAlarm : scheduledAlarms) { 175 if (scheduledAlarm.operation != null) { 176 ShadowPendingIntent scheduledShadowPendingIntent = Shadow.extract(scheduledAlarm.operation); 177 final Intent scheduledIntent = scheduledShadowPendingIntent.getSavedIntent(); 178 final int scheduledRequestCode = scheduledShadowPendingIntent.getRequestCode(); 179 if (scheduledIntent.filterEquals(toRemove) && scheduledRequestCode == requestCode) { 180 scheduledAlarms.remove(scheduledAlarm); 181 break; 182 } 183 } 184 } 185 } 186 187 @Implementation(minSdk = N) cancel(OnAlarmListener listener)188 protected void cancel(OnAlarmListener listener) { 189 for (ScheduledAlarm scheduledAlarm : scheduledAlarms) { 190 if (scheduledAlarm.onAlarmListener != null) { 191 if (scheduledAlarm.onAlarmListener.equals(listener)) { 192 scheduledAlarms.remove(scheduledAlarm); 193 break; 194 } 195 } 196 } 197 } 198 199 /** 200 * Container object to hold a PendingIntent and parameters describing when to send it. 201 */ 202 public static class ScheduledAlarm implements Comparable<ScheduledAlarm> { 203 204 public final int type; 205 public final long triggerAtTime; 206 public final long interval; 207 public final PendingIntent operation; 208 209 // A non-null showIntent implies this alarm has a user interface. (i.e. in an alarm clock app) 210 public final PendingIntent showIntent; 211 212 public final OnAlarmListener onAlarmListener; 213 public final Handler handler; 214 ScheduledAlarm(int type, long triggerAtTime, PendingIntent operation, PendingIntent showIntent)215 public ScheduledAlarm(int type, long triggerAtTime, PendingIntent operation, 216 PendingIntent showIntent) { 217 this(type, triggerAtTime, 0, operation, showIntent); 218 } 219 ScheduledAlarm(int type, long triggerAtTime, long interval, PendingIntent operation, PendingIntent showIntent)220 public ScheduledAlarm(int type, long triggerAtTime, long interval, PendingIntent operation, 221 PendingIntent showIntent) { 222 this(type, triggerAtTime, interval, operation, showIntent, null, null); 223 } 224 ScheduledAlarm( int type, long triggerAtTime, long interval, OnAlarmListener onAlarmListener, Handler handler)225 private ScheduledAlarm( 226 int type, 227 long triggerAtTime, 228 long interval, 229 OnAlarmListener onAlarmListener, 230 Handler handler) { 231 this(type, triggerAtTime, interval, null, null, onAlarmListener, handler); 232 } 233 ScheduledAlarm( int type, long triggerAtTime, long interval, PendingIntent operation, PendingIntent showIntent, OnAlarmListener onAlarmListener, Handler handler)234 private ScheduledAlarm( 235 int type, 236 long triggerAtTime, 237 long interval, 238 PendingIntent operation, 239 PendingIntent showIntent, 240 OnAlarmListener onAlarmListener, 241 Handler handler) { 242 this.type = type; 243 this.triggerAtTime = triggerAtTime; 244 this.operation = operation; 245 this.interval = interval; 246 this.showIntent = showIntent; 247 this.onAlarmListener = onAlarmListener; 248 this.handler = handler; 249 } 250 251 @TargetApi(LOLLIPOP) getAlarmClockInfo()252 public AlarmClockInfo getAlarmClockInfo() { 253 return showIntent == null ? null : new AlarmClockInfo(triggerAtTime, showIntent); 254 } 255 256 @Override compareTo(ScheduledAlarm scheduledAlarm)257 public int compareTo(ScheduledAlarm scheduledAlarm) { 258 return Long.compare(triggerAtTime, scheduledAlarm.triggerAtTime); 259 } 260 } 261 } 262