summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAhaan Ugale <augale@google.com>2021-07-12 17:50:57 +0000
committerAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>2021-07-12 17:50:57 +0000
commit4a299daf0c87f9fb93ffbd97d128cf376a472ccc (patch)
treedba7d9b2927409b574b33a80bde3afa3d22acf2c
parent5a82abc8b9b5288433d2af8083e89bd643f0ccce (diff)
parente360fc69a74a75c6c9517d8eda55a0441b594e3c (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
-rw-r--r--core/java/android/service/voice/AbstractHotwordDetector.java6
-rw-r--r--core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl4
-rw-r--r--services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java66
-rw-r--r--services/voiceinteraction/java/com/android/server/voiceinteraction/SoundTriggerSessionPermissionsDecorator.java4
-rw-r--r--services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java14
-rw-r--r--services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java13
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);
}