Merge "Check/note ops when delivering HotwordDetectedResult" into sc-dev
This commit is contained in:
@@ -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();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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";
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user