• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /**
2  * Copyright (C) 2014 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server.soundtrigger;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.content.BroadcastReceiver;
22 import android.content.Context;
23 import android.content.Intent;
24 import android.content.IntentFilter;
25 import android.hardware.soundtrigger.IRecognitionStatusCallback;
26 import android.hardware.soundtrigger.ModelParams;
27 import android.hardware.soundtrigger.SoundTrigger;
28 import android.hardware.soundtrigger.SoundTrigger.GenericRecognitionEvent;
29 import android.hardware.soundtrigger.SoundTrigger.GenericSoundModel;
30 import android.hardware.soundtrigger.SoundTrigger.Keyphrase;
31 import android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionEvent;
32 import android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra;
33 import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel;
34 import android.hardware.soundtrigger.SoundTrigger.ModelParamRange;
35 import android.hardware.soundtrigger.SoundTrigger.ModuleProperties;
36 import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig;
37 import android.hardware.soundtrigger.SoundTrigger.RecognitionEvent;
38 import android.hardware.soundtrigger.SoundTrigger.SoundModel;
39 import android.hardware.soundtrigger.SoundTrigger.SoundModelEvent;
40 import android.hardware.soundtrigger.SoundTriggerModule;
41 import android.os.Binder;
42 import android.os.DeadObjectException;
43 import android.os.Handler;
44 import android.os.Looper;
45 import android.os.Message;
46 import android.os.PowerManager;
47 import android.os.PowerManager.SoundTriggerPowerSaveMode;
48 import android.os.RemoteException;
49 import android.telephony.PhoneStateListener;
50 import android.telephony.TelephonyManager;
51 import android.util.Slog;
52 
53 import com.android.internal.logging.MetricsLogger;
54 
55 import java.io.FileDescriptor;
56 import java.io.PrintWriter;
57 import java.util.ArrayList;
58 import java.util.HashMap;
59 import java.util.Iterator;
60 import java.util.Map;
61 import java.util.Objects;
62 import java.util.UUID;
63 
64 /**
65  * Helper for {@link SoundTrigger} APIs. Supports two types of models:
66  * (i) A voice model which is exported via the {@link VoiceInteractionService}. There can only be
67  * a single voice model running on the DSP at any given time.
68  *
69  * (ii) Generic sound-trigger models: Supports multiple of these.
70  *
71  * Currently this just acts as an abstraction over all SoundTrigger API calls.
72  * @hide
73  */
74 public class SoundTriggerHelper implements SoundTrigger.StatusListener {
75     static final String TAG = "SoundTriggerHelper";
76     static final boolean DBG = false;
77 
78     /**
79      * Return codes for {@link #startRecognition(int, KeyphraseSoundModel,
80      *      IRecognitionStatusCallback, RecognitionConfig)},
81      * {@link #stopRecognition(int, IRecognitionStatusCallback)}
82      */
83     public static final int STATUS_ERROR = SoundTrigger.STATUS_ERROR;
84     public static final int STATUS_OK = SoundTrigger.STATUS_OK;
85 
86     private static final int INVALID_VALUE = Integer.MIN_VALUE;
87 
88     /** The {@link ModuleProperties} for the system, or null if none exists. */
89     final ModuleProperties mModuleProperties;
90 
91     /** The properties for the DSP module */
92     private SoundTriggerModule mModule;
93     private final Object mLock = new Object();
94     private final Context mContext;
95     private final TelephonyManager mTelephonyManager;
96     private final PhoneStateListener mPhoneStateListener;
97     private final PowerManager mPowerManager;
98 
99     // The SoundTriggerManager layer handles multiple recognition models of type generic and
100     // keyphrase. We store the ModelData here in a hashmap.
101     private final HashMap<UUID, ModelData> mModelDataMap;
102 
103     // An index of keyphrase sound models so that we can reach them easily. We support indexing
104     // keyphrase sound models with a keyphrase ID. Sound model with the same keyphrase ID will
105     // replace an existing model, thus there is a 1:1 mapping from keyphrase ID to a voice
106     // sound model.
107     private HashMap<Integer, UUID> mKeyphraseUuidMap;
108 
109     private boolean mCallActive = false;
110     private @SoundTriggerPowerSaveMode int mSoundTriggerPowerSaveMode =
111             PowerManager.SOUND_TRIGGER_MODE_ALL_ENABLED;
112     // Indicates if the native sound trigger service is disabled or not.
113     // This is an indirect indication of the microphone being open in some other application.
114     private boolean mServiceDisabled = false;
115 
116     // Whether ANY recognition (keyphrase or generic) has been requested.
117     private boolean mRecognitionRequested = false;
118 
119     private PowerSaveModeListener mPowerSaveModeListener;
120 
121     private final SoundTriggerModuleProvider mModuleProvider;
122 
123     // Handler to process call state changes will delay to allow time for the audio
124     // and sound trigger HALs to process the end of call notifications
125     // before we re enable pending recognition requests.
126     private final Handler mHandler;
127     private static final int MSG_CALL_STATE_CHANGED = 0;
128     private static final int CALL_INACTIVE_MSG_DELAY_MS = 1000;
129 
130     /**
131      * Provider interface for retrieving SoundTriggerModule instances
132      */
133     public interface SoundTriggerModuleProvider {
134         /**
135          * Populate module properties for all available modules
136          *
137          * @param modules List of ModuleProperties to be populated
138          * @return Status int 0 on success.
139          */
listModuleProperties(@onNull ArrayList<SoundTrigger.ModuleProperties> modules)140         int listModuleProperties(@NonNull ArrayList<SoundTrigger.ModuleProperties> modules);
141 
142         /**
143          * Get SoundTriggerModule based on {@link SoundTrigger.ModuleProperties#getId()}
144          *
145          * @param moduleId Module ID
146          * @param statusListener Client listener to be associated with the returned module
147          * @return Module associated with moduleId
148          */
getModule(int moduleId, SoundTrigger.StatusListener statusListener)149         SoundTriggerModule getModule(int moduleId, SoundTrigger.StatusListener statusListener);
150     }
151 
SoundTriggerHelper(Context context, SoundTriggerModuleProvider moduleProvider)152     SoundTriggerHelper(Context context, SoundTriggerModuleProvider moduleProvider) {
153         ArrayList <ModuleProperties> modules = new ArrayList<>();
154         mModuleProvider = moduleProvider;
155         int status = mModuleProvider.listModuleProperties(modules);
156         mContext = context;
157         mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
158         mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
159         mModelDataMap = new HashMap<UUID, ModelData>();
160         mKeyphraseUuidMap = new HashMap<Integer, UUID>();
161         if (status != SoundTrigger.STATUS_OK || modules.size() == 0) {
162             Slog.w(TAG, "listModules status=" + status + ", # of modules=" + modules.size());
163             mModuleProperties = null;
164             mModule = null;
165         } else {
166             // TODO: Figure out how to determine which module corresponds to the DSP hardware.
167             mModuleProperties = modules.get(0);
168         }
169 
170         Looper looper = Looper.myLooper();
171         if (looper == null) {
172             looper = Looper.getMainLooper();
173         }
174         mPhoneStateListener = new MyCallStateListener(looper);
175         if (looper != null) {
176             mHandler = new Handler(looper) {
177                 @Override
178                 public void handleMessage(Message msg) {
179                     switch (msg.what) {
180                         case MSG_CALL_STATE_CHANGED:
181                             synchronized (mLock) {
182                                 onCallStateChangedLocked(
183                                         TelephonyManager.CALL_STATE_OFFHOOK == msg.arg1);
184                             }
185                             break;
186                         default:
187                             Slog.e(TAG, "unknown message in handler:" + msg.what);
188                             break;
189                     }
190                 }
191             };
192         } else {
193             mHandler = null;
194         }
195     }
196 
197     /**
198      * Starts recognition for the given generic sound model ID. This is a wrapper around {@link
199      * startRecognition()}.
200      *
201      * @param modelId UUID of the sound model.
202      * @param soundModel The generic sound model to use for recognition.
203      * @param callback Callack for the recognition events related to the given keyphrase.
204      * @param recognitionConfig Instance of RecognitionConfig containing the parameters for the
205      * recognition.
206      * @return One of {@link #STATUS_ERROR} or {@link #STATUS_OK}.
207      */
startGenericRecognition(UUID modelId, GenericSoundModel soundModel, IRecognitionStatusCallback callback, RecognitionConfig recognitionConfig, boolean runInBatterySaverMode)208     int startGenericRecognition(UUID modelId, GenericSoundModel soundModel,
209             IRecognitionStatusCallback callback, RecognitionConfig recognitionConfig,
210             boolean runInBatterySaverMode) {
211         MetricsLogger.count(mContext, "sth_start_recognition", 1);
212         if (modelId == null || soundModel == null || callback == null ||
213                 recognitionConfig == null) {
214             Slog.w(TAG, "Passed in bad data to startGenericRecognition().");
215             return STATUS_ERROR;
216         }
217 
218         synchronized (mLock) {
219             ModelData modelData = getOrCreateGenericModelDataLocked(modelId);
220             if (modelData == null) {
221                 Slog.w(TAG, "Irrecoverable error occurred, check UUID / sound model data.");
222                 return STATUS_ERROR;
223             }
224             return startRecognition(soundModel, modelData, callback, recognitionConfig,
225                     INVALID_VALUE /* keyphraseId */, runInBatterySaverMode);
226         }
227     }
228 
229     /**
230      * Starts recognition for the given keyphraseId.
231      *
232      * @param keyphraseId The identifier of the keyphrase for which
233      *        the recognition is to be started.
234      * @param soundModel The sound model to use for recognition.
235      * @param callback The callback for the recognition events related to the given keyphrase.
236      * @return One of {@link #STATUS_ERROR} or {@link #STATUS_OK}.
237      */
startKeyphraseRecognition(int keyphraseId, KeyphraseSoundModel soundModel, IRecognitionStatusCallback callback, RecognitionConfig recognitionConfig, boolean runInBatterySaverMode)238     int startKeyphraseRecognition(int keyphraseId, KeyphraseSoundModel soundModel,
239             IRecognitionStatusCallback callback, RecognitionConfig recognitionConfig,
240             boolean runInBatterySaverMode) {
241         synchronized (mLock) {
242             MetricsLogger.count(mContext, "sth_start_recognition", 1);
243             if (soundModel == null || callback == null || recognitionConfig == null) {
244                 return STATUS_ERROR;
245             }
246 
247             if (DBG) {
248                 Slog.d(TAG, "startKeyphraseRecognition for keyphraseId=" + keyphraseId
249                         + " soundModel=" + soundModel + ", callback=" + callback.asBinder()
250                         + ", recognitionConfig=" + recognitionConfig
251                         + ", runInBatterySaverMode=" + runInBatterySaverMode);
252                 Slog.d(TAG, "moduleProperties=" + mModuleProperties);
253                 dumpModelStateLocked();
254             }
255 
256             ModelData model = getKeyphraseModelDataLocked(keyphraseId);
257             if (model != null && !model.isKeyphraseModel()) {
258                 Slog.e(TAG, "Generic model with same UUID exists.");
259                 return STATUS_ERROR;
260             }
261 
262             // Process existing model first.
263             if (model != null && !model.getModelId().equals(soundModel.getUuid())) {
264                 // The existing model has a different UUID, should be replaced.
265                 int status = cleanUpExistingKeyphraseModelLocked(model);
266                 if (status != STATUS_OK) {
267                     return status;
268                 }
269                 removeKeyphraseModelLocked(keyphraseId);
270                 model = null;
271             }
272 
273             // We need to create a new one: either no previous models existed for given keyphrase id
274             // or the existing model had a different UUID and was cleaned up.
275             if (model == null) {
276                 model = createKeyphraseModelDataLocked(soundModel.getUuid(), keyphraseId);
277             }
278 
279             return startRecognition(soundModel, model, callback, recognitionConfig,
280                     keyphraseId, runInBatterySaverMode);
281         }
282     }
283 
cleanUpExistingKeyphraseModelLocked(ModelData modelData)284     private int cleanUpExistingKeyphraseModelLocked(ModelData modelData) {
285         // Stop and clean up a previous ModelData if one exists. This usually is used when the
286         // previous model has a different UUID for the same keyphrase ID.
287         int status = tryStopAndUnloadLocked(modelData, true /* stop */, true /* unload */);
288         if (status != STATUS_OK) {
289             Slog.w(TAG, "Unable to stop or unload previous model: " +
290                     modelData.toString());
291         }
292         return status;
293     }
294 
prepareForRecognition(ModelData modelData)295     private int prepareForRecognition(ModelData modelData) {
296         if (mModule == null) {
297             mModule = mModuleProvider.getModule(mModuleProperties.getId(), this);
298             if (mModule == null) {
299                 Slog.w(TAG, "prepareForRecognition: cannot attach to sound trigger module");
300                 return STATUS_ERROR;
301             }
302         }
303         // Load the model if it is not loaded.
304         if (!modelData.isModelLoaded()) {
305             // Before we try and load this model, we should first make sure that any other
306             // models that don't have an active recognition/dead callback are unloaded. Since
307             // there is a finite limit on the number of models that the hardware may be able to
308             // have loaded, we want to make sure there's room for our model.
309             stopAndUnloadDeadModelsLocked();
310             int[] handle = new int[] { 0 };
311             int status = mModule.loadSoundModel(modelData.getSoundModel(), handle);
312             if (status != SoundTrigger.STATUS_OK) {
313                 Slog.w(TAG, "prepareForRecognition: loadSoundModel failed with status: " + status);
314                 return status;
315             }
316             modelData.setHandle(handle[0]);
317             modelData.setLoaded();
318             if (DBG) {
319                 Slog.d(TAG, "prepareForRecognition: Sound model loaded with handle:" + handle[0]);
320             }
321         }
322         return STATUS_OK;
323     }
324 
325 
326     /**
327      * Starts recognition for the given sound model. A single routine for both keyphrase and
328      * generic sound models.
329      *
330      * @param soundModel The sound model to use for recognition.
331      * @param modelData Instance of {@link #ModelData} for the given model.
332      * @param callback Callback for the recognition events related to the given keyphrase.
333      * @param recognitionConfig Instance of {@link RecognitionConfig} containing the parameters
334      * @param keyphraseId Keyphrase ID for keyphrase models only. Pass in INVALID_VALUE for other
335      * models.
336      * for the recognition.
337      * @return One of {@link #STATUS_ERROR} or {@link #STATUS_OK}.
338      */
startRecognition(SoundModel soundModel, ModelData modelData, IRecognitionStatusCallback callback, RecognitionConfig recognitionConfig, int keyphraseId, boolean runInBatterySaverMode)339     int startRecognition(SoundModel soundModel, ModelData modelData,
340             IRecognitionStatusCallback callback, RecognitionConfig recognitionConfig,
341             int keyphraseId, boolean runInBatterySaverMode) {
342         synchronized (mLock) {
343             if (mModuleProperties == null) {
344                 Slog.w(TAG, "Attempting startRecognition without the capability");
345                 return STATUS_ERROR;
346             }
347 
348             IRecognitionStatusCallback oldCallback = modelData.getCallback();
349             if (oldCallback != null && oldCallback.asBinder() != callback.asBinder()) {
350                 Slog.w(TAG, "Canceling previous recognition for model id: "
351                         + modelData.getModelId());
352                 try {
353                     oldCallback.onError(STATUS_ERROR);
354                 } catch (RemoteException e) {
355                     Slog.w(TAG, "RemoteException in onDetectionStopped", e);
356                 }
357                 modelData.clearCallback();
358             }
359 
360             // If the existing SoundModel is different (for the same UUID for Generic and same
361             // keyphrase ID for voice), ensure that it is unloaded and stopped before proceeding.
362             // This works for both keyphrase and generic models. This logic also ensures that a
363             // previously loaded (or started) model is appropriately stopped. Since this is a
364             // generalization of the previous logic with a single keyphrase model, we should have
365             // no regression with the previous version of this code as was given in the
366             // startKeyphrase() routine.
367             if (modelData.getSoundModel() != null) {
368                 boolean stopModel = false; // Stop the model after checking that it is started.
369                 boolean unloadModel = false;
370                 if (modelData.getSoundModel().equals(soundModel) && modelData.isModelStarted()) {
371                     // The model has not changed, but the previous model is "started".
372                     // Stop the previously running model.
373                     stopModel = true;
374                     unloadModel = false; // No need to unload if the model hasn't changed.
375                 } else if (!modelData.getSoundModel().equals(soundModel)) {
376                     // We have a different model for this UUID. Stop and unload if needed. This
377                     // helps maintain the singleton restriction for keyphrase sound models.
378                     stopModel = modelData.isModelStarted();
379                     unloadModel = modelData.isModelLoaded();
380                 }
381                 if (stopModel || unloadModel) {
382                     int status = tryStopAndUnloadLocked(modelData, stopModel, unloadModel);
383                     if (status != STATUS_OK) {
384                         Slog.w(TAG, "Unable to stop or unload previous model: " +
385                                 modelData.toString());
386                         return status;
387                     }
388                 }
389             }
390 
391             modelData.setCallback(callback);
392             modelData.setRequested(true);
393             modelData.setRecognitionConfig(recognitionConfig);
394             modelData.setRunInBatterySaverMode(runInBatterySaverMode);
395             modelData.setSoundModel(soundModel);
396 
397             if (!isRecognitionAllowedByDeviceState(modelData)) {
398                 initializeDeviceStateListeners();
399                 return STATUS_OK;
400             }
401 
402             int status = prepareForRecognition(modelData);
403             if (status != STATUS_OK) {
404                 Slog.w(TAG, "startRecognition failed to prepare model for recognition");
405                 return status;
406             }
407             status = startRecognitionLocked(modelData,
408                     false /* Don't notify for synchronous calls */);
409 
410             // Initialize power save, call active state monitoring logic.
411             if (status == STATUS_OK) {
412                 initializeDeviceStateListeners();
413             }
414 
415             return status;
416         }
417     }
418 
419     /**
420      * Stops recognition for the given generic sound model. This is a wrapper for {@link
421      * #stopRecognition}.
422      *
423      * @param modelId The identifier of the generic sound model for which
424      *        the recognition is to be stopped.
425      * @param callback The callback for the recognition events related to the given sound model.
426      *
427      * @return One of {@link #STATUS_ERROR} or {@link #STATUS_OK}.
428      */
stopGenericRecognition(UUID modelId, IRecognitionStatusCallback callback)429     int stopGenericRecognition(UUID modelId, IRecognitionStatusCallback callback) {
430         synchronized (mLock) {
431             MetricsLogger.count(mContext, "sth_stop_recognition", 1);
432             if (callback == null || modelId == null) {
433                 Slog.e(TAG, "Null callbackreceived for stopGenericRecognition() for modelid:" +
434                         modelId);
435                 return STATUS_ERROR;
436             }
437 
438             ModelData modelData = mModelDataMap.get(modelId);
439             if (modelData == null || !modelData.isGenericModel()) {
440                 Slog.w(TAG, "Attempting stopRecognition on invalid model with id:" + modelId);
441                 return STATUS_ERROR;
442             }
443 
444             int status = stopRecognition(modelData, callback);
445             if (status != SoundTrigger.STATUS_OK) {
446                 Slog.w(TAG, "stopGenericRecognition failed: " + status);
447             }
448             return status;
449         }
450     }
451 
452     /**
453      * Stops recognition for the given {@link Keyphrase} if a recognition is
454      * currently active. This is a wrapper for {@link #stopRecognition()}.
455      *
456      * @param keyphraseId The identifier of the keyphrase for which
457      *        the recognition is to be stopped.
458      * @param callback The callback for the recognition events related to the given keyphrase.
459      *
460      * @return One of {@link #STATUS_ERROR} or {@link #STATUS_OK}.
461      */
stopKeyphraseRecognition(int keyphraseId, IRecognitionStatusCallback callback)462     int stopKeyphraseRecognition(int keyphraseId, IRecognitionStatusCallback callback) {
463         synchronized (mLock) {
464             MetricsLogger.count(mContext, "sth_stop_recognition", 1);
465             if (callback == null) {
466                 Slog.e(TAG, "Null callback received for stopKeyphraseRecognition() for keyphraseId:" +
467                         keyphraseId);
468                 return STATUS_ERROR;
469             }
470 
471             ModelData modelData = getKeyphraseModelDataLocked(keyphraseId);
472             if (modelData == null || !modelData.isKeyphraseModel()) {
473                 Slog.w(TAG, "No model exists for given keyphrase Id " + keyphraseId);
474                 return STATUS_ERROR;
475             }
476 
477             if (DBG) {
478                 Slog.d(TAG, "stopRecognition for keyphraseId=" + keyphraseId + ", callback =" +
479                         callback.asBinder());
480                 Slog.d(TAG, "current callback="
481                         + ((modelData == null || modelData.getCallback() == null) ? "null" :
482                             modelData.getCallback().asBinder()));
483             }
484             int status = stopRecognition(modelData, callback);
485             if (status != SoundTrigger.STATUS_OK) {
486                 return status;
487             }
488 
489             return status;
490         }
491     }
492 
493     /**
494      * Stops recognition for the given ModelData instance.
495      *
496      * @param modelData Instance of {@link #ModelData} sound model.
497      * @param callback The callback for the recognition events related to the given keyphrase.
498      * @return One of {@link #STATUS_ERROR} or {@link #STATUS_OK}.
499      */
stopRecognition(ModelData modelData, IRecognitionStatusCallback callback)500     private int stopRecognition(ModelData modelData, IRecognitionStatusCallback callback) {
501         synchronized (mLock) {
502             if (callback == null) {
503                 return STATUS_ERROR;
504             }
505             if (mModuleProperties == null || mModule == null) {
506                 Slog.w(TAG, "Attempting stopRecognition without the capability");
507                 return STATUS_ERROR;
508             }
509 
510             IRecognitionStatusCallback currentCallback = modelData.getCallback();
511             if (modelData == null || currentCallback == null ||
512                     (!modelData.isRequested() && !modelData.isModelStarted())) {
513                 // startGenericRecognition hasn't been called or it failed.
514                 Slog.w(TAG, "Attempting stopRecognition without a successful startRecognition");
515                 return STATUS_ERROR;
516             }
517 
518             if (currentCallback.asBinder() != callback.asBinder()) {
519                 // We don't allow a different listener to stop the recognition than the one
520                 // that started it.
521                 Slog.w(TAG, "Attempting stopRecognition for another recognition");
522                 return STATUS_ERROR;
523             }
524 
525             // Request stop recognition via the update() method.
526             modelData.setRequested(false);
527             int status = updateRecognitionLocked(modelData, false);
528             if (status != SoundTrigger.STATUS_OK) {
529                 return status;
530             }
531 
532             // We leave the sound model loaded but not started, this helps us when we start back.
533             // Also clear the internal state once the recognition has been stopped.
534             modelData.setLoaded();
535             modelData.clearCallback();
536             modelData.setRecognitionConfig(null);
537 
538             if (!computeRecognitionRequestedLocked()) {
539                 internalClearGlobalStateLocked();
540             }
541 
542             return status;
543         }
544     }
545 
546     // Stop a previously started model if it was started. Optionally, unload if the previous model
547     // is stale and is about to be replaced.
548     // Needs to be called with the mLock held.
tryStopAndUnloadLocked(ModelData modelData, boolean stopModel, boolean unloadModel)549     private int tryStopAndUnloadLocked(ModelData modelData, boolean stopModel,
550             boolean unloadModel) {
551         int status = STATUS_OK;
552         if (modelData.isModelNotLoaded()) {
553             return status;
554         }
555         if (stopModel && modelData.isModelStarted()) {
556             status = stopRecognitionLocked(modelData,
557                     false /* don't notify for synchronous calls */);
558             if (status != SoundTrigger.STATUS_OK) {
559                 Slog.w(TAG, "stopRecognition failed: " + status);
560                 return status;
561             }
562         }
563 
564         if (unloadModel && modelData.isModelLoaded()) {
565             Slog.d(TAG, "Unloading previously loaded stale model.");
566             if (mModule == null) {
567                 return STATUS_ERROR;
568             }
569             status = mModule.unloadSoundModel(modelData.getHandle());
570             MetricsLogger.count(mContext, "sth_unloading_stale_model", 1);
571             if (status != SoundTrigger.STATUS_OK) {
572                 Slog.w(TAG, "unloadSoundModel call failed with " + status);
573             } else {
574                 // Clear the ModelData state if successful.
575                 modelData.clearState();
576             }
577         }
578         return status;
579     }
580 
getModuleProperties()581     public ModuleProperties getModuleProperties() {
582         return mModuleProperties;
583     }
584 
unloadKeyphraseSoundModel(int keyphraseId)585     int unloadKeyphraseSoundModel(int keyphraseId) {
586         synchronized (mLock) {
587             MetricsLogger.count(mContext, "sth_unload_keyphrase_sound_model", 1);
588             ModelData modelData = getKeyphraseModelDataLocked(keyphraseId);
589             if (mModule == null || modelData == null || !modelData.isModelLoaded()
590                     || !modelData.isKeyphraseModel()) {
591                 return STATUS_ERROR;
592             }
593 
594             // Stop recognition if it's the current one.
595             modelData.setRequested(false);
596             int status = updateRecognitionLocked(modelData, false);
597             if (status != SoundTrigger.STATUS_OK) {
598                 Slog.w(TAG, "Stop recognition failed for keyphrase ID:" + status);
599             }
600 
601             status = mModule.unloadSoundModel(modelData.getHandle());
602             if (status != SoundTrigger.STATUS_OK) {
603                 Slog.w(TAG, "unloadKeyphraseSoundModel call failed with " + status);
604             }
605 
606             // Remove it from existence.
607             removeKeyphraseModelLocked(keyphraseId);
608             return status;
609         }
610     }
611 
unloadGenericSoundModel(UUID modelId)612     int unloadGenericSoundModel(UUID modelId) {
613         synchronized (mLock) {
614             MetricsLogger.count(mContext, "sth_unload_generic_sound_model", 1);
615             if (modelId == null || mModule == null) {
616                 return STATUS_ERROR;
617             }
618             ModelData modelData = mModelDataMap.get(modelId);
619             if (modelData == null || !modelData.isGenericModel()) {
620                 Slog.w(TAG, "Unload error: Attempting unload invalid generic model with id:" +
621                         modelId);
622                 return STATUS_ERROR;
623             }
624             if (!modelData.isModelLoaded()) {
625                 // Nothing to do here.
626                 Slog.i(TAG, "Unload: Given generic model is not loaded:" + modelId);
627                 return STATUS_OK;
628             }
629             if (modelData.isModelStarted()) {
630                 int status = stopRecognitionLocked(modelData,
631                         false /* don't notify for synchronous calls */);
632                 if (status != SoundTrigger.STATUS_OK) {
633                     Slog.w(TAG, "stopGenericRecognition failed: " + status);
634                 }
635             }
636 
637             if (mModule == null) {
638                 return STATUS_ERROR;
639             }
640             int status = mModule.unloadSoundModel(modelData.getHandle());
641             if (status != SoundTrigger.STATUS_OK) {
642                 Slog.w(TAG, "unloadGenericSoundModel() call failed with " + status);
643                 Slog.w(TAG, "unloadGenericSoundModel() force-marking model as unloaded.");
644             }
645 
646             // Remove it from existence.
647             mModelDataMap.remove(modelId);
648             if (DBG) dumpModelStateLocked();
649             return status;
650         }
651     }
652 
isRecognitionRequested(UUID modelId)653     boolean isRecognitionRequested(UUID modelId) {
654         synchronized (mLock) {
655             ModelData modelData = mModelDataMap.get(modelId);
656             return modelData != null && modelData.isRequested();
657         }
658     }
659 
getGenericModelState(UUID modelId)660     int getGenericModelState(UUID modelId) {
661         synchronized (mLock) {
662             MetricsLogger.count(mContext, "sth_get_generic_model_state", 1);
663             if (modelId == null || mModule == null) {
664                 return STATUS_ERROR;
665             }
666             ModelData modelData = mModelDataMap.get(modelId);
667             if (modelData == null || !modelData.isGenericModel()) {
668                 Slog.w(TAG, "GetGenericModelState error: Invalid generic model id:" +
669                         modelId);
670                 return STATUS_ERROR;
671             }
672             if (!modelData.isModelLoaded()) {
673                 Slog.i(TAG, "GetGenericModelState: Given generic model is not loaded:" + modelId);
674                 return STATUS_ERROR;
675             }
676             if (!modelData.isModelStarted()) {
677                 Slog.i(TAG, "GetGenericModelState: Given generic model is not started:" + modelId);
678                 return STATUS_ERROR;
679             }
680 
681             return mModule.getModelState(modelData.getHandle());
682         }
683     }
684 
getKeyphraseModelState(UUID modelId)685     int getKeyphraseModelState(UUID modelId) {
686         Slog.w(TAG, "GetKeyphraseModelState error: Not implemented");
687         return STATUS_ERROR;
688     }
689 
setParameter(UUID modelId, @ModelParams int modelParam, int value)690     int setParameter(UUID modelId, @ModelParams int modelParam, int value) {
691         synchronized (mLock) {
692             return setParameterLocked(mModelDataMap.get(modelId), modelParam, value);
693         }
694     }
695 
setKeyphraseParameter(int keyphraseId, @ModelParams int modelParam, int value)696     int setKeyphraseParameter(int keyphraseId, @ModelParams int modelParam, int value) {
697         synchronized (mLock) {
698             return setParameterLocked(getKeyphraseModelDataLocked(keyphraseId), modelParam, value);
699         }
700     }
701 
setParameterLocked(@ullable ModelData modelData, @ModelParams int modelParam, int value)702     private int setParameterLocked(@Nullable ModelData modelData, @ModelParams int modelParam,
703             int value) {
704         MetricsLogger.count(mContext, "sth_set_parameter", 1);
705         if (mModule == null) {
706             return SoundTrigger.STATUS_NO_INIT;
707         }
708         if (modelData == null || !modelData.isModelLoaded()) {
709             Slog.i(TAG, "SetParameter: Given model is not loaded:" + modelData);
710             return SoundTrigger.STATUS_BAD_VALUE;
711         }
712 
713         return mModule.setParameter(modelData.getHandle(), modelParam, value);
714     }
715 
getParameter(@onNull UUID modelId, @ModelParams int modelParam)716     int getParameter(@NonNull UUID modelId, @ModelParams int modelParam) {
717         synchronized (mLock) {
718             return getParameterLocked(mModelDataMap.get(modelId), modelParam);
719         }
720     }
721 
getKeyphraseParameter(int keyphraseId, @ModelParams int modelParam)722     int getKeyphraseParameter(int keyphraseId, @ModelParams int modelParam) {
723         synchronized (mLock) {
724             return getParameterLocked(getKeyphraseModelDataLocked(keyphraseId), modelParam);
725         }
726     }
727 
getParameterLocked(@ullable ModelData modelData, @ModelParams int modelParam)728     private int getParameterLocked(@Nullable ModelData modelData, @ModelParams int modelParam) {
729         MetricsLogger.count(mContext, "sth_get_parameter", 1);
730         if (mModule == null) {
731             throw new UnsupportedOperationException("SoundTriggerModule not initialized");
732         }
733 
734         if (modelData == null) {
735             throw new IllegalArgumentException("Invalid model id");
736         }
737         if (!modelData.isModelLoaded()) {
738             throw new UnsupportedOperationException("Given model is not loaded:" + modelData);
739         }
740 
741         return mModule.getParameter(modelData.getHandle(), modelParam);
742     }
743 
744     @Nullable
queryParameter(@onNull UUID modelId, @ModelParams int modelParam)745     ModelParamRange queryParameter(@NonNull UUID modelId, @ModelParams int modelParam) {
746         synchronized (mLock) {
747             return queryParameterLocked(mModelDataMap.get(modelId), modelParam);
748         }
749     }
750 
751     @Nullable
queryKeyphraseParameter(int keyphraseId, @ModelParams int modelParam)752     ModelParamRange queryKeyphraseParameter(int keyphraseId, @ModelParams int modelParam) {
753         synchronized (mLock) {
754             return queryParameterLocked(getKeyphraseModelDataLocked(keyphraseId), modelParam);
755         }
756     }
757 
758     @Nullable
queryParameterLocked(@ullable ModelData modelData, @ModelParams int modelParam)759     private ModelParamRange queryParameterLocked(@Nullable ModelData modelData,
760             @ModelParams int modelParam) {
761         MetricsLogger.count(mContext, "sth_query_parameter", 1);
762         if (mModule == null) {
763             return null;
764         }
765         if (modelData == null) {
766             Slog.w(TAG, "queryParameter: Invalid model id");
767             return null;
768         }
769         if (!modelData.isModelLoaded()) {
770             Slog.i(TAG, "queryParameter: Given model is not loaded:" + modelData);
771             return null;
772         }
773 
774         return mModule.queryParameter(modelData.getHandle(), modelParam);
775     }
776 
777     //---- SoundTrigger.StatusListener methods
778     @Override
onRecognition(RecognitionEvent event)779     public void onRecognition(RecognitionEvent event) {
780         if (event == null) {
781             Slog.w(TAG, "Null recognition event!");
782             return;
783         }
784 
785         if (!(event instanceof KeyphraseRecognitionEvent) &&
786                 !(event instanceof GenericRecognitionEvent)) {
787             Slog.w(TAG, "Invalid recognition event type (not one of generic or keyphrase)!");
788             return;
789         }
790 
791         if (DBG) Slog.d(TAG, "onRecognition: " + event);
792         synchronized (mLock) {
793             switch (event.status) {
794                 case SoundTrigger.RECOGNITION_STATUS_ABORT:
795                     onRecognitionAbortLocked(event);
796                     break;
797                 case SoundTrigger.RECOGNITION_STATUS_FAILURE:
798                     // Fire failures to all listeners since it's not tied to a keyphrase.
799                     onRecognitionFailureLocked();
800                     break;
801                 case SoundTrigger.RECOGNITION_STATUS_SUCCESS:
802                 case SoundTrigger.RECOGNITION_STATUS_GET_STATE_RESPONSE:
803                     if (isKeyphraseRecognitionEvent(event)) {
804                         onKeyphraseRecognitionSuccessLocked((KeyphraseRecognitionEvent) event);
805                     } else {
806                         onGenericRecognitionSuccessLocked((GenericRecognitionEvent) event);
807                     }
808                     break;
809             }
810         }
811     }
812 
isKeyphraseRecognitionEvent(RecognitionEvent event)813     private boolean isKeyphraseRecognitionEvent(RecognitionEvent event) {
814         return event instanceof KeyphraseRecognitionEvent;
815     }
816 
onGenericRecognitionSuccessLocked(GenericRecognitionEvent event)817     private void onGenericRecognitionSuccessLocked(GenericRecognitionEvent event) {
818         MetricsLogger.count(mContext, "sth_generic_recognition_event", 1);
819         if (event.status != SoundTrigger.RECOGNITION_STATUS_SUCCESS
820                 && event.status != SoundTrigger.RECOGNITION_STATUS_GET_STATE_RESPONSE) {
821             return;
822         }
823         ModelData model = getModelDataForLocked(event.soundModelHandle);
824         if (model == null || !model.isGenericModel()) {
825             Slog.w(TAG, "Generic recognition event: Model does not exist for handle: "
826                     + event.soundModelHandle);
827             return;
828         }
829 
830         IRecognitionStatusCallback callback = model.getCallback();
831         if (callback == null) {
832             Slog.w(TAG, "Generic recognition event: Null callback for model handle: "
833                     + event.soundModelHandle);
834             return;
835         }
836 
837         if (event.status != SoundTrigger.RECOGNITION_STATUS_GET_STATE_RESPONSE) {
838             model.setStopped();
839         }
840 
841         try {
842             callback.onGenericSoundTriggerDetected((GenericRecognitionEvent) event);
843         } catch (DeadObjectException e) {
844             forceStopAndUnloadModelLocked(model, e);
845             return;
846         } catch (RemoteException e) {
847             Slog.w(TAG, "RemoteException in onGenericSoundTriggerDetected", e);
848         }
849 
850         RecognitionConfig config = model.getRecognitionConfig();
851         if (config == null) {
852             Slog.w(TAG, "Generic recognition event: Null RecognitionConfig for model handle: "
853                     + event.soundModelHandle);
854             return;
855         }
856 
857         model.setRequested(config.allowMultipleTriggers);
858         // TODO: Remove this block if the lower layer supports multiple triggers.
859         if (model.isRequested()) {
860             updateRecognitionLocked(model, true);
861         }
862     }
863 
864     @Override
onSoundModelUpdate(SoundModelEvent event)865     public void onSoundModelUpdate(SoundModelEvent event) {
866         if (event == null) {
867             Slog.w(TAG, "Invalid sound model event!");
868             return;
869         }
870         if (DBG) Slog.d(TAG, "onSoundModelUpdate: " + event);
871         synchronized (mLock) {
872             MetricsLogger.count(mContext, "sth_sound_model_updated", 1);
873             onSoundModelUpdatedLocked(event);
874         }
875     }
876 
877     @Override
onServiceStateChange(int state)878     public void onServiceStateChange(int state) {
879         if (DBG) Slog.d(TAG, "onServiceStateChange, state: " + state);
880         synchronized (mLock) {
881             onServiceStateChangedLocked(SoundTrigger.SERVICE_STATE_DISABLED == state);
882         }
883     }
884 
885     @Override
onServiceDied()886     public void onServiceDied() {
887         Slog.e(TAG, "onServiceDied!!");
888         MetricsLogger.count(mContext, "sth_service_died", 1);
889         synchronized (mLock) {
890             onServiceDiedLocked();
891         }
892     }
893 
onCallStateChangedLocked(boolean callActive)894     private void onCallStateChangedLocked(boolean callActive) {
895         if (mCallActive == callActive) {
896             // We consider multiple call states as being active
897             // so we check if something really changed or not here.
898             return;
899         }
900         mCallActive = callActive;
901         updateAllRecognitionsLocked();
902     }
903 
onPowerSaveModeChangedLocked( @oundTriggerPowerSaveMode int soundTriggerPowerSaveMode)904     private void onPowerSaveModeChangedLocked(
905             @SoundTriggerPowerSaveMode int soundTriggerPowerSaveMode) {
906         if (mSoundTriggerPowerSaveMode == soundTriggerPowerSaveMode) {
907             return;
908         }
909         mSoundTriggerPowerSaveMode = soundTriggerPowerSaveMode;
910         updateAllRecognitionsLocked();
911     }
912 
onSoundModelUpdatedLocked(SoundModelEvent event)913     private void onSoundModelUpdatedLocked(SoundModelEvent event) {
914         // TODO: Handle sound model update here.
915     }
916 
onServiceStateChangedLocked(boolean disabled)917     private void onServiceStateChangedLocked(boolean disabled) {
918         if (disabled == mServiceDisabled) {
919             return;
920         }
921         mServiceDisabled = disabled;
922         updateAllRecognitionsLocked();
923     }
924 
onRecognitionAbortLocked(RecognitionEvent event)925     private void onRecognitionAbortLocked(RecognitionEvent event) {
926         Slog.w(TAG, "Recognition aborted");
927         MetricsLogger.count(mContext, "sth_recognition_aborted", 1);
928         ModelData modelData = getModelDataForLocked(event.soundModelHandle);
929         if (modelData != null && modelData.isModelStarted()) {
930             modelData.setStopped();
931             try {
932                 modelData.getCallback().onRecognitionPaused();
933             } catch (DeadObjectException e) {
934                 forceStopAndUnloadModelLocked(modelData, e);
935             } catch (RemoteException e) {
936                 Slog.w(TAG, "RemoteException in onRecognitionPaused", e);
937             }
938         }
939     }
940 
onRecognitionFailureLocked()941     private void onRecognitionFailureLocked() {
942         Slog.w(TAG, "Recognition failure");
943         MetricsLogger.count(mContext, "sth_recognition_failure_event", 1);
944         try {
945             sendErrorCallbacksToAllLocked(STATUS_ERROR);
946         } finally {
947             internalClearModelStateLocked();
948             internalClearGlobalStateLocked();
949         }
950     }
951 
getKeyphraseIdFromEvent(KeyphraseRecognitionEvent event)952     private int getKeyphraseIdFromEvent(KeyphraseRecognitionEvent event) {
953         if (event == null) {
954             Slog.w(TAG, "Null RecognitionEvent received.");
955             return INVALID_VALUE;
956         }
957         KeyphraseRecognitionExtra[] keyphraseExtras =
958                 ((KeyphraseRecognitionEvent) event).keyphraseExtras;
959         if (keyphraseExtras == null || keyphraseExtras.length == 0) {
960             Slog.w(TAG, "Invalid keyphrase recognition event!");
961             return INVALID_VALUE;
962         }
963         // TODO: Handle more than one keyphrase extras.
964         return keyphraseExtras[0].id;
965     }
966 
onKeyphraseRecognitionSuccessLocked(KeyphraseRecognitionEvent event)967     private void onKeyphraseRecognitionSuccessLocked(KeyphraseRecognitionEvent event) {
968         Slog.i(TAG, "Recognition success");
969         MetricsLogger.count(mContext, "sth_keyphrase_recognition_event", 1);
970         int keyphraseId = getKeyphraseIdFromEvent(event);
971         ModelData modelData = getKeyphraseModelDataLocked(keyphraseId);
972 
973         if (modelData == null || !modelData.isKeyphraseModel()) {
974             Slog.e(TAG, "Keyphase model data does not exist for ID:" + keyphraseId);
975             return;
976         }
977 
978         if (modelData.getCallback() == null) {
979             Slog.w(TAG, "Received onRecognition event without callback for keyphrase model.");
980             return;
981         }
982 
983         if (event.status != SoundTrigger.RECOGNITION_STATUS_GET_STATE_RESPONSE) {
984             modelData.setStopped();
985         }
986 
987         try {
988             modelData.getCallback().onKeyphraseDetected((KeyphraseRecognitionEvent) event);
989         } catch (DeadObjectException e) {
990             forceStopAndUnloadModelLocked(modelData, e);
991             return;
992         } catch (RemoteException e) {
993             Slog.w(TAG, "RemoteException in onKeyphraseDetected", e);
994         }
995 
996         RecognitionConfig config = modelData.getRecognitionConfig();
997         if (config != null) {
998             // Whether we should continue by starting this again.
999             modelData.setRequested(config.allowMultipleTriggers);
1000         }
1001         // TODO: Remove this block if the lower layer supports multiple triggers.
1002         if (modelData.isRequested()) {
1003             updateRecognitionLocked(modelData, true);
1004         }
1005     }
1006 
updateAllRecognitionsLocked()1007     private void updateAllRecognitionsLocked() {
1008         // updateRecognitionLocked can possibly update the list of models
1009         ArrayList<ModelData> modelDatas = new ArrayList<ModelData>(mModelDataMap.values());
1010         for (ModelData modelData : modelDatas) {
1011             updateRecognitionLocked(modelData, true);
1012         }
1013     }
1014 
updateRecognitionLocked(ModelData model, boolean notifyClientOnError)1015     private int updateRecognitionLocked(ModelData model, boolean notifyClientOnError) {
1016         boolean shouldStartModel = model.isRequested() && isRecognitionAllowedByDeviceState(model);
1017         if (shouldStartModel == model.isModelStarted()) {
1018             // No-op.
1019             return STATUS_OK;
1020         }
1021         if (shouldStartModel) {
1022             int status = prepareForRecognition(model);
1023             if (status != STATUS_OK) {
1024                 return status;
1025             }
1026             return startRecognitionLocked(model, notifyClientOnError);
1027         } else {
1028             return stopRecognitionLocked(model, notifyClientOnError);
1029         }
1030     }
1031 
onServiceDiedLocked()1032     private void onServiceDiedLocked() {
1033         try {
1034             MetricsLogger.count(mContext, "sth_service_died", 1);
1035             sendErrorCallbacksToAllLocked(SoundTrigger.STATUS_DEAD_OBJECT);
1036         } finally {
1037             internalClearModelStateLocked();
1038             internalClearGlobalStateLocked();
1039             if (mModule != null) {
1040                 mModule.detach();
1041                 mModule = null;
1042                 mServiceDisabled = false;
1043             }
1044         }
1045     }
1046 
1047     // internalClearGlobalStateLocked() cleans up the telephony and power save listeners.
internalClearGlobalStateLocked()1048     private void internalClearGlobalStateLocked() {
1049         // Unregister from call state changes.
1050         final long token = Binder.clearCallingIdentity();
1051         try {
1052             mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE);
1053         } finally {
1054             Binder.restoreCallingIdentity(token);
1055         }
1056 
1057         // Unregister from power save mode changes.
1058         if (mPowerSaveModeListener != null) {
1059             mContext.unregisterReceiver(mPowerSaveModeListener);
1060             mPowerSaveModeListener = null;
1061         }
1062         mRecognitionRequested = false;
1063     }
1064 
1065     // Clears state for all models (generic and keyphrase).
internalClearModelStateLocked()1066     private void internalClearModelStateLocked() {
1067         for (ModelData modelData : mModelDataMap.values()) {
1068             modelData.clearState();
1069         }
1070     }
1071 
1072     class MyCallStateListener extends PhoneStateListener {
MyCallStateListener(@onNull Looper looper)1073         MyCallStateListener(@NonNull Looper looper) {
1074             super(Objects.requireNonNull(looper));
1075         }
1076 
1077         @Override
onCallStateChanged(int state, String arg1)1078         public void onCallStateChanged(int state, String arg1) {
1079             if (DBG) Slog.d(TAG, "onCallStateChanged: " + state);
1080 
1081             if (mHandler != null) {
1082                 synchronized (mLock) {
1083                     mHandler.removeMessages(MSG_CALL_STATE_CHANGED);
1084                     Message msg = mHandler.obtainMessage(MSG_CALL_STATE_CHANGED, state, 0);
1085                     mHandler.sendMessageDelayed(
1086                             msg, (TelephonyManager.CALL_STATE_OFFHOOK == state) ? 0
1087                                     : CALL_INACTIVE_MSG_DELAY_MS);
1088                 }
1089             }
1090         }
1091     }
1092 
1093     class PowerSaveModeListener extends BroadcastReceiver {
1094         @Override
onReceive(Context context, Intent intent)1095         public void onReceive(Context context, Intent intent) {
1096             if (!PowerManager.ACTION_POWER_SAVE_MODE_CHANGED.equals(intent.getAction())) {
1097                 return;
1098             }
1099             @SoundTriggerPowerSaveMode int soundTriggerPowerSaveMode =
1100                     mPowerManager.getSoundTriggerPowerSaveMode();
1101             if (DBG) {
1102                 Slog.d(TAG, "onPowerSaveModeChanged: " + soundTriggerPowerSaveMode);
1103             }
1104             synchronized (mLock) {
1105                 onPowerSaveModeChangedLocked(soundTriggerPowerSaveMode);
1106             }
1107         }
1108     }
1109 
dump(FileDescriptor fd, PrintWriter pw, String[] args)1110     void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
1111         synchronized (mLock) {
1112             pw.print("  module properties=");
1113             pw.println(mModuleProperties == null ? "null" : mModuleProperties);
1114             pw.print("  call active=");
1115             pw.println(mCallActive);
1116             pw.println("  SoundTrigger Power State=" + mSoundTriggerPowerSaveMode);
1117             pw.print("  service disabled=");
1118             pw.println(mServiceDisabled);
1119         }
1120     }
1121 
initializeDeviceStateListeners()1122     private void initializeDeviceStateListeners() {
1123         if (mRecognitionRequested) {
1124             return;
1125         }
1126         final long token = Binder.clearCallingIdentity();
1127         try {
1128             // Get the current call state synchronously for the first recognition.
1129             mCallActive = mTelephonyManager.getCallState() == TelephonyManager.CALL_STATE_OFFHOOK;
1130 
1131             // Register for call state changes when the first call to start recognition occurs.
1132             mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
1133 
1134             // Register for power saver mode changes when the first call to start recognition
1135             // occurs.
1136             if (mPowerSaveModeListener == null) {
1137                 mPowerSaveModeListener = new PowerSaveModeListener();
1138                 mContext.registerReceiver(mPowerSaveModeListener,
1139                         new IntentFilter(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED));
1140             }
1141             mSoundTriggerPowerSaveMode = mPowerManager.getSoundTriggerPowerSaveMode();
1142 
1143             mRecognitionRequested = true;
1144         } finally {
1145             Binder.restoreCallingIdentity(token);
1146         }
1147     }
1148 
1149     // Sends an error callback to all models with a valid registered callback.
sendErrorCallbacksToAllLocked(int errorCode)1150     private void sendErrorCallbacksToAllLocked(int errorCode) {
1151         for (ModelData modelData : mModelDataMap.values()) {
1152             IRecognitionStatusCallback callback = modelData.getCallback();
1153             if (callback != null) {
1154                 try {
1155                     callback.onError(errorCode);
1156                 } catch (RemoteException e) {
1157                     Slog.w(TAG, "RemoteException sendErrorCallbacksToAllLocked for model handle " +
1158                             modelData.getHandle(), e);
1159                 }
1160             }
1161         }
1162     }
1163 
1164     /**
1165      * Stops and unloads all models. This is intended as a clean-up call with the expectation that
1166      * this instance is not used after.
1167      * @hide
1168      */
detach()1169     public void detach() {
1170         synchronized (mLock) {
1171             for (ModelData model : mModelDataMap.values()) {
1172                 forceStopAndUnloadModelLocked(model, null);
1173             }
1174             mModelDataMap.clear();
1175             internalClearGlobalStateLocked();
1176             if (mModule != null) {
1177                 mModule.detach();
1178                 mModule = null;
1179             }
1180         }
1181     }
1182 
1183     /**
1184      * Stops and unloads a sound model, and removes any reference to the model if successful.
1185      *
1186      * @param modelData The model data to remove.
1187      * @param exception Optional exception to print in logcat. May be null.
1188      */
forceStopAndUnloadModelLocked(ModelData modelData, Exception exception)1189     private void forceStopAndUnloadModelLocked(ModelData modelData, Exception exception) {
1190       forceStopAndUnloadModelLocked(modelData, exception, null /* modelDataIterator */);
1191     }
1192 
1193     /**
1194      * Stops and unloads a sound model, and removes any reference to the model if successful.
1195      *
1196      * @param modelData The model data to remove.
1197      * @param exception Optional exception to print in logcat. May be null.
1198      * @param modelDataIterator If this function is to be used while iterating over the
1199      *        mModelDataMap, you can provide the iterator for the current model data to be used to
1200      *        remove the modelData from the map. This avoids generating a
1201      *        ConcurrentModificationException, since this function will try and remove the model
1202      *        data from the mModelDataMap when it can successfully unload the model.
1203      */
forceStopAndUnloadModelLocked(ModelData modelData, Exception exception, Iterator modelDataIterator)1204     private void forceStopAndUnloadModelLocked(ModelData modelData, Exception exception,
1205             Iterator modelDataIterator) {
1206         if (exception != null) {
1207           Slog.e(TAG, "forceStopAndUnloadModel", exception);
1208         }
1209         if (mModule == null) {
1210             return;
1211         }
1212         if (modelData.isModelStarted()) {
1213             Slog.d(TAG, "Stopping previously started dangling model " + modelData.getHandle());
1214             if (mModule.stopRecognition(modelData.getHandle()) == STATUS_OK) {
1215                 modelData.setStopped();
1216                 modelData.setRequested(false);
1217             } else {
1218                 Slog.e(TAG, "Failed to stop model " + modelData.getHandle());
1219             }
1220         }
1221         if (modelData.isModelLoaded()) {
1222             Slog.d(TAG, "Unloading previously loaded dangling model " + modelData.getHandle());
1223             if (mModule.unloadSoundModel(modelData.getHandle()) == STATUS_OK) {
1224                 // Remove the model data from existence.
1225                 if (modelDataIterator != null) {
1226                     modelDataIterator.remove();
1227                 } else {
1228                     mModelDataMap.remove(modelData.getModelId());
1229                 }
1230                 Iterator it = mKeyphraseUuidMap.entrySet().iterator();
1231                 while (it.hasNext()) {
1232                     Map.Entry pair = (Map.Entry) it.next();
1233                     if (pair.getValue().equals(modelData.getModelId())) {
1234                         it.remove();
1235                     }
1236                 }
1237                 modelData.clearState();
1238             } else {
1239                 Slog.e(TAG, "Failed to unload model " + modelData.getHandle());
1240             }
1241         }
1242     }
1243 
stopAndUnloadDeadModelsLocked()1244     private void stopAndUnloadDeadModelsLocked() {
1245         Iterator it = mModelDataMap.entrySet().iterator();
1246         while (it.hasNext()) {
1247             ModelData modelData = (ModelData) ((Map.Entry) it.next()).getValue();
1248             if (!modelData.isModelLoaded()) {
1249                 continue;
1250             }
1251             if (modelData.getCallback() == null
1252                     || (modelData.getCallback().asBinder() != null
1253                         && !modelData.getCallback().asBinder().pingBinder())) {
1254                 // No one is listening on this model, so we might as well evict it.
1255                 Slog.w(TAG, "Removing model " + modelData.getHandle() + " that has no clients");
1256                 forceStopAndUnloadModelLocked(modelData, null /* exception */, it);
1257             }
1258         }
1259     }
1260 
getOrCreateGenericModelDataLocked(UUID modelId)1261     private ModelData getOrCreateGenericModelDataLocked(UUID modelId) {
1262         ModelData modelData = mModelDataMap.get(modelId);
1263         if (modelData == null) {
1264             modelData = ModelData.createGenericModelData(modelId);
1265             mModelDataMap.put(modelId, modelData);
1266         } else if (!modelData.isGenericModel()) {
1267             Slog.e(TAG, "UUID already used for non-generic model.");
1268             return null;
1269         }
1270         return modelData;
1271     }
1272 
removeKeyphraseModelLocked(int keyphraseId)1273     private void removeKeyphraseModelLocked(int keyphraseId) {
1274         UUID uuid = mKeyphraseUuidMap.get(keyphraseId);
1275         if (uuid == null) {
1276             return;
1277         }
1278         mModelDataMap.remove(uuid);
1279         mKeyphraseUuidMap.remove(keyphraseId);
1280     }
1281 
getKeyphraseModelDataLocked(int keyphraseId)1282     private ModelData getKeyphraseModelDataLocked(int keyphraseId) {
1283         UUID uuid = mKeyphraseUuidMap.get(keyphraseId);
1284         if (uuid == null) {
1285             return null;
1286         }
1287         return mModelDataMap.get(uuid);
1288     }
1289 
1290     // Use this to create a new ModelData entry for a keyphrase Id. It will overwrite existing
1291     // mapping if one exists.
createKeyphraseModelDataLocked(UUID modelId, int keyphraseId)1292     private ModelData createKeyphraseModelDataLocked(UUID modelId, int keyphraseId) {
1293         mKeyphraseUuidMap.remove(keyphraseId);
1294         mModelDataMap.remove(modelId);
1295         mKeyphraseUuidMap.put(keyphraseId, modelId);
1296         ModelData modelData = ModelData.createKeyphraseModelData(modelId);
1297         mModelDataMap.put(modelId, modelData);
1298         return modelData;
1299     }
1300 
1301     // Instead of maintaining a second hashmap of modelHandle -> ModelData, we just
1302     // iterate through to find the right object (since we don't expect 100s of models
1303     // to be stored).
getModelDataForLocked(int modelHandle)1304     private ModelData getModelDataForLocked(int modelHandle) {
1305         // Fetch ModelData object corresponding to the model handle.
1306         for (ModelData model : mModelDataMap.values()) {
1307             if (model.getHandle() == modelHandle) {
1308                 return model;
1309             }
1310         }
1311         return null;
1312     }
1313 
1314     /**
1315      * Determines if recognition is allowed at all based on device state
1316      *
1317      * <p>Depending on the state of the SoundTrigger service, whether a call is active, or if
1318      * battery saver mode is enabled, a specific model may or may not be able to run. The result
1319      * of this check is not permanent, and the state of the device can change at any time.
1320      *
1321      * @param modelData Model data to be used for recognition
1322      * @return True if recognition is allowed to run at this time. False if not.
1323      */
isRecognitionAllowedByDeviceState(ModelData modelData)1324     private boolean isRecognitionAllowedByDeviceState(ModelData modelData) {
1325         // if mRecognitionRequested is false, call and power state listeners are not registered so
1326         // we read current state directly from services
1327         if (!mRecognitionRequested) {
1328             mCallActive = mTelephonyManager.getCallState() == TelephonyManager.CALL_STATE_OFFHOOK;
1329             mSoundTriggerPowerSaveMode = mPowerManager.getSoundTriggerPowerSaveMode();
1330         }
1331 
1332         return !mCallActive && !mServiceDisabled
1333                 && isRecognitionAllowedByPowerState(
1334                 modelData);
1335     }
1336 
1337     /**
1338      * Helper function to validate if a recognition should run based on the current power state
1339      *
1340      * @param modelData Model data to be used for recognition
1341      * @return True if device state allows recognition to run, false if not.
1342      */
isRecognitionAllowedByPowerState(ModelData modelData)1343     boolean isRecognitionAllowedByPowerState(ModelData modelData) {
1344         return mSoundTriggerPowerSaveMode == PowerManager.SOUND_TRIGGER_MODE_ALL_ENABLED
1345                 || (mSoundTriggerPowerSaveMode == PowerManager.SOUND_TRIGGER_MODE_CRITICAL_ONLY
1346                 && modelData.shouldRunInBatterySaverMode());
1347     }
1348 
1349     // A single routine that implements the start recognition logic for both generic and keyphrase
1350     // models.
startRecognitionLocked(ModelData modelData, boolean notifyClientOnError)1351     private int startRecognitionLocked(ModelData modelData, boolean notifyClientOnError) {
1352         IRecognitionStatusCallback callback = modelData.getCallback();
1353         RecognitionConfig config = modelData.getRecognitionConfig();
1354         if (callback == null || !modelData.isModelLoaded() || config == null) {
1355             // Nothing to do here.
1356             Slog.w(TAG, "startRecognition: Bad data passed in.");
1357             MetricsLogger.count(mContext, "sth_start_recognition_error", 1);
1358             return STATUS_ERROR;
1359         }
1360 
1361         if (!isRecognitionAllowedByDeviceState(modelData)) {
1362             // Nothing to do here.
1363             Slog.w(TAG, "startRecognition requested but not allowed.");
1364             MetricsLogger.count(mContext, "sth_start_recognition_not_allowed", 1);
1365             return STATUS_OK;
1366         }
1367 
1368         if (mModule == null) {
1369             return STATUS_ERROR;
1370         }
1371         int status = mModule.startRecognition(modelData.getHandle(), config);
1372         if (status != SoundTrigger.STATUS_OK) {
1373             Slog.w(TAG, "startRecognition failed with " + status);
1374             MetricsLogger.count(mContext, "sth_start_recognition_error", 1);
1375             // Notify of error if needed.
1376             if (notifyClientOnError) {
1377                 try {
1378                     callback.onError(status);
1379                 } catch (DeadObjectException e) {
1380                     forceStopAndUnloadModelLocked(modelData, e);
1381                 } catch (RemoteException e) {
1382                     Slog.w(TAG, "RemoteException in onError", e);
1383                 }
1384             }
1385         } else {
1386             Slog.i(TAG, "startRecognition successful.");
1387             MetricsLogger.count(mContext, "sth_start_recognition_success", 1);
1388             modelData.setStarted();
1389             // Notify of resume if needed.
1390             if (notifyClientOnError) {
1391                 try {
1392                     callback.onRecognitionResumed();
1393                 } catch (DeadObjectException e) {
1394                     forceStopAndUnloadModelLocked(modelData, e);
1395                 } catch (RemoteException e) {
1396                     Slog.w(TAG, "RemoteException in onRecognitionResumed", e);
1397                 }
1398             }
1399         }
1400         if (DBG) {
1401             Slog.d(TAG, "Model being started :" + modelData.toString());
1402         }
1403         return status;
1404     }
1405 
stopRecognitionLocked(ModelData modelData, boolean notify)1406     private int stopRecognitionLocked(ModelData modelData, boolean notify) {
1407         if (mModule == null) {
1408             return STATUS_ERROR;
1409         }
1410 
1411         IRecognitionStatusCallback callback = modelData.getCallback();
1412         // Stop recognition.
1413         int status = STATUS_OK;
1414 
1415         status = mModule.stopRecognition(modelData.getHandle());
1416 
1417         if (status != SoundTrigger.STATUS_OK) {
1418             Slog.w(TAG, "stopRecognition call failed with " + status);
1419             MetricsLogger.count(mContext, "sth_stop_recognition_error", 1);
1420             if (notify) {
1421                 try {
1422                     callback.onError(status);
1423                 } catch (DeadObjectException e) {
1424                     forceStopAndUnloadModelLocked(modelData, e);
1425                 } catch (RemoteException e) {
1426                     Slog.w(TAG, "RemoteException in onError", e);
1427                 }
1428             }
1429         } else {
1430             modelData.setStopped();
1431             MetricsLogger.count(mContext, "sth_stop_recognition_success", 1);
1432             // Notify of pause if needed.
1433             if (notify) {
1434                 try {
1435                     callback.onRecognitionPaused();
1436                 } catch (DeadObjectException e) {
1437                     forceStopAndUnloadModelLocked(modelData, e);
1438                 } catch (RemoteException e) {
1439                     Slog.w(TAG, "RemoteException in onRecognitionPaused", e);
1440                 }
1441             }
1442         }
1443         if (DBG) {
1444             Slog.d(TAG, "Model being stopped :" + modelData.toString());
1445         }
1446         return status;
1447     }
1448 
dumpModelStateLocked()1449     private void dumpModelStateLocked() {
1450         for (UUID modelId : mModelDataMap.keySet()) {
1451             ModelData modelData = mModelDataMap.get(modelId);
1452             Slog.i(TAG, "Model :" + modelData.toString());
1453         }
1454     }
1455 
1456     // Computes whether we have any recognition running at all (voice or generic). Sets
1457     // the mRecognitionRequested variable with the result.
computeRecognitionRequestedLocked()1458     private boolean computeRecognitionRequestedLocked() {
1459         if (mModuleProperties == null || mModule == null) {
1460             mRecognitionRequested = false;
1461             return mRecognitionRequested;
1462         }
1463         for (ModelData modelData : mModelDataMap.values()) {
1464             if (modelData.isRequested()) {
1465                 mRecognitionRequested = true;
1466                 return mRecognitionRequested;
1467             }
1468         }
1469         mRecognitionRequested = false;
1470         return mRecognitionRequested;
1471     }
1472 
1473     // This class encapsulates the callbacks, state, handles and any other information that
1474     // represents a model.
1475     private static class ModelData {
1476         // Model not loaded (and hence not started).
1477         static final int MODEL_NOTLOADED = 0;
1478 
1479         // Loaded implies model was successfully loaded. Model not started yet.
1480         static final int MODEL_LOADED = 1;
1481 
1482         // Started implies model was successfully loaded and start was called.
1483         static final int MODEL_STARTED = 2;
1484 
1485         // One of MODEL_NOTLOADED, MODEL_LOADED, MODEL_STARTED (which implies loaded).
1486         private int mModelState;
1487         private UUID mModelId;
1488 
1489         // mRequested captures the explicit intent that a start was requested for this model. We
1490         // continue to capture and retain this state even after the model gets started, so that we
1491         // know when a model gets stopped due to "other" reasons, that we should start it again.
1492         // This was the intended behavior of the "mRequested" variable in the previous version of
1493         // this code that we are replicating here.
1494         //
1495         // The "other" reasons include power save, abort being called from the lower layer (due
1496         // to concurrent capture not being supported) and phone call state. Once we recover from
1497         // these transient disruptions, we would start such models again where mRequested == true.
1498         // Thus, mRequested gets reset only when there is an explicit intent to stop the model
1499         // coming from the SoundTriggerService layer that uses this class (and thus eventually
1500         // from the app that manages this model).
1501         private boolean mRequested = false;
1502 
1503         // One of SoundModel.TYPE_GENERIC or SoundModel.TYPE_KEYPHRASE. Initially set
1504         // to SoundModel.TYPE_UNKNOWN;
1505         private int mModelType = SoundModel.TYPE_UNKNOWN;
1506 
1507         private IRecognitionStatusCallback mCallback = null;
1508         private RecognitionConfig mRecognitionConfig = null;
1509 
1510         // Model handle is an integer used by the HAL as an identifier for sound
1511         // models.
1512         private int mModelHandle;
1513 
1514         /**
1515          * True if the service should continue listening when battery saver mode is enabled.
1516          * Having this flag set requires the client calling
1517          * {@link SoundTriggerModule#startRecognition(int, RecognitionConfig)} to be granted
1518          * {@link android.Manifest.permission#SOUND_TRIGGER_RUN_IN_BATTERY_SAVER}.
1519          */
1520         public boolean mRunInBatterySaverMode = false;
1521 
1522         // The SoundModel instance, one of KeyphraseSoundModel or GenericSoundModel.
1523         private SoundModel mSoundModel = null;
1524 
ModelData(UUID modelId, int modelType)1525         private ModelData(UUID modelId, int modelType) {
1526             mModelId = modelId;
1527             // Private constructor, since we require modelType to be one of TYPE_GENERIC,
1528             // TYPE_KEYPHRASE or TYPE_UNKNOWN.
1529             mModelType = modelType;
1530         }
1531 
createKeyphraseModelData(UUID modelId)1532         static ModelData createKeyphraseModelData(UUID modelId) {
1533             return new ModelData(modelId, SoundModel.TYPE_KEYPHRASE);
1534         }
1535 
createGenericModelData(UUID modelId)1536         static ModelData createGenericModelData(UUID modelId) {
1537             return new ModelData(modelId, SoundModel.TYPE_GENERIC_SOUND);
1538         }
1539 
1540         // Note that most of the functionality in this Java class will not work for
1541         // SoundModel.TYPE_UNKNOWN nevertheless we have it since lower layers support it.
createModelDataOfUnknownType(UUID modelId)1542         static ModelData createModelDataOfUnknownType(UUID modelId) {
1543             return new ModelData(modelId, SoundModel.TYPE_UNKNOWN);
1544         }
1545 
setCallback(IRecognitionStatusCallback callback)1546         synchronized void setCallback(IRecognitionStatusCallback callback) {
1547             mCallback = callback;
1548         }
1549 
getCallback()1550         synchronized IRecognitionStatusCallback getCallback() {
1551             return mCallback;
1552         }
1553 
isModelLoaded()1554         synchronized boolean isModelLoaded() {
1555             return (mModelState == MODEL_LOADED || mModelState == MODEL_STARTED);
1556         }
1557 
isModelNotLoaded()1558         synchronized boolean isModelNotLoaded() {
1559             return mModelState == MODEL_NOTLOADED;
1560         }
1561 
setStarted()1562         synchronized void setStarted() {
1563             mModelState = MODEL_STARTED;
1564         }
1565 
setStopped()1566         synchronized void setStopped() {
1567             mModelState = MODEL_LOADED;
1568         }
1569 
setLoaded()1570         synchronized void setLoaded() {
1571             mModelState = MODEL_LOADED;
1572         }
1573 
isModelStarted()1574         synchronized boolean isModelStarted() {
1575             return mModelState == MODEL_STARTED;
1576         }
1577 
clearState()1578         synchronized void clearState() {
1579             mModelState = MODEL_NOTLOADED;
1580             mRecognitionConfig = null;
1581             mRequested = false;
1582             mCallback = null;
1583         }
1584 
clearCallback()1585         synchronized void clearCallback() {
1586             mCallback = null;
1587         }
1588 
setHandle(int handle)1589         synchronized void setHandle(int handle) {
1590             mModelHandle = handle;
1591         }
1592 
setRecognitionConfig(RecognitionConfig config)1593         synchronized void setRecognitionConfig(RecognitionConfig config) {
1594             mRecognitionConfig = config;
1595         }
1596 
setRunInBatterySaverMode(boolean runInBatterySaverMode)1597         synchronized void setRunInBatterySaverMode(boolean runInBatterySaverMode) {
1598             mRunInBatterySaverMode = runInBatterySaverMode;
1599         }
1600 
shouldRunInBatterySaverMode()1601         synchronized boolean shouldRunInBatterySaverMode() {
1602             return mRunInBatterySaverMode;
1603         }
1604 
getHandle()1605         synchronized int getHandle() {
1606             return mModelHandle;
1607         }
1608 
getModelId()1609         synchronized UUID getModelId() {
1610             return mModelId;
1611         }
1612 
getRecognitionConfig()1613         synchronized RecognitionConfig getRecognitionConfig() {
1614             return mRecognitionConfig;
1615         }
1616 
1617         // Whether a start recognition was requested.
isRequested()1618         synchronized boolean isRequested() {
1619             return mRequested;
1620         }
1621 
setRequested(boolean requested)1622         synchronized void setRequested(boolean requested) {
1623             mRequested = requested;
1624         }
1625 
setSoundModel(SoundModel soundModel)1626         synchronized void setSoundModel(SoundModel soundModel) {
1627             mSoundModel = soundModel;
1628         }
1629 
getSoundModel()1630         synchronized SoundModel getSoundModel() {
1631             return mSoundModel;
1632         }
1633 
getModelType()1634         synchronized int getModelType() {
1635             return mModelType;
1636         }
1637 
isKeyphraseModel()1638         synchronized boolean isKeyphraseModel() {
1639             return mModelType == SoundModel.TYPE_KEYPHRASE;
1640         }
1641 
isGenericModel()1642         synchronized boolean isGenericModel() {
1643             return mModelType == SoundModel.TYPE_GENERIC_SOUND;
1644         }
1645 
stateToString()1646         synchronized String stateToString() {
1647             switch(mModelState) {
1648                 case MODEL_NOTLOADED: return "NOT_LOADED";
1649                 case MODEL_LOADED: return "LOADED";
1650                 case MODEL_STARTED: return "STARTED";
1651             }
1652             return "Unknown state";
1653         }
1654 
requestedToString()1655         synchronized String requestedToString() {
1656             return "Requested: " + (mRequested ? "Yes" : "No");
1657         }
1658 
callbackToString()1659         synchronized String callbackToString() {
1660             return "Callback: " + (mCallback != null ? mCallback.asBinder() : "null");
1661         }
1662 
uuidToString()1663         synchronized String uuidToString() {
1664             return "UUID: " + mModelId;
1665         }
1666 
toString()1667         synchronized public String toString() {
1668             return "Handle: " + mModelHandle + "\n" +
1669                     "ModelState: " + stateToString() + "\n" +
1670                     requestedToString() + "\n" +
1671                     callbackToString() + "\n" +
1672                     uuidToString() + "\n" +
1673                     modelTypeToString() +
1674                     "RunInBatterySaverMode=" + mRunInBatterySaverMode;
1675         }
1676 
modelTypeToString()1677         synchronized String modelTypeToString() {
1678             String type = null;
1679             switch (mModelType) {
1680                 case SoundModel.TYPE_GENERIC_SOUND: type = "Generic"; break;
1681                 case SoundModel.TYPE_UNKNOWN: type = "Unknown"; break;
1682                 case SoundModel.TYPE_KEYPHRASE: type = "Keyphrase"; break;
1683             }
1684             return "Model type: " + type + "\n";
1685         }
1686     }
1687 }
1688