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

Commit efc9d1ab authored by Rahul Banerjee's avatar Rahul Banerjee Committed by Automerger Merge Worker
Browse files

Merge "Migrate RemoteInputView to Predictive Back." into tm-qpr-dev am: 23161303 am: fb3360ff

parents 4177a77b fb3360ff
Loading
Loading
Loading
Loading
+71 −2
Original line number Diff line number Diff line
@@ -47,6 +47,7 @@ import android.view.OnReceiveContentListener;
import android.view.View;
import android.view.ViewAnimationUtils;
import android.view.ViewGroup;
import android.view.ViewRootImpl;
import android.view.WindowInsets;
import android.view.WindowInsetsAnimation;
import android.view.WindowInsetsController;
@@ -61,6 +62,8 @@ import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.window.OnBackInvokedCallback;
import android.window.OnBackInvokedDispatcher;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -88,6 +91,7 @@ import java.util.function.Consumer;
 */
public class RemoteInputView extends LinearLayout implements View.OnClickListener {

    private static final boolean DEBUG = false;
    private static final String TAG = "RemoteInput";

    // A marker object that let's us easily find views of this class.
@@ -124,6 +128,7 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
    // TODO(b/193539698): remove this; views shouldn't have access to their controller, and places
    //  that need the controller shouldn't have access to the view
    private RemoteInputViewController mViewController;
    private ViewRootImpl mTestableViewRootImpl;

    /**
     * Enum for logged notification remote input UiEvents.
@@ -430,10 +435,20 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
        }
    }

    @VisibleForTesting
    protected void setViewRootImpl(ViewRootImpl viewRoot) {
        mTestableViewRootImpl = viewRoot;
    }

    @VisibleForTesting
    protected void setEditTextReferenceToSelf() {
        mEditText.mRemoteInputView = this;
    }

    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
        mEditText.mRemoteInputView = this;
        setEditTextReferenceToSelf();
        mEditText.setOnEditorActionListener(mEditorActionHandler);
        mEditText.addTextChangedListener(mTextWatcher);
        if (mEntry.getRow().isChangingPosition()) {
@@ -456,8 +471,51 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
        mController.removeSpinning(mEntry.getKey(), mToken);
    }

    @Override
    public ViewRootImpl getViewRootImpl() {
        if (mTestableViewRootImpl != null) {
            return mTestableViewRootImpl;
        }
        return super.getViewRootImpl();
    }

    private void registerBackCallback() {
        ViewRootImpl viewRoot = getViewRootImpl();
        if (viewRoot == null) {
            if (DEBUG) {
                Log.d(TAG, "ViewRoot was null, NOT registering Predictive Back callback");
            }
            return;
        }
        if (DEBUG) {
            Log.d(TAG, "registering Predictive Back callback");
        }
        viewRoot.getOnBackInvokedDispatcher().registerOnBackInvokedCallback(
                OnBackInvokedDispatcher.PRIORITY_OVERLAY, mEditText.mOnBackInvokedCallback);
    }

    private void unregisterBackCallback() {
        ViewRootImpl viewRoot = getViewRootImpl();
        if (viewRoot == null) {
            if (DEBUG) {
                Log.d(TAG, "ViewRoot was null, NOT unregistering Predictive Back callback");
            }
            return;
        }
        if (DEBUG) {
            Log.d(TAG, "unregistering Predictive Back callback");
        }
        viewRoot.getOnBackInvokedDispatcher().unregisterOnBackInvokedCallback(
                mEditText.mOnBackInvokedCallback);
    }

    @Override
    public void onVisibilityAggregated(boolean isVisible) {
        if (isVisible) {
            registerBackCallback();
        } else {
            unregisterBackCallback();
        }
        super.onVisibilityAggregated(isVisible);
        mEditText.setEnabled(isVisible && !mSending);
    }
@@ -822,10 +880,21 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
            return super.onKeyDown(keyCode, event);
        }

        private final OnBackInvokedCallback mOnBackInvokedCallback = () -> {
            if (DEBUG) {
                Log.d(TAG, "Predictive Back Callback dispatched");
            }
            respondToKeycodeBack();
        };

        private void respondToKeycodeBack() {
            defocusIfNeeded(true /* animate */);
        }

        @Override
        public boolean onKeyUp(int keyCode, KeyEvent event) {
            if (keyCode == KeyEvent.KEYCODE_BACK) {
                defocusIfNeeded(true /* animate */);
                respondToKeycodeBack();
                return true;
            }
            return super.onKeyUp(keyCode, event);
+70 −0
Original line number Diff line number Diff line
@@ -23,8 +23,12 @@ import static junit.framework.Assert.assertNotNull;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import android.app.ActivityManager;
import android.app.PendingIntent;
@@ -43,10 +47,14 @@ import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.ContentInfo;
import android.view.View;
import android.view.ViewRootImpl;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
import android.widget.EditText;
import android.widget.ImageButton;
import android.window.OnBackInvokedCallback;
import android.window.OnBackInvokedDispatcher;
import android.window.WindowOnBackInvokedDispatcher;

import androidx.annotation.NonNull;
import androidx.test.filters.SmallTest;
@@ -67,6 +75,7 @@ import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

@@ -228,6 +237,67 @@ public class RemoteInputViewTest extends SysuiTestCase {
        view.setVisibility(View.VISIBLE);
    }

    @Test
    public void testPredictiveBack_registerAndUnregister() throws Exception {
        NotificationTestHelper helper = new NotificationTestHelper(
                mContext,
                mDependency,
                TestableLooper.get(this));
        ExpandableNotificationRow row = helper.createRow();
        RemoteInputView view = RemoteInputView.inflate(mContext, null, row.getEntry(), mController);

        ViewRootImpl viewRoot = mock(ViewRootImpl.class);
        WindowOnBackInvokedDispatcher backInvokedDispatcher = mock(
                WindowOnBackInvokedDispatcher.class);
        ArgumentCaptor<OnBackInvokedCallback> onBackInvokedCallbackCaptor = ArgumentCaptor.forClass(
                OnBackInvokedCallback.class);
        when(viewRoot.getOnBackInvokedDispatcher()).thenReturn(backInvokedDispatcher);
        view.setViewRootImpl(viewRoot);

        /* verify that predictive back callback registered when RemoteInputView becomes visible */
        view.onVisibilityAggregated(true);
        verify(backInvokedDispatcher).registerOnBackInvokedCallback(
                eq(OnBackInvokedDispatcher.PRIORITY_OVERLAY),
                onBackInvokedCallbackCaptor.capture());

        /* verify that same callback unregistered when RemoteInputView becomes invisible */
        view.onVisibilityAggregated(false);
        verify(backInvokedDispatcher).unregisterOnBackInvokedCallback(
                eq(onBackInvokedCallbackCaptor.getValue()));
    }

    @Test
    public void testUiPredictiveBack_openAndDispatchCallback() throws Exception {
        NotificationTestHelper helper = new NotificationTestHelper(
                mContext,
                mDependency,
                TestableLooper.get(this));
        ExpandableNotificationRow row = helper.createRow();
        RemoteInputView view = RemoteInputView.inflate(mContext, null, row.getEntry(), mController);
        ViewRootImpl viewRoot = mock(ViewRootImpl.class);
        WindowOnBackInvokedDispatcher backInvokedDispatcher = mock(
                WindowOnBackInvokedDispatcher.class);
        ArgumentCaptor<OnBackInvokedCallback> onBackInvokedCallbackCaptor = ArgumentCaptor.forClass(
                OnBackInvokedCallback.class);
        when(viewRoot.getOnBackInvokedDispatcher()).thenReturn(backInvokedDispatcher);
        view.setViewRootImpl(viewRoot);
        view.onVisibilityAggregated(true);
        view.setEditTextReferenceToSelf();

        /* capture the callback during registration */
        verify(backInvokedDispatcher).registerOnBackInvokedCallback(
                eq(OnBackInvokedDispatcher.PRIORITY_OVERLAY),
                onBackInvokedCallbackCaptor.capture());

        view.focus();

        /* invoke the captured callback */
        onBackInvokedCallbackCaptor.getValue().onBackInvoked();

        /* verify that the RemoteInputView goes away */
        assertEquals(view.getVisibility(), View.GONE);
    }

    @Test
    public void testUiEventLogging_openAndSend() throws Exception {
        NotificationTestHelper helper = new NotificationTestHelper(