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

Commit 7920359f authored by Julia Reynolds's avatar Julia Reynolds Committed by Android (Google) Code Review
Browse files

Merge "Allow apps to require auth before triggering actions"

parents 3542f6c0 2a8d8bb7
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -5596,6 +5596,7 @@ package android.app {
    method public android.graphics.drawable.Icon getIcon();
    method public android.app.RemoteInput[] getRemoteInputs();
    method public int getSemanticAction();
    method public boolean isAuthenticationRequired();
    method public boolean isContextual();
    method public void writeToParcel(android.os.Parcel, int);
    field @NonNull public static final android.os.Parcelable.Creator<android.app.Notification.Action> CREATOR;
@@ -5625,6 +5626,7 @@ package android.app {
    method @NonNull public android.app.Notification.Action.Builder extend(android.app.Notification.Action.Extender);
    method @NonNull public android.os.Bundle getExtras();
    method @NonNull public android.app.Notification.Action.Builder setAllowGeneratedReplies(boolean);
    method @NonNull public android.app.Notification.Action.Builder setAuthenticationRequired(boolean);
    method @NonNull public android.app.Notification.Action.Builder setContextual(boolean);
    method @NonNull public android.app.Notification.Action.Builder setSemanticAction(int);
  }
+44 −8
Original line number Diff line number Diff line
@@ -1492,6 +1492,7 @@ public class Notification implements Parcelable
        private boolean mAllowGeneratedReplies = true;
        private final @SemanticAction int mSemanticAction;
        private final boolean mIsContextual;
        private boolean mAuthenticationRequired;

        /**
         * Small icon representing the action.
@@ -1528,6 +1529,7 @@ public class Notification implements Parcelable
            mAllowGeneratedReplies = in.readInt() == 1;
            mSemanticAction = in.readInt();
            mIsContextual = in.readInt() == 1;
            mAuthenticationRequired = in.readInt() == 1;
        }

        /**
@@ -1536,13 +1538,14 @@ public class Notification implements Parcelable
        @Deprecated
        public Action(int icon, CharSequence title, PendingIntent intent) {
            this(Icon.createWithResource("", icon), title, intent, new Bundle(), null, true,
                    SEMANTIC_ACTION_NONE, false /* isContextual */);
                    SEMANTIC_ACTION_NONE, false /* isContextual */, false /* requireAuth */);
        }

        /** Keep in sync with {@link Notification.Action.Builder#Builder(Action)}! */
        private Action(Icon icon, CharSequence title, PendingIntent intent, Bundle extras,
                RemoteInput[] remoteInputs, boolean allowGeneratedReplies,
                       @SemanticAction int semanticAction, boolean isContextual) {
                @SemanticAction int semanticAction, boolean isContextual,
                boolean requireAuth) {
            this.mIcon = icon;
            if (icon != null && icon.getType() == Icon.TYPE_RESOURCE) {
                this.icon = icon.getResId();
@@ -1554,6 +1557,7 @@ public class Notification implements Parcelable
            this.mAllowGeneratedReplies = allowGeneratedReplies;
            this.mSemanticAction = semanticAction;
            this.mIsContextual = isContextual;
            this.mAuthenticationRequired = requireAuth;
        }

        /**
@@ -1623,6 +1627,17 @@ public class Notification implements Parcelable
            return getParcelableArrayFromBundle(mExtras, EXTRA_DATA_ONLY_INPUTS, RemoteInput.class);
        }

        /**
         * Returns whether the OS should only send this action's {@link PendingIntent} on an
         * unlocked device.
         *
         * If the device is locked when the action is invoked, the OS should show the keyguard and
         * require successful authentication before invoking the intent.
         */
        public boolean isAuthenticationRequired() {
            return mAuthenticationRequired;
        }

        /**
         * Builder class for {@link Action} objects.
         */
@@ -1635,6 +1650,7 @@ public class Notification implements Parcelable
            @Nullable private ArrayList<RemoteInput> mRemoteInputs;
            private @SemanticAction int mSemanticAction;
            private boolean mIsContextual;
            private boolean mAuthenticationRequired;

            /**
             * Construct a new builder for {@link Action} object.
@@ -1654,7 +1670,7 @@ public class Notification implements Parcelable
             * @param intent the {@link PendingIntent} to fire when users trigger this action
             */
            public Builder(Icon icon, CharSequence title, PendingIntent intent) {
                this(icon, title, intent, new Bundle(), null, true, SEMANTIC_ACTION_NONE);
                this(icon, title, intent, new Bundle(), null, true, SEMANTIC_ACTION_NONE, false);
            }

            /**
@@ -1665,23 +1681,25 @@ public class Notification implements Parcelable
            public Builder(Action action) {
                this(action.getIcon(), action.title, action.actionIntent,
                        new Bundle(action.mExtras), action.getRemoteInputs(),
                        action.getAllowGeneratedReplies(), action.getSemanticAction());
                        action.getAllowGeneratedReplies(), action.getSemanticAction(),
                        action.isAuthenticationRequired());
            }

            private Builder(@Nullable Icon icon, @Nullable CharSequence title,
                    @Nullable PendingIntent intent, @NonNull Bundle extras,
                    @Nullable RemoteInput[] remoteInputs, boolean allowGeneratedReplies,
                    @SemanticAction int semanticAction) {
                    @SemanticAction int semanticAction, boolean authRequired) {
                mIcon = icon;
                mTitle = title;
                mIntent = intent;
                mExtras = extras;
                if (remoteInputs != null) {
                    mRemoteInputs = new ArrayList<RemoteInput>(remoteInputs.length);
                    mRemoteInputs = new ArrayList<>(remoteInputs.length);
                    Collections.addAll(mRemoteInputs, remoteInputs);
                }
                mAllowGeneratedReplies = allowGeneratedReplies;
                mSemanticAction = semanticAction;
                mAuthenticationRequired = authRequired;
            }

            /**
@@ -1775,6 +1793,21 @@ public class Notification implements Parcelable
                return this;
            }

            /**
             * Sets whether the OS should only send this action's {@link PendingIntent} on an
             * unlocked device.
             *
             * If this is true and the device is locked when the action is invoked, the OS will
             * show the keyguard and require successful authentication before invoking the intent.
             * If this is false and the device is locked, the OS will decide whether authentication
             * should be required.
             */
            @NonNull
            public Builder setAuthenticationRequired(boolean authenticationRequired) {
                mAuthenticationRequired = authenticationRequired;
                return this;
            }

            /**
             * Throws an NPE if we are building a contextual action missing one of the fields
             * necessary to display the action.
@@ -1827,7 +1860,8 @@ public class Notification implements Parcelable
                RemoteInput[] textInputsArr = textInputs.isEmpty()
                        ? null : textInputs.toArray(new RemoteInput[textInputs.size()]);
                return new Action(mIcon, mTitle, mIntent, mExtras, textInputsArr,
                        mAllowGeneratedReplies, mSemanticAction, mIsContextual);
                        mAllowGeneratedReplies, mSemanticAction, mIsContextual,
                        mAuthenticationRequired);
            }
        }

@@ -1841,7 +1875,8 @@ public class Notification implements Parcelable
                    getRemoteInputs(),
                    getAllowGeneratedReplies(),
                    getSemanticAction(),
                    isContextual());
                    isContextual(),
                    isAuthenticationRequired());
        }

        @Override
@@ -1870,6 +1905,7 @@ public class Notification implements Parcelable
            out.writeInt(mAllowGeneratedReplies ? 1 : 0);
            out.writeInt(mSemanticAction);
            out.writeInt(mIsContextual ? 1 : 0);
            out.writeInt(mAuthenticationRequired ? 1 : 0);
        }

        public static final @android.annotation.NonNull Parcelable.Creator<Action> CREATOR =
+2 −0
Original line number Diff line number Diff line
@@ -5596,6 +5596,7 @@ package android.app {
    method public android.graphics.drawable.Icon getIcon();
    method public android.app.RemoteInput[] getRemoteInputs();
    method public int getSemanticAction();
    method public boolean isAuthenticationRequired();
    method public boolean isContextual();
    method public void writeToParcel(android.os.Parcel, int);
    field @NonNull public static final android.os.Parcelable.Creator<android.app.Notification.Action> CREATOR;
@@ -5625,6 +5626,7 @@ package android.app {
    method @NonNull public android.app.Notification.Action.Builder extend(android.app.Notification.Action.Extender);
    method @NonNull public android.os.Bundle getExtras();
    method @NonNull public android.app.Notification.Action.Builder setAllowGeneratedReplies(boolean);
    method @NonNull public android.app.Notification.Action.Builder setAuthenticationRequired(boolean);
    method @NonNull public android.app.Notification.Action.Builder setContextual(boolean);
    method @NonNull public android.app.Notification.Action.Builder setSemanticAction(int);
  }
+21 −6
Original line number Diff line number Diff line
@@ -44,6 +44,7 @@ import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.statusbar.notification.MediaNotificationProcessor
import com.android.systemui.statusbar.notification.row.HybridGroupManager
import com.android.systemui.util.Assert
@@ -97,6 +98,7 @@ class MediaDataManager(
    dumpManager: DumpManager,
    mediaTimeoutListener: MediaTimeoutListener,
    mediaResumeListener: MediaResumeListener,
    private val activityStarter: ActivityStarter,
    private var useMediaResumption: Boolean,
    private val useQsMediaPlayer: Boolean
) : Dumpable {
@@ -113,10 +115,11 @@ class MediaDataManager(
        dumpManager: DumpManager,
        broadcastDispatcher: BroadcastDispatcher,
        mediaTimeoutListener: MediaTimeoutListener,
        mediaResumeListener: MediaResumeListener
        mediaResumeListener: MediaResumeListener,
        activityStarter: ActivityStarter
    ) : this(context, backgroundExecutor, foregroundExecutor, mediaControllerFactory,
            broadcastDispatcher, dumpManager, mediaTimeoutListener, mediaResumeListener,
            Utils.useMediaResumption(context), Utils.useQsMediaPlayer(context))
            activityStarter, Utils.useMediaResumption(context), Utils.useQsMediaPlayer(context))

    private val appChangeReceiver = object : BroadcastReceiver() {
        override fun onReceive(context: Context, intent: Intent) {
@@ -403,10 +406,13 @@ class MediaDataManager(
                }
                val runnable = if (action.actionIntent != null) {
                    Runnable {
                        try {
                            action.actionIntent.send()
                        } catch (e: PendingIntent.CanceledException) {
                            Log.d(TAG, "Intent canceled", e)
                        if (action.isAuthenticationRequired()) {
                            activityStarter.dismissKeyguardThenExecute ({
                                var result = sendPendingIntent(action.actionIntent)
                                result
                            }, {}, true)
                        } else {
                            sendPendingIntent(action.actionIntent)
                        }
                    }
                } else {
@@ -449,6 +455,15 @@ class MediaDataManager(
        return null
    }

    private fun sendPendingIntent(intent: PendingIntent): Boolean {
        return try {
            intent.send()
            true
        } catch (e: PendingIntent.CanceledException) {
            Log.d(TAG, "Intent canceled", e)
            false
        }
    }
    /**
     * Load a bitmap from a URI
     * @param uri the uri to load
+35 −23
Original line number Diff line number Diff line
@@ -161,7 +161,9 @@ public class NotificationRemoteInputManager implements Dumpable {
                ActivityManager.getService().resumeAppSwitches();
            } catch (RemoteException e) {
            }
            return mCallback.handleRemoteViewClick(view, pendingIntent, () -> {
            Notification.Action action = getActionFromView(view, entry, pendingIntent);
            return mCallback.handleRemoteViewClick(view, pendingIntent,
                    action == null ? false : action.isAuthenticationRequired(), () -> {
                Pair<Intent, ActivityOptions> options = response.getLaunchOptions(view);
                options.second.setLaunchWindowingMode(
                        WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY);
@@ -170,47 +172,56 @@ public class NotificationRemoteInputManager implements Dumpable {
            });
        }

        private void logActionClick(
                View view,
                NotificationEntry entry,
                PendingIntent actionIntent) {
        private @Nullable Notification.Action getActionFromView(View view,
                NotificationEntry entry, PendingIntent actionIntent) {
            Integer actionIndex = (Integer)
                    view.getTag(com.android.internal.R.id.notification_action_index_tag);
            if (actionIndex == null) {
                // Custom action button, not logging.
                return;
                return null;
            }
            ViewParent parent = view.getParent();
            if (entry == null) {
                Log.w(TAG, "Couldn't determine notification for click.");
                return;
            }
            StatusBarNotification statusBarNotification = entry.getSbn();
            String key = statusBarNotification.getKey();
            int buttonIndex = -1;
            // If this is a default template, determine the index of the button.
            if (view.getId() == com.android.internal.R.id.action0 &&
                    parent != null && parent instanceof ViewGroup) {
                ViewGroup actionGroup = (ViewGroup) parent;
                buttonIndex = actionGroup.indexOfChild(view);
                return null;
            }
            final int count = mEntryManager.getActiveNotificationsCount();
            final int rank = mEntryManager
                    .getActiveNotificationUnfiltered(key).getRanking().getRank();

            // Notification may be updated before this function is executed, and thus play safe
            // here and verify that the action object is still the one that where the click happens.
            StatusBarNotification statusBarNotification = entry.getSbn();
            Notification.Action[] actions = statusBarNotification.getNotification().actions;
            if (actions == null || actionIndex >= actions.length) {
                Log.w(TAG, "statusBarNotification.getNotification().actions is null or invalid");
                return;
                return null ;
            }
            final Notification.Action action =
                    statusBarNotification.getNotification().actions[actionIndex];
            if (!Objects.equals(action.actionIntent, actionIntent)) {
                Log.w(TAG, "actionIntent does not match");
                return null;
            }
            return action;
        }

        private void logActionClick(
                View view,
                NotificationEntry entry,
                PendingIntent actionIntent) {
            Notification.Action action = getActionFromView(view, entry, actionIntent);
            if (action == null) {
                return;
            }
            ViewParent parent = view.getParent();
            String key = entry.getSbn().getKey();
            int buttonIndex = -1;
            // If this is a default template, determine the index of the button.
            if (view.getId() == com.android.internal.R.id.action0 &&
                    parent != null && parent instanceof ViewGroup) {
                ViewGroup actionGroup = (ViewGroup) parent;
                buttonIndex = actionGroup.indexOfChild(view);
            }
            final int count = mEntryManager.getActiveNotificationsCount();
            final int rank = mEntryManager
                    .getActiveNotificationUnfiltered(key).getRanking().getRank();

            NotificationVisibility.NotificationLocation location =
                    NotificationLogger.getNotificationLocation(
                            mEntryManager.getActiveNotificationUnfiltered(key));
@@ -813,11 +824,12 @@ public class NotificationRemoteInputManager implements Dumpable {
         *
         * @param view
         * @param pendingIntent
         * @param appRequestedAuth
         * @param defaultHandler
         * @return  true iff the click was handled
         */
        boolean handleRemoteViewClick(View view, PendingIntent pendingIntent,
                ClickHandler defaultHandler);
                boolean appRequestedAuth, ClickHandler defaultHandler);
    }

    /**
Loading