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

Commit 7fea5527 authored by Ibrahim Yilmaz's avatar Ibrahim Yilmaz Committed by Android (Google) Code Review
Browse files

Merge changes I4c65a713,If6df50c9,I34e4299e into main

* changes:
  [Inline Reply] Stop inflating remote input view for heads up
  [Inline Reply] Expand Notification on inline reply
  [Inline Reply] Add onMakeExpandedVisibleForRemoteInput tests
parents a42860d0 f82bbcda
Loading
Loading
Loading
Loading
+88 −1
Original line number Diff line number Diff line
@@ -62,6 +62,7 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntry.
import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
import com.android.systemui.statusbar.notification.logging.NotificationLogger;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.phone.ExpandHeadsUpOnInlineReply;
import com.android.systemui.statusbar.policy.RemoteInputUriController;
import com.android.systemui.statusbar.policy.RemoteInputView;
import com.android.systemui.util.DumpUtilsKt;
@@ -420,6 +421,12 @@ public class NotificationRemoteInputManager implements CoreStartable {
            PendingIntent pendingIntent, @Nullable EditedSuggestionInfo editedSuggestionInfo,
            @Nullable String userMessageContent,
            @Nullable AuthBypassPredicate authBypassCheck) {
        if (ExpandHeadsUpOnInlineReply.isEnabled()) {
            return activateRemoteInputOnExpanded(view, inputs, input, pendingIntent,
                    editedSuggestionInfo, userMessageContent,
                    authBypassCheck);
        }

        ViewParent p = view.getParent();
        RemoteInputView riv = null;
        ExpandableNotificationRow row = null;
@@ -491,6 +498,86 @@ public class NotificationRemoteInputManager implements CoreStartable {
        return true;
    }

    /**
     * Activates a given {@link RemoteInput} on the expanded notification.
     * If the given notification is not expanded, this method will expand the notification
     * first and after that activate remote input on the expanded.
     * @param view The view of the action button or suggestion chip that was tapped.
     * @param inputs The remote inputs that need to be sent to the app.
     * @param input The remote input that needs to be activated.
     * @param pendingIntent The pending intent to be sent to the app.
     * @param editedSuggestionInfo The smart reply that should be inserted in the remote input, or
     *         {@code null} if the user is not editing a smart reply.
     * @param userMessageContent User-entered text with which to initialize the remote input view.
     * @param authBypassCheck Optional auth bypass check associated with this remote input
     *         activation. If {@code null}, we never bypass.
     * @return Whether the {@link RemoteInput} was activated.
     */
    public boolean activateRemoteInputOnExpanded(View view, RemoteInput[] inputs, RemoteInput input,
            PendingIntent pendingIntent, @Nullable EditedSuggestionInfo editedSuggestionInfo,
            @Nullable String userMessageContent,
            @Nullable AuthBypassPredicate authBypassCheck) {
        ViewParent p = view.getParent();
        RemoteInputView riv = null;
        ExpandableNotificationRow row = null;
        while (p != null) {
            if (p instanceof View) {
                View pv = (View) p;
                if (pv.getId() == com.android.internal.R.id.status_bar_latest_event_content) {
                    row = (ExpandableNotificationRow) pv.getTag(R.id.row_tag_for_content_view);
                    break;
                }
            }
            p = p.getParent();
        }

        if (row == null) {
            return false;
        }

        final boolean deferBouncer = authBypassCheck != null;
        if (!deferBouncer && showBouncerForRemoteInput(view, pendingIntent, row)) {
            return true;
        }

        if (!row.getPrivateLayout().getExpandedChild().isShown()) {
            // The expanded layout is selected, but it's not shown yet, let's wait on it to
            // show before we do the animation.
            mCallback.onMakeExpandedVisibleForRemoteInput(row, view, deferBouncer, () -> {
                activateRemoteInputOnExpanded(view, inputs, input, pendingIntent,
                        editedSuggestionInfo, userMessageContent, authBypassCheck);
            });
            return true;
        }

        riv = findRemoteInputView(row.getPrivateLayout().getExpandedChild());
        if (riv == null) {
            return false;
        }

        if (!riv.isAttachedToWindow()) {
            // if we still didn't find a view that is attached, let's abort.
            return false;
        }

        riv.getController().setPendingIntent(pendingIntent);
        riv.getController().setRemoteInput(input);
        riv.getController().setRemoteInputs(inputs);
        riv.getController().setEditedSuggestionInfo(editedSuggestionInfo);
        riv.focusAnimated();
        if (userMessageContent != null) {
            riv.setEditTextContent(userMessageContent);
        }
        if (deferBouncer) {
            final ExpandableNotificationRow finalRow = row;
            riv.getController().setBouncerChecker(() ->
                    !authBypassCheck.canSendRemoteInputWithoutBouncer()
                            && showBouncerForRemoteInput(view, pendingIntent, finalRow));
        }

        return true;
    }

    private boolean showBouncerForRemoteInput(View view, PendingIntent pendingIntent,
            ExpandableNotificationRow row) {

+48 −32
Original line number Diff line number Diff line
@@ -329,13 +329,27 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
    private OnClickListener mExpandClickListener = new OnClickListener() {
        @Override
        public void onClick(View v) {
            toggleExpansionState(v, /* shouldLogExpandClickMetric = */true);
        }
    };

    /**
     * Toggles expansion state.
     */
    public void toggleExpansionState() {
        toggleExpansionState(this, /*shouldLogExpandClickMetric*/ false);
    }

    private void toggleExpansionState(View v, boolean shouldLogExpandClickMetric) {
        if (!shouldShowPublic() && (!mIsMinimized || isExpanded())
                && mGroupMembershipManager.isGroupSummary(mEntry)) {
            mGroupExpansionChanging = true;
            final boolean wasExpanded = mGroupExpansionManager.isGroupExpanded(mEntry);
            boolean nowExpanded = mGroupExpansionManager.toggleGroupExpansion(mEntry);
            mOnExpandClickListener.onExpandClicked(mEntry, v, nowExpanded);
            if (shouldLogExpandClickMetric) {
                mMetricsLogger.action(MetricsEvent.ACTION_NOTIFICATION_GROUP_EXPANDER, nowExpanded);
            }
            onExpansionChanged(true /* userAction */, wasExpanded);
        } else if (mEnableNonGroupedNotificationExpand) {
            if (v.isAccessibilityFocused()) {
@@ -364,10 +378,12 @@ public class ExpandableNotificationRow extends ActivatableNotificationView

            notifyHeightChanged(/* needsAnimation= */ true);
            mOnExpandClickListener.onExpandClicked(mEntry, v, nowExpanded);
            if (shouldLogExpandClickMetric) {
                mMetricsLogger.action(MetricsEvent.ACTION_NOTIFICATION_EXPANDER, nowExpanded);
            }
        }
    };
    }

    private boolean mKeepInParentForDismissAnimation;
    private boolean mRemoved;
    public static final FloatProperty<ExpandableNotificationRow> TRANSLATE_CONTENT =
+34 −22
Original line number Diff line number Diff line
@@ -61,6 +61,7 @@ import com.android.systemui.statusbar.notification.row.shared.AsyncHybridViewInf
import com.android.systemui.statusbar.notification.row.wrapper.NotificationCustomViewWrapper;
import com.android.systemui.statusbar.notification.row.wrapper.NotificationHeaderViewWrapper;
import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper;
import com.android.systemui.statusbar.phone.ExpandHeadsUpOnInlineReply;
import com.android.systemui.statusbar.policy.InflatedSmartReplyState;
import com.android.systemui.statusbar.policy.InflatedSmartReplyViewHolder;
import com.android.systemui.statusbar.policy.RemoteInputView;
@@ -913,8 +914,10 @@ public class NotificationContentView extends FrameLayout implements Notification
                View visibleView = getViewForVisibleType(visibleType);
                if (visibleView != null) {
                    visibleView.setVisibility(VISIBLE);
                    if (!ExpandHeadsUpOnInlineReply.isEnabled()) {
                        transferRemoteInputFocus(visibleType);
                    }
                }

                if (animate && ((visibleType == VISIBLE_TYPE_EXPANDED && mExpandedChild != null)
                        || (visibleType == VISIBLE_TYPE_HEADSUP && mHeadsUpChild != null)
@@ -1397,8 +1400,16 @@ public class NotificationContentView extends FrameLayout implements Notification
        mCachedExpandedRemoteInput = null;
        mCachedExpandedRemoteInputViewController = null;

        if (ExpandHeadsUpOnInlineReply.isEnabled()) {
            mHeadsUpRemoteInput = null;
            mHeadsUpRemoteInputController = null;
            mCachedHeadsUpRemoteInput = null;
            mCachedHeadsUpRemoteInputViewController = null;
        } else {
            ExpandHeadsUpOnInlineReply.assertInLegacyMode();
            if (mHeadsUpChild != null) {
            RemoteInputViewData headsUpData = applyRemoteInput(mHeadsUpChild, mNotificationEntry,
                RemoteInputViewData headsUpData = applyRemoteInput(mHeadsUpChild,
                        mNotificationEntry,
                        hasFreeformRemoteInput, mPreviousHeadsUpRemoteInputIntent,
                        mCachedHeadsUpRemoteInput, mCachedHeadsUpRemoteInputViewController,
                        mHeadsUpWrapper);
@@ -1422,6 +1433,7 @@ public class NotificationContentView extends FrameLayout implements Notification
            mCachedHeadsUpRemoteInput = null;
            mCachedHeadsUpRemoteInputViewController = null;
        }
    }

    private RemoteInputViewData applyRemoteInput(View view, NotificationEntry entry,
            boolean hasRemoteInput, PendingIntent existingPendingIntent, RemoteInputView cachedView,
+33 −18
Original line number Diff line number Diff line
@@ -191,6 +191,16 @@ public class StatusBarRemoteInputCallback implements Callback, Callbacks,
            View clickedView, boolean deferBouncer, Runnable runnable) {
        if (!deferBouncer && mKeyguardStateController.isShowing()) {
            onLockedRemoteInput(row, clickedView);
        } else {
            if (ExpandHeadsUpOnInlineReply.isEnabled()) {
                if (row.isChildInGroup() && !row.areChildrenExpanded()) {
                    // The group isn't expanded, let's make sure it's visible!
                    mGroupExpansionManager.toggleGroupExpansion(row.getEntry());
                } else if (!row.isChildInGroup() && !row.isExpanded()) {
                    // notification isn't expanded, let's make sure it's visible!
                    row.toggleExpansionState();
                    row.getPrivateLayout().setOnExpandedVisibleListener(runnable);
                }
            } else {
                if (row.isChildInGroup() && !row.areChildrenExpanded()) {
                    // The group isn't expanded, let's make sure it's visible!
@@ -200,14 +210,18 @@ public class StatusBarRemoteInputCallback implements Callback, Callbacks,
                if (android.app.Flags.compactHeadsUpNotificationReply()
                        && row.isCompactConversationHeadsUpOnScreen()) {
                    // Notification can be system expanded true and it is set user expanded in
                // activateRemoteInput. notifyHeightChanged also doesn't work as visibleType doesn't
                // change. To expand huning notification properly, we need set userExpanded false.
                    // activateRemoteInput. notifyHeightChanged also doesn't work as visibleType
                    // doesn't change. To expand huning notification properly,
                    // we need set userExpanded false.
                    if (!row.isPinned() && row.isExpanded()) {
                        row.setUserExpanded(false);
                    }
                    // expand notification emits expanded information to HUN listener.
                    row.expandNotification();
                } else {
                    // TODO(b/346976443) Group and normal notification expansions are two different
                    // concepts. We should never call setUserExpanded for expanding groups.

                    // Note: Since Normal HUN has remote input view in it, we don't expect to hit
                    // onMakeExpandedVisibleForRemoteInput from activateRemoteInput for Normal HUN.
                    row.setUserExpanded(true);
@@ -215,6 +229,7 @@ public class StatusBarRemoteInputCallback implements Callback, Callbacks,
                row.getPrivateLayout().setOnExpandedVisibleListener(runnable);
            }
        }
    }

    @Override
    public void onLockedWorkRemoteInput(int userId, ExpandableNotificationRow row,
+182 −1
Original line number Diff line number Diff line
@@ -16,14 +16,18 @@ package com.android.systemui.statusbar.phone;

import static android.content.Intent.ACTION_DEVICE_LOCKED_CHANGED;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.mockito.internal.verification.VerificationModeFactory.times;

import android.content.Intent;
import android.platform.test.annotations.EnableFlags;
import android.testing.TestableLooper;
import android.view.View;

@@ -38,8 +42,10 @@ import com.android.systemui.statusbar.ActionClickLogger;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.NotificationContentView;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.concurrency.FakeExecutor;
@@ -55,6 +61,8 @@ import org.mockito.MockitoAnnotations;
@RunWith(AndroidJUnit4.class)
@TestableLooper.RunWithLooper
public class StatusBarRemoteInputCallbackTest extends SysuiTestCase {

    @Mock private GroupExpansionManager mGroupExpansionManager;
    @Mock private DeviceProvisionedController mDeviceProvisionedController;
    @Mock private com.android.systemui.shade.ShadeController mShadeController;
    @Mock private NotificationLockscreenUserManager mNotificationLockscreenUserManager;
@@ -77,7 +85,7 @@ public class StatusBarRemoteInputCallbackTest extends SysuiTestCase {
                mNotificationLockscreenUserManager);

        mRemoteInputCallback = spy(new StatusBarRemoteInputCallback(mContext,
                mock(GroupExpansionManager.class), mNotificationLockscreenUserManager,
                mGroupExpansionManager, mNotificationLockscreenUserManager,
                mKeyguardStateController, mStatusBarStateController, mStatusBarKeyguardViewManager,
                mActivityStarter, mShadeController,
                new CommandQueue(mContext, new FakeDisplayTracker(mContext)),
@@ -103,4 +111,177 @@ public class StatusBarRemoteInputCallbackTest extends SysuiTestCase {

        verify(mStatusBarKeyguardViewManager).showBouncer(true);
    }

    @Test
    public void onMakeExpandedVisibleForRemoteInput_collapsedGroup_expandGroupExpansion() {
        // GIVEN
        final Runnable onExpandedVisibleRunner = mock(Runnable.class);

        final ExpandableNotificationRow enr = mock(ExpandableNotificationRow.class);
        final NotificationContentView privateLayout = mock(NotificationContentView.class);
        final NotificationEntry enrEntry = mock(NotificationEntry.class);

        when(enr.getPrivateLayout()).thenReturn(privateLayout);
        when(enr.getEntry()).thenReturn(enrEntry);
        when(enr.isChildInGroup()).thenReturn(true);
        when(enr.areChildrenExpanded()).thenReturn(false);

        // WHEN
        mRemoteInputCallback.onMakeExpandedVisibleForRemoteInput(
                enr, mock(View.class), false, onExpandedVisibleRunner);

        // THEN
        verify(mGroupExpansionManager).toggleGroupExpansion(enrEntry);
        verify(enr).setUserExpanded(true);
        verify(privateLayout).setOnExpandedVisibleListener(onExpandedVisibleRunner);
    }

    @Test
    public void onMakeExpandedVisibleForRemoteInput_expandedGroup_setUserExpandedTrue() {
        // GIVEN
        final Runnable onExpandedVisibleRunner = mock(Runnable.class);

        final ExpandableNotificationRow enr = mock(ExpandableNotificationRow.class);
        final NotificationContentView privateLayout = mock(NotificationContentView.class);
        final NotificationEntry enrEntry = mock(NotificationEntry.class);

        when(enr.getPrivateLayout()).thenReturn(privateLayout);
        when(enr.getEntry()).thenReturn(enrEntry);
        when(enr.isChildInGroup()).thenReturn(true);
        when(enr.areChildrenExpanded()).thenReturn(true);

        // WHEN
        mRemoteInputCallback.onMakeExpandedVisibleForRemoteInput(
                enr, mock(View.class), false, onExpandedVisibleRunner);

        // THEN
        verify(mGroupExpansionManager, never()).toggleGroupExpansion(any());
        verify(enr).setUserExpanded(true);
        verify(privateLayout).setOnExpandedVisibleListener(onExpandedVisibleRunner);
    }

    @Test
    public void onMakeExpandedVisibleForRemoteInput_nonGroupNotifications_setUserExpandedTrue() {
        // GIVEN
        final Runnable onExpandedVisibleRunner = mock(Runnable.class);

        final ExpandableNotificationRow enr = mock(ExpandableNotificationRow.class);
        final NotificationContentView privateLayout = mock(NotificationContentView.class);
        final NotificationEntry enrEntry = mock(NotificationEntry.class);

        when(enr.getPrivateLayout()).thenReturn(privateLayout);
        when(enr.getEntry()).thenReturn(enrEntry);
        when(enr.isChildInGroup()).thenReturn(false);

        // WHEN
        mRemoteInputCallback.onMakeExpandedVisibleForRemoteInput(
                enr, mock(View.class), false, onExpandedVisibleRunner);

        // THEN
        verify(mGroupExpansionManager, never()).toggleGroupExpansion(any());
        verify(enr).setUserExpanded(true);
        verify(privateLayout).setOnExpandedVisibleListener(onExpandedVisibleRunner);
    }

    @Test
    @EnableFlags(ExpandHeadsUpOnInlineReply.FLAG_NAME)
    public void onMakeExpandedVisibleForRemoteInput_notExpandedGroup_toggleExpansion() {
        // GIVEN
        final Runnable onExpandedVisibleRunner = mock(Runnable.class);

        final ExpandableNotificationRow enr = mock(ExpandableNotificationRow.class);
        final NotificationContentView privateLayout = mock(NotificationContentView.class);
        final NotificationEntry enrEntry = mock(NotificationEntry.class);

        when(enr.getPrivateLayout()).thenReturn(privateLayout);
        when(enr.getEntry()).thenReturn(enrEntry);
        when(enr.isChildInGroup()).thenReturn(true);
        when(enr.areChildrenExpanded()).thenReturn(false);

        // WHEN
        mRemoteInputCallback.onMakeExpandedVisibleForRemoteInput(
                enr, mock(View.class), false, onExpandedVisibleRunner);

        // THEN
        verify(mGroupExpansionManager).toggleGroupExpansion(enrEntry);
        verify(enr, never()).setUserExpanded(anyBoolean());
        verify(privateLayout, never()).setOnExpandedVisibleListener(any());
    }

    @Test
    @EnableFlags(ExpandHeadsUpOnInlineReply.FLAG_NAME)
    public void onMakeExpandedVisibleForRemoteInput_expandedGroup_notToggleExpansion() {
        // GIVEN
        final Runnable onExpandedVisibleRunner = mock(Runnable.class);

        final ExpandableNotificationRow enr = mock(ExpandableNotificationRow.class);
        final NotificationContentView privateLayout = mock(NotificationContentView.class);
        final NotificationEntry enrEntry = mock(NotificationEntry.class);

        when(enr.getPrivateLayout()).thenReturn(privateLayout);
        when(enr.getEntry()).thenReturn(enrEntry);
        when(enr.isChildInGroup()).thenReturn(true);
        when(enr.areChildrenExpanded()).thenReturn(true);

        // WHEN
        mRemoteInputCallback.onMakeExpandedVisibleForRemoteInput(
                enr, mock(View.class), false, onExpandedVisibleRunner);

        // THEN
        verify(mGroupExpansionManager, never()).toggleGroupExpansion(enrEntry);
        verify(enr, never()).setUserExpanded(anyBoolean());
        verify(privateLayout, never()).setOnExpandedVisibleListener(any());
    }

    @Test
    @EnableFlags(ExpandHeadsUpOnInlineReply.FLAG_NAME)
    public void onMakeExpandedVisibleForRemoteInput_notExpandedNotification_toggleExpansion() {
        // GIVEN
        final Runnable onExpandedVisibleRunner = mock(Runnable.class);

        final ExpandableNotificationRow enr = mock(ExpandableNotificationRow.class);
        final NotificationContentView privateLayout = mock(NotificationContentView.class);
        final NotificationEntry enrEntry = mock(NotificationEntry.class);

        when(enr.getPrivateLayout()).thenReturn(privateLayout);
        when(enr.getEntry()).thenReturn(enrEntry);
        when(enr.isChildInGroup()).thenReturn(false);
        when(enr.isExpanded()).thenReturn(false);

        // WHEN
        mRemoteInputCallback.onMakeExpandedVisibleForRemoteInput(
                enr, mock(View.class), false, onExpandedVisibleRunner);

        // THEN
        verify(enr).toggleExpansionState();
        verify(privateLayout).setOnExpandedVisibleListener(onExpandedVisibleRunner);
        verify(enr, never()).setUserExpanded(anyBoolean());
        verify(mGroupExpansionManager, never()).toggleGroupExpansion(any());
    }

    @Test
    @EnableFlags(ExpandHeadsUpOnInlineReply.FLAG_NAME)
    public void onMakeExpandedVisibleForRemoteInput_expandedNotification_notToggleExpansion() {
        // GIVEN
        final Runnable onExpandedVisibleRunner = mock(Runnable.class);

        final ExpandableNotificationRow enr = mock(ExpandableNotificationRow.class);
        final NotificationContentView privateLayout = mock(NotificationContentView.class);
        final NotificationEntry enrEntry = mock(NotificationEntry.class);

        when(enr.getPrivateLayout()).thenReturn(privateLayout);
        when(enr.getEntry()).thenReturn(enrEntry);
        when(enr.isChildInGroup()).thenReturn(false);
        when(enr.isExpanded()).thenReturn(true);

        // WHEN
        mRemoteInputCallback.onMakeExpandedVisibleForRemoteInput(
                enr, mock(View.class), false, onExpandedVisibleRunner);

        // THEN
        verify(enr, never()).toggleExpansionState();
        verify(privateLayout, never()).setOnExpandedVisibleListener(onExpandedVisibleRunner);
        verify(enr, never()).setUserExpanded(anyBoolean());
        verify(mGroupExpansionManager, never()).toggleGroupExpansion(any());
    }
}