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