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:
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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. */
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user