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

Commit 6b52eb8b authored by Kevin Rocard's avatar Kevin Rocard Committed by android-build-merger
Browse files

Merge changes I256e6ed1,I97d41ed0,I6b4177e4 into qt-dev am: 111a49ba

am: ce30c8ee

Change-Id: Ic70c911e0b5f6f2c32d896445b68c1ec4301b2db
parents 16590e52 ce30c8ee
Loading
Loading
Loading
Loading
+5 −5
Original line number 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.
     *
     * 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:
     *  - the audio cannot leave the device
     *  - 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}.
     */
+56 −1
Original line number Diff line number Diff line
@@ -91,6 +91,20 @@ public class AudioMix {
     */
    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;

    // 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;

    /** 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.
     * @return one of {@link #MIX_STATE_DISABLED}, {@link #MIX_STATE_IDLE},
@@ -140,7 +163,8 @@ public class AudioMix {
        return mRouteFlags;
    }

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

@@ -182,6 +206,31 @@ public class AudioMix {
        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 */
    @Override
    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,
                    mDeviceAddress);
        }
+4 −0
Original line number 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.
         *
         * 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
         * to ignore the opt-out.
         *
+57 −25
Original line number Diff line number Diff line
@@ -6698,7 +6698,10 @@ public class AudioService extends IAudioService.Stub
            boolean isVolumeController, IMediaProjection projection) {
        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 "
                    + Binder.getCallingPid() + " / uid " + Binder.getCallingUid()
                    + ", 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.
     */
    private boolean isPolicyRegisterAllowed(AudioPolicyConfig policyConfig,
                                            boolean hasFocusAccess,
                                            boolean isVolumeController,
                                            IMediaProjection projection) {

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

        if (isLoopbackRenderPolicy) {
            boolean allowPrivilegedPlaybackCapture = policyConfig.getMixes().stream().anyMatch(
                    mix -> mix.getRule().allowPrivilegedPlaybackCapture());
            if (allowPrivilegedPlaybackCapture
                    && !(hasPermission(android.Manifest.permission.CAPTURE_AUDIO_OUTPUT)
                    || hasPermission(android.Manifest.permission.CAPTURE_MEDIA_OUTPUT))) {
                // Opt-out can not be bypassed without a system permission
        boolean requireValidProjection = false;
        boolean requireCaptureAudioOrMediaOutputPerm = false;
        boolean requireModifyRouting = false;

        if (hasFocusAccess || isVolumeController) {
            requireModifyRouting |= true;
        } else if (policyConfig.getMixes().isEmpty()) {
            // An empty policy could be used to lock the focus or add mixes later
            requireModifyRouting |= true;
        }
        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;
                }
            }

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

        boolean hasPermissionModifyAudioRouting =
                (PackageManager.PERMISSION_GRANTED == mContext.checkCallingPermission(
                        android.Manifest.permission.MODIFY_AUDIO_ROUTING));
        if (hasPermissionModifyAudioRouting) {
            return true;
        if (requireCaptureAudioOrMediaOutputPerm
                && !callerHasPermission(android.Manifest.permission.CAPTURE_MEDIA_OUTPUT)
                && !callerHasPermission(android.Manifest.permission.CAPTURE_AUDIO_OUTPUT)) {
            Log.e(TAG, "Privileged audio capture requires CAPTURE_MEDIA_OUTPUT or "
                      + "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;
        }
    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. */
    private boolean canProjectAudio(IMediaProjection projection) {
        if (projection == null) {
            Log.e(TAG, "MediaProjection is null");
            return false;
        }