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