Add attribution context for MusicRecognitionManager attribution.

In addition:
  1) Use attribution tag specified in the MusicRecognitionService's manifest.
  2) Explicitly check for RECORD_AUDIO permission of receiving service.

Example audio attribution from dumpsys:
  RECORD_AUDIO (allow):
    MusicRecognitionDoneByServiceX=[
      Access: [fg-tpd] 2021-02-23 18:21:01.753 (-14s877ms) duration=+8s250ms proxy[uid=1000, pkg=android, attributionTag=MusicRecognitionManagerService]
    ]

Test: atest and manual inspection of attributions with dumpsys.
Change-Id: Ie415104580a1814b0b74f2d5b489bf8247d9238d
Bug: 178174412
Bug: 169403302
This commit is contained in:
Christian Frank
2021-02-25 10:09:15 +01:00
parent 8d90f15a45
commit f6848802cb
8 changed files with 100 additions and 22 deletions

View File

@@ -5592,6 +5592,10 @@
<!-- Attribution for Gnss Time Update service. -->
<attribution android:tag="GnssTimeUpdateService"
android:label="@string/gnss_time_update_service"/>
<!-- Attribution for MusicRecognitionManagerService.
<p>Not for use by third-party applications.</p> -->
<attribution android:tag="MusicRecognitionManagerService"
android:label="@string/music_recognition_manager_service"/>
<application android:process="system"
android:persistent="true"

View File

@@ -453,6 +453,9 @@
<!-- Attribution for Gnss Time Update service. [CHAR LIMIT=NONE]-->
<string name="gnss_time_update_service">GNSS Time Update Service</string>
<!-- Attribution for MusicRecognitionManagerService. [CHAR LIMIT=NONE]-->
<string name="music_recognition_manager_service">Music Recognition Manager Service</string>
<!-- Factory reset warning dialog strings--> <skip />
<!-- Shows up in the dialog's title to warn about an impeding factory reset. [CHAR LIMIT=NONE] -->
<string name="factory_reset_warning">Your device will be erased</string>

View File

@@ -0,0 +1,10 @@
package android.media.musicrecognition;
/**
* Interface from {@link MusicRecognitionService} to system to pass attribution tag.
*
* @hide
*/
oneway interface IMusicRecognitionAttributionTagCallback {
void onAttributionTag(in String attributionTag);
}

View File

@@ -4,6 +4,7 @@ import android.media.AudioFormat;
import android.os.ParcelFileDescriptor;
import android.os.IBinder;
import android.media.musicrecognition.IMusicRecognitionServiceCallback;
import android.media.musicrecognition.IMusicRecognitionAttributionTagCallback;
/**
* Interface from the system to a {@link MusicRecognitionService}.
@@ -15,4 +16,6 @@ oneway interface IMusicRecognitionService {
in ParcelFileDescriptor fd,
in AudioFormat audioFormat,
in IMusicRecognitionServiceCallback callback);
void getAttributionTag(in IMusicRecognitionAttributionTagCallback callback);
}

View File

@@ -4,7 +4,7 @@ import android.os.Bundle;
import android.media.MediaMetadata;
/**
* Interface from a {@MusicRecognitionService} the system.
* Interface from a {@MusicRecognitionService} to the system.
*
* @hide
*/

View File

@@ -90,7 +90,7 @@ public abstract class MusicRecognitionService extends Service {
try {
callback.onRecognitionSucceeded(result, extras);
} catch (RemoteException e) {
e.rethrowFromSystemServer();
throw e.rethrowFromSystemServer();
}
}
@@ -99,11 +99,18 @@ public abstract class MusicRecognitionService extends Service {
try {
callback.onRecognitionFailed(failureCode);
} catch (RemoteException e) {
e.rethrowFromSystemServer();
throw e.rethrowFromSystemServer();
}
}
}));
}
@Override
public void getAttributionTag(
IMusicRecognitionAttributionTagCallback callback) throws RemoteException {
String tag = MusicRecognitionService.this.getAttributionTag();
callback.onAttributionTag(tag);
}
};
@Override

View File

@@ -48,6 +48,8 @@ import com.android.server.infra.AbstractPerUserSystemService;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
/**
* Handles per-user requests received by
@@ -60,6 +62,11 @@ public final class MusicRecognitionManagerPerUserService extends
implements RemoteMusicRecognitionService.Callbacks {
private static final String TAG = MusicRecognitionManagerPerUserService.class.getSimpleName();
private static final String MUSIC_RECOGNITION_MANAGER_ATTRIBUTION_TAG =
"MusicRecognitionManagerService";
private static final String KEY_MUSIC_RECOGNITION_SERVICE_ATTRIBUTION_TAG =
"android.media.musicrecognition.attributiontag";
// Number of bytes per sample of audio (which is a short).
private static final int BYTES_PER_SAMPLE = 2;
private static final int MAX_STREAMING_SECONDS = 24;
@@ -68,18 +75,24 @@ public final class MusicRecognitionManagerPerUserService extends
@GuardedBy("mLock")
private RemoteMusicRecognitionService mRemoteService;
private final AppOpsManager mAppOpsManager;
private final String mAttributionMessage;
private String mAttributionTag;
private String mAttributionMessage;
// Service info of the remote MusicRecognitionService (which the audio gets forwarded to).
private ServiceInfo mServiceInfo;
private CompletableFuture<String> mAttributionTagFuture;
MusicRecognitionManagerPerUserService(
@NonNull MusicRecognitionManagerService primary,
@NonNull Object lock, int userId) {
super(primary, lock, userId);
mAppOpsManager = getContext().getSystemService(AppOpsManager.class);
// When attributing audio-access, this establishes that audio access is performed by
// MusicRecognitionManager (on behalf of the receiving service, whose attribution tag,
// provided by mAttributionTagFuture, is used for the actual calls to startProxyOp(...).
mAppOpsManager = getContext().createAttributionContext(
MUSIC_RECOGNITION_MANAGER_ATTRIBUTION_TAG).getSystemService(AppOpsManager.class);
mAttributionMessage = String.format("MusicRecognitionManager.invokedByUid.%s", userId);
mAttributionTag = null;
mAttributionTagFuture = null;
mServiceInfo = null;
}
@@ -126,10 +139,13 @@ public final class MusicRecognitionManagerPerUserService extends
new MusicRecognitionServiceCallback(clientCallback),
mMaster.isBindInstantServiceAllowed(),
mMaster.verbose);
try {
mServiceInfo =
getContext().getPackageManager().getServiceInfo(
mRemoteService.getComponentName(), 0);
mRemoteService.getComponentName(), PackageManager.GET_META_DATA);
mAttributionTagFuture = mRemoteService.getAttributionTag();
Slog.i(TAG, "Remote service bound: " + mRemoteService.getComponentName());
} catch (PackageManager.NameNotFoundException e) {
Slog.e(TAG, "Service was not found.", e);
}
@@ -172,11 +188,13 @@ public final class MusicRecognitionManagerPerUserService extends
ParcelFileDescriptor audioSink = clientPipe.second;
ParcelFileDescriptor clientRead = clientPipe.first;
mMaster.mExecutorService.execute(() -> {
streamAudio(recognitionRequest, clientCallback, audioSink);
});
mAttributionTagFuture.thenAcceptAsync(
tag -> {
streamAudio(tag, recognitionRequest, clientCallback, audioSink);
}, mMaster.mExecutorService);
// Send the pipe down to the lookup service while we write to it asynchronously.
mRemoteService.writeAudioToPipe(clientRead, recognitionRequest.getAudioFormat());
mRemoteService.onAudioStreamStarted(clientRead, recognitionRequest.getAudioFormat());
}
/**
@@ -186,10 +204,12 @@ public final class MusicRecognitionManagerPerUserService extends
* @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) {
private void streamAudio(@Nullable String attributionTag,
@NonNull RecognitionRequest recognitionRequest,
IMusicRecognitionManagerCallback clientCallback,
ParcelFileDescriptor audioSink) {
try {
startRecordAudioOp();
startRecordAudioOp(attributionTag);
} 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.
@@ -214,7 +234,7 @@ public final class MusicRecognitionManagerPerUserService extends
Slog.e(TAG, "Audio streaming stopped.", e);
} finally {
audioRecord.release();
finishRecordAudioOp();
finishRecordAudioOp(attributionTag);
try {
clientCallback.onAudioStreamClosed();
} catch (RemoteException ignored) {
@@ -323,23 +343,32 @@ 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(
private void startRecordAudioOp(@Nullable String attributionTag) {
int status = mAppOpsManager.startProxyOp(
Objects.requireNonNull(AppOpsManager.permissionToOp(RECORD_AUDIO)),
mServiceInfo.applicationInfo.uid,
mServiceInfo.packageName,
mAttributionTag,
attributionTag,
mAttributionMessage);
// The above should already throw a SecurityException. This is just a fallback.
if (status != AppOpsManager.MODE_ALLOWED) {
throw new SecurityException(String.format(
"Failed to obtain RECORD_AUDIO permission (status: %d) for "
+ "receiving service: %s", status, mServiceInfo.getComponentName()));
}
Slog.i(TAG, String.format(
"Starting audio streaming. Attributing to %s (%d) with tag '%s'",
mServiceInfo.packageName, mServiceInfo.applicationInfo.uid, attributionTag));
}
/** Tracks that the RECORD_AUDIO operation finished. */
private void finishRecordAudioOp() {
private void finishRecordAudioOp(@Nullable String attributionTag) {
mAppOpsManager.finishProxyOp(
Objects.requireNonNull(AppOpsManager.permissionToOp(RECORD_AUDIO)),
mServiceInfo.applicationInfo.uid,
mServiceInfo.packageName,
mAttributionTag);
attributionTag);
}
/** Establishes an audio stream from the DSP audio source. */

View File

@@ -20,15 +20,20 @@ import android.annotation.NonNull;
import android.content.ComponentName;
import android.content.Context;
import android.media.AudioFormat;
import android.media.musicrecognition.IMusicRecognitionAttributionTagCallback;
import android.media.musicrecognition.IMusicRecognitionService;
import android.media.musicrecognition.MusicRecognitionService;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.text.format.DateUtils;
import com.android.internal.infra.AbstractMultiplePendingRequestsRemoteService;
import com.android.server.musicrecognition.MusicRecognitionManagerPerUserService.MusicRecognitionServiceCallback;
import java.util.concurrent.CompletableFuture;
/** Remote connection to an instance of {@link MusicRecognitionService}. */
public class RemoteMusicRecognitionService extends
AbstractMultiplePendingRequestsRemoteService<RemoteMusicRecognitionService,
@@ -81,9 +86,26 @@ public class RemoteMusicRecognitionService extends
* Sends the given descriptor to the app's {@link MusicRecognitionService} to read the
* audio.
*/
public void writeAudioToPipe(@NonNull ParcelFileDescriptor fd,
public void onAudioStreamStarted(@NonNull ParcelFileDescriptor fd,
@NonNull AudioFormat audioFormat) {
scheduleAsyncRequest(
binder -> binder.onAudioStreamStarted(fd, audioFormat, mServerCallback));
}
/**
* Returns the name of the <attribution> tag defined in the remote service's manifest.
*/
public CompletableFuture<String> getAttributionTag() {
CompletableFuture<String> attributionTagFuture = new CompletableFuture<String>();
scheduleAsyncRequest(
binder -> binder.getAttributionTag(
new IMusicRecognitionAttributionTagCallback.Stub() {
@Override
public void onAttributionTag(String tag) throws RemoteException {
attributionTagFuture.complete(tag);
}
}));
return attributionTagFuture;
}
}