1 /*
2  * Copyright (C) 2023 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 com.android.server.wm;
18 
19 import static android.Manifest.permission.ACCESS_SURFACE_FLINGER;
20 import static android.Manifest.permission.INTERNAL_SYSTEM_WINDOW;
21 import static android.server.wm.CtsWindowInfoUtils.waitForWindowVisible;
22 import static android.server.wm.CtsWindowInfoUtils.waitForWindowFocus;
23 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
24 
25 import static org.junit.Assert.assertTrue;
26 
27 import android.app.Activity;
28 import android.app.Instrumentation;
29 import android.content.res.Configuration;
30 import android.graphics.Color;
31 import android.graphics.PixelFormat;
32 import android.os.Bundle;
33 import android.os.IBinder;
34 import android.os.RemoteException;
35 import android.platform.test.annotations.Presubmit;
36 import android.view.Gravity;
37 import android.view.IWindow;
38 import android.view.SurfaceControl;
39 import android.view.SurfaceControlViewHost;
40 import android.view.SurfaceHolder;
41 import android.view.SurfaceView;
42 import android.view.View;
43 import android.view.WindowManager;
44 import android.view.WindowManagerGlobal;
45 import android.view.WindowlessWindowManager;
46 import android.widget.Button;
47 import android.widget.FrameLayout;
48 
49 import androidx.annotation.NonNull;
50 import androidx.annotation.Nullable;
51 import androidx.test.filters.SmallTest;
52 import androidx.test.platform.app.InstrumentationRegistry;
53 import androidx.test.rule.ActivityTestRule;
54 
55 import org.junit.After;
56 import org.junit.Before;
57 import org.junit.Test;
58 import org.junit.runner.RunWith;
59 
60 import java.util.concurrent.CountDownLatch;
61 
62 @Presubmit
63 @SmallTest
64 @RunWith(WindowTestRunner.class)
65 public class SurfaceControlViewHostTests {
66     private final ActivityTestRule<TestActivity> mActivityRule = new ActivityTestRule<>(
67             TestActivity.class);
68     private Instrumentation mInstrumentation;
69     private TestActivity mActivity;
70 
71     private View mView1;
72     private View mView2;
73     private SurfaceControlViewHost mScvh1;
74     private SurfaceControlViewHost mScvh2;
75 
76     @Before
setUp()77     public void setUp() throws Exception {
78         mInstrumentation = InstrumentationRegistry.getInstrumentation();
79 
80         // ACCESS_SURFACE_FLINGER is necessary to call waitForWindow
81         // INTERNAL_SYSTEM_WINDOW is necessary to add SCVH with no host parent
82         mInstrumentation.getUiAutomation().adoptShellPermissionIdentity(ACCESS_SURFACE_FLINGER,
83                 INTERNAL_SYSTEM_WINDOW);
84         mActivity = mActivityRule.launchActivity(null);
85     }
86 
87     @After
tearDown()88     public void tearDown() {
89         mInstrumentation.getUiAutomation().dropShellPermissionIdentity();
90     }
91 
92     @Test
requestFocusWithMultipleWindows()93     public void requestFocusWithMultipleWindows() throws InterruptedException, RemoteException {
94         SurfaceControl sc = new SurfaceControl.Builder()
95                 .setName("SurfaceControlViewHostTests")
96                 .setCallsite("requestFocusWithMultipleWindows")
97                 .build();
98         mView1 = new Button(mActivity);
99         mView2 = new Button(mActivity);
100 
101         mActivity.runOnUiThread(() -> {
102             TestWindowlessWindowManager wwm = new TestWindowlessWindowManager(
103                     mActivity.getResources().getConfiguration(), sc, null);
104 
105             try {
106                 mActivity.attachToSurfaceView(sc);
107             } catch (InterruptedException e) {
108             }
109 
110             mScvh1 = new SurfaceControlViewHost(mActivity, mActivity.getDisplay(),
111                     wwm, "requestFocusWithMultipleWindows");
112             mScvh2 = new SurfaceControlViewHost(mActivity, mActivity.getDisplay(),
113                     wwm, "requestFocusWithMultipleWindows");
114 
115 
116             mView1.setBackgroundColor(Color.RED);
117             mView2.setBackgroundColor(Color.BLUE);
118 
119             WindowManager.LayoutParams lp1 = new WindowManager.LayoutParams(200, 200,
120                     TYPE_APPLICATION, 0, PixelFormat.OPAQUE);
121             WindowManager.LayoutParams lp2 = new WindowManager.LayoutParams(100, 100,
122                     TYPE_APPLICATION, 0, PixelFormat.OPAQUE);
123             mScvh1.setView(mView1, lp1);
124             mScvh2.setView(mView2, lp2);
125         });
126 
127         assertTrue("Failed to wait for view1", waitForWindowVisible(mView1));
128         assertTrue("Failed to wait for view2", waitForWindowVisible(mView2));
129 
130         WindowManagerGlobal.getWindowSession().grantEmbeddedWindowFocus(null /* window */,
131                 mScvh1.getFocusGrantToken(), true);
132         assertTrue("Failed to gain focus for view1", waitForWindowFocus(mView1, true));
133 
134         WindowManagerGlobal.getWindowSession().grantEmbeddedWindowFocus(null /* window */,
135                 mScvh2.getFocusGrantToken(), true);
136         assertTrue("Failed to gain focus for view2", waitForWindowFocus(mView2, true));
137     }
138 
139     private static class TestWindowlessWindowManager extends WindowlessWindowManager {
140         private final SurfaceControl mRoot;
141 
TestWindowlessWindowManager(Configuration c, SurfaceControl rootSurface, IBinder hostInputToken)142         TestWindowlessWindowManager(Configuration c, SurfaceControl rootSurface,
143                 IBinder hostInputToken) {
144             super(c, rootSurface, hostInputToken);
145             mRoot = rootSurface;
146         }
147 
148         @Override
getParentSurface(IWindow window, WindowManager.LayoutParams attrs)149         protected SurfaceControl getParentSurface(IWindow window,
150                 WindowManager.LayoutParams attrs) {
151             return mRoot;
152         }
153     }
154 
155     public static class TestActivity extends Activity implements SurfaceHolder.Callback {
156         private SurfaceView mSurfaceView;
157         private final CountDownLatch mSvReadyLatch = new CountDownLatch(1);
158 
159         @Override
onCreate(@ullable Bundle savedInstanceState)160         protected void onCreate(@Nullable Bundle savedInstanceState) {
161             super.onCreate(savedInstanceState);
162             final FrameLayout content = new FrameLayout(this);
163             mSurfaceView = new SurfaceView(this);
164             mSurfaceView.setBackgroundColor(Color.BLACK);
165             mSurfaceView.setZOrderOnTop(true);
166             final FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(500, 500,
167                     Gravity.LEFT | Gravity.TOP);
168             content.addView(mSurfaceView, lp);
169             setContentView(content);
170             mSurfaceView.getHolder().addCallback(this);
171         }
172 
173         @Override
surfaceCreated(@onNull SurfaceHolder holder)174         public void surfaceCreated(@NonNull SurfaceHolder holder) {
175             mSvReadyLatch.countDown();
176         }
177 
178         @Override
surfaceChanged(@onNull SurfaceHolder holder, int format, int width, int height)179         public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width,
180                 int height) {
181         }
182 
183         @Override
surfaceDestroyed(@onNull SurfaceHolder holder)184         public void surfaceDestroyed(@NonNull SurfaceHolder holder) {
185         }
186 
attachToSurfaceView(SurfaceControl sc)187         public void attachToSurfaceView(SurfaceControl sc) throws InterruptedException {
188             mSvReadyLatch.await();
189             new SurfaceControl.Transaction().reparent(sc, mSurfaceView.getSurfaceControl())
190                     .show(sc).apply();
191         }
192     }
193 }
194 
195