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