• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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