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

Commit 4c1fcc89 authored by Adrian Roos's avatar Adrian Roos
Browse files

Fix remote input view clobbering

- If a notification is updated, make sure that the focus
  is kept even when the change cannot be applied inline.
- Make sure to update the pending intent to match one
  of the new actions in case the old one is no longer
  valid, and to prevent sending text to the wrong action
  unintentionally.
- Keep focus when switching between heads-up and expanded.
- Prevent the action container from becoming transiently
  gone during reapplying.

Fixes: 27357771
Change-Id: Ie9f49935827f6efb15b73f8d686f0c110448456a
parent 9fa8b545
Loading
Loading
Loading
Loading
+4 −1
Original line number Diff line number Diff line
@@ -3351,7 +3351,8 @@ public class Notification implements Parcelable
        }

        private void resetStandardTemplateWithActions(RemoteViews big) {
            big.setViewVisibility(R.id.actions_container, View.GONE);
            // actions_container is only reset when there are no actions to avoid focus issues with
            // remote inputs.
            big.setViewVisibility(R.id.actions, View.GONE);
            big.removeAllViews(R.id.actions);

@@ -3396,6 +3397,8 @@ public class Notification implements Parcelable
                    }
                    big.addView(R.id.actions, button);
                }
            } else {
                big.setViewVisibility(R.id.actions_container, View.GONE);
            }

            CharSequence[] replyText = mN.extras.getCharSequenceArray(EXTRA_REMOTE_INPUT_HISTORY);
+55 −3
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar;

import android.app.Notification;
import android.app.PendingIntent;
import android.app.RemoteInput;
import android.content.Context;
import android.graphics.Rect;
@@ -118,6 +119,8 @@ public class NotificationContentView extends FrameLayout {
    private int mTransformationStartVisibleType;
    private boolean mUserExpanding;
    private int mSingleLineWidthIndention;
    private PendingIntent mPreviousExpandedRemoteInputIntent;
    private PendingIntent mPreviousHeadsUpRemoteInputIntent;

    public NotificationContentView(Context context, AttributeSet attrs) {
        super(context, attrs);
@@ -280,13 +283,19 @@ public class NotificationContentView extends FrameLayout {
            mContractedChild.animate().cancel();
            removeView(mContractedChild);
        }
        mPreviousExpandedRemoteInputIntent =
                mExpandedRemoteInput != null ? mExpandedRemoteInput.getPendingIntent() : null;
        if (mExpandedChild != null) {
            mExpandedChild.animate().cancel();
            removeView(mExpandedChild);
            mExpandedRemoteInput = null;
        }
        mPreviousHeadsUpRemoteInputIntent =
                mHeadsUpRemoteInput != null ? mHeadsUpRemoteInput.getPendingIntent() : null;
        if (mHeadsUpChild != null) {
            mHeadsUpChild.animate().cancel();
            removeView(mHeadsUpChild);
            mHeadsUpRemoteInput = null;
        }
        mContractedChild = null;
        mExpandedChild = null;
@@ -496,6 +505,12 @@ public class NotificationContentView extends FrameLayout {
        }
        int visibleType = calculateVisibleType();
        if (visibleType != mVisibleType || force) {
            View visibleView = getViewForVisibleType(visibleType);
            if (visibleView != null) {
                visibleView.setVisibility(VISIBLE);
                transferRemoteInputFocus(visibleType);
            }

            if (animate && ((visibleType == VISIBLE_TYPE_EXPANDED && mExpandedChild != null)
                    || (visibleType == VISIBLE_TYPE_HEADSUP && mHeadsUpChild != null)
                    || (visibleType == VISIBLE_TYPE_SINGLELINE && mSingleLineView != null)
@@ -559,6 +574,19 @@ public class NotificationContentView extends FrameLayout {
        });
    }

    private void transferRemoteInputFocus(int visibleType) {
        if (visibleType == VISIBLE_TYPE_HEADSUP
                && mHeadsUpRemoteInput != null
                && (mExpandedRemoteInput != null && mExpandedRemoteInput.isActive())) {
            mHeadsUpRemoteInput.stealFocusFrom(mExpandedRemoteInput);
        }
        if (visibleType == VISIBLE_TYPE_EXPANDED
                && mExpandedRemoteInput != null
                && (mHeadsUpRemoteInput != null && mHeadsUpRemoteInput.isActive())) {
            mExpandedRemoteInput.stealFocusFrom(mHeadsUpRemoteInput);
        }
    }

    /**
     * @param visibleType one of the static enum types in this view
     * @return the corresponding transformable view according to the given visible type
@@ -736,6 +764,8 @@ public class NotificationContentView extends FrameLayout {
        updateShowingLegacyBackground();
        selectLayout(false /* animate */, true /* force */);
        setDark(mDark, false /* animate */, 0 /* delay */);
        mPreviousExpandedRemoteInputIntent = null;
        mPreviousHeadsUpRemoteInputIntent = null;
    }

    private void updateSingleLineView() {
@@ -771,19 +801,23 @@ public class NotificationContentView extends FrameLayout {

        View bigContentView = mExpandedChild;
        if (bigContentView != null) {
            mExpandedRemoteInput = applyRemoteInput(bigContentView, entry, hasRemoteInput);
            mExpandedRemoteInput = applyRemoteInput(bigContentView, entry, hasRemoteInput,
                    mPreviousExpandedRemoteInputIntent);
        } else {
            mExpandedRemoteInput = null;
        }

        View headsUpContentView = mHeadsUpChild;
        if (headsUpContentView != null) {
            mHeadsUpRemoteInput = applyRemoteInput(headsUpContentView, entry, hasRemoteInput);
            mHeadsUpRemoteInput = applyRemoteInput(headsUpContentView, entry, hasRemoteInput,
                    mPreviousHeadsUpRemoteInputIntent);
        } else {
            mHeadsUpRemoteInput = null;
        }
    }

    private RemoteInputView applyRemoteInput(View view, NotificationData.Entry entry, boolean hasRemoteInput) {
    private RemoteInputView applyRemoteInput(View view, NotificationData.Entry entry,
            boolean hasRemoteInput, PendingIntent existingPendingIntent) {
        View actionContainerCandidate = view.findViewById(
                com.android.internal.R.id.actions_container);
        if (actionContainerCandidate instanceof FrameLayout) {
@@ -814,6 +848,24 @@ public class NotificationContentView extends FrameLayout {
                existing.setBackgroundColor(NotificationColorUtil.ensureTextBackgroundColor(color,
                        mContext.getColor(R.color.remote_input_text),
                        mContext.getColor(R.color.remote_input_hint)));

                if (existingPendingIntent != null || existing.isActive()) {
                    // The current action could be gone, or the pending intent no longer valid.
                    // If we find a matching action in the new notification, focus, otherwise close.
                    Notification.Action[] actions = entry.notification.getNotification().actions;
                    if (existingPendingIntent != null) {
                        existing.setPendingIntent(existingPendingIntent);
                    }
                    if (existing.updatePendingIntentFromActions(actions)) {
                        if (!existing.isActive()) {
                            existing.focus();
                        }
                    } else {
                        if (existing.isActive()) {
                            existing.close();
                        }
                    }
                }
            }
            return existing;
        }
+59 −0
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package com.android.systemui.statusbar.policy;

import android.app.Notification;
import android.app.PendingIntent;
import android.app.RemoteInput;
import android.content.Context;
@@ -197,6 +198,7 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
    }

    public void focus() {
        setVisibility(VISIBLE);
        mController.addRemoteInput(mEntry);
        mEditText.setInnerFocusable(true);
        mEditText.mShowImeOnInputConnection = true;
@@ -275,6 +277,63 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
        }
    }

    public boolean isActive() {
        return mEditText.isFocused();
    }

    public void stealFocusFrom(RemoteInputView other) {
        other.close();
        setPendingIntent(other.mPendingIntent);
        setRemoteInput(other.mRemoteInputs, other.mRemoteInput);
        focus();
    }

    /**
     * Tries to find an action in {@param actions} that matches the current pending intent
     * of this view and updates its state to that of the found action
     *
     * @return true if a matching action was found, false otherwise
     */
    public boolean updatePendingIntentFromActions(Notification.Action[] actions) {
        boolean found = false;
        if (mPendingIntent == null || actions == null) {
            return false;
        }
        Intent current = mPendingIntent.getIntent();
        if (current == null) {
            return false;
        }

        for (Notification.Action a : actions) {
            RemoteInput[] inputs = a.getRemoteInputs();
            if (a.actionIntent == null || inputs == null) {
                continue;
            }
            Intent candidate = a.actionIntent.getIntent();
            if (!current.filterEquals(candidate)) {
                continue;
            }

            RemoteInput input = null;
            for (RemoteInput i : inputs) {
                if (i.getAllowFreeFormInput()) {
                    input = i;
                }
            }
            if (input == null) {
                continue;
            }
            setPendingIntent(a.actionIntent);
            setRemoteInput(inputs, input);
            return true;
        }
        return false;
    }

    public PendingIntent getPendingIntent() {
        return mPendingIntent;
    }

    /**
     * An EditText that changes appearance based on whether it's focusable and becomes
     * un-focusable whenever the user navigates away from it or it becomes invisible.