diff options
4 files changed, 214 insertions, 5 deletions
diff --git a/core/java/android/view/SurfaceControlViewHost.java b/core/java/android/view/SurfaceControlViewHost.java index e9984daaf389..16c133523303 100644 --- a/core/java/android/view/SurfaceControlViewHost.java +++ b/core/java/android/view/SurfaceControlViewHost.java @@ -400,8 +400,7 @@ public class SurfaceControlViewHost { public @Nullable SurfacePackage getSurfacePackage() { if (mSurfaceControl != null && mAccessibilityEmbeddedConnection != null) { return new SurfacePackage(new SurfaceControl(mSurfaceControl, "getSurfacePackage"), - mAccessibilityEmbeddedConnection, - mWm.getFocusGrantToken(), mRemoteInterface); + mAccessibilityEmbeddedConnection, getFocusGrantToken(), mRemoteInterface); } else { return null; } @@ -507,7 +506,7 @@ public class SurfaceControlViewHost { * @hide */ public IBinder getFocusGrantToken() { - return mWm.getFocusGrantToken(); + return mWm.getFocusGrantToken(getWindowToken().asBinder()); } private void addWindowToken(WindowManager.LayoutParams attrs) { diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java index 98681446446b..96bfb2d9e1e6 100644 --- a/core/java/android/view/WindowlessWindowManager.java +++ b/core/java/android/view/WindowlessWindowManager.java @@ -106,8 +106,22 @@ public class WindowlessWindowManager implements IWindowSession { mConfiguration.setTo(configuration); } - IBinder getFocusGrantToken() { - return mFocusGrantToken; + IBinder getFocusGrantToken(IBinder window) { + synchronized (this) { + // This can only happen if someone requested the focusGrantToken before setView was + // called for the SCVH. In that case, use the root focusGrantToken since this will be + // the same token sent to WMS for the root window once setView is called. + if (mStateForWindow.isEmpty()) { + return mFocusGrantToken; + } + State state = mStateForWindow.get(window); + if (state != null) { + return state.mFocusGrantToken; + } + } + + Log.w(TAG, "Failed to get focusGrantToken. Returning null token"); + return null; } /** diff --git a/services/tests/wmtests/AndroidManifest.xml b/services/tests/wmtests/AndroidManifest.xml index 2696d2bf58b9..f12b53acd06b 100644 --- a/services/tests/wmtests/AndroidManifest.xml +++ b/services/tests/wmtests/AndroidManifest.xml @@ -87,6 +87,8 @@ android:showWhenLocked="true"/> <activity android:name="android.view.cts.surfacevalidator.CapturedActivity"/> + <activity android:name="com.android.server.wm.SurfaceControlViewHostTests$TestActivity" /> + <service android:name="android.view.cts.surfacevalidator.LocalMediaProjectionService" android:foregroundServiceType="mediaProjection" android:enabled="true"> diff --git a/services/tests/wmtests/src/com/android/server/wm/SurfaceControlViewHostTests.java b/services/tests/wmtests/src/com/android/server/wm/SurfaceControlViewHostTests.java new file mode 100644 index 000000000000..41bfc806f839 --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/SurfaceControlViewHostTests.java @@ -0,0 +1,194 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm; + +import static android.Manifest.permission.ACCESS_SURFACE_FLINGER; +import static android.Manifest.permission.INTERNAL_SYSTEM_WINDOW; +import static android.server.wm.CtsWindowInfoUtils.waitForWindowVisible; +import static android.server.wm.CtsWindowInfoUtils.waitForWindowFocus; +import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; + +import static org.junit.Assert.assertTrue; + +import android.app.Activity; +import android.app.Instrumentation; +import android.content.res.Configuration; +import android.graphics.Color; +import android.graphics.PixelFormat; +import android.os.Bundle; +import android.os.IBinder; +import android.os.RemoteException; +import android.platform.test.annotations.Presubmit; +import android.view.Gravity; +import android.view.IWindow; +import android.view.SurfaceControl; +import android.view.SurfaceControlViewHost; +import android.view.SurfaceHolder; +import android.view.SurfaceView; +import android.view.View; +import android.view.WindowManager; +import android.view.WindowManagerGlobal; +import android.view.WindowlessWindowManager; +import android.widget.Button; +import android.widget.FrameLayout; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.test.filters.SmallTest; +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.rule.ActivityTestRule; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.concurrent.CountDownLatch; + +@Presubmit +@SmallTest +@RunWith(WindowTestRunner.class) +public class SurfaceControlViewHostTests { + private final ActivityTestRule<TestActivity> mActivityRule = new ActivityTestRule<>( + TestActivity.class); + private Instrumentation mInstrumentation; + private TestActivity mActivity; + + private View mView1; + private View mView2; + private SurfaceControlViewHost mScvh1; + private SurfaceControlViewHost mScvh2; + + @Before + public void setUp() throws Exception { + mInstrumentation = InstrumentationRegistry.getInstrumentation(); + + // ACCESS_SURFACE_FLINGER is necessary to call waitForWindow + // INTERNAL_SYSTEM_WINDOW is necessary to add SCVH with no host parent + mInstrumentation.getUiAutomation().adoptShellPermissionIdentity(ACCESS_SURFACE_FLINGER, + INTERNAL_SYSTEM_WINDOW); + mActivity = mActivityRule.launchActivity(null); + } + + @After + public void tearDown() { + mInstrumentation.getUiAutomation().dropShellPermissionIdentity(); + } + + @Test + public void requestFocusWithMultipleWindows() throws InterruptedException, RemoteException { + SurfaceControl sc = new SurfaceControl.Builder() + .setName("SurfaceControlViewHostTests") + .setCallsite("requestFocusWithMultipleWindows") + .build(); + mView1 = new Button(mActivity); + mView2 = new Button(mActivity); + + mActivity.runOnUiThread(() -> { + TestWindowlessWindowManager wwm = new TestWindowlessWindowManager( + mActivity.getResources().getConfiguration(), sc, null); + + try { + mActivity.attachToSurfaceView(sc); + } catch (InterruptedException e) { + } + + mScvh1 = new SurfaceControlViewHost(mActivity, mActivity.getDisplay(), + wwm, "requestFocusWithMultipleWindows"); + mScvh2 = new SurfaceControlViewHost(mActivity, mActivity.getDisplay(), + wwm, "requestFocusWithMultipleWindows"); + + + mView1.setBackgroundColor(Color.RED); + mView2.setBackgroundColor(Color.BLUE); + + WindowManager.LayoutParams lp1 = new WindowManager.LayoutParams(200, 200, + TYPE_APPLICATION, 0, PixelFormat.OPAQUE); + WindowManager.LayoutParams lp2 = new WindowManager.LayoutParams(100, 100, + TYPE_APPLICATION, 0, PixelFormat.OPAQUE); + mScvh1.setView(mView1, lp1); + mScvh2.setView(mView2, lp2); + }); + + assertTrue("Failed to wait for view1", waitForWindowVisible(mView1)); + assertTrue("Failed to wait for view2", waitForWindowVisible(mView2)); + + WindowManagerGlobal.getWindowSession().grantEmbeddedWindowFocus(null /* window */, + mScvh1.getFocusGrantToken(), true); + assertTrue("Failed to gain focus for view1", waitForWindowFocus(mView1, true)); + + WindowManagerGlobal.getWindowSession().grantEmbeddedWindowFocus(null /* window */, + mScvh2.getFocusGrantToken(), true); + assertTrue("Failed to gain focus for view2", waitForWindowFocus(mView2, true)); + } + + private static class TestWindowlessWindowManager extends WindowlessWindowManager { + private final SurfaceControl mRoot; + + TestWindowlessWindowManager(Configuration c, SurfaceControl rootSurface, + IBinder hostInputToken) { + super(c, rootSurface, hostInputToken); + mRoot = rootSurface; + } + + @Override + protected SurfaceControl getParentSurface(IWindow window, + WindowManager.LayoutParams attrs) { + return mRoot; + } + } + + public static class TestActivity extends Activity implements SurfaceHolder.Callback { + private SurfaceView mSurfaceView; + private final CountDownLatch mSvReadyLatch = new CountDownLatch(1); + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + final FrameLayout content = new FrameLayout(this); + mSurfaceView = new SurfaceView(this); + mSurfaceView.setBackgroundColor(Color.BLACK); + mSurfaceView.setZOrderOnTop(true); + final FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(500, 500, + Gravity.LEFT | Gravity.TOP); + content.addView(mSurfaceView, lp); + setContentView(content); + mSurfaceView.getHolder().addCallback(this); + } + + @Override + public void surfaceCreated(@NonNull SurfaceHolder holder) { + mSvReadyLatch.countDown(); + } + + @Override + public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width, + int height) { + } + + @Override + public void surfaceDestroyed(@NonNull SurfaceHolder holder) { + } + + public void attachToSurfaceView(SurfaceControl sc) throws InterruptedException { + mSvReadyLatch.await(); + new SurfaceControl.Transaction().reparent(sc, mSurfaceView.getSurfaceControl()) + .show(sc).apply(); + } + } +} + |