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

Commit e4e77f45 authored by Ivan Chiang's avatar Ivan Chiang
Browse files

[RESTRICT AUTOMERGE] Check permission for VoiceInteraction

The service must have the CAPTURE_AUDIO_HOTWORD permission to access
AlwaysOnHotwordDetector. If it doesn't have the permission, return
STATE_HARDWARE_UNAVAILABLE state. If it is not granted the
RECORD_AUDIO permisison, it also can't start to recognize the audio.

Test: manual
Test: atest CtsVoiceInteractionTestCases
Test: atest CtsAssistTestCases
Bug: 229793943
Change-Id: I7d0f8d2f6af4bc4210060f0a44469db2afc7a1bb
Merged-In: I7d0f8d2f6af4bc4210060f0a44469db2afc7a1bb
parent 750c978d
Loading
Loading
Loading
Loading
+38 −2
Original line number Diff line number Diff line
@@ -16,12 +16,15 @@

package android.service.voice;

import android.Manifest;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UnsupportedAppUsage;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.hardware.soundtrigger.IRecognitionStatusCallback;
import android.hardware.soundtrigger.KeyphraseEnrollmentInfo;
import android.hardware.soundtrigger.KeyphraseMetadata;
@@ -195,8 +198,10 @@ public class AlwaysOnHotwordDetector {
    private final Callback mExternalCallback;
    private final Object mLock = new Object();
    private final Handler mHandler;
    private final Context mContext;

    private int mAvailability = STATE_NOT_READY;
    private boolean mIsGrantedHotwordPermission;

    /**
     * Additional payload for {@link Callback#onDetected}.
@@ -324,19 +329,32 @@ public class AlwaysOnHotwordDetector {
        public abstract void onRecognitionResumed();
    }

    private static boolean hasHotwordPermission(Context context) {
        return context.checkSelfPermission(Manifest.permission.CAPTURE_AUDIO_HOTWORD)
            == PackageManager.PERMISSION_GRANTED;
    }

    private static boolean hasRecordAudioPermission(Context context) {
        return context.checkSelfPermission(Manifest.permission.RECORD_AUDIO)
            == PackageManager.PERMISSION_GRANTED;
    }

    /**
     * @param context The context to check permission
     * @param text The keyphrase text to get the detector for.
     * @param locale The java locale for the detector.
     * @param callback A non-null Callback for receiving the recognition events.
     * @param keyphraseEnrollmentInfo The Enrollment info of key phrase
     * @param voiceInteractionService The current voice interaction service.
     * @param modelManagementService A service that allows management of sound models.
     *
     * @hide
     */
    public AlwaysOnHotwordDetector(String text, Locale locale, Callback callback,
    public AlwaysOnHotwordDetector(Context context, String text, Locale locale, Callback callback,
            KeyphraseEnrollmentInfo keyphraseEnrollmentInfo,
            IVoiceInteractionService voiceInteractionService,
            IVoiceInteractionManagerService modelManagementService) {
        mContext = context;
        mText = text;
        mLocale = locale;
        mKeyphraseEnrollmentInfo = keyphraseEnrollmentInfo;
@@ -346,6 +364,7 @@ public class AlwaysOnHotwordDetector {
        mInternalCallback = new SoundTriggerListener(mHandler);
        mVoiceInteractionService = voiceInteractionService;
        mModelManagementService = modelManagementService;
        mIsGrantedHotwordPermission = hasHotwordPermission(mContext);
        new RefreshAvailabiltyTask().execute();
    }

@@ -402,6 +421,12 @@ public class AlwaysOnHotwordDetector {
     */
    public boolean startRecognition(@RecognitionFlags int recognitionFlags) {
        if (DBG) Slog.d(TAG, "startRecognition(" + recognitionFlags + ")");

        if (!mIsGrantedHotwordPermission || !hasRecordAudioPermission(mContext)) {
            throw new IllegalStateException("Must have the RECORD_AUDIO and CAPTURE_AUDIO_HOTWORD "
                + "permissions to access the detector.");
        }

        synchronized (mLock) {
            if (mAvailability == STATE_INVALID) {
                throw new IllegalStateException("startRecognition called on an invalid detector");
@@ -430,6 +455,12 @@ public class AlwaysOnHotwordDetector {
     */
    public boolean stopRecognition() {
        if (DBG) Slog.d(TAG, "stopRecognition()");

        if (!mIsGrantedHotwordPermission || !hasRecordAudioPermission(mContext)) {
            throw new IllegalStateException("Must have the RECORD_AUDIO and CAPTURE_AUDIO_HOTWORD "
                + "permissions to access the detector.");
        }

        synchronized (mLock) {
            if (mAvailability == STATE_INVALID) {
                throw new IllegalStateException("stopRecognition called on an invalid detector");
@@ -546,7 +577,8 @@ public class AlwaysOnHotwordDetector {
        synchronized (mLock) {
            if (mAvailability == STATE_INVALID
                    || mAvailability == STATE_HARDWARE_UNAVAILABLE
                    || mAvailability == STATE_KEYPHRASE_UNSUPPORTED) {
                    || mAvailability == STATE_KEYPHRASE_UNSUPPORTED
                    || !hasRecordAudioPermission(mContext)) {
                Slog.w(TAG, "Received onSoundModelsChanged for an unsupported keyphrase/config");
                return;
            }
@@ -717,6 +749,10 @@ public class AlwaysOnHotwordDetector {
         * @return The initial availability without checking the enrollment status.
         */
        private int internalGetInitialAvailability() {
            if (!mIsGrantedHotwordPermission) {
                return STATE_HARDWARE_UNAVAILABLE;
            }

            synchronized (mLock) {
                // This detector has already been invalidated.
                if (mAvailability == STATE_INVALID) {
+1 −1
Original line number Diff line number Diff line
@@ -297,7 +297,7 @@ public class VoiceInteractionService extends Service {
        synchronized (mLock) {
            // Allow only one concurrent recognition via the APIs.
            safelyShutdownHotwordDetector();
            mHotwordDetector = new AlwaysOnHotwordDetector(keyphrase, locale, callback,
            mHotwordDetector = new AlwaysOnHotwordDetector(this, keyphrase, locale, callback,
                    mKeyphraseEnrollmentInfo, mInterface, mSystemService);
        }
        return mHotwordDetector;
+14 −0
Original line number Diff line number Diff line
@@ -956,6 +956,9 @@ public class VoiceInteractionManagerService extends SystemService {

        @Override
        public ModuleProperties getDspModuleProperties(IVoiceInteractionService service) {
            // Allow the call if it is granted CAPTURE_AUDIO_HOTWORD.
            enforceCallingPermission(Manifest.permission.CAPTURE_AUDIO_HOTWORD);

            // Allow the call if this is the current voice interaction service.
            synchronized (this) {
                enforceIsCurrentVoiceInteractionService(service);
@@ -973,6 +976,9 @@ public class VoiceInteractionManagerService extends SystemService {
        public int startRecognition(IVoiceInteractionService service, int keyphraseId,
                String bcp47Locale, IRecognitionStatusCallback callback,
                RecognitionConfig recognitionConfig) {
            // Allow the call if it is granted RECORD_AUDIO and CAPTURE_AUDIO_HOTWORD.
            enforceAlwaysOnHotwordPermissions();

            // Allow the call if this is the current voice interaction service.
            synchronized (this) {
                enforceIsCurrentVoiceInteractionService(service);
@@ -1009,6 +1015,9 @@ public class VoiceInteractionManagerService extends SystemService {
        @Override
        public int stopRecognition(IVoiceInteractionService service, int keyphraseId,
                IRecognitionStatusCallback callback) {
            // Allow the call if it is granted RECORD_AUDIO and CAPTURE_AUDIO_HOTWORD.
            enforceAlwaysOnHotwordPermissions();

            // Allow the call if this is the current voice interaction service.
            synchronized (this) {
                enforceIsCurrentVoiceInteractionService(service);
@@ -1254,6 +1263,11 @@ public class VoiceInteractionManagerService extends SystemService {
            }
        }

        private void enforceAlwaysOnHotwordPermissions() {
            enforceCallingPermission(Manifest.permission.RECORD_AUDIO);
            enforceCallingPermission(Manifest.permission.CAPTURE_AUDIO_HOTWORD);
        }

        private void enforceCallingPermission(String permission) {
            if (mContext.checkCallingOrSelfPermission(permission)
                    != PackageManager.PERMISSION_GRANTED) {