TouchMode (5/n) Implementing onTouchModeChanged

Changing touch mode will now have effect on every existing window
(including sys UI windows).

Manual testing steps (on gcar_emu):
1. Start emulator;
2. Ensure touch mode on by tapping anywhere in the status bar or nav
bar;
3. Tap on the launcher icon in nav bar;
4. Tap on the 3 dots in the top right corner;
5. Tap on the Close button in the dialog;
6. Rotate clockwise once to enter rotary mode. This will exit touch mode and now with this CL it will have effect on the sys UI process;
7. When tapping on home button in the nav bar, sys ui will be in touch
mode as expected.

Fixes: 173468133
Bug: 193718270
Test: manual (see steps above)
Test: atest FrameworksCoreTests:ViewRootImplTest
Change-Id: If37cd5c48087f5a25d7b8cee8fe29a5e11d9d6ae
This commit is contained in:
Antonio Kantek
2021-10-11 15:05:28 -07:00
parent ab13590c57
commit 487559898c
3 changed files with 111 additions and 32 deletions

View File

@@ -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) {
}
/**

View File

@@ -3384,14 +3384,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.
@@ -3404,9 +3402,7 @@ public final class ViewRootImpl implements ViewParent,
if (mAdded) {
profileRendering(hasWindowFocus);
if (hasWindowFocus) {
ensureTouchModeLocally(inTouchMode);
if (mAttachInfo.mThreadedRenderer != null && mSurface.isValid()) {
mFullRedrawNeeded = true;
try {
@@ -3478,6 +3474,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;
@@ -5135,6 +5139,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 {
@@ -5201,6 +5206,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);
}
@@ -5323,9 +5330,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;
@@ -8695,6 +8705,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);
@@ -8953,17 +8968,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): Delete unused inTouchMode parameter here once fully removing touch
// mode status from focus event.
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);
}

View File

@@ -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;
@@ -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;
@@ -58,7 +62,7 @@ import java.util.concurrent.TimeUnit;
* Tests for {@link ViewRootImpl}
*
* Build/Install/Run:
* atest FrameworksCoreTests:ViewRootImplTest
* atest FrameworksCoreTests:ViewRootImplTest
*/
@Presubmit
@SmallTest
@@ -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
@@ -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
@@ -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.
*/
@@ -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();
}
}