Merge "Add TestAPI to replace content on a display." into main
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 7c3d8fb..cf26e12 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -3622,6 +3622,8 @@
     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 072a7f5..bbd8255 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -1044,4 +1044,13 @@
      * @return List of ComponentNames corresponding to the activities that were notified.
     */
     List<ComponentName> 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 d702367..52710bf 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -5701,4 +5701,35 @@
     default @NonNull List<ComponentName> 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 b57163c..d7b74b3 100644
--- a/core/java/android/view/WindowManagerImpl.java
+++ b/core/java/android/view/WindowManagerImpl.java
@@ -34,6 +34,7 @@
 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 @@
  * @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 @@
             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 1dad48f..b3a05f1 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 @@
         // 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 c992ed9..db92191 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.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 @@
             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 9c79375..554b0f4 100644
--- a/services/tests/wmtests/AndroidManifest.xml
+++ b/services/tests/wmtests/AndroidManifest.xml
@@ -109,10 +109,6 @@
             </intent-filter>
         </activity>
 
-        <service android:name="android.view.cts.surfacevalidator.LocalMediaProjectionService"
-            android:foregroundServiceType="mediaProjection"
-            android:enabled="true">
-        </service>
     </application>
 
     <instrumentation
diff --git a/services/tests/wmtests/src/com/android/server/wm/SurfaceViewSyncContinuousTest.java b/services/tests/wmtests/src/com/android/server/wm/SurfaceViewSyncContinuousTest.java
index ad7314c..f958e6f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SurfaceViewSyncContinuousTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SurfaceViewSyncContinuousTest.java
@@ -18,7 +18,6 @@
 
 import static android.server.wm.UiDeviceUtils.pressUnlockButton;
 import static android.server.wm.UiDeviceUtils.pressWakeupButton;
-import static android.server.wm.WindowManagerState.getLogicalDisplaySize;
 
 import android.app.KeyguardManager;
 import android.os.PowerManager;
@@ -46,7 +45,6 @@
     @Before
     public void setup() {
         mCapturedActivity = mActivityRule.getActivity();
-        mCapturedActivity.setLogicalDisplaySize(getLogicalDisplaySize());
 
         final KeyguardManager km = mCapturedActivity.getSystemService(KeyguardManager.class);
         if (km != null && km.isKeyguardLocked() || !Objects.requireNonNull(
diff --git a/tests/SurfaceViewBufferTests/AndroidManifest.xml b/tests/SurfaceViewBufferTests/AndroidManifest.xml
index c910ecd..78415e8 100644
--- a/tests/SurfaceViewBufferTests/AndroidManifest.xml
+++ b/tests/SurfaceViewBufferTests/AndroidManifest.xml
@@ -43,7 +43,7 @@
                 <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
-        <service android:name="android.view.cts.surfacevalidator.LocalMediaProjectionService"
+        <service android:name="com.android.test.LocalMediaProjectionService"
                  android:foregroundServiceType="mediaProjection"
                  android:enabled="true">
         </service>
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 0000000..7339a6b8
--- /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 df3d30e..e80dd8e 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.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 @@
         mActivity = mActivityRule.launchActivity(Intent())
         lateinit var surfaceReadyLatch: CountDownLatch
         runOnUiThread {
-            it.dismissPermissionDialog()
-            it.setLogicalDisplaySize(getLogicalDisplaySize())
             surfaceReadyLatch = it.addSurfaceView(defaultBufferSize)
         }
         surfaceReadyLatch.await()