diff options
| author | Ahaan Ugale <augale@google.com> | 2021-07-12 17:50:57 +0000 |
|---|---|---|
| committer | Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com> | 2021-07-12 17:50:57 +0000 |
| commit | 4a299daf0c87f9fb93ffbd97d128cf376a472ccc (patch) | |
| tree | dba7d9b2927409b574b33a80bde3afa3d22acf2c | |
| parent | 5a82abc8b9b5288433d2af8083e89bd643f0ccce (diff) | |
| parent | e360fc69a74a75c6c9517d8eda55a0441b594e3c (diff) | |
Merge "Check/note ops when delivering HotwordDetectedResult" into sc-dev am: e360fc69a7
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/15241921
Change-Id: If57ad52446076e4548529e14913b1e710d70e947
6 files changed, 91 insertions, 16 deletions
diff --git a/core/java/android/service/voice/AbstractHotwordDetector.java b/core/java/android/service/voice/AbstractHotwordDetector.java index 54ccf309a58e..dbe108974684 100644 --- a/core/java/android/service/voice/AbstractHotwordDetector.java +++ b/core/java/android/service/voice/AbstractHotwordDetector.java @@ -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(); } diff --git a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl index dddc08a88062..c8a4425409e8 100644 --- a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl +++ b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl @@ -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); diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java index 666265e55372..e408cfc77ad0 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java @@ -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"; }; diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/SoundTriggerSessionPermissionsDecorator.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/SoundTriggerSessionPermissionsDecorator.java index bb7ca168fcaa..b9e1fcd7ffd3 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/SoundTriggerSessionPermissionsDecorator.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/SoundTriggerSessionPermissionsDecorator.java @@ -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 diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java index 91d17f74c676..ccf4267a0fbc 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java @@ -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); } diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java index 89c5a720ee7e..6be47e171ed7 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java @@ -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); } |
