From d83203cde49842eec04cd9d8b43a20acf6cd227e Mon Sep 17 00:00:00 2001 From: Selim Cinek Date: Wed, 18 Apr 2018 14:34:27 +0800 Subject: [PATCH] Disabled reply action when pending intents are cancelled Previously the user could open inline reply even when the action was already cancelled. This also enables listening to pending intent cancellations. Test: manual Fixes: 77811784 Change-Id: I4ae164081c6abdeb60a8e78d61bf5e4f26cca1d3 --- core/java/android/app/PendingIntent.java | 87 +++++++++++++++++++ core/java/android/widget/RemoteViews.java | 1 + core/res/res/values/ids.xml | 1 + core/res/res/values/symbols.xml | 1 + .../NotificationTemplateViewWrapper.java | 64 ++++++++++++++ .../server/am/ActivityManagerService.java | 13 ++- 6 files changed, 166 insertions(+), 1 deletion(-) diff --git a/core/java/android/app/PendingIntent.java b/core/java/android/app/PendingIntent.java index 315259bdf3887..bdaf80e374dfe 100644 --- a/core/java/android/app/PendingIntent.java +++ b/core/java/android/app/PendingIntent.java @@ -33,8 +33,11 @@ import android.os.Parcelable; import android.os.RemoteException; import android.os.UserHandle; import android.util.AndroidException; +import android.util.ArraySet; import android.util.proto.ProtoOutputStream; +import com.android.internal.os.IResultReceiver; + import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -93,7 +96,9 @@ import java.lang.annotation.RetentionPolicy; */ public final class PendingIntent implements Parcelable { private final IIntentSender mTarget; + private IResultReceiver mCancelReceiver; private IBinder mWhitelistToken; + private ArraySet mCancelListeners; /** @hide */ @IntDef(flag = true, @@ -963,6 +968,74 @@ public final class PendingIntent implements Parcelable { } } + /** + * Register a listener to when this pendingIntent is cancelled. There are no guarantees on which + * thread a listener will be called and it's up to the caller to synchronize. This may + * trigger a synchronous binder call so should therefore usually be called on a background + * thread. + * + * @hide + */ + public void registerCancelListener(CancelListener cancelListener) { + synchronized (this) { + if (mCancelReceiver == null) { + mCancelReceiver = new IResultReceiver.Stub() { + @Override + public void send(int resultCode, Bundle resultData) throws RemoteException { + notifyCancelListeners(); + } + }; + } + if (mCancelListeners == null) { + mCancelListeners = new ArraySet<>(); + } + boolean wasEmpty = mCancelListeners.isEmpty(); + mCancelListeners.add(cancelListener); + if (wasEmpty) { + try { + ActivityManager.getService().registerIntentSenderCancelListener(mTarget, + mCancelReceiver); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + } + } + + private void notifyCancelListeners() { + ArraySet cancelListeners; + synchronized (this) { + cancelListeners = new ArraySet<>(mCancelListeners); + } + int size = cancelListeners.size(); + for (int i = 0; i < size; i++) { + cancelListeners.valueAt(i).onCancelled(this); + } + } + + /** + * Un-register a listener to when this pendingIntent is cancelled. + * + * @hide + */ + public void unregisterCancelListener(CancelListener cancelListener) { + synchronized (this) { + if (mCancelListeners == null) { + return; + } + boolean wasEmpty = mCancelListeners.isEmpty(); + mCancelListeners.remove(cancelListener); + if (mCancelListeners.isEmpty() && !wasEmpty) { + try { + ActivityManager.getService().unregisterIntentSenderCancelListener(mTarget, + mCancelReceiver); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + } + } + /** * Return the user handle of the application that created this * PendingIntent, that is the user under which you will actually be @@ -1184,4 +1257,18 @@ public final class PendingIntent implements Parcelable { public IBinder getWhitelistToken() { return mWhitelistToken; } + + /** + * A listener to when a pending intent is cancelled + * + * @hide + */ + public interface CancelListener { + /** + * Called when a Pending Intent is cancelled. + * + * @param intent The intent that was cancelled. + */ + void onCancelled(PendingIntent intent); + } } diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java index ff9daee180bff..5ecbf90a4ecac 100644 --- a/core/java/android/widget/RemoteViews.java +++ b/core/java/android/widget/RemoteViews.java @@ -946,6 +946,7 @@ public class RemoteViews implements Parcelable, Filter { } }; } + target.setTagInternal(R.id.pending_intent_tag, pendingIntent); target.setOnClickListener(listener); } diff --git a/core/res/res/values/ids.xml b/core/res/res/values/ids.xml index c90a0df5d7b1d..47d04edfc281c 100644 --- a/core/res/res/values/ids.xml +++ b/core/res/res/values/ids.xml @@ -128,6 +128,7 @@ + diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 3ee333cadadce..e0dfa00f7dbfa 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -2615,6 +2615,7 @@ + diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java index 5f7b638dc872b..9ed5b7fd45743 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java @@ -16,16 +16,21 @@ package com.android.systemui.statusbar.notification; +import android.app.PendingIntent; import android.content.Context; import android.graphics.Color; import android.graphics.Rect; import android.service.notification.StatusBarNotification; +import android.util.ArraySet; import android.view.View; import android.widget.ImageView; import android.widget.ProgressBar; import android.widget.TextView; +import com.android.internal.R; import com.android.internal.widget.NotificationActionListLayout; +import com.android.systemui.Dependency; +import com.android.systemui.UiOffloadThread; import com.android.systemui.statusbar.CrossFadeHelper; import com.android.systemui.statusbar.ExpandableNotificationRow; import com.android.systemui.statusbar.TransformableView; @@ -49,6 +54,8 @@ public class NotificationTemplateViewWrapper extends NotificationHeaderViewWrapp private int mContentHeight; private int mMinHeightHint; private NotificationActionListLayout mActions; + private ArraySet mCancelledPendingIntents = new ArraySet<>(); + private UiOffloadThread mUiOffloadThread; protected NotificationTemplateViewWrapper(Context ctx, View view, ExpandableNotificationRow row) { @@ -137,6 +144,63 @@ public class NotificationTemplateViewWrapper extends NotificationHeaderViewWrapp mActionsContainer = mView.findViewById(com.android.internal.R.id.actions_container); mActions = mView.findViewById(com.android.internal.R.id.actions); mReplyAction = mView.findViewById(com.android.internal.R.id.reply_icon_action); + updatePendingIntentCancellations(); + } + + private void updatePendingIntentCancellations() { + if (mActions != null) { + int numActions = mActions.getChildCount(); + for (int i = 0; i < numActions; i++) { + View action = mActions.getChildAt(i); + performOnPendingIntentCancellation(action, () -> { + action.setEnabled(false); + // The visual appearance doesn't look disabled enough yet, let's add the + // alpha as well. Selectors unfortunately don't seem to work here. + action.setAlpha(0.5f); + }); + } + } + if (mReplyAction != null) { + performOnPendingIntentCancellation(mReplyAction, () -> { + mReplyAction.setEnabled(false); + // The visual appearance doesn't look disabled enough yet, let's add the + // alpha as well. Selectors unfortunately don't seem to work here. + mReplyAction.setAlpha(0.5f); + }); + } + } + + private void performOnPendingIntentCancellation(View view, Runnable cancellationRunnable) { + PendingIntent pendingIntent = (PendingIntent) view.getTag( + com.android.internal.R.id.pending_intent_tag); + if (pendingIntent == null) { + return; + } + if (mCancelledPendingIntents.contains(pendingIntent)) { + cancellationRunnable.run(); + } else { + PendingIntent.CancelListener listener = (PendingIntent intent) -> { + mView.post(() -> { + mCancelledPendingIntents.add(pendingIntent); + cancellationRunnable.run(); + }); + }; + if (mUiOffloadThread == null) { + mUiOffloadThread = Dependency.get(UiOffloadThread.class); + } + mUiOffloadThread.submit(() -> pendingIntent.registerCancelListener(listener)); + view.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() { + @Override + public void onViewAttachedToWindow(View v) { + mUiOffloadThread.submit(() -> pendingIntent.registerCancelListener(listener)); + } + + @Override + public void onViewDetachedFromWindow(View v) { + mUiOffloadThread.submit(() -> pendingIntent.unregisterCancelListener(listener)); + } + }); + } } @Override diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 62a055d330c81..22adf352ac609 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -8476,8 +8476,19 @@ public class ActivityManagerService extends IActivityManager.Stub if (!(sender instanceof PendingIntentRecord)) { return; } + boolean isCancelled; synchronized(this) { - ((PendingIntentRecord)sender).registerCancelListenerLocked(receiver); + PendingIntentRecord pendingIntent = (PendingIntentRecord) sender; + isCancelled = pendingIntent.canceled; + if (!isCancelled) { + pendingIntent.registerCancelListenerLocked(receiver); + } + } + if (isCancelled) { + try { + receiver.send(Activity.RESULT_CANCELED, null); + } catch (RemoteException e) { + } } }