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.UiDeviceUtils.pressHomeButton; 20 import static android.server.wm.UiDeviceUtils.pressUnlockButton; 21 import static android.server.wm.UiDeviceUtils.pressWakeupButton; 22 import static android.view.SurfaceControlViewHost.SurfacePackage; 23 import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; 24 import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; 25 26 import static org.junit.Assert.assertEquals; 27 import static org.junit.Assert.assertFalse; 28 import static org.junit.Assert.assertTrue; 29 import static org.junit.Assert.fail; 30 31 import android.app.Activity; 32 import android.app.ActivityManager; 33 import android.app.Instrumentation; 34 import android.content.Context; 35 import android.content.pm.ConfigurationInfo; 36 import android.content.pm.FeatureInfo; 37 import android.graphics.PixelFormat; 38 import android.platform.test.annotations.Presubmit; 39 import android.platform.test.annotations.RequiresDevice; 40 import android.view.Gravity; 41 import android.view.SurfaceControlViewHost; 42 import android.view.SurfaceHolder; 43 import android.view.SurfaceView; 44 import android.view.View; 45 import android.view.ViewGroup; 46 import android.view.ViewTreeObserver; 47 import android.view.WindowManager; 48 import android.widget.Button; 49 import android.widget.FrameLayout; 50 51 import androidx.test.InstrumentationRegistry; 52 import androidx.test.filters.FlakyTest; 53 import androidx.test.rule.ActivityTestRule; 54 55 import com.android.compatibility.common.util.CtsTouchUtils; 56 import com.android.compatibility.common.util.WidgetTestUtils; 57 58 import org.junit.Before; 59 import org.junit.Test; 60 61 import java.util.concurrent.CountDownLatch; 62 import java.util.concurrent.TimeUnit; 63 import java.util.concurrent.atomic.AtomicReference; 64 65 /** 66 * Ensure end-to-end functionality of SurfaceControlViewHost. 67 * 68 * Build/Install/Run: 69 * atest CtsWindowManagerDeviceTestCases:SurfaceControlViewHostTests 70 */ 71 @Presubmit 72 public class SurfaceControlViewHostTests implements SurfaceHolder.Callback { 73 private final ActivityTestRule<Activity> mActivityRule = new ActivityTestRule<>(Activity.class); 74 75 private Instrumentation mInstrumentation; 76 private Activity mActivity; 77 private SurfaceView mSurfaceView; 78 79 private SurfaceControlViewHost mVr; 80 private View mEmbeddedView; 81 private WindowManager.LayoutParams mEmbeddedLayoutParams; 82 83 private volatile boolean mClicked = false; 84 85 /* 86 * Configurable state to control how the surfaceCreated callback 87 * will initialize the embedded view hierarchy. 88 */ 89 int mEmbeddedViewWidth = 100; 90 int mEmbeddedViewHeight = 100; 91 92 private static final int DEFAULT_SURFACE_VIEW_WIDTH = 100; 93 private static final int DEFAULT_SURFACE_VIEW_HEIGHT = 100; 94 95 @Before setUp()96 public void setUp() { 97 pressWakeupButton(); 98 pressUnlockButton(); 99 pressHomeButton(); 100 101 mClicked = false; 102 mEmbeddedLayoutParams = null; 103 104 mInstrumentation = InstrumentationRegistry.getInstrumentation(); 105 mActivity = mActivityRule.launchActivity(null); 106 mInstrumentation.waitForIdleSync(); 107 } 108 addSurfaceView(int width, int height)109 private void addSurfaceView(int width, int height) throws Throwable { 110 mActivityRule.runOnUiThread(() -> { 111 final FrameLayout content = new FrameLayout(mActivity); 112 mSurfaceView = new SurfaceView(mActivity); 113 mSurfaceView.setZOrderOnTop(true); 114 content.addView(mSurfaceView, new FrameLayout.LayoutParams( 115 width, height, Gravity.LEFT | Gravity.TOP)); 116 mActivity.setContentView(content, new ViewGroup.LayoutParams(width, height)); 117 mSurfaceView.getHolder().addCallback(this); 118 }); 119 } 120 addViewToSurfaceView(SurfaceView sv, View v, int width, int height)121 private void addViewToSurfaceView(SurfaceView sv, View v, int width, int height) { 122 mVr = new SurfaceControlViewHost(mActivity, mActivity.getDisplay(), sv.getHostToken()); 123 124 125 if (mEmbeddedLayoutParams == null) { 126 mVr.setView(v, width, height); 127 } else { 128 mVr.setView(v, mEmbeddedLayoutParams); 129 } 130 131 sv.setChildSurfacePackage(mVr.getSurfacePackage()); 132 133 assertEquals(v, mVr.getView()); 134 } 135 requestSurfaceViewFocus()136 private void requestSurfaceViewFocus() throws Throwable { 137 mActivityRule.runOnUiThread(() -> { 138 mSurfaceView.setFocusableInTouchMode(true); 139 mSurfaceView.requestFocusFromTouch(); 140 }); 141 } 142 assertWindowFocused(final View view, boolean hasWindowFocus)143 private void assertWindowFocused(final View view, boolean hasWindowFocus) { 144 final CountDownLatch latch = new CountDownLatch(1); 145 WidgetTestUtils.runOnMainAndDrawSync(mActivityRule, 146 view, () -> { 147 if (view.hasWindowFocus() == hasWindowFocus) { 148 latch.countDown(); 149 return; 150 } 151 view.getViewTreeObserver().addOnWindowFocusChangeListener( 152 new ViewTreeObserver.OnWindowFocusChangeListener() { 153 @Override 154 public void onWindowFocusChanged(boolean newFocusState) { 155 if (hasWindowFocus == newFocusState) { 156 view.getViewTreeObserver() 157 .removeOnWindowFocusChangeListener(this); 158 latch.countDown(); 159 } 160 } 161 }); 162 } 163 ); 164 165 try { 166 if (!latch.await(3, TimeUnit.SECONDS)) { 167 fail(); 168 } 169 } catch (InterruptedException e) { 170 fail(); 171 } 172 } 173 waitUntilEmbeddedViewDrawn()174 private void waitUntilEmbeddedViewDrawn() throws Throwable { 175 // We use frameCommitCallback because we need to ensure HWUI 176 // has actually queued the frame. 177 final CountDownLatch latch = new CountDownLatch(1); 178 mActivityRule.runOnUiThread(() -> { 179 mEmbeddedView.getViewTreeObserver().registerFrameCommitCallback( 180 latch::countDown); 181 mEmbeddedView.invalidate(); 182 }); 183 assertTrue(latch.await(1, TimeUnit.SECONDS)); 184 185 } 186 187 @Override surfaceCreated(SurfaceHolder holder)188 public void surfaceCreated(SurfaceHolder holder) { 189 addViewToSurfaceView(mSurfaceView, mEmbeddedView, 190 mEmbeddedViewWidth, mEmbeddedViewHeight); 191 } 192 193 @Override surfaceDestroyed(SurfaceHolder holder)194 public void surfaceDestroyed(SurfaceHolder holder) { 195 } 196 197 @Override surfaceChanged(SurfaceHolder holder, int format, int width, int height)198 public void surfaceChanged(SurfaceHolder holder, int format, int width, 199 int height) { 200 } 201 202 @Test testEmbeddedViewReceivesInput()203 public void testEmbeddedViewReceivesInput() throws Throwable { 204 mEmbeddedView = new Button(mActivity); 205 mEmbeddedView.setOnClickListener((View v) -> { 206 mClicked = true; 207 }); 208 209 addSurfaceView(DEFAULT_SURFACE_VIEW_WIDTH, DEFAULT_SURFACE_VIEW_HEIGHT); 210 mInstrumentation.waitForIdleSync(); 211 waitUntilEmbeddedViewDrawn(); 212 213 CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, mActivityRule, mSurfaceView); 214 assertTrue(mClicked); 215 } 216 getGlEsVersion(Context context)217 private static int getGlEsVersion(Context context) { 218 ActivityManager activityManager = 219 (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); 220 ConfigurationInfo configInfo = activityManager.getDeviceConfigurationInfo(); 221 if (configInfo.reqGlEsVersion != ConfigurationInfo.GL_ES_VERSION_UNDEFINED) { 222 return getMajorVersion(configInfo.reqGlEsVersion); 223 } else { 224 return 1; // Lack of property means OpenGL ES version 1 225 } 226 } 227 228 /** @see FeatureInfo#getGlEsVersion() */ getMajorVersion(int glEsVersion)229 private static int getMajorVersion(int glEsVersion) { 230 return ((glEsVersion & 0xffff0000) >> 16); 231 } 232 233 @Test 234 @RequiresDevice 235 @FlakyTest(bugId = 152103238) testEmbeddedViewIsHardwareAccelerated()236 public void testEmbeddedViewIsHardwareAccelerated() throws Throwable { 237 // Hardware accel may not be supported on devices without GLES 2.0 238 if (getGlEsVersion(mActivity) < 2) { 239 return; 240 } 241 mEmbeddedView = new Button(mActivity); 242 mEmbeddedView.setOnClickListener((View v) -> { 243 mClicked = true; 244 }); 245 246 addSurfaceView(DEFAULT_SURFACE_VIEW_WIDTH, DEFAULT_SURFACE_VIEW_HEIGHT); 247 mInstrumentation.waitForIdleSync(); 248 249 // If we don't support hardware acceleration on the main activity the embedded 250 // view also won't be. 251 if (!mSurfaceView.isHardwareAccelerated()) { 252 return; 253 } 254 255 assertTrue(mEmbeddedView.isHardwareAccelerated()); 256 } 257 258 @Test testEmbeddedViewResizes()259 public void testEmbeddedViewResizes() throws Throwable { 260 mEmbeddedView = new Button(mActivity); 261 mEmbeddedView.setOnClickListener((View v) -> { 262 mClicked = true; 263 }); 264 265 final int bigEdgeLength = mEmbeddedViewWidth * 3; 266 267 // We make the SurfaceView more than twice as big as the embedded view 268 // so that a touch in the middle of the SurfaceView won't land 269 // on the embedded view. 270 addSurfaceView(bigEdgeLength, bigEdgeLength); 271 mInstrumentation.waitForIdleSync(); 272 273 CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, mActivityRule, mSurfaceView); 274 assertFalse(mClicked); 275 276 mActivityRule.runOnUiThread(() -> { 277 mVr.relayout(bigEdgeLength, bigEdgeLength); 278 }); 279 mInstrumentation.waitForIdleSync(); 280 waitUntilEmbeddedViewDrawn(); 281 282 // But after the click should hit. 283 CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, mActivityRule, mSurfaceView); 284 assertTrue(mClicked); 285 } 286 287 @Test testEmbeddedViewReleases()288 public void testEmbeddedViewReleases() throws Throwable { 289 mEmbeddedView = new Button(mActivity); 290 mEmbeddedView.setOnClickListener((View v) -> { 291 mClicked = true; 292 }); 293 294 addSurfaceView(DEFAULT_SURFACE_VIEW_WIDTH, DEFAULT_SURFACE_VIEW_HEIGHT); 295 mInstrumentation.waitForIdleSync(); 296 297 CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, mActivityRule, mSurfaceView); 298 assertTrue(mClicked); 299 300 mActivityRule.runOnUiThread(() -> { 301 mVr.release(); 302 }); 303 mInstrumentation.waitForIdleSync(); 304 305 mClicked = false; 306 CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, mActivityRule, mSurfaceView); 307 assertFalse(mClicked); 308 } 309 310 @Test testDisableInputTouch()311 public void testDisableInputTouch() throws Throwable { 312 mEmbeddedView = new Button(mActivity); 313 mEmbeddedView.setOnClickListener((View v) -> { 314 mClicked = true; 315 }); 316 317 mEmbeddedLayoutParams = new WindowManager.LayoutParams(mEmbeddedViewWidth, 318 mEmbeddedViewHeight, WindowManager.LayoutParams.TYPE_APPLICATION, 0, 319 PixelFormat.OPAQUE); 320 321 addSurfaceView(DEFAULT_SURFACE_VIEW_WIDTH, DEFAULT_SURFACE_VIEW_HEIGHT); 322 mInstrumentation.waitForIdleSync(); 323 324 mActivityRule.runOnUiThread(() -> { 325 mEmbeddedLayoutParams.flags |= FLAG_NOT_TOUCHABLE; 326 mVr.relayout(mEmbeddedLayoutParams); 327 }); 328 mInstrumentation.waitForIdleSync(); 329 330 CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, mActivityRule, mSurfaceView); 331 assertFalse(mClicked); 332 333 mActivityRule.runOnUiThread(() -> { 334 mEmbeddedLayoutParams.flags &= ~FLAG_NOT_TOUCHABLE; 335 mVr.relayout(mEmbeddedLayoutParams); 336 }); 337 mInstrumentation.waitForIdleSync(); 338 339 CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, mActivityRule, mSurfaceView); 340 assertTrue(mClicked); 341 } 342 343 @Test testFocusable()344 public void testFocusable() throws Throwable { 345 mEmbeddedView = new Button(mActivity); 346 addSurfaceView(DEFAULT_SURFACE_VIEW_WIDTH, DEFAULT_SURFACE_VIEW_HEIGHT); 347 mInstrumentation.waitForIdleSync(); 348 waitUntilEmbeddedViewDrawn(); 349 350 // When surface view is focused, it should transfer focus to the embedded view. 351 requestSurfaceViewFocus(); 352 assertWindowFocused(mEmbeddedView, true); 353 // assert host does not have focus 354 assertWindowFocused(mSurfaceView, false); 355 356 // When surface view is no longer focused, it should transfer focus back to the host window. 357 mActivityRule.runOnUiThread(() -> mSurfaceView.setFocusable(false)); 358 assertWindowFocused(mEmbeddedView, false); 359 // assert host has focus 360 assertWindowFocused(mSurfaceView, true); 361 } 362 363 @Test testNotFocusable()364 public void testNotFocusable() throws Throwable { 365 mEmbeddedView = new Button(mActivity); 366 addSurfaceView(DEFAULT_SURFACE_VIEW_WIDTH, DEFAULT_SURFACE_VIEW_HEIGHT); 367 mEmbeddedLayoutParams = new WindowManager.LayoutParams(mEmbeddedViewWidth, 368 mEmbeddedViewHeight, WindowManager.LayoutParams.TYPE_APPLICATION, 0, 369 PixelFormat.OPAQUE); 370 mActivityRule.runOnUiThread(() -> { 371 mEmbeddedLayoutParams.flags |= FLAG_NOT_FOCUSABLE; 372 mVr.relayout(mEmbeddedLayoutParams); 373 }); 374 mInstrumentation.waitForIdleSync(); 375 waitUntilEmbeddedViewDrawn(); 376 377 // When surface view is focused, nothing should happen since the embedded view is not 378 // focusable. 379 requestSurfaceViewFocus(); 380 assertWindowFocused(mEmbeddedView, false); 381 // assert host has focus 382 assertWindowFocused(mSurfaceView, true); 383 } 384 385 private static class SurfaceCreatedCallback implements SurfaceHolder.Callback { 386 private final CountDownLatch mSurfaceCreated; SurfaceCreatedCallback(CountDownLatch latch)387 SurfaceCreatedCallback(CountDownLatch latch) { 388 mSurfaceCreated = latch; 389 } 390 @Override surfaceCreated(SurfaceHolder holder)391 public void surfaceCreated(SurfaceHolder holder) { 392 mSurfaceCreated.countDown(); 393 } 394 395 @Override surfaceDestroyed(SurfaceHolder holder)396 public void surfaceDestroyed(SurfaceHolder holder) {} 397 398 @Override surfaceChanged(SurfaceHolder holder, int format, int width, int height)399 public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {} 400 } 401 402 @Test testCanCopySurfacePackage()403 public void testCanCopySurfacePackage() throws Throwable { 404 // Create a surface view and wait for its surface to be created. 405 CountDownLatch surfaceCreated = new CountDownLatch(1); 406 mActivityRule.runOnUiThread(() -> { 407 final FrameLayout content = new FrameLayout(mActivity); 408 mSurfaceView = new SurfaceView(mActivity); 409 mSurfaceView.setZOrderOnTop(true); 410 content.addView(mSurfaceView, new FrameLayout.LayoutParams( 411 DEFAULT_SURFACE_VIEW_WIDTH, DEFAULT_SURFACE_VIEW_HEIGHT, Gravity.LEFT | Gravity.TOP)); 412 mActivity.setContentView(content, new ViewGroup.LayoutParams(DEFAULT_SURFACE_VIEW_WIDTH, DEFAULT_SURFACE_VIEW_HEIGHT)); 413 mSurfaceView.getHolder().addCallback(new SurfaceCreatedCallback(surfaceCreated)); 414 415 // Create an embedded view. 416 mVr = new SurfaceControlViewHost(mActivity, mActivity.getDisplay(), 417 mSurfaceView.getHostToken()); 418 mEmbeddedView = new Button(mActivity); 419 mEmbeddedView.setOnClickListener((View v) -> mClicked = true); 420 mVr.setView(mEmbeddedView, mEmbeddedViewWidth, mEmbeddedViewHeight); 421 422 }); 423 surfaceCreated.await(); 424 425 // Make a copy of the SurfacePackage and release the original package. 426 SurfacePackage surfacePackage = mVr.getSurfacePackage(); 427 SurfacePackage copy = new SurfacePackage(surfacePackage); 428 surfacePackage.release(); 429 mSurfaceView.setChildSurfacePackage(copy); 430 431 mInstrumentation.waitForIdleSync(); 432 waitUntilEmbeddedViewDrawn(); 433 434 // Check if SurfacePackage copy remains valid even though the original package has 435 // been released. 436 CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, mActivityRule, mSurfaceView); 437 assertTrue(mClicked); 438 } 439 440 @Test testTransferSurfacePackage()441 public void testTransferSurfacePackage() throws Throwable { 442 // Create a surface view and wait for its surface to be created. 443 CountDownLatch surfaceCreated = new CountDownLatch(1); 444 CountDownLatch surface2Created = new CountDownLatch(1); 445 CountDownLatch viewDetached = new CountDownLatch(1); 446 AtomicReference<SurfacePackage> surfacePackageRef = new AtomicReference<>(null); 447 AtomicReference<SurfacePackage> surfacePackageCopyRef = new AtomicReference<>(null); 448 AtomicReference<SurfaceView> secondSurfaceRef = new AtomicReference<>(null); 449 450 mActivityRule.runOnUiThread(() -> { 451 final FrameLayout content = new FrameLayout(mActivity); 452 mSurfaceView = new SurfaceView(mActivity); 453 mSurfaceView.setZOrderOnTop(true); 454 content.addView(mSurfaceView, new FrameLayout.LayoutParams(DEFAULT_SURFACE_VIEW_WIDTH, 455 DEFAULT_SURFACE_VIEW_HEIGHT, Gravity.LEFT | Gravity.TOP)); 456 mActivity.setContentView(content, new ViewGroup.LayoutParams(DEFAULT_SURFACE_VIEW_WIDTH, 457 DEFAULT_SURFACE_VIEW_HEIGHT)); 458 mSurfaceView.getHolder().addCallback(new SurfaceCreatedCallback(surfaceCreated)); 459 460 // Create an embedded view. 461 mVr = new SurfaceControlViewHost(mActivity, mActivity.getDisplay(), 462 mSurfaceView.getHostToken()); 463 mEmbeddedView = new Button(mActivity); 464 mEmbeddedView.setOnClickListener((View v) -> mClicked = true); 465 mVr.setView(mEmbeddedView, mEmbeddedViewWidth, mEmbeddedViewHeight); 466 467 SurfacePackage surfacePackage = mVr.getSurfacePackage(); 468 surfacePackageRef.set(surfacePackage); 469 surfacePackageCopyRef.set(new SurfacePackage(surfacePackage)); 470 471 // Assign the surface package to the first surface 472 mSurfaceView.setChildSurfacePackage(surfacePackage); 473 474 475 // Create the second surface view to which we'll assign the surface package copy 476 SurfaceView secondSurface = new SurfaceView(mActivity); 477 secondSurfaceRef.set(secondSurface); 478 479 mSurfaceView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() { 480 @Override 481 public void onViewAttachedToWindow(View v) { 482 } 483 484 @Override 485 public void onViewDetachedFromWindow(View v) { 486 viewDetached.countDown(); 487 } 488 }); 489 490 secondSurface.getHolder().addCallback(new SurfaceCreatedCallback(surface2Created)); 491 492 }); 493 surfaceCreated.await(); 494 495 // Add the second surface view and assign it the surface package copy 496 mActivityRule.runOnUiThread(() -> { 497 ViewGroup content = (ViewGroup) mSurfaceView.getParent(); 498 content.addView(secondSurfaceRef.get(), 499 new FrameLayout.LayoutParams(DEFAULT_SURFACE_VIEW_WIDTH, 500 DEFAULT_SURFACE_VIEW_HEIGHT, Gravity.TOP | Gravity.LEFT)); 501 secondSurfaceRef.get().setZOrderOnTop(true); 502 surfacePackageRef.get().release(); 503 secondSurfaceRef.get().setChildSurfacePackage(surfacePackageCopyRef.get()); 504 505 content.removeView(mSurfaceView); 506 }); 507 508 // Wait for the first surface to be removed 509 surface2Created.await(); 510 viewDetached.await(); 511 512 mInstrumentation.waitForIdleSync(); 513 waitUntilEmbeddedViewDrawn(); 514 515 // Check if SurfacePackage copy remains valid even though the original package has 516 // been released and the original surface view removed. 517 CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, mActivityRule, 518 secondSurfaceRef.get()); 519 assertTrue(mClicked); 520 } 521 522 } 523