Loading core/java/android/view/InputEventReceiver.java +2 −2 Original line number Diff line number Diff line Loading @@ -174,10 +174,10 @@ public abstract class InputEventReceiver { * Called when the display for the window associated with the input channel has entered or * exited touch mode. * * @param isInTouchMode {@code true} if the display showing the window associated with the * @param inTouchMode {@code true} if the display showing the window associated with the * input channel entered touch mode. */ public void onTouchModeChanged(boolean isInTouchMode) { public void onTouchModeChanged(boolean inTouchMode) { } /** Loading core/java/android/view/ViewRootImpl.java +39 −8 Original line number Diff line number Diff line Loading @@ -3376,14 +3376,12 @@ public final class ViewRootImpl implements ViewParent, private void handleWindowFocusChanged() { final boolean hasWindowFocus; final boolean inTouchMode; synchronized (this) { if (!mWindowFocusChanged) { return; } mWindowFocusChanged = false; hasWindowFocus = mUpcomingWindowFocus; inTouchMode = mUpcomingInTouchMode; } // TODO (b/131181940): Make sure this doesn't leak Activity with mActivityConfigCallback // config changes. Loading @@ -3396,9 +3394,7 @@ public final class ViewRootImpl implements ViewParent, if (mAdded) { profileRendering(hasWindowFocus); if (hasWindowFocus) { ensureTouchModeLocally(inTouchMode); if (mAttachInfo.mThreadedRenderer != null && mSurface.isValid()) { mFullRedrawNeeded = true; try { Loading Loading @@ -3470,6 +3466,14 @@ public final class ViewRootImpl implements ViewParent, } } private void handleWindowTouchModeChanged() { final boolean inTouchMode; synchronized (this) { inTouchMode = mUpcomingInTouchMode; } ensureTouchModeLocally(inTouchMode); } private void fireAccessibilityFocusEventIfHasFocusedNode() { if (!AccessibilityManager.getInstance(mContext).isEnabled()) { return; Loading Loading @@ -5127,6 +5131,7 @@ public final class ViewRootImpl implements ViewParent, private static final int MSG_SHOW_INSETS = 34; private static final int MSG_HIDE_INSETS = 35; private static final int MSG_REQUEST_SCROLL_CAPTURE = 36; private static final int MSG_WINDOW_TOUCH_MODE_CHANGED = 37; final class ViewRootHandler extends Handler { Loading Loading @@ -5193,6 +5198,8 @@ public final class ViewRootImpl implements ViewParent, return "MSG_SHOW_INSETS"; case MSG_HIDE_INSETS: return "MSG_HIDE_INSETS"; case MSG_WINDOW_TOUCH_MODE_CHANGED: return "MSG_WINDOW_TOUCH_MODE_CHANGED"; } return super.getMessageName(message); } Loading Loading @@ -5315,9 +5322,12 @@ public final class ViewRootImpl implements ViewParent, case MSG_WINDOW_FOCUS_CHANGED: { handleWindowFocusChanged(); } break; case MSG_DIE: case MSG_WINDOW_TOUCH_MODE_CHANGED: { handleWindowTouchModeChanged(); } break; case MSG_DIE: { doDie(); break; } break; case MSG_DISPATCH_INPUT_EVENT: { SomeArgs args = (SomeArgs) msg.obj; InputEvent event = (InputEvent) args.arg1; Loading Loading @@ -8687,6 +8697,11 @@ public final class ViewRootImpl implements ViewParent, windowFocusChanged(hasFocus, inTouchMode); } @Override public void onTouchModeChanged(boolean inTouchMode) { touchModeChanged(inTouchMode); } @Override public void onPointerCaptureEvent(boolean pointerCaptureEnabled) { dispatchPointerCaptureChanged(pointerCaptureEnabled); Loading Loading @@ -8945,17 +8960,33 @@ public final class ViewRootImpl implements ViewParent, mHandler.sendMessage(msg); } public void windowFocusChanged(boolean hasFocus, boolean inTouchMode) { /** * Notifies this {@link ViewRootImpl} object that window focus has changed. */ public void windowFocusChanged(boolean hasFocus, boolean unusedInTouchMode) { // TODO(b/193718270): Remove inTouchMode parameter from this method and update related code // accordingly. synchronized (this) { mWindowFocusChanged = true; mUpcomingWindowFocus = hasFocus; mUpcomingInTouchMode = inTouchMode; } Message msg = Message.obtain(); msg.what = MSG_WINDOW_FOCUS_CHANGED; mHandler.sendMessage(msg); } /** * Notifies this {@link ViewRootImpl} object that touch mode state has changed. */ public void touchModeChanged(boolean inTouchMode) { synchronized (this) { mUpcomingInTouchMode = inTouchMode; } Message msg = Message.obtain(); msg.what = MSG_WINDOW_TOUCH_MODE_CHANGED; mHandler.sendMessage(msg); } public void dispatchWindowShown() { mHandler.sendEmptyMessage(MSG_DISPATCH_WINDOW_SHOWN); } Loading core/java/com/android/internal/policy/PhoneWindow.java +3 −2 Original line number Diff line number Diff line Loading @@ -1841,8 +1841,9 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { @Override public void setLocalFocus(boolean hasFocus, boolean inTouchMode) { getViewRootImpl().windowFocusChanged(hasFocus, inTouchMode); ViewRootImpl viewRoot = getViewRootImpl(); viewRoot.windowFocusChanged(hasFocus, inTouchMode); viewRoot.touchModeChanged(inTouchMode); } @Override Loading core/tests/coretests/src/android/view/ViewRootImplTest.java +70 −22 Original line number Diff line number Diff line Loading @@ -31,12 +31,14 @@ import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ALERT; import static android.view.WindowManager.LayoutParams.TYPE_TOAST; import static androidx.test.InstrumentationRegistry.getInstrumentation; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import android.app.Instrumentation; import android.content.Context; import android.os.Binder; import android.platform.test.annotations.Presubmit; Loading @@ -47,7 +49,9 @@ import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import androidx.test.platform.app.InstrumentationRegistry; import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; Loading @@ -66,15 +70,32 @@ import java.util.concurrent.TimeUnit; public class ViewRootImplTest { private ViewRootImpl mViewRootImpl; private Context mContext; private volatile boolean mKeyReceived = false; private static Context sContext; private static Instrumentation sInstrumentation = InstrumentationRegistry.getInstrumentation(); // The touch mode state before the test was started, needed to return the system to the original // state after the test completes. private static boolean sOriginalTouchMode; @BeforeClass public static void setUpClass() { sContext = sInstrumentation.getTargetContext(); View view = new View(sContext); sOriginalTouchMode = view.isInTouchMode(); } @AfterClass public static void tearDownClass() { sInstrumentation.setInTouchMode(sOriginalTouchMode); } @Before public void setUp() throws Exception { mContext = getInstrumentation().getTargetContext(); getInstrumentation().runOnMainSync(() -> mViewRootImpl = new ViewRootImpl(mContext, mContext.getDisplayNoVerify())); sInstrumentation.setInTouchMode(true); sInstrumentation.runOnMainSync(() -> mViewRootImpl = new ViewRootImpl(sContext, sContext.getDisplayNoVerify())); } @Test Loading Loading @@ -224,9 +245,9 @@ public class ViewRootImplTest { */ @Test public void requestScrollCapture_timeout() { final View view = new View(mContext); final View view = new View(sContext); view.setScrollCaptureCallback(new TestScrollCaptureCallback()); // Does nothing InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { sInstrumentation.runOnMainSync(() -> { WindowManager.LayoutParams wmlp = new WindowManager.LayoutParams(TYPE_APPLICATION_OVERLAY); // Set a fake token to bypass 'is your activity running' check Loading @@ -250,6 +271,29 @@ public class ViewRootImplTest { } catch (InterruptedException e) { /* ignore */ } } @Test public void whenTouchModeChanges_viewRootIsNotified() throws Exception { View view = new View(sContext); attachViewToWindow(view); ViewTreeObserver viewTreeObserver = view.getRootView().getViewTreeObserver(); CountDownLatch latch = new CountDownLatch(1); ViewTreeObserver.OnTouchModeChangeListener touchModeListener = (boolean inTouchMode) -> { assertWithMessage("addOnTouchModeChangeListener parameter").that( inTouchMode).isFalse(); latch.countDown(); }; viewTreeObserver.addOnTouchModeChangeListener(touchModeListener); try { view.requestFocusFromTouch(); assertThat(latch.await(1, TimeUnit.SECONDS)).isTrue(); assertThat(view.isInTouchMode()).isFalse(); } finally { viewTreeObserver.removeOnTouchModeChangeListener(touchModeListener); } } /** * When window doesn't have focus, keys should be dropped. */ Loading Loading @@ -308,27 +352,31 @@ public class ViewRootImplTest { * Next, inject an event into this view, and check whether it is received. */ private void checkKeyEvent(Runnable setup, boolean shouldReceiveKey) { final KeyView view = new KeyView(mContext); final KeyView view = new KeyView(sContext); WindowManager.LayoutParams wmlp = new WindowManager.LayoutParams(TYPE_APPLICATION_OVERLAY); wmlp.token = new Binder(); // Set a fake token to bypass 'is your activity running' check InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { WindowManager wm = mContext.getSystemService(WindowManager.class); wm.addView(view, wmlp); }); InstrumentationRegistry.getInstrumentation().waitForIdleSync(); attachViewToWindow(view); mViewRootImpl = view.getViewRootImpl(); InstrumentationRegistry.getInstrumentation().runOnMainSync(setup); InstrumentationRegistry.getInstrumentation().waitForIdleSync(); sInstrumentation.runOnMainSync(setup); sInstrumentation.waitForIdleSync(); // Inject a key event, and wait for it to be processed InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { sInstrumentation.runOnMainSync(() -> { KeyEvent event = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_A); mViewRootImpl.dispatchInputEvent(event); }); InstrumentationRegistry.getInstrumentation().waitForIdleSync(); sInstrumentation.waitForIdleSync(); assertEquals(mKeyReceived, shouldReceiveKey); } private void attachViewToWindow(View view) { WindowManager.LayoutParams wmlp = new WindowManager.LayoutParams(TYPE_APPLICATION_OVERLAY); wmlp.token = new Binder(); // Set a fake token to bypass 'is your activity running' check sInstrumentation.runOnMainSync(() -> { WindowManager wm = sContext.getSystemService(WindowManager.class); wm.addView(view, wmlp); }); sInstrumentation.waitForIdleSync(); } } Loading
core/java/android/view/InputEventReceiver.java +2 −2 Original line number Diff line number Diff line Loading @@ -174,10 +174,10 @@ public abstract class InputEventReceiver { * Called when the display for the window associated with the input channel has entered or * exited touch mode. * * @param isInTouchMode {@code true} if the display showing the window associated with the * @param inTouchMode {@code true} if the display showing the window associated with the * input channel entered touch mode. */ public void onTouchModeChanged(boolean isInTouchMode) { public void onTouchModeChanged(boolean inTouchMode) { } /** Loading
core/java/android/view/ViewRootImpl.java +39 −8 Original line number Diff line number Diff line Loading @@ -3376,14 +3376,12 @@ public final class ViewRootImpl implements ViewParent, private void handleWindowFocusChanged() { final boolean hasWindowFocus; final boolean inTouchMode; synchronized (this) { if (!mWindowFocusChanged) { return; } mWindowFocusChanged = false; hasWindowFocus = mUpcomingWindowFocus; inTouchMode = mUpcomingInTouchMode; } // TODO (b/131181940): Make sure this doesn't leak Activity with mActivityConfigCallback // config changes. Loading @@ -3396,9 +3394,7 @@ public final class ViewRootImpl implements ViewParent, if (mAdded) { profileRendering(hasWindowFocus); if (hasWindowFocus) { ensureTouchModeLocally(inTouchMode); if (mAttachInfo.mThreadedRenderer != null && mSurface.isValid()) { mFullRedrawNeeded = true; try { Loading Loading @@ -3470,6 +3466,14 @@ public final class ViewRootImpl implements ViewParent, } } private void handleWindowTouchModeChanged() { final boolean inTouchMode; synchronized (this) { inTouchMode = mUpcomingInTouchMode; } ensureTouchModeLocally(inTouchMode); } private void fireAccessibilityFocusEventIfHasFocusedNode() { if (!AccessibilityManager.getInstance(mContext).isEnabled()) { return; Loading Loading @@ -5127,6 +5131,7 @@ public final class ViewRootImpl implements ViewParent, private static final int MSG_SHOW_INSETS = 34; private static final int MSG_HIDE_INSETS = 35; private static final int MSG_REQUEST_SCROLL_CAPTURE = 36; private static final int MSG_WINDOW_TOUCH_MODE_CHANGED = 37; final class ViewRootHandler extends Handler { Loading Loading @@ -5193,6 +5198,8 @@ public final class ViewRootImpl implements ViewParent, return "MSG_SHOW_INSETS"; case MSG_HIDE_INSETS: return "MSG_HIDE_INSETS"; case MSG_WINDOW_TOUCH_MODE_CHANGED: return "MSG_WINDOW_TOUCH_MODE_CHANGED"; } return super.getMessageName(message); } Loading Loading @@ -5315,9 +5322,12 @@ public final class ViewRootImpl implements ViewParent, case MSG_WINDOW_FOCUS_CHANGED: { handleWindowFocusChanged(); } break; case MSG_DIE: case MSG_WINDOW_TOUCH_MODE_CHANGED: { handleWindowTouchModeChanged(); } break; case MSG_DIE: { doDie(); break; } break; case MSG_DISPATCH_INPUT_EVENT: { SomeArgs args = (SomeArgs) msg.obj; InputEvent event = (InputEvent) args.arg1; Loading Loading @@ -8687,6 +8697,11 @@ public final class ViewRootImpl implements ViewParent, windowFocusChanged(hasFocus, inTouchMode); } @Override public void onTouchModeChanged(boolean inTouchMode) { touchModeChanged(inTouchMode); } @Override public void onPointerCaptureEvent(boolean pointerCaptureEnabled) { dispatchPointerCaptureChanged(pointerCaptureEnabled); Loading Loading @@ -8945,17 +8960,33 @@ public final class ViewRootImpl implements ViewParent, mHandler.sendMessage(msg); } public void windowFocusChanged(boolean hasFocus, boolean inTouchMode) { /** * Notifies this {@link ViewRootImpl} object that window focus has changed. */ public void windowFocusChanged(boolean hasFocus, boolean unusedInTouchMode) { // TODO(b/193718270): Remove inTouchMode parameter from this method and update related code // accordingly. synchronized (this) { mWindowFocusChanged = true; mUpcomingWindowFocus = hasFocus; mUpcomingInTouchMode = inTouchMode; } Message msg = Message.obtain(); msg.what = MSG_WINDOW_FOCUS_CHANGED; mHandler.sendMessage(msg); } /** * Notifies this {@link ViewRootImpl} object that touch mode state has changed. */ public void touchModeChanged(boolean inTouchMode) { synchronized (this) { mUpcomingInTouchMode = inTouchMode; } Message msg = Message.obtain(); msg.what = MSG_WINDOW_TOUCH_MODE_CHANGED; mHandler.sendMessage(msg); } public void dispatchWindowShown() { mHandler.sendEmptyMessage(MSG_DISPATCH_WINDOW_SHOWN); } Loading
core/java/com/android/internal/policy/PhoneWindow.java +3 −2 Original line number Diff line number Diff line Loading @@ -1841,8 +1841,9 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { @Override public void setLocalFocus(boolean hasFocus, boolean inTouchMode) { getViewRootImpl().windowFocusChanged(hasFocus, inTouchMode); ViewRootImpl viewRoot = getViewRootImpl(); viewRoot.windowFocusChanged(hasFocus, inTouchMode); viewRoot.touchModeChanged(inTouchMode); } @Override Loading
core/tests/coretests/src/android/view/ViewRootImplTest.java +70 −22 Original line number Diff line number Diff line Loading @@ -31,12 +31,14 @@ import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ALERT; import static android.view.WindowManager.LayoutParams.TYPE_TOAST; import static androidx.test.InstrumentationRegistry.getInstrumentation; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import android.app.Instrumentation; import android.content.Context; import android.os.Binder; import android.platform.test.annotations.Presubmit; Loading @@ -47,7 +49,9 @@ import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import androidx.test.platform.app.InstrumentationRegistry; import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; Loading @@ -66,15 +70,32 @@ import java.util.concurrent.TimeUnit; public class ViewRootImplTest { private ViewRootImpl mViewRootImpl; private Context mContext; private volatile boolean mKeyReceived = false; private static Context sContext; private static Instrumentation sInstrumentation = InstrumentationRegistry.getInstrumentation(); // The touch mode state before the test was started, needed to return the system to the original // state after the test completes. private static boolean sOriginalTouchMode; @BeforeClass public static void setUpClass() { sContext = sInstrumentation.getTargetContext(); View view = new View(sContext); sOriginalTouchMode = view.isInTouchMode(); } @AfterClass public static void tearDownClass() { sInstrumentation.setInTouchMode(sOriginalTouchMode); } @Before public void setUp() throws Exception { mContext = getInstrumentation().getTargetContext(); getInstrumentation().runOnMainSync(() -> mViewRootImpl = new ViewRootImpl(mContext, mContext.getDisplayNoVerify())); sInstrumentation.setInTouchMode(true); sInstrumentation.runOnMainSync(() -> mViewRootImpl = new ViewRootImpl(sContext, sContext.getDisplayNoVerify())); } @Test Loading Loading @@ -224,9 +245,9 @@ public class ViewRootImplTest { */ @Test public void requestScrollCapture_timeout() { final View view = new View(mContext); final View view = new View(sContext); view.setScrollCaptureCallback(new TestScrollCaptureCallback()); // Does nothing InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { sInstrumentation.runOnMainSync(() -> { WindowManager.LayoutParams wmlp = new WindowManager.LayoutParams(TYPE_APPLICATION_OVERLAY); // Set a fake token to bypass 'is your activity running' check Loading @@ -250,6 +271,29 @@ public class ViewRootImplTest { } catch (InterruptedException e) { /* ignore */ } } @Test public void whenTouchModeChanges_viewRootIsNotified() throws Exception { View view = new View(sContext); attachViewToWindow(view); ViewTreeObserver viewTreeObserver = view.getRootView().getViewTreeObserver(); CountDownLatch latch = new CountDownLatch(1); ViewTreeObserver.OnTouchModeChangeListener touchModeListener = (boolean inTouchMode) -> { assertWithMessage("addOnTouchModeChangeListener parameter").that( inTouchMode).isFalse(); latch.countDown(); }; viewTreeObserver.addOnTouchModeChangeListener(touchModeListener); try { view.requestFocusFromTouch(); assertThat(latch.await(1, TimeUnit.SECONDS)).isTrue(); assertThat(view.isInTouchMode()).isFalse(); } finally { viewTreeObserver.removeOnTouchModeChangeListener(touchModeListener); } } /** * When window doesn't have focus, keys should be dropped. */ Loading Loading @@ -308,27 +352,31 @@ public class ViewRootImplTest { * Next, inject an event into this view, and check whether it is received. */ private void checkKeyEvent(Runnable setup, boolean shouldReceiveKey) { final KeyView view = new KeyView(mContext); final KeyView view = new KeyView(sContext); WindowManager.LayoutParams wmlp = new WindowManager.LayoutParams(TYPE_APPLICATION_OVERLAY); wmlp.token = new Binder(); // Set a fake token to bypass 'is your activity running' check InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { WindowManager wm = mContext.getSystemService(WindowManager.class); wm.addView(view, wmlp); }); InstrumentationRegistry.getInstrumentation().waitForIdleSync(); attachViewToWindow(view); mViewRootImpl = view.getViewRootImpl(); InstrumentationRegistry.getInstrumentation().runOnMainSync(setup); InstrumentationRegistry.getInstrumentation().waitForIdleSync(); sInstrumentation.runOnMainSync(setup); sInstrumentation.waitForIdleSync(); // Inject a key event, and wait for it to be processed InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { sInstrumentation.runOnMainSync(() -> { KeyEvent event = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_A); mViewRootImpl.dispatchInputEvent(event); }); InstrumentationRegistry.getInstrumentation().waitForIdleSync(); sInstrumentation.waitForIdleSync(); assertEquals(mKeyReceived, shouldReceiveKey); } private void attachViewToWindow(View view) { WindowManager.LayoutParams wmlp = new WindowManager.LayoutParams(TYPE_APPLICATION_OVERLAY); wmlp.token = new Binder(); // Set a fake token to bypass 'is your activity running' check sInstrumentation.runOnMainSync(() -> { WindowManager wm = sContext.getSystemService(WindowManager.class); wm.addView(view, wmlp); }); sInstrumentation.waitForIdleSync(); } }