Loading core/java/android/view/InputEventReceiver.java +2 −2 Original line number Original line 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 * Called when the display for the window associated with the input channel has entered or * exited touch mode. * 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. * 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 Original line Diff line number Diff line Loading @@ -3369,14 +3369,12 @@ public final class ViewRootImpl implements ViewParent, private void handleWindowFocusChanged() { private void handleWindowFocusChanged() { final boolean hasWindowFocus; final boolean hasWindowFocus; final boolean inTouchMode; synchronized (this) { synchronized (this) { if (!mWindowFocusChanged) { if (!mWindowFocusChanged) { return; return; } } mWindowFocusChanged = false; mWindowFocusChanged = false; hasWindowFocus = mUpcomingWindowFocus; hasWindowFocus = mUpcomingWindowFocus; inTouchMode = mUpcomingInTouchMode; } } // TODO (b/131181940): Make sure this doesn't leak Activity with mActivityConfigCallback // TODO (b/131181940): Make sure this doesn't leak Activity with mActivityConfigCallback // config changes. // config changes. Loading @@ -3389,9 +3387,7 @@ public final class ViewRootImpl implements ViewParent, if (mAdded) { if (mAdded) { profileRendering(hasWindowFocus); profileRendering(hasWindowFocus); if (hasWindowFocus) { if (hasWindowFocus) { ensureTouchModeLocally(inTouchMode); if (mAttachInfo.mThreadedRenderer != null && mSurface.isValid()) { if (mAttachInfo.mThreadedRenderer != null && mSurface.isValid()) { mFullRedrawNeeded = true; mFullRedrawNeeded = true; try { try { Loading Loading @@ -3463,6 +3459,14 @@ public final class ViewRootImpl implements ViewParent, } } } } private void handleWindowTouchModeChanged() { final boolean inTouchMode; synchronized (this) { inTouchMode = mUpcomingInTouchMode; } ensureTouchModeLocally(inTouchMode); } private void fireAccessibilityFocusEventIfHasFocusedNode() { private void fireAccessibilityFocusEventIfHasFocusedNode() { if (!AccessibilityManager.getInstance(mContext).isEnabled()) { if (!AccessibilityManager.getInstance(mContext).isEnabled()) { return; return; Loading Loading @@ -5120,6 +5124,7 @@ public final class ViewRootImpl implements ViewParent, private static final int MSG_SHOW_INSETS = 34; private static final int MSG_SHOW_INSETS = 34; private static final int MSG_HIDE_INSETS = 35; private static final int MSG_HIDE_INSETS = 35; private static final int MSG_REQUEST_SCROLL_CAPTURE = 36; private static final int MSG_REQUEST_SCROLL_CAPTURE = 36; private static final int MSG_WINDOW_TOUCH_MODE_CHANGED = 37; final class ViewRootHandler extends Handler { final class ViewRootHandler extends Handler { Loading Loading @@ -5186,6 +5191,8 @@ public final class ViewRootImpl implements ViewParent, return "MSG_SHOW_INSETS"; return "MSG_SHOW_INSETS"; case MSG_HIDE_INSETS: case MSG_HIDE_INSETS: return "MSG_HIDE_INSETS"; return "MSG_HIDE_INSETS"; case MSG_WINDOW_TOUCH_MODE_CHANGED: return "MSG_WINDOW_TOUCH_MODE_CHANGED"; } } return super.getMessageName(message); return super.getMessageName(message); } } Loading Loading @@ -5308,9 +5315,12 @@ public final class ViewRootImpl implements ViewParent, case MSG_WINDOW_FOCUS_CHANGED: { case MSG_WINDOW_FOCUS_CHANGED: { handleWindowFocusChanged(); handleWindowFocusChanged(); } break; } break; case MSG_DIE: case MSG_WINDOW_TOUCH_MODE_CHANGED: { handleWindowTouchModeChanged(); } break; case MSG_DIE: { doDie(); doDie(); break; } break; case MSG_DISPATCH_INPUT_EVENT: { case MSG_DISPATCH_INPUT_EVENT: { SomeArgs args = (SomeArgs) msg.obj; SomeArgs args = (SomeArgs) msg.obj; InputEvent event = (InputEvent) args.arg1; InputEvent event = (InputEvent) args.arg1; Loading Loading @@ -8680,6 +8690,11 @@ public final class ViewRootImpl implements ViewParent, windowFocusChanged(hasFocus, inTouchMode); windowFocusChanged(hasFocus, inTouchMode); } } @Override public void onTouchModeChanged(boolean inTouchMode) { touchModeChanged(inTouchMode); } @Override @Override public void onPointerCaptureEvent(boolean pointerCaptureEnabled) { public void onPointerCaptureEvent(boolean pointerCaptureEnabled) { dispatchPointerCaptureChanged(pointerCaptureEnabled); dispatchPointerCaptureChanged(pointerCaptureEnabled); Loading Loading @@ -8938,17 +8953,33 @@ public final class ViewRootImpl implements ViewParent, mHandler.sendMessage(msg); 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): Delete unused inTouchMode parameter here once fully removing touch // mode status from focus event. synchronized (this) { synchronized (this) { mWindowFocusChanged = true; mWindowFocusChanged = true; mUpcomingWindowFocus = hasFocus; mUpcomingWindowFocus = hasFocus; mUpcomingInTouchMode = inTouchMode; } } Message msg = Message.obtain(); Message msg = Message.obtain(); msg.what = MSG_WINDOW_FOCUS_CHANGED; msg.what = MSG_WINDOW_FOCUS_CHANGED; mHandler.sendMessage(msg); 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() { public void dispatchWindowShown() { mHandler.sendEmptyMessage(MSG_DISPATCH_WINDOW_SHOWN); mHandler.sendEmptyMessage(MSG_DISPATCH_WINDOW_SHOWN); } } Loading core/tests/coretests/src/android/view/ViewRootImplTest.java +70 −22 Original line number Original line 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_SYSTEM_ALERT; import static android.view.WindowManager.LayoutParams.TYPE_TOAST; 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.assertEquals; import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.junit.Assert.fail; import android.app.Instrumentation; import android.content.Context; import android.content.Context; import android.os.Binder; import android.os.Binder; import android.platform.test.annotations.Presubmit; 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.filters.SmallTest; import androidx.test.platform.app.InstrumentationRegistry; import androidx.test.platform.app.InstrumentationRegistry; import org.junit.AfterClass; import org.junit.Before; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runner.RunWith; Loading @@ -66,15 +70,32 @@ import java.util.concurrent.TimeUnit; public class ViewRootImplTest { public class ViewRootImplTest { private ViewRootImpl mViewRootImpl; private ViewRootImpl mViewRootImpl; private Context mContext; private volatile boolean mKeyReceived = false; 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 @Before public void setUp() throws Exception { public void setUp() throws Exception { mContext = getInstrumentation().getTargetContext(); sInstrumentation.setInTouchMode(true); sInstrumentation.runOnMainSync(() -> getInstrumentation().runOnMainSync(() -> mViewRootImpl = new ViewRootImpl(sContext, sContext.getDisplayNoVerify())); mViewRootImpl = new ViewRootImpl(mContext, mContext.getDisplayNoVerify())); } } @Test @Test Loading Loading @@ -224,9 +245,9 @@ public class ViewRootImplTest { */ */ @Test @Test public void requestScrollCapture_timeout() { public void requestScrollCapture_timeout() { final View view = new View(mContext); final View view = new View(sContext); view.setScrollCaptureCallback(new TestScrollCaptureCallback()); // Does nothing view.setScrollCaptureCallback(new TestScrollCaptureCallback()); // Does nothing InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { sInstrumentation.runOnMainSync(() -> { WindowManager.LayoutParams wmlp = WindowManager.LayoutParams wmlp = new WindowManager.LayoutParams(TYPE_APPLICATION_OVERLAY); new WindowManager.LayoutParams(TYPE_APPLICATION_OVERLAY); // Set a fake token to bypass 'is your activity running' check // Set a fake token to bypass 'is your activity running' check Loading @@ -250,6 +271,29 @@ public class ViewRootImplTest { } catch (InterruptedException e) { /* ignore */ } } 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. * 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. * Next, inject an event into this view, and check whether it is received. */ */ private void checkKeyEvent(Runnable setup, boolean shouldReceiveKey) { 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); attachViewToWindow(view); 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(); mViewRootImpl = view.getViewRootImpl(); mViewRootImpl = view.getViewRootImpl(); InstrumentationRegistry.getInstrumentation().runOnMainSync(setup); sInstrumentation.runOnMainSync(setup); InstrumentationRegistry.getInstrumentation().waitForIdleSync(); sInstrumentation.waitForIdleSync(); // Inject a key event, and wait for it to be processed // 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); KeyEvent event = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_A); mViewRootImpl.dispatchInputEvent(event); mViewRootImpl.dispatchInputEvent(event); }); }); InstrumentationRegistry.getInstrumentation().waitForIdleSync(); sInstrumentation.waitForIdleSync(); assertEquals(mKeyReceived, shouldReceiveKey); 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 Original line 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 * Called when the display for the window associated with the input channel has entered or * exited touch mode. * 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. * 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 Original line Diff line number Diff line Loading @@ -3369,14 +3369,12 @@ public final class ViewRootImpl implements ViewParent, private void handleWindowFocusChanged() { private void handleWindowFocusChanged() { final boolean hasWindowFocus; final boolean hasWindowFocus; final boolean inTouchMode; synchronized (this) { synchronized (this) { if (!mWindowFocusChanged) { if (!mWindowFocusChanged) { return; return; } } mWindowFocusChanged = false; mWindowFocusChanged = false; hasWindowFocus = mUpcomingWindowFocus; hasWindowFocus = mUpcomingWindowFocus; inTouchMode = mUpcomingInTouchMode; } } // TODO (b/131181940): Make sure this doesn't leak Activity with mActivityConfigCallback // TODO (b/131181940): Make sure this doesn't leak Activity with mActivityConfigCallback // config changes. // config changes. Loading @@ -3389,9 +3387,7 @@ public final class ViewRootImpl implements ViewParent, if (mAdded) { if (mAdded) { profileRendering(hasWindowFocus); profileRendering(hasWindowFocus); if (hasWindowFocus) { if (hasWindowFocus) { ensureTouchModeLocally(inTouchMode); if (mAttachInfo.mThreadedRenderer != null && mSurface.isValid()) { if (mAttachInfo.mThreadedRenderer != null && mSurface.isValid()) { mFullRedrawNeeded = true; mFullRedrawNeeded = true; try { try { Loading Loading @@ -3463,6 +3459,14 @@ public final class ViewRootImpl implements ViewParent, } } } } private void handleWindowTouchModeChanged() { final boolean inTouchMode; synchronized (this) { inTouchMode = mUpcomingInTouchMode; } ensureTouchModeLocally(inTouchMode); } private void fireAccessibilityFocusEventIfHasFocusedNode() { private void fireAccessibilityFocusEventIfHasFocusedNode() { if (!AccessibilityManager.getInstance(mContext).isEnabled()) { if (!AccessibilityManager.getInstance(mContext).isEnabled()) { return; return; Loading Loading @@ -5120,6 +5124,7 @@ public final class ViewRootImpl implements ViewParent, private static final int MSG_SHOW_INSETS = 34; private static final int MSG_SHOW_INSETS = 34; private static final int MSG_HIDE_INSETS = 35; private static final int MSG_HIDE_INSETS = 35; private static final int MSG_REQUEST_SCROLL_CAPTURE = 36; private static final int MSG_REQUEST_SCROLL_CAPTURE = 36; private static final int MSG_WINDOW_TOUCH_MODE_CHANGED = 37; final class ViewRootHandler extends Handler { final class ViewRootHandler extends Handler { Loading Loading @@ -5186,6 +5191,8 @@ public final class ViewRootImpl implements ViewParent, return "MSG_SHOW_INSETS"; return "MSG_SHOW_INSETS"; case MSG_HIDE_INSETS: case MSG_HIDE_INSETS: return "MSG_HIDE_INSETS"; return "MSG_HIDE_INSETS"; case MSG_WINDOW_TOUCH_MODE_CHANGED: return "MSG_WINDOW_TOUCH_MODE_CHANGED"; } } return super.getMessageName(message); return super.getMessageName(message); } } Loading Loading @@ -5308,9 +5315,12 @@ public final class ViewRootImpl implements ViewParent, case MSG_WINDOW_FOCUS_CHANGED: { case MSG_WINDOW_FOCUS_CHANGED: { handleWindowFocusChanged(); handleWindowFocusChanged(); } break; } break; case MSG_DIE: case MSG_WINDOW_TOUCH_MODE_CHANGED: { handleWindowTouchModeChanged(); } break; case MSG_DIE: { doDie(); doDie(); break; } break; case MSG_DISPATCH_INPUT_EVENT: { case MSG_DISPATCH_INPUT_EVENT: { SomeArgs args = (SomeArgs) msg.obj; SomeArgs args = (SomeArgs) msg.obj; InputEvent event = (InputEvent) args.arg1; InputEvent event = (InputEvent) args.arg1; Loading Loading @@ -8680,6 +8690,11 @@ public final class ViewRootImpl implements ViewParent, windowFocusChanged(hasFocus, inTouchMode); windowFocusChanged(hasFocus, inTouchMode); } } @Override public void onTouchModeChanged(boolean inTouchMode) { touchModeChanged(inTouchMode); } @Override @Override public void onPointerCaptureEvent(boolean pointerCaptureEnabled) { public void onPointerCaptureEvent(boolean pointerCaptureEnabled) { dispatchPointerCaptureChanged(pointerCaptureEnabled); dispatchPointerCaptureChanged(pointerCaptureEnabled); Loading Loading @@ -8938,17 +8953,33 @@ public final class ViewRootImpl implements ViewParent, mHandler.sendMessage(msg); 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): Delete unused inTouchMode parameter here once fully removing touch // mode status from focus event. synchronized (this) { synchronized (this) { mWindowFocusChanged = true; mWindowFocusChanged = true; mUpcomingWindowFocus = hasFocus; mUpcomingWindowFocus = hasFocus; mUpcomingInTouchMode = inTouchMode; } } Message msg = Message.obtain(); Message msg = Message.obtain(); msg.what = MSG_WINDOW_FOCUS_CHANGED; msg.what = MSG_WINDOW_FOCUS_CHANGED; mHandler.sendMessage(msg); 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() { public void dispatchWindowShown() { mHandler.sendEmptyMessage(MSG_DISPATCH_WINDOW_SHOWN); mHandler.sendEmptyMessage(MSG_DISPATCH_WINDOW_SHOWN); } } Loading
core/tests/coretests/src/android/view/ViewRootImplTest.java +70 −22 Original line number Original line 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_SYSTEM_ALERT; import static android.view.WindowManager.LayoutParams.TYPE_TOAST; 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.assertEquals; import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.junit.Assert.fail; import android.app.Instrumentation; import android.content.Context; import android.content.Context; import android.os.Binder; import android.os.Binder; import android.platform.test.annotations.Presubmit; 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.filters.SmallTest; import androidx.test.platform.app.InstrumentationRegistry; import androidx.test.platform.app.InstrumentationRegistry; import org.junit.AfterClass; import org.junit.Before; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runner.RunWith; Loading @@ -66,15 +70,32 @@ import java.util.concurrent.TimeUnit; public class ViewRootImplTest { public class ViewRootImplTest { private ViewRootImpl mViewRootImpl; private ViewRootImpl mViewRootImpl; private Context mContext; private volatile boolean mKeyReceived = false; 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 @Before public void setUp() throws Exception { public void setUp() throws Exception { mContext = getInstrumentation().getTargetContext(); sInstrumentation.setInTouchMode(true); sInstrumentation.runOnMainSync(() -> getInstrumentation().runOnMainSync(() -> mViewRootImpl = new ViewRootImpl(sContext, sContext.getDisplayNoVerify())); mViewRootImpl = new ViewRootImpl(mContext, mContext.getDisplayNoVerify())); } } @Test @Test Loading Loading @@ -224,9 +245,9 @@ public class ViewRootImplTest { */ */ @Test @Test public void requestScrollCapture_timeout() { public void requestScrollCapture_timeout() { final View view = new View(mContext); final View view = new View(sContext); view.setScrollCaptureCallback(new TestScrollCaptureCallback()); // Does nothing view.setScrollCaptureCallback(new TestScrollCaptureCallback()); // Does nothing InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { sInstrumentation.runOnMainSync(() -> { WindowManager.LayoutParams wmlp = WindowManager.LayoutParams wmlp = new WindowManager.LayoutParams(TYPE_APPLICATION_OVERLAY); new WindowManager.LayoutParams(TYPE_APPLICATION_OVERLAY); // Set a fake token to bypass 'is your activity running' check // Set a fake token to bypass 'is your activity running' check Loading @@ -250,6 +271,29 @@ public class ViewRootImplTest { } catch (InterruptedException e) { /* ignore */ } } 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. * 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. * Next, inject an event into this view, and check whether it is received. */ */ private void checkKeyEvent(Runnable setup, boolean shouldReceiveKey) { 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); attachViewToWindow(view); 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(); mViewRootImpl = view.getViewRootImpl(); mViewRootImpl = view.getViewRootImpl(); InstrumentationRegistry.getInstrumentation().runOnMainSync(setup); sInstrumentation.runOnMainSync(setup); InstrumentationRegistry.getInstrumentation().waitForIdleSync(); sInstrumentation.waitForIdleSync(); // Inject a key event, and wait for it to be processed // 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); KeyEvent event = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_A); mViewRootImpl.dispatchInputEvent(event); mViewRootImpl.dispatchInputEvent(event); }); }); InstrumentationRegistry.getInstrumentation().waitForIdleSync(); sInstrumentation.waitForIdleSync(); assertEquals(mKeyReceived, shouldReceiveKey); 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(); } } }