Expose PendingIntent.addCancelListener
Bug: 195146423 Test: atest android.app.cts.PendingIntentTest#testCancelListener Change-Id: I74e6ae49bfb2b31f0bc693d75e1f1433206011ce
This commit is contained in:
committed by
Etienne Ruffieux
parent
49d0f6788a
commit
30bd1bb72d
@@ -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 {
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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) {
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user