[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
This commit is contained in:
Ivan Chiang
2022-08-15 15:09:33 +08:00
parent 750c978db5
commit e4e77f4570
3 changed files with 53 additions and 3 deletions

View File

@@ -16,12 +16,15 @@
package android.service.voice; package android.service.voice;
import android.Manifest;
import android.annotation.IntDef; import android.annotation.IntDef;
import android.annotation.NonNull; import android.annotation.NonNull;
import android.annotation.Nullable; import android.annotation.Nullable;
import android.annotation.UnsupportedAppUsage; import android.annotation.UnsupportedAppUsage;
import android.app.Activity; import android.app.Activity;
import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.pm.PackageManager;
import android.hardware.soundtrigger.IRecognitionStatusCallback; import android.hardware.soundtrigger.IRecognitionStatusCallback;
import android.hardware.soundtrigger.KeyphraseEnrollmentInfo; import android.hardware.soundtrigger.KeyphraseEnrollmentInfo;
import android.hardware.soundtrigger.KeyphraseMetadata; import android.hardware.soundtrigger.KeyphraseMetadata;
@@ -195,8 +198,10 @@ public class AlwaysOnHotwordDetector {
private final Callback mExternalCallback; private final Callback mExternalCallback;
private final Object mLock = new Object(); private final Object mLock = new Object();
private final Handler mHandler; private final Handler mHandler;
private final Context mContext;
private int mAvailability = STATE_NOT_READY; private int mAvailability = STATE_NOT_READY;
private boolean mIsGrantedHotwordPermission;
/** /**
* Additional payload for {@link Callback#onDetected}. * Additional payload for {@link Callback#onDetected}.
@@ -324,19 +329,32 @@ public class AlwaysOnHotwordDetector {
public abstract void onRecognitionResumed(); 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 text The keyphrase text to get the detector for.
* @param locale The java locale for the detector. * @param locale The java locale for the detector.
* @param callback A non-null Callback for receiving the recognition events. * @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 voiceInteractionService The current voice interaction service.
* @param modelManagementService A service that allows management of sound models. * @param modelManagementService A service that allows management of sound models.
* *
* @hide * @hide
*/ */
public AlwaysOnHotwordDetector(String text, Locale locale, Callback callback, public AlwaysOnHotwordDetector(Context context, String text, Locale locale, Callback callback,
KeyphraseEnrollmentInfo keyphraseEnrollmentInfo, KeyphraseEnrollmentInfo keyphraseEnrollmentInfo,
IVoiceInteractionService voiceInteractionService, IVoiceInteractionService voiceInteractionService,
IVoiceInteractionManagerService modelManagementService) { IVoiceInteractionManagerService modelManagementService) {
mContext = context;
mText = text; mText = text;
mLocale = locale; mLocale = locale;
mKeyphraseEnrollmentInfo = keyphraseEnrollmentInfo; mKeyphraseEnrollmentInfo = keyphraseEnrollmentInfo;
@@ -346,6 +364,7 @@ public class AlwaysOnHotwordDetector {
mInternalCallback = new SoundTriggerListener(mHandler); mInternalCallback = new SoundTriggerListener(mHandler);
mVoiceInteractionService = voiceInteractionService; mVoiceInteractionService = voiceInteractionService;
mModelManagementService = modelManagementService; mModelManagementService = modelManagementService;
mIsGrantedHotwordPermission = hasHotwordPermission(mContext);
new RefreshAvailabiltyTask().execute(); new RefreshAvailabiltyTask().execute();
} }
@@ -402,6 +421,12 @@ public class AlwaysOnHotwordDetector {
*/ */
public boolean startRecognition(@RecognitionFlags int recognitionFlags) { public boolean startRecognition(@RecognitionFlags int recognitionFlags) {
if (DBG) Slog.d(TAG, "startRecognition(" + 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) { synchronized (mLock) {
if (mAvailability == STATE_INVALID) { if (mAvailability == STATE_INVALID) {
throw new IllegalStateException("startRecognition called on an invalid detector"); throw new IllegalStateException("startRecognition called on an invalid detector");
@@ -430,6 +455,12 @@ public class AlwaysOnHotwordDetector {
*/ */
public boolean stopRecognition() { public boolean stopRecognition() {
if (DBG) Slog.d(TAG, "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) { synchronized (mLock) {
if (mAvailability == STATE_INVALID) { if (mAvailability == STATE_INVALID) {
throw new IllegalStateException("stopRecognition called on an invalid detector"); throw new IllegalStateException("stopRecognition called on an invalid detector");
@@ -546,7 +577,8 @@ public class AlwaysOnHotwordDetector {
synchronized (mLock) { synchronized (mLock) {
if (mAvailability == STATE_INVALID if (mAvailability == STATE_INVALID
|| mAvailability == STATE_HARDWARE_UNAVAILABLE || mAvailability == STATE_HARDWARE_UNAVAILABLE
|| mAvailability == STATE_KEYPHRASE_UNSUPPORTED) { || mAvailability == STATE_KEYPHRASE_UNSUPPORTED
|| !hasRecordAudioPermission(mContext)) {
Slog.w(TAG, "Received onSoundModelsChanged for an unsupported keyphrase/config"); Slog.w(TAG, "Received onSoundModelsChanged for an unsupported keyphrase/config");
return; return;
} }
@@ -717,6 +749,10 @@ public class AlwaysOnHotwordDetector {
* @return The initial availability without checking the enrollment status. * @return The initial availability without checking the enrollment status.
*/ */
private int internalGetInitialAvailability() { private int internalGetInitialAvailability() {
if (!mIsGrantedHotwordPermission) {
return STATE_HARDWARE_UNAVAILABLE;
}
synchronized (mLock) { synchronized (mLock) {
// This detector has already been invalidated. // This detector has already been invalidated.
if (mAvailability == STATE_INVALID) { if (mAvailability == STATE_INVALID) {

View File

@@ -297,7 +297,7 @@ public class VoiceInteractionService extends Service {
synchronized (mLock) { synchronized (mLock) {
// Allow only one concurrent recognition via the APIs. // Allow only one concurrent recognition via the APIs.
safelyShutdownHotwordDetector(); safelyShutdownHotwordDetector();
mHotwordDetector = new AlwaysOnHotwordDetector(keyphrase, locale, callback, mHotwordDetector = new AlwaysOnHotwordDetector(this, keyphrase, locale, callback,
mKeyphraseEnrollmentInfo, mInterface, mSystemService); mKeyphraseEnrollmentInfo, mInterface, mSystemService);
} }
return mHotwordDetector; return mHotwordDetector;

View File

@@ -956,6 +956,9 @@ public class VoiceInteractionManagerService extends SystemService {
@Override @Override
public ModuleProperties getDspModuleProperties(IVoiceInteractionService service) { 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. // Allow the call if this is the current voice interaction service.
synchronized (this) { synchronized (this) {
enforceIsCurrentVoiceInteractionService(service); enforceIsCurrentVoiceInteractionService(service);
@@ -973,6 +976,9 @@ public class VoiceInteractionManagerService extends SystemService {
public int startRecognition(IVoiceInteractionService service, int keyphraseId, public int startRecognition(IVoiceInteractionService service, int keyphraseId,
String bcp47Locale, IRecognitionStatusCallback callback, String bcp47Locale, IRecognitionStatusCallback callback,
RecognitionConfig recognitionConfig) { 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. // Allow the call if this is the current voice interaction service.
synchronized (this) { synchronized (this) {
enforceIsCurrentVoiceInteractionService(service); enforceIsCurrentVoiceInteractionService(service);
@@ -1009,6 +1015,9 @@ public class VoiceInteractionManagerService extends SystemService {
@Override @Override
public int stopRecognition(IVoiceInteractionService service, int keyphraseId, public int stopRecognition(IVoiceInteractionService service, int keyphraseId,
IRecognitionStatusCallback callback) { 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. // Allow the call if this is the current voice interaction service.
synchronized (this) { synchronized (this) {
enforceIsCurrentVoiceInteractionService(service); 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) { private void enforceCallingPermission(String permission) {
if (mContext.checkCallingOrSelfPermission(permission) if (mContext.checkCallingOrSelfPermission(permission)
!= PackageManager.PERMISSION_GRANTED) { != PackageManager.PERMISSION_GRANTED) {