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