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 17 package android.server.wm; 18 19 import static android.server.wm.CtsWindowInfoUtils.tapOnWindow; 20 import static android.server.wm.CtsWindowInfoUtils.tapOnWindowCenter; 21 import static android.server.wm.CtsWindowInfoUtils.waitForWindowFocus; 22 import static android.server.wm.CtsWindowInfoUtils.waitForWindowInfo; 23 import static android.server.wm.CtsWindowInfoUtils.waitForWindowInfos; 24 import static android.server.wm.CtsWindowInfoUtils.waitForWindowOnTop; 25 import static android.server.wm.CtsWindowInfoUtils.waitForWindowVisible; 26 import static android.server.wm.MockImeHelper.createManagedMockImeSession; 27 import static android.view.SurfaceControlViewHost.SurfacePackage; 28 import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; 29 import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; 30 31 import static com.android.cts.mockime.ImeEventStreamTestUtils.editorMatcher; 32 import static com.android.cts.mockime.ImeEventStreamTestUtils.expectEvent; 33 34 import static org.junit.Assert.assertEquals; 35 import static org.junit.Assert.assertFalse; 36 import static org.junit.Assert.assertNotNull; 37 import static org.junit.Assert.assertTrue; 38 import static org.junit.Assert.fail; 39 import static org.junit.Assume.assumeTrue; 40 41 import android.app.Activity; 42 import android.app.ActivityManager; 43 import android.app.Instrumentation; 44 import android.app.UiAutomation; 45 import android.content.ComponentName; 46 import android.content.Context; 47 import android.content.Intent; 48 import android.content.pm.ActivityInfo; 49 import android.content.pm.ConfigurationInfo; 50 import android.content.pm.FeatureInfo; 51 import android.content.res.Configuration; 52 import android.graphics.Color; 53 import android.graphics.PixelFormat; 54 import android.graphics.Rect; 55 import android.graphics.Region; 56 import android.os.Binder; 57 import android.os.IBinder; 58 import android.os.RemoteException; 59 import android.os.SystemClock; 60 import android.platform.test.annotations.Presubmit; 61 import android.platform.test.annotations.RequiresDevice; 62 import android.server.wm.scvh.Components; 63 import android.server.wm.shared.ICrossProcessSurfaceControlViewHostTestService; 64 import android.util.ArrayMap; 65 import android.view.Gravity; 66 import android.view.MotionEvent; 67 import android.view.SurfaceControl; 68 import android.view.SurfaceControlViewHost; 69 import android.view.SurfaceHolder; 70 import android.view.SurfaceView; 71 import android.view.View; 72 import android.view.ViewGroup; 73 import android.view.WindowManager; 74 import android.widget.Button; 75 import android.widget.EditText; 76 import android.widget.FrameLayout; 77 import android.widget.PopupWindow; 78 import android.window.WindowInfosListenerForTest.WindowInfo; 79 80 import androidx.test.InstrumentationRegistry; 81 import androidx.test.rule.ActivityTestRule; 82 83 import com.android.compatibility.common.util.CtsTouchUtils; 84 import com.android.cts.mockime.ImeEventStream; 85 import com.android.cts.mockime.MockImeSession; 86 87 import org.junit.After; 88 import org.junit.Before; 89 import org.junit.Test; 90 91 import java.util.ArrayList; 92 import java.util.List; 93 import java.util.Map; 94 import java.util.concurrent.CountDownLatch; 95 import java.util.concurrent.TimeUnit; 96 import java.util.concurrent.atomic.AtomicReference; 97 import java.util.function.Consumer; 98 import java.util.function.Predicate; 99 100 /** 101 * Ensure end-to-end functionality of SurfaceControlViewHost. 102 * 103 * Build/Install/Run: 104 * atest CtsWindowManagerDeviceTestCases:SurfaceControlViewHostTests 105 */ 106 @Presubmit 107 public class SurfaceControlViewHostTests extends ActivityManagerTestBase implements SurfaceHolder.Callback { 108 109 public static class TestActivity extends Activity {} 110 111 private final ActivityTestRule<TestActivity> mActivityRule = new ActivityTestRule<>( 112 TestActivity.class); 113 114 private Instrumentation mInstrumentation; 115 private CtsTouchUtils mCtsTouchUtils; 116 private Activity mActivity; 117 private SurfaceView mSurfaceView; 118 private ViewGroup mViewParent; 119 120 private SurfaceControlViewHost mVr; 121 private View mEmbeddedView; 122 private WindowManager.LayoutParams mEmbeddedLayoutParams; 123 124 private volatile boolean mClicked = false; 125 private volatile boolean mPopupClicked = false; 126 private volatile PopupWindow mPopupWindow; 127 128 private SurfaceControlViewHost.SurfacePackage mRemoteSurfacePackage; 129 130 private final Map<String, 131 FutureConnection<ICrossProcessSurfaceControlViewHostTestService>> mConnections = 132 new ArrayMap<>(); 133 private ICrossProcessSurfaceControlViewHostTestService mTestService = null; 134 private static final long TIMEOUT_MS = 3000L; 135 136 /* 137 * Configurable state to control how the surfaceCreated callback 138 * will initialize the embedded view hierarchy. 139 */ 140 int mEmbeddedViewWidth = 100; 141 int mEmbeddedViewHeight = 100; 142 143 private static final int DEFAULT_SURFACE_VIEW_WIDTH = 100; 144 private static final int DEFAULT_SURFACE_VIEW_HEIGHT = 100; 145 MockImeSession mImeSession; 146 147 Consumer<MotionEvent> mSurfaceViewMotionConsumer = null; 148 149 private CountDownLatch mSvCreatedLatch; 150 151 class MotionConsumingSurfaceView extends SurfaceView { MotionConsumingSurfaceView(Context c)152 MotionConsumingSurfaceView(Context c) { 153 super(c); 154 } 155 156 @Override onTouchEvent(MotionEvent ev)157 public boolean onTouchEvent(MotionEvent ev) { 158 if (mSurfaceViewMotionConsumer == null) { 159 return false; 160 } else { 161 mSurfaceViewMotionConsumer.accept(ev); 162 return true; 163 } 164 } 165 } 166 167 boolean mHostGotEvent = false; 168 169 @Before setUp()170 public void setUp() throws Exception { 171 super.setUp(); 172 mClicked = false; 173 mEmbeddedLayoutParams = null; 174 mPopupWindow = null; 175 mRemoteSurfacePackage = null; 176 177 if (supportsInstallableIme()) { 178 mImeSession = createManagedMockImeSession(this); 179 } 180 181 mInstrumentation = InstrumentationRegistry.getInstrumentation(); 182 mCtsTouchUtils = new CtsTouchUtils(mInstrumentation.getTargetContext()); 183 mActivity = mActivityRule.launchActivity(null); 184 mInstrumentation.waitForIdleSync(); 185 // Wait for device animation that shows above the activity to leave. 186 waitForWindowOnTop(mActivity.getWindow()); 187 188 // This is necessary to call waitForWindowInfos 189 mInstrumentation.getUiAutomation().adoptShellPermissionIdentity( 190 android.Manifest.permission.ACCESS_SURFACE_FLINGER); 191 192 mSvCreatedLatch = new CountDownLatch(1); 193 } 194 195 @After tearDown()196 public void tearDown() throws Throwable { 197 for (FutureConnection<ICrossProcessSurfaceControlViewHostTestService> connection : 198 mConnections.values()) { 199 mInstrumentation.getContext().unbindService(connection); 200 } 201 mConnections.clear(); 202 mInstrumentation.getUiAutomation().dropShellPermissionIdentity(); 203 } 204 addSurfaceView(int width, int height)205 private void addSurfaceView(int width, int height) throws Throwable { 206 addSurfaceView(width, height, true); 207 } 208 addSurfaceView(int width, int height, boolean onTop)209 private void addSurfaceView(int width, int height, boolean onTop) throws Throwable { 210 addSurfaceView(width, height, onTop, 0 /* leftMargin */, 0 /* topMargin */); 211 } 212 addSurfaceView(int width, int height, boolean onTop, int leftMargin, int topMargin)213 private void addSurfaceView(int width, int height, boolean onTop, int leftMargin, int topMargin) 214 throws Throwable { 215 mActivityRule.runOnUiThread(() -> { 216 final FrameLayout content = new FrameLayout(mActivity); 217 mSurfaceView = new MotionConsumingSurfaceView(mActivity); 218 mSurfaceView.setBackgroundColor(Color.BLACK); 219 mSurfaceView.setZOrderOnTop(onTop); 220 final FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams( 221 width, height, Gravity.LEFT | Gravity.TOP); 222 lp.leftMargin = leftMargin; 223 lp.topMargin = topMargin; 224 content.addView(mSurfaceView, lp); 225 mViewParent = content; 226 mActivity.setContentView(content, 227 new ViewGroup.LayoutParams(width + leftMargin, height + topMargin)); 228 mSurfaceView.getHolder().addCallback(this); 229 }); 230 } 231 addViewToSurfaceView(SurfaceView sv, View v, int width, int height)232 private void addViewToSurfaceView(SurfaceView sv, View v, int width, int height) { 233 mVr = new SurfaceControlViewHost(mActivity, mActivity.getDisplay(), sv.getHostToken()); 234 235 if (mEmbeddedLayoutParams == null) { 236 mVr.setView(v, width, height); 237 } else { 238 mVr.setView(v, mEmbeddedLayoutParams); 239 } 240 241 sv.setChildSurfacePackage(mVr.getSurfacePackage()); 242 243 assertEquals(v, mVr.getView()); 244 } 245 requestSurfaceViewFocus()246 private void requestSurfaceViewFocus() throws Throwable { 247 mActivityRule.runOnUiThread(() -> { 248 mSurfaceView.setFocusableInTouchMode(true); 249 mSurfaceView.requestFocusFromTouch(); 250 }); 251 } 252 assertWindowFocused(final View view, boolean hasWindowFocus)253 private void assertWindowFocused(final View view, boolean hasWindowFocus) { 254 if (!waitForWindowFocus(view, hasWindowFocus)) { 255 fail(); 256 } 257 } 258 waitUntilViewDrawn(View view)259 private void waitUntilViewDrawn(View view) throws Throwable { 260 // We use frameCommitCallback because we need to ensure HWUI 261 // has actually queued the frame. 262 final CountDownLatch latch = new CountDownLatch(1); 263 mActivityRule.runOnUiThread(() -> { 264 view.getViewTreeObserver().registerFrameCommitCallback( 265 latch::countDown); 266 view.invalidate(); 267 }); 268 assertTrue(latch.await(1, TimeUnit.SECONDS)); 269 } 270 waitUntilEmbeddedViewDrawn()271 private void waitUntilEmbeddedViewDrawn() throws Throwable { 272 waitUntilViewDrawn(mEmbeddedView); 273 } 274 getTouchableRegionFromDump()275 private String getTouchableRegionFromDump() { 276 final String output = runCommandAndPrintOutput("dumpsys window windows"); 277 boolean foundWindow = false; 278 for (String line : output.split("\\n")) { 279 if (line.contains("SurfaceControlViewHostTests$TestActivity")) { 280 foundWindow = true; 281 } 282 if (foundWindow && line.contains("touchable region")) { 283 return line; 284 } 285 } 286 return null; 287 } 288 waitForTouchableRegionChanged(String originalTouchableRegion)289 private boolean waitForTouchableRegionChanged(String originalTouchableRegion) { 290 int retries = 0; 291 while (retries < 50) { 292 if (getTouchableRegionFromDump() != originalTouchableRegion) { 293 return true; 294 } 295 try { 296 Thread.sleep(100); 297 } catch (Exception e) { 298 } 299 } 300 return false; 301 } 302 waitForViewFocus(final View view, boolean hasViewFocus)303 public static boolean waitForViewFocus(final View view, boolean hasViewFocus) { 304 final CountDownLatch latch = new CountDownLatch(1); 305 306 view.getHandler().post(() -> { 307 if (view.hasFocus() == hasViewFocus) { 308 latch.countDown(); 309 return; 310 } 311 view.setOnFocusChangeListener((v, hasFocus) -> { 312 if (hasViewFocus == hasFocus) { 313 view.setOnFocusChangeListener(null); 314 latch.countDown(); 315 } 316 }); 317 }); 318 319 try { 320 if (!latch.await(3, TimeUnit.SECONDS)) { 321 return false; 322 } 323 } catch (InterruptedException e) { 324 return false; 325 } 326 return true; 327 } 328 329 @Override surfaceCreated(SurfaceHolder holder)330 public void surfaceCreated(SurfaceHolder holder) { 331 if (mTestService == null) { 332 if (mEmbeddedView != null) { 333 addViewToSurfaceView(mSurfaceView, mEmbeddedView, 334 mEmbeddedViewWidth, mEmbeddedViewHeight); 335 } 336 } else if (mRemoteSurfacePackage == null) { 337 try { 338 mRemoteSurfacePackage = mTestService.getSurfacePackage(mSurfaceView.getHostToken()); 339 } catch (Exception e) { 340 } 341 mSurfaceView.setChildSurfacePackage(mRemoteSurfacePackage); 342 } else { 343 mSurfaceView.setChildSurfacePackage(mRemoteSurfacePackage); 344 } 345 mSvCreatedLatch.countDown(); 346 } 347 348 @Override surfaceDestroyed(SurfaceHolder holder)349 public void surfaceDestroyed(SurfaceHolder holder) { 350 } 351 352 @Override surfaceChanged(SurfaceHolder holder, int format, int width, int height)353 public void surfaceChanged(SurfaceHolder holder, int format, int width, 354 int height) { 355 } 356 357 @Test testEmbeddedViewReceivesInput()358 public void testEmbeddedViewReceivesInput() throws Throwable { 359 mEmbeddedView = new Button(mActivity); 360 mEmbeddedView.setOnClickListener((View v) -> { 361 mClicked = true; 362 }); 363 364 addSurfaceView(DEFAULT_SURFACE_VIEW_WIDTH, DEFAULT_SURFACE_VIEW_HEIGHT); 365 mInstrumentation.waitForIdleSync(); 366 waitUntilEmbeddedViewDrawn(); 367 368 mCtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, mActivityRule, mSurfaceView); 369 assertTrue(mClicked); 370 } 371 372 @Test testEmbeddedViewReceivesRawInputCoordinatesInDisplaySpace()373 public void testEmbeddedViewReceivesRawInputCoordinatesInDisplaySpace() throws Throwable { 374 final UiAutomation uiAutomation = mInstrumentation.getUiAutomation(); 375 final int viewX = DEFAULT_SURFACE_VIEW_WIDTH / 2; 376 final int viewY = DEFAULT_SURFACE_VIEW_HEIGHT / 2; 377 378 // Verify the input coordinates received by the embedded view in three different locations. 379 for (int i = 0; i < 3; i++) { 380 final List<MotionEvent> events = new ArrayList<>(); 381 mEmbeddedView = new View(mActivity); 382 mEmbeddedView.setOnTouchListener((v, e) -> events.add(MotionEvent.obtain(e))); 383 384 // Add a margin to the SurfaceView to offset the embedded view's location on the screen. 385 final int leftMargin = i * 20; 386 final int topMargin = i * 10; 387 addSurfaceView(DEFAULT_SURFACE_VIEW_WIDTH, DEFAULT_SURFACE_VIEW_HEIGHT, true /*onTop*/, 388 leftMargin, topMargin); 389 mInstrumentation.waitForIdleSync(); 390 waitUntilEmbeddedViewDrawn(); 391 392 final int[] surfaceLocation = new int[2]; 393 mSurfaceView.getLocationOnScreen(surfaceLocation); 394 395 final int displayX = surfaceLocation[0] + viewX; 396 final int displayY = surfaceLocation[1] + viewY; 397 final long downTime = SystemClock.uptimeMillis(); 398 mCtsTouchUtils.injectDownEvent(uiAutomation, downTime, displayX, displayY, 399 null /*eventInjectionListener*/); 400 mCtsTouchUtils.injectUpEvent(uiAutomation, downTime, true /*useCurrentEventTime*/, 401 displayX, displayY, null /*eventInjectionListener*/); 402 403 assertEquals("Expected to capture all injected events.", 2, events.size()); 404 final float epsilon = 0.001f; 405 events.forEach(e -> { 406 assertEquals("Expected to get the x coordinate in View space.", 407 viewX, e.getX(), epsilon); 408 assertEquals("Expected to get the y coordinate in View space.", 409 viewY, e.getY(), epsilon); 410 assertEquals("Expected to get raw x coordinate in Display space.", 411 displayX, e.getRawX(), epsilon); 412 assertEquals("Expected to get raw y coordinate in Display space.", 413 displayY, e.getRawY(), epsilon); 414 }); 415 } 416 } 417 getGlEsVersion(Context context)418 private static int getGlEsVersion(Context context) { 419 ActivityManager activityManager = 420 (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); 421 ConfigurationInfo configInfo = activityManager.getDeviceConfigurationInfo(); 422 if (configInfo.reqGlEsVersion != ConfigurationInfo.GL_ES_VERSION_UNDEFINED) { 423 return getMajorVersion(configInfo.reqGlEsVersion); 424 } else { 425 return 1; // Lack of property means OpenGL ES version 1 426 } 427 } 428 429 /** @see FeatureInfo#getGlEsVersion() */ getMajorVersion(int glEsVersion)430 private static int getMajorVersion(int glEsVersion) { 431 return ((glEsVersion & 0xffff0000) >> 16); 432 } 433 434 @Test 435 @RequiresDevice testEmbeddedViewIsHardwareAccelerated()436 public void testEmbeddedViewIsHardwareAccelerated() throws Throwable { 437 // Hardware accel may not be supported on devices without GLES 2.0 438 if (getGlEsVersion(mActivity) < 2) { 439 return; 440 } 441 mEmbeddedView = new Button(mActivity); 442 mEmbeddedView.setOnClickListener((View v) -> { 443 mClicked = true; 444 }); 445 446 addSurfaceView(DEFAULT_SURFACE_VIEW_WIDTH, DEFAULT_SURFACE_VIEW_HEIGHT); 447 mInstrumentation.waitForIdleSync(); 448 449 // If we don't support hardware acceleration on the main activity the embedded 450 // view also won't be. 451 if (!mSurfaceView.isHardwareAccelerated()) { 452 return; 453 } 454 455 assertTrue(mEmbeddedView.isHardwareAccelerated()); 456 } 457 458 @Test testEmbeddedViewResizes()459 public void testEmbeddedViewResizes() throws Throwable { 460 mEmbeddedView = new Button(mActivity); 461 mEmbeddedView.setOnClickListener((View v) -> { 462 mClicked = true; 463 }); 464 465 final int bigEdgeLength = mEmbeddedViewWidth * 3; 466 467 // We make the SurfaceView more than twice as big as the embedded view 468 // so that a touch in the middle of the SurfaceView won't land 469 // on the embedded view. 470 addSurfaceView(bigEdgeLength, bigEdgeLength); 471 mInstrumentation.waitForIdleSync(); 472 473 mCtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, mActivityRule, mSurfaceView); 474 assertFalse(mClicked); 475 476 mActivityRule.runOnUiThread(() -> { 477 mVr.relayout(bigEdgeLength, bigEdgeLength); 478 }); 479 mInstrumentation.waitForIdleSync(); 480 waitUntilEmbeddedViewDrawn(); 481 482 // But after the click should hit. 483 mCtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, mActivityRule, mSurfaceView); 484 assertTrue(mClicked); 485 } 486 487 @Test testEmbeddedViewReleases()488 public void testEmbeddedViewReleases() throws Throwable { 489 mEmbeddedView = new Button(mActivity); 490 mEmbeddedView.setOnClickListener((View v) -> { 491 mClicked = true; 492 }); 493 494 addSurfaceView(DEFAULT_SURFACE_VIEW_WIDTH, DEFAULT_SURFACE_VIEW_HEIGHT); 495 mInstrumentation.waitForIdleSync(); 496 497 mCtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, mActivityRule, mSurfaceView); 498 assertTrue(mClicked); 499 500 mActivityRule.runOnUiThread(() -> { 501 mVr.release(); 502 }); 503 mInstrumentation.waitForIdleSync(); 504 505 mClicked = false; 506 mCtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, mActivityRule, mSurfaceView); 507 assertFalse(mClicked); 508 } 509 510 @Test testDisableInputTouch()511 public void testDisableInputTouch() throws Throwable { 512 mEmbeddedView = new Button(mActivity); 513 mEmbeddedView.setOnClickListener((View v) -> { 514 mClicked = true; 515 }); 516 517 mEmbeddedLayoutParams = new WindowManager.LayoutParams(mEmbeddedViewWidth, 518 mEmbeddedViewHeight, WindowManager.LayoutParams.TYPE_APPLICATION, 0, 519 PixelFormat.OPAQUE); 520 521 addSurfaceView(DEFAULT_SURFACE_VIEW_WIDTH, DEFAULT_SURFACE_VIEW_HEIGHT); 522 mInstrumentation.waitForIdleSync(); 523 524 mActivityRule.runOnUiThread(() -> { 525 mEmbeddedLayoutParams.flags |= FLAG_NOT_TOUCHABLE; 526 mVr.relayout(mEmbeddedLayoutParams); 527 }); 528 mInstrumentation.waitForIdleSync(); 529 530 mCtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, mActivityRule, mSurfaceView); 531 assertFalse(mClicked); 532 533 mActivityRule.runOnUiThread(() -> { 534 mEmbeddedLayoutParams.flags &= ~FLAG_NOT_TOUCHABLE; 535 mVr.relayout(mEmbeddedLayoutParams); 536 }); 537 mInstrumentation.waitForIdleSync(); 538 539 mCtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, mActivityRule, mSurfaceView); 540 assertTrue(mClicked); 541 } 542 543 @Test testFocusable()544 public void testFocusable() throws Throwable { 545 mEmbeddedView = new Button(mActivity); 546 addSurfaceView(DEFAULT_SURFACE_VIEW_WIDTH, DEFAULT_SURFACE_VIEW_HEIGHT); 547 mInstrumentation.waitForIdleSync(); 548 waitUntilEmbeddedViewDrawn(); 549 550 // When surface view is focused, it should transfer focus to the embedded view. 551 requestSurfaceViewFocus(); 552 assertWindowFocused(mEmbeddedView, true); 553 // assert host does not have focus 554 assertWindowFocused(mSurfaceView, false); 555 556 // When surface view is no longer focused, it should transfer focus back to the host window. 557 mActivityRule.runOnUiThread(() -> mSurfaceView.setFocusable(false)); 558 assertWindowFocused(mEmbeddedView, false); 559 // assert host has focus 560 assertWindowFocused(mSurfaceView, true); 561 } 562 563 @Test testFocusWithTouch()564 public void testFocusWithTouch() throws Throwable { 565 mEmbeddedView = new Button(mActivity); 566 addSurfaceView(DEFAULT_SURFACE_VIEW_WIDTH, DEFAULT_SURFACE_VIEW_HEIGHT); 567 mInstrumentation.waitForIdleSync(); 568 waitUntilEmbeddedViewDrawn(); 569 570 // Tap where the embedded window is placed to ensure focus is given via touch 571 assertTrue("Failed to tap on embedded", 572 tapOnWindowCenter(mInstrumentation, () -> mEmbeddedView.getWindowToken())); 573 assertWindowFocused(mEmbeddedView, true); 574 // assert host does not have focus 575 assertWindowFocused(mSurfaceView, false); 576 577 // Tap where the host window is placed to ensure focus is given back to host when touched 578 assertTrue("Failed to tap on host", 579 tapOnWindowCenter(mInstrumentation, () -> mViewParent.getWindowToken())); 580 assertWindowFocused(mEmbeddedView, false); 581 // assert host does not have focus 582 assertWindowFocused(mViewParent, true); 583 } 584 585 @Test testChildWindowFocusable()586 public void testChildWindowFocusable() throws Throwable { 587 mEmbeddedView = new Button(mActivity); 588 mEmbeddedView.setBackgroundColor(Color.BLUE); 589 View embeddedViewChild = new Button(mActivity); 590 embeddedViewChild.setBackgroundColor(Color.RED); 591 addSurfaceView(DEFAULT_SURFACE_VIEW_WIDTH, DEFAULT_SURFACE_VIEW_HEIGHT); 592 mInstrumentation.waitForIdleSync(); 593 waitUntilEmbeddedViewDrawn(); 594 595 mActivityRule.runOnUiThread(() -> { 596 final WindowManager.LayoutParams embeddedViewChildParams = 597 new WindowManager.LayoutParams(25, 25, 598 WindowManager.LayoutParams.TYPE_APPLICATION, 0, PixelFormat.OPAQUE); 599 embeddedViewChildParams.token = mEmbeddedView.getWindowToken(); 600 WindowManager wm = mActivity.getSystemService(WindowManager.class); 601 wm.addView(embeddedViewChild, embeddedViewChildParams); 602 }); 603 604 waitUntilViewDrawn(embeddedViewChild); 605 606 assertTrue("Failed to tap on embedded child", 607 tapOnWindowCenter(mInstrumentation, () -> embeddedViewChild.getWindowToken())); 608 // When tapping on the child embedded window, it should gain focus. 609 assertWindowFocused(embeddedViewChild, true); 610 // assert parent embedded window does not have focus. 611 assertWindowFocused(mEmbeddedView, false); 612 // assert host does not have focus 613 assertWindowFocused(mSurfaceView, false); 614 615 assertTrue("Failed to tap on embedded parent", 616 tapOnWindow(mInstrumentation, () -> mEmbeddedView.getWindowToken(), 617 null /* offset */)); 618 // When tapping on the parent embedded window, it should gain focus. 619 assertWindowFocused(mEmbeddedView, true); 620 // assert child embedded window does not have focus. 621 assertWindowFocused(embeddedViewChild, false); 622 // assert host does not have focus 623 assertWindowFocused(mSurfaceView, false); 624 } 625 626 @Test testFocusWithTouchCrossProcess()627 public void testFocusWithTouchCrossProcess() throws Throwable { 628 mTestService = getService(); 629 assertNotNull(mTestService); 630 631 addSurfaceView(DEFAULT_SURFACE_VIEW_WIDTH, DEFAULT_SURFACE_VIEW_HEIGHT); 632 mSvCreatedLatch.await(5, TimeUnit.SECONDS); 633 634 // Tap where the embedded window is placed to ensure focus is given via touch 635 assertTrue("Failed to tap on embedded", 636 tapOnWindowCenter(mInstrumentation, () -> { 637 try { 638 return mTestService.getWindowToken(); 639 } catch (RemoteException e) { 640 return null; 641 } 642 })); 643 assertTrue(mTestService.waitForFocus(true)); 644 // assert host does not have focus 645 assertWindowFocused(mSurfaceView, false); 646 647 // Tap where the host window is placed to ensure focus is given back to host when touched 648 assertTrue("Failed to tap on host", 649 tapOnWindowCenter(mInstrumentation, () -> mViewParent.getWindowToken())); 650 assertTrue(mTestService.waitForFocus(false)); 651 // assert host does not have focus 652 assertWindowFocused(mViewParent, true); 653 } 654 655 @Test testWindowResumes_FocusTransfersToEmbedded()656 public void testWindowResumes_FocusTransfersToEmbedded() throws Throwable { 657 mEmbeddedView = new Button(mActivity); 658 addSurfaceView(DEFAULT_SURFACE_VIEW_WIDTH, DEFAULT_SURFACE_VIEW_HEIGHT); 659 mInstrumentation.waitForIdleSync(); 660 waitUntilEmbeddedViewDrawn(); 661 662 // When surface view is focused, it should transfer focus to the embedded view. 663 requestSurfaceViewFocus(); 664 assertWindowFocused(mEmbeddedView, true); 665 // assert host does not have focus 666 assertWindowFocused(mSurfaceView, false); 667 668 WindowManager wm = mActivity.getSystemService(WindowManager.class); 669 View childView = new Button(mActivity); 670 mActivityRule.runOnUiThread(() -> { 671 final WindowManager.LayoutParams childWindowParams = 672 new WindowManager.LayoutParams(25, 25, 673 WindowManager.LayoutParams.TYPE_APPLICATION, 0, PixelFormat.OPAQUE); 674 wm.addView(childView, childWindowParams); 675 }); 676 waitUntilViewDrawn(childView); 677 assertWindowFocused(childView, true); 678 // Neither host or embedded should be focus 679 assertWindowFocused(mSurfaceView, false); 680 assertWindowFocused(mEmbeddedView, false); 681 682 mActivityRule.runOnUiThread(() -> wm.removeView(childView)); 683 mInstrumentation.waitForIdleSync(); 684 685 assertWindowFocused(mEmbeddedView, true); 686 assertWindowFocused(mSurfaceView, false); 687 } 688 689 @Test testImeVisible()690 public void testImeVisible() throws Throwable { 691 assumeTrue(MSG_NO_MOCK_IME, supportsInstallableIme()); 692 EditText editText = new EditText(mActivity); 693 694 mEmbeddedView = editText; 695 editText.setBackgroundColor(Color.BLUE); 696 editText.setPrivateImeOptions("Hello reader! This is a random string"); 697 addSurfaceView(DEFAULT_SURFACE_VIEW_WIDTH, DEFAULT_SURFACE_VIEW_HEIGHT); 698 mInstrumentation.waitForIdleSync(); 699 waitUntilEmbeddedViewDrawn(); 700 701 // When surface view is focused, it should transfer focus to the embedded view. 702 requestSurfaceViewFocus(); 703 assertWindowFocused(mEmbeddedView, true); 704 // assert host does not have focus 705 assertWindowFocused(mSurfaceView, false); 706 707 mCtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, mActivityRule, mSurfaceView); 708 final ImeEventStream stream = mImeSession.openEventStream(); 709 expectEvent(stream, editorMatcher("onStartInputView", 710 editText.getPrivateImeOptions()), TIMEOUT_MS); 711 } 712 713 @Test testNotFocusable()714 public void testNotFocusable() throws Throwable { 715 mEmbeddedView = new Button(mActivity); 716 addSurfaceView(DEFAULT_SURFACE_VIEW_WIDTH, DEFAULT_SURFACE_VIEW_HEIGHT); 717 mEmbeddedLayoutParams = new WindowManager.LayoutParams(mEmbeddedViewWidth, 718 mEmbeddedViewHeight, WindowManager.LayoutParams.TYPE_APPLICATION, 0, 719 PixelFormat.OPAQUE); 720 mActivityRule.runOnUiThread(() -> { 721 mEmbeddedLayoutParams.flags |= FLAG_NOT_FOCUSABLE; 722 mVr.relayout(mEmbeddedLayoutParams); 723 }); 724 mInstrumentation.waitForIdleSync(); 725 waitUntilEmbeddedViewDrawn(); 726 727 // When surface view is focused, nothing should happen since the embedded view is not 728 // focusable. 729 requestSurfaceViewFocus(); 730 assertWindowFocused(mEmbeddedView, false); 731 // assert host has focus 732 assertWindowFocused(mSurfaceView, true); 733 } 734 735 @Test testFocusBeforeAddingEmbedded()736 public void testFocusBeforeAddingEmbedded() throws Throwable { 737 addSurfaceView(DEFAULT_SURFACE_VIEW_WIDTH, DEFAULT_SURFACE_VIEW_HEIGHT); 738 // Request focus to the SV before adding the embedded. 739 requestSurfaceViewFocus(); 740 mSvCreatedLatch.await(); 741 assertTrue("Failed to wait for sv to gain focus", waitForViewFocus(mSurfaceView, true)); 742 743 mEmbeddedView = new Button(mActivity); 744 mActivityRule.runOnUiThread(() -> { 745 addViewToSurfaceView(mSurfaceView, mEmbeddedView, mEmbeddedViewWidth, 746 mEmbeddedViewHeight); 747 }); 748 waitForWindowVisible(mEmbeddedView); 749 assertWindowFocused(mEmbeddedView, true); 750 assertWindowFocused(mSurfaceView, false); 751 } 752 753 private static class SurfaceCreatedCallback implements SurfaceHolder.Callback { 754 private final CountDownLatch mSurfaceCreated; SurfaceCreatedCallback(CountDownLatch latch)755 SurfaceCreatedCallback(CountDownLatch latch) { 756 mSurfaceCreated = latch; 757 } 758 @Override surfaceCreated(SurfaceHolder holder)759 public void surfaceCreated(SurfaceHolder holder) { 760 mSurfaceCreated.countDown(); 761 } 762 763 @Override surfaceDestroyed(SurfaceHolder holder)764 public void surfaceDestroyed(SurfaceHolder holder) {} 765 766 @Override surfaceChanged(SurfaceHolder holder, int format, int width, int height)767 public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {} 768 } 769 770 @Test testCanCopySurfacePackage()771 public void testCanCopySurfacePackage() throws Throwable { 772 // Create a surface view and wait for its surface to be created. 773 CountDownLatch surfaceCreated = new CountDownLatch(1); 774 mActivityRule.runOnUiThread(() -> { 775 final FrameLayout content = new FrameLayout(mActivity); 776 mSurfaceView = new SurfaceView(mActivity); 777 mSurfaceView.setZOrderOnTop(true); 778 content.addView(mSurfaceView, new FrameLayout.LayoutParams( 779 DEFAULT_SURFACE_VIEW_WIDTH, DEFAULT_SURFACE_VIEW_HEIGHT, Gravity.LEFT | Gravity.TOP)); 780 mActivity.setContentView(content, new ViewGroup.LayoutParams(DEFAULT_SURFACE_VIEW_WIDTH, DEFAULT_SURFACE_VIEW_HEIGHT)); 781 mSurfaceView.getHolder().addCallback(new SurfaceCreatedCallback(surfaceCreated)); 782 783 // Create an embedded view. 784 mVr = new SurfaceControlViewHost(mActivity, mActivity.getDisplay(), 785 mSurfaceView.getHostToken()); 786 mEmbeddedView = new Button(mActivity); 787 mEmbeddedView.setOnClickListener((View v) -> mClicked = true); 788 mVr.setView(mEmbeddedView, mEmbeddedViewWidth, mEmbeddedViewHeight); 789 790 }); 791 assertTrue("Failed to wait for SurfaceView created", 792 surfaceCreated.await(5, TimeUnit.SECONDS)); 793 794 // Make a copy of the SurfacePackage and release the original package. 795 SurfacePackage surfacePackage = mVr.getSurfacePackage(); 796 SurfacePackage copy = new SurfacePackage(surfacePackage); 797 surfacePackage.release(); 798 799 CountDownLatch surfacePackageReparented = new CountDownLatch(1); 800 mActivityRule.runOnUiThread(() -> { 801 mSurfaceView.setChildSurfacePackage(copy); 802 SurfaceControl.Transaction t = new SurfaceControl.Transaction(); 803 t.addTransactionCommittedListener(Runnable::run, surfacePackageReparented::countDown); 804 mSurfaceView.getRootSurfaceControl().applyTransactionOnDraw(t); 805 }); 806 assertTrue("Failed to wait for surface package to get reparented", 807 surfacePackageReparented.await(5, TimeUnit.SECONDS)); 808 809 mInstrumentation.waitForIdleSync(); 810 waitUntilEmbeddedViewDrawn(); 811 812 // Check if SurfacePackage copy remains valid even though the original package has 813 // been released. 814 mCtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, mActivityRule, mSurfaceView); 815 assertTrue(mClicked); 816 } 817 818 @Test testTransferSurfacePackage()819 public void testTransferSurfacePackage() throws Throwable { 820 // Create a surface view and wait for its surface to be created. 821 CountDownLatch surfaceCreated = new CountDownLatch(1); 822 CountDownLatch surface2Created = new CountDownLatch(1); 823 CountDownLatch viewDetached = new CountDownLatch(1); 824 AtomicReference<SurfacePackage> surfacePackageRef = new AtomicReference<>(null); 825 AtomicReference<SurfacePackage> surfacePackageCopyRef = new AtomicReference<>(null); 826 AtomicReference<SurfaceView> secondSurfaceRef = new AtomicReference<>(null); 827 828 mActivityRule.runOnUiThread(() -> { 829 final FrameLayout content = new FrameLayout(mActivity); 830 mSurfaceView = new SurfaceView(mActivity); 831 mSurfaceView.setZOrderOnTop(true); 832 content.addView(mSurfaceView, new FrameLayout.LayoutParams(DEFAULT_SURFACE_VIEW_WIDTH, 833 DEFAULT_SURFACE_VIEW_HEIGHT, Gravity.LEFT | Gravity.TOP)); 834 mActivity.setContentView(content, new ViewGroup.LayoutParams(DEFAULT_SURFACE_VIEW_WIDTH, 835 DEFAULT_SURFACE_VIEW_HEIGHT)); 836 mSurfaceView.getHolder().addCallback(new SurfaceCreatedCallback(surfaceCreated)); 837 838 // Create an embedded view. 839 mVr = new SurfaceControlViewHost(mActivity, mActivity.getDisplay(), 840 mSurfaceView.getHostToken()); 841 mEmbeddedView = new Button(mActivity); 842 mEmbeddedView.setOnClickListener((View v) -> mClicked = true); 843 mVr.setView(mEmbeddedView, mEmbeddedViewWidth, mEmbeddedViewHeight); 844 845 SurfacePackage surfacePackage = mVr.getSurfacePackage(); 846 surfacePackageRef.set(surfacePackage); 847 surfacePackageCopyRef.set(new SurfacePackage(surfacePackage)); 848 849 // Assign the surface package to the first surface 850 mSurfaceView.setChildSurfacePackage(surfacePackage); 851 852 853 // Create the second surface view to which we'll assign the surface package copy 854 SurfaceView secondSurface = new SurfaceView(mActivity); 855 secondSurfaceRef.set(secondSurface); 856 857 mSurfaceView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() { 858 @Override 859 public void onViewAttachedToWindow(View v) { 860 } 861 862 @Override 863 public void onViewDetachedFromWindow(View v) { 864 viewDetached.countDown(); 865 } 866 }); 867 868 secondSurface.getHolder().addCallback(new SurfaceCreatedCallback(surface2Created)); 869 870 }); 871 surfaceCreated.await(); 872 873 // Add the second surface view and assign it the surface package copy 874 mActivityRule.runOnUiThread(() -> { 875 ViewGroup content = (ViewGroup) mSurfaceView.getParent(); 876 content.addView(secondSurfaceRef.get(), 877 new FrameLayout.LayoutParams(DEFAULT_SURFACE_VIEW_WIDTH, 878 DEFAULT_SURFACE_VIEW_HEIGHT, Gravity.TOP | Gravity.LEFT)); 879 secondSurfaceRef.get().setZOrderOnTop(true); 880 surfacePackageRef.get().release(); 881 secondSurfaceRef.get().setChildSurfacePackage(surfacePackageCopyRef.get()); 882 883 content.removeView(mSurfaceView); 884 }); 885 886 // Wait for the first surface to be removed 887 surface2Created.await(); 888 viewDetached.await(); 889 890 mInstrumentation.waitForIdleSync(); 891 waitUntilEmbeddedViewDrawn(); 892 893 // Check if SurfacePackage copy remains valid even though the original package has 894 // been released and the original surface view removed. 895 mCtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, mActivityRule, 896 secondSurfaceRef.get()); 897 assertTrue(mClicked); 898 } 899 900 @Test testCanReplaceSurfacePackage()901 public void testCanReplaceSurfacePackage() throws Throwable { 902 // Create a surface view and wait for its surface to be created. 903 { 904 CountDownLatch surfaceCreated = new CountDownLatch(1); 905 mActivityRule.runOnUiThread(() -> { 906 final FrameLayout content = new FrameLayout(mActivity); 907 mSurfaceView = new SurfaceView(mActivity); 908 mSurfaceView.setZOrderOnTop(true); 909 content.addView(mSurfaceView, new FrameLayout.LayoutParams( 910 DEFAULT_SURFACE_VIEW_WIDTH, DEFAULT_SURFACE_VIEW_HEIGHT, 911 Gravity.LEFT | Gravity.TOP)); 912 mActivity.setContentView(content, new ViewGroup.LayoutParams( 913 DEFAULT_SURFACE_VIEW_WIDTH, DEFAULT_SURFACE_VIEW_HEIGHT)); 914 mSurfaceView.getHolder().addCallback(new SurfaceCreatedCallback(surfaceCreated)); 915 916 // Create an embedded view without click handling. 917 mVr = new SurfaceControlViewHost(mActivity, mActivity.getDisplay(), 918 mSurfaceView.getHostToken()); 919 mEmbeddedView = new Button(mActivity); 920 mVr.setView(mEmbeddedView, mEmbeddedViewWidth, mEmbeddedViewHeight); 921 922 }); 923 surfaceCreated.await(); 924 mSurfaceView.setChildSurfacePackage(mVr.getSurfacePackage()); 925 mInstrumentation.waitForIdleSync(); 926 waitUntilEmbeddedViewDrawn(); 927 } 928 929 { 930 CountDownLatch hostReady = new CountDownLatch(1); 931 // Create a second surface view and wait for its surface to be created. 932 mActivityRule.runOnUiThread(() -> { 933 // Create an embedded view. 934 mVr = new SurfaceControlViewHost(mActivity, mActivity.getDisplay(), 935 mSurfaceView.getHostToken()); 936 mEmbeddedView = new Button(mActivity); 937 mEmbeddedView.setOnClickListener((View v) -> mClicked = true); 938 mVr.setView(mEmbeddedView, mEmbeddedViewWidth, mEmbeddedViewHeight); 939 hostReady.countDown(); 940 941 }); 942 hostReady.await(); 943 mSurfaceView.setChildSurfacePackage(mVr.getSurfacePackage()); 944 mInstrumentation.waitForIdleSync(); 945 waitUntilEmbeddedViewDrawn(); 946 } 947 948 // Check to see if the click went through - this only would happen if the surface package 949 // was replaced 950 mCtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, mActivityRule, mSurfaceView); 951 assertTrue(mClicked); 952 } 953 954 class MotionRecordingSurfaceView extends SurfaceView { 955 boolean mGotEvent = false; MotionRecordingSurfaceView(Context c)956 MotionRecordingSurfaceView(Context c) { 957 super(c); 958 } onTouchEvent(MotionEvent e)959 public boolean onTouchEvent(MotionEvent e) { 960 super.onTouchEvent(e); 961 synchronized (this) { 962 mGotEvent = true; 963 } 964 return true; 965 } gotEvent()966 boolean gotEvent() { 967 synchronized (this) { 968 return mGotEvent; 969 } 970 } reset()971 void reset() { 972 synchronized (this) { 973 mGotEvent = false; 974 } 975 } 976 } 977 978 class TouchPunchingView extends View { TouchPunchingView(Context context)979 public TouchPunchingView(Context context) { 980 super(context); 981 } 982 punchHoleInTouchableRegion()983 void punchHoleInTouchableRegion() { 984 getRootSurfaceControl().setTouchableRegion(new Region()); 985 } 986 } 987 addMotionRecordingSurfaceView(int width, int height)988 private void addMotionRecordingSurfaceView(int width, int height) throws Throwable { 989 mActivityRule.runOnUiThread(() -> { 990 final FrameLayout content = new FrameLayout(mActivity); 991 mSurfaceView = new MotionRecordingSurfaceView(mActivity); 992 mSurfaceView.setZOrderOnTop(true); 993 content.addView(mSurfaceView, new FrameLayout.LayoutParams( 994 width, height, Gravity.LEFT | Gravity.TOP)); 995 mActivity.setContentView(content, new ViewGroup.LayoutParams(width, height)); 996 mSurfaceView.getHolder().addCallback(this); 997 }); 998 } 999 1000 class ForwardingSurfaceView extends SurfaceView { 1001 SurfaceControlViewHost.SurfacePackage mPackage; 1002 ForwardingSurfaceView(Context c)1003 ForwardingSurfaceView(Context c) { 1004 super(c); 1005 } 1006 1007 @Override onDetachedFromWindow()1008 protected void onDetachedFromWindow() { 1009 mPackage.notifyDetachedFromWindow(); 1010 } 1011 1012 @Override onConfigurationChanged(Configuration newConfig)1013 protected void onConfigurationChanged(Configuration newConfig) { 1014 super.onConfigurationChanged(newConfig); 1015 mPackage.notifyConfigurationChanged(newConfig); 1016 } 1017 1018 @Override setChildSurfacePackage(SurfaceControlViewHost.SurfacePackage p)1019 public void setChildSurfacePackage(SurfaceControlViewHost.SurfacePackage p) { 1020 super.setChildSurfacePackage(p); 1021 mPackage = p; 1022 } 1023 } 1024 1025 class DetachRecordingView extends View { 1026 boolean mDetached = false; DetachRecordingView(Context c)1027 DetachRecordingView(Context c) { 1028 super(c); 1029 } 1030 1031 @Override onDetachedFromWindow()1032 protected void onDetachedFromWindow() { 1033 mDetached = true; 1034 } 1035 } 1036 1037 class ConfigRecordingView extends View { 1038 CountDownLatch mLatch; ConfigRecordingView(Context c, CountDownLatch latch)1039 ConfigRecordingView(Context c, CountDownLatch latch) { 1040 super(c); 1041 mLatch = latch; 1042 } 1043 1044 @Override onConfigurationChanged(Configuration newConfig)1045 protected void onConfigurationChanged(Configuration newConfig) { 1046 mLatch.countDown(); 1047 } 1048 } 1049 addForwardingSurfaceView(int width, int height)1050 private void addForwardingSurfaceView(int width, int height) throws Throwable { 1051 mActivityRule.runOnUiThread(() -> { 1052 final FrameLayout content = new FrameLayout(mActivity); 1053 mSurfaceView = new ForwardingSurfaceView(mActivity); 1054 mSurfaceView.setZOrderOnTop(true); 1055 content.addView(mSurfaceView, new FrameLayout.LayoutParams( 1056 width, height, Gravity.LEFT | Gravity.TOP)); 1057 mViewParent = content; 1058 mActivity.setContentView(content, new ViewGroup.LayoutParams(width, height)); 1059 mSurfaceView.getHolder().addCallback(this); 1060 }); 1061 } 1062 1063 @Test testEmbeddedViewCanSetTouchableRegion()1064 public void testEmbeddedViewCanSetTouchableRegion() throws Throwable { 1065 TouchPunchingView tpv; 1066 mEmbeddedView = tpv = new TouchPunchingView(mActivity); 1067 1068 addMotionRecordingSurfaceView(DEFAULT_SURFACE_VIEW_WIDTH, DEFAULT_SURFACE_VIEW_HEIGHT); 1069 mInstrumentation.waitForIdleSync(); 1070 waitUntilEmbeddedViewDrawn(); 1071 mCtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, mActivityRule, mSurfaceView); 1072 mInstrumentation.waitForIdleSync(); 1073 1074 MotionRecordingSurfaceView mrsv = (MotionRecordingSurfaceView)mSurfaceView; 1075 assertFalse(mrsv.gotEvent()); 1076 mActivityRule.runOnUiThread(() -> { 1077 tpv.punchHoleInTouchableRegion(); 1078 }); 1079 mInstrumentation.waitForIdleSync(); 1080 mCtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, mActivityRule, mSurfaceView); 1081 mInstrumentation.waitForIdleSync(); 1082 assertTrue(mrsv.gotEvent()); 1083 } 1084 1085 @Test forwardDetachedFromWindow()1086 public void forwardDetachedFromWindow() throws Throwable { 1087 DetachRecordingView drv = new DetachRecordingView(mActivity); 1088 mEmbeddedView = drv; 1089 addForwardingSurfaceView(100, 100); 1090 mInstrumentation.waitForIdleSync(); 1091 waitUntilEmbeddedViewDrawn(); 1092 1093 assertFalse(drv.mDetached); 1094 mActivityRule.runOnUiThread(() -> { 1095 mViewParent.removeView(mSurfaceView); 1096 }); 1097 mInstrumentation.waitForIdleSync(); 1098 assertTrue(drv.mDetached); 1099 } 1100 1101 @Test forwardConfigurationChange()1102 public void forwardConfigurationChange() throws Throwable { 1103 if (!supportsOrientationRequest()) { 1104 return; 1105 } 1106 final CountDownLatch embeddedConfigLatch = new CountDownLatch(1); 1107 ConfigRecordingView crv = new ConfigRecordingView(mActivity, embeddedConfigLatch); 1108 mEmbeddedView = crv; 1109 addForwardingSurfaceView(100, 100); 1110 mInstrumentation.waitForIdleSync(); 1111 waitUntilEmbeddedViewDrawn(); 1112 mActivityRule.runOnUiThread(() -> { 1113 int orientation = mActivity.getResources().getConfiguration().orientation; 1114 if (orientation == Configuration.ORIENTATION_LANDSCAPE) { 1115 orientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; 1116 } else { 1117 orientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; 1118 } 1119 mActivity.setRequestedOrientation(orientation); 1120 }); 1121 embeddedConfigLatch.await(3, TimeUnit.SECONDS); 1122 mInstrumentation.waitForIdleSync(); 1123 mActivityRule.runOnUiThread(() -> { 1124 assertEquals(mEmbeddedView.getResources().getConfiguration().orientation, 1125 mSurfaceView.getResources().getConfiguration().orientation); 1126 }); 1127 } 1128 1129 @Test testEmbeddedViewReceivesInputOnBottom()1130 public void testEmbeddedViewReceivesInputOnBottom() throws Throwable { 1131 mEmbeddedView = new Button(mActivity); 1132 mEmbeddedView.setOnClickListener((View v) -> { 1133 mClicked = true; 1134 }); 1135 1136 addSurfaceView(DEFAULT_SURFACE_VIEW_WIDTH, DEFAULT_SURFACE_VIEW_HEIGHT, false); 1137 mInstrumentation.waitForIdleSync(); 1138 waitUntilEmbeddedViewDrawn(); 1139 1140 // We should receive no input until we punch a hole 1141 mCtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, mActivityRule, mSurfaceView); 1142 mInstrumentation.waitForIdleSync(); 1143 assertFalse(mClicked); 1144 1145 String originalRegion = getTouchableRegionFromDump(); 1146 1147 mActivityRule.runOnUiThread(() -> { 1148 mSurfaceView.getRootSurfaceControl().setTouchableRegion(new Region(0,0,1,1)); 1149 }); 1150 mInstrumentation.waitForIdleSync(); 1151 // ViewRootImpl sends the touchable region to the WM via a one-way call, which is great 1152 // for performance...however not so good for testability, we have no way 1153 // to verify it has arrived! It doesn't make so much sense to bloat 1154 // the system image size with a completion callback for just this one test 1155 // so we settle for some inelegant spin-polling on the WM dump. 1156 // In the future when we revisit WM/Client interface and transactionalize 1157 // everything, we should have a standard way to wait on the completion of async 1158 // operations 1159 waitForTouchableRegionChanged(originalRegion); 1160 1161 mCtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, mActivityRule, mSurfaceView); 1162 mInstrumentation.waitForIdleSync(); 1163 assertTrue(mClicked); 1164 } 1165 getService()1166 private ICrossProcessSurfaceControlViewHostTestService getService() throws Exception { 1167 return mConnections.computeIfAbsent("android.server.wm.scvh", this::connect).get(TIMEOUT_MS); 1168 } 1169 repackage(String packageName, ComponentName baseComponent)1170 private static ComponentName repackage(String packageName, ComponentName baseComponent) { 1171 return new ComponentName(packageName, baseComponent.getClassName()); 1172 } 1173 connect( String packageName)1174 private FutureConnection<ICrossProcessSurfaceControlViewHostTestService> connect( 1175 String packageName) { 1176 FutureConnection<ICrossProcessSurfaceControlViewHostTestService> connection = 1177 new FutureConnection<>( 1178 ICrossProcessSurfaceControlViewHostTestService.Stub::asInterface); 1179 Intent intent = new Intent(); 1180 intent.setComponent(repackage(packageName, 1181 Components.CrossProcessSurfaceControlViewHostTestService.COMPONENT)); 1182 assertTrue(mInstrumentation.getContext().bindService(intent, 1183 connection, Context.BIND_AUTO_CREATE)); 1184 return connection; 1185 } 1186 1187 @Test testHostInputTokenAllowsObscuredTouches()1188 public void testHostInputTokenAllowsObscuredTouches() throws Throwable { 1189 mTestService = getService(); 1190 assertTrue(mTestService != null); 1191 1192 addSurfaceView(DEFAULT_SURFACE_VIEW_WIDTH, DEFAULT_SURFACE_VIEW_HEIGHT, false); 1193 assertTrue("Failed to wait for SV to get created", 1194 mSvCreatedLatch.await(5, TimeUnit.SECONDS)); 1195 mActivityRule.runOnUiThread(() -> { 1196 mSurfaceView.getRootSurfaceControl().setTouchableRegion(new Region()); 1197 }); 1198 // TODO(b/279051608): Add touchable regions in WindowInfo test so we can make sure the 1199 // touchable regions for the host have been set before proceeding. 1200 assertTrue("Failed to wait for host window to be visible", 1201 waitForWindowVisible(mSurfaceView)); 1202 assertTrue("Failed to wait for embedded window to be visible", 1203 waitForWindowVisible(mTestService.getWindowToken())); 1204 mCtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, mActivityRule, mSurfaceView); 1205 1206 int retryCount = 0; 1207 boolean isViewTouchedAndObscured = mTestService.getViewIsTouchedAndObscured(); 1208 while (!isViewTouchedAndObscured && retryCount < 3) { 1209 retryCount++; 1210 Thread.sleep(100); 1211 isViewTouchedAndObscured = mTestService.getViewIsTouchedAndObscured(); 1212 } 1213 1214 assertTrue(isViewTouchedAndObscured); 1215 } 1216 1217 @Test testNoHostInputTokenDisallowsObscuredTouches()1218 public void testNoHostInputTokenDisallowsObscuredTouches() throws Throwable { 1219 mTestService = getService(); 1220 mRemoteSurfacePackage = mTestService.getSurfacePackage(new Binder()); 1221 assertTrue(mRemoteSurfacePackage != null); 1222 1223 addSurfaceView(DEFAULT_SURFACE_VIEW_WIDTH, DEFAULT_SURFACE_VIEW_HEIGHT, false); 1224 assertTrue("Failed to wait for SV to get created", 1225 mSvCreatedLatch.await(5, TimeUnit.SECONDS)); 1226 mActivityRule.runOnUiThread(() -> { 1227 mSurfaceView.getRootSurfaceControl().setTouchableRegion(new Region()); 1228 }); 1229 // TODO(b/279051608): Add touchable regions in WindowInfo test so we can make sure the 1230 // touchable regions for the host have been set before proceeding. 1231 assertTrue("Failed to wait for host window to be visible", 1232 waitForWindowVisible(mSurfaceView)); 1233 assertTrue("Failed to wait for embedded window to be visible", 1234 waitForWindowVisible(mTestService.getWindowToken())); 1235 1236 mCtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, mActivityRule, mSurfaceView); 1237 1238 assertFalse(mTestService.getViewIsTouched()); 1239 } 1240 1241 @Test testPopupWindowReceivesInput()1242 public void testPopupWindowReceivesInput() throws Throwable { 1243 mEmbeddedView = new Button(mActivity); 1244 mEmbeddedView.setOnClickListener((View v) -> { 1245 mClicked = true; 1246 }); 1247 addSurfaceView(DEFAULT_SURFACE_VIEW_WIDTH, DEFAULT_SURFACE_VIEW_HEIGHT); 1248 mInstrumentation.waitForIdleSync(); 1249 waitUntilEmbeddedViewDrawn(); 1250 1251 mActivityRule.runOnUiThread(() -> { 1252 PopupWindow pw = new PopupWindow(); 1253 mPopupWindow = pw; 1254 Button popupButton = new Button(mActivity); 1255 popupButton.setOnClickListener((View v) -> { 1256 mPopupClicked = true; 1257 }); 1258 pw.setWidth(DEFAULT_SURFACE_VIEW_WIDTH); 1259 pw.setHeight(DEFAULT_SURFACE_VIEW_HEIGHT); 1260 pw.setContentView(popupButton); 1261 pw.showAsDropDown(mEmbeddedView); 1262 }); 1263 mInstrumentation.waitForIdleSync(); 1264 1265 mCtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, mActivityRule, mSurfaceView); 1266 assertTrue(mPopupClicked); 1267 assertFalse(mClicked); 1268 1269 mActivityRule.runOnUiThread(() -> { 1270 mPopupWindow.dismiss(); 1271 }); 1272 mInstrumentation.waitForIdleSync(); 1273 1274 mCtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, mActivityRule, mSurfaceView); 1275 mInstrumentation.waitForIdleSync(); 1276 assertTrue(mClicked); 1277 } 1278 1279 @Test testPopupWindowPosition()1280 public void testPopupWindowPosition() throws Throwable { 1281 mEmbeddedView = new View(mActivity); 1282 addSurfaceView(DEFAULT_SURFACE_VIEW_WIDTH, DEFAULT_SURFACE_VIEW_HEIGHT); 1283 mInstrumentation.waitForIdleSync(); 1284 waitUntilEmbeddedViewDrawn(); 1285 1286 mActivityRule.runOnUiThread(() -> { 1287 View popupContent = new View(mActivity); 1288 popupContent.setBackgroundColor(Color.BLUE); 1289 1290 mPopupWindow = new PopupWindow(); 1291 mPopupWindow.setWidth(50); 1292 mPopupWindow.setHeight(50); 1293 mPopupWindow.setContentView(popupContent); 1294 mPopupWindow.showAtLocation(mEmbeddedView, Gravity.BOTTOM | Gravity.RIGHT, 0, 0); 1295 }); 1296 1297 Predicate<List<WindowInfo>> hasExpectedFrame = windowInfos -> { 1298 if (mPopupWindow == null) { 1299 return false; 1300 } 1301 1302 IBinder parentWindowToken = mEmbeddedView.getWindowToken(); 1303 IBinder popupWindowToken = mPopupWindow.getContentView().getWindowToken(); 1304 if (parentWindowToken == null || popupWindowToken == null) { 1305 return false; 1306 } 1307 1308 Rect parentBounds = null; 1309 Rect popupBounds = null; 1310 for (WindowInfo windowInfo : windowInfos) { 1311 if (!windowInfo.isVisible) { 1312 continue; 1313 } 1314 if (windowInfo.windowToken == parentWindowToken) { 1315 parentBounds = windowInfo.bounds; 1316 } else if (windowInfo.windowToken == popupWindowToken) { 1317 popupBounds = windowInfo.bounds; 1318 } 1319 } 1320 1321 if (parentBounds == null) { 1322 return false; 1323 } 1324 1325 var expectedBounds = new Rect(parentBounds.left + 50, parentBounds.top + 50, 1326 parentBounds.left + 100, parentBounds.top + 100); 1327 return expectedBounds.equals(popupBounds); 1328 }; 1329 assertTrue(waitForWindowInfos(hasExpectedFrame, 5, TimeUnit.SECONDS)); 1330 } 1331 1332 @Test testFloatingWindowWrapContent()1333 public void testFloatingWindowWrapContent() throws Throwable { 1334 mEmbeddedView = new View(mActivity); 1335 addSurfaceView(DEFAULT_SURFACE_VIEW_WIDTH, DEFAULT_SURFACE_VIEW_HEIGHT); 1336 mInstrumentation.waitForIdleSync(); 1337 waitUntilEmbeddedViewDrawn(); 1338 1339 View popupContent = new View(mActivity); 1340 popupContent.setBackgroundColor(Color.BLUE); 1341 popupContent.setLayoutParams(new ViewGroup.LayoutParams(50, 50)); 1342 1343 FrameLayout popupView = new FrameLayout(mActivity); 1344 popupView.addView(popupContent); 1345 1346 WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(); 1347 layoutParams.setTitle("FloatingWindow"); 1348 layoutParams.gravity = Gravity.TOP | Gravity.LEFT; 1349 layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT; 1350 layoutParams.width = ViewGroup.LayoutParams.WRAP_CONTENT; 1351 layoutParams.token = mEmbeddedView.getWindowToken(); 1352 1353 mActivityRule.runOnUiThread(() -> { 1354 WindowManager windowManager = mActivity.getSystemService(WindowManager.class); 1355 windowManager.addView(popupView, layoutParams); 1356 }); 1357 1358 Predicate<WindowInfo> hasExpectedDimensions = 1359 windowInfo -> windowInfo.bounds.width() == 50 && windowInfo.bounds.height() == 50; 1360 // We pass popupView::getWindowToken as a java.util.function.Supplier 1361 // because the popupView is initially unattached and doesn't have a 1362 // window token. The supplier is called each time the predicate is 1363 // tested, eventually returning the window token. 1364 assertTrue(waitForWindowInfo(hasExpectedDimensions, 5, TimeUnit.SECONDS, 1365 popupView::getWindowToken)); 1366 } 1367 1368 @Test testFloatingWindowMatchParent()1369 public void testFloatingWindowMatchParent() throws Throwable { 1370 mEmbeddedView = new View(mActivity); 1371 mEmbeddedViewWidth = 50; 1372 mEmbeddedViewHeight = 50; 1373 addSurfaceView(100, 100); 1374 mInstrumentation.waitForIdleSync(); 1375 1376 View popupView = new FrameLayout(mActivity); 1377 popupView.setBackgroundColor(Color.BLUE); 1378 1379 WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(); 1380 layoutParams.setTitle("FloatingWindow"); 1381 layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT; 1382 layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT; 1383 layoutParams.token = mEmbeddedView.getWindowToken(); 1384 1385 mActivityRule.runOnUiThread(() -> { 1386 WindowManager windowManager = mActivity.getSystemService(WindowManager.class); 1387 windowManager.addView(popupView, layoutParams); 1388 }); 1389 1390 Predicate<WindowInfo> hasExpectedDimensions = 1391 windowInfo -> windowInfo.bounds.width() == 50 && windowInfo.bounds.height() == 50; 1392 assertTrue(waitForWindowInfo(hasExpectedDimensions, 5, TimeUnit.SECONDS, 1393 popupView::getWindowToken)); 1394 } 1395 1396 class TouchTransferringView extends View { 1397 boolean mExpectsFirstMotion = true; 1398 boolean mExpectsCancel = false; 1399 boolean mGotCancel = false; 1400 TouchTransferringView(Context c)1401 TouchTransferringView(Context c) { 1402 super(c); 1403 } 1404 1405 @Override onTouchEvent(MotionEvent ev)1406 public boolean onTouchEvent(MotionEvent ev) { 1407 int action = ev.getAction(); 1408 synchronized (this) { 1409 if (mExpectsFirstMotion) { 1410 assertEquals(action, MotionEvent.ACTION_DOWN); 1411 assertTrue(mVr.transferTouchGestureToHost()); 1412 mExpectsFirstMotion = false; 1413 mExpectsCancel = true; 1414 } else if (mExpectsCancel) { 1415 assertEquals(action, MotionEvent.ACTION_CANCEL); 1416 mExpectsCancel = false; 1417 mGotCancel = true; 1418 } 1419 this.notifyAll(); 1420 } 1421 return true; 1422 } 1423 waitForEmbeddedTouch()1424 void waitForEmbeddedTouch() { 1425 synchronized (this) { 1426 if (!mExpectsFirstMotion) { 1427 assertTrue(mExpectsCancel || mGotCancel); 1428 return; 1429 } 1430 try { 1431 this.wait(); 1432 } catch (Exception e) { 1433 } 1434 assertFalse(mExpectsFirstMotion); 1435 } 1436 } 1437 waitForCancel()1438 void waitForCancel() { 1439 synchronized (this) { 1440 if (!mExpectsCancel) { 1441 return; 1442 } 1443 try { 1444 this.wait(); 1445 } catch (Exception e) { 1446 } 1447 assertTrue(mGotCancel); 1448 } 1449 } 1450 } 1451 1452 @Test testEmbeddedWindowCanTransferTouchGestureToHost()1453 public void testEmbeddedWindowCanTransferTouchGestureToHost() throws Throwable { 1454 // Inside the embedded view hierarchy, we set up a view that transfers touch 1455 // to the host upon receiving a touch event 1456 TouchTransferringView ttv = new TouchTransferringView(mActivity); 1457 mEmbeddedView = ttv; 1458 addSurfaceView(DEFAULT_SURFACE_VIEW_WIDTH, DEFAULT_SURFACE_VIEW_HEIGHT); 1459 mInstrumentation.waitForIdleSync(); 1460 waitUntilEmbeddedViewDrawn(); 1461 // On the host SurfaceView, we set a motion consumer which expects to receive one event. 1462 mHostGotEvent = false; 1463 mSurfaceViewMotionConsumer = (ev) -> { 1464 synchronized (this) { 1465 mHostGotEvent = true; 1466 this.notifyAll(); 1467 } 1468 }; 1469 1470 // Prepare to inject an event offset one pixel from the top of the SurfaceViews location 1471 // on-screen. 1472 final int[] viewOnScreenXY = new int[2]; 1473 mSurfaceView.getLocationOnScreen(viewOnScreenXY); 1474 final int injectedX = viewOnScreenXY[0] + 1; 1475 final int injectedY = viewOnScreenXY[1] + 1; 1476 final UiAutomation uiAutomation = mInstrumentation.getUiAutomation(); 1477 long downTime = SystemClock.uptimeMillis(); 1478 1479 // We inject a down event 1480 mCtsTouchUtils.injectDownEvent(uiAutomation, downTime, injectedX, injectedY, null); 1481 1482 1483 // And this down event should arrive on the embedded view, which should transfer the touch 1484 // focus 1485 ttv.waitForEmbeddedTouch(); 1486 ttv.waitForCancel(); 1487 1488 downTime = SystemClock.uptimeMillis(); 1489 // Now we inject an up event 1490 mCtsTouchUtils.injectUpEvent(uiAutomation, downTime, false, injectedX, injectedY, null); 1491 // This should arrive on the host now, since we have transferred the touch focus 1492 synchronized (this) { 1493 if (!mHostGotEvent) { 1494 try { 1495 this.wait(); 1496 } catch (Exception e) { 1497 } 1498 } 1499 } 1500 assertTrue(mHostGotEvent); 1501 } 1502 } 1503