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()