Loading core/api/current.txt +2 −2 Original line number Diff line number Diff line Loading @@ -26284,9 +26284,9 @@ package android.media.projection { public final class MediaProjection { method public android.hardware.display.VirtualDisplay createVirtualDisplay(@NonNull String, int, int, int, int, @Nullable android.view.Surface, @Nullable android.hardware.display.VirtualDisplay.Callback, @Nullable android.os.Handler); method public void registerCallback(android.media.projection.MediaProjection.Callback, android.os.Handler); method public void registerCallback(@NonNull android.media.projection.MediaProjection.Callback, @Nullable android.os.Handler); method public void stop(); method public void unregisterCallback(android.media.projection.MediaProjection.Callback); method public void unregisterCallback(@NonNull android.media.projection.MediaProjection.Callback); } public abstract static class MediaProjection.Callback { media/java/android/media/projection/MediaProjection.java +121 −71 Original line number Diff line number Diff line Loading @@ -20,43 +20,73 @@ import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.compat.CompatChanges; import android.compat.annotation.ChangeId; import android.compat.annotation.EnabledSince; import android.content.Context; import android.hardware.display.DisplayManager; import android.hardware.display.VirtualDisplay; import android.hardware.display.VirtualDisplayConfig; import android.os.Build; import android.os.Handler; import android.os.IBinder; import android.os.RemoteException; import android.os.ServiceManager; import android.util.ArrayMap; import android.util.Log; import android.util.Slog; import android.view.ContentRecordingSession; import android.view.Surface; import com.android.internal.annotations.VisibleForTesting; import java.util.Map; import java.util.Objects; /** * A token granting applications the ability to capture screen contents and/or * record system audio. The exact capabilities granted depend on the type of * MediaProjection. * * <p> * A screen capture session can be started through {@link * <p>A screen capture session can be started through {@link * MediaProjectionManager#createScreenCaptureIntent}. This grants the ability to * capture screen contents, but not system audio. * </p> */ public final class MediaProjection { private static final String TAG = "MediaProjection"; /** * Requires an app registers a {@link Callback} before invoking * {@link #createVirtualDisplay(String, int, int, int, int, Surface, VirtualDisplay.Callback, * Handler) createVirtualDisplay}. * * <p>Enabled after version 33 (Android T), so applies to target SDK of 34+ (Android U+). * * @hide */ @VisibleForTesting @ChangeId @EnabledSince(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE) static final long MEDIA_PROJECTION_REQUIRES_CALLBACK = 269849258L; // buganizer id private final IMediaProjection mImpl; private final Context mContext; private final Map<Callback, CallbackRecord> mCallbacks; @Nullable private IMediaProjectionManager mProjectionService = null; private final DisplayManager mDisplayManager; private final IMediaProjectionManager mProjectionService; @NonNull private final Map<Callback, CallbackRecord> mCallbacks = new ArrayMap<>(); /** @hide */ public MediaProjection(Context context, IMediaProjection impl) { mCallbacks = new ArrayMap<Callback, CallbackRecord>(); this(context, impl, IMediaProjectionManager.Stub.asInterface( ServiceManager.getService(Context.MEDIA_PROJECTION_SERVICE)), context.getSystemService(DisplayManager.class)); } /** @hide */ @VisibleForTesting public MediaProjection(Context context, IMediaProjection impl, IMediaProjectionManager service, DisplayManager displayManager) { mContext = context; mImpl = impl; try { Loading @@ -64,46 +94,44 @@ public final class MediaProjection { } catch (RemoteException e) { throw new RuntimeException("Failed to start media projection", e); } mProjectionService = service; mDisplayManager = displayManager; } /** * Register a listener to receive notifications about when the {@link MediaProjection} or * captured content changes state. * <p> * The callback should be registered before invoking * * <p>The callback must be registered before invoking * {@link #createVirtualDisplay(String, int, int, int, int, Surface, VirtualDisplay.Callback, * Handler)} * to ensure that any notifications on the callback are not missed. * </p> * Handler)} to ensure that any notifications on the callback are not missed. The client must * implement {@link Callback#onStop()} and clean up any resources it is holding, e.g. the * {@link VirtualDisplay} and {@link Surface}. * * @param callback The callback to call. * @param handler The handler on which the callback should be invoked, or * null if the callback should be invoked on the calling thread's looper. * @throws IllegalArgumentException If the given callback is null. * @throws NullPointerException If the given callback is null. * @see #unregisterCallback */ public void registerCallback(Callback callback, Handler handler) { if (callback == null) { throw new IllegalArgumentException("callback should not be null"); } public void registerCallback(@NonNull Callback callback, @Nullable Handler handler) { final Callback c = Objects.requireNonNull(callback); if (handler == null) { handler = new Handler(); } mCallbacks.put(callback, new CallbackRecord(callback, handler)); mCallbacks.put(c, new CallbackRecord(c, handler)); } /** * Unregister a {@link MediaProjection} listener. * * @param callback The callback to unregister. * @throws IllegalArgumentException If the given callback is null. * @throws NullPointerException If the given callback is null. * @see #registerCallback */ public void unregisterCallback(Callback callback) { if (callback == null) { throw new IllegalArgumentException("callback should not be null"); } mCallbacks.remove(callback); public void unregisterCallback(@NonNull Callback callback) { final Callback c = Objects.requireNonNull(callback); mCallbacks.remove(c); } /** Loading @@ -122,43 +150,55 @@ public final class MediaProjection { if (surface != null) { builder.setSurface(surface); } VirtualDisplay virtualDisplay = createVirtualDisplay(builder, callback, handler); return virtualDisplay; return createVirtualDisplay(builder, callback, handler); } /** * Creates a {@link android.hardware.display.VirtualDisplay} to capture the * contents of the screen. * * @param name The name of the virtual display, must be non-empty. * @param width The width of the virtual display in pixels. Must be * greater than 0. * @param height The height of the virtual display in pixels. Must be * greater than 0. * @param dpi The density of the virtual display in dpi. Must be greater * than 0. * @param surface The surface to which the content of the virtual display * should be rendered, or null if there is none initially. * @param flags A combination of virtual display flags. See {@link DisplayManager} for the full * list of flags. * @param callback Callback to call when the virtual display's state * changes, or null if none. * @param handler The {@link android.os.Handler} on which the callback should be * invoked, or null if the callback should be invoked on the calling * thread's main {@link android.os.Looper}. * <p>To correctly clean up resources associated with a capture, the application must register a * {@link Callback} before invocation. The app must override {@link Callback#onStop()} to clean * up (by invoking{@link VirtualDisplay#release()}, {@link Surface#release()} and related * resources). * * @see android.hardware.display.VirtualDisplay * @param name The name of the virtual display, must be non-empty. * @param width The width of the virtual display in pixels. Must be greater than 0. * @param height The height of the virtual display in pixels. Must be greater than 0. * @param dpi The density of the virtual display in dpi. Must be greater than 0. * @param surface The surface to which the content of the virtual display should be rendered, * or null if there is none initially. * @param flags A combination of virtual display flags. See {@link DisplayManager} for the * full list of flags. * @param callback Callback invoked when the virtual display's state changes, or null. * @param handler The {@link android.os.Handler} on which the callback should be invoked, or * null if the callback should be invoked on the calling thread's main * {@link android.os.Looper}. * @throws IllegalStateException If the target SDK is * {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE U} and * up and no {@link Callback} * is registered. If the target SDK is less than * {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE U}, no * exception is thrown. * @see VirtualDisplay * @see VirtualDisplay.Callback */ public VirtualDisplay createVirtualDisplay(@NonNull String name, int width, int height, int dpi, int flags, @Nullable Surface surface, @Nullable VirtualDisplay.Callback callback, @Nullable Handler handler) { if (shouldMediaProjectionRequireCallback()) { if (mCallbacks.isEmpty()) { throw new IllegalStateException( "Must register a callback before starting capture, to manage resources in" + " response to MediaProjection states."); } } final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(name, width, height, dpi).setFlags(flags); if (surface != null) { builder.setSurface(surface); } VirtualDisplay virtualDisplay = createVirtualDisplay(builder, callback, handler); return virtualDisplay; return createVirtualDisplay(builder, callback, handler); } /** Loading Loading @@ -191,13 +231,21 @@ 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). // Pass in the current session details, so they are guaranteed to only be set in // WindowManagerService 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, final VirtualDisplay virtualDisplay = mDisplayManager.createVirtualDisplay(this, virtualDisplayConfig.build(), callback, handler, windowContext); if (virtualDisplay == null) { // Since WindowManager handling a new display and DisplayManager creating a new // VirtualDisplay is async, WindowManager may have tried to start task recording // and encountered an error that required stopping recording entirely. The // VirtualDisplay would then be null and the MediaProjection is no longer active. Slog.w(TAG, "Failed to create virtual display."); return null; } return virtualDisplay; } catch (RemoteException e) { // Can not capture if WMS is not accessible, so bail out. Loading @@ -205,12 +253,14 @@ public final class MediaProjection { } } private IMediaProjectionManager getProjectionService() { if (mProjectionService == null) { mProjectionService = IMediaProjectionManager.Stub.asInterface( ServiceManager.getService(Context.MEDIA_PROJECTION_SERVICE)); } return mProjectionService; /** * Returns {@code true} when MediaProjection requires the app registers a callback before * beginning to capture via * {@link #createVirtualDisplay(String, int, int, int, int, Surface, VirtualDisplay.Callback, * Handler)}. */ private boolean shouldMediaProjectionRequireCallback() { return CompatChanges.isChangeEnabled(MEDIA_PROJECTION_REQUIRES_CALLBACK); } /** Loading Loading @@ -238,28 +288,26 @@ public final class MediaProjection { public abstract static class Callback { /** * Called when the MediaProjection session is no longer valid. * <p> * Once a MediaProjection has been stopped, it's up to the application to release any * resources it may be holding (e.g. {@link android.hardware.display.VirtualDisplay}s). * </p> * * <p>Once a MediaProjection has been stopped, it's up to the application to release any * resources it may be holding (e.g. releasing the {@link VirtualDisplay} and * {@link Surface}). */ public void onStop() { } /** * Invoked immediately after capture begins or when the size of the captured region changes, * providing the accurate sizing for the streamed capture. * <p> * The given width and height, in pixels, corresponds to the same width and height that * * <p>The given width and height, in pixels, corresponds to the same width and height that * would be returned from {@link android.view.WindowMetrics#getBounds()} of the captured * region. * </p> * <p> * If the recorded content has a different aspect ratio from either the * * <p>If the recorded content has a different aspect ratio from either the * {@link VirtualDisplay} or output {@link Surface}, the captured stream has letterboxing * (black bars) around the recorded content. The application can avoid the letterboxing * around the recorded content by updating the size of both the {@link VirtualDisplay} and * output {@link Surface}: * </p> * * <pre> * @Override Loading @@ -284,13 +332,12 @@ public final class MediaProjection { /** * Invoked immediately after capture begins or when the visibility of the captured region * changes, providing the current visibility of the captured region. * <p> * Applications can take advantage of this callback by showing or hiding the captured * * <p>Applications can take advantage of this callback by showing or hiding the captured * content from the output {@link Surface}, based on if the captured region is currently * visible to the user. * </p> * <p> * For example, if the user elected to capture a single app (from the activity shown from * * <p>For example, if the user elected to capture a single app (from the activity shown from * {@link MediaProjectionManager#createScreenCaptureIntent()}), the following scenarios * trigger the callback: * <ul> Loading @@ -307,7 +354,6 @@ public final class MediaProjection { * captured app, or the user navigates away from the captured app. * </li> * </ul> * </p> */ public void onCapturedContentVisibilityChanged(boolean isVisible) { } } Loading Loading @@ -335,7 +381,7 @@ public final class MediaProjection { } } private final static class CallbackRecord { private static final class CallbackRecord extends Callback { private final Callback mCallback; private final Handler mHandler; Loading @@ -344,6 +390,8 @@ public final class MediaProjection { mHandler = handler; } @Override public void onStop() { mHandler.post(new Runnable() { @Override Loading @@ -353,10 +401,12 @@ public final class MediaProjection { }); } @Override public void onCapturedContentResize(int width, int height) { mHandler.post(() -> mCallback.onCapturedContentResize(width, height)); } @Override public void onCapturedContentVisibilityChanged(boolean isVisible) { mHandler.post(() -> mCallback.onCapturedContentVisibilityChanged(isVisible)); } Loading media/tests/projection/Android.bp +2 −0 Original line number Diff line number Diff line Loading @@ -29,7 +29,9 @@ android_test { "mockito-target-extended-minus-junit4", "platform-test-annotations", "testng", "testables", "truth-prebuilt", "platform-compat-test-rules", ], // Needed for mockito-target-extended-minus-junit4 Loading media/tests/projection/AndroidManifest.xml +1 −0 Original line number Diff line number Diff line Loading @@ -19,6 +19,7 @@ package="android.media.projection.mediaprojectiontests" android:sharedUserId="com.android.uid.test"> <uses-permission android:name="android.permission.READ_COMPAT_CHANGE_CONFIG" /> <uses-permission android:name="android.permission.MANAGE_MEDIA_PROJECTION" /> <application android:debuggable="true" android:testOnly="true"> Loading media/tests/projection/src/android/media/projection/FakeIMediaProjection.java 0 → 100644 +82 −0 Original line number Diff line number Diff line /* * 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 android.media.projection; import android.os.IBinder; import android.os.RemoteException; /** * The connection between MediaProjection and system server is represented by IMediaProjection; * outside the test it is implemented by the system server. */ public final class FakeIMediaProjection extends IMediaProjection.Stub { boolean mIsStarted = false; IBinder mLaunchCookie = null; IMediaProjectionCallback mIMediaProjectionCallback = null; @Override public void start(IMediaProjectionCallback callback) throws RemoteException { mIMediaProjectionCallback = callback; mIsStarted = true; } @Override public void stop() throws RemoteException { // Pass along to the client's callback wrapper. mIMediaProjectionCallback.onStop(); } @Override public boolean canProjectAudio() throws RemoteException { return false; } @Override public boolean canProjectVideo() throws RemoteException { return false; } @Override public boolean canProjectSecureVideo() throws RemoteException { return false; } @Override public int applyVirtualDisplayFlags(int flags) throws RemoteException { return 0; } @Override public void registerCallback(IMediaProjectionCallback callback) throws RemoteException { } @Override public void unregisterCallback(IMediaProjectionCallback callback) throws RemoteException { } @Override public IBinder getLaunchCookie() throws RemoteException { return mLaunchCookie; } @Override public void setLaunchCookie(IBinder launchCookie) throws RemoteException { mLaunchCookie = launchCookie; } } Loading
core/api/current.txt +2 −2 Original line number Diff line number Diff line Loading @@ -26284,9 +26284,9 @@ package android.media.projection { public final class MediaProjection { method public android.hardware.display.VirtualDisplay createVirtualDisplay(@NonNull String, int, int, int, int, @Nullable android.view.Surface, @Nullable android.hardware.display.VirtualDisplay.Callback, @Nullable android.os.Handler); method public void registerCallback(android.media.projection.MediaProjection.Callback, android.os.Handler); method public void registerCallback(@NonNull android.media.projection.MediaProjection.Callback, @Nullable android.os.Handler); method public void stop(); method public void unregisterCallback(android.media.projection.MediaProjection.Callback); method public void unregisterCallback(@NonNull android.media.projection.MediaProjection.Callback); } public abstract static class MediaProjection.Callback {
media/java/android/media/projection/MediaProjection.java +121 −71 Original line number Diff line number Diff line Loading @@ -20,43 +20,73 @@ import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.compat.CompatChanges; import android.compat.annotation.ChangeId; import android.compat.annotation.EnabledSince; import android.content.Context; import android.hardware.display.DisplayManager; import android.hardware.display.VirtualDisplay; import android.hardware.display.VirtualDisplayConfig; import android.os.Build; import android.os.Handler; import android.os.IBinder; import android.os.RemoteException; import android.os.ServiceManager; import android.util.ArrayMap; import android.util.Log; import android.util.Slog; import android.view.ContentRecordingSession; import android.view.Surface; import com.android.internal.annotations.VisibleForTesting; import java.util.Map; import java.util.Objects; /** * A token granting applications the ability to capture screen contents and/or * record system audio. The exact capabilities granted depend on the type of * MediaProjection. * * <p> * A screen capture session can be started through {@link * <p>A screen capture session can be started through {@link * MediaProjectionManager#createScreenCaptureIntent}. This grants the ability to * capture screen contents, but not system audio. * </p> */ public final class MediaProjection { private static final String TAG = "MediaProjection"; /** * Requires an app registers a {@link Callback} before invoking * {@link #createVirtualDisplay(String, int, int, int, int, Surface, VirtualDisplay.Callback, * Handler) createVirtualDisplay}. * * <p>Enabled after version 33 (Android T), so applies to target SDK of 34+ (Android U+). * * @hide */ @VisibleForTesting @ChangeId @EnabledSince(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE) static final long MEDIA_PROJECTION_REQUIRES_CALLBACK = 269849258L; // buganizer id private final IMediaProjection mImpl; private final Context mContext; private final Map<Callback, CallbackRecord> mCallbacks; @Nullable private IMediaProjectionManager mProjectionService = null; private final DisplayManager mDisplayManager; private final IMediaProjectionManager mProjectionService; @NonNull private final Map<Callback, CallbackRecord> mCallbacks = new ArrayMap<>(); /** @hide */ public MediaProjection(Context context, IMediaProjection impl) { mCallbacks = new ArrayMap<Callback, CallbackRecord>(); this(context, impl, IMediaProjectionManager.Stub.asInterface( ServiceManager.getService(Context.MEDIA_PROJECTION_SERVICE)), context.getSystemService(DisplayManager.class)); } /** @hide */ @VisibleForTesting public MediaProjection(Context context, IMediaProjection impl, IMediaProjectionManager service, DisplayManager displayManager) { mContext = context; mImpl = impl; try { Loading @@ -64,46 +94,44 @@ public final class MediaProjection { } catch (RemoteException e) { throw new RuntimeException("Failed to start media projection", e); } mProjectionService = service; mDisplayManager = displayManager; } /** * Register a listener to receive notifications about when the {@link MediaProjection} or * captured content changes state. * <p> * The callback should be registered before invoking * * <p>The callback must be registered before invoking * {@link #createVirtualDisplay(String, int, int, int, int, Surface, VirtualDisplay.Callback, * Handler)} * to ensure that any notifications on the callback are not missed. * </p> * Handler)} to ensure that any notifications on the callback are not missed. The client must * implement {@link Callback#onStop()} and clean up any resources it is holding, e.g. the * {@link VirtualDisplay} and {@link Surface}. * * @param callback The callback to call. * @param handler The handler on which the callback should be invoked, or * null if the callback should be invoked on the calling thread's looper. * @throws IllegalArgumentException If the given callback is null. * @throws NullPointerException If the given callback is null. * @see #unregisterCallback */ public void registerCallback(Callback callback, Handler handler) { if (callback == null) { throw new IllegalArgumentException("callback should not be null"); } public void registerCallback(@NonNull Callback callback, @Nullable Handler handler) { final Callback c = Objects.requireNonNull(callback); if (handler == null) { handler = new Handler(); } mCallbacks.put(callback, new CallbackRecord(callback, handler)); mCallbacks.put(c, new CallbackRecord(c, handler)); } /** * Unregister a {@link MediaProjection} listener. * * @param callback The callback to unregister. * @throws IllegalArgumentException If the given callback is null. * @throws NullPointerException If the given callback is null. * @see #registerCallback */ public void unregisterCallback(Callback callback) { if (callback == null) { throw new IllegalArgumentException("callback should not be null"); } mCallbacks.remove(callback); public void unregisterCallback(@NonNull Callback callback) { final Callback c = Objects.requireNonNull(callback); mCallbacks.remove(c); } /** Loading @@ -122,43 +150,55 @@ public final class MediaProjection { if (surface != null) { builder.setSurface(surface); } VirtualDisplay virtualDisplay = createVirtualDisplay(builder, callback, handler); return virtualDisplay; return createVirtualDisplay(builder, callback, handler); } /** * Creates a {@link android.hardware.display.VirtualDisplay} to capture the * contents of the screen. * * @param name The name of the virtual display, must be non-empty. * @param width The width of the virtual display in pixels. Must be * greater than 0. * @param height The height of the virtual display in pixels. Must be * greater than 0. * @param dpi The density of the virtual display in dpi. Must be greater * than 0. * @param surface The surface to which the content of the virtual display * should be rendered, or null if there is none initially. * @param flags A combination of virtual display flags. See {@link DisplayManager} for the full * list of flags. * @param callback Callback to call when the virtual display's state * changes, or null if none. * @param handler The {@link android.os.Handler} on which the callback should be * invoked, or null if the callback should be invoked on the calling * thread's main {@link android.os.Looper}. * <p>To correctly clean up resources associated with a capture, the application must register a * {@link Callback} before invocation. The app must override {@link Callback#onStop()} to clean * up (by invoking{@link VirtualDisplay#release()}, {@link Surface#release()} and related * resources). * * @see android.hardware.display.VirtualDisplay * @param name The name of the virtual display, must be non-empty. * @param width The width of the virtual display in pixels. Must be greater than 0. * @param height The height of the virtual display in pixels. Must be greater than 0. * @param dpi The density of the virtual display in dpi. Must be greater than 0. * @param surface The surface to which the content of the virtual display should be rendered, * or null if there is none initially. * @param flags A combination of virtual display flags. See {@link DisplayManager} for the * full list of flags. * @param callback Callback invoked when the virtual display's state changes, or null. * @param handler The {@link android.os.Handler} on which the callback should be invoked, or * null if the callback should be invoked on the calling thread's main * {@link android.os.Looper}. * @throws IllegalStateException If the target SDK is * {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE U} and * up and no {@link Callback} * is registered. If the target SDK is less than * {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE U}, no * exception is thrown. * @see VirtualDisplay * @see VirtualDisplay.Callback */ public VirtualDisplay createVirtualDisplay(@NonNull String name, int width, int height, int dpi, int flags, @Nullable Surface surface, @Nullable VirtualDisplay.Callback callback, @Nullable Handler handler) { if (shouldMediaProjectionRequireCallback()) { if (mCallbacks.isEmpty()) { throw new IllegalStateException( "Must register a callback before starting capture, to manage resources in" + " response to MediaProjection states."); } } final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(name, width, height, dpi).setFlags(flags); if (surface != null) { builder.setSurface(surface); } VirtualDisplay virtualDisplay = createVirtualDisplay(builder, callback, handler); return virtualDisplay; return createVirtualDisplay(builder, callback, handler); } /** Loading Loading @@ -191,13 +231,21 @@ 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). // Pass in the current session details, so they are guaranteed to only be set in // WindowManagerService 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, final VirtualDisplay virtualDisplay = mDisplayManager.createVirtualDisplay(this, virtualDisplayConfig.build(), callback, handler, windowContext); if (virtualDisplay == null) { // Since WindowManager handling a new display and DisplayManager creating a new // VirtualDisplay is async, WindowManager may have tried to start task recording // and encountered an error that required stopping recording entirely. The // VirtualDisplay would then be null and the MediaProjection is no longer active. Slog.w(TAG, "Failed to create virtual display."); return null; } return virtualDisplay; } catch (RemoteException e) { // Can not capture if WMS is not accessible, so bail out. Loading @@ -205,12 +253,14 @@ public final class MediaProjection { } } private IMediaProjectionManager getProjectionService() { if (mProjectionService == null) { mProjectionService = IMediaProjectionManager.Stub.asInterface( ServiceManager.getService(Context.MEDIA_PROJECTION_SERVICE)); } return mProjectionService; /** * Returns {@code true} when MediaProjection requires the app registers a callback before * beginning to capture via * {@link #createVirtualDisplay(String, int, int, int, int, Surface, VirtualDisplay.Callback, * Handler)}. */ private boolean shouldMediaProjectionRequireCallback() { return CompatChanges.isChangeEnabled(MEDIA_PROJECTION_REQUIRES_CALLBACK); } /** Loading Loading @@ -238,28 +288,26 @@ public final class MediaProjection { public abstract static class Callback { /** * Called when the MediaProjection session is no longer valid. * <p> * Once a MediaProjection has been stopped, it's up to the application to release any * resources it may be holding (e.g. {@link android.hardware.display.VirtualDisplay}s). * </p> * * <p>Once a MediaProjection has been stopped, it's up to the application to release any * resources it may be holding (e.g. releasing the {@link VirtualDisplay} and * {@link Surface}). */ public void onStop() { } /** * Invoked immediately after capture begins or when the size of the captured region changes, * providing the accurate sizing for the streamed capture. * <p> * The given width and height, in pixels, corresponds to the same width and height that * * <p>The given width and height, in pixels, corresponds to the same width and height that * would be returned from {@link android.view.WindowMetrics#getBounds()} of the captured * region. * </p> * <p> * If the recorded content has a different aspect ratio from either the * * <p>If the recorded content has a different aspect ratio from either the * {@link VirtualDisplay} or output {@link Surface}, the captured stream has letterboxing * (black bars) around the recorded content. The application can avoid the letterboxing * around the recorded content by updating the size of both the {@link VirtualDisplay} and * output {@link Surface}: * </p> * * <pre> * @Override Loading @@ -284,13 +332,12 @@ public final class MediaProjection { /** * Invoked immediately after capture begins or when the visibility of the captured region * changes, providing the current visibility of the captured region. * <p> * Applications can take advantage of this callback by showing or hiding the captured * * <p>Applications can take advantage of this callback by showing or hiding the captured * content from the output {@link Surface}, based on if the captured region is currently * visible to the user. * </p> * <p> * For example, if the user elected to capture a single app (from the activity shown from * * <p>For example, if the user elected to capture a single app (from the activity shown from * {@link MediaProjectionManager#createScreenCaptureIntent()}), the following scenarios * trigger the callback: * <ul> Loading @@ -307,7 +354,6 @@ public final class MediaProjection { * captured app, or the user navigates away from the captured app. * </li> * </ul> * </p> */ public void onCapturedContentVisibilityChanged(boolean isVisible) { } } Loading Loading @@ -335,7 +381,7 @@ public final class MediaProjection { } } private final static class CallbackRecord { private static final class CallbackRecord extends Callback { private final Callback mCallback; private final Handler mHandler; Loading @@ -344,6 +390,8 @@ public final class MediaProjection { mHandler = handler; } @Override public void onStop() { mHandler.post(new Runnable() { @Override Loading @@ -353,10 +401,12 @@ public final class MediaProjection { }); } @Override public void onCapturedContentResize(int width, int height) { mHandler.post(() -> mCallback.onCapturedContentResize(width, height)); } @Override public void onCapturedContentVisibilityChanged(boolean isVisible) { mHandler.post(() -> mCallback.onCapturedContentVisibilityChanged(isVisible)); } Loading
media/tests/projection/Android.bp +2 −0 Original line number Diff line number Diff line Loading @@ -29,7 +29,9 @@ android_test { "mockito-target-extended-minus-junit4", "platform-test-annotations", "testng", "testables", "truth-prebuilt", "platform-compat-test-rules", ], // Needed for mockito-target-extended-minus-junit4 Loading
media/tests/projection/AndroidManifest.xml +1 −0 Original line number Diff line number Diff line Loading @@ -19,6 +19,7 @@ package="android.media.projection.mediaprojectiontests" android:sharedUserId="com.android.uid.test"> <uses-permission android:name="android.permission.READ_COMPAT_CHANGE_CONFIG" /> <uses-permission android:name="android.permission.MANAGE_MEDIA_PROJECTION" /> <application android:debuggable="true" android:testOnly="true"> Loading
media/tests/projection/src/android/media/projection/FakeIMediaProjection.java 0 → 100644 +82 −0 Original line number Diff line number Diff line /* * 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 android.media.projection; import android.os.IBinder; import android.os.RemoteException; /** * The connection between MediaProjection and system server is represented by IMediaProjection; * outside the test it is implemented by the system server. */ public final class FakeIMediaProjection extends IMediaProjection.Stub { boolean mIsStarted = false; IBinder mLaunchCookie = null; IMediaProjectionCallback mIMediaProjectionCallback = null; @Override public void start(IMediaProjectionCallback callback) throws RemoteException { mIMediaProjectionCallback = callback; mIsStarted = true; } @Override public void stop() throws RemoteException { // Pass along to the client's callback wrapper. mIMediaProjectionCallback.onStop(); } @Override public boolean canProjectAudio() throws RemoteException { return false; } @Override public boolean canProjectVideo() throws RemoteException { return false; } @Override public boolean canProjectSecureVideo() throws RemoteException { return false; } @Override public int applyVirtualDisplayFlags(int flags) throws RemoteException { return 0; } @Override public void registerCallback(IMediaProjectionCallback callback) throws RemoteException { } @Override public void unregisterCallback(IMediaProjectionCallback callback) throws RemoteException { } @Override public IBinder getLaunchCookie() throws RemoteException { return mLaunchCookie; } @Override public void setLaunchCookie(IBinder launchCookie) throws RemoteException { mLaunchCookie = launchCookie; } }