diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 7c3d8fb856a49..cf26e12dc7619 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -3622,6 +3622,8 @@ package android.view { method public default void holdLock(android.os.IBinder, int); method public default boolean isGlobalKey(int); method public default boolean isTaskSnapshotSupported(); + method @RequiresPermission(android.Manifest.permission.ACCESS_SURFACE_FLINGER) public default boolean replaceContentOnDisplayWithMirror(int, @NonNull android.view.Window); + method @RequiresPermission(android.Manifest.permission.ACCESS_SURFACE_FLINGER) public default boolean replaceContentOnDisplayWithSc(int, @NonNull android.view.SurfaceControl); method public default void setDisplayImePolicy(int, int); method public default void setShouldShowSystemDecors(int, boolean); method public default void setShouldShowWithInsecureKeyguard(int, boolean); diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl index 072a7f5ea3045..bbd8255f4978c 100644 --- a/core/java/android/view/IWindowManager.aidl +++ b/core/java/android/view/IWindowManager.aidl @@ -1044,4 +1044,13 @@ interface IWindowManager * @return List of ComponentNames corresponding to the activities that were notified. */ List notifyScreenshotListeners(int displayId); + + /** + * Replace the content of the displayId with the SurfaceControl passed in. This can be used for + * tests when creating a VirtualDisplay, but only want to capture specific content and not + * mirror the entire display. + */ + @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest" + + ".permission.ACCESS_SURFACE_FLINGER)") + boolean replaceContentOnDisplay(int displayId, in SurfaceControl sc); } diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index d702367965a1a..52710bf59761c 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -5701,4 +5701,35 @@ public interface WindowManager extends ViewManager { default @NonNull List notifyScreenshotListeners(int displayId) { throw new UnsupportedOperationException(); } + + /** + * @param displayId The displayId to that should have its content replaced. + * @param window The window that should get mirrored and the mirrored content rendered on + * displayId passed in. + * + * @return Whether it successfully created a mirror SurfaceControl and replaced the display + * content with the mirror of the Window. + * + * @hide + */ + @TestApi + @RequiresPermission(permission.ACCESS_SURFACE_FLINGER) + default boolean replaceContentOnDisplayWithMirror(int displayId, @NonNull Window window) { + throw new UnsupportedOperationException(); + } + + /** + * @param displayId The displayId to that should have its content replaced. + * @param sc The SurfaceControl that should get rendered onto the displayId passed in. + * + * @return Whether it successfully created a mirror SurfaceControl and replaced the display + * content with the mirror of the Window. + * + * @hide + */ + @TestApi + @RequiresPermission(permission.ACCESS_SURFACE_FLINGER) + default boolean replaceContentOnDisplayWithSc(int displayId, @NonNull SurfaceControl sc) { + throw new UnsupportedOperationException(); + } } diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java index b57163c4e4356..d7b74b3bcfe23 100644 --- a/core/java/android/view/WindowManagerImpl.java +++ b/core/java/android/view/WindowManagerImpl.java @@ -34,6 +34,7 @@ import android.os.Bundle; import android.os.IBinder; import android.os.RemoteException; import android.os.StrictMode; +import android.util.Log; import android.window.ITaskFpsCallback; import android.window.TaskFpsCallback; import android.window.WindowContext; @@ -80,6 +81,8 @@ import java.util.function.IntConsumer; * @hide */ public final class WindowManagerImpl implements WindowManager { + private static final String TAG = "WindowManager"; + @UnsupportedAppUsage private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance(); @UiContext @@ -467,4 +470,42 @@ public final class WindowManagerImpl implements WindowManager { throw e.rethrowFromSystemServer(); } } + + @Override + public boolean replaceContentOnDisplayWithMirror(int displayId, @NonNull Window window) { + View decorView = window.peekDecorView(); + if (decorView == null) { + Log.e(TAG, "replaceContentOnDisplayWithMirror: Window's decorView was null."); + return false; + } + + ViewRootImpl viewRoot = decorView.getViewRootImpl(); + if (viewRoot == null) { + Log.e(TAG, "replaceContentOnDisplayWithMirror: Window's viewRootImpl was null."); + return false; + } + + SurfaceControl sc = viewRoot.getSurfaceControl(); + if (!sc.isValid()) { + Log.e(TAG, "replaceContentOnDisplayWithMirror: Window's SC is invalid."); + return false; + } + return replaceContentOnDisplayWithSc(displayId, SurfaceControl.mirrorSurface(sc)); + } + + @Override + public boolean replaceContentOnDisplayWithSc(int displayId, @NonNull SurfaceControl sc) { + if (!sc.isValid()) { + Log.e(TAG, "replaceContentOnDisplayWithSc: Invalid SC."); + return false; + } + + try { + return WindowManagerGlobal.getWindowManagerService() + .replaceContentOnDisplay(displayId, sc); + } catch (RemoteException e) { + e.rethrowAsRuntimeException(); + } + return false; + } } diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 1dad48f8307de..b3a05f1287722 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -7032,4 +7032,12 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp // Display is the root, so it's not rotated relative to anything. return Surface.ROTATION_0; } + + public void replaceContent(SurfaceControl sc) { + new Transaction().reparent(sc, getSurfaceControl()) + .reparent(mWindowingLayer, null) + .reparent(mOverlayLayer, null) + .reparent(mA11yOverlayLayer, null) + .apply(); + } } diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index c992ed94da17e..db92191e542ad 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -16,6 +16,7 @@ package com.android.server.wm; +import static android.Manifest.permission.ACCESS_SURFACE_FLINGER; import static android.Manifest.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS; import static android.Manifest.permission.INPUT_CONSUMER; import static android.Manifest.permission.INTERNAL_SYSTEM_WINDOW; @@ -24,7 +25,6 @@ import static android.Manifest.permission.MANAGE_APP_TOKENS; import static android.Manifest.permission.MODIFY_TOUCH_MODE_STATE; import static android.Manifest.permission.READ_FRAME_BUFFER; import static android.Manifest.permission.REGISTER_WINDOW_MANAGER_LISTENERS; -import static android.Manifest.permission.RESTRICTED_VR_ACCESS; import static android.Manifest.permission.START_TASKS_FROM_RECENTS; import static android.Manifest.permission.STATUS_BAR_SERVICE; import static android.Manifest.permission.WRITE_SECURE_SETTINGS; @@ -9552,4 +9552,23 @@ public class WindowManagerService extends IWindowManager.Stub return List.copyOf(notifiedApps); } } + + @RequiresPermission(ACCESS_SURFACE_FLINGER) + @Override + public boolean replaceContentOnDisplay(int displayId, SurfaceControl sc) { + if (!checkCallingPermission(ACCESS_SURFACE_FLINGER, + "replaceDisplayContent()")) { + throw new SecurityException("Requires ACCESS_SURFACE_FLINGER permission"); + } + + DisplayContent dc; + synchronized (mGlobalLock) { + dc = mRoot.getDisplayContentOrCreate(displayId); + if (dc == null) { + return false; + } + dc.replaceContent(sc); + return true; + } + } } diff --git a/services/tests/wmtests/AndroidManifest.xml b/services/tests/wmtests/AndroidManifest.xml index 9c793752bdf98..554b0f408ef9e 100644 --- a/services/tests/wmtests/AndroidManifest.xml +++ b/services/tests/wmtests/AndroidManifest.xml @@ -109,10 +109,6 @@ - - - diff --git a/tests/SurfaceViewBufferTests/src/com/android/test/LocalMediaProjectionService.java b/tests/SurfaceViewBufferTests/src/com/android/test/LocalMediaProjectionService.java new file mode 100644 index 0000000000000..7339a6b8c9a40 --- /dev/null +++ b/tests/SurfaceViewBufferTests/src/com/android/test/LocalMediaProjectionService.java @@ -0,0 +1,100 @@ +/* + * 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.test; + +import android.app.Notification; +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.app.Service; +import android.content.Context; +import android.content.Intent; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.drawable.Icon; +import android.os.IBinder; +import android.os.Message; +import android.os.Messenger; +import android.os.RemoteException; + +public class LocalMediaProjectionService extends Service { + + private Bitmap mTestBitmap; + + private static final String NOTIFICATION_CHANNEL_ID = "Surfacevalidator"; + private static final String CHANNEL_NAME = "ProjectionService"; + + static final int MSG_START_FOREGROUND_DONE = 1; + static final String EXTRA_MESSENGER = "messenger"; + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + startForeground(intent); + return super.onStartCommand(intent, flags, startId); + } + + @Override + public IBinder onBind(Intent intent) { + return null; + } + + @Override + public void onDestroy() { + if (mTestBitmap != null) { + mTestBitmap.recycle(); + mTestBitmap = null; + } + super.onDestroy(); + } + + private Icon createNotificationIcon() { + mTestBitmap = Bitmap.createBitmap(50, 50, Bitmap.Config.ARGB_8888); + final Canvas canvas = new Canvas(mTestBitmap); + canvas.drawColor(Color.BLUE); + return Icon.createWithBitmap(mTestBitmap); + } + + private void startForeground(Intent intent) { + final NotificationChannel channel = new NotificationChannel(NOTIFICATION_CHANNEL_ID, + CHANNEL_NAME, NotificationManager.IMPORTANCE_NONE); + channel.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE); + + final NotificationManager notificationManager = + (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); + notificationManager.createNotificationChannel(channel); + + final Notification.Builder notificationBuilder = + new Notification.Builder(this, NOTIFICATION_CHANNEL_ID); + + final Notification notification = notificationBuilder.setOngoing(true) + .setContentTitle("App is running") + .setSmallIcon(createNotificationIcon()) + .setCategory(Notification.CATEGORY_SERVICE) + .setContentText("Context") + .build(); + + startForeground(2, notification); + + final Messenger messenger = intent.getParcelableExtra(EXTRA_MESSENGER); + final Message msg = Message.obtain(); + msg.what = MSG_START_FOREGROUND_DONE; + try { + messenger.send(msg); + } catch (RemoteException e) { + } + } + +} diff --git a/tests/SurfaceViewBufferTests/src/com/android/test/ScreenRecordTestBase.kt b/tests/SurfaceViewBufferTests/src/com/android/test/ScreenRecordTestBase.kt index df3d30e139085..e80dd8ed06902 100644 --- a/tests/SurfaceViewBufferTests/src/com/android/test/ScreenRecordTestBase.kt +++ b/tests/SurfaceViewBufferTests/src/com/android/test/ScreenRecordTestBase.kt @@ -19,7 +19,6 @@ import android.annotation.ColorInt import android.content.Context import android.content.Intent import android.graphics.Rect -import android.server.wm.WindowManagerState.getLogicalDisplaySize import android.view.cts.surfacevalidator.CapturedActivity import android.view.cts.surfacevalidator.ISurfaceValidatorTestCase import android.view.cts.surfacevalidator.PixelChecker @@ -44,8 +43,6 @@ open class ScreenRecordTestBase(useBlastAdapter: Boolean) : mActivity = mActivityRule.launchActivity(Intent()) lateinit var surfaceReadyLatch: CountDownLatch runOnUiThread { - it.dismissPermissionDialog() - it.setLogicalDisplaySize(getLogicalDisplaySize()) surfaceReadyLatch = it.addSurfaceView(defaultBufferSize) } surfaceReadyLatch.await()