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

Commit 6d62ef5d authored by Christian Frank's avatar Christian Frank
Browse files

Add attribution to the audio access of

MusicRecognitionManagerService.

In particular:
 1) Check that the MusicRecognitionService implementation (receiving the
    audio) has permission to record audio.
 2) Better track audio access and show mic indicator.

Bug: 169403302
Bug: 178174412
Test: Manually tested and observed attributions, atest MusicRecognitionManagerService.
Change-Id: Ie4f11a55df4f6643efeef7c5f037a307fdde01af
parent 30017003
Loading
Loading
Loading
Loading
+122 −44
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package com.android.server.musicrecognition;

import static android.Manifest.permission.RECORD_AUDIO;
import static android.media.musicrecognition.MusicRecognitionManager.RECOGNITION_FAILED_AUDIO_UNAVAILABLE;
import static android.media.musicrecognition.MusicRecognitionManager.RECOGNITION_FAILED_SERVICE_KILLED;
import static android.media.musicrecognition.MusicRecognitionManager.RECOGNITION_FAILED_SERVICE_UNAVAILABLE;
@@ -25,6 +26,7 @@ import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.AppGlobals;
import android.app.AppOpsManager;
import android.content.ComponentName;
import android.content.pm.PackageManager;
import android.content.pm.ServiceInfo;
@@ -45,6 +47,7 @@ import com.android.server.infra.AbstractPerUserSystemService;

import java.io.IOException;
import java.io.OutputStream;
import java.util.Objects;

/**
 * Handles per-user requests received by
@@ -64,11 +67,20 @@ public final class MusicRecognitionManagerPerUserService extends
    @Nullable
    @GuardedBy("mLock")
    private RemoteMusicRecognitionService mRemoteService;
    private final AppOpsManager mAppOpsManager;

    private String mAttributionTag;
    private String mAttributionMessage;
    private ServiceInfo mServiceInfo;

    MusicRecognitionManagerPerUserService(
            @NonNull MusicRecognitionManagerService primary,
            @NonNull Object lock, int userId) {
        super(primary, lock, userId);
        mAppOpsManager = getContext().getSystemService(AppOpsManager.class);
        mAttributionMessage = String.format("MusicRecognitionManager.invokedByUid.%s", userId);
        mAttributionTag = null;
        mServiceInfo = null;
    }

    @NonNull
@@ -114,6 +126,13 @@ public final class MusicRecognitionManagerPerUserService extends
                    new MusicRecognitionServiceCallback(clientCallback),
                    mMaster.isBindInstantServiceAllowed(),
                    mMaster.verbose);
            try {
                mServiceInfo =
                        getContext().getPackageManager().getServiceInfo(
                                mRemoteService.getComponentName(), 0);
            } catch (PackageManager.NameNotFoundException e) {
                Slog.e(TAG, "Service was not found.", e);
            }
        }

        return mRemoteService;
@@ -127,12 +146,8 @@ public final class MusicRecognitionManagerPerUserService extends
    public void beginRecognitionLocked(
            @NonNull RecognitionRequest recognitionRequest,
            @NonNull IBinder callback) {
        int maxAudioLengthSeconds = Math.min(recognitionRequest.getMaxAudioLengthSeconds(),
                MAX_STREAMING_SECONDS);
        IMusicRecognitionManagerCallback clientCallback =
                IMusicRecognitionManagerCallback.Stub.asInterface(callback);
        AudioRecord audioRecord = createAudioRecord(recognitionRequest, maxAudioLengthSeconds);

        mRemoteService = ensureRemoteServiceLocked(clientCallback);
        if (mRemoteService == null) {
            try {
@@ -158,8 +173,60 @@ public final class MusicRecognitionManagerPerUserService extends
        ParcelFileDescriptor clientRead = clientPipe.first;

        mMaster.mExecutorService.execute(() -> {
            streamAudio(recognitionRequest, clientCallback, audioSink);
        });
        // Send the pipe down to the lookup service while we write to it asynchronously.
        mRemoteService.writeAudioToPipe(clientRead, recognitionRequest.getAudioFormat());
    }

    /**
     * Streams audio based on given request to the given audioSink. Notifies callback of errors.
     *
     * @param recognitionRequest the recognition request specifying audio parameters.
     * @param clientCallback the callback to notify on errors.
     * @param audioSink the sink to which to stream audio to.
     */
    private void streamAudio(@NonNull RecognitionRequest recognitionRequest,
            IMusicRecognitionManagerCallback clientCallback, ParcelFileDescriptor audioSink) {
        try {
            startRecordAudioOp();
        } catch (SecurityException e) {
            // A security exception can occur if the MusicRecognitionService (receiving the audio)
            // does not (or does no longer) hold the necessary permissions to record audio.
            Slog.e(TAG, "RECORD_AUDIO op not permitted on behalf of "
                    + mServiceInfo.getComponentName(), e);
            try {
                clientCallback.onRecognitionFailed(
                        RECOGNITION_FAILED_AUDIO_UNAVAILABLE);
            } catch (RemoteException ignored) {
                // Ignored.
            }
            return;
        }

        int maxAudioLengthSeconds = Math.min(recognitionRequest.getMaxAudioLengthSeconds(),
                MAX_STREAMING_SECONDS);
        AudioRecord audioRecord = createAudioRecord(recognitionRequest, maxAudioLengthSeconds);
        try (OutputStream fos =
                     new ParcelFileDescriptor.AutoCloseOutputStream(audioSink)) {
            streamAudio(recognitionRequest, maxAudioLengthSeconds, audioRecord, fos);
        } catch (IOException e) {
            Slog.e(TAG, "Audio streaming stopped.", e);
        } finally {
            audioRecord.release();
            finishRecordAudioOp();
            try {
                clientCallback.onAudioStreamClosed();
            } catch (RemoteException ignored) {
                // Ignored.
            }
        }
    }

    /** Performs the actual streaming from audioRecord into outputStream. **/
    private void streamAudio(@NonNull RecognitionRequest recognitionRequest,
            int maxAudioLengthSeconds, AudioRecord audioRecord, OutputStream outputStream)
            throws IOException {
        int halfSecondBufferSize =
                audioRecord.getBufferSizeInFrames() / maxAudioLengthSeconds;
        byte[] byteBuffer = new byte[halfSecondBufferSize];
@@ -180,27 +247,15 @@ public final class MusicRecognitionManagerPerUserService extends
                    // If we've dipped negative, we've skipped through all ignored bytes
                    // and then some.  Write out the bytes we shouldn't have skipped.
                    if (ignoreBytes < 0) {
                                fos.write(byteBuffer, bytesRead + ignoreBytes, -ignoreBytes);
                        outputStream.write(byteBuffer, bytesRead + ignoreBytes, -ignoreBytes);
                    }
                } else {
                            fos.write(byteBuffer);
                    outputStream.write(byteBuffer);
                }
            }
        }
                Slog.i(TAG, String.format("Streamed %s bytes from audio record", totalBytesRead));
            } catch (IOException e) {
                Slog.e(TAG, "Audio streaming stopped.", e);
            } finally {
                audioRecord.release();
                try {
                    clientCallback.onAudioStreamClosed();
                } catch (RemoteException ignored) {
                    // Ignored.
                }
            }
        });
        // Send the pipe down to the lookup service while we write to it asynchronously.
        mRemoteService.writeAudioToPipe(clientRead, recognitionRequest.getAudioFormat());
        Slog.i(TAG,
                String.format("Streamed %s bytes from audio record", totalBytesRead));
    }

    /**
@@ -264,6 +319,29 @@ public final class MusicRecognitionManagerPerUserService extends
        }
    }

    /**
     * Tracks that the RECORD_AUDIO operation started (attributes it to the service receiving the
     * audio).
     */
    private void startRecordAudioOp() {
        mAppOpsManager.startProxyOp(
                Objects.requireNonNull(AppOpsManager.permissionToOp(RECORD_AUDIO)),
                mServiceInfo.applicationInfo.uid,
                mServiceInfo.packageName,
                mAttributionTag,
                mAttributionMessage);
    }


    /** Tracks that the RECORD_AUDIO operation finished. */
    private void finishRecordAudioOp() {
        mAppOpsManager.finishProxyOp(
                Objects.requireNonNull(AppOpsManager.permissionToOp(RECORD_AUDIO)),
                mServiceInfo.applicationInfo.uid,
                mServiceInfo.packageName,
                mAttributionTag);
    }

    /** Establishes an audio stream from the DSP audio source. */
    private static AudioRecord createAudioRecord(
            @NonNull RecognitionRequest recognitionRequest,
+1 −1
Original line number Diff line number Diff line
@@ -45,7 +45,7 @@ import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * Service which allows a DSP audio event to be securely streamed to a designated {@link
 * Service which allows audio to be securely streamed to a designated {@link
 * MusicRecognitionService}.
 */
public class MusicRecognitionManagerService extends