Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit d83203cd authored by Selim Cinek's avatar Selim Cinek
Browse files

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
parent 384804b4
Loading
Loading
Loading
Loading
+87 −0
Original line number Diff line number Diff line
@@ -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<CancelListener> 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<CancelListener> 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);
    }
}
+1 −0
Original line number Diff line number Diff line
@@ -946,6 +946,7 @@ public class RemoteViews implements Parcelable, Filter {
                    }
                };
            }
            target.setTagInternal(R.id.pending_intent_tag, pendingIntent);
            target.setOnClickListener(listener);
        }

+1 −0
Original line number Diff line number Diff line
@@ -128,6 +128,7 @@
  <item type="id" name="accessibilityActionContextClick" />

  <item type="id" name="remote_input_tag" />
  <item type="id" name="pending_intent_tag" />

  <item type="id" name="cross_task_transition" />

+1 −0
Original line number Diff line number Diff line
@@ -2615,6 +2615,7 @@
  <java-symbol type="id" name="actions_container" />
  <java-symbol type="id" name="smart_reply_container" />
  <java-symbol type="id" name="remote_input_tag" />
  <java-symbol type="id" name="pending_intent_tag" />

  <java-symbol type="attr" name="seekBarDialogPreferenceStyle" />
  <java-symbol type="string" name="ext_media_status_removed" />
+64 −0
Original line number Diff line number Diff line
@@ -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<PendingIntent> 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
Loading