From a03b86f3d74289c67fdc84eceb431ad419f3ce6e Mon Sep 17 00:00:00 2001 From: Bernardo Rufino Date: Wed, 4 Nov 2020 17:12:37 +0000 Subject: [PATCH] Make SnapshotStartingWindow and its child surface trusted Touches were being blocked by go/untrusted-touches feature when the user started interacting with the app early on while this window was being displayed. Original intent of the window is to let touches pass through (ie it sets FLAG_NOT_TOUCHABLE), the window is posted by the system and the content controlled by the system (a snapshot of the app). So, we make the window trusted and allow touches to pass through. Bug: 171683699 Bug: 158002302 Test: atest WmTests Test: Open app for 2nd time and tap on the screen quickly, verify no toast about untrusted touch. Test: Verify in input dumpsys that window is trusted. Change-Id: I018ebf9e2e27a81c553749a05a2b343a25144d9d --- .../server/wm/TaskSnapshotSurface.java | 46 ++++++++++----- .../server/wm/SystemServicesTestRule.java | 8 ++- .../server/wm/TaskSnapshotSurfaceTest.java | 58 ++++++++++++++++--- 3 files changed, 90 insertions(+), 22 deletions(-) diff --git a/services/core/java/com/android/server/wm/TaskSnapshotSurface.java b/services/core/java/com/android/server/wm/TaskSnapshotSurface.java index aab5da6de2142..30f09ce9a3e67 100644 --- a/services/core/java/com/android/server/wm/TaskSnapshotSurface.java +++ b/services/core/java/com/android/server/wm/TaskSnapshotSurface.java @@ -36,6 +36,7 @@ import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATIO import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS; import static android.view.WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS; +import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING; import static com.android.internal.policy.DecorView.NAVIGATION_BAR_COLOR_VIEW_ATTRIBUTES; @@ -129,6 +130,7 @@ class TaskSnapshotSurface implements StartingSurface { private SurfaceControl mChildSurfaceControl; private final IWindowSession mSession; private final WindowManagerService mService; + private final int mDisplayId; private final Rect mTaskBounds; private final Rect mFrame = new Rect(); private final Rect mSystemBarInsets = new Rect(); @@ -151,10 +153,15 @@ class TaskSnapshotSurface implements StartingSurface { static TaskSnapshotSurface create(WindowManagerService service, ActivityRecord activity, TaskSnapshot snapshot) { + return create(service, activity, snapshot, WindowManagerGlobal.getWindowSession()); + } + + @VisibleForTesting + static TaskSnapshotSurface create(WindowManagerService service, ActivityRecord activity, + TaskSnapshot snapshot, IWindowSession session) { final WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(); final Window window = new Window(); - final IWindowSession session = WindowManagerGlobal.getWindowSession(); window.setSession(session); final SurfaceControl surfaceControl = new SurfaceControl(); final ClientWindowFrames tmpFrames = new ClientWindowFrames(); @@ -215,7 +222,10 @@ class TaskSnapshotSurface implements StartingSurface { layoutParams.flags = (windowFlags & ~FLAG_INHERIT_EXCLUDES) | FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCHABLE; - layoutParams.privateFlags = windowPrivateFlags & PRIVATE_FLAG_INHERITS; + // Setting as trusted overlay to let touches pass through. This is safe because this + // window is controlled by the system. + layoutParams.privateFlags = (windowPrivateFlags & PRIVATE_FLAG_INHERITS) + | PRIVATE_FLAG_TRUSTED_OVERLAY; layoutParams.token = activity.token; layoutParams.width = LayoutParams.MATCH_PARENT; layoutParams.height = LayoutParams.MATCH_PARENT; @@ -239,11 +249,11 @@ class TaskSnapshotSurface implements StartingSurface { insetsState = getInsetsStateWithVisibilityOverride(topFullscreenOpaqueWindow); } + int displayId = activity.getDisplayContent().getDisplayId(); try { final int res = session.addToDisplay(window, layoutParams, - View.GONE, activity.getDisplayContent().getDisplayId(), mTmpInsetsState, - tmpFrames.frame, tmpFrames.displayCutout, null /* outInputChannel */, - mTmpInsetsState, mTempControls); + View.GONE, displayId, mTmpInsetsState, tmpFrames.frame, tmpFrames.displayCutout, + null /* outInputChannel */, mTmpInsetsState, mTempControls); if (res < 0) { Slog.w(TAG, "Failed to add snapshot starting window res=" + res); return null; @@ -251,10 +261,10 @@ class TaskSnapshotSurface implements StartingSurface { } catch (RemoteException e) { // Local call. } - final TaskSnapshotSurface snapshotSurface = new TaskSnapshotSurface(service, window, - surfaceControl, snapshot, layoutParams.getTitle(), taskDescription, appearance, - windowFlags, windowPrivateFlags, taskBounds, currentOrientation, activityType, - insetsState); + final TaskSnapshotSurface snapshotSurface = new TaskSnapshotSurface(service, displayId, + window, surfaceControl, snapshot, layoutParams.getTitle(), taskDescription, + appearance, windowFlags, windowPrivateFlags, taskBounds, currentOrientation, + activityType, insetsState); window.setOuter(snapshotSurface); try { session.relayout(window, layoutParams, -1, -1, View.VISIBLE, 0, -1, @@ -271,11 +281,13 @@ class TaskSnapshotSurface implements StartingSurface { } @VisibleForTesting - TaskSnapshotSurface(WindowManagerService service, Window window, SurfaceControl surfaceControl, - TaskSnapshot snapshot, CharSequence title, TaskDescription taskDescription, - int appearance, int windowFlags, int windowPrivateFlags, Rect taskBounds, - int currentOrientation, int activityType, InsetsState insetsState) { + TaskSnapshotSurface(WindowManagerService service, int displayId, Window window, + SurfaceControl surfaceControl, TaskSnapshot snapshot, CharSequence title, + TaskDescription taskDescription, int appearance, int windowFlags, + int windowPrivateFlags, Rect taskBounds, int currentOrientation, int activityType, + InsetsState insetsState) { mService = service; + mDisplayId = displayId; mSurface = service.mSurfaceFactory.get(); mHandler = new Handler(mService.mH.getLooper()); mSession = WindowManagerGlobal.getWindowSession(); @@ -368,8 +380,9 @@ class TaskSnapshotSurface implements StartingSurface { - ((float) mFrame.width() / mFrame.height())) > 0.01f; // Keep a reference to it such that it doesn't get destroyed when finalized. + final String name = mTitle + " - task-snapshot-surface"; mChildSurfaceControl = mService.mSurfaceControlFactory.apply(session) - .setName(mTitle + " - task-snapshot-surface") + .setName(name) .setBufferSize(buffer.getWidth(), buffer.getHeight()) .setFormat(buffer.getFormat()) .setParent(mSurfaceControl) @@ -401,6 +414,11 @@ class TaskSnapshotSurface implements StartingSurface { mSnapshotMatrix.setRectToRect(mTmpSnapshotSize, mTmpDstFrame, Matrix.ScaleToFit.FILL); mTransaction.setMatrix(mChildSurfaceControl, mSnapshotMatrix, mTmpFloat9); + // This is the way to tell the input system to exclude this surface from occlusion + // detection since we don't have a window for it. We do this because this window is + // generated by the system as well as its content (the snapshot of the app). + InputMonitor.setTrustedOverlayInputInfo(mChildSurfaceControl, mTransaction, mDisplayId, + name); mTransaction.apply(); surface.attachAndQueueBufferWithColorSpace(buffer, mSnapshot.getColorSpace()); surface.release(); diff --git a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java index 740f605d4393d..a79a5194c51c1 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java +++ b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java @@ -88,6 +88,7 @@ import org.mockito.quality.Strictness; import java.io.File; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Supplier; /** * JUnit test rule to correctly setting up system services like {@link WindowManagerService} @@ -112,6 +113,7 @@ public class SystemServicesTestRule implements TestRule { private WindowState.PowerManagerWrapper mPowerManagerWrapper; private InputManagerService mImService; private InputChannel mInputChannel; + private Supplier mSurfaceFactory = () -> mock(Surface.class); /** * Spied {@link SurfaceControl.Transaction} class than can be used to verify calls. */ @@ -286,7 +288,7 @@ public class SystemServicesTestRule implements TestRule { DisplayThread.getHandler().post(StrictMode::allowThreadDiskWritesMask); mWmService = WindowManagerService.main( mContext, mImService, false, false, mWMPolicy, mAtmService, StubTransaction::new, - () -> mock(Surface.class), (unused) -> new MockSurfaceControlBuilder()); + () -> mSurfaceFactory.get(), (unused) -> new MockSurfaceControlBuilder()); spyOn(mWmService); spyOn(mWmService.mRoot); // Invoked during {@link ActivityStack} creation. @@ -396,6 +398,10 @@ public class SystemServicesTestRule implements TestRule { return mPowerManagerWrapper; } + void setSurfaceFactory(Supplier factory) { + mSurfaceFactory = factory; + } + void cleanupWindowManagerHandlers() { final WindowManagerService wm = getWindowManagerService(); if (wm == null) { diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotSurfaceTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotSurfaceTest.java index b4a13375aeec9..5ea73b830582a 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotSurfaceTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotSurfaceTest.java @@ -20,17 +20,23 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.content.res.Configuration.ORIENTATION_PORTRAIT; import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; +import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY; +import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock; import static com.android.dx.mockito.inline.extended.ExtendedMockito.never; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; import static com.android.dx.mockito.inline.extended.ExtendedMockito.when; +import static com.google.common.truth.Truth.assertThat; + import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.eq; +import android.app.ActivityManager; import android.app.ActivityManager.TaskDescription; import android.app.ActivityManager.TaskSnapshot; import android.content.ComponentName; @@ -41,14 +47,18 @@ import android.graphics.Point; import android.graphics.Rect; import android.hardware.HardwareBuffer; import android.platform.test.annotations.Presubmit; +import android.view.Display; +import android.view.IWindowSession; import android.view.InsetsState; import android.view.Surface; import android.view.SurfaceControl; +import android.view.WindowManager; import androidx.test.filters.SmallTest; import com.android.server.wm.TaskSnapshotSurface.Window; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -67,9 +77,6 @@ public class TaskSnapshotSurfaceTest extends WindowTestsBase { private void setupSurface(int width, int height, Rect contentInsets, int sysuiVis, int windowFlags, Rect taskBounds) { - final HardwareBuffer buffer = HardwareBuffer.create(width, height, HardwareBuffer.RGBA_8888, - 1, HardwareBuffer.USAGE_CPU_READ_RARELY); - // Previously when constructing TaskSnapshots for this test, scale was 1.0f, so to mimic // this behavior set the taskSize to be the same as the taskBounds width and height. The // taskBounds passed here are assumed to be the same task bounds as when the snapshot was @@ -79,16 +86,24 @@ public class TaskSnapshotSurfaceTest extends WindowTestsBase { assertEquals(height, taskBounds.height()); Point taskSize = new Point(taskBounds.width(), taskBounds.height()); - final TaskSnapshot snapshot = new TaskSnapshot( + final TaskSnapshot snapshot = createTaskSnapshot(width, height, taskSize, contentInsets); + mSurface = new TaskSnapshotSurface(mWm, Display.DEFAULT_DISPLAY, new Window(), + new SurfaceControl(), snapshot, "Test", createTaskDescription(Color.WHITE, + Color.RED, Color.BLUE), sysuiVis, windowFlags, 0, taskBounds, ORIENTATION_PORTRAIT, + ACTIVITY_TYPE_STANDARD, new InsetsState()); + } + + private ActivityManager.TaskSnapshot createTaskSnapshot(int width, int height, Point taskSize, + Rect contentInsets) { + final HardwareBuffer buffer = HardwareBuffer.create(width, height, HardwareBuffer.RGBA_8888, + 1, HardwareBuffer.USAGE_CPU_READ_RARELY); + return new TaskSnapshot( System.currentTimeMillis(), new ComponentName("", ""), buffer, ColorSpace.get(ColorSpace.Named.SRGB), ORIENTATION_PORTRAIT, Surface.ROTATION_0, taskSize, contentInsets, false, true /* isRealSnapshot */, WINDOWING_MODE_FULLSCREEN, 0 /* systemUiVisibility */, false /* isTranslucent */); - mSurface = new TaskSnapshotSurface(mWm, new Window(), new SurfaceControl(), snapshot, "Test", - createTaskDescription(Color.WHITE, Color.RED, Color.BLUE), sysuiVis, windowFlags, 0, - taskBounds, ORIENTATION_PORTRAIT, ACTIVITY_TYPE_STANDARD, new InsetsState()); } private static TaskDescription createTaskDescription(int background, int statusBar, @@ -105,6 +120,35 @@ public class TaskSnapshotSurfaceTest extends WindowTestsBase { new Rect(0, 0, width, height)); } + private boolean isTrustedOverlay(WindowManager.LayoutParams params) { + return (params.privateFlags & PRIVATE_FLAG_TRUSTED_OVERLAY) != 0; + } + + @Before + public void setUp() throws Exception { + mSystemServicesTestRule.setSurfaceFactory(() -> { + Surface surface = mock(Surface.class); + when(surface.isValid()).thenReturn(true); + return surface; + }); + } + + @Test + public void createSurface_asTrustedOverlay() throws Exception { + Point task = new Point(200, 100); + ActivityRecord activityRecord = createActivityRecord(mDisplayContent); + createWindow(null, TYPE_BASE_APPLICATION, activityRecord, "window"); + TaskSnapshot taskSnapshot = createTaskSnapshot(task.x, task.y, task, new Rect()); + IWindowSession session = mock(IWindowSession.class); + + TaskSnapshotSurface surface = TaskSnapshotSurface.create(mWm, activityRecord, taskSnapshot, + session); + + assertThat(surface).isNotNull(); + verify(session).addToDisplay(any(), argThat(this::isTrustedOverlay), anyInt(), anyInt(), + any(), any(), any(), any(), any(), any()); + } + @Test public void fillEmptyBackground_fillHorizontally() { setupSurface(200, 100);