Loading core/java/android/service/voice/AbstractHotwordDetector.java +5 −1 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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(); } Loading core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl +4 −0 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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); Loading services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java +60 −6 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -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") Loading @@ -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); Loading Loading @@ -310,6 +324,7 @@ final class HotwordDetectionConnection { } synchronized (mLock) { if (mPerformingSoftwareHotwordDetection) { enforcePermissionsForDataDelivery(); mSoftwareCallback.onDetected(result, null, null); mPerformingSoftwareHotwordDetection = false; if (result != null) { Loading Loading @@ -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) Loading Loading @@ -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) Loading Loading @@ -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( Loading Loading @@ -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) { Loading Loading @@ -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"; }; services/voiceinteraction/java/com/android/server/voiceinteraction/SoundTriggerSessionPermissionsDecorator.java +2 −2 Original line number Diff line number Diff line Loading @@ -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); Loading @@ -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 Loading services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java +11 −3 Original line number Diff line number Diff line Loading @@ -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(); Loading @@ -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); } Loading Loading
core/java/android/service/voice/AbstractHotwordDetector.java +5 −1 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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(); } Loading
core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl +4 −0 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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); Loading
services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java +60 −6 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -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") Loading @@ -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); Loading Loading @@ -310,6 +324,7 @@ final class HotwordDetectionConnection { } synchronized (mLock) { if (mPerformingSoftwareHotwordDetection) { enforcePermissionsForDataDelivery(); mSoftwareCallback.onDetected(result, null, null); mPerformingSoftwareHotwordDetection = false; if (result != null) { Loading Loading @@ -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) Loading Loading @@ -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) Loading Loading @@ -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( Loading Loading @@ -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) { Loading Loading @@ -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"; };
services/voiceinteraction/java/com/android/server/voiceinteraction/SoundTriggerSessionPermissionsDecorator.java +2 −2 Original line number Diff line number Diff line Loading @@ -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); Loading @@ -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 Loading
services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java +11 −3 Original line number Diff line number Diff line Loading @@ -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(); Loading @@ -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); } Loading