diff --git a/core/api/current.txt b/core/api/current.txt index 1b9bac5519f3b..aedad0d748f88 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -25464,6 +25464,7 @@ package android.media.projection { public abstract static class MediaProjection.Callback { ctor public MediaProjection.Callback(); + method public void onCapturedContentResize(int, int); method public void onStop(); } diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json index d4ac531b9c2dd..1ab5e4bef2bbe 100644 --- a/data/etc/services.core.protolog.json +++ b/data/etc/services.core.protolog.json @@ -169,6 +169,12 @@ "group": "WM_ERROR", "at": "com\/android\/server\/wm\/WindowManagerService.java" }, + "-1944652783": { + "message": "Unable to tell MediaProjectionManagerService to stop the active projection: %s", + "level": "ERROR", + "group": "WM_DEBUG_CONTENT_RECORDING", + "at": "com\/android\/server\/wm\/ContentRecorder.java" + }, "-1941440781": { "message": "Creating Pending Move-to-back: %s", "level": "VERBOSE", @@ -715,6 +721,12 @@ "group": "WM_DEBUG_ADD_REMOVE", "at": "com\/android\/server\/wm\/WindowManagerService.java" }, + "-1423223548": { + "message": "Unable to tell MediaProjectionManagerService about resizing the active projection: %s", + "level": "ERROR", + "group": "WM_DEBUG_CONTENT_RECORDING", + "at": "com\/android\/server\/wm\/ContentRecorder.java" + }, "-1421296808": { "message": "Moving to RESUMED: %s (in existing)", "level": "VERBOSE", diff --git a/media/java/android/media/projection/IMediaProjectionCallback.aidl b/media/java/android/media/projection/IMediaProjectionCallback.aidl index f3743d1307e93..2c8de2e4eec1d 100644 --- a/media/java/android/media/projection/IMediaProjectionCallback.aidl +++ b/media/java/android/media/projection/IMediaProjectionCallback.aidl @@ -19,4 +19,5 @@ package android.media.projection; /** {@hide} */ oneway interface IMediaProjectionCallback { void onStop(); + void onCapturedContentResize(int width, int height); } diff --git a/media/java/android/media/projection/IMediaProjectionManager.aidl b/media/java/android/media/projection/IMediaProjectionManager.aidl index 1d58a409718de..a63d02bb11103 100644 --- a/media/java/android/media/projection/IMediaProjectionManager.aidl +++ b/media/java/android/media/projection/IMediaProjectionManager.aidl @@ -27,12 +27,32 @@ import android.view.ContentRecordingSession; interface IMediaProjectionManager { @UnsupportedAppUsage boolean hasProjectionPermission(int uid, String packageName); + + @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest" + + ".permission.MANAGE_MEDIA_PROJECTION)") IMediaProjection createProjection(int uid, String packageName, int type, boolean permanentGrant); + boolean isValidMediaProjection(IMediaProjection projection); + + @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest" + + ".permission.MANAGE_MEDIA_PROJECTION)") MediaProjectionInfo getActiveProjectionInfo(); + + @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest" + + ".permission.MANAGE_MEDIA_PROJECTION)") void stopActiveProjection(); + + @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest" + + ".permission.MANAGE_MEDIA_PROJECTION)") + void notifyActiveProjectionCapturedContentResized(int width, int height); + + @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest" + + ".permission.MANAGE_MEDIA_PROJECTION)") void addCallback(IMediaProjectionWatcherCallback callback); + + @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest" + + ".permission.MANAGE_MEDIA_PROJECTION)") void removeCallback(IMediaProjectionWatcherCallback callback); /** diff --git a/media/java/android/media/projection/MediaProjection.java b/media/java/android/media/projection/MediaProjection.java index ae44fc575f7ce..3dfff1fbfc1b7 100644 --- a/media/java/android/media/projection/MediaProjection.java +++ b/media/java/android/media/projection/MediaProjection.java @@ -234,7 +234,7 @@ public final class MediaProjection { /** * Callbacks for the projection session. */ - public static abstract class Callback { + public abstract static class Callback { /** * Called when the MediaProjection session is no longer valid. *
@@ -243,6 +243,46 @@ public final class MediaProjection { *
*/ public void onStop() { } + + /** + * Indicates the width and height of the captured region in pixels. Called immediately after + * capture begins to provide the app with accurate sizing for the stream. Also called + * when the region captured in this MediaProjection session is resized. + *+ * The given width and height, in pixels, corresponds to the same width and height that + * would be returned from {@link android.view.WindowMetrics#getBounds()} + *
+ *+ * Without the application resizing the {@link VirtualDisplay} (returned from + * {@code MediaProjection#createVirtualDisplay}) and output {@link Surface} (provided + * to {@code MediaProjection#createVirtualDisplay}), the captured stream will have + * letterboxing (black bars) around the recorded content to make up for the + * difference in aspect ratio. + *
+ *+ * The application can prevent the letterboxing by overriding this method, and + * updating the size of both the {@link VirtualDisplay} and output {@link Surface}: + *
+ * + *
+ * @Override
+ * public String onCapturedContentResize(int width, int height) {
+ * // VirtualDisplay instance from MediaProjection#createVirtualDisplay
+ * virtualDisplay.resize(width, height, dpi);
+ *
+ * // Create a new Surface with the updated size (depending on the application's use
+ * // case, this may be through different APIs - see Surface documentation for
+ * // options).
+ * int texName; // the OpenGL texture object name
+ * SurfaceTexture surfaceTexture = new SurfaceTexture(texName);
+ * surfaceTexture.setDefaultBufferSize(width, height);
+ * Surface surface = new Surface(surfaceTexture);
+ *
+ * // Ensure the VirtualDisplay has the updated Surface to send the capture to.
+ * virtualDisplay.setSurface(surface);
+ * }
+ */
+ public void onCapturedContentResize(int width, int height) { }
}
private final class MediaProjectionCallback extends IMediaProjectionCallback.Stub {
@@ -252,6 +292,13 @@ public final class MediaProjection {
cbr.onStop();
}
}
+
+ @Override
+ public void onCapturedContentResize(int width, int height) {
+ for (CallbackRecord cbr : mCallbacks.values()) {
+ cbr.onCapturedContentResize(width, height);
+ }
+ }
}
private final static class CallbackRecord {
@@ -271,5 +318,9 @@ public final class MediaProjection {
}
});
}
+
+ public void onCapturedContentResize(int width, int height) {
+ mHandler.post(() -> mCallback.onCapturedContentResize(width, height));
+ }
}
}
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index fe40bd60bc017..9b433cf654b02 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -11420,6 +11420,11 @@ public class AudioService extends IAudioService.Stub
public void onStop() {
unregisterAudioPolicyAsync(mPolicyCallback);
}
+
+ @Override
+ public void onCapturedContentResize(int width, int height) {
+ // Ignore resize of the captured content.
+ }
};
UnregisterOnStopCallback mProjectionCallback;
diff --git a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
index a118b2fa37c31..7c647cf6f4aa4 100644
--- a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
@@ -599,6 +599,15 @@ public class VirtualDisplayAdapter extends DisplayAdapter {
handleMediaProjectionStoppedLocked(mAppToken);
}
}
+
+ @Override
+ public void onCapturedContentResize(int width, int height) {
+ // Do nothing when we tell the client that the content is resized - it is up to them
+ // to decide to update the VirtualDisplay and Surface.
+ // We could only update the VirtualDisplay size, anyway (which the client wouldn't
+ // expect), and there will still be letterboxing on the output content since the
+ // Surface and VirtualDisplay would then have different aspect ratios.
+ }
}
@VisibleForTesting
diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
index ed8d852ad2da1..50e1fca138776 100644
--- a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
+++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
@@ -315,7 +315,7 @@ public final class MediaProjectionManagerService extends SystemService
@Override // Binder call
public boolean isValidMediaProjection(IMediaProjection projection) {
return MediaProjectionManagerService.this.isValidMediaProjection(
- projection.asBinder());
+ projection == null ? null : projection.asBinder());
}
@Override // Binder call
@@ -348,7 +348,26 @@ public final class MediaProjectionManagerService extends SystemService
} finally {
Binder.restoreCallingIdentity(token);
}
+ }
+ @Override // Binder call
+ public void notifyActiveProjectionCapturedContentResized(int width, int height) {
+ if (mContext.checkCallingOrSelfPermission(Manifest.permission.MANAGE_MEDIA_PROJECTION)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Requires MANAGE_MEDIA_PROJECTION in order to notify "
+ + "on captured content resize");
+ }
+ if (!isValidMediaProjection(mProjectionGrant)) {
+ return;
+ }
+ final long token = Binder.clearCallingIdentity();
+ try {
+ if (mProjectionGrant != null && mCallbackDelegate != null) {
+ mCallbackDelegate.dispatchResize(mProjectionGrant, width, height);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
}
@Override //Binder call
@@ -659,9 +678,11 @@ public final class MediaProjectionManagerService extends SystemService
private static class CallbackDelegate {
private Map