1 package org.robolectric.shadows; 2 3 import static android.app.PendingIntent.FLAG_CANCEL_CURRENT; 4 import static android.app.PendingIntent.FLAG_IMMUTABLE; 5 import static android.app.PendingIntent.FLAG_NO_CREATE; 6 import static android.app.PendingIntent.FLAG_ONE_SHOT; 7 import static android.app.PendingIntent.FLAG_UPDATE_CURRENT; 8 import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1; 9 import static android.os.Build.VERSION_CODES.M; 10 import static android.os.Build.VERSION_CODES.O; 11 12 import android.annotation.NonNull; 13 import android.annotation.SuppressLint; 14 import android.app.Activity; 15 import android.app.ActivityThread; 16 import android.app.PendingIntent; 17 import android.app.PendingIntent.CanceledException; 18 import android.content.Context; 19 import android.content.Intent; 20 import android.content.IntentSender; 21 import android.os.Bundle; 22 import android.os.Handler; 23 import android.os.IBinder; 24 import java.util.ArrayList; 25 import java.util.Arrays; 26 import java.util.Iterator; 27 import java.util.List; 28 import java.util.Objects; 29 import org.robolectric.RuntimeEnvironment; 30 import org.robolectric.annotation.Implementation; 31 import org.robolectric.annotation.Implements; 32 import org.robolectric.annotation.RealObject; 33 import org.robolectric.annotation.Resetter; 34 import org.robolectric.fakes.RoboIntentSender; 35 import org.robolectric.shadow.api.Shadow; 36 import org.robolectric.util.ReflectionHelpers; 37 38 @Implements(PendingIntent.class) 39 @SuppressLint("NewApi") 40 public class ShadowPendingIntent { 41 42 private enum Type { 43 ACTIVITY, 44 BROADCAST, 45 SERVICE, 46 FOREGROUND_SERVICE 47 } 48 49 private static final List<PendingIntent> createdIntents = new ArrayList<>(); 50 51 @RealObject 52 private PendingIntent realPendingIntent; 53 54 @NonNull private Intent[] savedIntents; 55 private Context savedContext; 56 private Type type; 57 private int requestCode; 58 private int flags; 59 private String creatorPackage; 60 private boolean canceled; 61 62 @Implementation getActivity( Context context, int requestCode, @NonNull Intent intent, int flags)63 protected static PendingIntent getActivity( 64 Context context, int requestCode, @NonNull Intent intent, int flags) { 65 return create(context, new Intent[] {intent}, Type.ACTIVITY, requestCode, flags); 66 } 67 68 @Implementation getActivity( Context context, int requestCode, @NonNull Intent intent, int flags, Bundle options)69 protected static PendingIntent getActivity( 70 Context context, int requestCode, @NonNull Intent intent, int flags, Bundle options) { 71 return create(context, new Intent[] {intent}, Type.ACTIVITY, requestCode, flags); 72 } 73 74 @Implementation getActivities( Context context, int requestCode, @NonNull Intent[] intents, int flags)75 protected static PendingIntent getActivities( 76 Context context, int requestCode, @NonNull Intent[] intents, int flags) { 77 return create(context, intents, Type.ACTIVITY, requestCode, flags); 78 } 79 80 @Implementation getActivities( Context context, int requestCode, @NonNull Intent[] intents, int flags, Bundle options)81 protected static PendingIntent getActivities( 82 Context context, int requestCode, @NonNull Intent[] intents, int flags, Bundle options) { 83 return create(context, intents, Type.ACTIVITY, requestCode, flags); 84 } 85 86 @Implementation getBroadcast( Context context, int requestCode, @NonNull Intent intent, int flags)87 protected static PendingIntent getBroadcast( 88 Context context, int requestCode, @NonNull Intent intent, int flags) { 89 return create(context, new Intent[] {intent}, Type.BROADCAST, requestCode, flags); 90 } 91 92 @Implementation getService( Context context, int requestCode, @NonNull Intent intent, int flags)93 protected static PendingIntent getService( 94 Context context, int requestCode, @NonNull Intent intent, int flags) { 95 return create(context, new Intent[] {intent}, Type.SERVICE, requestCode, flags); 96 } 97 98 @Implementation(minSdk = O) getForegroundService( Context context, int requestCode, @NonNull Intent intent, int flags)99 protected static PendingIntent getForegroundService( 100 Context context, int requestCode, @NonNull Intent intent, int flags) { 101 return create(context, new Intent[] {intent}, Type.FOREGROUND_SERVICE, requestCode, flags); 102 } 103 104 @Implementation 105 @SuppressWarnings("ReferenceEquality") cancel()106 protected void cancel() { 107 for (Iterator<PendingIntent> i = createdIntents.iterator(); i.hasNext(); ) { 108 PendingIntent pendingIntent = i.next(); 109 if (pendingIntent == realPendingIntent) { 110 canceled = true; 111 i.remove(); 112 break; 113 } 114 } 115 } 116 117 @Implementation send()118 protected void send() throws CanceledException { 119 send(savedContext, 0, null); 120 } 121 122 @Implementation send(int code, PendingIntent.OnFinished onFinished, Handler handler)123 protected void send(int code, PendingIntent.OnFinished onFinished, Handler handler) 124 throws CanceledException { 125 send(savedContext, code, null, onFinished, handler); 126 } 127 128 @Implementation send(Context context, int code, Intent intent)129 protected void send(Context context, int code, Intent intent) throws CanceledException { 130 send(context, code, intent, null, null); 131 } 132 133 @Implementation send(Context context, int code, Intent intent, PendingIntent.OnFinished onFinished, Handler handler)134 protected void send(Context context, int code, Intent intent, PendingIntent.OnFinished onFinished, 135 Handler handler) throws CanceledException { 136 send(context, code, intent, onFinished, handler, null); 137 } 138 139 @Implementation send(Context context, int code, Intent intent, PendingIntent.OnFinished onFinished, Handler handler, String requiredPermission)140 protected void send(Context context, int code, Intent intent, PendingIntent.OnFinished onFinished, 141 Handler handler, String requiredPermission) throws CanceledException { 142 // Manually propagating to keep only one implementation regardless of SDK 143 send(context, code, intent, onFinished, handler, requiredPermission, null); 144 } 145 146 @Implementation(minSdk = M) send(Context context, int code, Intent intent, PendingIntent.OnFinished onFinished, Handler handler, String requiredPermission, Bundle options)147 protected void send(Context context, int code, Intent intent, PendingIntent.OnFinished onFinished, 148 Handler handler, String requiredPermission, Bundle options) throws CanceledException { 149 if (canceled) { 150 throw new CanceledException(); 151 } 152 153 // Fill in the last Intent, if it is mutable, with information now available at send-time. 154 Intent[] intentsToSend; 155 if (intent != null && isMutable(flags)) { 156 // Copy the last intent before filling it in to avoid modifying this PendingIntent. 157 intentsToSend = Arrays.copyOf(savedIntents, savedIntents.length); 158 Intent lastIntentCopy = new Intent(intentsToSend[intentsToSend.length - 1]); 159 lastIntentCopy.fillIn(intent, 0); 160 intentsToSend[intentsToSend.length - 1] = lastIntentCopy; 161 } else { 162 intentsToSend = savedIntents; 163 } 164 165 ActivityThread activityThread = (ActivityThread) RuntimeEnvironment.getActivityThread(); 166 ShadowInstrumentation shadowInstrumentation = Shadow.extract(activityThread.getInstrumentation()); 167 if (isActivityIntent()) { 168 for (Intent intentToSend : intentsToSend) { 169 shadowInstrumentation.execStartActivity( 170 context, 171 (IBinder) null, 172 (IBinder) null, 173 (Activity) null, 174 intentToSend, 175 0, 176 (Bundle) null); 177 } 178 } else if (isBroadcastIntent()) { 179 for (Intent intentToSend : intentsToSend) { 180 context.sendBroadcast(intentToSend); 181 } 182 } else if (isServiceIntent()) { 183 for (Intent intentToSend : intentsToSend) { 184 context.startService(intentToSend); 185 } 186 } else if (isForegroundServiceIntent()) { 187 for (Intent intentToSend : intentsToSend) { 188 context.startForegroundService(intentToSend); 189 } 190 } 191 192 if (isOneShot(flags)) { 193 cancel(); 194 } 195 } 196 197 @Implementation getIntentSender()198 protected IntentSender getIntentSender() { 199 return new RoboIntentSender(realPendingIntent); 200 } 201 202 /** 203 * @return {@code true} iff sending this PendingIntent will start an activity 204 */ isActivityIntent()205 public boolean isActivityIntent() { 206 return type == Type.ACTIVITY; 207 } 208 209 /** 210 * @return {@code true} iff sending this PendingIntent will broadcast an Intent 211 */ isBroadcastIntent()212 public boolean isBroadcastIntent() { 213 return type == Type.BROADCAST; 214 } 215 216 /** 217 * @return {@code true} iff sending this PendingIntent will start a service 218 */ isServiceIntent()219 public boolean isServiceIntent() { 220 return type == Type.SERVICE; 221 } 222 223 /** @return {@code true} iff sending this PendingIntent will start a foreground service */ isForegroundServiceIntent()224 public boolean isForegroundServiceIntent() { 225 return type == Type.FOREGROUND_SERVICE; 226 } 227 228 /** 229 * @return the context in which this PendingIntent was created 230 */ getSavedContext()231 public Context getSavedContext() { 232 return savedContext; 233 } 234 235 /** 236 * This returns the last Intent in the Intent[] to be delivered when the PendingIntent is sent. 237 * This method is particularly useful for PendingIntents created with a single Intent: 238 * <ul> 239 * <li>{@link #getActivity(Context, int, Intent, int)}</li> 240 * <li>{@link #getActivity(Context, int, Intent, int, Bundle)}</li> 241 * <li>{@link #getBroadcast(Context, int, Intent, int)}</li> 242 * <li>{@link #getService(Context, int, Intent, int)}</li> 243 * </ul> 244 * 245 * @return the final Intent to be delivered when the PendingIntent is sent 246 */ getSavedIntent()247 public Intent getSavedIntent() { 248 return savedIntents[savedIntents.length - 1]; 249 } 250 251 /** 252 * This method is particularly useful for PendingIntents created with multiple Intents: 253 * <ul> 254 * <li>{@link #getActivities(Context, int, Intent[], int)}</li> 255 * <li>{@link #getActivities(Context, int, Intent[], int, Bundle)}</li> 256 * </ul> 257 * 258 * @return all Intents to be delivered when the PendingIntent is sent 259 */ getSavedIntents()260 public Intent[] getSavedIntents() { 261 return savedIntents; 262 } 263 264 /** 265 * @return {@true} iff this PendingIntent has been canceled 266 */ isCanceled()267 public boolean isCanceled() { 268 return canceled; 269 } 270 271 /** 272 * @return the request code with which this PendingIntent was created 273 */ getRequestCode()274 public int getRequestCode() { 275 return requestCode; 276 } 277 278 /** 279 * @return the flags with which this PendingIntent was created 280 */ getFlags()281 public int getFlags() { 282 return flags; 283 } 284 285 @Implementation getTargetPackage()286 protected String getTargetPackage() { 287 return getCreatorPackage(); 288 } 289 290 @Implementation(minSdk = JELLY_BEAN_MR1) getCreatorPackage()291 protected String getCreatorPackage() { 292 return (creatorPackage == null) 293 ? RuntimeEnvironment.application.getPackageName() 294 : creatorPackage; 295 } 296 setCreatorPackage(String creatorPackage)297 public void setCreatorPackage(String creatorPackage) { 298 this.creatorPackage = creatorPackage; 299 } 300 301 @Override 302 @Implementation equals(Object o)303 public boolean equals(Object o) { 304 if (this == o) return true; 305 if (o == null || realPendingIntent.getClass() != o.getClass()) return false; 306 ShadowPendingIntent that = Shadow.extract((PendingIntent) o); 307 308 String packageName = savedContext == null ? null : savedContext.getPackageName(); 309 String thatPackageName = that.savedContext == null ? null : that.savedContext.getPackageName(); 310 if (!Objects.equals(packageName, thatPackageName)) { 311 return false; 312 } 313 314 if (this.savedIntents.length != that.savedIntents.length) { 315 return false; 316 } 317 318 for (int i = 0; i < this.savedIntents.length; i++) { 319 if (!this.savedIntents[i].filterEquals(that.savedIntents[i])) { 320 return false; 321 } 322 } 323 324 if (this.requestCode != that.requestCode) { 325 return false; 326 } 327 return true; 328 } 329 330 @Override 331 @Implementation hashCode()332 public int hashCode() { 333 int result = savedIntents != null ? Arrays.hashCode(savedIntents) : 0; 334 if (savedContext != null) { 335 String packageName = savedContext.getPackageName(); 336 result = 31 * result + (packageName != null ? packageName.hashCode() : 0); 337 } 338 result = 31 * result + requestCode; 339 return result; 340 } 341 create(Context context, Intent[] intents, Type type, int requestCode, int flags)342 private static PendingIntent create(Context context, Intent[] intents, Type type, int requestCode, 343 int flags) { 344 Objects.requireNonNull(intents, "intents may not be null"); 345 346 // Search for a matching PendingIntent. 347 PendingIntent pendingIntent = getCreatedIntentFor(type, intents, requestCode, flags); 348 if ((flags & FLAG_NO_CREATE) != 0) { 349 return pendingIntent; 350 } 351 352 // If requested, update the existing PendingIntent if one exists. 353 if (pendingIntent != null && (flags & FLAG_UPDATE_CURRENT) != 0) { 354 ShadowPendingIntent shadowPendingIntent = Shadow.extract(pendingIntent); 355 Intent intent = shadowPendingIntent.getSavedIntent(); 356 Bundle extras = intent.getExtras(); 357 if (extras != null) { 358 extras.clear(); 359 } 360 intent.putExtras(intents[intents.length - 1]); 361 return pendingIntent; 362 } 363 364 // If requested, cancel the existing PendingIntent if one exists. 365 if (pendingIntent != null && (flags & FLAG_CANCEL_CURRENT) != 0) { 366 ShadowPendingIntent shadowPendingIntent = Shadow.extract(pendingIntent); 367 shadowPendingIntent.cancel(); 368 pendingIntent = null; 369 } 370 371 // Build the PendingIntent if it does not exist. 372 if (pendingIntent == null) { 373 pendingIntent = ReflectionHelpers.callConstructor(PendingIntent.class); 374 ShadowPendingIntent shadowPendingIntent = Shadow.extract(pendingIntent); 375 shadowPendingIntent.savedIntents = intents; 376 shadowPendingIntent.type = type; 377 shadowPendingIntent.savedContext = context; 378 shadowPendingIntent.requestCode = requestCode; 379 shadowPendingIntent.flags = flags; 380 381 createdIntents.add(pendingIntent); 382 } 383 384 return pendingIntent; 385 } 386 getCreatedIntentFor(Type type, Intent[] intents, int requestCode, int flags)387 private static PendingIntent getCreatedIntentFor(Type type, Intent[] intents, int requestCode, 388 int flags) { 389 for (PendingIntent createdIntent : createdIntents) { 390 ShadowPendingIntent shadowPendingIntent = Shadow.extract(createdIntent); 391 392 if (isOneShot(shadowPendingIntent.flags) != isOneShot(flags)) { 393 continue; 394 } 395 396 if (isMutable(shadowPendingIntent.flags) != isMutable(flags)) { 397 continue; 398 } 399 400 if (shadowPendingIntent.type != type) { 401 continue; 402 } 403 404 if (shadowPendingIntent.requestCode != requestCode) { 405 continue; 406 } 407 408 // The last Intent in the array acts as the "significant element" for matching as per 409 // {@link #getActivities(Context, int, Intent[], int)}. 410 Intent savedIntent = shadowPendingIntent.getSavedIntent(); 411 Intent targetIntent = intents[intents.length - 1]; 412 413 if (savedIntent == null ? targetIntent == null : savedIntent.filterEquals(targetIntent)) { 414 return createdIntent; 415 } 416 } 417 return null; 418 } 419 isOneShot(int flags)420 private static boolean isOneShot(int flags) { 421 return (flags & FLAG_ONE_SHOT) != 0; 422 } 423 isMutable(int flags)424 private static boolean isMutable(int flags) { 425 return (flags & FLAG_IMMUTABLE) == 0; 426 } 427 428 @Resetter reset()429 public static void reset() { 430 createdIntents.clear(); 431 } 432 } 433