Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 2fe8b8b6 authored by Kevin Rocard's avatar Kevin Rocard
Browse files

Public Audio playback capture must have a valid projection



For privacy, require the app wanting to capture other app audio to have
a valid MediaProjection.

Test: adb shell audiorecorder --target /data/file.raw
Bug: 111453086
Change-Id: I1323048fe308282d3719e38915818a0da17567de
Signed-off-by: default avatarKevin Rocard <krocard@google.com>
parent 73cee8c4
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -23383,7 +23383,7 @@ package android.media {
  }
  public static final class AudioPlaybackCaptureConfiguration.Builder {
    ctor public AudioPlaybackCaptureConfiguration.Builder();
    ctor public AudioPlaybackCaptureConfiguration.Builder(@NonNull android.media.projection.MediaProjection);
    method public android.media.AudioPlaybackCaptureConfiguration.Builder addMatchingUid(int);
    method public android.media.AudioPlaybackCaptureConfiguration.Builder addMatchingUsage(@NonNull android.media.AudioAttributes);
    method public android.media.AudioPlaybackCaptureConfiguration build();
+4 −1
Original line number Diff line number Diff line
@@ -36,6 +36,7 @@ import android.content.Context;
import android.content.Intent;
import android.media.audiopolicy.AudioPolicy;
import android.media.audiopolicy.AudioPolicy.AudioPolicyFocusListener;
import android.media.projection.MediaProjection;
import android.media.session.MediaController;
import android.media.session.MediaSession;
import android.media.session.MediaSessionLegacyHelper;
@@ -3197,8 +3198,10 @@ public class AudioManager {
        }
        final IAudioService service = getService();
        try {
            MediaProjection projection = policy.getMediaProjection();
            String regId = service.registerAudioPolicy(policy.getConfig(), policy.cb(),
                    policy.hasFocusListener(), policy.isFocusPolicy(), policy.isVolumeController());
                    policy.hasFocusListener(), policy.isFocusPolicy(), policy.isVolumeController(),
                    projection == null ? null : projection.getProjection());
            if (regId == null) {
                return ERROR;
            } else {
+42 −6
Original line number Diff line number Diff line
@@ -19,34 +19,52 @@ package android.media;
import android.annotation.NonNull;
import android.media.audiopolicy.AudioMix;
import android.media.audiopolicy.AudioMixingRule;
import android.media.projection.MediaProjection;
import android.os.RemoteException;

import com.android.internal.util.Preconditions;

/**
 * Configuration for capturing audio played by other apps.
 *
 * For privacy and copyright reason, only the following audio can be captured:
 *  - usage MUST be UNKNOWN or GAME or MEDIA. All other usages CAN NOT be capturable.
 *  - audio attributes MUST NOT have the FLAG_NO_CAPTURE
 *  - played by apps that MUST be in the same user profile as the capturing app
 *    (eg work profile can not capture user profile apps and vice-versa).
 *  - played by apps that MUST NOT have in their manifest.xml the application
 *    attribute android:allowPlaybackCapture="false"
 *  - played by apps that MUST have a targetSdkVersion higher or equal to 29 (Q).
 *
 * <p>An example for creating a capture configuration for capturing all media playback:
 *
 * <pre>
 *     MediaProjection mediaProjection;
 *     // Retrieve a audio capable projection from the MediaProjectionManager
 *     AudioAttributes mediaAttr = new AudioAttributes.Builder()
 *         .setUsage(AudioAttributes.USAGE_MEDIA)
 *         .build();
 *     AudioPlaybackCaptureConfiguration config = new AudioPlaybackCaptureConfiguration.Builder()
 *     AudioPlaybackCaptureConfiguration config =
 *              new AudioPlaybackCaptureConfiguration.Builder(mediaProjection)
 *         .addMatchingUsage(mediaAttr)
 *         .build();
 *     AudioRecord record = new AudioRecord.Builder()
 *         .setPlaybackCaptureConfig(config)
 *         .setAudioPlaybackCaptureConfig(config)
 *         .build();
 * </pre>
 *
 * @see AudioRecord.Builder#setPlaybackCaptureConfig(AudioPlaybackCaptureConfiguration)
 * @see MediaProjectionManager#getMediaProjection(int, Intent)
 * @see AudioRecord.Builder#setAudioPlaybackCaptureConfig(AudioPlaybackCaptureConfiguration)
 */
public final class AudioPlaybackCaptureConfiguration {

    private final AudioMixingRule mAudioMixingRule;
    private final MediaProjection mProjection;

    private AudioPlaybackCaptureConfiguration(AudioMixingRule audioMixingRule) {
    private AudioPlaybackCaptureConfiguration(AudioMixingRule audioMixingRule,
                                              MediaProjection projection) {
        mAudioMixingRule = audioMixingRule;
        mProjection = projection;
    }

    /**
@@ -60,6 +78,9 @@ public final class AudioPlaybackCaptureConfiguration {
                .setRouteFlags(AudioMix.ROUTE_FLAG_LOOP_BACK | AudioMix.ROUTE_FLAG_RENDER)
                .build();
    }
    MediaProjection getMediaProjection() {
        return mProjection;
    }

    /** Builder for creating {@link AudioPlaybackCaptureConfiguration} instances. */
    public static final class Builder {
@@ -70,12 +91,26 @@ public final class AudioPlaybackCaptureConfiguration {

        private static final String ERROR_MESSAGE_MISMATCHED_RULES =
                "Inclusive and exclusive usage rules cannot be combined";
        private static final String ERROR_MESSAGE_START_ACTIVITY_FAILED =
                "startActivityForResult failed";
        private static final String ERROR_MESSAGE_NON_AUDIO_PROJECTION =
                "MediaProjection can not project audio";

        private final AudioMixingRule.Builder mAudioMixingRuleBuilder;
        private final MediaProjection mProjection;
        private int mUsageMatchType = MATCH_TYPE_UNSPECIFIED;
        private int mUidMatchType = MATCH_TYPE_UNSPECIFIED;

        public Builder() {
        /** @param projection A MediaProjection that supports audio projection. */
        public Builder(@NonNull MediaProjection projection) {
            Preconditions.checkNotNull(projection);
            try {
                Preconditions.checkArgument(projection.getProjection().canProjectAudio(),
                                            ERROR_MESSAGE_NON_AUDIO_PROJECTION);
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
            mProjection = projection;
            mAudioMixingRuleBuilder = new AudioMixingRule.Builder();
        }

@@ -155,7 +190,8 @@ public final class AudioPlaybackCaptureConfiguration {
         * @throws UnsupportedOperationException if the parameters set are incompatible.
         */
        public AudioPlaybackCaptureConfiguration build() {
            return new AudioPlaybackCaptureConfiguration(mAudioMixingRuleBuilder.build());
            return new AudioPlaybackCaptureConfiguration(mAudioMixingRuleBuilder.build(),
                                                         mProjection);
        }
    }
}
+3 −0
Original line number Diff line number Diff line
@@ -26,6 +26,7 @@ import android.annotation.UnsupportedAppUsage;
import android.app.ActivityThread;
import android.media.audiopolicy.AudioMix;
import android.media.audiopolicy.AudioPolicy;
import android.media.projection.MediaProjection;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
@@ -648,7 +649,9 @@ public class AudioRecord implements AudioRouting, MicrophoneDirection,

        private AudioRecord buildAudioPlaybackCaptureRecord() {
            AudioMix audioMix = mAudioPlaybackCaptureConfiguration.createAudioMix(mFormat);
            MediaProjection projection = mAudioPlaybackCaptureConfiguration.getMediaProjection();
            AudioPolicy audioPolicy = new AudioPolicy.Builder(/*context=*/ null)
                    .setMediaProjection(projection)
                    .addMix(audioMix).build();
            AudioRecord record = audioPolicy.createAudioRecordSink(audioMix);
            record.unregisterAudioPolicyOnRelease(audioPolicy);
+2 −1
Original line number Diff line number Diff line
@@ -34,6 +34,7 @@ import android.media.PlayerBase;
import android.media.VolumePolicy;
import android.media.audiopolicy.AudioPolicyConfig;
import android.media.audiopolicy.IAudioPolicyCallback;
import android.media.projection.IMediaProjection;

/**
 * {@hide}
@@ -176,7 +177,7 @@ interface IAudioService {

    String registerAudioPolicy(in AudioPolicyConfig policyConfig,
            in IAudioPolicyCallback pcb, boolean hasFocusListener, boolean isFocusPolicy,
            boolean isVolumeController);
            boolean isVolumeController, in IMediaProjection projection);

    oneway void unregisterAudioPolicyAsync(in IAudioPolicyCallback pcb);

Loading