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