From 28cc6364d639016fa9fccf98f2ab4bc005d08b7d Mon Sep 17 00:00:00 2001 From: Nicholas Ambur Date: Fri, 20 Nov 2020 15:50:02 -0800 Subject: [PATCH] add API for ST clients run in battery saver mode New system API interface for both SoundTriggerManager and AlwaysOnHotwordDetector to indicate if a recognition should run in battery saver mode or not. Clients supply this information through the existing startRecognition calls, and the client must hold a new privledged permission, SOUND_TRIGGER_RUN_IN_BATTERY_SAVER, to indicate this intention. As a prerequisite, the device PowerManagerService must have the SoundTrigger service enabled in the battery saver mode battery policy. If not enabled, recognition will be paused as if the client did not provide the indication to run in battery saver mode. Bug: 172294448 Test: build with Google search apk using this feature and verify recognition keeps running in battery saver mode Change-Id: Ia43be99290e6fd7c50ff8e4908d6c60ea513b19a --- core/api/system-current.txt | 2 + .../voice/AlwaysOnHotwordDetector.java | 13 +- .../internal/app/ISoundTriggerSession.aidl | 2 +- .../IVoiceInteractionSoundTriggerSession.aidl | 3 +- .../soundtrigger/SoundTriggerDetector.java | 16 +- .../soundtrigger/SoundTriggerHelper.java | 156 +++++++++++------- .../soundtrigger/SoundTriggerInternal.java | 6 +- .../soundtrigger/SoundTriggerService.java | 15 +- .../VoiceInteractionManagerService.java | 10 +- 9 files changed, 151 insertions(+), 72 deletions(-) diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 972995bf74e94..7833eda405e64 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -5061,6 +5061,7 @@ package android.media.soundtrigger { field public static final int RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO = 1; // 0x1 field public static final int RECOGNITION_FLAG_ENABLE_AUDIO_ECHO_CANCELLATION = 4; // 0x4 field public static final int RECOGNITION_FLAG_ENABLE_AUDIO_NOISE_SUPPRESSION = 8; // 0x8 + field public static final int RECOGNITION_FLAG_RUN_IN_BATTERY_SAVER = 16; // 0x10 } public abstract static class SoundTriggerDetector.Callback { @@ -10204,6 +10205,7 @@ package android.service.voice { field public static final int RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO = 1; // 0x1 field public static final int RECOGNITION_FLAG_ENABLE_AUDIO_ECHO_CANCELLATION = 4; // 0x4 field public static final int RECOGNITION_FLAG_ENABLE_AUDIO_NOISE_SUPPRESSION = 8; // 0x8 + field public static final int RECOGNITION_FLAG_RUN_IN_BATTERY_SAVER = 16; // 0x10 field public static final int RECOGNITION_MODE_USER_IDENTIFICATION = 2; // 0x2 field public static final int RECOGNITION_MODE_VOICE_TRIGGER = 1; // 0x1 field public static final int STATE_ERROR = 3; // 0x3 diff --git a/core/java/android/service/voice/AlwaysOnHotwordDetector.java b/core/java/android/service/voice/AlwaysOnHotwordDetector.java index e934fa483ebe1..117b769d64051 100644 --- a/core/java/android/service/voice/AlwaysOnHotwordDetector.java +++ b/core/java/android/service/voice/AlwaysOnHotwordDetector.java @@ -125,6 +125,7 @@ public class AlwaysOnHotwordDetector { RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS, RECOGNITION_FLAG_ENABLE_AUDIO_ECHO_CANCELLATION, RECOGNITION_FLAG_ENABLE_AUDIO_NOISE_SUPPRESSION, + RECOGNITION_FLAG_RUN_IN_BATTERY_SAVER, }) public @interface RecognitionFlags {} @@ -171,6 +172,14 @@ public class AlwaysOnHotwordDetector { */ public static final int RECOGNITION_FLAG_ENABLE_AUDIO_NOISE_SUPPRESSION = 0x8; + /** + * Recognition flag for {@link #startRecognition(int)} that indicates whether the recognition + * should continue after battery saver mode is enabled. + * When this flag is specified, the caller will be checked for + * {@link android.Manifest.permission#SOUND_TRIGGER_RUN_IN_BATTERY_SAVER} permission granted. + */ + public static final int RECOGNITION_FLAG_RUN_IN_BATTERY_SAVER = 0x10; + //---- Recognition mode flags. Return codes for getSupportedRecognitionModes() ----// // Must be kept in sync with the related attribute defined as searchKeyphraseRecognitionFlags. @@ -860,6 +869,7 @@ public class AlwaysOnHotwordDetector { (recognitionFlags&RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO) != 0; boolean allowMultipleTriggers = (recognitionFlags&RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS) != 0; + boolean runInBatterySaver = (recognitionFlags&RECOGNITION_FLAG_RUN_IN_BATTERY_SAVER) != 0; int audioCapabilities = 0; if ((recognitionFlags & RECOGNITION_FLAG_ENABLE_AUDIO_ECHO_CANCELLATION) != 0) { @@ -874,7 +884,8 @@ public class AlwaysOnHotwordDetector { code = mSoundTriggerSession.startRecognition( mKeyphraseMetadata.getId(), mLocale.toLanguageTag(), mInternalCallback, new RecognitionConfig(captureTriggerAudio, allowMultipleTriggers, - recognitionExtra, null /* additional data */, audioCapabilities)); + recognitionExtra, null /* additional data */, audioCapabilities), + runInBatterySaver); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/core/java/com/android/internal/app/ISoundTriggerSession.aidl b/core/java/com/android/internal/app/ISoundTriggerSession.aidl index ec7d282522c8b..a1ba558a5e7fb 100644 --- a/core/java/com/android/internal/app/ISoundTriggerSession.aidl +++ b/core/java/com/android/internal/app/ISoundTriggerSession.aidl @@ -37,7 +37,7 @@ interface ISoundTriggerSession { void deleteSoundModel(in ParcelUuid soundModelId); int startRecognition(in ParcelUuid soundModelId, in IRecognitionStatusCallback callback, - in SoundTrigger.RecognitionConfig config); + in SoundTrigger.RecognitionConfig config, boolean runInBatterySaver); int stopRecognition(in ParcelUuid soundModelId, in IRecognitionStatusCallback callback); diff --git a/core/java/com/android/internal/app/IVoiceInteractionSoundTriggerSession.aidl b/core/java/com/android/internal/app/IVoiceInteractionSoundTriggerSession.aidl index 33aab4132150d..6149260726016 100644 --- a/core/java/com/android/internal/app/IVoiceInteractionSoundTriggerSession.aidl +++ b/core/java/com/android/internal/app/IVoiceInteractionSoundTriggerSession.aidl @@ -41,7 +41,8 @@ interface IVoiceInteractionSoundTriggerSession { */ int startRecognition(int keyphraseId, in String bcp47Locale, in IRecognitionStatusCallback callback, - in SoundTrigger.RecognitionConfig recognitionConfig); + in SoundTrigger.RecognitionConfig recognitionConfig, + boolean runInBatterySaver); /** * Stops a recognition for the given keyphrase. * Caller must be the active voice interaction service via diff --git a/media/java/android/media/soundtrigger/SoundTriggerDetector.java b/media/java/android/media/soundtrigger/SoundTriggerDetector.java index 0a1eefae59d6b..d9c8a240fa1a1 100644 --- a/media/java/android/media/soundtrigger/SoundTriggerDetector.java +++ b/media/java/android/media/soundtrigger/SoundTriggerDetector.java @@ -79,7 +79,8 @@ public final class SoundTriggerDetector { RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO, RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS, RECOGNITION_FLAG_ENABLE_AUDIO_ECHO_CANCELLATION, - RECOGNITION_FLAG_ENABLE_AUDIO_NOISE_SUPPRESSION, + RECOGNITION_FLAG_ENABLE_AUDIO_NOISE_SUPPRESSION, + RECOGNITION_FLAG_RUN_IN_BATTERY_SAVER, }) public @interface RecognitionFlags {} @@ -132,6 +133,14 @@ public final class SoundTriggerDetector { */ public static final int RECOGNITION_FLAG_ENABLE_AUDIO_NOISE_SUPPRESSION = 0x8; + /** + * Recognition flag for {@link #startRecognition(int)} that indicates whether the recognition + * should continue after battery saver mode is enabled. + * When this flag is specified, the caller will be checked for + * {@link android.Manifest.permission#SOUND_TRIGGER_RUN_IN_BATTERY_SAVER} permission granted. + */ + public static final int RECOGNITION_FLAG_RUN_IN_BATTERY_SAVER = 0x10; + /** * Additional payload for {@link Callback#onDetected}. */ @@ -296,6 +305,8 @@ public final class SoundTriggerDetector { boolean allowMultipleTriggers = (recognitionFlags & RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS) != 0; + boolean runInBatterySaver = (recognitionFlags & RECOGNITION_FLAG_RUN_IN_BATTERY_SAVER) != 0; + int audioCapabilities = 0; if ((recognitionFlags & RECOGNITION_FLAG_ENABLE_AUDIO_ECHO_CANCELLATION) != 0) { audioCapabilities |= SoundTrigger.ModuleProperties.AUDIO_CAPABILITY_ECHO_CANCELLATION; @@ -308,7 +319,8 @@ public final class SoundTriggerDetector { try { status = mSoundTriggerSession.startRecognition(new ParcelUuid(mSoundModelId), mRecognitionCallback, new RecognitionConfig(captureTriggerAudio, - allowMultipleTriggers, null, null, audioCapabilities)); + allowMultipleTriggers, null, null, audioCapabilities), + runInBatterySaver); } catch (RemoteException e) { return false; } diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java index 283fae563ea34..16d83d133fbe6 100644 --- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java +++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java @@ -44,7 +44,7 @@ import android.os.Handler; import android.os.Looper; import android.os.Message; import android.os.PowerManager; -import android.os.PowerManager.ServiceType; +import android.os.PowerManager.SoundTriggerPowerSaveMode; import android.os.RemoteException; import android.telephony.PhoneStateListener; import android.telephony.TelephonyManager; @@ -107,7 +107,8 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener { private HashMap mKeyphraseUuidMap; private boolean mCallActive = false; - private boolean mIsPowerSaveMode = false; + private @SoundTriggerPowerSaveMode int mSoundTriggerPowerSaveMode = + PowerManager.SOUND_TRIGGER_MODE_ALL_ENABLED; // Indicates if the native sound trigger service is disabled or not. // This is an indirect indication of the microphone being open in some other application. private boolean mServiceDisabled = false; @@ -205,7 +206,8 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener { * @return One of {@link #STATUS_ERROR} or {@link #STATUS_OK}. */ int startGenericRecognition(UUID modelId, GenericSoundModel soundModel, - IRecognitionStatusCallback callback, RecognitionConfig recognitionConfig) { + IRecognitionStatusCallback callback, RecognitionConfig recognitionConfig, + boolean runInBatterySaverMode) { MetricsLogger.count(mContext, "sth_start_recognition", 1); if (modelId == null || soundModel == null || callback == null || recognitionConfig == null) { @@ -220,7 +222,7 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener { return STATUS_ERROR; } return startRecognition(soundModel, modelData, callback, recognitionConfig, - INVALID_VALUE /* keyphraseId */); + INVALID_VALUE /* keyphraseId */, runInBatterySaverMode); } } @@ -234,7 +236,8 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener { * @return One of {@link #STATUS_ERROR} or {@link #STATUS_OK}. */ int startKeyphraseRecognition(int keyphraseId, KeyphraseSoundModel soundModel, - IRecognitionStatusCallback callback, RecognitionConfig recognitionConfig) { + IRecognitionStatusCallback callback, RecognitionConfig recognitionConfig, + boolean runInBatterySaverMode) { synchronized (mLock) { MetricsLogger.count(mContext, "sth_start_recognition", 1); if (soundModel == null || callback == null || recognitionConfig == null) { @@ -244,7 +247,8 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener { if (DBG) { Slog.d(TAG, "startKeyphraseRecognition for keyphraseId=" + keyphraseId + " soundModel=" + soundModel + ", callback=" + callback.asBinder() - + ", recognitionConfig=" + recognitionConfig); + + ", recognitionConfig=" + recognitionConfig + + ", runInBatterySaverMode=" + runInBatterySaverMode); Slog.d(TAG, "moduleProperties=" + mModuleProperties); dumpModelStateLocked(); } @@ -273,7 +277,7 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener { } return startRecognition(soundModel, model, callback, recognitionConfig, - keyphraseId); + keyphraseId, runInBatterySaverMode); } } @@ -334,7 +338,7 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener { */ int startRecognition(SoundModel soundModel, ModelData modelData, IRecognitionStatusCallback callback, RecognitionConfig recognitionConfig, - int keyphraseId) { + int keyphraseId, boolean runInBatterySaverMode) { synchronized (mLock) { if (mModuleProperties == null) { Slog.w(TAG, "Attempting startRecognition without the capability"); @@ -387,10 +391,11 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener { modelData.setCallback(callback); modelData.setRequested(true); modelData.setRecognitionConfig(recognitionConfig); + modelData.setRunInBatterySaverMode(runInBatterySaverMode); modelData.setSoundModel(soundModel); - if (!isRecognitionAllowed()) { - initializeTelephonyAndPowerStateListeners(); + if (!isRecognitionAllowedByDeviceState(modelData)) { + initializeDeviceStateListeners(); return STATUS_OK; } @@ -404,7 +409,7 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener { // Initialize power save, call active state monitoring logic. if (status == STATUS_OK) { - initializeTelephonyAndPowerStateListeners(); + initializeDeviceStateListeners(); } return status; @@ -519,8 +524,7 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener { // Request stop recognition via the update() method. modelData.setRequested(false); - int status = updateRecognitionLocked(modelData, isRecognitionAllowed(), - false /* don't notify for synchronous calls */); + int status = updateRecognitionLocked(modelData, false); if (status != SoundTrigger.STATUS_OK) { return status; } @@ -589,8 +593,7 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener { // Stop recognition if it's the current one. modelData.setRequested(false); - int status = updateRecognitionLocked(modelData, isRecognitionAllowed(), - false /* don't notify */); + int status = updateRecognitionLocked(modelData, false); if (status != SoundTrigger.STATUS_OK) { Slog.w(TAG, "Stop recognition failed for keyphrase ID:" + status); } @@ -854,8 +857,7 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener { model.setRequested(config.allowMultipleTriggers); // TODO: Remove this block if the lower layer supports multiple triggers. if (model.isRequested()) { - updateRecognitionLocked(model, isRecognitionAllowed() /* isAllowed */, - true /* notify */); + updateRecognitionLocked(model, true); } } @@ -896,15 +898,16 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener { return; } mCallActive = callActive; - updateAllRecognitionsLocked(true /* notify */); + updateAllRecognitionsLocked(); } - private void onPowerSaveModeChangedLocked(boolean isPowerSaveMode) { - if (mIsPowerSaveMode == isPowerSaveMode) { + private void onPowerSaveModeChangedLocked( + @SoundTriggerPowerSaveMode int soundTriggerPowerSaveMode) { + if (mSoundTriggerPowerSaveMode == soundTriggerPowerSaveMode) { return; } - mIsPowerSaveMode = isPowerSaveMode; - updateAllRecognitionsLocked(true /* notify */); + mSoundTriggerPowerSaveMode = soundTriggerPowerSaveMode; + updateAllRecognitionsLocked(); } private void onSoundModelUpdatedLocked(SoundModelEvent event) { @@ -916,7 +919,7 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener { return; } mServiceDisabled = disabled; - updateAllRecognitionsLocked(true /* notify */); + updateAllRecognitionsLocked(); } private void onRecognitionAbortLocked(RecognitionEvent event) { @@ -997,34 +1000,32 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener { } // TODO: Remove this block if the lower layer supports multiple triggers. if (modelData.isRequested()) { - updateRecognitionLocked(modelData, isRecognitionAllowed(), true /* notify */); + updateRecognitionLocked(modelData, true); } } - private void updateAllRecognitionsLocked(boolean notify) { - boolean isAllowed = isRecognitionAllowed(); + private void updateAllRecognitionsLocked() { // updateRecognitionLocked can possibly update the list of models ArrayList modelDatas = new ArrayList(mModelDataMap.values()); for (ModelData modelData : modelDatas) { - updateRecognitionLocked(modelData, isAllowed, notify); + updateRecognitionLocked(modelData, true); } } - private int updateRecognitionLocked(ModelData model, boolean isAllowed, - boolean notify) { - boolean start = model.isRequested() && isAllowed; - if (start == model.isModelStarted()) { + private int updateRecognitionLocked(ModelData model, boolean notifyClientOnError) { + boolean shouldStartModel = model.isRequested() && isRecognitionAllowedByDeviceState(model); + if (shouldStartModel == model.isModelStarted()) { // No-op. return STATUS_OK; } - if (start) { + if (shouldStartModel) { int status = prepareForRecognition(model); if (status != STATUS_OK) { return status; } - return startRecognitionLocked(model, notify); + return startRecognitionLocked(model, notifyClientOnError); } else { - return stopRecognitionLocked(model, notify); + return stopRecognitionLocked(model, notifyClientOnError); } } @@ -1095,11 +1096,13 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener { if (!PowerManager.ACTION_POWER_SAVE_MODE_CHANGED.equals(intent.getAction())) { return; } - boolean active = mPowerManager.getPowerSaveState(ServiceType.SOUND) - .batterySaverEnabled; - if (DBG) Slog.d(TAG, "onPowerSaveModeChanged: " + active); + @SoundTriggerPowerSaveMode int soundTriggerPowerSaveMode = + mPowerManager.getSoundTriggerPowerSaveMode(); + if (DBG) { + Slog.d(TAG, "onPowerSaveModeChanged: " + soundTriggerPowerSaveMode); + } synchronized (mLock) { - onPowerSaveModeChangedLocked(active); + onPowerSaveModeChangedLocked(soundTriggerPowerSaveMode); } } } @@ -1108,14 +1111,15 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener { synchronized (mLock) { pw.print(" module properties="); pw.println(mModuleProperties == null ? "null" : mModuleProperties); - - pw.print(" call active="); pw.println(mCallActive); - pw.print(" power save mode active="); pw.println(mIsPowerSaveMode); - pw.print(" service disabled="); pw.println(mServiceDisabled); + pw.print(" call active="); + pw.println(mCallActive); + pw.println(" SoundTrigger Power State=" + mSoundTriggerPowerSaveMode); + pw.print(" service disabled="); + pw.println(mServiceDisabled); } } - private void initializeTelephonyAndPowerStateListeners() { + private void initializeDeviceStateListeners() { if (mRecognitionRequested) { return; } @@ -1134,8 +1138,7 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener { mContext.registerReceiver(mPowerSaveModeListener, new IntentFilter(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED)); } - mIsPowerSaveMode = mPowerManager.getPowerSaveState(ServiceType.SOUND) - .batterySaverEnabled; + mSoundTriggerPowerSaveMode = mPowerManager.getSoundTriggerPowerSaveMode(); mRecognitionRequested = true; } finally { @@ -1308,23 +1311,44 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener { return null; } - // Whether we are allowed to run any recognition at all. The conditions that let us run - // a recognition include: no active phone call or not being in a power save mode. Also, - // the native service should be enabled. - private boolean isRecognitionAllowed() { + /** + * Determines if recognition is allowed at all based on device state + * + *

Depending on the state of the SoundTrigger service, whether a call is active, or if + * battery saver mode is enabled, a specific model may or may not be able to run. The result + * of this check is not permanent, and the state of the device can change at any time. + * + * @param modelData Model data to be used for recognition + * @return True if recognition is allowed to run at this time. False if not. + */ + private boolean isRecognitionAllowedByDeviceState(ModelData modelData) { // if mRecognitionRequested is false, call and power state listeners are not registered so // we read current state directly from services if (!mRecognitionRequested) { mCallActive = mTelephonyManager.getCallState() == TelephonyManager.CALL_STATE_OFFHOOK; - mIsPowerSaveMode = - mPowerManager.getPowerSaveState(ServiceType.SOUND).batterySaverEnabled; + mSoundTriggerPowerSaveMode = mPowerManager.getSoundTriggerPowerSaveMode(); } - return !mCallActive && !mServiceDisabled && !mIsPowerSaveMode; + + return !mCallActive && !mServiceDisabled + && isRecognitionAllowedByPowerState( + modelData); + } + + /** + * Helper function to validate if a recognition should run based on the current power state + * + * @param modelData Model data to be used for recognition + * @return True if device state allows recognition to run, false if not. + */ + boolean isRecognitionAllowedByPowerState(ModelData modelData) { + return mSoundTriggerPowerSaveMode == PowerManager.SOUND_TRIGGER_MODE_ALL_ENABLED + || (mSoundTriggerPowerSaveMode == PowerManager.SOUND_TRIGGER_MODE_CRITICAL_ONLY + && modelData.shouldRunInBatterySaverMode()); } // A single routine that implements the start recognition logic for both generic and keyphrase // models. - private int startRecognitionLocked(ModelData modelData, boolean notify) { + private int startRecognitionLocked(ModelData modelData, boolean notifyClientOnError) { IRecognitionStatusCallback callback = modelData.getCallback(); RecognitionConfig config = modelData.getRecognitionConfig(); if (callback == null || !modelData.isModelLoaded() || config == null) { @@ -1334,7 +1358,7 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener { return STATUS_ERROR; } - if (!isRecognitionAllowed()) { + if (!isRecognitionAllowedByDeviceState(modelData)) { // Nothing to do here. Slog.w(TAG, "startRecognition requested but not allowed."); MetricsLogger.count(mContext, "sth_start_recognition_not_allowed", 1); @@ -1349,7 +1373,7 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener { Slog.w(TAG, "startRecognition failed with " + status); MetricsLogger.count(mContext, "sth_start_recognition_error", 1); // Notify of error if needed. - if (notify) { + if (notifyClientOnError) { try { callback.onError(status); } catch (DeadObjectException e) { @@ -1363,7 +1387,7 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener { MetricsLogger.count(mContext, "sth_start_recognition_success", 1); modelData.setStarted(); // Notify of resume if needed. - if (notify) { + if (notifyClientOnError) { try { callback.onRecognitionResumed(); } catch (DeadObjectException e) { @@ -1487,6 +1511,14 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener { // models. private int mModelHandle; + /** + * True if the service should continue listening when battery saver mode is enabled. + * Having this flag set requires the client calling + * {@link SoundTriggerModule#startRecognition(int, RecognitionConfig)} to be granted + * {@link android.Manifest.permission#SOUND_TRIGGER_RUN_IN_BATTERY_SAVER}. + */ + public boolean mRunInBatterySaverMode = false; + // The SoundModel instance, one of KeyphraseSoundModel or GenericSoundModel. private SoundModel mSoundModel = null; @@ -1562,6 +1594,14 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener { mRecognitionConfig = config; } + synchronized void setRunInBatterySaverMode(boolean runInBatterySaverMode) { + mRunInBatterySaverMode = runInBatterySaverMode; + } + + synchronized boolean shouldRunInBatterySaverMode() { + return mRunInBatterySaverMode; + } + synchronized int getHandle() { return mModelHandle; } @@ -1629,7 +1669,9 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener { "ModelState: " + stateToString() + "\n" + requestedToString() + "\n" + callbackToString() + "\n" + - uuidToString() + "\n" + modelTypeToString(); + uuidToString() + "\n" + + modelTypeToString() + + "RunInBatterySaverMode=" + mRunInBatterySaverMode; } synchronized String modelTypeToString() { diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerInternal.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerInternal.java index 7cec783fb7c05..7071e23a5ebb8 100644 --- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerInternal.java +++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerInternal.java @@ -26,7 +26,6 @@ import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel; import android.hardware.soundtrigger.SoundTrigger.ModelParamRange; import android.hardware.soundtrigger.SoundTrigger.ModuleProperties; import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig; -import android.media.permission.Identity; import android.os.IBinder; import com.android.server.voiceinteraction.VoiceInteractionManagerService; @@ -63,10 +62,13 @@ public interface SoundTriggerInternal { * @param soundModel The sound model to use for recognition. * @param listener The listener for the recognition events related to the given * keyphrase. + * @param runInBatterySaverMode flag that indicates whether the recognition should continue + * after battery saver mode is enabled. * @return One of {@link #STATUS_ERROR} or {@link #STATUS_OK}. */ int startRecognition(int keyphraseId, KeyphraseSoundModel soundModel, - IRecognitionStatusCallback listener, RecognitionConfig recognitionConfig); + IRecognitionStatusCallback listener, RecognitionConfig recognitionConfig, + boolean runInBatterySaverMode); /** * Stops recognition for the given {@link Keyphrase} if a recognition is diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java index bd678fd540634..7a53f1e159a96 100644 --- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java +++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java @@ -296,8 +296,11 @@ public class SoundTriggerService extends SystemService { @Override public int startRecognition(ParcelUuid parcelUuid, IRecognitionStatusCallback callback, - RecognitionConfig config) { + RecognitionConfig config, boolean runInBatterySaverMode) { enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER); + if (runInBatterySaverMode) { + enforceCallingPermission(Manifest.permission.SOUND_TRIGGER_RUN_IN_BATTERY_SAVER); + } if (DEBUG) { Slog.i(TAG, "startRecognition(): Uuid : " + parcelUuid); } @@ -316,7 +319,7 @@ public class SoundTriggerService extends SystemService { } int ret = mSoundTriggerHelper.startGenericRecognition(parcelUuid.getUuid(), model, - callback, config); + callback, config, runInBatterySaverMode); if (ret == STATUS_OK) { mSoundModelStatTracker.onStart(parcelUuid.getUuid()); } @@ -514,7 +517,7 @@ public class SoundTriggerService extends SystemService { switch (soundModel.getType()) { case SoundModel.TYPE_GENERIC_SOUND: ret = mSoundTriggerHelper.startGenericRecognition(soundModel.getUuid(), - (GenericSoundModel) soundModel, callback, config); + (GenericSoundModel) soundModel, callback, config, false); break; default: Slog.e(TAG, "Unknown model type"); @@ -1505,10 +1508,10 @@ public class SoundTriggerService extends SystemService { @Override public int startRecognition(int keyphraseId, KeyphraseSoundModel soundModel, - IRecognitionStatusCallback listener, RecognitionConfig recognitionConfig) { + IRecognitionStatusCallback listener, RecognitionConfig recognitionConfig, + boolean runInBatterySaverMode) { return mSoundTriggerHelper.startKeyphraseRecognition(keyphraseId, soundModel, - listener, - recognitionConfig); + listener, recognitionConfig, runInBatterySaverMode); } @Override diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java index ec371bf311609..9f203e1090bd3 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java @@ -1146,7 +1146,8 @@ public class VoiceInteractionManagerService extends SystemService { @Override public int startRecognition(int keyphraseId, String bcp47Locale, - IRecognitionStatusCallback callback, RecognitionConfig recognitionConfig) { + IRecognitionStatusCallback callback, RecognitionConfig recognitionConfig, + boolean runInBatterySaverMode) { // Allow the call if this is the current voice interaction service. synchronized (VoiceInteractionManagerServiceStub.this) { enforceIsCurrentVoiceInteractionService(); @@ -1154,6 +1155,10 @@ public class VoiceInteractionManagerService extends SystemService { if (callback == null || recognitionConfig == null || bcp47Locale == null) { throw new IllegalArgumentException("Illegal argument(s) in startRecognition"); } + if (runInBatterySaverMode) { + enforceCallingPermission( + Manifest.permission.SOUND_TRIGGER_RUN_IN_BATTERY_SAVER); + } } final int callingUserId = UserHandle.getCallingUserId(); @@ -1173,7 +1178,8 @@ public class VoiceInteractionManagerService extends SystemService { mLoadedKeyphraseIds.put(keyphraseId, this); } return mSession.startRecognition( - keyphraseId, soundModel, callback, recognitionConfig); + keyphraseId, soundModel, callback, recognitionConfig, + runInBatterySaverMode); } } finally { Binder.restoreCallingIdentity(caller);