Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit e360fc69 authored by Ahaan Ugale's avatar Ahaan Ugale Committed by Android (Google) Code Review
Browse files

Merge "Check/note ops when delivering HotwordDetectedResult" into sc-dev

parents 1634df7f c5e855a9
Loading
Loading
Loading
Loading
+5 −1
Original line number Diff line number Diff line
@@ -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();
        }
+4 −0
Original line number Diff line number Diff line
@@ -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);
+60 −6
Original line number Diff line number Diff line
@@ -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";
};
+2 −2
Original line number Diff line number Diff line
@@ -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
+11 −3
Original line number Diff line number Diff line
@@ -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);
                }
Loading