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

Commit 1a7ae5b9 authored by Jeff DeCew's avatar Jeff DeCew
Browse files

CallStyle notifications now include their adjusted actions in the actions list.

Also, NotificationManagerService.fixNotification ensures correct actions.

Bug: 179178086
Test: Post CallStyle notification, validate actions exist.
Test: Set breakpoint inside fixNotification logic to ensure conditional is catching the style correctly.
Change-Id: I669c89d844b3e6258831543417f49efddd4cd4f2
parent d6209d4e
Loading
Loading
Loading
Loading
+111 −85
Original line number Diff line number Diff line
@@ -5443,8 +5443,12 @@ public class Notification implements Parcelable
            return p.allowColorization && mN.isColorized();
        }

        private boolean isCallActionColorCustomizable(StandardTemplateParams p) {
            return isColorized(p) && mContext.getResources().getBoolean(
        private boolean isCallActionColorCustomizable() {
            // NOTE: this doesn't need to check StandardTemplateParams.allowColorization because
            //  that is only used for disallowing colorization of headers for the minimized state,
            //  and neither of those conditions applies when showing actions.
            //  Not requiring StandardTemplateParams as an argument simplifies the creation process.
            return mN.isColorized() && mContext.getResources().getBoolean(
                    R.bool.config_callNotificationActionColorsRequireColorized);
        }

@@ -5510,13 +5514,13 @@ public class Notification implements Parcelable
         */
        private @NonNull List<Notification.Action> getNonContextualActions() {
            if (mActions == null) return Collections.emptyList();
            List<Notification.Action> contextualActions = new ArrayList<>();
            List<Notification.Action> standardActions = new ArrayList<>();
            for (Notification.Action action : mActions) {
                if (!action.isContextual()) {
                    contextualActions.add(action);
                    standardActions.add(action);
                }
            }
            return contextualActions;
            return standardActions;
        }

        private RemoteViews applyStandardTemplateWithActions(int layoutId,
@@ -5536,16 +5540,29 @@ public class Notification implements Parcelable
            // filter them out here.
            List<Notification.Action> nonContextualActions = getNonContextualActions();

            int N = nonContextualActions.size();
            boolean emphazisedMode = mN.fullScreenIntent != null;
            int numActions = Math.min(nonContextualActions.size(), MAX_ACTION_BUTTONS);
            boolean emphazisedMode = mN.fullScreenIntent != null || p.mCallStyleActions;
            if (p.mCallStyleActions) {
                // Clear view padding to allow buttons to start on the left edge.
                // This must be done before 'setEmphasizedMode' which sets top/bottom margins.
                big.setViewPadding(R.id.actions, 0, 0, 0, 0);
                // Add an optional indent that will make buttons start at the correct column when
                // there is enough space to do so (and fall back to the left edge if not).
                big.setInt(R.id.actions, "setCollapsibleIndentDimen",
                        R.dimen.call_notification_collapsible_indent);
            }
            big.setBoolean(R.id.actions, "setEmphasizedMode", emphazisedMode);
            if (N > 0 && !p.mHideActions) {
            if (p.mCallStyleActions) {
                // Use "wrap_content" (unlike normal emphasized mode) and allow prioritizing the
                // required actions (Answer, Decline, and Hang Up).
                big.setBoolean(R.id.actions, "setPrioritizedWrapMode", true);
            }
            if (numActions > 0 && !p.mHideActions) {
                big.setViewVisibility(R.id.actions_container, View.VISIBLE);
                big.setViewVisibility(R.id.actions, View.VISIBLE);
                big.setViewLayoutMarginDimen(R.id.notification_action_list_margin_target,
                        RemoteViews.MARGIN_BOTTOM, 0);
                if (N>MAX_ACTION_BUTTONS) N=MAX_ACTION_BUTTONS;
                for (int i=0; i<N; i++) {
                for (int i = 0; i < numActions; i++) {
                    Action action = nonContextualActions.get(i);

                    boolean actionHasValidInput = hasValidRemoteInput(action);
@@ -5556,6 +5573,11 @@ public class Notification implements Parcelable
                        // Clear the drawable
                        button.setInt(R.id.action0, "setBackgroundResource", 0);
                    }
                    if (p.mCallStyleActions && i > 0) {
                        // Clear start margin from non-first buttons to reduce the gap between them.
                        //  (8dp remaining gap is from all buttons' standard 4dp inset).
                        button.setViewLayoutMarginDimen(R.id.action0, RemoteViews.MARGIN_START, 0);
                    }
                    big.addView(R.id.actions, button);
                }
            } else {
@@ -6017,7 +6039,7 @@ public class Notification implements Parcelable
                button.setColorStateList(R.id.action0, "setButtonBackground",
                        ColorStateList.valueOf(background));
                button.setBoolean(R.id.action0, "setHasStroke", !hasColorOverride);
                if (p.mAllowActionIcons) {
                if (p.mCallStyleActions) {
                    button.setImageViewIcon(R.id.action0, action.getIcon());
                    boolean priority = action.getExtras().getBoolean(CallStyle.KEY_ACTION_PRIORITY);
                    button.setBoolean(R.id.action0, "setWrapModePriority", priority);
@@ -9260,6 +9282,17 @@ public class Notification implements Parcelable
            return this;
        }

        /** @hide */
        @Override
        public Notification buildStyled(Notification wip) {
            wip = super.buildStyled(wip);
            // ensure that the actions in the builder and notification are corrected.
            mBuilder.mActions = getActionsListWithSystemActions();
            wip.actions = new Action[mBuilder.mActions.size()];
            mBuilder.mActions.toArray(wip.actions);
            return wip;
        }

        /**
         * @hide
         */
@@ -9319,14 +9352,14 @@ public class Notification implements Parcelable
        }

        @NonNull
        private Action makeNegativeAction(@NonNull StandardTemplateParams p) {
        private Action makeNegativeAction() {
            if (mDeclineIntent == null) {
                return makeAction(p, R.drawable.ic_call_decline,
                return makeAction(R.drawable.ic_call_decline,
                        R.string.call_notification_hang_up_action,
                        mDeclineButtonColor, R.color.call_notification_decline_color,
                        mHangUpIntent);
            } else {
                return makeAction(p, R.drawable.ic_call_decline,
                return makeAction(R.drawable.ic_call_decline,
                        R.string.call_notification_decline_action,
                        mDeclineButtonColor, R.color.call_notification_decline_color,
                        mDeclineIntent);
@@ -9334,18 +9367,17 @@ public class Notification implements Parcelable
        }

        @Nullable
        private Action makeAnswerAction(@NonNull StandardTemplateParams p) {
            return mAnswerIntent == null ? null : makeAction(p, R.drawable.ic_call_answer,
        private Action makeAnswerAction() {
            return mAnswerIntent == null ? null : makeAction(R.drawable.ic_call_answer,
                    R.string.call_notification_answer_action,
                    mAnswerButtonColor, R.color.call_notification_answer_color,
                    mAnswerIntent);
        }

        @NonNull
        private Action makeAction(@NonNull StandardTemplateParams p,
                @DrawableRes int icon, @StringRes int title,
        private Action makeAction(@DrawableRes int icon, @StringRes int title,
                @ColorInt Integer colorInt, @ColorRes int defaultColorRes, PendingIntent intent) {
            if (colorInt == null || !mBuilder.isCallActionColorCustomizable(p)) {
            if (colorInt == null || !mBuilder.isCallActionColorCustomizable()) {
                colorInt = mBuilder.mContext.getColor(defaultColorRes);
            }
            Action action = new Action.Builder(Icon.createWithResource("", icon),
@@ -9357,29 +9389,62 @@ public class Notification implements Parcelable
            return action;
        }

        private ArrayList<Action> makeActionsList(@NonNull StandardTemplateParams p) {
            final Action negativeAction = makeNegativeAction(p);
            final Action answerAction = makeAnswerAction(p);
        private boolean isActionAddedByCallStyle(Action action) {
            // This is an internal extra added by the style to these actions. If an app were to add
            // this extra to the action themselves, the action would be dropped.  :shrug:
            return action != null && action.getExtras().getBoolean(KEY_ACTION_PRIORITY);
        }

            ArrayList<Action> actions = new ArrayList<>(MAX_ACTION_BUTTONS);
            final Action lastAction;
            if (answerAction == null) {
                // If there's no answer action, put the hang up / decline action at the end
                lastAction = negativeAction;
        /**
         * Gets the actions list for the call with the answer/decline/hangUp actions inserted in
         * the correct place.  This returns the correct result even if the system actions have
         * already been added, and even if more actions were added since then.
         * @hide
         */
        @NonNull
        public ArrayList<Action> getActionsListWithSystemActions() {
            // Define the system actions we expect to see
            final Action negativeAction = makeNegativeAction();
            final Action answerAction = makeAnswerAction();
            // Sort the expected actions into the correct order:
            // * If there's no answer action, put the hang up / decline action at the end
            // * Otherwise put the answer action at the end, and put the decline action at start.
            final Action firstAction = answerAction == null ? null : negativeAction;
            final Action lastAction = answerAction == null ? negativeAction : answerAction;

            // Start creating the result list.
            int nonContextualActionSlotsRemaining = MAX_ACTION_BUTTONS;
            ArrayList<Action> resultActions = new ArrayList<>(MAX_ACTION_BUTTONS);
            if (firstAction != null) {
                resultActions.add(firstAction);
                --nonContextualActionSlotsRemaining;
            }

            // Copy actions into the new list, correcting system actions.
            if (mBuilder.mActions != null) {
                for (Notification.Action action : mBuilder.mActions) {
                    if (action.isContextual()) {
                        // Always include all contextual actions
                        resultActions.add(action);
                    } else if (isActionAddedByCallStyle(action)) {
                        // Drop any old versions of system actions
                    } else {
                // Otherwise put the answer action at the end, and put the decline action at start.
                actions.add(negativeAction);
                lastAction = answerAction;
                        // Copy non-contextual actions; decrement the remaining action slots.
                        resultActions.add(action);
                        --nonContextualActionSlotsRemaining;
                    }
                    // If there's exactly one action slot left, fill it with the lastAction.
                    if (nonContextualActionSlotsRemaining == 1) {
                        resultActions.add(lastAction);
                        --nonContextualActionSlotsRemaining;
                    }
            // For consistency with the standard actions bar, contextual actions are ignored.
            for (Action action : mBuilder.getNonContextualActions()) {
                if (actions.size() >= MAX_ACTION_BUTTONS - 1) {
                    break;
                }
                actions.add(action);
            }
            actions.add(lastAction);
            return actions;
            // If there are any action slots left, the lastAction still needs to be added.
            if (nonContextualActionSlotsRemaining >= 1) {
                resultActions.add(lastAction);
            }
            return resultActions;
        }

        private RemoteViews makeCallLayout() {
@@ -9392,19 +9457,15 @@ public class Notification implements Parcelable
            // Bind standard template
            StandardTemplateParams p = mBuilder.mParams.reset()
                    .viewType(StandardTemplateParams.VIEW_TYPE_BIG)
                    .allowActionIcons(true)
                    .callStyleActions(true)
                    .allowTextWithProgress(true)
                    .hideLargeIcon(true)
                    .text(text)
                    .summaryText(mBuilder.processLegacyText(mVerificationText));
            RemoteViews contentView = mBuilder.applyStandardTemplate(
            mBuilder.mActions = getActionsListWithSystemActions();
            RemoteViews contentView = mBuilder.applyStandardTemplateWithActions(
                    mBuilder.getCallLayoutResource(), p, null /* result */);

            // Bind actions.
            mBuilder.resetStandardTemplateWithActions(contentView);
            mBuilder.bindSnoozeAction(contentView, p);
            bindCallActions(contentView, p);

            // Bind some extra conversation-specific header fields.
            mBuilder.setTextViewColorPrimary(contentView, R.id.conversation_text, p);
            mBuilder.setTextViewColorSecondary(contentView, R.id.app_name_divider, p);
@@ -9424,41 +9485,6 @@ public class Notification implements Parcelable
            return contentView;
        }

        private void bindCallActions(RemoteViews view, StandardTemplateParams p) {
            view.setViewVisibility(R.id.actions_container, View.VISIBLE);
            view.setViewVisibility(R.id.actions, View.VISIBLE);
            view.setViewLayoutMarginDimen(R.id.notification_action_list_margin_target,
                    RemoteViews.MARGIN_BOTTOM, 0);

            // Clear view padding to allow buttons to start on the left edge.
            // This must be done before 'setEmphasizedMode' which sets top/bottom margins.
            view.setViewPadding(R.id.actions, 0, 0, 0, 0);
            // Add an optional indent that will make buttons start at the correct column when
            // there is enough space to do so (and fall back to the left edge if not).
            view.setInt(R.id.actions, "setCollapsibleIndentDimen",
                    R.dimen.call_notification_collapsible_indent);

            // Emphasize so that buttons have borders or colored backgrounds
            boolean emphasizedMode = true;
            view.setBoolean(R.id.actions, "setEmphasizedMode", emphasizedMode);
            // Use "wrap_content" (unlike normal emphasized mode) and allow prioritizing the
            // required actions (Answer, Decline, and Hang Up).
            view.setBoolean(R.id.actions, "setPrioritizedWrapMode", true);

            // Create the buttons for the generated actions list.
            int i = 0;
            for (Action action : makeActionsList(p)) {
                final RemoteViews button = mBuilder.generateActionButton(action, emphasizedMode, p);
                if (i > 0) {
                    // Clear start margin from non-first buttons to reduce the gap between buttons.
                    // (8dp remaining gap is from all buttons' standard 4dp inset).
                    button.setViewLayoutMarginDimen(R.id.action0, RemoteViews.MARGIN_START, 0);
                }
                view.addView(R.id.actions, button);
                ++i;
            }
        }

        private void bindCallerVerification(RemoteViews contentView, StandardTemplateParams p) {
            String iconContentDescription = null;
            boolean showDivider = true;
@@ -12057,7 +12083,7 @@ public class Notification implements Parcelable
        boolean mHideProgress;
        boolean mHideSnoozeButton;
        boolean mPromotePicture;
        boolean mAllowActionIcons;
        boolean mCallStyleActions;
        boolean mAllowTextWithProgress;
        CharSequence title;
        CharSequence text;
@@ -12076,7 +12102,7 @@ public class Notification implements Parcelable
            mHideProgress = false;
            mHideSnoozeButton = false;
            mPromotePicture = false;
            mAllowActionIcons = false;
            mCallStyleActions = false;
            mAllowTextWithProgress = false;
            title = null;
            text = null;
@@ -12117,8 +12143,8 @@ public class Notification implements Parcelable
            return this;
        }

        final StandardTemplateParams allowActionIcons(boolean allowActionIcons) {
            this.mAllowActionIcons = allowActionIcons;
        final StandardTemplateParams callStyleActions(boolean callStyleActions) {
            this.mCallStyleActions = callStyleActions;
            return this;
        }

+11 −1
Original line number Diff line number Diff line
@@ -134,7 +134,6 @@ import android.app.AlarmManager;
import android.app.AppGlobals;
import android.app.AppOpsManager;
import android.app.AutomaticZenRule;
import android.app.BroadcastOptions;
import android.app.IActivityManager;
import android.app.INotificationManager;
import android.app.ITransientNotification;
@@ -6070,6 +6069,17 @@ public class NotificationManagerService extends SystemService {
            }
        }

        // Ensure CallStyle has all the correct actions
        if ("android.app.Notification$CallStyle".equals(
                notification.extras.getString(Notification.EXTRA_TEMPLATE))) {
            Notification.Builder builder =
                    Notification.Builder.recoverBuilder(getContext(), notification);
            Notification.CallStyle style = (Notification.CallStyle) builder.getStyle();
            List<Notification.Action> actions = style.getActionsListWithSystemActions();
            notification.actions = new Notification.Action[actions.size()];
            actions.toArray(notification.actions);
        }

        // Remote views? Are they too big?
        checkRemoteViews(pkg, tag, id, notification);
    }