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

Commit 638430e7 authored by Tony Mak's avatar Tony Mak
Browse files

Fix and cleanup smart replies checking

1. We now apply smart replies to the first action button that has
   (freeform) remote input. For example, if the notification have
   both reply and reply all buttons, smart replies will be applied
   to the first button only.
2. Enforced getAllowGeneratedReplies check in system generated
   smart replies.
3. Fixed an bug that smartRepliesAdded is not called for system
   generated smart replies.

BUG: 111546109
BUG: 111406942

Test: atest com.android.server.notification.NotificationTest
Test: Check apps generated smart replies via Messenger
Test: Check system generated smart replies via Hangouts

Change-Id: I4db34f557f7e9988be612e4162347b86393d1faf
parent dfc12dba
Loading
Loading
Loading
Loading
+32 −0
Original line number Original line Diff line number Diff line
@@ -68,6 +68,7 @@ import android.text.style.RelativeSizeSpan;
import android.text.style.TextAppearanceSpan;
import android.text.style.TextAppearanceSpan;
import android.util.ArraySet;
import android.util.ArraySet;
import android.util.Log;
import android.util.Log;
import android.util.Pair;
import android.util.SparseArray;
import android.util.SparseArray;
import android.util.proto.ProtoOutputStream;
import android.util.proto.ProtoOutputStream;
import android.view.Gravity;
import android.view.Gravity;
@@ -3129,6 +3130,37 @@ public class Notification implements Parcelable
        return false;
        return false;
    }
    }



    /**
     * Finds and returns a remote input and its corresponding action.
     *
     * @param requiresFreeform requires the remoteinput to allow freeform or not.
     * @return the result pair, {@code null} if no result is found.
     *
     * @hide
     */
    @Nullable
    public Pair<RemoteInput, Action> findRemoteInputActionPair(boolean requiresFreeform) {
        if (actions == null) {
            return null;
        }
        for (Notification.Action action : actions) {
            if (action.getRemoteInputs() == null) {
                continue;
            }
            RemoteInput resultRemoteInput = null;
            for (RemoteInput remoteInput : action.getRemoteInputs()) {
                if (remoteInput.getAllowFreeFormInput() || !requiresFreeform) {
                    resultRemoteInput = remoteInput;
                }
            }
            if (resultRemoteInput != null) {
                return Pair.create(resultRemoteInput, action);
            }
        }
        return null;
    }

    /**
    /**
     * Builder class for {@link Notification} objects.
     * Builder class for {@link Notification} objects.
     *
     *
+37 −43
Original line number Original line Diff line number Diff line
@@ -26,6 +26,7 @@ import android.service.notification.StatusBarNotification;
import android.util.ArraySet;
import android.util.ArraySet;
import android.util.AttributeSet;
import android.util.AttributeSet;
import android.util.Log;
import android.util.Log;
import android.util.Pair;
import android.view.MotionEvent;
import android.view.MotionEvent;
import android.view.NotificationHeaderView;
import android.view.NotificationHeaderView;
import android.view.View;
import android.view.View;
@@ -40,12 +41,12 @@ import com.android.internal.util.ArrayUtils;
import com.android.internal.util.ContrastColorUtil;
import com.android.internal.util.ContrastColorUtil;
import com.android.systemui.Dependency;
import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.R;
import com.android.systemui.statusbar.notification.NotificationData;
import com.android.systemui.statusbar.RemoteInputController;
import com.android.systemui.statusbar.RemoteInputController;
import com.android.systemui.statusbar.SmartReplyController;
import com.android.systemui.statusbar.SmartReplyController;
import com.android.systemui.statusbar.TransformableView;
import com.android.systemui.statusbar.TransformableView;
import com.android.systemui.statusbar.notification.row.wrapper.NotificationCustomViewWrapper;
import com.android.systemui.statusbar.notification.NotificationData;
import com.android.systemui.statusbar.notification.NotificationUtils;
import com.android.systemui.statusbar.notification.NotificationUtils;
import com.android.systemui.statusbar.notification.row.wrapper.NotificationCustomViewWrapper;
import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper;
import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper;
import com.android.systemui.statusbar.phone.NotificationGroupManager;
import com.android.systemui.statusbar.phone.NotificationGroupManager;
import com.android.systemui.statusbar.policy.RemoteInputView;
import com.android.systemui.statusbar.policy.RemoteInputView;
@@ -1188,6 +1189,7 @@ public class NotificationContentView extends FrameLayout {
        updateSingleLineView();
        updateSingleLineView();
        updateAmbientSingleLineView();
        updateAmbientSingleLineView();
    }
    }

    private void updateSingleLineView() {
    private void updateSingleLineView() {
        if (mIsChildInGroup) {
        if (mIsChildInGroup) {
            boolean isNewView = mSingleLineView == null;
            boolean isNewView = mSingleLineView == null;
@@ -1223,53 +1225,44 @@ public class NotificationContentView extends FrameLayout {
            return;
            return;
        }
        }


        boolean enableSmartReplies = (mSmartReplyConstants.isEnabled()
        Notification notification = entry.notification.getNotification();

        Pair<RemoteInput, Notification.Action> remoteInputActionPair =
                entry.notification.getNotification().findRemoteInputActionPair(false /*freeform */);
        Pair<RemoteInput, Notification.Action> freeformRemoteInputActionPair =
                notification.findRemoteInputActionPair(true /*freeform */);

        boolean enableAppGeneratedSmartReplies = (mSmartReplyConstants.isEnabled()
                && (!mSmartReplyConstants.requiresTargetingP()
                && (!mSmartReplyConstants.requiresTargetingP()
                || entry.targetSdk >= Build.VERSION_CODES.P));
                || entry.targetSdk >= Build.VERSION_CODES.P));


        boolean hasRemoteInput = false;
        RemoteInput remoteInputWithChoices = null;
        RemoteInput remoteInputWithChoices = null;
        PendingIntent pendingIntentWithChoices= null;
        PendingIntent pendingIntentWithChoices= null;
        CharSequence[] choices = null;
        CharSequence[] choices = null;

        if (enableAppGeneratedSmartReplies
        Notification.Action[] actions = entry.notification.getNotification().actions;
                && remoteInputActionPair != null
        if (actions != null) {
                && !ArrayUtils.isEmpty(remoteInputActionPair.first.getChoices())) {
            for (Notification.Action a : actions) {
            // app generated smart replies
                if (a.getRemoteInputs() == null) {
            remoteInputWithChoices = remoteInputActionPair.first;
                    continue;
            pendingIntentWithChoices = remoteInputActionPair.second.actionIntent;
                }
            choices = remoteInputActionPair.first.getChoices();
                for (RemoteInput ri : a.getRemoteInputs()) {
        } else if (!ArrayUtils.isEmpty(entry.smartReplies)
                    boolean showRemoteInputView = ri.getAllowFreeFormInput();
                && freeformRemoteInputActionPair != null
                    boolean showSmartReplyView = enableSmartReplies
                && freeformRemoteInputActionPair.second.getAllowGeneratedReplies()) {
                            && (ArrayUtils.isEmpty(ri.getChoices())
            // system generated smart replies
                            || (showRemoteInputView && !ArrayUtils.isEmpty(entry.smartReplies)));
            remoteInputWithChoices = freeformRemoteInputActionPair.first;
                    if (showRemoteInputView) {
            pendingIntentWithChoices = freeformRemoteInputActionPair.second.actionIntent;
                        hasRemoteInput = true;
                    }
                    if (showSmartReplyView) {
                        remoteInputWithChoices = ri;
                        pendingIntentWithChoices = a.actionIntent;
                        if (!ArrayUtils.isEmpty(ri.getChoices())) {
                            choices = ri.getChoices();
                        } else {
            choices = entry.smartReplies;
            choices = entry.smartReplies;
        }
        }
                    }
                    if (showRemoteInputView || showSmartReplyView) {
                        break;
                    }
                }
            }
        }


        applyRemoteInput(entry, hasRemoteInput);
        applyRemoteInput(entry, freeformRemoteInputActionPair != null);
        applySmartReplyView(remoteInputWithChoices, pendingIntentWithChoices, entry, choices);
        applySmartReplyView(remoteInputWithChoices, pendingIntentWithChoices, entry, choices);
    }
    }


    private void applyRemoteInput(NotificationData.Entry entry, boolean hasRemoteInput) {
    private void applyRemoteInput(NotificationData.Entry entry, boolean hasFreeformRemoteInput) {
        View bigContentView = mExpandedChild;
        View bigContentView = mExpandedChild;
        if (bigContentView != null) {
        if (bigContentView != null) {
            mExpandedRemoteInput = applyRemoteInput(bigContentView, entry, hasRemoteInput,
            mExpandedRemoteInput = applyRemoteInput(bigContentView, entry, hasFreeformRemoteInput,
                    mPreviousExpandedRemoteInputIntent, mCachedExpandedRemoteInput,
                    mPreviousExpandedRemoteInputIntent, mCachedExpandedRemoteInput,
                    mExpandedWrapper);
                    mExpandedWrapper);
        } else {
        } else {
@@ -1284,7 +1277,8 @@ public class NotificationContentView extends FrameLayout {


        View headsUpContentView = mHeadsUpChild;
        View headsUpContentView = mHeadsUpChild;
        if (headsUpContentView != null) {
        if (headsUpContentView != null) {
            mHeadsUpRemoteInput = applyRemoteInput(headsUpContentView, entry, hasRemoteInput,
            mHeadsUpRemoteInput = applyRemoteInput(
                    headsUpContentView, entry, hasFreeformRemoteInput,
                    mPreviousHeadsUpRemoteInputIntent, mCachedHeadsUpRemoteInput, mHeadsUpWrapper);
                    mPreviousHeadsUpRemoteInputIntent, mCachedHeadsUpRemoteInput, mHeadsUpWrapper);
        } else {
        } else {
            mHeadsUpRemoteInput = null;
            mHeadsUpRemoteInput = null;
@@ -1370,8 +1364,8 @@ public class NotificationContentView extends FrameLayout {
            mExpandedSmartReplyView =
            mExpandedSmartReplyView =
                    applySmartReplyView(mExpandedChild, remoteInput, pendingIntent, entry, choices);
                    applySmartReplyView(mExpandedChild, remoteInput, pendingIntent, entry, choices);
            if (mExpandedSmartReplyView != null && remoteInput != null
            if (mExpandedSmartReplyView != null && remoteInput != null
                    && remoteInput.getChoices() != null && remoteInput.getChoices().length > 0) {
                    && choices != null && choices.length > 0) {
                mSmartReplyController.smartRepliesAdded(entry, remoteInput.getChoices().length);
                mSmartReplyController.smartRepliesAdded(entry, choices.length);
            }
            }
        }
        }
    }
    }
+90 −0
Original line number Original line Diff line number Diff line
@@ -48,6 +48,7 @@ import android.support.test.runner.AndroidJUnit4;
import android.text.SpannableStringBuilder;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.Spanned;
import android.text.style.StyleSpan;
import android.text.style.StyleSpan;
import android.util.Pair;
import android.widget.RemoteViews;
import android.widget.RemoteViews;


import com.android.server.UiServiceTestCase;
import com.android.server.UiServiceTestCase;
@@ -541,5 +542,94 @@ public class NotificationTest extends UiServiceTestCase {


        assertFalse(Notification.areActionsVisiblyDifferent(n1, n2));
        assertFalse(Notification.areActionsVisiblyDifferent(n1, n2));
    }
    }

    @Test
    public void testFreeformRemoteInputActionPair_noRemoteInput() {
        PendingIntent intent = mock(PendingIntent.class);
        Icon icon = mock(Icon.class);
        Notification notification = new Notification.Builder(mContext, "test")
                .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent)
                        .build())
                .build();
        assertNull(notification.findRemoteInputActionPair(false));
    }

    @Test
    public void testFreeformRemoteInputActionPair_hasRemoteInput() {
        PendingIntent intent = mock(PendingIntent.class);
        Icon icon = mock(Icon.class);

        RemoteInput remoteInput = new RemoteInput.Builder("a").build();

        Notification.Action actionWithRemoteInput =
                new Notification.Action.Builder(icon, "TEXT 1", intent)
                        .addRemoteInput(remoteInput)
                        .addRemoteInput(remoteInput)
                        .build();

        Notification.Action actionWithoutRemoteInput =
                new Notification.Action.Builder(icon, "TEXT 2", intent)
                        .build();

        Notification notification = new Notification.Builder(mContext, "test")
                .addAction(actionWithoutRemoteInput)
                .addAction(actionWithRemoteInput)
                .build();

        Pair<RemoteInput, Notification.Action> remoteInputActionPair =
                notification.findRemoteInputActionPair(false);

        assertNotNull(remoteInputActionPair);
        assertEquals(remoteInput, remoteInputActionPair.first);
        assertEquals(actionWithRemoteInput, remoteInputActionPair.second);
    }

    @Test
    public void testFreeformRemoteInputActionPair_requestFreeform_noFreeformRemoteInput() {
        PendingIntent intent = mock(PendingIntent.class);
        Icon icon = mock(Icon.class);
        Notification notification = new Notification.Builder(mContext, "test")
                .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent)
                        .addRemoteInput(
                                new RemoteInput.Builder("a")
                                        .setAllowFreeFormInput(false).build())
                        .build())
                .build();
        assertNull(notification.findRemoteInputActionPair(true));
    }

    @Test
    public void testFreeformRemoteInputActionPair_requestFreeform_hasFreeformRemoteInput() {
        PendingIntent intent = mock(PendingIntent.class);
        Icon icon = mock(Icon.class);

        RemoteInput remoteInput =
                new RemoteInput.Builder("a").setAllowFreeFormInput(false).build();
        RemoteInput freeformRemoteInput =
                new RemoteInput.Builder("b").setAllowFreeFormInput(true).build();

        Notification.Action actionWithFreeformRemoteInput =
                new Notification.Action.Builder(icon, "TEXT 1", intent)
                        .addRemoteInput(remoteInput)
                        .addRemoteInput(freeformRemoteInput)
                        .build();

        Notification.Action actionWithoutFreeformRemoteInput =
                new Notification.Action.Builder(icon, "TEXT 2", intent)
                        .addRemoteInput(remoteInput)
                        .build();

        Notification notification = new Notification.Builder(mContext, "test")
                .addAction(actionWithoutFreeformRemoteInput)
                .addAction(actionWithFreeformRemoteInput)
                .build();

        Pair<RemoteInput, Notification.Action> remoteInputActionPair =
                notification.findRemoteInputActionPair(true);

        assertNotNull(remoteInputActionPair);
        assertEquals(freeformRemoteInput, remoteInputActionPair.first);
        assertEquals(actionWithFreeformRemoteInput, remoteInputActionPair.second);
    }
}
}