Loading packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java +71 −2 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -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. Loading Loading @@ -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. Loading Loading @@ -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()) { Loading @@ -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); } Loading Loading @@ -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); Loading packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java +70 −0 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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; Loading Loading @@ -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( Loading Loading
packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java +71 −2 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -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. Loading Loading @@ -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. Loading Loading @@ -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()) { Loading @@ -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); } Loading Loading @@ -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); Loading
packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java +70 −0 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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; Loading Loading @@ -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( Loading