Expose PendingIntent.addCancelListener

Bug: 195146423
Test: atest android.app.cts.PendingIntentTest#testCancelListener
Change-Id: I74e6ae49bfb2b31f0bc693d75e1f1433206011ce
This commit is contained in:
Makoto Onuki
2021-09-14 17:31:45 -07:00
committed by Etienne Ruffieux
parent 49d0f6788a
commit 30bd1bb72d
6 changed files with 122 additions and 32 deletions

View File

@@ -35,8 +35,14 @@ package android.app {
} }
public final class PendingIntent implements android.os.Parcelable { public final class PendingIntent implements android.os.Parcelable {
method public boolean addCancelListener(@NonNull java.util.concurrent.Executor, @NonNull android.app.PendingIntent.CancelListener);
method @RequiresPermission(android.Manifest.permission.GET_INTENT_SENDER_INTENT) public boolean intentFilterEquals(@Nullable android.app.PendingIntent); method @RequiresPermission(android.Manifest.permission.GET_INTENT_SENDER_INTENT) public boolean intentFilterEquals(@Nullable android.app.PendingIntent);
method @NonNull @RequiresPermission(android.Manifest.permission.GET_INTENT_SENDER_INTENT) public java.util.List<android.content.pm.ResolveInfo> queryIntentComponents(int); method @NonNull @RequiresPermission(android.Manifest.permission.GET_INTENT_SENDER_INTENT) public java.util.List<android.content.pm.ResolveInfo> queryIntentComponents(int);
method public void removeCancelListener(@NonNull android.app.PendingIntent.CancelListener);
}
public static interface PendingIntent.CancelListener {
method public void onCancelled(@NonNull android.app.PendingIntent);
} }
public class StatusBarManager { public class StatusBarManager {

View File

@@ -313,11 +313,17 @@ package android.app {
} }
public final class PendingIntent implements android.os.Parcelable { public final class PendingIntent implements android.os.Parcelable {
method public boolean addCancelListener(@NonNull java.util.concurrent.Executor, @NonNull android.app.PendingIntent.CancelListener);
method @RequiresPermission("android.permission.GET_INTENT_SENDER_INTENT") public boolean intentFilterEquals(@Nullable android.app.PendingIntent); method @RequiresPermission("android.permission.GET_INTENT_SENDER_INTENT") public boolean intentFilterEquals(@Nullable android.app.PendingIntent);
method @NonNull @RequiresPermission("android.permission.GET_INTENT_SENDER_INTENT") public java.util.List<android.content.pm.ResolveInfo> queryIntentComponents(int); method @NonNull @RequiresPermission("android.permission.GET_INTENT_SENDER_INTENT") public java.util.List<android.content.pm.ResolveInfo> queryIntentComponents(int);
method public void removeCancelListener(@NonNull android.app.PendingIntent.CancelListener);
field @Deprecated public static final int FLAG_MUTABLE_UNAUDITED = 33554432; // 0x2000000 field @Deprecated public static final int FLAG_MUTABLE_UNAUDITED = 33554432; // 0x2000000
} }
public static interface PendingIntent.CancelListener {
method public void onCancelled(@NonNull android.app.PendingIntent);
}
public final class PictureInPictureParams implements android.os.Parcelable { public final class PictureInPictureParams implements android.os.Parcelable {
method public java.util.List<android.app.RemoteAction> getActions(); method public java.util.List<android.app.RemoteAction> getActions();
method public float getAspectRatio(); method public float getAspectRatio();

View File

@@ -250,7 +250,14 @@ interface IActivityManager {
in String[] resolvedTypes, int flags, in Bundle options, int userId); in String[] resolvedTypes, int flags, in Bundle options, int userId);
void cancelIntentSender(in IIntentSender sender); void cancelIntentSender(in IIntentSender sender);
ActivityManager.PendingIntentInfo getInfoForIntentSender(in IIntentSender sender); ActivityManager.PendingIntentInfo getInfoForIntentSender(in IIntentSender sender);
void registerIntentSenderCancelListener(in IIntentSender sender, in IResultReceiver receiver); /**
This method used to be called registerIntentSenderCancelListener(), was void, and
would call `receiver` if the PI has already been canceled.
Now it returns false if the PI is cancelled, without calling `receiver`.
The method was renamed to catch calls to the original method.
*/
boolean registerIntentSenderCancelListenerEx(in IIntentSender sender,
in IResultReceiver receiver);
void unregisterIntentSenderCancelListener(in IIntentSender sender, in IResultReceiver receiver); void unregisterIntentSenderCancelListener(in IIntentSender sender, in IResultReceiver receiver);
void enterSafeMode(); void enterSafeMode();
void noteWakeupAlarm(in IIntentSender sender, in WorkSource workSource, int sourceUid, void noteWakeupAlarm(in IIntentSender sender, in WorkSource workSource, int sourceUid,

View File

@@ -53,8 +53,10 @@ import android.os.RemoteException;
import android.os.UserHandle; import android.os.UserHandle;
import android.util.AndroidException; import android.util.AndroidException;
import android.util.ArraySet; import android.util.ArraySet;
import android.util.Pair;
import android.util.proto.ProtoOutputStream; import android.util.proto.ProtoOutputStream;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.os.IResultReceiver; import com.android.internal.os.IResultReceiver;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
@@ -62,6 +64,7 @@ import java.lang.annotation.RetentionPolicy;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.concurrent.Executor;
/** /**
* A description of an Intent and target action to perform with it. Instances * A description of an Intent and target action to perform with it. Instances
@@ -127,7 +130,27 @@ public final class PendingIntent implements Parcelable {
private final IIntentSender mTarget; private final IIntentSender mTarget;
private IResultReceiver mCancelReceiver; private IResultReceiver mCancelReceiver;
private IBinder mWhitelistToken; private IBinder mWhitelistToken;
private ArraySet<CancelListener> mCancelListeners;
/**
* To protect {@link #mCancelListeners}. We could stop lazy-initialization and synchronize
* on {@link #mCancelListeners} directly, and that wouldn't increase allocations
* (an empty ArraySet won't causew extra allocations), but
* because an empty ArraySet is slightly larger than an Object, and because
* {@link #addCancelListener} is rarely used, having a separate lock object would probably
* be a net win.
*/
private final Object mLock = new Object();
@GuardedBy("mLock")
private ArraySet<Pair<Executor, CancelListener>> mCancelListeners;
/**
* Whether the PI is canceld or not. Note this is essentially a "cache" that's updated
* only when the client uses {@link #addCancelListener}. Even if this is fase, that
* still doesn't know the PI is *not* cancled, but if it's true, this PI is definitely canceled.
*/
@GuardedBy("mLock")
private boolean mCanceled;
// cached pending intent information // cached pending intent information
private @Nullable PendingIntentInfo mCachedInfo; private @Nullable PendingIntentInfo mCachedInfo;
@@ -1048,19 +1071,38 @@ public final class PendingIntent implements Parcelable {
} }
/** /**
* Register a listener to when this pendingIntent is cancelled. There are no guarantees on which * @hide
* thread a listener will be called and it's up to the caller to synchronize. This may * @deprecated use {@link #addCancelListener(Executor, CancelListener)} instead.
* trigger a synchronous binder call so should therefore usually be called on a background */
* thread. @Deprecated
public void registerCancelListener(@NonNull CancelListener cancelListener) {
if (!addCancelListener(Runnable::run, cancelListener)) {
// Call the callback right away synchronously, if the PI has been canceled already.
cancelListener.onCancelled(this);
}
}
/**
* Register a listener to when this pendingIntent is cancelled.
*
* @return true if the listener has been set successfully. false if the {@link PendingIntent}
* has already been canceled.
* *
* @hide * @hide
*/ */
public void registerCancelListener(CancelListener cancelListener) { @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
synchronized (this) { @TestApi
public boolean addCancelListener(@NonNull Executor executor,
@NonNull CancelListener cancelListener) {
synchronized (mLock) {
if (mCanceled) {
return false;
}
if (mCancelReceiver == null) { if (mCancelReceiver == null) {
mCancelReceiver = new IResultReceiver.Stub() { mCancelReceiver = new IResultReceiver.Stub() {
@Override @Override
public void send(int resultCode, Bundle resultData) throws RemoteException { public void send(int resultCode, Bundle resultData) {
notifyCancelListeners(); notifyCancelListeners();
} }
}; };
@@ -1069,42 +1111,69 @@ public final class PendingIntent implements Parcelable {
mCancelListeners = new ArraySet<>(); mCancelListeners = new ArraySet<>();
} }
boolean wasEmpty = mCancelListeners.isEmpty(); boolean wasEmpty = mCancelListeners.isEmpty();
mCancelListeners.add(cancelListener); mCancelListeners.add(Pair.create(executor, cancelListener));
if (wasEmpty) { if (wasEmpty) {
boolean success;
try { try {
ActivityManager.getService().registerIntentSenderCancelListener(mTarget, success = ActivityManager.getService().registerIntentSenderCancelListenerEx(
mCancelReceiver); mTarget, mCancelReceiver);
} catch (RemoteException e) { } catch (RemoteException e) {
throw e.rethrowFromSystemServer(); throw e.rethrowFromSystemServer();
} }
if (!success) {
mCanceled = true;
}
return success;
} else {
return !mCanceled;
} }
} }
} }
private void notifyCancelListeners() { private void notifyCancelListeners() {
ArraySet<CancelListener> cancelListeners; ArraySet<Pair<Executor, CancelListener>> cancelListeners;
synchronized (this) { synchronized (mLock) {
if (mCancelListeners == null || mCancelListeners.size() == 0) {
return;
}
mCanceled = true;
cancelListeners = new ArraySet<>(mCancelListeners); cancelListeners = new ArraySet<>(mCancelListeners);
mCancelListeners.clear();
} }
int size = cancelListeners.size(); int size = cancelListeners.size();
for (int i = 0; i < size; i++) { for (int i = 0; i < size; i++) {
cancelListeners.valueAt(i).onCancelled(this); final Pair<Executor, CancelListener> pair = cancelListeners.valueAt(i);
pair.first.execute(() -> pair.second.onCancelled(this));
} }
} }
/**
* @hide
* @deprecated use {@link #removeCancelListener(CancelListener)} instead.
*/
@Deprecated
public void unregisterCancelListener(CancelListener cancelListener) {
removeCancelListener(cancelListener);
}
/** /**
* Un-register a listener to when this pendingIntent is cancelled. * Un-register a listener to when this pendingIntent is cancelled.
* *
* @hide * @hide
*/ */
public void unregisterCancelListener(CancelListener cancelListener) { @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
synchronized (this) { @TestApi
if (mCancelListeners == null) { public void removeCancelListener(@NonNull CancelListener cancelListener) {
synchronized (mLock) {
if (mCancelListeners.size() == 0) {
return; return;
} }
boolean wasEmpty = mCancelListeners.isEmpty(); for (int i = mCancelListeners.size() - 1; i >= 0; i--) {
mCancelListeners.remove(cancelListener); if (mCancelListeners.valueAt(i).second == cancelListener) {
if (mCancelListeners.isEmpty() && !wasEmpty) { mCancelListeners.removeAt(i);
}
}
if (mCancelListeners.isEmpty()) {
try { try {
ActivityManager.getService().unregisterIntentSenderCancelListener(mTarget, ActivityManager.getService().unregisterIntentSenderCancelListener(mTarget,
mCancelReceiver); mCancelReceiver);
@@ -1401,13 +1470,15 @@ public final class PendingIntent implements Parcelable {
* *
* @hide * @hide
*/ */
@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
@TestApi
public interface CancelListener { public interface CancelListener {
/** /**
* Called when a Pending Intent is cancelled. * Called when a Pending Intent is cancelled.
* *
* @param intent The intent that was cancelled. * @param intent The intent that was cancelled.
*/ */
void onCancelled(PendingIntent intent); void onCancelled(@NonNull PendingIntent intent);
} }
private PendingIntentInfo getCachedInfo() { private PendingIntentInfo getCachedInfo() {

View File

@@ -4990,8 +4990,9 @@ public class ActivityManagerService extends IActivityManager.Stub
} }
@Override @Override
public void registerIntentSenderCancelListener(IIntentSender sender, IResultReceiver receiver) { public boolean registerIntentSenderCancelListenerEx(
mPendingIntentController.registerIntentSenderCancelListener(sender, receiver); IIntentSender sender, IResultReceiver receiver) {
return mPendingIntentController.registerIntentSenderCancelListener(sender, receiver);
} }
@Override @Override

View File

@@ -271,9 +271,11 @@ public class PendingIntentController {
} }
} }
void registerIntentSenderCancelListener(IIntentSender sender, IResultReceiver receiver) { boolean registerIntentSenderCancelListener(IIntentSender sender, IResultReceiver receiver) {
if (!(sender instanceof PendingIntentRecord)) { if (!(sender instanceof PendingIntentRecord)) {
return; Slog.w(TAG, "registerIntentSenderCancelListener called on non-PendingIntentRecord");
// In this case, it's not "success", but we don't know if it's canceld either.
return true;
} }
boolean isCancelled; boolean isCancelled;
synchronized (mLock) { synchronized (mLock) {
@@ -281,12 +283,9 @@ public class PendingIntentController {
isCancelled = pendingIntent.canceled; isCancelled = pendingIntent.canceled;
if (!isCancelled) { if (!isCancelled) {
pendingIntent.registerCancelListenerLocked(receiver); pendingIntent.registerCancelListenerLocked(receiver);
} return true;
} } else {
if (isCancelled) { return false;
try {
receiver.send(Activity.RESULT_CANCELED, null);
} catch (RemoteException e) {
} }
} }
} }