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:
@@ -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);
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
Reference in New Issue
Block a user