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
This commit is contained in:
Christian Frank
2021-01-26 14:25:51 +01:00
parent 3001700366
commit 6d62ef5da3
2 changed files with 123 additions and 45 deletions

View File

@@ -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,51 +173,91 @@ public final class MusicRecognitionManagerPerUserService extends
ParcelFileDescriptor clientRead = clientPipe.first;
mMaster.mExecutorService.execute(() -> {
try (OutputStream fos =
new ParcelFileDescriptor.AutoCloseOutputStream(audioSink)) {
int halfSecondBufferSize =
audioRecord.getBufferSizeInFrames() / maxAudioLengthSeconds;
byte[] byteBuffer = new byte[halfSecondBufferSize];
int bytesRead = 0;
int totalBytesRead = 0;
int ignoreBytes =
recognitionRequest.getIgnoreBeginningFrames() * BYTES_PER_SAMPLE;
audioRecord.startRecording();
while (bytesRead >= 0 && totalBytesRead
< audioRecord.getBufferSizeInFrames() * BYTES_PER_SAMPLE
&& mRemoteService != null) {
bytesRead = audioRecord.read(byteBuffer, 0, byteBuffer.length);
if (bytesRead > 0) {
totalBytesRead += bytesRead;
// If we are ignoring the first x bytes, update that counter.
if (ignoreBytes > 0) {
ignoreBytes -= bytesRead;
// 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);
}
} else {
fos.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.
}
}
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];
int bytesRead = 0;
int totalBytesRead = 0;
int ignoreBytes =
recognitionRequest.getIgnoreBeginningFrames() * BYTES_PER_SAMPLE;
audioRecord.startRecording();
while (bytesRead >= 0 && totalBytesRead
< audioRecord.getBufferSizeInFrames() * BYTES_PER_SAMPLE
&& mRemoteService != null) {
bytesRead = audioRecord.read(byteBuffer, 0, byteBuffer.length);
if (bytesRead > 0) {
totalBytesRead += bytesRead;
// If we are ignoring the first x bytes, update that counter.
if (ignoreBytes > 0) {
ignoreBytes -= bytesRead;
// 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) {
outputStream.write(byteBuffer, bytesRead + ignoreBytes, -ignoreBytes);
}
} else {
outputStream.write(byteBuffer);
}
}
}
Slog.i(TAG,
String.format("Streamed %s bytes from audio record", totalBytesRead));
}
/**
* Callback invoked by {@link android.service.musicrecognition.MusicRecognitionService} to pass
* back the music search result.
@@ -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,

View File

@@ -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