Loading core/java/android/view/ViewRootImpl.java +29 −16 Original line number Diff line number Diff line Loading @@ -5511,27 +5511,40 @@ public final class ViewRootImpl implements ViewParent, if (mView == null || !mAdded) { Slog.w(mTag, "Dropping event due to root view being removed: " + q.mEvent); return true; } else if ((!mAttachInfo.mHasWindowFocus } // Find a reason for dropping or canceling the event. final String reason; if (!mAttachInfo.mHasWindowFocus && !q.mEvent.isFromSource(InputDevice.SOURCE_CLASS_POINTER) && !isAutofillUiShowing()) || mStopped || (mIsAmbientMode && !q.mEvent.isFromSource(InputDevice.SOURCE_CLASS_BUTTON)) || (mPausedForTransition && !isBack(q.mEvent))) { // This is a focus event and the window doesn't currently have input focus or // has stopped. This could be an event that came back from the previous stage && !isAutofillUiShowing()) { // This is a non-pointer event and the window doesn't currently have input focus // This could be an event that came back from the previous stage // but the window has lost focus or stopped in the meantime. reason = "no window focus"; } else if (mStopped) { reason = "window is stopped"; } else if (mIsAmbientMode && !q.mEvent.isFromSource(InputDevice.SOURCE_CLASS_BUTTON)) { reason = "non-button event in ambient mode"; } else if (mPausedForTransition && !isBack(q.mEvent)) { reason = "paused for transition"; } else { // Most common path: no reason to drop or cancel the event return false; } if (isTerminalInputEvent(q.mEvent)) { // Don't drop terminal input events, however mark them as canceled. q.mEvent.cancel(); Slog.w(mTag, "Cancelling event due to no window focus: " + q.mEvent); Slog.w(mTag, "Cancelling event (" + reason + "):" + q.mEvent); return false; } // Drop non-terminal input events. Slog.w(mTag, "Dropping event due to no window focus: " + q.mEvent); Slog.w(mTag, "Dropping event (" + reason + "):" + q.mEvent); return true; } return false; } void dump(String prefix, PrintWriter writer) { if (mNext != null) { Loading core/tests/coretests/src/android/view/ViewRootImplTest.java +89 −2 Original line number Diff line number Diff line Loading @@ -27,6 +27,7 @@ import static android.view.WindowInsetsController.BEHAVIOR_SHOW_BARS_BY_SWIPE; import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN; import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; 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; Loading @@ -36,12 +37,14 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import android.content.Context; import android.os.Binder; import android.platform.test.annotations.Presubmit; import android.view.WindowInsets.Side; import android.view.WindowInsets.Type; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import androidx.test.platform.app.InstrumentationRegistry; import org.junit.Before; import org.junit.Test; Loading @@ -59,13 +62,15 @@ import org.junit.runner.RunWith; public class ViewRootImplTest { private ViewRootImpl mViewRootImpl; private Context mContext; private volatile boolean mKeyReceived = false; @Before public void setUp() throws Exception { final Context context = getInstrumentation().getTargetContext(); mContext = getInstrumentation().getTargetContext(); getInstrumentation().runOnMainSync(() -> mViewRootImpl = new ViewRootImpl(context, context.getDisplayNoVerify())); mViewRootImpl = new ViewRootImpl(mContext, mContext.getDisplayNoVerify())); } @Test Loading Loading @@ -189,4 +194,86 @@ public class ViewRootImplTest { // Behavior must not be adjusted due to setting new LayoutParams. assertEquals(behavior, controller.getSystemBarsBehavior()); } /** * When window doesn't have focus, keys should be dropped. */ @Test public void whenWindowDoesNotHaveFocus_keysAreDropped() { checkKeyEvent(() -> { mViewRootImpl.windowFocusChanged(false /*hasFocus*/, true /*inTouchMode*/); }, false /*shouldReceiveKey*/); } /** * When window has focus, keys should be received */ @Test public void whenWindowHasFocus_keysAreReceived() { checkKeyEvent(() -> { mViewRootImpl.windowFocusChanged(true /*hasFocus*/, true /*inTouchMode*/); }, true /*shouldReceiveKey*/); } /** * When window is in ambient mode, keys should be dropped */ @Test public void whenWindowIsInAmbientMode_keysAreDropped() { checkKeyEvent(() -> { mViewRootImpl.setIsAmbientMode(true /*ambient*/); }, false /*shouldReceiveKey*/); } /** * When window is paused for transition, keys should be dropped */ @Test public void whenWindowIsPausedForTransition_keysAreDropped() { checkKeyEvent(() -> { mViewRootImpl.setPausedForTransition(true /*paused*/); }, false /*shouldReceiveKey*/); } class KeyView extends View { KeyView(Context context) { super(context); } @Override public boolean dispatchKeyEventPreIme(KeyEvent event) { mKeyReceived = true; return true /*handled*/; } } /** * Create a new view, and add it to window manager. * Run the precondition 'setup'. * 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); 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(); mViewRootImpl = view.getViewRootImpl(); InstrumentationRegistry.getInstrumentation().runOnMainSync(setup); InstrumentationRegistry.getInstrumentation().waitForIdleSync(); // Inject a key event, and wait for it to be processed InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { KeyEvent event = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_A); mViewRootImpl.dispatchInputEvent(event); }); InstrumentationRegistry.getInstrumentation().waitForIdleSync(); assertEquals(mKeyReceived, shouldReceiveKey); } } Loading
core/java/android/view/ViewRootImpl.java +29 −16 Original line number Diff line number Diff line Loading @@ -5511,27 +5511,40 @@ public final class ViewRootImpl implements ViewParent, if (mView == null || !mAdded) { Slog.w(mTag, "Dropping event due to root view being removed: " + q.mEvent); return true; } else if ((!mAttachInfo.mHasWindowFocus } // Find a reason for dropping or canceling the event. final String reason; if (!mAttachInfo.mHasWindowFocus && !q.mEvent.isFromSource(InputDevice.SOURCE_CLASS_POINTER) && !isAutofillUiShowing()) || mStopped || (mIsAmbientMode && !q.mEvent.isFromSource(InputDevice.SOURCE_CLASS_BUTTON)) || (mPausedForTransition && !isBack(q.mEvent))) { // This is a focus event and the window doesn't currently have input focus or // has stopped. This could be an event that came back from the previous stage && !isAutofillUiShowing()) { // This is a non-pointer event and the window doesn't currently have input focus // This could be an event that came back from the previous stage // but the window has lost focus or stopped in the meantime. reason = "no window focus"; } else if (mStopped) { reason = "window is stopped"; } else if (mIsAmbientMode && !q.mEvent.isFromSource(InputDevice.SOURCE_CLASS_BUTTON)) { reason = "non-button event in ambient mode"; } else if (mPausedForTransition && !isBack(q.mEvent)) { reason = "paused for transition"; } else { // Most common path: no reason to drop or cancel the event return false; } if (isTerminalInputEvent(q.mEvent)) { // Don't drop terminal input events, however mark them as canceled. q.mEvent.cancel(); Slog.w(mTag, "Cancelling event due to no window focus: " + q.mEvent); Slog.w(mTag, "Cancelling event (" + reason + "):" + q.mEvent); return false; } // Drop non-terminal input events. Slog.w(mTag, "Dropping event due to no window focus: " + q.mEvent); Slog.w(mTag, "Dropping event (" + reason + "):" + q.mEvent); return true; } return false; } void dump(String prefix, PrintWriter writer) { if (mNext != null) { Loading
core/tests/coretests/src/android/view/ViewRootImplTest.java +89 −2 Original line number Diff line number Diff line Loading @@ -27,6 +27,7 @@ import static android.view.WindowInsetsController.BEHAVIOR_SHOW_BARS_BY_SWIPE; import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN; import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; 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; Loading @@ -36,12 +37,14 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import android.content.Context; import android.os.Binder; import android.platform.test.annotations.Presubmit; import android.view.WindowInsets.Side; import android.view.WindowInsets.Type; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import androidx.test.platform.app.InstrumentationRegistry; import org.junit.Before; import org.junit.Test; Loading @@ -59,13 +62,15 @@ import org.junit.runner.RunWith; public class ViewRootImplTest { private ViewRootImpl mViewRootImpl; private Context mContext; private volatile boolean mKeyReceived = false; @Before public void setUp() throws Exception { final Context context = getInstrumentation().getTargetContext(); mContext = getInstrumentation().getTargetContext(); getInstrumentation().runOnMainSync(() -> mViewRootImpl = new ViewRootImpl(context, context.getDisplayNoVerify())); mViewRootImpl = new ViewRootImpl(mContext, mContext.getDisplayNoVerify())); } @Test Loading Loading @@ -189,4 +194,86 @@ public class ViewRootImplTest { // Behavior must not be adjusted due to setting new LayoutParams. assertEquals(behavior, controller.getSystemBarsBehavior()); } /** * When window doesn't have focus, keys should be dropped. */ @Test public void whenWindowDoesNotHaveFocus_keysAreDropped() { checkKeyEvent(() -> { mViewRootImpl.windowFocusChanged(false /*hasFocus*/, true /*inTouchMode*/); }, false /*shouldReceiveKey*/); } /** * When window has focus, keys should be received */ @Test public void whenWindowHasFocus_keysAreReceived() { checkKeyEvent(() -> { mViewRootImpl.windowFocusChanged(true /*hasFocus*/, true /*inTouchMode*/); }, true /*shouldReceiveKey*/); } /** * When window is in ambient mode, keys should be dropped */ @Test public void whenWindowIsInAmbientMode_keysAreDropped() { checkKeyEvent(() -> { mViewRootImpl.setIsAmbientMode(true /*ambient*/); }, false /*shouldReceiveKey*/); } /** * When window is paused for transition, keys should be dropped */ @Test public void whenWindowIsPausedForTransition_keysAreDropped() { checkKeyEvent(() -> { mViewRootImpl.setPausedForTransition(true /*paused*/); }, false /*shouldReceiveKey*/); } class KeyView extends View { KeyView(Context context) { super(context); } @Override public boolean dispatchKeyEventPreIme(KeyEvent event) { mKeyReceived = true; return true /*handled*/; } } /** * Create a new view, and add it to window manager. * Run the precondition 'setup'. * 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); 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(); mViewRootImpl = view.getViewRootImpl(); InstrumentationRegistry.getInstrumentation().runOnMainSync(setup); InstrumentationRegistry.getInstrumentation().waitForIdleSync(); // Inject a key event, and wait for it to be processed InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { KeyEvent event = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_A); mViewRootImpl.dispatchInputEvent(event); }); InstrumentationRegistry.getInstrumentation().waitForIdleSync(); assertEquals(mKeyReceived, shouldReceiveKey); } }