blob: 254e6cc348e6ed117c4029814d177b4eff0939fb [file] [log] [blame]
package org.robolectric.shadows;
import static android.app.PendingIntent.FLAG_CANCEL_CURRENT;
import static android.app.PendingIntent.FLAG_IMMUTABLE;
import static android.app.PendingIntent.FLAG_NO_CREATE;
import static android.app.PendingIntent.FLAG_ONE_SHOT;
import static android.app.PendingIntent.FLAG_UPDATE_CURRENT;
import static org.robolectric.Shadows.shadowOf;
import android.annotation.NonNull;
import android.app.PendingIntent;
import android.app.PendingIntent.CanceledException;
import android.content.Context;
import android.content.Intent;
import android.content.IntentSender;
import android.os.Bundle;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.annotation.RealObject;
import org.robolectric.annotation.Resetter;
import org.robolectric.fakes.RoboIntentSender;
import org.robolectric.util.ReflectionHelpers;
@Implements(PendingIntent.class)
public class ShadowPendingIntent {
private enum Type {ACTIVITY, BROADCAST, SERVICE}
private static final List<PendingIntent> createdIntents = new ArrayList<>();
@RealObject
private PendingIntent realPendingIntent;
@NonNull private Intent[] savedIntents;
private Context savedContext;
private Type type;
private int requestCode;
private int flags;
private String creatorPackage;
private boolean canceled;
@Implementation
public static PendingIntent getActivity(
Context context, int requestCode, @NonNull Intent intent, int flags) {
return create(context, new Intent[] {intent}, Type.ACTIVITY, requestCode, flags);
}
@Implementation
public static PendingIntent getActivity(
Context context, int requestCode, @NonNull Intent intent, int flags, Bundle options) {
return create(context, new Intent[] {intent}, Type.ACTIVITY, requestCode, flags);
}
@Implementation
public static PendingIntent getActivities(
Context context, int requestCode, @NonNull Intent[] intents, int flags) {
return create(context, intents, Type.ACTIVITY, requestCode, flags);
}
@Implementation
public static PendingIntent getActivities(
Context context, int requestCode, @NonNull Intent[] intents, int flags, Bundle options) {
return create(context, intents, Type.ACTIVITY, requestCode, flags);
}
@Implementation
public static PendingIntent getBroadcast(
Context context, int requestCode, @NonNull Intent intent, int flags) {
return create(context, new Intent[] {intent}, Type.BROADCAST, requestCode, flags);
}
@Implementation
public static PendingIntent getService(
Context context, int requestCode, @NonNull Intent intent, int flags) {
return create(context, new Intent[] {intent}, Type.SERVICE, requestCode, flags);
}
@Implementation
@SuppressWarnings("ReferenceEquality")
public void cancel() {
for (Iterator<PendingIntent> i = createdIntents.iterator(); i.hasNext(); ) {
PendingIntent pendingIntent = i.next();
if (pendingIntent == realPendingIntent) {
canceled = true;
i.remove();
break;
}
}
}
@Implementation
public void send() throws CanceledException {
send(savedContext, 0, null);
}
@Implementation
public void send(Context context, int code, Intent intent) throws CanceledException {
if (canceled) {
throw new CanceledException();
}
// Fill in the last Intent, if it is mutable, with information now available at send-time.
if (intent != null && isMutable(flags)) {
getSavedIntent().fillIn(intent, 0);
}
if (isActivityIntent()) {
for (Intent savedIntent : savedIntents) {
context.startActivity(savedIntent);
}
} else if (isBroadcastIntent()) {
for (Intent savedIntent : savedIntents) {
context.sendBroadcast(savedIntent);
}
} else if (isServiceIntent()) {
for (Intent savedIntent : savedIntents) {
context.startService(savedIntent);
}
}
if (isOneShot(flags)) {
cancel();
}
}
@Implementation
public IntentSender getIntentSender() {
return new RoboIntentSender(realPendingIntent);
}
/**
* @return {@code true} iff sending this PendingIntent will start an activity
*/
public boolean isActivityIntent() {
return type == Type.ACTIVITY;
}
/**
* @return {@code true} iff sending this PendingIntent will broadcast an Intent
*/
public boolean isBroadcastIntent() {
return type == Type.BROADCAST;
}
/**
* @return {@code true} iff sending this PendingIntent will start a service
*/
public boolean isServiceIntent() {
return type == Type.SERVICE;
}
/**
* @return the context in which this PendingIntent was created
*/
public Context getSavedContext() {
return savedContext;
}
/**
* This returns the last Intent in the Intent[] to be delivered when the PendingIntent is sent.
* This method is particularly useful for PendingIntents created with a single Intent:
* <ul>
* <li>{@link #getActivity(Context, int, Intent, int)}</li>
* <li>{@link #getActivity(Context, int, Intent, int, Bundle)}</li>
* <li>{@link #getBroadcast(Context, int, Intent, int)}</li>
* <li>{@link #getService(Context, int, Intent, int)}</li>
* </ul>
*
* @return the final Intent to be delivered when the PendingIntent is sent
*/
public Intent getSavedIntent() {
return savedIntents[savedIntents.length - 1];
}
/**
* This method is particularly useful for PendingIntents created with multiple Intents:
* <ul>
* <li>{@link #getActivities(Context, int, Intent[], int)}</li>
* <li>{@link #getActivities(Context, int, Intent[], int, Bundle)}</li>
* </ul>
*
* @return all Intents to be delivered when the PendingIntent is sent
*/
public Intent[] getSavedIntents() {
return savedIntents;
}
/**
* @return {@true} iff this PendingIntent has been canceled
*/
public boolean isCanceled() {
return canceled;
}
/**
* @return the request code with which this PendingIntent was created
*/
public int getRequestCode() {
return requestCode;
}
/**
* @return the flags with which this PendingIntent was created
*/
public int getFlags() {
return flags;
}
@Implementation
public String getTargetPackage() {
return getCreatorPackage();
}
@Implementation
public String getCreatorPackage() {
return (creatorPackage == null)
? RuntimeEnvironment.application.getPackageName()
: creatorPackage;
}
public void setCreatorPackage(String creatorPackage) {
this.creatorPackage = creatorPackage;
}
@Override
@Implementation
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || realPendingIntent.getClass() != o.getClass()) return false;
ShadowPendingIntent that = shadowOf((PendingIntent) o);
String packageName = savedContext == null ? null : savedContext.getPackageName();
String thatPackageName = that.savedContext == null ? null : that.savedContext.getPackageName();
if (!Objects.equals(packageName, thatPackageName)) {
return false;
}
if (this.savedIntents.length != that.savedIntents.length) {
return false;
}
for (int i = 0; i < this.savedIntents.length; i++) {
if (!this.savedIntents[i].filterEquals(that.savedIntents[i])) {
return false;
}
}
if (this.requestCode != that.requestCode) {
return false;
}
return true;
}
@Override
@Implementation
public int hashCode() {
int result = savedIntents != null ? Arrays.hashCode(savedIntents) : 0;
if (savedContext != null) {
String packageName = savedContext.getPackageName();
result = 31 * result + (packageName != null ? packageName.hashCode() : 0);
}
result = 31 * result + requestCode;
return result;
}
private static PendingIntent create(Context context, Intent[] intents, Type type, int requestCode,
int flags) {
Objects.requireNonNull(intents, "intents may not be null");
// Search for a matching PendingIntent.
PendingIntent pendingIntent = getCreatedIntentFor(type, intents, requestCode, flags);
if ((flags & FLAG_NO_CREATE) != 0) {
return pendingIntent;
}
// If requested, update the existing PendingIntent if one exists.
if (pendingIntent != null && (flags & FLAG_UPDATE_CURRENT) != 0) {
Intent intent = shadowOf(pendingIntent).getSavedIntent();
Bundle extras = intent.getExtras();
if (extras != null) {
extras.clear();
}
intent.putExtras(intents[intents.length - 1]);
return pendingIntent;
}
// If requested, cancel the existing PendingIntent if one exists.
if (pendingIntent != null && (flags & FLAG_CANCEL_CURRENT) != 0) {
ShadowPendingIntent toCancel = shadowOf(pendingIntent);
toCancel.cancel();
pendingIntent = null;
}
// Build the PendingIntent if it does not exist.
if (pendingIntent == null) {
pendingIntent = ReflectionHelpers.callConstructor(PendingIntent.class);
ShadowPendingIntent shadowPendingIntent = shadowOf(pendingIntent);
shadowPendingIntent.savedIntents = intents;
shadowPendingIntent.type = type;
shadowPendingIntent.savedContext = context;
shadowPendingIntent.requestCode = requestCode;
shadowPendingIntent.flags = flags;
createdIntents.add(pendingIntent);
}
return pendingIntent;
}
private static PendingIntent getCreatedIntentFor(Type type, Intent[] intents, int requestCode,
int flags) {
for (PendingIntent createdIntent : createdIntents) {
ShadowPendingIntent shadowPendingIntent = shadowOf(createdIntent);
if (isOneShot(shadowPendingIntent.flags) != isOneShot(flags)) {
continue;
}
if (isMutable(shadowPendingIntent.flags) != isMutable(flags)) {
continue;
}
if (shadowPendingIntent.type != type) {
continue;
}
if (shadowPendingIntent.requestCode != requestCode) {
continue;
}
// The last Intent in the array acts as the "significant element" for matching as per
// {@link #getActivities(Context, int, Intent[], int)}.
Intent savedIntent = shadowPendingIntent.getSavedIntent();
Intent targetIntent = intents[intents.length - 1];
if (savedIntent == null ? targetIntent == null : savedIntent.filterEquals(targetIntent)) {
return createdIntent;
}
}
return null;
}
private static boolean isOneShot(int flags) {
return (flags & FLAG_ONE_SHOT) != 0;
}
private static boolean isMutable(int flags) {
return (flags & FLAG_IMMUTABLE) == 0;
}
@Resetter
public static void reset() {
createdIntents.clear();
}
}