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