1 /* 2 * Copyright (C) 2019 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package android.view.surfacecontrol.cts; 17 18 import static android.server.wm.BuildUtils.HW_TIMEOUT_MULTIPLIER; 19 import static android.server.wm.CtsWindowInfoUtils.getWindowBoundsInDisplaySpace; 20 import static android.server.wm.CtsWindowInfoUtils.getWindowCenter; 21 import static android.server.wm.CtsWindowInfoUtils.waitForStableWindowGeometry; 22 import static android.server.wm.CtsWindowInfoUtils.waitForWindowFocus; 23 import static android.server.wm.CtsWindowInfoUtils.waitForWindowInfo; 24 import static android.server.wm.CtsWindowInfoUtils.waitForWindowInfos; 25 import static android.server.wm.CtsWindowInfoUtils.waitForWindowOnTop; 26 import static android.server.wm.CtsWindowInfoUtils.waitForWindowVisible; 27 import static android.server.wm.MockImeHelper.createManagedMockImeSession; 28 import static android.server.wm.WindowManagerState.STATE_RESUMED; 29 import static android.server.wm.WindowManagerState.STATE_STOPPED; 30 import static android.view.SurfaceControlViewHost.SurfacePackage; 31 import static android.view.WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON; 32 import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; 33 import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; 34 import static android.view.WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH; 35 36 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; 37 38 import static com.android.cts.input.inputeventmatchers.InputEventMatchersKt.withFlags; 39 import static com.android.cts.input.inputeventmatchers.InputEventMatchersKt.withMotionAction; 40 import static com.android.cts.mockime.ImeEventStreamTestUtils.editorMatcher; 41 import static com.android.cts.mockime.ImeEventStreamTestUtils.expectEvent; 42 43 import static org.hamcrest.CoreMatchers.allOf; 44 import static org.hamcrest.MatcherAssert.assertThat; 45 import static org.junit.Assert.assertEquals; 46 import static org.junit.Assert.assertFalse; 47 import static org.junit.Assert.assertNotEquals; 48 import static org.junit.Assert.assertNotNull; 49 import static org.junit.Assert.assertNull; 50 import static org.junit.Assert.assertThrows; 51 import static org.junit.Assert.assertTrue; 52 import static org.junit.Assert.fail; 53 import static org.junit.Assume.assumeFalse; 54 import static org.junit.Assume.assumeTrue; 55 56 import android.app.Activity; 57 import android.app.ActivityManager; 58 import android.app.Instrumentation; 59 import android.app.KeyguardManager; 60 import android.app.UiAutomation; 61 import android.content.ComponentName; 62 import android.content.Context; 63 import android.content.Intent; 64 import android.content.pm.ActivityInfo; 65 import android.content.pm.ConfigurationInfo; 66 import android.content.pm.FeatureInfo; 67 import android.content.res.Configuration; 68 import android.graphics.Color; 69 import android.graphics.PixelFormat; 70 import android.graphics.Point; 71 import android.graphics.Rect; 72 import android.graphics.Region; 73 import android.os.Binder; 74 import android.os.Bundle; 75 import android.os.IBinder; 76 import android.os.RemoteException; 77 import android.os.SystemClock; 78 import android.platform.test.annotations.Presubmit; 79 import android.platform.test.annotations.RequiresDevice; 80 import android.platform.test.annotations.RequiresFlagsEnabled; 81 import android.platform.test.flag.junit.CheckFlagsRule; 82 import android.platform.test.flag.junit.DeviceFlagsValueProvider; 83 import android.server.wm.ActivityManagerTestBase; 84 import android.server.wm.CtsWindowInfoUtils; 85 import android.server.wm.FutureConnection; 86 import android.server.wm.TouchHelper; 87 import android.server.wm.WindowManagerState; 88 import android.server.wm.scvh.Components; 89 import android.server.wm.scvh.ICrossProcessSurfaceControlViewHostTestService; 90 import android.util.ArrayMap; 91 import android.view.Gravity; 92 import android.view.KeyEvent; 93 import android.view.MotionEvent; 94 import android.view.SurfaceControl; 95 import android.view.SurfaceControlViewHost; 96 import android.view.SurfaceHolder; 97 import android.view.SurfaceView; 98 import android.view.View; 99 import android.view.ViewGroup; 100 import android.view.WindowManager; 101 import android.view.inputmethod.InputMethodManager; 102 import android.widget.Button; 103 import android.widget.EditText; 104 import android.widget.FrameLayout; 105 import android.widget.PopupWindow; 106 import android.window.InputTransferToken; 107 import android.window.WindowInfosListenerForTest.WindowInfo; 108 109 import androidx.annotation.NonNull; 110 import androidx.annotation.Nullable; 111 import androidx.test.InstrumentationRegistry; 112 import androidx.test.rule.ActivityTestRule; 113 114 import com.android.compatibility.common.util.CtsTouchUtils; 115 import com.android.compatibility.common.util.FeatureUtil; 116 import com.android.compatibility.common.util.PollingCheck; 117 import com.android.cts.input.UinputTouchDevice; 118 import com.android.cts.input.UinputTouchScreen; 119 import com.android.cts.mockime.ImeEventStream; 120 import com.android.cts.mockime.MockImeSession; 121 import com.android.window.flags.Flags; 122 123 import org.junit.After; 124 import org.junit.Before; 125 import org.junit.Rule; 126 import org.junit.Test; 127 import org.junit.rules.TestName; 128 129 import java.time.Duration; 130 import java.util.ArrayList; 131 import java.util.List; 132 import java.util.Map; 133 import java.util.Objects; 134 import java.util.concurrent.CountDownLatch; 135 import java.util.concurrent.TimeUnit; 136 import java.util.concurrent.atomic.AtomicReference; 137 import java.util.function.Consumer; 138 import java.util.function.Predicate; 139 import java.util.function.Supplier; 140 141 /** 142 * Ensure end-to-end functionality of SurfaceControlViewHost. 143 * <p> 144 * Build/Install/Run: 145 * atest CtsWindowManagerDeviceTestCases:SurfaceControlViewHostTests 146 */ 147 @Presubmit 148 public class SurfaceControlViewHostTests extends ActivityManagerTestBase implements 149 SurfaceHolder.Callback { 150 151 public static class TestActivity extends Activity { 152 @Override onCreate(@ullable Bundle savedInstanceState)153 protected void onCreate(@Nullable Bundle savedInstanceState) { 154 super.onCreate(savedInstanceState); 155 KeyguardManager keyguardManager = getSystemService(KeyguardManager.class); 156 if (keyguardManager.isKeyguardLocked()) { 157 keyguardManager.requestDismissKeyguard(this, null); 158 } 159 } 160 } 161 162 public static class SecondActivity extends Activity {} 163 164 private static final String TAG = "SurfaceControlViewHostTests"; 165 166 private static final long WAIT_TIMEOUT_S = 5L * HW_TIMEOUT_MULTIPLIER; 167 168 private static final ComponentName TEST_ACTIVITY = new ComponentName( 169 getInstrumentation().getContext(), TestActivity.class); 170 171 private final ActivityTestRule<TestActivity> mActivityRule = new ActivityTestRule<>( 172 TestActivity.class); 173 174 @Rule 175 public TestName mName = new TestName(); 176 177 @Rule 178 public final CheckFlagsRule mCheckFlagsRule = 179 DeviceFlagsValueProvider.createCheckFlagsRule(); 180 181 private Instrumentation mInstrumentation; 182 private CtsTouchUtils mCtsTouchUtils; 183 private Activity mActivity; 184 private SurfaceView mSurfaceView; 185 private ViewGroup mViewParent; 186 187 private SurfaceControlViewHost mVr; 188 private View mEmbeddedView; 189 private WindowManager.LayoutParams mEmbeddedLayoutParams; 190 191 private volatile boolean mClicked = false; 192 private volatile boolean mPopupClicked = false; 193 private volatile PopupWindow mPopupWindow; 194 195 private SurfaceControlViewHost.SurfacePackage mRemoteSurfacePackage; 196 197 private final Map<String, 198 FutureConnection<ICrossProcessSurfaceControlViewHostTestService>> mConnections = 199 new ArrayMap<>(); 200 private ICrossProcessSurfaceControlViewHostTestService mTestService = null; 201 private static final long TIMEOUT_MS = 3000L * HW_TIMEOUT_MULTIPLIER; 202 203 /* 204 * Configurable state to control how the surfaceCreated callback 205 * will initialize the embedded view hierarchy. 206 */ 207 int mEmbeddedViewWidth = 100; 208 int mEmbeddedViewHeight = 100; 209 210 private static final int DEFAULT_SURFACE_VIEW_WIDTH = 100; 211 private static final int DEFAULT_SURFACE_VIEW_HEIGHT = 100; 212 MockImeSession mImeSession; 213 214 Consumer<MotionEvent> mSurfaceViewMotionConsumer = null; 215 216 private CountDownLatch mSvCreatedLatch; 217 218 UinputTouchDevice mTouchScreen; 219 220 private int mDisplayId; 221 222 class MotionConsumingSurfaceView extends SurfaceView { MotionConsumingSurfaceView(Context c)223 MotionConsumingSurfaceView(Context c) { 224 super(c); 225 } 226 227 @Override onTouchEvent(MotionEvent ev)228 public boolean onTouchEvent(MotionEvent ev) { 229 if (mSurfaceViewMotionConsumer == null) { 230 return false; 231 } else { 232 mSurfaceViewMotionConsumer.accept(ev); 233 return true; 234 } 235 } 236 } 237 238 boolean mHostGotEvent = false; 239 240 @Before setUp()241 public void setUp() throws Exception { 242 super.setUp(); 243 mClicked = false; 244 mEmbeddedLayoutParams = null; 245 mPopupWindow = null; 246 mRemoteSurfacePackage = null; 247 248 if (supportsInstallableIme()) { 249 mImeSession = createManagedMockImeSession(this); 250 } 251 252 mInstrumentation = InstrumentationRegistry.getInstrumentation(); 253 mCtsTouchUtils = new CtsTouchUtils(mInstrumentation.getTargetContext()); 254 mActivity = mActivityRule.launchActivity(null); 255 mDisplayId = mActivity.getDisplayId(); 256 mTouchScreen = new UinputTouchScreen(mInstrumentation, mActivity.getDisplay()); 257 mInstrumentation.waitForIdleSync(); 258 // Wait for device animation that shows above the activity to leave. 259 waitForWindowOnTop(mActivity.getWindow()); 260 261 // This is necessary to call waitForWindowInfos 262 mInstrumentation.getUiAutomation().adoptShellPermissionIdentity( 263 android.Manifest.permission.ACCESS_SURFACE_FLINGER); 264 265 mSvCreatedLatch = new CountDownLatch(1); 266 } 267 268 @After tearDown()269 public void tearDown() throws Throwable { 270 for (FutureConnection<ICrossProcessSurfaceControlViewHostTestService> connection : 271 mConnections.values()) { 272 mInstrumentation.getContext().unbindService(connection); 273 } 274 mConnections.clear(); 275 mInstrumentation.getUiAutomation().dropShellPermissionIdentity(); 276 mTouchScreen.close(); 277 } 278 getViewLocationOnScreen(@onNull View view)279 private static int[] getViewLocationOnScreen(@NonNull View view) { 280 final int[] xy = new int[2]; 281 view.getLocationOnScreen(xy); 282 return xy; 283 } 284 getViewCenterOnScreen(@onNull View view)285 private static Point getViewCenterOnScreen(@NonNull View view) { 286 final int[] xy = getViewLocationOnScreen(view); 287 final int viewWidth = view.getWidth(); 288 final int viewHeight = view.getHeight(); 289 290 return new Point(xy[0] + viewWidth / 2, xy[1] + viewHeight / 2); 291 } 292 globalTapOnViewCenter(@onNull View view)293 private void globalTapOnViewCenter(@NonNull View view) { 294 final Point location = getViewCenterOnScreen(view); 295 UinputTouchDevice.Pointer pointer = mTouchScreen.touchDown(location.x, location.y); 296 pointer.lift(); 297 } 298 globalTapOnWindowCenter(@onNull Supplier<IBinder> windowTokenSupplier)299 private void globalTapOnWindowCenter(@NonNull Supplier<IBinder> windowTokenSupplier) 300 throws InterruptedException { 301 final Point center = getWindowCenter(windowTokenSupplier, mDisplayId); 302 UinputTouchDevice.Pointer pointer = mTouchScreen.touchDown(center.x, center.y); 303 pointer.lift(); 304 } 305 globalTapOnWindowCorner(@onNull Supplier<IBinder> windowTokenSupplier)306 private void globalTapOnWindowCorner(@NonNull Supplier<IBinder> windowTokenSupplier) 307 throws InterruptedException { 308 globalTapOnWindow(windowTokenSupplier, 0 /*xOffset*/, 0 /*yOffset*/); 309 } 310 globalTapOnWindow(@onNull Supplier<IBinder> windowTokenSupplier, int xOffset, int yOffset)311 private void globalTapOnWindow(@NonNull Supplier<IBinder> windowTokenSupplier, int xOffset, 312 int yOffset) throws InterruptedException { 313 Rect bounds = getWindowBoundsInDisplaySpace(windowTokenSupplier, mDisplayId); 314 if (bounds == null) { 315 fail("Could not get bounds for window!"); 316 } 317 318 UinputTouchDevice.Pointer pointer = mTouchScreen.touchDown(bounds.left + xOffset, 319 bounds.top + yOffset); 320 pointer.lift(); 321 } 322 addSurfaceView(int width, int height)323 private void addSurfaceView(int width, int height) throws Throwable { 324 addSurfaceView(width, height, true); 325 } 326 addSurfaceView(int width, int height, boolean onTop)327 private void addSurfaceView(int width, int height, boolean onTop) throws Throwable { 328 addSurfaceView(width, height, onTop, 0 /* leftMargin */, 0 /* topMargin */); 329 } 330 addSurfaceView(int width, int height, boolean onTop, int leftMargin, int topMargin)331 private void addSurfaceView(int width, int height, boolean onTop, int leftMargin, int topMargin) 332 throws Throwable { 333 mActivityRule.runOnUiThread(() -> { 334 final FrameLayout content = new FrameLayout(mActivity); 335 mSurfaceView = new MotionConsumingSurfaceView(mActivity); 336 mSurfaceView.setBackgroundColor(Color.BLACK); 337 mSurfaceView.setZOrderOnTop(onTop); 338 final FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams( 339 width, height, Gravity.LEFT | Gravity.TOP); 340 lp.leftMargin = leftMargin; 341 lp.topMargin = topMargin; 342 content.addView(mSurfaceView, lp); 343 mViewParent = content; 344 mActivity.setContentView(content, 345 new ViewGroup.LayoutParams(width + leftMargin, height + topMargin)); 346 mSurfaceView.getHolder().addCallback(this); 347 }); 348 } 349 addViewToSurfaceView(SurfaceView sv, View v, int width, int height)350 private void addViewToSurfaceView(SurfaceView sv, View v, int width, int height) { 351 mVr = new SurfaceControlViewHost(mActivity, mActivity.getDisplay(), sv.getHostToken()); 352 353 if (mEmbeddedLayoutParams == null) { 354 mVr.setView(v, width, height); 355 } else { 356 mVr.setView(v, mEmbeddedLayoutParams); 357 } 358 359 sv.setChildSurfacePackage(mVr.getSurfacePackage()); 360 361 assertEquals(v, mVr.getView()); 362 } 363 requestSurfaceViewFocus()364 private void requestSurfaceViewFocus() throws Throwable { 365 mActivityRule.runOnUiThread(() -> { 366 mSurfaceView.setFocusableInTouchMode(true); 367 mSurfaceView.requestFocusFromTouch(); 368 }); 369 } 370 assertWindowFocused(final View view, boolean hasWindowFocus)371 private void assertWindowFocused(final View view, boolean hasWindowFocus) { 372 if (!waitForWindowFocus(view, hasWindowFocus)) { 373 fail(); 374 } 375 } 376 waitUntilViewDrawn(View view)377 private void waitUntilViewDrawn(View view) throws Throwable { 378 // We use frameCommitCallback because we need to ensure HWUI 379 // has actually queued the frame. 380 final CountDownLatch latch = new CountDownLatch(1); 381 mActivityRule.runOnUiThread(() -> { 382 view.getViewTreeObserver().registerFrameCommitCallback( 383 latch::countDown); 384 view.invalidate(); 385 }); 386 assertTrue(latch.await(HW_TIMEOUT_MULTIPLIER, TimeUnit.SECONDS)); 387 } 388 waitUntilEmbeddedViewDrawn()389 private void waitUntilEmbeddedViewDrawn() throws Throwable { 390 waitUntilViewDrawn(mEmbeddedView); 391 } 392 getTouchableRegionFromDump()393 private String getTouchableRegionFromDump() { 394 final String output = runCommandAndPrintOutput("dumpsys window windows"); 395 boolean foundWindow = false; 396 for (String line : output.split("\\n")) { 397 if (line.contains("SurfaceControlViewHostTests$TestActivity")) { 398 foundWindow = true; 399 } 400 if (foundWindow && line.contains("touchable region")) { 401 return line; 402 } 403 } 404 return null; 405 } 406 waitForTouchableRegionChanged(String originalTouchableRegion)407 private boolean waitForTouchableRegionChanged(String originalTouchableRegion) { 408 int retries = 0; 409 while (retries < 50) { 410 if (getTouchableRegionFromDump() != originalTouchableRegion) { 411 return true; 412 } 413 try { 414 Thread.sleep(100); 415 } catch (Exception e) { 416 } 417 } 418 return false; 419 } 420 waitForViewFocus(final View view, boolean hasViewFocus)421 public static boolean waitForViewFocus(final View view, boolean hasViewFocus) { 422 final CountDownLatch latch = new CountDownLatch(1); 423 424 view.getHandler().post(() -> { 425 if (view.hasFocus() == hasViewFocus) { 426 latch.countDown(); 427 return; 428 } 429 view.setOnFocusChangeListener((v, hasFocus) -> { 430 if (hasViewFocus == hasFocus) { 431 view.setOnFocusChangeListener(null); 432 latch.countDown(); 433 } 434 }); 435 }); 436 437 try { 438 if (!latch.await(3, TimeUnit.SECONDS)) { 439 return false; 440 } 441 } catch (InterruptedException e) { 442 return false; 443 } 444 return true; 445 } 446 447 @Override surfaceCreated(SurfaceHolder holder)448 public void surfaceCreated(SurfaceHolder holder) { 449 if (mTestService == null) { 450 if (mEmbeddedView != null) { 451 addViewToSurfaceView(mSurfaceView, mEmbeddedView, 452 mEmbeddedViewWidth, mEmbeddedViewHeight); 453 } 454 } else if (mRemoteSurfacePackage == null) { 455 try { 456 mRemoteSurfacePackage = mTestService.getSurfacePackage(mSurfaceView.getHostToken()); 457 } catch (Exception e) { 458 } 459 mSurfaceView.setChildSurfacePackage(mRemoteSurfacePackage); 460 } else { 461 mSurfaceView.setChildSurfacePackage(mRemoteSurfacePackage); 462 } 463 mSvCreatedLatch.countDown(); 464 } 465 466 @Override surfaceDestroyed(SurfaceHolder holder)467 public void surfaceDestroyed(SurfaceHolder holder) { 468 } 469 470 @Override surfaceChanged(SurfaceHolder holder, int format, int width, int height)471 public void surfaceChanged(SurfaceHolder holder, int format, int width, 472 int height) { 473 } 474 475 @Test testEmbeddedViewReceivesInput()476 public void testEmbeddedViewReceivesInput() throws Throwable { 477 mEmbeddedView = new Button(mActivity); 478 mEmbeddedView.setOnClickListener((View v) -> { 479 mClicked = true; 480 }); 481 482 addSurfaceView(DEFAULT_SURFACE_VIEW_WIDTH, DEFAULT_SURFACE_VIEW_HEIGHT); 483 mInstrumentation.waitForIdleSync(); 484 waitUntilEmbeddedViewDrawn(); 485 486 mCtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, mActivityRule, mSurfaceView); 487 assertTrue(mClicked); 488 } 489 490 @Test testEmbeddedViewReceivesRawInputCoordinatesInDisplaySpace()491 public void testEmbeddedViewReceivesRawInputCoordinatesInDisplaySpace() throws Throwable { 492 assumeFalse("Automotive splitscreen uses multi-window root tasks. Because of the " 493 + "lack of a correct display coordinates transform from logical to physical this " 494 + "test fails on android15", hasAutomotiveSplitscreenMultitaskingFeature()); 495 496 final UiAutomation uiAutomation = mInstrumentation.getUiAutomation(); 497 final int viewX = DEFAULT_SURFACE_VIEW_WIDTH / 2; 498 final int viewY = DEFAULT_SURFACE_VIEW_HEIGHT / 2; 499 500 // Verify the input coordinates received by the embedded view in three different locations. 501 for (int i = 0; i < 3; i++) { 502 final List<MotionEvent> events = new ArrayList<>(); 503 mEmbeddedView = new View(mActivity); 504 mEmbeddedView.setOnTouchListener((v, e) -> events.add(MotionEvent.obtain(e))); 505 506 // Add a margin to the SurfaceView to offset the embedded view's location on the screen. 507 final int leftMargin = i * 20; 508 final int topMargin = i * 10; 509 addSurfaceView(DEFAULT_SURFACE_VIEW_WIDTH, DEFAULT_SURFACE_VIEW_HEIGHT, true /*onTop*/, 510 leftMargin, topMargin); 511 mInstrumentation.waitForIdleSync(); 512 waitUntilEmbeddedViewDrawn(); 513 waitForStableWindowGeometry(Duration.ofSeconds(WAIT_TIMEOUT_S)); 514 515 final int[] surfaceLocation = new int[2]; 516 mSurfaceView.getLocationOnScreen(surfaceLocation); 517 518 final int displayX = surfaceLocation[0] + viewX; 519 final int displayY = surfaceLocation[1] + viewY; 520 521 UinputTouchDevice.Pointer pointer = mTouchScreen.touchDown(displayX, displayY); 522 pointer.lift(); 523 524 PollingCheck.waitFor(() -> (events.size() == 2)); 525 events.forEach(e -> { 526 assertEquals("Expected to get the x coordinate in View space.", 527 viewX, e.getX(), UinputTouchDevice.TOUCH_COORDINATE_EPSILON); 528 assertEquals("Expected to get the y coordinate in View space.", 529 viewY, e.getY(), UinputTouchDevice.TOUCH_COORDINATE_EPSILON); 530 assertEquals("Expected to get raw x coordinate in Display space.", 531 displayX, e.getRawX(), UinputTouchDevice.TOUCH_COORDINATE_EPSILON); 532 assertEquals("Expected to get raw y coordinate in Display space.", 533 displayY, e.getRawY(), UinputTouchDevice.TOUCH_COORDINATE_EPSILON); 534 }); 535 } 536 } 537 getGlEsVersion(Context context)538 private static int getGlEsVersion(Context context) { 539 ActivityManager activityManager = 540 (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); 541 ConfigurationInfo configInfo = activityManager.getDeviceConfigurationInfo(); 542 if (configInfo.reqGlEsVersion != ConfigurationInfo.GL_ES_VERSION_UNDEFINED) { 543 return getMajorVersion(configInfo.reqGlEsVersion); 544 } else { 545 return 1; // Lack of property means OpenGL ES version 1 546 } 547 } 548 549 /** 550 * @see FeatureInfo#getGlEsVersion() 551 */ getMajorVersion(int glEsVersion)552 private static int getMajorVersion(int glEsVersion) { 553 return ((glEsVersion & 0xffff0000) >> 16); 554 } 555 getImeTestMarker()556 private @NonNull String getImeTestMarker() { 557 return mName + Long.toString(SystemClock.elapsedRealtimeNanos()); 558 } 559 560 @Test 561 @RequiresDevice testEmbeddedViewIsHardwareAccelerated()562 public void testEmbeddedViewIsHardwareAccelerated() throws Throwable { 563 // Hardware accel may not be supported on devices without GLES 2.0 564 if (getGlEsVersion(mActivity) < 2) { 565 return; 566 } 567 mEmbeddedView = new Button(mActivity); 568 mEmbeddedView.setOnClickListener((View v) -> { 569 mClicked = true; 570 }); 571 572 addSurfaceView(DEFAULT_SURFACE_VIEW_WIDTH, DEFAULT_SURFACE_VIEW_HEIGHT); 573 mInstrumentation.waitForIdleSync(); 574 575 // If we don't support hardware acceleration on the main activity the embedded 576 // view also won't be. 577 if (!mSurfaceView.isHardwareAccelerated()) { 578 return; 579 } 580 581 assertTrue(mEmbeddedView.isHardwareAccelerated()); 582 } 583 584 @Test testEmbeddedViewResizes()585 public void testEmbeddedViewResizes() throws Throwable { 586 mEmbeddedView = new Button(mActivity); 587 mEmbeddedView.setOnClickListener((View v) -> { 588 mClicked = true; 589 }); 590 591 final int bigEdgeLength = mEmbeddedViewWidth * 3; 592 593 // We make the SurfaceView more than twice as big as the embedded view 594 // so that a touch in the middle of the SurfaceView won't land 595 // on the embedded view. 596 addSurfaceView(bigEdgeLength, bigEdgeLength); 597 mInstrumentation.waitForIdleSync(); 598 599 mCtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, mActivityRule, mSurfaceView); 600 assertFalse(mClicked); 601 602 mActivityRule.runOnUiThread(() -> { 603 mVr.relayout(bigEdgeLength, bigEdgeLength); 604 }); 605 mInstrumentation.waitForIdleSync(); 606 waitUntilEmbeddedViewDrawn(); 607 608 // But after the click should hit. 609 mCtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, mActivityRule, mSurfaceView); 610 assertTrue(mClicked); 611 } 612 613 @Test testEmbeddedViewReleases()614 public void testEmbeddedViewReleases() throws Throwable { 615 mEmbeddedView = new Button(mActivity); 616 mEmbeddedView.setOnClickListener((View v) -> { 617 mClicked = true; 618 }); 619 620 addSurfaceView(DEFAULT_SURFACE_VIEW_WIDTH, DEFAULT_SURFACE_VIEW_HEIGHT); 621 mInstrumentation.waitForIdleSync(); 622 623 mCtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, mActivityRule, mSurfaceView); 624 assertTrue(mClicked); 625 626 mActivityRule.runOnUiThread(() -> { 627 mVr.release(); 628 }); 629 mInstrumentation.waitForIdleSync(); 630 631 mClicked = false; 632 mCtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, mActivityRule, mSurfaceView); 633 assertFalse(mClicked); 634 } 635 636 @Test testDisableInputTouch()637 public void testDisableInputTouch() throws Throwable { 638 mEmbeddedView = new Button(mActivity); 639 mEmbeddedView.setOnClickListener((View v) -> { 640 mClicked = true; 641 }); 642 643 mEmbeddedLayoutParams = new WindowManager.LayoutParams(mEmbeddedViewWidth, 644 mEmbeddedViewHeight, WindowManager.LayoutParams.TYPE_APPLICATION, 0, 645 PixelFormat.OPAQUE); 646 647 addSurfaceView(DEFAULT_SURFACE_VIEW_WIDTH, DEFAULT_SURFACE_VIEW_HEIGHT); 648 mInstrumentation.waitForIdleSync(); 649 650 mActivityRule.runOnUiThread(() -> { 651 mEmbeddedLayoutParams.flags |= FLAG_NOT_TOUCHABLE; 652 mVr.relayout(mEmbeddedLayoutParams); 653 }); 654 mInstrumentation.waitForIdleSync(); 655 656 mCtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, mActivityRule, mSurfaceView); 657 assertFalse(mClicked); 658 659 mActivityRule.runOnUiThread(() -> { 660 mEmbeddedLayoutParams.flags &= ~FLAG_NOT_TOUCHABLE; 661 mVr.relayout(mEmbeddedLayoutParams); 662 }); 663 mInstrumentation.waitForIdleSync(); 664 665 mCtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, mActivityRule, mSurfaceView); 666 assertTrue(mClicked); 667 } 668 669 @Test testReceivesOutsideEvents()670 public void testReceivesOutsideEvents() throws Throwable { 671 final CountDownLatch outsideEventLatch = new CountDownLatch(1); 672 mEmbeddedView = new Button(mActivity); 673 mEmbeddedView.setOnTouchListener((view, event) -> { 674 if (event.getActionMasked() == MotionEvent.ACTION_OUTSIDE) { 675 outsideEventLatch.countDown(); 676 } 677 return true; 678 }); 679 680 mEmbeddedLayoutParams = new WindowManager.LayoutParams(mEmbeddedViewWidth, 681 mEmbeddedViewHeight, WindowManager.LayoutParams.TYPE_APPLICATION, 0, 682 PixelFormat.OPAQUE); 683 684 final int marginLeft = 20; 685 final int marginTop = 20; 686 addSurfaceView(DEFAULT_SURFACE_VIEW_WIDTH, DEFAULT_SURFACE_VIEW_HEIGHT, true /*onTop*/, 687 marginLeft, marginTop); 688 mInstrumentation.waitForIdleSync(); 689 690 mActivityRule.runOnUiThread(() -> { 691 mEmbeddedLayoutParams.flags |= FLAG_WATCH_OUTSIDE_TOUCH; 692 mVr.relayout(mEmbeddedLayoutParams); 693 }); 694 mInstrumentation.waitForIdleSync(); 695 696 // Tap outside the embedded window. 697 globalTapOnWindow(mEmbeddedView::getWindowToken, -10 /*xOffset*/, -10 /*yOffset*/); 698 assertTrue(outsideEventLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); 699 } 700 701 @Test testFocusable()702 public void testFocusable() throws Throwable { 703 mEmbeddedView = new Button(mActivity); 704 addSurfaceView(DEFAULT_SURFACE_VIEW_WIDTH, DEFAULT_SURFACE_VIEW_HEIGHT); 705 mInstrumentation.waitForIdleSync(); 706 waitUntilEmbeddedViewDrawn(); 707 708 // When surface view is focused, it should transfer focus to the embedded view. 709 requestSurfaceViewFocus(); 710 assertWindowFocused(mEmbeddedView, true); 711 // assert host does not have focus 712 assertWindowFocused(mSurfaceView, false); 713 714 // When surface view is no longer focused, it should transfer focus back to the host window. 715 mActivityRule.runOnUiThread(() -> mSurfaceView.setFocusable(false)); 716 assertWindowFocused(mEmbeddedView, false); 717 // assert host has focus 718 assertWindowFocused(mSurfaceView, true); 719 } 720 721 @Test testFocusWithTouch()722 public void testFocusWithTouch() throws Throwable { 723 assumeFalse("Automotive splitscreen uses multi-window root tasks. Because of the " 724 + "lack of a correct display coordinates transform from logical to physical this " 725 + "test fails on android15", hasAutomotiveSplitscreenMultitaskingFeature()); 726 727 mEmbeddedView = new Button(mActivity); 728 addSurfaceView(DEFAULT_SURFACE_VIEW_WIDTH, DEFAULT_SURFACE_VIEW_HEIGHT); 729 mInstrumentation.waitForIdleSync(); 730 waitUntilEmbeddedViewDrawn(); 731 732 // Tap where the embedded window is placed to ensure focus is given via touch 733 globalTapOnWindowCenter(mEmbeddedView::getWindowToken); 734 735 assertWindowFocused(mEmbeddedView, true); 736 // assert host does not have focus 737 assertWindowFocused(mSurfaceView, false); 738 739 // Tap where the host window is placed to ensure focus is given back to host when touched 740 globalTapOnWindowCenter(mViewParent::getWindowToken); 741 assertWindowFocused(mEmbeddedView, false); 742 // assert host does not have focus 743 assertWindowFocused(mViewParent, true); 744 } 745 746 @Test testChildWindowFocusable()747 public void testChildWindowFocusable() throws Throwable { 748 assumeFalse("Automotive splitscreen uses multi-window root tasks. Because of the " 749 + "lack of a correct display coordinates transform from logical to physical this " 750 + "test fails on android15", hasAutomotiveSplitscreenMultitaskingFeature()); 751 752 mEmbeddedView = new Button(mActivity); 753 mEmbeddedView.setBackgroundColor(Color.BLUE); 754 View embeddedViewChild = new Button(mActivity); 755 embeddedViewChild.setBackgroundColor(Color.RED); 756 addSurfaceView(DEFAULT_SURFACE_VIEW_WIDTH, DEFAULT_SURFACE_VIEW_HEIGHT); 757 mInstrumentation.waitForIdleSync(); 758 waitUntilEmbeddedViewDrawn(); 759 760 mActivityRule.runOnUiThread(() -> { 761 final WindowManager.LayoutParams embeddedViewChildParams = 762 new WindowManager.LayoutParams(25, 25, 763 WindowManager.LayoutParams.TYPE_APPLICATION, 0, PixelFormat.OPAQUE); 764 embeddedViewChildParams.token = mEmbeddedView.getWindowToken(); 765 WindowManager wm = mActivity.getSystemService(WindowManager.class); 766 wm.addView(embeddedViewChild, embeddedViewChildParams); 767 }); 768 769 waitUntilViewDrawn(embeddedViewChild); 770 771 globalTapOnWindowCenter(embeddedViewChild::getWindowToken); 772 // When tapping on the child embedded window, it should gain focus. 773 assertWindowFocused(embeddedViewChild, true); 774 // assert parent embedded window does not have focus. 775 assertWindowFocused(mEmbeddedView, false); 776 // assert host does not have focus 777 assertWindowFocused(mSurfaceView, false); 778 779 globalTapOnWindowCorner(mEmbeddedView::getWindowToken); 780 781 // When tapping on the parent embedded window, it should gain focus. 782 assertWindowFocused(mEmbeddedView, true); 783 // assert child embedded window does not have focus. 784 assertWindowFocused(embeddedViewChild, false); 785 // assert host does not have focus 786 assertWindowFocused(mSurfaceView, false); 787 } 788 789 @Test testFocusWithTouchCrossProcess()790 public void testFocusWithTouchCrossProcess() throws Throwable { 791 assumeFalse("Automotive splitscreen uses multi-window root tasks. Because of the " 792 + "lack of a correct display coordinates transform from logical to physical this " 793 + "test fails on android15", hasAutomotiveSplitscreenMultitaskingFeature()); 794 795 mTestService = getService(); 796 assertNotNull(mTestService); 797 798 addSurfaceView(DEFAULT_SURFACE_VIEW_WIDTH, DEFAULT_SURFACE_VIEW_HEIGHT); 799 mSvCreatedLatch.await(5, TimeUnit.SECONDS); 800 801 // Tap where the embedded window is placed to ensure focus is given via touch 802 globalTapOnWindowCenter(() -> { 803 try { 804 return mTestService.getWindowToken(); 805 } catch (RemoteException e) { 806 fail("Could not get token from service, got " + e); 807 return null; 808 } 809 }); 810 assertTrue(mTestService.waitForFocus(true)); 811 // assert host does not have focus 812 assertWindowFocused(mSurfaceView, false); 813 814 // Tap where the host window is placed to ensure focus is given back to host when touched 815 globalTapOnWindowCenter(mViewParent::getWindowToken); 816 assertTrue(mTestService.waitForFocus(false)); 817 // assert host does not have focus 818 assertWindowFocused(mViewParent, true); 819 } 820 821 @Test testWindowResumes_FocusTransfersToEmbedded()822 public void testWindowResumes_FocusTransfersToEmbedded() throws Throwable { 823 mEmbeddedView = new Button(mActivity); 824 addSurfaceView(DEFAULT_SURFACE_VIEW_WIDTH, DEFAULT_SURFACE_VIEW_HEIGHT); 825 mInstrumentation.waitForIdleSync(); 826 waitUntilEmbeddedViewDrawn(); 827 828 // When surface view is focused, it should transfer focus to the embedded view. 829 requestSurfaceViewFocus(); 830 assertWindowFocused(mEmbeddedView, true); 831 // assert host does not have focus 832 assertWindowFocused(mSurfaceView, false); 833 834 WindowManager wm = mActivity.getSystemService(WindowManager.class); 835 View childView = new Button(mActivity); 836 mActivityRule.runOnUiThread(() -> { 837 final WindowManager.LayoutParams childWindowParams = 838 new WindowManager.LayoutParams(25, 25, 839 WindowManager.LayoutParams.TYPE_APPLICATION, 0, PixelFormat.OPAQUE); 840 wm.addView(childView, childWindowParams); 841 }); 842 waitUntilViewDrawn(childView); 843 assertWindowFocused(childView, true); 844 // Neither host or embedded should be focus 845 assertWindowFocused(mSurfaceView, false); 846 assertWindowFocused(mEmbeddedView, false); 847 848 mActivityRule.runOnUiThread(() -> wm.removeView(childView)); 849 mInstrumentation.waitForIdleSync(); 850 851 assertWindowFocused(mEmbeddedView, true); 852 assertWindowFocused(mSurfaceView, false); 853 } 854 855 @Test testImeVisible()856 public void testImeVisible() throws Throwable { 857 assumeTrue(MSG_NO_MOCK_IME, supportsInstallableIme()); 858 EditText editText = new EditText(mActivity); 859 860 mEmbeddedView = editText; 861 editText.setBackgroundColor(Color.BLUE); 862 editText.setPrivateImeOptions(getImeTestMarker()); 863 addSurfaceView(DEFAULT_SURFACE_VIEW_WIDTH, DEFAULT_SURFACE_VIEW_HEIGHT); 864 mInstrumentation.waitForIdleSync(); 865 waitUntilEmbeddedViewDrawn(); 866 867 // When surface view is focused, it should transfer focus to the embedded view. 868 requestSurfaceViewFocus(); 869 assertWindowFocused(mEmbeddedView, true); 870 // assert host does not have focus 871 assertWindowFocused(mSurfaceView, false); 872 873 mCtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, mActivityRule, mSurfaceView); 874 final ImeEventStream stream = mImeSession.openEventStream(); 875 expectEvent(stream, editorMatcher("onStartInputView", 876 editText.getPrivateImeOptions()), TIMEOUT_MS); 877 } 878 879 @Test testImeVisibleWithZBelowRequest()880 public void testImeVisibleWithZBelowRequest() throws Throwable { 881 assumeTrue(MSG_NO_MOCK_IME, supportsInstallableIme()); 882 EditText editText = new EditText(mActivity); 883 884 mEmbeddedView = editText; 885 editText.setBackgroundColor(Color.BLUE); 886 editText.setPrivateImeOptions(getImeTestMarker()); 887 addSurfaceView(DEFAULT_SURFACE_VIEW_WIDTH, DEFAULT_SURFACE_VIEW_HEIGHT, /*onTop*/ false); 888 mInstrumentation.waitForIdleSync(); 889 waitUntilEmbeddedViewDrawn(); 890 891 // When surface view is focused, it should transfer focus to the embedded view. 892 requestSurfaceViewFocus(); 893 assertWindowFocused(mEmbeddedView, true); 894 // assert host does not have focus 895 assertWindowFocused(mSurfaceView, false); 896 897 mActivityRule.runOnUiThread( 898 () -> { 899 editText.requestFocus(); 900 }); 901 final ImeEventStream stream = mImeSession.openEventStream(); 902 expectEvent(stream, editorMatcher("onStartInput", 903 editText.getPrivateImeOptions()), TIMEOUT_MS); 904 905 mActivityRule.runOnUiThread( 906 () -> { 907 final InputMethodManager imm = 908 mActivity.getSystemService(InputMethodManager.class); 909 imm.showSoftInput(editText, 0); 910 }); 911 expectEvent( 912 stream, 913 editorMatcher("onStartInputView", editText.getPrivateImeOptions()), 914 TIMEOUT_MS); 915 } 916 917 @Test testImeVisibleWithZBelowTouch()918 public void testImeVisibleWithZBelowTouch() throws Throwable { 919 assumeTrue(MSG_NO_MOCK_IME, supportsInstallableIme()); 920 EditText editText = new EditText(mActivity); 921 922 mEmbeddedView = editText; 923 editText.setBackgroundColor(Color.BLUE); 924 editText.setPrivateImeOptions(getImeTestMarker()); 925 addSurfaceView(DEFAULT_SURFACE_VIEW_WIDTH, DEFAULT_SURFACE_VIEW_HEIGHT, /*onTop*/ false); 926 mInstrumentation.waitForIdleSync(); 927 waitUntilEmbeddedViewDrawn(); 928 929 // When surface view is focused, it should transfer focus to the embedded view. 930 requestSurfaceViewFocus(); 931 assertWindowFocused(mEmbeddedView, true); 932 // assert host does not have focus 933 assertWindowFocused(mSurfaceView, false); 934 935 CountDownLatch waitForClientDraw = new CountDownLatch(1); 936 SurfaceControl.Transaction t = new SurfaceControl.Transaction(); 937 t.addTransactionCommittedListener(Runnable::run, waitForClientDraw::countDown); 938 mActivityRule.runOnUiThread( 939 () -> { 940 mSurfaceView.getRootSurfaceControl().applyTransactionOnDraw(t); 941 mSurfaceView.getRootSurfaceControl().setTouchableRegion(new Region()); 942 }); 943 assertTrue( 944 "Failed to wait for touchable region to be updated", 945 waitForClientDraw.await(WAIT_TIMEOUT_S, TimeUnit.SECONDS)); 946 947 // wait for input to get the updated touch regions 948 mInstrumentation.getUiAutomation().syncInputTransactions(true); 949 950 mCtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, mActivityRule, mSurfaceView); 951 final ImeEventStream stream = mImeSession.openEventStream(); 952 expectEvent( 953 stream, 954 editorMatcher("onStartInputView", editText.getPrivateImeOptions()), 955 TIMEOUT_MS); 956 } 957 958 @Test testNotFocusable()959 public void testNotFocusable() throws Throwable { 960 mEmbeddedView = new Button(mActivity); 961 addSurfaceView(DEFAULT_SURFACE_VIEW_WIDTH, DEFAULT_SURFACE_VIEW_HEIGHT); 962 mEmbeddedLayoutParams = new WindowManager.LayoutParams(mEmbeddedViewWidth, 963 mEmbeddedViewHeight, WindowManager.LayoutParams.TYPE_APPLICATION, 0, 964 PixelFormat.OPAQUE); 965 mActivityRule.runOnUiThread(() -> { 966 mEmbeddedLayoutParams.flags |= FLAG_NOT_FOCUSABLE; 967 mVr.relayout(mEmbeddedLayoutParams); 968 }); 969 mInstrumentation.waitForIdleSync(); 970 waitUntilEmbeddedViewDrawn(); 971 972 // When surface view is focused, nothing should happen since the embedded view is not 973 // focusable. 974 requestSurfaceViewFocus(); 975 assertWindowFocused(mEmbeddedView, false); 976 // assert host has focus 977 assertWindowFocused(mSurfaceView, true); 978 } 979 980 @Test testFocusBeforeAddingEmbedded()981 public void testFocusBeforeAddingEmbedded() throws Throwable { 982 addSurfaceView(DEFAULT_SURFACE_VIEW_WIDTH, DEFAULT_SURFACE_VIEW_HEIGHT); 983 // Request focus to the SV before adding the embedded. 984 requestSurfaceViewFocus(); 985 mSvCreatedLatch.await(); 986 assertTrue("Failed to wait for sv to gain focus", waitForViewFocus(mSurfaceView, true)); 987 988 mEmbeddedView = new Button(mActivity); 989 mActivityRule.runOnUiThread(() -> { 990 addViewToSurfaceView(mSurfaceView, mEmbeddedView, mEmbeddedViewWidth, 991 mEmbeddedViewHeight); 992 }); 993 waitForWindowVisible(mEmbeddedView); 994 assertWindowFocused(mEmbeddedView, true); 995 assertWindowFocused(mSurfaceView, false); 996 } 997 998 @Test testViewHostParentRemainConnected()999 public void testViewHostParentRemainConnected() throws Throwable { 1000 addSurfaceView(DEFAULT_SURFACE_VIEW_WIDTH, DEFAULT_SURFACE_VIEW_HEIGHT); 1001 requestSurfaceViewFocus(); 1002 mSvCreatedLatch.await(); 1003 mEmbeddedView = new Button(mActivity); 1004 mActivityRule.runOnUiThread( 1005 () -> { 1006 addViewToSurfaceView( 1007 mSurfaceView, mEmbeddedView, mEmbeddedViewWidth, mEmbeddedViewHeight); 1008 }); 1009 waitForWindowVisible(mEmbeddedView); 1010 assertWindowFocused(mEmbeddedView, true); 1011 assertWindowFocused(mSurfaceView, false); 1012 1013 final ActivityTestRule<SecondActivity> secondActivityRule = 1014 new ActivityTestRule<>(SecondActivity.class); 1015 final Activity secondActivity = secondActivityRule.launchActivity(null); 1016 waitAndAssertActivityState( 1017 secondActivity.getComponentName(), STATE_RESUMED, "Top activity must be resumed."); 1018 waitAndAssertActivityState( 1019 mActivity.getComponentName(), STATE_STOPPED, "Test activity must be stopped."); 1020 1021 secondActivity.finish(); 1022 waitAndAssertActivityState( 1023 mActivity.getComponentName(), STATE_RESUMED, "Test activity must be resumed."); 1024 // Input focus should remained as the remote view. 1025 assertWindowFocused(mEmbeddedView, false); 1026 // The remote view should forward the back key to host activity, which will finish itself. 1027 TouchHelper.injectKey(KeyEvent.KEYCODE_BACK, false /* longpress */, true /* sync */); 1028 mWmState.waitForHomeActivityVisible(); 1029 } 1030 1031 private static class SurfaceCreatedCallback implements SurfaceHolder.Callback { 1032 private final CountDownLatch mSurfaceCreated; 1033 SurfaceCreatedCallback(CountDownLatch latch)1034 SurfaceCreatedCallback(CountDownLatch latch) { 1035 mSurfaceCreated = latch; 1036 } 1037 1038 @Override surfaceCreated(SurfaceHolder holder)1039 public void surfaceCreated(SurfaceHolder holder) { 1040 mSurfaceCreated.countDown(); 1041 } 1042 1043 @Override surfaceDestroyed(SurfaceHolder holder)1044 public void surfaceDestroyed(SurfaceHolder holder) { 1045 } 1046 1047 @Override surfaceChanged(SurfaceHolder holder, int format, int width, int height)1048 public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { 1049 } 1050 } 1051 1052 @Test testCanCopySurfacePackage()1053 public void testCanCopySurfacePackage() throws Throwable { 1054 assumeFalse("Automotive splitscreen uses multi-window root tasks. Because of the " 1055 + "lack of a correct display coordinates transform from logical to physical this " 1056 + "test fails on android15", hasAutomotiveSplitscreenMultitaskingFeature()); 1057 1058 // Create a surface view and wait for its surface to be created. 1059 CountDownLatch surfaceCreated = new CountDownLatch(1); 1060 mActivityRule.runOnUiThread(() -> { 1061 final FrameLayout content = new FrameLayout(mActivity); 1062 mSurfaceView = new SurfaceView(mActivity); 1063 mSurfaceView.setZOrderOnTop(true); 1064 content.addView(mSurfaceView, new FrameLayout.LayoutParams( 1065 DEFAULT_SURFACE_VIEW_WIDTH, DEFAULT_SURFACE_VIEW_HEIGHT, 1066 Gravity.LEFT | Gravity.TOP)); 1067 mActivity.setContentView(content, new ViewGroup.LayoutParams(DEFAULT_SURFACE_VIEW_WIDTH, 1068 DEFAULT_SURFACE_VIEW_HEIGHT)); 1069 mSurfaceView.getHolder().addCallback(new SurfaceCreatedCallback(surfaceCreated)); 1070 1071 // Create an embedded view. 1072 mVr = new SurfaceControlViewHost(mActivity, mActivity.getDisplay(), 1073 mSurfaceView.getHostToken()); 1074 mEmbeddedView = new Button(mActivity); 1075 mEmbeddedView.setOnClickListener((View v) -> mClicked = true); 1076 mVr.setView(mEmbeddedView, mEmbeddedViewWidth, mEmbeddedViewHeight); 1077 1078 }); 1079 assertTrue("Failed to wait for SurfaceView created", 1080 surfaceCreated.await(WAIT_TIMEOUT_S, TimeUnit.SECONDS)); 1081 1082 // Make a copy of the SurfacePackage and release the original package. 1083 SurfacePackage surfacePackage = mVr.getSurfacePackage(); 1084 SurfacePackage copy = new SurfacePackage(surfacePackage); 1085 surfacePackage.release(); 1086 1087 CountDownLatch surfacePackageReparented = new CountDownLatch(1); 1088 mActivityRule.runOnUiThread(() -> { 1089 mSurfaceView.setChildSurfacePackage(copy); 1090 SurfaceControl.Transaction t = new SurfaceControl.Transaction(); 1091 t.addTransactionCommittedListener(Runnable::run, surfacePackageReparented::countDown); 1092 mSurfaceView.getRootSurfaceControl().applyTransactionOnDraw(t); 1093 }); 1094 assertTrue("Failed to wait for surface package to get reparented", 1095 surfacePackageReparented.await(WAIT_TIMEOUT_S, TimeUnit.SECONDS)); 1096 1097 mInstrumentation.waitForIdleSync(); 1098 waitUntilEmbeddedViewDrawn(); 1099 1100 // Check if SurfacePackage copy remains valid even though the original package has 1101 // been released. 1102 globalTapOnWindowCenter(mEmbeddedView::getWindowToken); 1103 PollingCheck.waitFor(() -> mClicked); 1104 } 1105 1106 @Test testTransferSurfacePackage()1107 public void testTransferSurfacePackage() throws Throwable { 1108 // Create a surface view and wait for its surface to be created. 1109 CountDownLatch surfaceCreated = new CountDownLatch(1); 1110 CountDownLatch surface2Created = new CountDownLatch(1); 1111 CountDownLatch viewDetached = new CountDownLatch(1); 1112 AtomicReference<SurfacePackage> surfacePackageRef = new AtomicReference<>(null); 1113 AtomicReference<SurfacePackage> surfacePackageCopyRef = new AtomicReference<>(null); 1114 AtomicReference<SurfaceView> secondSurfaceRef = new AtomicReference<>(null); 1115 1116 mActivityRule.runOnUiThread(() -> { 1117 final FrameLayout content = new FrameLayout(mActivity); 1118 mSurfaceView = new SurfaceView(mActivity); 1119 mSurfaceView.setZOrderOnTop(true); 1120 content.addView(mSurfaceView, new FrameLayout.LayoutParams(DEFAULT_SURFACE_VIEW_WIDTH, 1121 DEFAULT_SURFACE_VIEW_HEIGHT, Gravity.LEFT | Gravity.TOP)); 1122 mActivity.setContentView(content, new ViewGroup.LayoutParams(DEFAULT_SURFACE_VIEW_WIDTH, 1123 DEFAULT_SURFACE_VIEW_HEIGHT)); 1124 mSurfaceView.getHolder().addCallback(new SurfaceCreatedCallback(surfaceCreated)); 1125 1126 // Create an embedded view. 1127 mVr = new SurfaceControlViewHost(mActivity, mActivity.getDisplay(), 1128 mSurfaceView.getHostToken()); 1129 mEmbeddedView = new Button(mActivity); 1130 mEmbeddedView.setOnClickListener((View v) -> mClicked = true); 1131 mVr.setView(mEmbeddedView, mEmbeddedViewWidth, mEmbeddedViewHeight); 1132 1133 SurfacePackage surfacePackage = mVr.getSurfacePackage(); 1134 surfacePackageRef.set(surfacePackage); 1135 surfacePackageCopyRef.set(new SurfacePackage(surfacePackage)); 1136 1137 // Assign the surface package to the first surface 1138 mSurfaceView.setChildSurfacePackage(surfacePackage); 1139 1140 // Create the second surface view to which we'll assign the surface package copy 1141 SurfaceView secondSurface = new SurfaceView(mActivity); 1142 secondSurfaceRef.set(secondSurface); 1143 1144 mSurfaceView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() { 1145 @Override 1146 public void onViewAttachedToWindow(View v) { 1147 } 1148 1149 @Override 1150 public void onViewDetachedFromWindow(View v) { 1151 viewDetached.countDown(); 1152 } 1153 }); 1154 1155 secondSurface.getHolder().addCallback(new SurfaceCreatedCallback(surface2Created)); 1156 1157 }); 1158 surfaceCreated.await(); 1159 1160 // Add the second surface view and assign it the surface package copy 1161 mActivityRule.runOnUiThread(() -> { 1162 ViewGroup content = (ViewGroup) mSurfaceView.getParent(); 1163 content.addView(secondSurfaceRef.get(), 1164 new FrameLayout.LayoutParams(DEFAULT_SURFACE_VIEW_WIDTH, 1165 DEFAULT_SURFACE_VIEW_HEIGHT, Gravity.TOP | Gravity.LEFT)); 1166 secondSurfaceRef.get().setZOrderOnTop(true); 1167 surfacePackageRef.get().release(); 1168 secondSurfaceRef.get().setChildSurfacePackage(surfacePackageCopyRef.get()); 1169 1170 content.removeView(mSurfaceView); 1171 }); 1172 1173 // Wait for the first surface to be removed 1174 surface2Created.await(); 1175 viewDetached.await(); 1176 1177 mInstrumentation.waitForIdleSync(); 1178 waitUntilEmbeddedViewDrawn(); 1179 1180 // Check if SurfacePackage copy remains valid even though the original package has 1181 // been released and the original surface view removed. 1182 mCtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, mActivityRule, 1183 secondSurfaceRef.get()); 1184 assertTrue(mClicked); 1185 } 1186 1187 @Test testCanReplaceSurfacePackage()1188 public void testCanReplaceSurfacePackage() throws Throwable { 1189 assumeFalse("Automotive splitscreen uses multi-window root tasks. Because of the " 1190 + "lack of a correct display coordinates transform from logical to physical this " 1191 + "test fails on android15", hasAutomotiveSplitscreenMultitaskingFeature()); 1192 1193 // Create a surface view and wait for its surface to be created. 1194 CountDownLatch surfaceCreated = new CountDownLatch(1); 1195 mActivityRule.runOnUiThread(() -> { 1196 final FrameLayout content = new FrameLayout(mActivity); 1197 mSurfaceView = new SurfaceView(mActivity); 1198 mSurfaceView.setZOrderOnTop(true); 1199 content.addView(mSurfaceView, new FrameLayout.LayoutParams( 1200 DEFAULT_SURFACE_VIEW_WIDTH, DEFAULT_SURFACE_VIEW_HEIGHT, 1201 Gravity.LEFT | Gravity.TOP)); 1202 mActivity.setContentView(content, new ViewGroup.LayoutParams( 1203 DEFAULT_SURFACE_VIEW_WIDTH, DEFAULT_SURFACE_VIEW_HEIGHT)); 1204 mSurfaceView.getHolder().addCallback(new SurfaceCreatedCallback(surfaceCreated)); 1205 1206 // Create an embedded view without click handling. 1207 mVr = new SurfaceControlViewHost(mActivity, mActivity.getDisplay(), 1208 mSurfaceView.getHostToken()); 1209 mEmbeddedView = new Button(mActivity); 1210 mVr.setView(mEmbeddedView, mEmbeddedViewWidth, mEmbeddedViewHeight); 1211 mSurfaceView.setChildSurfacePackage(mVr.getSurfacePackage()); 1212 }); 1213 surfaceCreated.await(); 1214 mInstrumentation.waitForIdleSync(); 1215 waitUntilEmbeddedViewDrawn(); 1216 1217 CountDownLatch hostReady = new CountDownLatch(1); 1218 // Create a second surface view and wait for its surface to be created. 1219 mActivityRule.runOnUiThread(() -> { 1220 // Create an embedded view. 1221 mVr = new SurfaceControlViewHost(mActivity, mActivity.getDisplay(), 1222 mSurfaceView.getHostToken()); 1223 mEmbeddedView = new Button(mActivity); 1224 mEmbeddedView.setOnClickListener((View v) -> mClicked = true); 1225 mVr.setView(mEmbeddedView, mEmbeddedViewWidth, mEmbeddedViewHeight); 1226 hostReady.countDown(); 1227 mSurfaceView.setChildSurfacePackage(mVr.getSurfacePackage()); 1228 }); 1229 hostReady.await(); 1230 mInstrumentation.waitForIdleSync(); 1231 waitUntilEmbeddedViewDrawn(); 1232 1233 1234 // Check to see if the click went through - this only would happen if the surface package 1235 // was replaced 1236 globalTapOnWindowCenter(mEmbeddedView::getWindowToken); 1237 PollingCheck.waitFor(() -> mClicked); 1238 } 1239 1240 class MotionRecordingSurfaceView extends SurfaceView { 1241 boolean mGotEvent = false; 1242 MotionRecordingSurfaceView(Context c)1243 MotionRecordingSurfaceView(Context c) { 1244 super(c); 1245 } 1246 onTouchEvent(MotionEvent e)1247 public boolean onTouchEvent(MotionEvent e) { 1248 super.onTouchEvent(e); 1249 synchronized (this) { 1250 mGotEvent = true; 1251 } 1252 return true; 1253 } 1254 gotEvent()1255 boolean gotEvent() { 1256 synchronized (this) { 1257 return mGotEvent; 1258 } 1259 } 1260 reset()1261 void reset() { 1262 synchronized (this) { 1263 mGotEvent = false; 1264 } 1265 } 1266 } 1267 1268 static class TouchPunchingView extends View { TouchPunchingView(Context context)1269 TouchPunchingView(Context context) { 1270 super(context); 1271 } 1272 punchHoleInTouchableRegion()1273 void punchHoleInTouchableRegion() { 1274 getRootSurfaceControl().setTouchableRegion(new Region()); 1275 } 1276 } 1277 addMotionRecordingSurfaceView(int width, int height)1278 private void addMotionRecordingSurfaceView(int width, int height) throws Throwable { 1279 mActivityRule.runOnUiThread(() -> { 1280 final FrameLayout content = new FrameLayout(mActivity); 1281 mSurfaceView = new MotionRecordingSurfaceView(mActivity); 1282 mSurfaceView.setZOrderOnTop(true); 1283 content.addView(mSurfaceView, new FrameLayout.LayoutParams( 1284 width, height, Gravity.LEFT | Gravity.TOP)); 1285 mActivity.setContentView(content, new ViewGroup.LayoutParams(width, height)); 1286 mSurfaceView.getHolder().addCallback(this); 1287 }); 1288 } 1289 1290 class ForwardingSurfaceView extends SurfaceView { 1291 SurfaceControlViewHost.SurfacePackage mPackage; 1292 ForwardingSurfaceView(Context c)1293 ForwardingSurfaceView(Context c) { 1294 super(c); 1295 } 1296 1297 @Override onDetachedFromWindow()1298 protected void onDetachedFromWindow() { 1299 if (mPackage == null) { 1300 return; 1301 } 1302 mPackage.notifyDetachedFromWindow(); 1303 } 1304 1305 @Override onConfigurationChanged(Configuration newConfig)1306 protected void onConfigurationChanged(Configuration newConfig) { 1307 super.onConfigurationChanged(newConfig); 1308 mPackage.notifyConfigurationChanged(newConfig); 1309 } 1310 1311 @Override setChildSurfacePackage(SurfaceControlViewHost.SurfacePackage p)1312 public void setChildSurfacePackage(SurfaceControlViewHost.SurfacePackage p) { 1313 super.setChildSurfacePackage(p); 1314 mPackage = p; 1315 } 1316 } 1317 1318 class DetachRecordingView extends View { 1319 boolean mDetached = false; 1320 DetachRecordingView(Context c)1321 DetachRecordingView(Context c) { 1322 super(c); 1323 } 1324 1325 @Override onDetachedFromWindow()1326 protected void onDetachedFromWindow() { 1327 mDetached = true; 1328 } 1329 } 1330 1331 class ConfigRecordingView extends View { 1332 CountDownLatch mLatch; 1333 ConfigRecordingView(Context c, CountDownLatch latch)1334 ConfigRecordingView(Context c, CountDownLatch latch) { 1335 super(c); 1336 mLatch = latch; 1337 } 1338 1339 @Override onConfigurationChanged(Configuration newConfig)1340 protected void onConfigurationChanged(Configuration newConfig) { 1341 mLatch.countDown(); 1342 } 1343 } 1344 addForwardingSurfaceView(int width, int height)1345 private void addForwardingSurfaceView(int width, int height) throws Throwable { 1346 mActivityRule.runOnUiThread(() -> { 1347 final FrameLayout content = new FrameLayout(mActivity); 1348 mSurfaceView = new ForwardingSurfaceView(mActivity); 1349 mSurfaceView.setZOrderOnTop(true); 1350 content.addView(mSurfaceView, new FrameLayout.LayoutParams( 1351 width, height, Gravity.LEFT | Gravity.TOP)); 1352 mViewParent = content; 1353 mActivity.setContentView(content, new ViewGroup.LayoutParams(width, height)); 1354 mSurfaceView.getHolder().addCallback(this); 1355 }); 1356 } 1357 1358 @Test testEmbeddedViewCanSetTouchableRegion()1359 public void testEmbeddedViewCanSetTouchableRegion() throws Throwable { 1360 TouchPunchingView tpv; 1361 mEmbeddedView = tpv = new TouchPunchingView(mActivity); 1362 1363 addMotionRecordingSurfaceView(DEFAULT_SURFACE_VIEW_WIDTH, DEFAULT_SURFACE_VIEW_HEIGHT); 1364 mInstrumentation.waitForIdleSync(); 1365 waitUntilEmbeddedViewDrawn(); 1366 mCtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, mActivityRule, mSurfaceView); 1367 mInstrumentation.waitForIdleSync(); 1368 1369 MotionRecordingSurfaceView mrsv = (MotionRecordingSurfaceView) mSurfaceView; 1370 assertFalse(mrsv.gotEvent()); 1371 mActivityRule.runOnUiThread(() -> { 1372 tpv.punchHoleInTouchableRegion(); 1373 }); 1374 mInstrumentation.waitForIdleSync(); 1375 mCtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, mActivityRule, mSurfaceView); 1376 mInstrumentation.waitForIdleSync(); 1377 assertTrue(mrsv.gotEvent()); 1378 } 1379 1380 @Test forwardDetachedFromWindow()1381 public void forwardDetachedFromWindow() throws Throwable { 1382 DetachRecordingView drv = new DetachRecordingView(mActivity); 1383 mEmbeddedView = drv; 1384 addForwardingSurfaceView(100, 100); 1385 mInstrumentation.waitForIdleSync(); 1386 waitUntilEmbeddedViewDrawn(); 1387 1388 assertFalse(drv.mDetached); 1389 mActivityRule.runOnUiThread(() -> { 1390 mViewParent.removeView(mSurfaceView); 1391 }); 1392 mInstrumentation.waitForIdleSync(); 1393 assertTrue(drv.mDetached); 1394 } 1395 1396 @Test forwardConfigurationChange()1397 public void forwardConfigurationChange() throws Throwable { 1398 if (!supportsOrientationRequest()) { 1399 return; 1400 } 1401 final CountDownLatch embeddedConfigLatch = new CountDownLatch(1); 1402 ConfigRecordingView crv = new ConfigRecordingView(mActivity, embeddedConfigLatch); 1403 mEmbeddedView = crv; 1404 addForwardingSurfaceView(100, 100); 1405 mInstrumentation.waitForIdleSync(); 1406 waitUntilEmbeddedViewDrawn(); 1407 mActivityRule.runOnUiThread(() -> { 1408 int orientation = mActivity.getResources().getConfiguration().orientation; 1409 if (orientation == Configuration.ORIENTATION_LANDSCAPE) { 1410 orientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; 1411 } else { 1412 orientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; 1413 } 1414 mActivity.setRequestedOrientation(orientation); 1415 }); 1416 embeddedConfigLatch.await(3, TimeUnit.SECONDS); 1417 mInstrumentation.waitForIdleSync(); 1418 mActivityRule.runOnUiThread(() -> { 1419 assertEquals(mEmbeddedView.getResources().getConfiguration().orientation, 1420 mSurfaceView.getResources().getConfiguration().orientation); 1421 }); 1422 } 1423 1424 @Test testEmbeddedViewReceivesInputOnBottom()1425 public void testEmbeddedViewReceivesInputOnBottom() throws Throwable { 1426 assumeFalse("Automotive splitscreen uses multi-window root tasks. Because of the " 1427 + "lack of a correct display coordinates transform from logical to physical this " 1428 + "test fails on android15", hasAutomotiveSplitscreenMultitaskingFeature()); 1429 1430 mEmbeddedView = new Button(mActivity); 1431 mEmbeddedView.setOnClickListener((View v) -> { 1432 mClicked = true; 1433 }); 1434 1435 addSurfaceView(DEFAULT_SURFACE_VIEW_WIDTH, DEFAULT_SURFACE_VIEW_HEIGHT, false); 1436 mInstrumentation.waitForIdleSync(); 1437 waitUntilEmbeddedViewDrawn(); 1438 1439 // We should receive no input until we punch a hole 1440 globalTapOnViewCenter(mSurfaceView); 1441 mInstrumentation.waitForIdleSync(); 1442 assertFalse(mClicked); 1443 1444 String originalRegion = getTouchableRegionFromDump(); 1445 1446 mActivityRule.runOnUiThread(() -> { 1447 mSurfaceView.getRootSurfaceControl().setTouchableRegion(new Region(0, 0, 1, 1)); 1448 }); 1449 mInstrumentation.waitForIdleSync(); 1450 // ViewRootImpl sends the touchable region to the WM via a one-way call, which is great 1451 // for performance...however not so good for testability, we have no way 1452 // to verify it has arrived! It doesn't make so much sense to bloat 1453 // the system image size with a completion callback for just this one test 1454 // so we settle for some inelegant spin-polling on the WM dump. 1455 // In the future when we revisit WM/Client interface and transactionalize 1456 // everything, we should have a standard way to wait on the completion of async 1457 // operations 1458 waitForTouchableRegionChanged(originalRegion); 1459 waitForStableWindowGeometry(Duration.ofSeconds(WAIT_TIMEOUT_S)); 1460 1461 globalTapOnViewCenter(mSurfaceView); 1462 PollingCheck.waitFor(() -> mClicked); 1463 } 1464 getService()1465 private ICrossProcessSurfaceControlViewHostTestService getService() throws Exception { 1466 return mConnections.computeIfAbsent("android.server.wm.scvh", this::connect) 1467 .get(TIMEOUT_MS); 1468 } 1469 repackage(String packageName, ComponentName baseComponent)1470 private static ComponentName repackage(String packageName, ComponentName baseComponent) { 1471 return new ComponentName(packageName, baseComponent.getClassName()); 1472 } 1473 connect( String packageName)1474 private FutureConnection<ICrossProcessSurfaceControlViewHostTestService> connect( 1475 String packageName) { 1476 FutureConnection<ICrossProcessSurfaceControlViewHostTestService> connection = 1477 new FutureConnection<>( 1478 ICrossProcessSurfaceControlViewHostTestService.Stub::asInterface); 1479 Intent intent = new Intent(); 1480 intent.setComponent(repackage(packageName, 1481 Components.CrossProcessSurfaceControlViewHostTestService.COMPONENT)); 1482 assertTrue(mInstrumentation.getContext().bindService(intent, 1483 connection, Context.BIND_AUTO_CREATE)); 1484 return connection; 1485 } 1486 1487 @Test testHostInputTokenAllowsObscuredTouches()1488 public void testHostInputTokenAllowsObscuredTouches() throws Throwable { 1489 assumeFalse("XR device uses a custom window occlusion check tested via CTS Verifier.", 1490 FeatureUtil.isXrHeadset()); 1491 assumeFalse("Automotive splitscreen uses multi-window root tasks. Because of the " 1492 + "lack of a correct display coordinates transform from logical to physical this " 1493 + "test fails on android15", hasAutomotiveSplitscreenMultitaskingFeature()); 1494 1495 mTestService = getService(); 1496 assertTrue(mTestService != null); 1497 1498 addSurfaceView(DEFAULT_SURFACE_VIEW_WIDTH, DEFAULT_SURFACE_VIEW_HEIGHT, false); 1499 assertTrue("Failed to wait for SV to get created", 1500 mSvCreatedLatch.await(5, TimeUnit.SECONDS)); 1501 mActivityRule.runOnUiThread(() -> { 1502 mSurfaceView.getRootSurfaceControl().setTouchableRegion(new Region()); 1503 }); 1504 // TODO(b/279051608): Add touchable regions in WindowInfo test so we can make sure the 1505 // touchable regions for the host have been set before proceeding. 1506 assertTrue("Failed to wait for host window to be visible", 1507 waitForWindowVisible(mSurfaceView)); 1508 assertTrue("Failed to wait for embedded window to be visible", 1509 waitForWindowVisible(mTestService.getWindowToken(), 1510 mDisplayId)); 1511 1512 waitForStableWindowGeometry(Duration.ofSeconds(WAIT_TIMEOUT_S)); 1513 globalTapOnViewCenter(mSurfaceView); 1514 1515 MotionEvent motionEvent = mTestService.getMotionEvent(); 1516 assertThat(motionEvent, allOf(withMotionAction(MotionEvent.ACTION_DOWN), 1517 withFlags(MotionEvent.FLAG_WINDOW_IS_OBSCURED))); 1518 motionEvent = mTestService.getMotionEvent(); 1519 assertThat(motionEvent, allOf(withMotionAction(MotionEvent.ACTION_UP), 1520 withFlags(MotionEvent.FLAG_WINDOW_IS_OBSCURED))); 1521 1522 } 1523 1524 @Test testNoHostInputTokenDisallowsObscuredTouches()1525 public void testNoHostInputTokenDisallowsObscuredTouches() throws Throwable { 1526 // TODO(b/398861504): Ensure this test case is covered by the CTS Verifier. 1527 assumeFalse("XR device uses a custom window occlusion check tested via CTS Verifier.", 1528 FeatureUtil.isXrHeadset()); 1529 assumeFalse("Automotive splitscreen uses multi-window root tasks. Because of the " 1530 + "lack of a correct display coordinates transform from logical to physical this " 1531 + "test fails on android15", hasAutomotiveSplitscreenMultitaskingFeature()); 1532 1533 mTestService = getService(); 1534 mRemoteSurfacePackage = mTestService.getSurfacePackage(new Binder()); 1535 assertTrue(mRemoteSurfacePackage != null); 1536 1537 addSurfaceView(DEFAULT_SURFACE_VIEW_WIDTH, DEFAULT_SURFACE_VIEW_HEIGHT, false); 1538 assertTrue("Failed to wait for SV to get created", 1539 mSvCreatedLatch.await(5, TimeUnit.SECONDS)); 1540 mActivityRule.runOnUiThread(() -> { 1541 mSurfaceView.getRootSurfaceControl().setTouchableRegion(new Region()); 1542 }); 1543 // TODO(b/279051608): Add touchable regions in WindowInfo test so we can make sure the 1544 // touchable regions for the host have been set before proceeding. 1545 assertTrue("Failed to wait for host window to be visible", 1546 waitForWindowVisible(mSurfaceView)); 1547 assertTrue("Failed to wait for embedded window to be visible", 1548 waitForWindowVisible(mTestService.getWindowToken(), 1549 mDisplayId)); 1550 1551 globalTapOnViewCenter(mSurfaceView); 1552 1553 assertNull(mTestService.getMotionEvent()); 1554 } 1555 1556 @Test testPopupWindowReceivesInput()1557 public void testPopupWindowReceivesInput() throws Throwable { 1558 mEmbeddedView = new Button(mActivity); 1559 mEmbeddedView.setOnClickListener((View v) -> { 1560 mClicked = true; 1561 }); 1562 addSurfaceView(DEFAULT_SURFACE_VIEW_WIDTH, DEFAULT_SURFACE_VIEW_HEIGHT); 1563 mInstrumentation.waitForIdleSync(); 1564 waitUntilEmbeddedViewDrawn(); 1565 1566 mActivityRule.runOnUiThread(() -> { 1567 PopupWindow pw = new PopupWindow(); 1568 mPopupWindow = pw; 1569 Button popupButton = new Button(mActivity); 1570 popupButton.setOnClickListener((View v) -> { 1571 mPopupClicked = true; 1572 }); 1573 pw.setWidth(DEFAULT_SURFACE_VIEW_WIDTH); 1574 pw.setHeight(DEFAULT_SURFACE_VIEW_HEIGHT); 1575 pw.setContentView(popupButton); 1576 pw.showAsDropDown(mEmbeddedView); 1577 }); 1578 mInstrumentation.waitForIdleSync(); 1579 1580 mCtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, mActivityRule, mSurfaceView); 1581 assertTrue(mPopupClicked); 1582 assertFalse(mClicked); 1583 1584 mActivityRule.runOnUiThread(() -> { 1585 mPopupWindow.dismiss(); 1586 }); 1587 mInstrumentation.waitForIdleSync(); 1588 1589 mCtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, mActivityRule, mSurfaceView); 1590 mInstrumentation.waitForIdleSync(); 1591 assertTrue(mClicked); 1592 } 1593 1594 @Test testPopupWindowPosition()1595 public void testPopupWindowPosition() throws Throwable { 1596 mEmbeddedView = new View(mActivity); 1597 addSurfaceView(DEFAULT_SURFACE_VIEW_WIDTH, DEFAULT_SURFACE_VIEW_HEIGHT); 1598 mInstrumentation.waitForIdleSync(); 1599 waitUntilEmbeddedViewDrawn(); 1600 1601 mActivityRule.runOnUiThread(() -> { 1602 View popupContent = new View(mActivity); 1603 popupContent.setBackgroundColor(Color.BLUE); 1604 1605 mPopupWindow = new PopupWindow(); 1606 mPopupWindow.setWidth(50); 1607 mPopupWindow.setHeight(50); 1608 mPopupWindow.setContentView(popupContent); 1609 mPopupWindow.showAtLocation(mEmbeddedView, Gravity.BOTTOM | Gravity.RIGHT, 0, 0); 1610 }); 1611 1612 Predicate<List<WindowInfo>> hasExpectedFrame = windowInfos -> { 1613 if (mPopupWindow == null) { 1614 return false; 1615 } 1616 1617 IBinder parentWindowToken = mEmbeddedView.getWindowToken(); 1618 IBinder popupWindowToken = mPopupWindow.getContentView().getWindowToken(); 1619 if (parentWindowToken == null || popupWindowToken == null) { 1620 return false; 1621 } 1622 1623 Rect parentBounds = null; 1624 Rect popupBounds = null; 1625 for (WindowInfo windowInfo : windowInfos) { 1626 if (!windowInfo.isVisible) { 1627 continue; 1628 } 1629 if (windowInfo.windowToken == parentWindowToken) { 1630 parentBounds = windowInfo.bounds; 1631 } else if (windowInfo.windowToken == popupWindowToken) { 1632 popupBounds = windowInfo.bounds; 1633 } 1634 } 1635 1636 if (parentBounds == null) { 1637 return false; 1638 } 1639 1640 var expectedBounds = new Rect(parentBounds.left + 50, parentBounds.top + 50, 1641 parentBounds.left + 100, parentBounds.top + 100); 1642 return expectedBounds.equals(popupBounds); 1643 }; 1644 assertTrue(waitForWindowInfos(hasExpectedFrame, Duration.ofSeconds(5))); 1645 } 1646 1647 @Test testFloatingWindowWrapContent()1648 public void testFloatingWindowWrapContent() throws Throwable { 1649 mEmbeddedView = new View(mActivity); 1650 addSurfaceView(DEFAULT_SURFACE_VIEW_WIDTH, DEFAULT_SURFACE_VIEW_HEIGHT); 1651 mInstrumentation.waitForIdleSync(); 1652 waitUntilEmbeddedViewDrawn(); 1653 1654 View popupContent = new View(mActivity); 1655 popupContent.setBackgroundColor(Color.BLUE); 1656 popupContent.setLayoutParams(new ViewGroup.LayoutParams(50, 50)); 1657 1658 FrameLayout popupView = new FrameLayout(mActivity); 1659 popupView.addView(popupContent); 1660 1661 WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(); 1662 layoutParams.setTitle("FloatingWindow"); 1663 layoutParams.gravity = Gravity.TOP | Gravity.LEFT; 1664 layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT; 1665 layoutParams.width = ViewGroup.LayoutParams.WRAP_CONTENT; 1666 layoutParams.token = mEmbeddedView.getWindowToken(); 1667 1668 mActivityRule.runOnUiThread(() -> { 1669 WindowManager windowManager = mActivity.getSystemService(WindowManager.class); 1670 windowManager.addView(popupView, layoutParams); 1671 }); 1672 1673 Predicate<WindowInfo> hasExpectedDimensions = 1674 windowInfo -> windowInfo.bounds.width() == 50 && windowInfo.bounds.height() == 50; 1675 // We pass popupView::getWindowToken as a java.util.function.Supplier 1676 // because the popupView is initially unattached and doesn't have a 1677 // window token. The supplier is called each time the predicate is 1678 // tested, eventually returning the window token. 1679 assertTrue(waitForWindowInfo(hasExpectedDimensions, Duration.ofSeconds(5), 1680 popupView::getWindowToken, mDisplayId)); 1681 } 1682 1683 @Test testFloatingWindowMatchParent()1684 public void testFloatingWindowMatchParent() throws Throwable { 1685 mEmbeddedView = new View(mActivity); 1686 mEmbeddedViewWidth = 50; 1687 mEmbeddedViewHeight = 50; 1688 addSurfaceView(100, 100); 1689 mInstrumentation.waitForIdleSync(); 1690 1691 View popupView = new FrameLayout(mActivity); 1692 popupView.setBackgroundColor(Color.BLUE); 1693 1694 WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(); 1695 layoutParams.setTitle("FloatingWindow"); 1696 layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT; 1697 layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT; 1698 layoutParams.token = mEmbeddedView.getWindowToken(); 1699 1700 mActivityRule.runOnUiThread(() -> { 1701 WindowManager windowManager = mActivity.getSystemService(WindowManager.class); 1702 windowManager.addView(popupView, layoutParams); 1703 }); 1704 1705 Predicate<WindowInfo> hasExpectedDimensions = 1706 windowInfo -> windowInfo.bounds.width() == 50 && windowInfo.bounds.height() == 50; 1707 assertTrue(waitForWindowInfo(hasExpectedDimensions, Duration.ofSeconds(5), 1708 popupView::getWindowToken, mDisplayId)); 1709 } 1710 1711 class TouchTransferringView extends View { 1712 boolean mExpectsFirstMotion = true; 1713 boolean mExpectsCancel = false; 1714 boolean mGotCancel = false; 1715 // True if the test should use the WindowManager#transferTouchGesture API. 1716 private final boolean mUseTransferTouchGestureApi; 1717 TouchTransferringView(Context c, boolean useTransferTouchGestureApi)1718 TouchTransferringView(Context c, boolean useTransferTouchGestureApi) { 1719 super(c); 1720 mUseTransferTouchGestureApi = useTransferTouchGestureApi; 1721 } 1722 1723 @Override onTouchEvent(MotionEvent ev)1724 public boolean onTouchEvent(MotionEvent ev) { 1725 int action = ev.getAction(); 1726 synchronized (this) { 1727 if (mExpectsFirstMotion) { 1728 assertEquals(action, MotionEvent.ACTION_DOWN); 1729 if (mUseTransferTouchGestureApi) { 1730 assertTrue(mWm.transferTouchGesture( 1731 mVr.getSurfacePackage().getInputTransferToken(), 1732 mSurfaceView.getRootSurfaceControl().getInputTransferToken())); 1733 } else { 1734 assertTrue(mVr.transferTouchGestureToHost()); 1735 } 1736 mExpectsFirstMotion = false; 1737 mExpectsCancel = true; 1738 } else if (mExpectsCancel) { 1739 assertEquals(action, MotionEvent.ACTION_CANCEL); 1740 mExpectsCancel = false; 1741 mGotCancel = true; 1742 } 1743 this.notifyAll(); 1744 } 1745 return true; 1746 } 1747 waitForEmbeddedTouch()1748 void waitForEmbeddedTouch() { 1749 synchronized (this) { 1750 if (!mExpectsFirstMotion) { 1751 assertTrue(mExpectsCancel || mGotCancel); 1752 return; 1753 } 1754 try { 1755 this.wait(); 1756 } catch (Exception e) { 1757 } 1758 assertFalse(mExpectsFirstMotion); 1759 } 1760 } 1761 waitForCancel()1762 void waitForCancel() { 1763 synchronized (this) { 1764 if (!mExpectsCancel) { 1765 return; 1766 } 1767 try { 1768 this.wait(); 1769 } catch (Exception e) { 1770 } 1771 assertTrue(mGotCancel); 1772 } 1773 } 1774 } 1775 testEmbeddedWindowCanTransferTouchGestureToHost(boolean useTransferTouchGestureApi)1776 private void testEmbeddedWindowCanTransferTouchGestureToHost(boolean useTransferTouchGestureApi) 1777 throws Throwable { 1778 // Inside the embedded view hierarchy, we set up a view that transfers touch 1779 // to the host upon receiving a touch event 1780 TouchTransferringView ttv = new TouchTransferringView(mActivity, 1781 useTransferTouchGestureApi); 1782 mEmbeddedView = ttv; 1783 addSurfaceView(DEFAULT_SURFACE_VIEW_WIDTH, DEFAULT_SURFACE_VIEW_HEIGHT); 1784 mInstrumentation.waitForIdleSync(); 1785 waitUntilEmbeddedViewDrawn(); 1786 // On the host SurfaceView, we set a motion consumer which expects to receive one event. 1787 mHostGotEvent = false; 1788 mSurfaceViewMotionConsumer = (ev) -> { 1789 synchronized (this) { 1790 mHostGotEvent = true; 1791 this.notifyAll(); 1792 } 1793 }; 1794 1795 // Prepare to inject an event offset one pixel from the top of the SurfaceViews location 1796 // on-screen. 1797 final int[] viewOnScreenXY = new int[2]; 1798 mSurfaceView.getLocationOnScreen(viewOnScreenXY); 1799 final int injectedX = viewOnScreenXY[0] + 1; 1800 final int injectedY = viewOnScreenXY[1] + 1; 1801 final UiAutomation uiAutomation = mInstrumentation.getUiAutomation(); 1802 long downTime = SystemClock.uptimeMillis(); 1803 1804 // We inject a down event 1805 mCtsTouchUtils.injectDownEvent(mInstrumentation, downTime, injectedX, injectedY, null); 1806 1807 1808 // And this down event should arrive on the embedded view, which should transfer the touch 1809 // focus 1810 ttv.waitForEmbeddedTouch(); 1811 ttv.waitForCancel(); 1812 1813 downTime = SystemClock.uptimeMillis(); 1814 // Now we inject an up event 1815 mCtsTouchUtils.injectUpEvent(mInstrumentation, downTime, false, injectedX, injectedY, null); 1816 // This should arrive on the host now, since we have transferred the touch focus 1817 synchronized (this) { 1818 if (!mHostGotEvent) { 1819 try { 1820 this.wait(); 1821 } catch (Exception e) { 1822 } 1823 } 1824 } 1825 assertTrue(mHostGotEvent); 1826 } 1827 1828 @Test testEmbeddedWindowCanTransferTouchGestureToHost_transferTouchGestureToHost()1829 public void testEmbeddedWindowCanTransferTouchGestureToHost_transferTouchGestureToHost() 1830 throws Throwable { 1831 testEmbeddedWindowCanTransferTouchGestureToHost(false); 1832 } 1833 1834 @RequiresFlagsEnabled(Flags.FLAG_SURFACE_CONTROL_INPUT_RECEIVER) 1835 @Test testEmbeddedWindowCanTransferTouchGestureToHost_transferTouchGesture()1836 public void testEmbeddedWindowCanTransferTouchGestureToHost_transferTouchGesture() 1837 throws Throwable { 1838 testEmbeddedWindowCanTransferTouchGestureToHost(true); 1839 } 1840 1841 @Test testEmbeddedCannotStealTouchGestureFromHost()1842 public void testEmbeddedCannotStealTouchGestureFromHost() throws Throwable { 1843 mTestService = getService(); 1844 assertNotNull(mTestService); 1845 1846 addSurfaceView(DEFAULT_SURFACE_VIEW_WIDTH, DEFAULT_SURFACE_VIEW_HEIGHT); 1847 mSvCreatedLatch.await(5, TimeUnit.SECONDS); 1848 1849 InputTransferToken hostInputTransferToken = Objects.requireNonNull( 1850 mSurfaceView.getRootSurfaceControl()).getInputTransferToken(); 1851 // Ask the embedded process to request gesture transfer from the host and then 1852 // verify that the call throws a security exception. We need to do the assertion 1853 // in the test process to handle the assertion correctly. 1854 assertTrue(mTestService.requestTouchGestureTransferFromHostThrows(hostInputTransferToken)); 1855 } 1856 1857 @Test testHostCannotStealTouchGestureFromEmbedded()1858 public void testHostCannotStealTouchGestureFromEmbedded() throws Throwable { 1859 mTestService = getService(); 1860 assertNotNull(mTestService); 1861 1862 addSurfaceView(DEFAULT_SURFACE_VIEW_WIDTH, DEFAULT_SURFACE_VIEW_HEIGHT); 1863 mSvCreatedLatch.await(5, TimeUnit.SECONDS); 1864 1865 InputTransferToken hostInputTransferToken = Objects.requireNonNull( 1866 mSurfaceView.getRootSurfaceControl()).getInputTransferToken(); 1867 InputTransferToken surfacePackageInputTransferToken = 1868 mRemoteSurfacePackage.getInputTransferToken(); 1869 WindowManager wm = mActivity.getSystemService(WindowManager.class); 1870 assertThrows(SecurityException.class, 1871 () -> wm.transferTouchGesture(surfacePackageInputTransferToken, 1872 hostInputTransferToken)); 1873 } 1874 1875 @Test testKeepScreenOn()1876 public void testKeepScreenOn() throws Throwable { 1877 mEmbeddedView = new Button(mActivity); 1878 addSurfaceView(DEFAULT_SURFACE_VIEW_WIDTH, DEFAULT_SURFACE_VIEW_HEIGHT); 1879 mInstrumentation.waitForIdleSync(); 1880 waitUntilEmbeddedViewDrawn(); 1881 1882 mWmState.computeState(); 1883 WindowManagerState.WindowState windowState = mWmState.getWindowState(TEST_ACTIVITY); 1884 // Assert the KEEP_SCREEN_ON flag is not set on the main window yet. 1885 assertNotEquals(FLAG_KEEP_SCREEN_ON, (windowState.getFlags() & FLAG_KEEP_SCREEN_ON)); 1886 1887 final CountDownLatch keepScreenOnSetLatch = new CountDownLatch(2); 1888 mActivityRule.runOnUiThread(() -> { 1889 mEmbeddedView.setKeepScreenOn(true); 1890 mEmbeddedView.getViewTreeObserver().addOnDrawListener(keepScreenOnSetLatch::countDown); 1891 mSurfaceView.getViewTreeObserver().addOnDrawListener(keepScreenOnSetLatch::countDown); 1892 }); 1893 keepScreenOnSetLatch.await(WAIT_TIMEOUT_S, TimeUnit.SECONDS); 1894 1895 mWmState.computeState(); 1896 windowState = mWmState.getWindowState(TEST_ACTIVITY); 1897 // Assert the KEEP_SCREEN_ON flag is now set on the main window. 1898 assertEquals(FLAG_KEEP_SCREEN_ON, (windowState.getFlags() & FLAG_KEEP_SCREEN_ON)); 1899 1900 final CountDownLatch keepScreenOnUnsetLatch = new CountDownLatch(2); 1901 mActivityRule.runOnUiThread(() -> { 1902 mEmbeddedView.setKeepScreenOn(false); 1903 mEmbeddedView.getViewTreeObserver().addOnDrawListener( 1904 keepScreenOnUnsetLatch::countDown); 1905 mSurfaceView.getViewTreeObserver().addOnDrawListener(keepScreenOnUnsetLatch::countDown); 1906 }); 1907 keepScreenOnUnsetLatch.await(WAIT_TIMEOUT_S, TimeUnit.SECONDS); 1908 1909 mWmState.computeState(); 1910 windowState = mWmState.getWindowState(TEST_ACTIVITY); 1911 // Assert the KEEP_SCREEN_ON flag is removed from the main window. 1912 assertNotEquals(FLAG_KEEP_SCREEN_ON, (windowState.getFlags() & FLAG_KEEP_SCREEN_ON)); 1913 } 1914 1915 @Test testKeepScreenOnCrossProcess()1916 public void testKeepScreenOnCrossProcess() throws Throwable { 1917 mTestService = getService(); 1918 assertNotNull(mTestService); 1919 1920 addSurfaceView(DEFAULT_SURFACE_VIEW_WIDTH, DEFAULT_SURFACE_VIEW_HEIGHT); 1921 mSvCreatedLatch.await(5, TimeUnit.SECONDS); 1922 1923 mWmState.computeState(); 1924 WindowManagerState.WindowState windowState = mWmState.getWindowState(TEST_ACTIVITY); 1925 // Assert the KEEP_SCREEN_ON flag is not set on the main window yet. 1926 assertNotEquals(FLAG_KEEP_SCREEN_ON, (windowState.getFlags() & FLAG_KEEP_SCREEN_ON)); 1927 1928 final CountDownLatch keepScreenOnSetLatch = new CountDownLatch(1); 1929 mActivityRule.runOnUiThread(() -> mSurfaceView.getViewTreeObserver().addOnDrawListener( 1930 keepScreenOnSetLatch::countDown)); 1931 mTestService.setKeepScreenOnFlag(true); 1932 keepScreenOnSetLatch.await(WAIT_TIMEOUT_S, TimeUnit.SECONDS); 1933 1934 mWmState.computeState(); 1935 windowState = mWmState.getWindowState(TEST_ACTIVITY); 1936 // Assert the KEEP_SCREEN_ON flag is now set on the main window. 1937 assertEquals(FLAG_KEEP_SCREEN_ON, (windowState.getFlags() & FLAG_KEEP_SCREEN_ON)); 1938 1939 final CountDownLatch keepScreenOnUnsetLatch = new CountDownLatch(1); 1940 mActivityRule.runOnUiThread(() -> mSurfaceView.getViewTreeObserver().addOnDrawListener( 1941 keepScreenOnUnsetLatch::countDown)); 1942 mTestService.setKeepScreenOnFlag(false); 1943 keepScreenOnUnsetLatch.await(WAIT_TIMEOUT_S, TimeUnit.SECONDS); 1944 1945 mWmState.computeState(); 1946 windowState = mWmState.getWindowState(TEST_ACTIVITY); 1947 // Assert the KEEP_SCREEN_ON flag is removed from the main window. 1948 assertNotEquals(FLAG_KEEP_SCREEN_ON, (windowState.getFlags() & FLAG_KEEP_SCREEN_ON)); 1949 } 1950 1951 @Test testKeepScreenOnAfterDetachSCVH()1952 public void testKeepScreenOnAfterDetachSCVH() throws Throwable { 1953 mEmbeddedView = new Button(mActivity); 1954 mEmbeddedView.setKeepScreenOn(true); 1955 addSurfaceView(DEFAULT_SURFACE_VIEW_WIDTH, DEFAULT_SURFACE_VIEW_HEIGHT); 1956 mInstrumentation.waitForIdleSync(); 1957 waitUntilEmbeddedViewDrawn(); 1958 1959 mWmState.computeState(); 1960 WindowManagerState.WindowState windowState = mWmState.getWindowState(TEST_ACTIVITY); 1961 // Assert the KEEP_SCREEN_ON flag is not set on the main window yet. 1962 assertEquals(FLAG_KEEP_SCREEN_ON, (windowState.getFlags() & FLAG_KEEP_SCREEN_ON)); 1963 1964 // Remove the SurfaceView from main window. 1965 final CountDownLatch countDownLatch = new CountDownLatch(1); 1966 mActivityRule.runOnUiThread(() -> { 1967 mViewParent.removeView(mSurfaceView); 1968 mSurfaceView.getViewTreeObserver().addOnDrawListener(countDownLatch::countDown); 1969 }); 1970 countDownLatch.await(WAIT_TIMEOUT_S, TimeUnit.SECONDS); 1971 1972 mWmState.computeState(); 1973 windowState = mWmState.getWindowState(TEST_ACTIVITY); 1974 // Assert the KEEP_SCREEN_ON flag is removed from the main window. 1975 assertNotEquals(FLAG_KEEP_SCREEN_ON, (windowState.getFlags() & FLAG_KEEP_SCREEN_ON)); 1976 } 1977 1978 @RequiresFlagsEnabled(Flags.FLAG_SURFACE_CONTROL_INPUT_RECEIVER) 1979 @Test testTransferHostTouchGestureToEmbedded()1980 public void testTransferHostTouchGestureToEmbedded() throws Throwable { 1981 mEmbeddedView = new Button(mActivity); 1982 addSurfaceView(DEFAULT_SURFACE_VIEW_WIDTH, DEFAULT_SURFACE_VIEW_HEIGHT, false /* onTop */); 1983 waitUntilEmbeddedViewDrawn(); 1984 1985 CountDownLatch receivedTouches = new CountDownLatch(1); 1986 boolean[] hostGotEvent = new boolean[1]; 1987 boolean[] embeddedGotEvent = new boolean[1]; 1988 mSurfaceViewMotionConsumer = (ev) -> { 1989 if (hostGotEvent[0]) { 1990 return; 1991 } 1992 hostGotEvent[0] = true; 1993 mActivity.getWindowManager().transferTouchGesture( 1994 mSurfaceView.getRootSurfaceControl().getInputTransferToken(), 1995 mVr.getSurfacePackage().getInputTransferToken()); 1996 receivedTouches.countDown(); 1997 }; 1998 1999 mEmbeddedView.setOnTouchListener((v, event) -> { 2000 if (embeddedGotEvent[0]) { 2001 return false; 2002 } 2003 embeddedGotEvent[0] = true; 2004 receivedTouches.countDown(); 2005 return false; 2006 }); 2007 2008 final int[] viewInWindow = new int[2]; 2009 mSurfaceView.getLocationInWindow(viewInWindow); 2010 Point point = new Point(viewInWindow[0] + 1, viewInWindow[1] + 1); 2011 2012 CtsWindowInfoUtils.tapOnWindow(mInstrumentation, mSurfaceView::getWindowToken, point, 2013 mDisplayId); 2014 2015 assertTrue("Failed to receive touch from host=" + hostGotEvent[0] + " or embedded=" 2016 + embeddedGotEvent[0], receivedTouches.await(WAIT_TIMEOUT_S, TimeUnit.SECONDS)); 2017 2018 assertTrue("Failed to receive touch event in host window", hostGotEvent[0]); 2019 assertTrue("Failed to receive touch event in embedded window", embeddedGotEvent[0]); 2020 } 2021 } 2022