Merge "Check/note ops when delivering HotwordDetectedResult" into sc-dev

This commit is contained in:
Ahaan Ugale
2021-07-12 17:46:28 +00:00
committed by Android (Google) Code Review
6 changed files with 91 additions and 16 deletions

View File

@@ -20,7 +20,9 @@ import static com.android.internal.util.function.pooled.PooledLambda.obtainMessa
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityThread;
import android.media.AudioFormat;
import android.media.permission.Identity;
import android.os.Handler;
import android.os.Looper;
import android.os.ParcelFileDescriptor;
@@ -111,8 +113,10 @@ abstract class AbstractHotwordDetector implements HotwordDetector {
if (DEBUG) {
Slog.d(TAG, "updateStateLocked()");
}
Identity identity = new Identity();
identity.packageName = ActivityThread.currentOpPackageName();
try {
mManagerService.updateState(options, sharedMemory, callback);
mManagerService.updateState(identity, options, sharedMemory, callback);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}

View File

@@ -231,6 +231,9 @@ interface IVoiceInteractionManagerService {
/**
* Set configuration and pass read-only data to hotword detection service.
* Caller must provide an identity, used for permission tracking purposes.
* The uid/pid elements of the identity will be ignored by the server and replaced with the ones
* provided by binder.
*
* @param options Application configuration data to provide to the
* {@link HotwordDetectionService}. PersistableBundle does not allow any remotable objects or
@@ -241,6 +244,7 @@ interface IVoiceInteractionManagerService {
* @param callback Use this to report {@link HotwordDetectionService} status.
*/
void updateState(
in Identity originatorIdentity,
in PersistableBundle options,
in SharedMemory sharedMemory,
in IHotwordRecognitionStatusCallback callback);

View File

@@ -16,20 +16,28 @@
package com.android.server.voiceinteraction;
import static android.Manifest.permission.CAPTURE_AUDIO_HOTWORD;
import static android.Manifest.permission.RECORD_AUDIO;
import static android.service.voice.HotwordDetectionService.AUDIO_SOURCE_EXTERNAL;
import static android.service.voice.HotwordDetectionService.AUDIO_SOURCE_MICROPHONE;
import static android.service.voice.HotwordDetectionService.INITIALIZATION_STATUS_UNKNOWN;
import static android.service.voice.HotwordDetectionService.KEY_INITIALIZATION_STATUS;
import static com.android.server.voiceinteraction.SoundTriggerSessionPermissionsDecorator.enforcePermissionForPreflight;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.AppOpsManager;
import android.content.ComponentName;
import android.content.ContentCaptureOptions;
import android.content.Context;
import android.content.Intent;
import android.content.PermissionChecker;
import android.hardware.soundtrigger.IRecognitionStatusCallback;
import android.hardware.soundtrigger.SoundTrigger;
import android.media.AudioFormat;
import android.media.permission.Identity;
import android.media.permission.PermissionUtil;
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
@@ -46,6 +54,7 @@ import android.service.voice.IDspHotwordDetectionCallback;
import android.service.voice.IHotwordDetectionService;
import android.service.voice.IMicrophoneHotwordDetectionVoiceInteractionCallback;
import android.service.voice.VoiceInteractionManagerInternal.HotwordDetectionServiceIdentity;
import android.text.TextUtils;
import android.util.Pair;
import android.util.Slog;
import android.view.contentcapture.IContentCaptureManager;
@@ -107,6 +116,10 @@ final class HotwordDetectionConnection {
private ScheduledFuture<?> mCancellationTaskFuture;
/** Identity used for attributing app ops when delivering data to the Interactor. */
@GuardedBy("mLock")
@Nullable
private final Identity mVoiceInteractorIdentity;
@GuardedBy("mLock")
private ParcelFileDescriptor mCurrentAudioSink;
@GuardedBy("mLock")
@@ -117,12 +130,13 @@ final class HotwordDetectionConnection {
private IBinder mAudioFlinger;
HotwordDetectionConnection(Object lock, Context context, int voiceInteractionServiceUid,
ComponentName serviceName, int userId, boolean bindInstantServiceAllowed,
@Nullable PersistableBundle options, @Nullable SharedMemory sharedMemory,
IHotwordRecognitionStatusCallback callback) {
Identity voiceInteractorIdentity, ComponentName serviceName, int userId,
boolean bindInstantServiceAllowed, @Nullable PersistableBundle options,
@Nullable SharedMemory sharedMemory, IHotwordRecognitionStatusCallback callback) {
mLock = lock;
mContext = context;
mVoiceInteractionServiceUid = voiceInteractionServiceUid;
mVoiceInteractorIdentity = voiceInteractorIdentity;
mDetectionComponentName = serviceName;
mUser = userId;
final Intent intent = new Intent(HotwordDetectionService.SERVICE_INTERFACE);
@@ -310,6 +324,7 @@ final class HotwordDetectionConnection {
}
synchronized (mLock) {
if (mPerformingSoftwareHotwordDetection) {
enforcePermissionsForDataDelivery();
mSoftwareCallback.onDetected(result, null, null);
mPerformingSoftwareHotwordDetection = false;
if (result != null) {
@@ -404,6 +419,7 @@ final class HotwordDetectionConnection {
synchronized (mLock) {
if (mValidatingDspTrigger) {
mValidatingDspTrigger = false;
enforcePermissionsForDataDelivery();
externalCallback.onKeyphraseDetected(recognitionEvent, result);
if (result != null) {
Slog.i(TAG, "Egressed " + HotwordDetectedResult.getUsageSize(result)
@@ -461,6 +477,7 @@ final class HotwordDetectionConnection {
return;
}
mValidatingDspTrigger = false;
enforcePermissionsForDataDelivery();
externalCallback.onKeyphraseDetected(recognitionEvent, result);
if (result != null) {
Slog.i(TAG, "Egressed " + HotwordDetectedResult.getUsageSize(result)
@@ -575,8 +592,7 @@ final class HotwordDetectionConnection {
if (DEBUG) {
Slog.d(TAG, "onKeyphraseDetected recognitionEvent : " + recognitionEvent);
}
final boolean useHotwordDetectionService = mHotwordDetectionConnection != null
&& mHotwordDetectionConnection.isBound();
final boolean useHotwordDetectionService = mHotwordDetectionConnection != null;
if (useHotwordDetectionService) {
mRecognitionEvent = recognitionEvent;
mHotwordDetectionConnection.detectFromDspSource(
@@ -692,7 +708,7 @@ final class HotwordDetectionConnection {
throws RemoteException {
bestEffortClose(serviceAudioSink);
bestEffortClose(serviceAudioSource);
// TODO: noteOp here.
enforcePermissionsForDataDelivery();
callback.onDetected(triggerResult, null /* audioFormat */,
null /* audioStream */);
if (triggerResult != null) {
@@ -872,4 +888,42 @@ final class HotwordDetectionConnection {
}
}
}
// TODO: Share this code with SoundTriggerMiddlewarePermission.
private void enforcePermissionsForDataDelivery() {
Binder.withCleanCallingIdentity(() -> {
enforcePermissionForPreflight(mContext, mVoiceInteractorIdentity, RECORD_AUDIO);
int hotwordOp = AppOpsManager.strOpToOp(AppOpsManager.OPSTR_RECORD_AUDIO_HOTWORD);
mContext.getSystemService(AppOpsManager.class).noteOpNoThrow(hotwordOp,
mVoiceInteractorIdentity.uid, mVoiceInteractorIdentity.packageName,
mVoiceInteractorIdentity.attributionTag, OP_MESSAGE);
enforcePermissionForDataDelivery(mContext, mVoiceInteractorIdentity,
CAPTURE_AUDIO_HOTWORD, OP_MESSAGE);
});
}
/**
* Throws a {@link SecurityException} iff the given identity has given permission to receive
* data.
*
* @param context A {@link Context}, used for permission checks.
* @param identity The identity to check.
* @param permission The identifier of the permission we want to check.
* @param reason The reason why we're requesting the permission, for auditing purposes.
*/
private static void enforcePermissionForDataDelivery(@NonNull Context context,
@NonNull Identity identity,
@NonNull String permission, @NonNull String reason) {
final int status = PermissionUtil.checkPermissionForDataDelivery(context, identity,
permission, reason);
if (status != PermissionChecker.PERMISSION_GRANTED) {
throw new SecurityException(
TextUtils.formatSimple("Failed to obtain permission %s for identity %s",
permission,
SoundTriggerSessionPermissionsDecorator.toString(identity)));
}
}
private static final String OP_MESSAGE =
"Providing hotword detection result to VoiceInteractionService";
};

View File

@@ -124,7 +124,7 @@ final class SoundTriggerSessionPermissionsDecorator implements
* @param identity The identity to check.
* @param permission The identifier of the permission we want to check.
*/
private static void enforcePermissionForPreflight(@NonNull Context context,
static void enforcePermissionForPreflight(@NonNull Context context,
@NonNull Identity identity, @NonNull String permission) {
final int status = PermissionUtil.checkPermissionForPreflight(context, identity,
permission);
@@ -144,7 +144,7 @@ final class SoundTriggerSessionPermissionsDecorator implements
}
}
private static String toString(Identity identity) {
static String toString(Identity identity) {
return "{uid=" + identity.uid
+ " pid=" + identity.pid
+ " packageName=" + identity.packageName

View File

@@ -1101,8 +1101,11 @@ public class VoiceInteractionManagerService extends SystemService {
//----------------- Hotword Detection/Validation APIs --------------------------------//
@Override
public void updateState(@Nullable PersistableBundle options,
@Nullable SharedMemory sharedMemory, IHotwordRecognitionStatusCallback callback) {
public void updateState(
@NonNull Identity voiceInteractorIdentity,
@Nullable PersistableBundle options,
@Nullable SharedMemory sharedMemory,
IHotwordRecognitionStatusCallback callback) {
enforceCallingPermission(Manifest.permission.MANAGE_HOTWORD_DETECTION);
synchronized (this) {
enforceIsCurrentVoiceInteractionService();
@@ -1111,9 +1114,14 @@ public class VoiceInteractionManagerService extends SystemService {
Slog.w(TAG, "updateState without running voice interaction service");
return;
}
voiceInteractorIdentity.uid = Binder.getCallingUid();
voiceInteractorIdentity.pid = Binder.getCallingPid();
final long caller = Binder.clearCallingIdentity();
try {
mImpl.updateStateLocked(options, sharedMemory, callback);
mImpl.updateStateLocked(
voiceInteractorIdentity, options, sharedMemory, callback);
} finally {
Binder.restoreCallingIdentity(caller);
}

View File

@@ -42,6 +42,7 @@ import android.content.pm.ServiceInfo;
import android.hardware.soundtrigger.IRecognitionStatusCallback;
import android.hardware.soundtrigger.SoundTrigger;
import android.media.AudioFormat;
import android.media.permission.Identity;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
@@ -405,8 +406,11 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne
return mInfo.getSupportsLocalInteraction();
}
public void updateStateLocked(@Nullable PersistableBundle options,
@Nullable SharedMemory sharedMemory, IHotwordRecognitionStatusCallback callback) {
public void updateStateLocked(
@NonNull Identity voiceInteractorIdentity,
@Nullable PersistableBundle options,
@Nullable SharedMemory sharedMemory,
IHotwordRecognitionStatusCallback callback) {
if (DEBUG) {
Slog.d(TAG, "updateStateLocked");
}
@@ -447,8 +451,9 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne
if (mHotwordDetectionConnection == null) {
mHotwordDetectionConnection = new HotwordDetectionConnection(mServiceStub, mContext,
mInfo.getServiceInfo().applicationInfo.uid, mHotwordDetectionComponentName,
mUser, /* bindInstantServiceAllowed= */ false, options, sharedMemory, callback);
mInfo.getServiceInfo().applicationInfo.uid, voiceInteractorIdentity,
mHotwordDetectionComponentName, mUser, /* bindInstantServiceAllowed= */ false,
options, sharedMemory, callback);
} else {
mHotwordDetectionConnection.updateStateLocked(options, sharedMemory);
}