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

Commit ddb58a79 authored by Tom Chan's avatar Tom Chan Committed by Android (Google) Code Review
Browse files

Merge "Remove permission and AppOpsManager checks for wearable hotword." into main

parents 0baf1490 cc21837b
Loading
Loading
Loading
Loading
+45 −23
Original line number Diff line number Diff line
@@ -411,7 +411,8 @@ abstract class DetectorSession {
                audioFormat,
                options,
                callback,
                /* shouldCloseAudioStreamWithDelayOnDetect= */ true);
                /* shouldCloseAudioStreamWithDelayOnDetect= */ true,
                /* shouldCheckPermissionsAndAppOpsOnDetected= */ true);
    }

    void startListeningFromWearableLocked(
@@ -481,12 +482,29 @@ abstract class DetectorSession {
                        return null;
                    }
                };
        /*
         * By setting shouldCheckPermissionsAndAppOpsOnDetected to false, when the audio
         * stream is sent from the sandboxed HotwordDetectionService to the non-sandboxed
         * VoiceInteractionService as a result of second-stage hotword detection, audio-related
         * permissions will not be checked against the VoiceInteractionService and the AppOpsManager
         * will not be notified of the data flow to the VoiceInteractionService. These checks are
         * not performed because the audio stream here originates from a remotely connected wearable
         * device. It does not originate from the microphone of the device where this code runs on,
         * or a microphone directly controlled by this system. Permission checks are expected to
         * happen on the remote wearable device. From the perspective of this system, the audio
         * stream is data received from an external source.
         *
         * Not notifying AppOpsManager allows this device's microphone indicator to remain off when
         * this data flow happens. It avoids confusion since the audio does not originate from
         * this device. The wearable is expected to turn on its own microphone indicator.
         */
        handleExternalSourceHotwordDetectionLocked(
                audioStream,
                audioFormat,
                options,
                voiceInteractionCallback,
                /* shouldCloseAudioStreamWithDelayOnDetect= */ false);
                /* shouldCloseAudioStreamWithDelayOnDetect= */ false,
                /* shouldCheckPermissionsAndAppOpsOnDetected= */ false);
    }

    @SuppressWarnings("GuardedBy")
@@ -495,7 +513,8 @@ abstract class DetectorSession {
            AudioFormat audioFormat,
            @Nullable PersistableBundle options,
            IMicrophoneHotwordDetectionVoiceInteractionCallback callback,
            boolean shouldCloseAudioStreamWithDelayOnDetect) {
            boolean shouldCloseAudioStreamWithDelayOnDetect,
            boolean shouldCheckPermissionsAndAppOpsOnDetected) {
        if (DEBUG) {
            Slog.d(TAG, "#handleExternalSourceHotwordDetectionLocked");
        }
@@ -631,6 +650,7 @@ abstract class DetectorSession {
                                                    EXTERNAL_HOTWORD_CLEANUP_MILLIS,
                                                    TimeUnit.MILLISECONDS);
                                        }
                                        if (shouldCheckPermissionsAndAppOpsOnDetected) {
                                            try {
                                                enforcePermissionsForDataDelivery();
                                            } catch (SecurityException e) {
@@ -655,12 +675,14 @@ abstract class DetectorSession {
                                                }
                                                return;
                                            }
                                        }
                                        HotwordDetectedResult newResult;
                                        try {
                                            newResult =
                                                mHotwordAudioStreamCopier
                                                    .startCopyingAudioStreams(
                                                                    triggerResult);
                                                        triggerResult,
                                                        shouldCheckPermissionsAndAppOpsOnDetected);
                                        } catch (IOException e) {
                                            Slog.w(
                                                    TAG,
+90 −58
Original line number Diff line number Diff line
@@ -86,19 +86,32 @@ final class HotwordAudioStreamCopier {
        mVoiceInteractorAttributionTag = voiceInteractorAttributionTag;
    }

    /**
     * Calls {@link #startCopyingAudioStreams(HotwordDetectedResult, boolean)} and notifies
     * AppOpsManager of the {@link AppOpsManager#OPSTR_RECORD_AUDIO_HOTWORD} operation.
     */
    @NonNull
    public HotwordDetectedResult startCopyingAudioStreams(@NonNull HotwordDetectedResult result)
            throws IOException {
        return startCopyingAudioStreams(result, /* shouldNotifyAppOpsManager= */ true);
    }

    /**
     * Starts copying the audio streams in the given {@link HotwordDetectedResult}.
     * <p>
     * The returned {@link HotwordDetectedResult} is identical the one that was passed in, except
     *
     * <p>The returned {@link HotwordDetectedResult} is identical the one that was passed in, except
     * that the {@link ParcelFileDescriptor}s within {@link HotwordDetectedResult#getAudioStreams()}
     * are replaced with descriptors from pipes managed by {@link HotwordAudioStreamCopier}. The
     * returned value should be passed on to the client (i.e., the voice interactor).
     * </p>
     *
     * @param result The {@link HotwordDetectedResult} to copy from.
     * @param shouldNotifyAppOpsManager Whether the {@link AppOpsManager} should be notified of the
     *     {@link AppOpsManager#OPSTR_RECORD_AUDIO_HOTWORD} operation during the copy.
     * @throws IOException If there was an error creating the managed pipe.
     */
    @NonNull
    public HotwordDetectedResult startCopyingAudioStreams(@NonNull HotwordDetectedResult result)
    public HotwordDetectedResult startCopyingAudioStreams(
            @NonNull HotwordDetectedResult result, boolean shouldNotifyAppOpsManager)
            throws IOException {
        List<HotwordAudioStream> audioStreams = result.getAudioStreams();
        if (audioStreams.isEmpty()) {
@@ -154,8 +167,12 @@ final class HotwordAudioStreamCopier {

        String resultTaskId = TASK_ID_PREFIX + System.identityHashCode(result);
        mExecutorService.execute(
                new HotwordDetectedResultCopyTask(resultTaskId, copyTaskInfos,
                        totalMetadataBundleSizeBytes, totalInitialAudioSizeBytes));
                new HotwordDetectedResultCopyTask(
                        resultTaskId,
                        copyTaskInfos,
                        totalMetadataBundleSizeBytes,
                        totalInitialAudioSizeBytes,
                        shouldNotifyAppOpsManager));

        return result.buildUpon().setAudioStreams(newAudioStreams).build();
    }
@@ -178,13 +195,19 @@ final class HotwordAudioStreamCopier {
        private final int mTotalMetadataSizeBytes;
        private final int mTotalInitialAudioSizeBytes;
        private final ExecutorService mExecutorService = Executors.newCachedThreadPool();

        HotwordDetectedResultCopyTask(String resultTaskId, List<CopyTaskInfo> copyTaskInfos,
                int totalMetadataSizeBytes, int totalInitialAudioSizeBytes) {
        private final boolean mShouldNotifyAppOpsManager;

        HotwordDetectedResultCopyTask(
                String resultTaskId,
                List<CopyTaskInfo> copyTaskInfos,
                int totalMetadataSizeBytes,
                int totalInitialAudioSizeBytes,
                boolean shouldNotifyAppOpsManager) {
            mResultTaskId = resultTaskId;
            mCopyTaskInfos = copyTaskInfos;
            mTotalMetadataSizeBytes = totalMetadataSizeBytes;
            mTotalInitialAudioSizeBytes = totalInitialAudioSizeBytes;
            mShouldNotifyAppOpsManager = shouldNotifyAppOpsManager;
        }

        @Override
@@ -200,9 +223,25 @@ final class HotwordAudioStreamCopier {
                        mVoiceInteractorUid));
            }

            if (mAppOpsManager.startOpNoThrow(AppOpsManager.OPSTR_RECORD_AUDIO_HOTWORD,
                    mVoiceInteractorUid, mVoiceInteractorPackageName,
                    mVoiceInteractorAttributionTag, OP_MESSAGE) == MODE_ALLOWED) {
            if (mShouldNotifyAppOpsManager
                    && mAppOpsManager.startOpNoThrow(
                                    AppOpsManager.OPSTR_RECORD_AUDIO_HOTWORD,
                                    mVoiceInteractorUid,
                                    mVoiceInteractorPackageName,
                                    mVoiceInteractorAttributionTag,
                                    OP_MESSAGE)
                            != MODE_ALLOWED) {
                HotwordMetricsLogger.writeAudioEgressEvent(mDetectorType,
                        HOTWORD_AUDIO_EGRESS_EVENT_REPORTED__EVENT__NO_PERMISSION,
                        mVoiceInteractorUid, /* streamSizeBytes= */ 0, /* bundleSizeBytes= */ 0,
                        size);
                bestEffortPropagateError(
                        "Failed to obtain RECORD_AUDIO_HOTWORD permission for voice interactor with"
                                + " uid=" + mVoiceInteractorUid
                                + " packageName=" + mVoiceInteractorPackageName
                                + " attributionTag=" + mVoiceInteractorAttributionTag);
                return;
            }
            try {
                HotwordMetricsLogger.writeAudioEgressEvent(mDetectorType,
                        HOTWORD_AUDIO_EGRESS_EVENT_REPORTED__EVENT__STARTED,
@@ -244,20 +283,13 @@ final class HotwordAudioStreamCopier {
                        + mTotalMetadataSizeBytes);
                bestEffortPropagateError(e.getMessage());
            } finally {
                    mAppOpsManager.finishOp(AppOpsManager.OPSTR_RECORD_AUDIO_HOTWORD,
                            mVoiceInteractorUid, mVoiceInteractorPackageName,
                if (mShouldNotifyAppOpsManager) {
                    mAppOpsManager.finishOp(
                            AppOpsManager.OPSTR_RECORD_AUDIO_HOTWORD,
                            mVoiceInteractorUid,
                            mVoiceInteractorPackageName,
                            mVoiceInteractorAttributionTag);
                }
            } else {
                HotwordMetricsLogger.writeAudioEgressEvent(mDetectorType,
                        HOTWORD_AUDIO_EGRESS_EVENT_REPORTED__EVENT__NO_PERMISSION,
                        mVoiceInteractorUid, /* streamSizeBytes= */ 0, /* bundleSizeBytes= */ 0,
                        size);
                bestEffortPropagateError(
                        "Failed to obtain RECORD_AUDIO_HOTWORD permission for voice interactor with"
                                + " uid=" + mVoiceInteractorUid
                                + " packageName=" + mVoiceInteractorPackageName
                                + " attributionTag=" + mVoiceInteractorAttributionTag);
            }
        }