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.NonNull;
import android.annotation.Nullable; import android.annotation.Nullable;
import android.app.ActivityThread;
import android.media.AudioFormat; import android.media.AudioFormat;
import android.media.permission.Identity;
import android.os.Handler; import android.os.Handler;
import android.os.Looper; import android.os.Looper;
import android.os.ParcelFileDescriptor; import android.os.ParcelFileDescriptor;
@@ -111,8 +113,10 @@ abstract class AbstractHotwordDetector implements HotwordDetector {
if (DEBUG) { if (DEBUG) {
Slog.d(TAG, "updateStateLocked()"); Slog.d(TAG, "updateStateLocked()");
} }
Identity identity = new Identity();
identity.packageName = ActivityThread.currentOpPackageName();
try { try {
mManagerService.updateState(options, sharedMemory, callback); mManagerService.updateState(identity, options, sharedMemory, callback);
} catch (RemoteException e) { } catch (RemoteException e) {
throw e.rethrowFromSystemServer(); throw e.rethrowFromSystemServer();
} }

View File

@@ -231,6 +231,9 @@ interface IVoiceInteractionManagerService {
/** /**
* Set configuration and pass read-only data to hotword detection service. * 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 * @param options Application configuration data to provide to the
* {@link HotwordDetectionService}. PersistableBundle does not allow any remotable objects or * {@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. * @param callback Use this to report {@link HotwordDetectionService} status.
*/ */
void updateState( void updateState(
in Identity originatorIdentity,
in PersistableBundle options, in PersistableBundle options,
in SharedMemory sharedMemory, in SharedMemory sharedMemory,
in IHotwordRecognitionStatusCallback callback); in IHotwordRecognitionStatusCallback callback);

View File

@@ -16,20 +16,28 @@
package com.android.server.voiceinteraction; 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_EXTERNAL;
import static android.service.voice.HotwordDetectionService.AUDIO_SOURCE_MICROPHONE; import static android.service.voice.HotwordDetectionService.AUDIO_SOURCE_MICROPHONE;
import static android.service.voice.HotwordDetectionService.INITIALIZATION_STATUS_UNKNOWN; import static android.service.voice.HotwordDetectionService.INITIALIZATION_STATUS_UNKNOWN;
import static android.service.voice.HotwordDetectionService.KEY_INITIALIZATION_STATUS; import static android.service.voice.HotwordDetectionService.KEY_INITIALIZATION_STATUS;
import static com.android.server.voiceinteraction.SoundTriggerSessionPermissionsDecorator.enforcePermissionForPreflight;
import android.annotation.NonNull; import android.annotation.NonNull;
import android.annotation.Nullable; import android.annotation.Nullable;
import android.app.AppOpsManager;
import android.content.ComponentName; import android.content.ComponentName;
import android.content.ContentCaptureOptions; import android.content.ContentCaptureOptions;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.PermissionChecker;
import android.hardware.soundtrigger.IRecognitionStatusCallback; import android.hardware.soundtrigger.IRecognitionStatusCallback;
import android.hardware.soundtrigger.SoundTrigger; import android.hardware.soundtrigger.SoundTrigger;
import android.media.AudioFormat; import android.media.AudioFormat;
import android.media.permission.Identity;
import android.media.permission.PermissionUtil;
import android.os.Binder; import android.os.Binder;
import android.os.Bundle; import android.os.Bundle;
import android.os.IBinder; import android.os.IBinder;
@@ -46,6 +54,7 @@ import android.service.voice.IDspHotwordDetectionCallback;
import android.service.voice.IHotwordDetectionService; import android.service.voice.IHotwordDetectionService;
import android.service.voice.IMicrophoneHotwordDetectionVoiceInteractionCallback; import android.service.voice.IMicrophoneHotwordDetectionVoiceInteractionCallback;
import android.service.voice.VoiceInteractionManagerInternal.HotwordDetectionServiceIdentity; import android.service.voice.VoiceInteractionManagerInternal.HotwordDetectionServiceIdentity;
import android.text.TextUtils;
import android.util.Pair; import android.util.Pair;
import android.util.Slog; import android.util.Slog;
import android.view.contentcapture.IContentCaptureManager; import android.view.contentcapture.IContentCaptureManager;
@@ -107,6 +116,10 @@ final class HotwordDetectionConnection {
private ScheduledFuture<?> mCancellationTaskFuture; private ScheduledFuture<?> mCancellationTaskFuture;
/** Identity used for attributing app ops when delivering data to the Interactor. */
@GuardedBy("mLock")
@Nullable
private final Identity mVoiceInteractorIdentity;
@GuardedBy("mLock") @GuardedBy("mLock")
private ParcelFileDescriptor mCurrentAudioSink; private ParcelFileDescriptor mCurrentAudioSink;
@GuardedBy("mLock") @GuardedBy("mLock")
@@ -117,12 +130,13 @@ final class HotwordDetectionConnection {
private IBinder mAudioFlinger; private IBinder mAudioFlinger;
HotwordDetectionConnection(Object lock, Context context, int voiceInteractionServiceUid, HotwordDetectionConnection(Object lock, Context context, int voiceInteractionServiceUid,
ComponentName serviceName, int userId, boolean bindInstantServiceAllowed, Identity voiceInteractorIdentity, ComponentName serviceName, int userId,
@Nullable PersistableBundle options, @Nullable SharedMemory sharedMemory, boolean bindInstantServiceAllowed, @Nullable PersistableBundle options,
IHotwordRecognitionStatusCallback callback) { @Nullable SharedMemory sharedMemory, IHotwordRecognitionStatusCallback callback) {
mLock = lock; mLock = lock;
mContext = context; mContext = context;
mVoiceInteractionServiceUid = voiceInteractionServiceUid; mVoiceInteractionServiceUid = voiceInteractionServiceUid;
mVoiceInteractorIdentity = voiceInteractorIdentity;
mDetectionComponentName = serviceName; mDetectionComponentName = serviceName;
mUser = userId; mUser = userId;
final Intent intent = new Intent(HotwordDetectionService.SERVICE_INTERFACE); final Intent intent = new Intent(HotwordDetectionService.SERVICE_INTERFACE);
@@ -310,6 +324,7 @@ final class HotwordDetectionConnection {
} }
synchronized (mLock) { synchronized (mLock) {
if (mPerformingSoftwareHotwordDetection) { if (mPerformingSoftwareHotwordDetection) {
enforcePermissionsForDataDelivery();
mSoftwareCallback.onDetected(result, null, null); mSoftwareCallback.onDetected(result, null, null);
mPerformingSoftwareHotwordDetection = false; mPerformingSoftwareHotwordDetection = false;
if (result != null) { if (result != null) {
@@ -404,6 +419,7 @@ final class HotwordDetectionConnection {
synchronized (mLock) { synchronized (mLock) {
if (mValidatingDspTrigger) { if (mValidatingDspTrigger) {
mValidatingDspTrigger = false; mValidatingDspTrigger = false;
enforcePermissionsForDataDelivery();
externalCallback.onKeyphraseDetected(recognitionEvent, result); externalCallback.onKeyphraseDetected(recognitionEvent, result);
if (result != null) { if (result != null) {
Slog.i(TAG, "Egressed " + HotwordDetectedResult.getUsageSize(result) Slog.i(TAG, "Egressed " + HotwordDetectedResult.getUsageSize(result)
@@ -461,6 +477,7 @@ final class HotwordDetectionConnection {
return; return;
} }
mValidatingDspTrigger = false; mValidatingDspTrigger = false;
enforcePermissionsForDataDelivery();
externalCallback.onKeyphraseDetected(recognitionEvent, result); externalCallback.onKeyphraseDetected(recognitionEvent, result);
if (result != null) { if (result != null) {
Slog.i(TAG, "Egressed " + HotwordDetectedResult.getUsageSize(result) Slog.i(TAG, "Egressed " + HotwordDetectedResult.getUsageSize(result)
@@ -575,8 +592,7 @@ final class HotwordDetectionConnection {
if (DEBUG) { if (DEBUG) {
Slog.d(TAG, "onKeyphraseDetected recognitionEvent : " + recognitionEvent); Slog.d(TAG, "onKeyphraseDetected recognitionEvent : " + recognitionEvent);
} }
final boolean useHotwordDetectionService = mHotwordDetectionConnection != null final boolean useHotwordDetectionService = mHotwordDetectionConnection != null;
&& mHotwordDetectionConnection.isBound();
if (useHotwordDetectionService) { if (useHotwordDetectionService) {
mRecognitionEvent = recognitionEvent; mRecognitionEvent = recognitionEvent;
mHotwordDetectionConnection.detectFromDspSource( mHotwordDetectionConnection.detectFromDspSource(
@@ -692,7 +708,7 @@ final class HotwordDetectionConnection {
throws RemoteException { throws RemoteException {
bestEffortClose(serviceAudioSink); bestEffortClose(serviceAudioSink);
bestEffortClose(serviceAudioSource); bestEffortClose(serviceAudioSource);
// TODO: noteOp here. enforcePermissionsForDataDelivery();
callback.onDetected(triggerResult, null /* audioFormat */, callback.onDetected(triggerResult, null /* audioFormat */,
null /* audioStream */); null /* audioStream */);
if (triggerResult != null) { 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 identity The identity to check.
* @param permission The identifier of the permission we want 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) { @NonNull Identity identity, @NonNull String permission) {
final int status = PermissionUtil.checkPermissionForPreflight(context, identity, final int status = PermissionUtil.checkPermissionForPreflight(context, identity,
permission); permission);
@@ -144,7 +144,7 @@ final class SoundTriggerSessionPermissionsDecorator implements
} }
} }
private static String toString(Identity identity) { static String toString(Identity identity) {
return "{uid=" + identity.uid return "{uid=" + identity.uid
+ " pid=" + identity.pid + " pid=" + identity.pid
+ " packageName=" + identity.packageName + " packageName=" + identity.packageName

View File

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

View File

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