Loading core/java/android/hardware/display/VirtualDisplayConfig.java +37 −2 Original line number Diff line number Diff line Loading @@ -28,6 +28,7 @@ import android.os.Handler; import android.os.Parcel; import android.os.Parcelable; import android.util.ArraySet; import android.view.ContentRecordingSession; import android.view.Display; import android.view.Surface; Loading @@ -53,6 +54,8 @@ public final class VirtualDisplayConfig implements Parcelable { private final int mDisplayIdToMirror; private final boolean mWindowManagerMirroringEnabled; private ArraySet<String> mDisplayCategories = null; @Nullable private ContentRecordingSession mContentRecordingSession; private final float mRequestedRefreshRate; private VirtualDisplayConfig( Loading @@ -65,6 +68,7 @@ public final class VirtualDisplayConfig implements Parcelable { @Nullable String uniqueId, int displayIdToMirror, boolean windowManagerMirroringEnabled, ContentRecordingSession session, @NonNull ArraySet<String> displayCategories, float requestedRefreshRate) { mName = name; Loading @@ -76,6 +80,7 @@ public final class VirtualDisplayConfig implements Parcelable { mUniqueId = uniqueId; mDisplayIdToMirror = displayIdToMirror; mWindowManagerMirroringEnabled = windowManagerMirroringEnabled; mContentRecordingSession = session; mDisplayCategories = displayCategories; mRequestedRefreshRate = requestedRefreshRate; } Loading Loading @@ -155,6 +160,17 @@ public final class VirtualDisplayConfig implements Parcelable { return mWindowManagerMirroringEnabled; } /** * Returns the recording session associated with this VirtualDisplay. Only used for * recording via {@link MediaProjection}. * * @hide */ @Nullable public ContentRecordingSession getContentRecordingSession() { return mContentRecordingSession; } /** * Returns the display categories. * Loading Loading @@ -186,6 +202,7 @@ public final class VirtualDisplayConfig implements Parcelable { dest.writeString8(mUniqueId); dest.writeInt(mDisplayIdToMirror); dest.writeBoolean(mWindowManagerMirroringEnabled); dest.writeTypedObject(mContentRecordingSession, flags); dest.writeArraySet(mDisplayCategories); dest.writeFloat(mRequestedRefreshRate); } Loading @@ -211,6 +228,7 @@ public final class VirtualDisplayConfig implements Parcelable { && Objects.equals(mUniqueId, that.mUniqueId) && mDisplayIdToMirror == that.mDisplayIdToMirror && mWindowManagerMirroringEnabled == that.mWindowManagerMirroringEnabled && Objects.equals(mContentRecordingSession, that.mContentRecordingSession) && Objects.equals(mDisplayCategories, that.mDisplayCategories) && mRequestedRefreshRate == that.mRequestedRefreshRate; } Loading @@ -219,8 +237,8 @@ public final class VirtualDisplayConfig implements Parcelable { public int hashCode() { int hashCode = Objects.hash( mName, mWidth, mHeight, mDensityDpi, mFlags, mSurface, mUniqueId, mDisplayIdToMirror, mWindowManagerMirroringEnabled, mDisplayCategories, mRequestedRefreshRate); mDisplayIdToMirror, mWindowManagerMirroringEnabled, mContentRecordingSession, mDisplayCategories, mRequestedRefreshRate); return hashCode; } Loading @@ -237,6 +255,7 @@ public final class VirtualDisplayConfig implements Parcelable { + " mUniqueId=" + mUniqueId + " mDisplayIdToMirror=" + mDisplayIdToMirror + " mWindowManagerMirroringEnabled=" + mWindowManagerMirroringEnabled + " mContentRecordingSession=" + mContentRecordingSession + " mDisplayCategories=" + mDisplayCategories + " mRequestedRefreshRate=" + mRequestedRefreshRate + ")"; Loading @@ -252,6 +271,7 @@ public final class VirtualDisplayConfig implements Parcelable { mUniqueId = in.readString8(); mDisplayIdToMirror = in.readInt(); mWindowManagerMirroringEnabled = in.readBoolean(); mContentRecordingSession = in.readTypedObject(ContentRecordingSession.CREATOR); mDisplayCategories = (ArraySet<String>) in.readArraySet(null); mRequestedRefreshRate = in.readFloat(); } Loading Loading @@ -283,6 +303,8 @@ public final class VirtualDisplayConfig implements Parcelable { private String mUniqueId = null; private int mDisplayIdToMirror = DEFAULT_DISPLAY; private boolean mWindowManagerMirroringEnabled = false; @Nullable private ContentRecordingSession mContentRecordingSession; private ArraySet<String> mDisplayCategories = new ArraySet<>(); private float mRequestedRefreshRate = 0.0f; Loading Loading @@ -374,6 +396,18 @@ public final class VirtualDisplayConfig implements Parcelable { return this; } /** * Sets the recording session associated with this {@link VirtualDisplay}. Only used for * recording via {@link MediaProjection}. * * @hide */ @NonNull public Builder setContentRecordingSession(@Nullable ContentRecordingSession session) { mContentRecordingSession = session; return this; } /** * Sets the display categories. * Loading Loading @@ -435,6 +469,7 @@ public final class VirtualDisplayConfig implements Parcelable { mUniqueId, mDisplayIdToMirror, mWindowManagerMirroringEnabled, mContentRecordingSession, mDisplayCategories, mRequestedRefreshRate); } Loading media/java/android/media/projection/MediaProjection.java +3 −10 Original line number Diff line number Diff line Loading @@ -191,20 +191,13 @@ public final class MediaProjection { } else { session = ContentRecordingSession.createTaskSession(launchCookie); } // Pass in the current session details, so they are guaranteed to only be set in WMS // AFTER a VirtualDisplay is constructed (assuming there are no errors during set-up). virtualDisplayConfig.setContentRecordingSession(session); virtualDisplayConfig.setWindowManagerMirroringEnabled(true); final DisplayManager dm = mContext.getSystemService(DisplayManager.class); final VirtualDisplay virtualDisplay = dm.createVirtualDisplay(this, virtualDisplayConfig.build(), callback, handler, windowContext); if (virtualDisplay == null) { // Since WM handling a new display and DM creating a new VirtualDisplay is async, // WM may have tried to start task recording and encountered an error that required // stopping recording entirely. The VirtualDisplay would then be null when the // MediaProjection is no longer active. return null; } session.setDisplayId(virtualDisplay.getDisplay().getDisplayId()); // Successfully set up, so save the current session details. getProjectionService().setContentRecordingSession(session, mImpl); return virtualDisplay; } catch (RemoteException e) { // Can not capture if WMS is not accessible, so bail out. Loading services/core/java/com/android/server/display/DisplayManagerService.java +42 −4 Original line number Diff line number Diff line Loading @@ -128,6 +128,7 @@ import android.util.Slog; import android.util.SparseArray; import android.util.SparseIntArray; import android.util.Spline; import android.view.ContentRecordingSession; import android.view.Display; import android.view.DisplayEventReceiver; import android.view.DisplayInfo; Loading Loading @@ -250,6 +251,7 @@ public final class DisplayManagerService extends SystemService { private ActivityManagerInternal mActivityManagerInternal; private ActivityManager mActivityManager; private UidImportanceListener mUidImportanceListener = new UidImportanceListener(); @Nullable private IMediaProjectionManager mProjectionService; private DeviceStateManagerInternal mDeviceStateManager; @GuardedBy("mSyncRoot") Loading Loading @@ -1494,8 +1496,9 @@ public final class DisplayManagerService extends SystemService { final long token = Binder.clearCallingIdentity(); try { final int displayId; synchronized (mSyncRoot) { final int displayId = displayId = createVirtualDisplayLocked( callback, projection, Loading @@ -1509,8 +1512,39 @@ public final class DisplayManagerService extends SystemService { mDisplayWindowPolicyControllers.put( displayId, Pair.create(virtualDevice, dwpc)); } return displayId; } // When calling setContentRecordingSession into the WindowManagerService, the WMS // attempts to acquire a lock before executing its main body. Due to this, we need // to be sure that it isn't called while the DisplayManagerService is also holding // a lock, to avoid a deadlock scenario. final ContentRecordingSession session = virtualDisplayConfig.getContentRecordingSession(); if (displayId != Display.INVALID_DISPLAY && session != null) { // Only attempt to set content recording session if there are details to set and a // VirtualDisplay has been successfully constructed. session.setDisplayId(displayId); // We set the content recording session here on the server side instead of using // a second AIDL call in MediaProjection. By ensuring that a virtual display has // been constructed before calling setContentRecordingSession, we avoid a race // condition between the DMS & WMS which could lead to the MediaProjection // being pre-emptively torn down. if (!mWindowManagerInternal.setContentRecordingSession(session)) { // Unable to start mirroring, so tear down projection & release VirtualDisplay. try { getProjectionService().stopActiveProjection(); } catch (RemoteException e) { Slog.e(TAG, "Unable to tell MediaProjectionManagerService to stop the " + "active projection", e); } releaseVirtualDisplayInternal(callback.asBinder()); return Display.INVALID_DISPLAY; } } return displayId; } finally { Binder.restoreCallingIdentity(token); } Loading Loading @@ -2804,8 +2838,7 @@ public final class DisplayManagerService extends SystemService { private IMediaProjectionManager getProjectionService() { if (mProjectionService == null) { IBinder b = ServiceManager.getService(Context.MEDIA_PROJECTION_SERVICE); mProjectionService = IMediaProjectionManager.Stub.asInterface(b); mProjectionService = mInjector.getProjectionService(); } return mProjectionService; } Loading Loading @@ -2964,6 +2997,11 @@ public final class DisplayManagerService extends SystemService { boolean getHdrOutputConversionSupport() { return DisplayControl.getHdrOutputConversionSupport(); } IMediaProjectionManager getProjectionService() { IBinder b = ServiceManager.getService(Context.MEDIA_PROJECTION_SERVICE); return IMediaProjectionManager.Stub.asInterface(b); } } @VisibleForTesting Loading services/core/java/com/android/server/display/VirtualDisplayAdapter.java +19 −2 Original line number Diff line number Diff line Loading @@ -88,7 +88,17 @@ public class VirtualDisplayAdapter extends DisplayAdapter { // Called with SyncRoot lock held. public VirtualDisplayAdapter(DisplayManagerService.SyncRoot syncRoot, Context context, Handler handler, Listener listener) { this(syncRoot, context, handler, listener, DisplayControl::createDisplay); this(syncRoot, context, handler, listener, new SurfaceControlDisplayFactory() { @Override public IBinder createDisplay(String name, boolean secure, float requestedRefreshRate) { return DisplayControl.createDisplay(name, secure, requestedRefreshRate); } @Override public void destroyDisplay(IBinder displayToken) { DisplayControl.destroyDisplay(displayToken); } }); } @VisibleForTesting Loading Loading @@ -311,7 +321,7 @@ public class VirtualDisplayAdapter extends DisplayAdapter { mSurface.release(); mSurface = null; } DisplayControl.destroyDisplay(getDisplayTokenLocked()); mSurfaceControlDisplayFactory.destroyDisplay(getDisplayTokenLocked()); if (mProjection != null && mMediaProjectionCallback != null) { try { mProjection.unregisterCallback(mMediaProjectionCallback); Loading Loading @@ -653,5 +663,12 @@ public class VirtualDisplayAdapter extends DisplayAdapter { * @return The token reference for the display in SurfaceFlinger. */ IBinder createDisplay(String name, boolean secure, float requestedRefreshRate); /** * Destroy a display in SurfaceFlinger. * * @param displayToken The display token for the display to be destroyed. */ void destroyDisplay(IBinder displayToken); } } services/tests/servicestests/AndroidManifest.xml +1 −0 Original line number Diff line number Diff line Loading @@ -108,6 +108,7 @@ <uses-permission android:name="android.permission.UPDATE_LOCK_TASK_PACKAGES" /> <uses-permission android:name="android.permission.ACCESS_CONTEXT_HUB" /> <uses-permission android:name="android.permission.USE_BIOMETRIC_INTERNAL" /> <uses-permission android:name="android.permission.MANAGE_MEDIA_PROJECTION" /> <queries> <package android:name="com.android.servicestests.apps.suspendtestapp" /> Loading Loading
core/java/android/hardware/display/VirtualDisplayConfig.java +37 −2 Original line number Diff line number Diff line Loading @@ -28,6 +28,7 @@ import android.os.Handler; import android.os.Parcel; import android.os.Parcelable; import android.util.ArraySet; import android.view.ContentRecordingSession; import android.view.Display; import android.view.Surface; Loading @@ -53,6 +54,8 @@ public final class VirtualDisplayConfig implements Parcelable { private final int mDisplayIdToMirror; private final boolean mWindowManagerMirroringEnabled; private ArraySet<String> mDisplayCategories = null; @Nullable private ContentRecordingSession mContentRecordingSession; private final float mRequestedRefreshRate; private VirtualDisplayConfig( Loading @@ -65,6 +68,7 @@ public final class VirtualDisplayConfig implements Parcelable { @Nullable String uniqueId, int displayIdToMirror, boolean windowManagerMirroringEnabled, ContentRecordingSession session, @NonNull ArraySet<String> displayCategories, float requestedRefreshRate) { mName = name; Loading @@ -76,6 +80,7 @@ public final class VirtualDisplayConfig implements Parcelable { mUniqueId = uniqueId; mDisplayIdToMirror = displayIdToMirror; mWindowManagerMirroringEnabled = windowManagerMirroringEnabled; mContentRecordingSession = session; mDisplayCategories = displayCategories; mRequestedRefreshRate = requestedRefreshRate; } Loading Loading @@ -155,6 +160,17 @@ public final class VirtualDisplayConfig implements Parcelable { return mWindowManagerMirroringEnabled; } /** * Returns the recording session associated with this VirtualDisplay. Only used for * recording via {@link MediaProjection}. * * @hide */ @Nullable public ContentRecordingSession getContentRecordingSession() { return mContentRecordingSession; } /** * Returns the display categories. * Loading Loading @@ -186,6 +202,7 @@ public final class VirtualDisplayConfig implements Parcelable { dest.writeString8(mUniqueId); dest.writeInt(mDisplayIdToMirror); dest.writeBoolean(mWindowManagerMirroringEnabled); dest.writeTypedObject(mContentRecordingSession, flags); dest.writeArraySet(mDisplayCategories); dest.writeFloat(mRequestedRefreshRate); } Loading @@ -211,6 +228,7 @@ public final class VirtualDisplayConfig implements Parcelable { && Objects.equals(mUniqueId, that.mUniqueId) && mDisplayIdToMirror == that.mDisplayIdToMirror && mWindowManagerMirroringEnabled == that.mWindowManagerMirroringEnabled && Objects.equals(mContentRecordingSession, that.mContentRecordingSession) && Objects.equals(mDisplayCategories, that.mDisplayCategories) && mRequestedRefreshRate == that.mRequestedRefreshRate; } Loading @@ -219,8 +237,8 @@ public final class VirtualDisplayConfig implements Parcelable { public int hashCode() { int hashCode = Objects.hash( mName, mWidth, mHeight, mDensityDpi, mFlags, mSurface, mUniqueId, mDisplayIdToMirror, mWindowManagerMirroringEnabled, mDisplayCategories, mRequestedRefreshRate); mDisplayIdToMirror, mWindowManagerMirroringEnabled, mContentRecordingSession, mDisplayCategories, mRequestedRefreshRate); return hashCode; } Loading @@ -237,6 +255,7 @@ public final class VirtualDisplayConfig implements Parcelable { + " mUniqueId=" + mUniqueId + " mDisplayIdToMirror=" + mDisplayIdToMirror + " mWindowManagerMirroringEnabled=" + mWindowManagerMirroringEnabled + " mContentRecordingSession=" + mContentRecordingSession + " mDisplayCategories=" + mDisplayCategories + " mRequestedRefreshRate=" + mRequestedRefreshRate + ")"; Loading @@ -252,6 +271,7 @@ public final class VirtualDisplayConfig implements Parcelable { mUniqueId = in.readString8(); mDisplayIdToMirror = in.readInt(); mWindowManagerMirroringEnabled = in.readBoolean(); mContentRecordingSession = in.readTypedObject(ContentRecordingSession.CREATOR); mDisplayCategories = (ArraySet<String>) in.readArraySet(null); mRequestedRefreshRate = in.readFloat(); } Loading Loading @@ -283,6 +303,8 @@ public final class VirtualDisplayConfig implements Parcelable { private String mUniqueId = null; private int mDisplayIdToMirror = DEFAULT_DISPLAY; private boolean mWindowManagerMirroringEnabled = false; @Nullable private ContentRecordingSession mContentRecordingSession; private ArraySet<String> mDisplayCategories = new ArraySet<>(); private float mRequestedRefreshRate = 0.0f; Loading Loading @@ -374,6 +396,18 @@ public final class VirtualDisplayConfig implements Parcelable { return this; } /** * Sets the recording session associated with this {@link VirtualDisplay}. Only used for * recording via {@link MediaProjection}. * * @hide */ @NonNull public Builder setContentRecordingSession(@Nullable ContentRecordingSession session) { mContentRecordingSession = session; return this; } /** * Sets the display categories. * Loading Loading @@ -435,6 +469,7 @@ public final class VirtualDisplayConfig implements Parcelable { mUniqueId, mDisplayIdToMirror, mWindowManagerMirroringEnabled, mContentRecordingSession, mDisplayCategories, mRequestedRefreshRate); } Loading
media/java/android/media/projection/MediaProjection.java +3 −10 Original line number Diff line number Diff line Loading @@ -191,20 +191,13 @@ public final class MediaProjection { } else { session = ContentRecordingSession.createTaskSession(launchCookie); } // Pass in the current session details, so they are guaranteed to only be set in WMS // AFTER a VirtualDisplay is constructed (assuming there are no errors during set-up). virtualDisplayConfig.setContentRecordingSession(session); virtualDisplayConfig.setWindowManagerMirroringEnabled(true); final DisplayManager dm = mContext.getSystemService(DisplayManager.class); final VirtualDisplay virtualDisplay = dm.createVirtualDisplay(this, virtualDisplayConfig.build(), callback, handler, windowContext); if (virtualDisplay == null) { // Since WM handling a new display and DM creating a new VirtualDisplay is async, // WM may have tried to start task recording and encountered an error that required // stopping recording entirely. The VirtualDisplay would then be null when the // MediaProjection is no longer active. return null; } session.setDisplayId(virtualDisplay.getDisplay().getDisplayId()); // Successfully set up, so save the current session details. getProjectionService().setContentRecordingSession(session, mImpl); return virtualDisplay; } catch (RemoteException e) { // Can not capture if WMS is not accessible, so bail out. Loading
services/core/java/com/android/server/display/DisplayManagerService.java +42 −4 Original line number Diff line number Diff line Loading @@ -128,6 +128,7 @@ import android.util.Slog; import android.util.SparseArray; import android.util.SparseIntArray; import android.util.Spline; import android.view.ContentRecordingSession; import android.view.Display; import android.view.DisplayEventReceiver; import android.view.DisplayInfo; Loading Loading @@ -250,6 +251,7 @@ public final class DisplayManagerService extends SystemService { private ActivityManagerInternal mActivityManagerInternal; private ActivityManager mActivityManager; private UidImportanceListener mUidImportanceListener = new UidImportanceListener(); @Nullable private IMediaProjectionManager mProjectionService; private DeviceStateManagerInternal mDeviceStateManager; @GuardedBy("mSyncRoot") Loading Loading @@ -1494,8 +1496,9 @@ public final class DisplayManagerService extends SystemService { final long token = Binder.clearCallingIdentity(); try { final int displayId; synchronized (mSyncRoot) { final int displayId = displayId = createVirtualDisplayLocked( callback, projection, Loading @@ -1509,8 +1512,39 @@ public final class DisplayManagerService extends SystemService { mDisplayWindowPolicyControllers.put( displayId, Pair.create(virtualDevice, dwpc)); } return displayId; } // When calling setContentRecordingSession into the WindowManagerService, the WMS // attempts to acquire a lock before executing its main body. Due to this, we need // to be sure that it isn't called while the DisplayManagerService is also holding // a lock, to avoid a deadlock scenario. final ContentRecordingSession session = virtualDisplayConfig.getContentRecordingSession(); if (displayId != Display.INVALID_DISPLAY && session != null) { // Only attempt to set content recording session if there are details to set and a // VirtualDisplay has been successfully constructed. session.setDisplayId(displayId); // We set the content recording session here on the server side instead of using // a second AIDL call in MediaProjection. By ensuring that a virtual display has // been constructed before calling setContentRecordingSession, we avoid a race // condition between the DMS & WMS which could lead to the MediaProjection // being pre-emptively torn down. if (!mWindowManagerInternal.setContentRecordingSession(session)) { // Unable to start mirroring, so tear down projection & release VirtualDisplay. try { getProjectionService().stopActiveProjection(); } catch (RemoteException e) { Slog.e(TAG, "Unable to tell MediaProjectionManagerService to stop the " + "active projection", e); } releaseVirtualDisplayInternal(callback.asBinder()); return Display.INVALID_DISPLAY; } } return displayId; } finally { Binder.restoreCallingIdentity(token); } Loading Loading @@ -2804,8 +2838,7 @@ public final class DisplayManagerService extends SystemService { private IMediaProjectionManager getProjectionService() { if (mProjectionService == null) { IBinder b = ServiceManager.getService(Context.MEDIA_PROJECTION_SERVICE); mProjectionService = IMediaProjectionManager.Stub.asInterface(b); mProjectionService = mInjector.getProjectionService(); } return mProjectionService; } Loading Loading @@ -2964,6 +2997,11 @@ public final class DisplayManagerService extends SystemService { boolean getHdrOutputConversionSupport() { return DisplayControl.getHdrOutputConversionSupport(); } IMediaProjectionManager getProjectionService() { IBinder b = ServiceManager.getService(Context.MEDIA_PROJECTION_SERVICE); return IMediaProjectionManager.Stub.asInterface(b); } } @VisibleForTesting Loading
services/core/java/com/android/server/display/VirtualDisplayAdapter.java +19 −2 Original line number Diff line number Diff line Loading @@ -88,7 +88,17 @@ public class VirtualDisplayAdapter extends DisplayAdapter { // Called with SyncRoot lock held. public VirtualDisplayAdapter(DisplayManagerService.SyncRoot syncRoot, Context context, Handler handler, Listener listener) { this(syncRoot, context, handler, listener, DisplayControl::createDisplay); this(syncRoot, context, handler, listener, new SurfaceControlDisplayFactory() { @Override public IBinder createDisplay(String name, boolean secure, float requestedRefreshRate) { return DisplayControl.createDisplay(name, secure, requestedRefreshRate); } @Override public void destroyDisplay(IBinder displayToken) { DisplayControl.destroyDisplay(displayToken); } }); } @VisibleForTesting Loading Loading @@ -311,7 +321,7 @@ public class VirtualDisplayAdapter extends DisplayAdapter { mSurface.release(); mSurface = null; } DisplayControl.destroyDisplay(getDisplayTokenLocked()); mSurfaceControlDisplayFactory.destroyDisplay(getDisplayTokenLocked()); if (mProjection != null && mMediaProjectionCallback != null) { try { mProjection.unregisterCallback(mMediaProjectionCallback); Loading Loading @@ -653,5 +663,12 @@ public class VirtualDisplayAdapter extends DisplayAdapter { * @return The token reference for the display in SurfaceFlinger. */ IBinder createDisplay(String name, boolean secure, float requestedRefreshRate); /** * Destroy a display in SurfaceFlinger. * * @param displayToken The display token for the display to be destroyed. */ void destroyDisplay(IBinder displayToken); } }
services/tests/servicestests/AndroidManifest.xml +1 −0 Original line number Diff line number Diff line Loading @@ -108,6 +108,7 @@ <uses-permission android:name="android.permission.UPDATE_LOCK_TASK_PACKAGES" /> <uses-permission android:name="android.permission.ACCESS_CONTEXT_HUB" /> <uses-permission android:name="android.permission.USE_BIOMETRIC_INTERNAL" /> <uses-permission android:name="android.permission.MANAGE_MEDIA_PROJECTION" /> <queries> <package android:name="com.android.servicestests.apps.suspendtestapp" /> Loading