diff options
author | 2023-05-23 20:51:44 +0000 | |
---|---|---|
committer | 2023-07-12 22:15:21 +0000 | |
commit | 2445f85894d582ee440541d17c6929c03249ba67 (patch) | |
tree | dc2234ef1c27c2efacbc553a776e6f7272a160eb | |
parent | 919cffbb37075dc080824dd89433a23c5c6e1c81 (diff) |
Add TestAPI to replace content on a display.
The current tests use MediaProjection to test that content is updated
frame by frame. However, this causes several issues, like having to
accept the permission dialog, having to wait for app launch animations
to complete, content overlaying the test area. Instead, the tests only
need to capture the window being tested. To do this, the test app
creates a VirtualDisplay and then swaps the content of the display with
a mirror of the window being tested. This way, the frames received in
the VD only contain the content in the window mirror and nothing else on
screen.
This reduces the test time since we don't need to wait for a foreground
service and the permission dialog to be clicked, nor does it have to
wait for any animations to complete. The test can start as soon as the
window is placed on screen and the VirtualDisplay contains the mirror.
It also reduces flakes becuase there's no chance of other content
showing over the test area so the pixels in the VD are deterministic.
Test: SurfaceSyncGroupContinuousTest
Test: ASurfaceControlBackPressureTest
Test: SurfacePackageFlickerTest
Test: AttachedSurfaceControlSyncTest
Test: SurfaceViewSyncTest
Test: SurfaceViewSyncContinuousTest
Bug: 282169297
Bug: 288339794
Change-Id: I311a30f8e16b99034b9a662fe2755630d68fcb80
11 files changed, 212 insertions, 11 deletions
diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 7c3d8fb856a4..cf26e12dc761 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 072a7f5ea304..bbd8255f4978 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<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 d702367965a1..52710bf59761 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<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 b57163c4e435..d7b74b3bcfe2 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 bfd2a10a8882..cbfed6c78a00 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 8dc2840cdc19..fa279a893319 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; @@ -9550,4 +9550,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 9c793752bdf9..554b0f408ef9 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 ad7314c9bc60..f958e6f98fcd 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 @@ package com.android.server.wm; 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 @@ public class SurfaceViewSyncContinuousTest { @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 c910ecdac1b3..78415e8641eb 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 000000000000..7339a6b8c9a4 --- /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 df3d30e13908..e80dd8ed0690 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() |