Merge "Add TestAPI to replace content on a display." into main
This commit is contained in:
committed by
Android (Google) Code Review
commit
c0cd2efb1d
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,10 +109,6 @@
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<service android:name="android.view.cts.surfacevalidator.LocalMediaProjectionService"
|
||||
android:foregroundServiceType="mediaProjection"
|
||||
android:enabled="true">
|
||||
</service>
|
||||
</application>
|
||||
|
||||
<instrumentation
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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) {
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user