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

Commit 111a49ba authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge changes I256e6ed1,I97d41ed0,I6b4177e4 into qt-dev

* changes:
  System apps are not allowed to record better then 16kHz mono
  Fix permission check when registering an audio policy
  Refactor security checks to register policies
parents 325c12be e5b29c98
Loading
Loading
Loading
Loading
+5 −5
Original line number Original line Diff line number Diff line
@@ -413,11 +413,11 @@ public final class AudioAttributes implements Parcelable {
    /**
    /**
     * Indicates that the audio may only be captured by system apps.
     * Indicates that the audio may only be captured by system apps.
     *
     *
     * System apps can capture for many purposes like accessibility, user guidance...
     * System apps can capture for many purposes like accessibility, live captions, user guidance...
     * but abide to the following restrictions:
     * but abide to the following restrictions:
     *  - the audio cannot leave the device
     *  - the audio cannot leave the device
     *  - the audio cannot be passed to a third party app
     *  - the audio cannot be passed to a third party app
     *  - the audio can not be recorded at a higher quality then 16kHz 16bit mono
     *  - the audio cannot be recorded at a higher quality than 16kHz 16bit mono
     *
     *
     * See {@link Builder#setAllowedCapturePolicy}.
     * See {@link Builder#setAllowedCapturePolicy}.
     */
     */
+56 −1
Original line number Original line Diff line number Diff line
@@ -91,6 +91,20 @@ public class AudioMix {
     */
     */
    public static final int ROUTE_FLAG_LOOP_BACK = 0x1 << 1;
    public static final int ROUTE_FLAG_LOOP_BACK = 0x1 << 1;


    /**
     * An audio mix behavior where the targeted audio is played unaffected but a copy is
     * accessible for capture through {@link AudioRecord}.
     *
     * Only capture of playback is supported, not capture of capture.
     * Use concurrent capture instead to capture what is captured by other apps.
     *
     * The captured audio is an approximation of the played audio.
     * Effects and volume are not applied, and track are mixed with different delay then in the HAL.
     * As a result, this API is not suitable for echo cancelling.
     * @hide
     */
    public static final int ROUTE_FLAG_LOOP_BACK_RENDER = ROUTE_FLAG_LOOP_BACK | ROUTE_FLAG_RENDER;

    private static final int ROUTE_FLAG_SUPPORTED = ROUTE_FLAG_RENDER | ROUTE_FLAG_LOOP_BACK;
    private static final int ROUTE_FLAG_SUPPORTED = ROUTE_FLAG_RENDER | ROUTE_FLAG_LOOP_BACK;


    // MIX_TYPE_* values to keep in sync with frameworks/av/include/media/AudioPolicy.h
    // MIX_TYPE_* values to keep in sync with frameworks/av/include/media/AudioPolicy.h
@@ -125,6 +139,15 @@ public class AudioMix {
     */
     */
    public static final int MIX_STATE_MIXING = 1;
    public static final int MIX_STATE_MIXING = 1;


    /** Maximum sampling rate for privileged playback capture*/
    private static final int PRIVILEDGED_CAPTURE_MAX_SAMPLE_RATE = 16000;

    /** Maximum channel number for privileged playback capture*/
    private static final int PRIVILEDGED_CAPTURE_MAX_CHANNEL_NUMBER = 1;

    /** Maximum channel number for privileged playback capture*/
    private static final int PRIVILEDGED_CAPTURE_MAX_BYTES_PER_SAMPLE = 2;

    /**
    /**
     * The current mixing state.
     * The current mixing state.
     * @return one of {@link #MIX_STATE_DISABLED}, {@link #MIX_STATE_IDLE},
     * @return one of {@link #MIX_STATE_DISABLED}, {@link #MIX_STATE_IDLE},
@@ -140,7 +163,8 @@ public class AudioMix {
        return mRouteFlags;
        return mRouteFlags;
    }
    }


    AudioFormat getFormat() {
    /** @hide */
    public AudioFormat getFormat() {
        return mFormat;
        return mFormat;
    }
    }


@@ -182,6 +206,31 @@ public class AudioMix {
        return true;
        return true;
    }
    }


    /** @return an error string if the format would not allow Privileged playbackCapture
     *          null otherwise
     * @hide */
    public static String canBeUsedForPrivilegedCapture(AudioFormat format) {
        int sampleRate = format.getSampleRate();
        if (sampleRate > PRIVILEDGED_CAPTURE_MAX_SAMPLE_RATE || sampleRate <= 0) {
            return "Privileged audio capture sample rate " + sampleRate
                   + " can not be over " + PRIVILEDGED_CAPTURE_MAX_SAMPLE_RATE + "kHz";
        }
        int channelCount = format.getChannelCount();
        if (channelCount > PRIVILEDGED_CAPTURE_MAX_CHANNEL_NUMBER || channelCount <= 0) {
            return "Privileged audio capture channel count " + channelCount + " can not be over "
                   + PRIVILEDGED_CAPTURE_MAX_CHANNEL_NUMBER;
        }
        int encoding = format.getEncoding();
        if (!format.isPublicEncoding(encoding) || !format.isEncodingLinearPcm(encoding)) {
            return "Privileged audio capture encoding " + encoding + "is not linear";
        }
        if (format.getBytesPerSample(encoding) > PRIVILEDGED_CAPTURE_MAX_BYTES_PER_SAMPLE) {
            return "Privileged audio capture encoding " + encoding + " can not be over "
                   + PRIVILEDGED_CAPTURE_MAX_BYTES_PER_SAMPLE + " bytes per sample";
        }
        return null;
    }

    /** @hide */
    /** @hide */
    @Override
    @Override
    public boolean equals(Object o) {
    public boolean equals(Object o) {
@@ -390,6 +439,12 @@ public class AudioMix {
                    }
                    }
                }
                }
            }
            }
            if (mRule.allowPrivilegedPlaybackCapture()) {
                String error = AudioMix.canBeUsedForPrivilegedCapture(mFormat);
                if (error != null) {
                    throw new IllegalArgumentException(error);
                }
            }
            return new AudioMix(mRule, mFormat, mRouteFlags, mCallbackFlags, mDeviceSystemType,
            return new AudioMix(mRule, mFormat, mRouteFlags, mCallbackFlags, mDeviceSystemType,
                    mDeviceAddress);
                    mDeviceAddress);
        }
        }
+4 −0
Original line number Original line Diff line number Diff line
@@ -365,6 +365,10 @@ public class AudioMixingRule {
        /**
        /**
         * Set if the audio of app that opted out of audio playback capture should be captured.
         * Set if the audio of app that opted out of audio playback capture should be captured.
         *
         *
         * Caller of this method with <code>true</code>, MUST abide to the restriction listed in
         * {@link ALLOW_CAPTURE_BY_SYSTEM}, including but not limited to the captured audio
         * can not leave the capturing app, and the quality is limited to 16k mono.
         *
         * The permission {@link CAPTURE_AUDIO_OUTPUT} or {@link CAPTURE_MEDIA_OUTPUT} is needed
         * The permission {@link CAPTURE_AUDIO_OUTPUT} or {@link CAPTURE_MEDIA_OUTPUT} is needed
         * to ignore the opt-out.
         * to ignore the opt-out.
         *
         *
+57 −25
Original line number Original line Diff line number Diff line
@@ -6698,7 +6698,10 @@ public class AudioService extends IAudioService.Stub
            boolean isVolumeController, IMediaProjection projection) {
            boolean isVolumeController, IMediaProjection projection) {
        AudioSystem.setDynamicPolicyCallback(mDynPolicyCallback);
        AudioSystem.setDynamicPolicyCallback(mDynPolicyCallback);


        if (!isPolicyRegisterAllowed(policyConfig, projection)) {
        if (!isPolicyRegisterAllowed(policyConfig,
                                     isFocusPolicy || isTestFocusPolicy || hasFocusListener,
                                     isVolumeController,
                                     projection)) {
            Slog.w(TAG, "Permission denied to register audio policy for pid "
            Slog.w(TAG, "Permission denied to register audio policy for pid "
                    + Binder.getCallingPid() + " / uid " + Binder.getCallingUid()
                    + Binder.getCallingPid() + " / uid " + Binder.getCallingUid()
                    + ", need MODIFY_AUDIO_ROUTING or MediaProjection that can project audio");
                    + ", need MODIFY_AUDIO_ROUTING or MediaProjection that can project audio");
@@ -6739,42 +6742,71 @@ public class AudioService extends IAudioService.Stub
     * as those policy do not modify the audio routing.
     * as those policy do not modify the audio routing.
     */
     */
    private boolean isPolicyRegisterAllowed(AudioPolicyConfig policyConfig,
    private boolean isPolicyRegisterAllowed(AudioPolicyConfig policyConfig,
                                            boolean hasFocusAccess,
                                            boolean isVolumeController,
                                            IMediaProjection projection) {
                                            IMediaProjection projection) {


        boolean isLoopbackRenderPolicy = policyConfig.getMixes().stream().allMatch(
        boolean requireValidProjection = false;
                mix -> mix.getRouteFlags() == (mix.ROUTE_FLAG_RENDER | mix.ROUTE_FLAG_LOOP_BACK));
        boolean requireCaptureAudioOrMediaOutputPerm = false;

        boolean requireModifyRouting = false;
        if (isLoopbackRenderPolicy) {

            boolean allowPrivilegedPlaybackCapture = policyConfig.getMixes().stream().anyMatch(
        if (hasFocusAccess || isVolumeController) {
                    mix -> mix.getRule().allowPrivilegedPlaybackCapture());
            requireModifyRouting |= true;
            if (allowPrivilegedPlaybackCapture
        } else if (policyConfig.getMixes().isEmpty()) {
                    && !(hasPermission(android.Manifest.permission.CAPTURE_AUDIO_OUTPUT)
            // An empty policy could be used to lock the focus or add mixes later
                    || hasPermission(android.Manifest.permission.CAPTURE_MEDIA_OUTPUT))) {
            requireModifyRouting |= true;
                // Opt-out can not be bypassed without a system permission
        }
        for (AudioMix mix : policyConfig.getMixes()) {
            // If mix is requesting a privileged capture
            if (mix.getRule().allowPrivilegedPlaybackCapture()) {
                // then it must have CAPTURE_MEDIA_OUTPUT or CAPTURE_AUDIO_OUTPUT permission
                requireCaptureAudioOrMediaOutputPerm |= true;
                // and its format must be low quality enough
                String error = mix.canBeUsedForPrivilegedCapture(mix.getFormat());
                if (error != null) {
                    Log.e(TAG, error);
                    return false;
                    return false;
                }
                }
            }


            if (canProjectAudio(projection)) {
            // If mix is RENDER|LOOPBACK, then an audio MediaProjection is enough
                // Policy that do not modify the audio routing only need an audio projection
            // otherwise MODIFY_AUDIO_ROUTING permission is required
                return true;
            if (mix.getRouteFlags() == mix.ROUTE_FLAG_LOOP_BACK_RENDER && projection != null) {
                requireValidProjection |= true;
            } else {
                requireModifyRouting |= true;
            }
            }
        }
        }


        boolean hasPermissionModifyAudioRouting =
        if (requireCaptureAudioOrMediaOutputPerm
                (PackageManager.PERMISSION_GRANTED == mContext.checkCallingPermission(
                && !callerHasPermission(android.Manifest.permission.CAPTURE_MEDIA_OUTPUT)
                        android.Manifest.permission.MODIFY_AUDIO_ROUTING));
                && !callerHasPermission(android.Manifest.permission.CAPTURE_AUDIO_OUTPUT)) {
        if (hasPermissionModifyAudioRouting) {
            Log.e(TAG, "Privileged audio capture requires CAPTURE_MEDIA_OUTPUT or "
            return true;
                      + "CAPTURE_AUDIO_OUTPUT system permission");
            return false;
        }
        }

        if (requireValidProjection && !canProjectAudio(projection)) {
            return false;
        }

        if (requireModifyRouting
                && !callerHasPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)) {
            Log.e(TAG, "Can not capture audio without MODIFY_AUDIO_ROUTING");
            return false;
            return false;
        }
        }
    private boolean hasPermission(String permission) {

        return PackageManager.PERMISSION_GRANTED == mContext.checkCallingPermission(permission);
        return true;
    }

    private boolean callerHasPermission(String permission) {
        return mContext.checkCallingPermission(permission) == PackageManager.PERMISSION_GRANTED;
    }
    }


    /** @return true if projection is a valid MediaProjection that can project audio. */
    /** @return true if projection is a valid MediaProjection that can project audio. */
    private boolean canProjectAudio(IMediaProjection projection) {
    private boolean canProjectAudio(IMediaProjection projection) {
        if (projection == null) {
        if (projection == null) {
            Log.e(TAG, "MediaProjection is null");
            return false;
            return false;
        }
        }