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

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/20228873

Change-Id: I1a4f8ece80c6124af9202cc28d6944fb6dd0481a
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
Rahul Banerjee
2022-10-24 17:24:01 +00:00
committed by Automerger Merge Worker
2 changed files with 141 additions and 2 deletions

View File

@@ -47,6 +47,7 @@ import android.view.OnReceiveContentListener;
import android.view.View; import android.view.View;
import android.view.ViewAnimationUtils; import android.view.ViewAnimationUtils;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.view.ViewRootImpl;
import android.view.WindowInsets; import android.view.WindowInsets;
import android.view.WindowInsetsAnimation; import android.view.WindowInsetsAnimation;
import android.view.WindowInsetsController; import android.view.WindowInsetsController;
@@ -61,6 +62,8 @@ import android.widget.ImageView;
import android.widget.LinearLayout; import android.widget.LinearLayout;
import android.widget.ProgressBar; import android.widget.ProgressBar;
import android.widget.TextView; import android.widget.TextView;
import android.window.OnBackInvokedCallback;
import android.window.OnBackInvokedDispatcher;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
@@ -88,6 +91,7 @@ import java.util.function.Consumer;
*/ */
public class RemoteInputView extends LinearLayout implements View.OnClickListener { public class RemoteInputView extends LinearLayout implements View.OnClickListener {
private static final boolean DEBUG = false;
private static final String TAG = "RemoteInput"; private static final String TAG = "RemoteInput";
// A marker object that let's us easily find views of this class. // 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 // 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 // that need the controller shouldn't have access to the view
private RemoteInputViewController mViewController; private RemoteInputViewController mViewController;
private ViewRootImpl mTestableViewRootImpl;
/** /**
* Enum for logged notification remote input UiEvents. * 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 @Override
protected void onAttachedToWindow() { protected void onAttachedToWindow() {
super.onAttachedToWindow(); super.onAttachedToWindow();
mEditText.mRemoteInputView = this; setEditTextReferenceToSelf();
mEditText.setOnEditorActionListener(mEditorActionHandler); mEditText.setOnEditorActionListener(mEditorActionHandler);
mEditText.addTextChangedListener(mTextWatcher); mEditText.addTextChangedListener(mTextWatcher);
if (mEntry.getRow().isChangingPosition()) { if (mEntry.getRow().isChangingPosition()) {
@@ -456,8 +471,51 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
mController.removeSpinning(mEntry.getKey(), mToken); 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 @Override
public void onVisibilityAggregated(boolean isVisible) { public void onVisibilityAggregated(boolean isVisible) {
if (isVisible) {
registerBackCallback();
} else {
unregisterBackCallback();
}
super.onVisibilityAggregated(isVisible); super.onVisibilityAggregated(isVisible);
mEditText.setEnabled(isVisible && !mSending); mEditText.setEnabled(isVisible && !mSending);
} }
@@ -822,10 +880,21 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
return super.onKeyDown(keyCode, event); 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 @Override
public boolean onKeyUp(int keyCode, KeyEvent event) { public boolean onKeyUp(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) { if (keyCode == KeyEvent.KEYCODE_BACK) {
defocusIfNeeded(true /* animate */); respondToKeycodeBack();
return true; return true;
} }
return super.onKeyUp(keyCode, event); return super.onKeyUp(keyCode, event);

View File

@@ -23,8 +23,12 @@ import static junit.framework.Assert.assertNotNull;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy; 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.ActivityManager;
import android.app.PendingIntent; import android.app.PendingIntent;
@@ -43,10 +47,14 @@ import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper; import android.testing.TestableLooper;
import android.view.ContentInfo; import android.view.ContentInfo;
import android.view.View; import android.view.View;
import android.view.ViewRootImpl;
import android.view.inputmethod.EditorInfo; import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputConnection;
import android.widget.EditText; import android.widget.EditText;
import android.widget.ImageButton; import android.widget.ImageButton;
import android.window.OnBackInvokedCallback;
import android.window.OnBackInvokedDispatcher;
import android.window.WindowOnBackInvokedDispatcher;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.test.filters.SmallTest; import androidx.test.filters.SmallTest;
@@ -67,6 +75,7 @@ import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.MockitoAnnotations; import org.mockito.MockitoAnnotations;
@@ -228,6 +237,67 @@ public class RemoteInputViewTest extends SysuiTestCase {
view.setVisibility(View.VISIBLE); 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 @Test
public void testUiEventLogging_openAndSend() throws Exception { public void testUiEventLogging_openAndSend() throws Exception {
NotificationTestHelper helper = new NotificationTestHelper( NotificationTestHelper helper = new NotificationTestHelper(