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 static android.hardware.soundtrigger.SoundTrigger.STATUS_ERROR; 20 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.SoundTrigger; 27 import android.hardware.soundtrigger.SoundTrigger.GenericRecognitionEvent; 28 import android.hardware.soundtrigger.SoundTrigger.GenericSoundModel; 29 import android.hardware.soundtrigger.SoundTrigger.Keyphrase; 30 import android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionEvent; 31 import android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra; 32 import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel; 33 import android.hardware.soundtrigger.SoundTrigger.ModuleProperties; 34 import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig; 35 import android.hardware.soundtrigger.SoundTrigger.RecognitionEvent; 36 import android.hardware.soundtrigger.SoundTrigger.SoundModel; 37 import android.hardware.soundtrigger.SoundTrigger.SoundModelEvent; 38 import android.hardware.soundtrigger.SoundTriggerModule; 39 import android.os.PowerManager; 40 import android.os.RemoteException; 41 import android.telephony.PhoneStateListener; 42 import android.telephony.TelephonyManager; 43 import android.util.Slog; 44 import com.android.internal.logging.MetricsLogger; 45 46 import java.io.FileDescriptor; 47 import java.io.PrintWriter; 48 import java.util.ArrayList; 49 import java.util.HashMap; 50 import 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 */ 62 public 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 SoundTriggerHelper(Context context)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 */ startGenericRecognition(UUID modelId, GenericSoundModel soundModel, IRecognitionStatusCallback callback, RecognitionConfig recognitionConfig)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 */ startKeyphraseRecognition(int keyphraseId, KeyphraseSoundModel soundModel, IRecognitionStatusCallback callback, RecognitionConfig recognitionConfig)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 cleanUpExistingKeyphraseModel(ModelData modelData)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 */ startRecognition(SoundModel soundModel, ModelData modelData, IRecognitionStatusCallback callback, RecognitionConfig recognitionConfig, int keyphraseId)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 */ stopGenericRecognition(UUID modelId, IRecognitionStatusCallback callback)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 */ stopKeyphraseRecognition(int keyphraseId, IRecognitionStatusCallback callback)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 */ stopRecognition(ModelData modelData, IRecognitionStatusCallback callback)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. tryStopAndUnloadLocked(ModelData modelData, boolean stopModel, boolean unloadModel)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 getModuleProperties()485 public ModuleProperties getModuleProperties() { 486 return mModuleProperties; 487 } 488 unloadKeyphraseSoundModel(int keyphraseId)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 unloadGenericSoundModel(UUID modelId)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 onRecognition(RecognitionEvent event)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 isKeyphraseRecognitionEvent(RecognitionEvent event)590 private boolean isKeyphraseRecognitionEvent(RecognitionEvent event) { 591 return event instanceof KeyphraseRecognitionEvent; 592 } 593 onGenericRecognitionSuccessLocked(GenericRecognitionEvent event)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 onSoundModelUpdate(SoundModelEvent event)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 onServiceStateChange(int state)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 onServiceDied()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 onCallStateChangedLocked(boolean callActive)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 onPowerSaveModeChangedLocked(boolean isPowerSaveMode)675 private void onPowerSaveModeChangedLocked(boolean isPowerSaveMode) { 676 if (mIsPowerSaveMode == isPowerSaveMode) { 677 return; 678 } 679 mIsPowerSaveMode = isPowerSaveMode; 680 updateAllRecognitionsLocked(true /* notify */); 681 } 682 onSoundModelUpdatedLocked(SoundModelEvent event)683 private void onSoundModelUpdatedLocked(SoundModelEvent event) { 684 // TODO: Handle sound model update here. 685 } 686 onServiceStateChangedLocked(boolean disabled)687 private void onServiceStateChangedLocked(boolean disabled) { 688 if (disabled == mServiceDisabled) { 689 return; 690 } 691 mServiceDisabled = disabled; 692 updateAllRecognitionsLocked(true /* notify */); 693 } 694 onRecognitionAbortLocked(RecognitionEvent event)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) { 700 modelData.setStopped(); 701 } 702 } 703 onRecognitionFailureLocked()704 private void onRecognitionFailureLocked() { 705 Slog.w(TAG, "Recognition failure"); 706 MetricsLogger.count(mContext, "sth_recognition_failure_event", 1); 707 try { 708 sendErrorCallbacksToAll(STATUS_ERROR); 709 } catch (RemoteException e) { 710 Slog.w(TAG, "RemoteException in onError", e); 711 } finally { 712 internalClearModelStateLocked(); 713 internalClearGlobalStateLocked(); 714 } 715 } 716 getKeyphraseIdFromEvent(KeyphraseRecognitionEvent event)717 private int getKeyphraseIdFromEvent(KeyphraseRecognitionEvent event) { 718 if (event == null) { 719 Slog.w(TAG, "Null RecognitionEvent received."); 720 return INVALID_VALUE; 721 } 722 KeyphraseRecognitionExtra[] keyphraseExtras = 723 ((KeyphraseRecognitionEvent) event).keyphraseExtras; 724 if (keyphraseExtras == null || keyphraseExtras.length == 0) { 725 Slog.w(TAG, "Invalid keyphrase recognition event!"); 726 return INVALID_VALUE; 727 } 728 // TODO: Handle more than one keyphrase extras. 729 return keyphraseExtras[0].id; 730 } 731 onKeyphraseRecognitionSuccessLocked(KeyphraseRecognitionEvent event)732 private void onKeyphraseRecognitionSuccessLocked(KeyphraseRecognitionEvent event) { 733 Slog.i(TAG, "Recognition success"); 734 MetricsLogger.count(mContext, "sth_keyphrase_recognition_event", 1); 735 int keyphraseId = getKeyphraseIdFromEvent(event); 736 ModelData modelData = getKeyphraseModelDataLocked(keyphraseId); 737 738 if (modelData == null || !modelData.isKeyphraseModel()) { 739 Slog.e(TAG, "Keyphase model data does not exist for ID:" + keyphraseId); 740 return; 741 } 742 743 if (modelData.getCallback() == null) { 744 Slog.w(TAG, "Received onRecognition event without callback for keyphrase model."); 745 return; 746 } 747 748 try { 749 modelData.getCallback().onKeyphraseDetected((KeyphraseRecognitionEvent) event); 750 } catch (RemoteException e) { 751 Slog.w(TAG, "RemoteException in onKeyphraseDetected", e); 752 } 753 754 modelData.setStopped(); 755 756 RecognitionConfig config = modelData.getRecognitionConfig(); 757 if (config != null) { 758 // Whether we should continue by starting this again. 759 modelData.setRequested(config.allowMultipleTriggers); 760 } 761 // TODO: Remove this block if the lower layer supports multiple triggers. 762 if (modelData.isRequested()) { 763 updateRecognitionLocked(modelData, isRecognitionAllowed(), true /* notify */); 764 } 765 } 766 updateAllRecognitionsLocked(boolean notify)767 private void updateAllRecognitionsLocked(boolean notify) { 768 boolean isAllowed = isRecognitionAllowed(); 769 for (ModelData modelData : mModelDataMap.values()) { 770 updateRecognitionLocked(modelData, isAllowed, notify); 771 } 772 } 773 updateRecognitionLocked(ModelData model, boolean isAllowed, boolean notify)774 private int updateRecognitionLocked(ModelData model, boolean isAllowed, 775 boolean notify) { 776 boolean start = model.isRequested() && isAllowed; 777 if (start == model.isModelStarted()) { 778 // No-op. 779 return STATUS_OK; 780 } 781 if (start) { 782 return startRecognitionLocked(model, notify); 783 } else { 784 return stopRecognitionLocked(model, notify); 785 } 786 } 787 onServiceDiedLocked()788 private void onServiceDiedLocked() { 789 try { 790 MetricsLogger.count(mContext, "sth_service_died", 1); 791 sendErrorCallbacksToAll(SoundTrigger.STATUS_DEAD_OBJECT); 792 } catch (RemoteException e) { 793 Slog.w(TAG, "RemoteException in onError", e); 794 } finally { 795 internalClearModelStateLocked(); 796 internalClearGlobalStateLocked(); 797 if (mModule != null) { 798 mModule.detach(); 799 mModule = null; 800 } 801 } 802 } 803 804 // internalClearGlobalStateLocked() cleans up the telephony and power save listeners. internalClearGlobalStateLocked()805 private void internalClearGlobalStateLocked() { 806 // Unregister from call state changes. 807 mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE); 808 809 // Unregister from power save mode changes. 810 if (mPowerSaveModeListener != null) { 811 mContext.unregisterReceiver(mPowerSaveModeListener); 812 mPowerSaveModeListener = null; 813 } 814 } 815 816 // Clears state for all models (generic and keyphrase). internalClearModelStateLocked()817 private void internalClearModelStateLocked() { 818 for (ModelData modelData : mModelDataMap.values()) { 819 modelData.clearState(); 820 } 821 } 822 823 class MyCallStateListener extends PhoneStateListener { 824 @Override onCallStateChanged(int state, String arg1)825 public void onCallStateChanged(int state, String arg1) { 826 if (DBG) Slog.d(TAG, "onCallStateChanged: " + state); 827 synchronized (mLock) { 828 onCallStateChangedLocked(TelephonyManager.CALL_STATE_IDLE != state); 829 } 830 } 831 } 832 833 class PowerSaveModeListener extends BroadcastReceiver { 834 @Override onReceive(Context context, Intent intent)835 public void onReceive(Context context, Intent intent) { 836 if (!PowerManager.ACTION_POWER_SAVE_MODE_CHANGED.equals(intent.getAction())) { 837 return; 838 } 839 boolean active = mPowerManager.isPowerSaveMode(); 840 if (DBG) Slog.d(TAG, "onPowerSaveModeChanged: " + active); 841 synchronized (mLock) { 842 onPowerSaveModeChangedLocked(active); 843 } 844 } 845 } 846 dump(FileDescriptor fd, PrintWriter pw, String[] args)847 void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 848 synchronized (mLock) { 849 pw.print(" module properties="); 850 pw.println(mModuleProperties == null ? "null" : mModuleProperties); 851 852 pw.print(" call active="); pw.println(mCallActive); 853 pw.print(" power save mode active="); pw.println(mIsPowerSaveMode); 854 pw.print(" service disabled="); pw.println(mServiceDisabled); 855 } 856 } 857 initializeTelephonyAndPowerStateListeners()858 private void initializeTelephonyAndPowerStateListeners() { 859 // Get the current call state synchronously for the first recognition. 860 mCallActive = mTelephonyManager.getCallState() != TelephonyManager.CALL_STATE_IDLE; 861 862 // Register for call state changes when the first call to start recognition occurs. 863 mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE); 864 865 // Register for power saver mode changes when the first call to start recognition 866 // occurs. 867 if (mPowerSaveModeListener == null) { 868 mPowerSaveModeListener = new PowerSaveModeListener(); 869 mContext.registerReceiver(mPowerSaveModeListener, 870 new IntentFilter(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED)); 871 } 872 mIsPowerSaveMode = mPowerManager.isPowerSaveMode(); 873 } 874 875 // Sends an error callback to all models with a valid registered callback. sendErrorCallbacksToAll(int errorCode)876 private void sendErrorCallbacksToAll(int errorCode) throws RemoteException { 877 for (ModelData modelData : mModelDataMap.values()) { 878 IRecognitionStatusCallback callback = modelData.getCallback(); 879 if (callback != null) { 880 callback.onError(STATUS_ERROR); 881 } 882 } 883 } 884 getOrCreateGenericModelDataLocked(UUID modelId)885 private ModelData getOrCreateGenericModelDataLocked(UUID modelId) { 886 ModelData modelData = mModelDataMap.get(modelId); 887 if (modelData == null) { 888 modelData = ModelData.createGenericModelData(modelId); 889 mModelDataMap.put(modelId, modelData); 890 } else if (!modelData.isGenericModel()) { 891 Slog.e(TAG, "UUID already used for non-generic model."); 892 return null; 893 } 894 return modelData; 895 } 896 removeKeyphraseModelLocked(int keyphraseId)897 private void removeKeyphraseModelLocked(int keyphraseId) { 898 UUID uuid = mKeyphraseUuidMap.get(keyphraseId); 899 if (uuid == null) { 900 return; 901 } 902 mModelDataMap.remove(uuid); 903 mKeyphraseUuidMap.remove(keyphraseId); 904 } 905 getKeyphraseModelDataLocked(int keyphraseId)906 private ModelData getKeyphraseModelDataLocked(int keyphraseId) { 907 UUID uuid = mKeyphraseUuidMap.get(keyphraseId); 908 if (uuid == null) { 909 return null; 910 } 911 return mModelDataMap.get(uuid); 912 } 913 914 // Use this to create a new ModelData entry for a keyphrase Id. It will overwrite existing 915 // mapping if one exists. createKeyphraseModelDataLocked(UUID modelId, int keyphraseId)916 private ModelData createKeyphraseModelDataLocked(UUID modelId, int keyphraseId) { 917 mKeyphraseUuidMap.remove(keyphraseId); 918 mModelDataMap.remove(modelId); 919 mKeyphraseUuidMap.put(keyphraseId, modelId); 920 ModelData modelData = ModelData.createKeyphraseModelData(modelId); 921 mModelDataMap.put(modelId, modelData); 922 return modelData; 923 } 924 925 // Instead of maintaining a second hashmap of modelHandle -> ModelData, we just 926 // iterate through to find the right object (since we don't expect 100s of models 927 // to be stored). getModelDataForLocked(int modelHandle)928 private ModelData getModelDataForLocked(int modelHandle) { 929 // Fetch ModelData object corresponding to the model handle. 930 for (ModelData model : mModelDataMap.values()) { 931 if (model.getHandle() == modelHandle) { 932 return model; 933 } 934 } 935 return null; 936 } 937 938 // Whether we are allowed to run any recognition at all. The conditions that let us run 939 // a recognition include: no active phone call or not being in a power save mode. Also, 940 // the native service should be enabled. isRecognitionAllowed()941 private boolean isRecognitionAllowed() { 942 return !mCallActive && !mServiceDisabled && !mIsPowerSaveMode; 943 } 944 945 // A single routine that implements the start recognition logic for both generic and keyphrase 946 // models. startRecognitionLocked(ModelData modelData, boolean notify)947 private int startRecognitionLocked(ModelData modelData, boolean notify) { 948 IRecognitionStatusCallback callback = modelData.getCallback(); 949 int handle = modelData.getHandle(); 950 RecognitionConfig config = modelData.getRecognitionConfig(); 951 if (callback == null || handle == INVALID_VALUE || config == null) { 952 // Nothing to do here. 953 Slog.w(TAG, "startRecognition: Bad data passed in."); 954 MetricsLogger.count(mContext, "sth_start_recognition_error", 1); 955 return STATUS_ERROR; 956 } 957 958 if (!isRecognitionAllowed()) { 959 // Nothing to do here. 960 Slog.w(TAG, "startRecognition requested but not allowed."); 961 MetricsLogger.count(mContext, "sth_start_recognition_not_allowed", 1); 962 return STATUS_OK; 963 } 964 965 int status = mModule.startRecognition(handle, config); 966 if (status != SoundTrigger.STATUS_OK) { 967 Slog.w(TAG, "startRecognition failed with " + status); 968 MetricsLogger.count(mContext, "sth_start_recognition_error", 1); 969 // Notify of error if needed. 970 if (notify) { 971 try { 972 callback.onError(status); 973 } catch (RemoteException e) { 974 Slog.w(TAG, "RemoteException in onError", e); 975 } 976 } 977 } else { 978 Slog.i(TAG, "startRecognition successful."); 979 MetricsLogger.count(mContext, "sth_start_recognition_success", 1); 980 modelData.setStarted(); 981 // Notify of resume if needed. 982 if (notify) { 983 try { 984 callback.onRecognitionResumed(); 985 } catch (RemoteException e) { 986 Slog.w(TAG, "RemoteException in onRecognitionResumed", e); 987 } 988 } 989 } 990 if (DBG) { 991 Slog.d(TAG, "Model being started :" + modelData.toString()); 992 } 993 return status; 994 } 995 stopRecognitionLocked(ModelData modelData, boolean notify)996 private int stopRecognitionLocked(ModelData modelData, boolean notify) { 997 IRecognitionStatusCallback callback = modelData.getCallback(); 998 999 // Stop recognition. 1000 int status = STATUS_OK; 1001 1002 status = mModule.stopRecognition(modelData.getHandle()); 1003 1004 if (status != SoundTrigger.STATUS_OK) { 1005 Slog.w(TAG, "stopRecognition call failed with " + status); 1006 MetricsLogger.count(mContext, "sth_stop_recognition_error", 1); 1007 if (notify) { 1008 try { 1009 callback.onError(status); 1010 } catch (RemoteException e) { 1011 Slog.w(TAG, "RemoteException in onError", e); 1012 } 1013 } 1014 } else { 1015 modelData.setStopped(); 1016 MetricsLogger.count(mContext, "sth_stop_recognition_success", 1); 1017 // Notify of pause if needed. 1018 if (notify) { 1019 try { 1020 callback.onRecognitionPaused(); 1021 } catch (RemoteException e) { 1022 Slog.w(TAG, "RemoteException in onRecognitionPaused", e); 1023 } 1024 } 1025 } 1026 if (DBG) { 1027 Slog.d(TAG, "Model being stopped :" + modelData.toString()); 1028 } 1029 return status; 1030 } 1031 dumpModelStateLocked()1032 private void dumpModelStateLocked() { 1033 for (UUID modelId : mModelDataMap.keySet()) { 1034 ModelData modelData = mModelDataMap.get(modelId); 1035 Slog.i(TAG, "Model :" + modelData.toString()); 1036 } 1037 } 1038 1039 // Computes whether we have any recognition running at all (voice or generic). Sets 1040 // the mRecognitionRunning variable with the result. computeRecognitionRunningLocked()1041 private boolean computeRecognitionRunningLocked() { 1042 if (mModuleProperties == null || mModule == null) { 1043 mRecognitionRunning = false; 1044 return mRecognitionRunning; 1045 } 1046 for (ModelData modelData : mModelDataMap.values()) { 1047 if (modelData.isModelStarted()) { 1048 mRecognitionRunning = true; 1049 return mRecognitionRunning; 1050 } 1051 } 1052 mRecognitionRunning = false; 1053 return mRecognitionRunning; 1054 } 1055 1056 // This class encapsulates the callbacks, state, handles and any other information that 1057 // represents a model. 1058 private static class ModelData { 1059 // Model not loaded (and hence not started). 1060 static final int MODEL_NOTLOADED = 0; 1061 1062 // Loaded implies model was successfully loaded. Model not started yet. 1063 static final int MODEL_LOADED = 1; 1064 1065 // Started implies model was successfully loaded and start was called. 1066 static final int MODEL_STARTED = 2; 1067 1068 // One of MODEL_NOTLOADED, MODEL_LOADED, MODEL_STARTED (which implies loaded). 1069 private int mModelState; 1070 private UUID mModelId; 1071 1072 // mRequested captures the explicit intent that a start was requested for this model. We 1073 // continue to capture and retain this state even after the model gets started, so that we 1074 // know when a model gets stopped due to "other" reasons, that we should start it again. 1075 // This was the intended behavior of the "mRequested" variable in the previous version of 1076 // this code that we are replicating here. 1077 // 1078 // The "other" reasons include power save, abort being called from the lower layer (due 1079 // to concurrent capture not being supported) and phone call state. Once we recover from 1080 // these transient disruptions, we would start such models again where mRequested == true. 1081 // Thus, mRequested gets reset only when there is an explicit intent to stop the model 1082 // coming from the SoundTriggerService layer that uses this class (and thus eventually 1083 // from the app that manages this model). 1084 private boolean mRequested = false; 1085 1086 // One of SoundModel.TYPE_GENERIC or SoundModel.TYPE_KEYPHRASE. Initially set 1087 // to SoundModel.TYPE_UNKNOWN; 1088 private int mModelType = SoundModel.TYPE_UNKNOWN; 1089 1090 private IRecognitionStatusCallback mCallback = null; 1091 private RecognitionConfig mRecognitionConfig = null; 1092 1093 // Model handle is an integer used by the HAL as an identifier for sound 1094 // models. 1095 private int mModelHandle = INVALID_VALUE; 1096 1097 // The SoundModel instance, one of KeyphraseSoundModel or GenericSoundModel. 1098 private SoundModel mSoundModel = null; 1099 ModelData(UUID modelId, int modelType)1100 private ModelData(UUID modelId, int modelType) { 1101 mModelId = modelId; 1102 // Private constructor, since we require modelType to be one of TYPE_GENERIC, 1103 // TYPE_KEYPHRASE or TYPE_UNKNOWN. 1104 mModelType = modelType; 1105 } 1106 createKeyphraseModelData(UUID modelId)1107 static ModelData createKeyphraseModelData(UUID modelId) { 1108 return new ModelData(modelId, SoundModel.TYPE_KEYPHRASE); 1109 } 1110 createGenericModelData(UUID modelId)1111 static ModelData createGenericModelData(UUID modelId) { 1112 return new ModelData(modelId, SoundModel.TYPE_GENERIC_SOUND); 1113 } 1114 1115 // Note that most of the functionality in this Java class will not work for 1116 // SoundModel.TYPE_UNKNOWN nevertheless we have it since lower layers support it. createModelDataOfUnknownType(UUID modelId)1117 static ModelData createModelDataOfUnknownType(UUID modelId) { 1118 return new ModelData(modelId, SoundModel.TYPE_UNKNOWN); 1119 } 1120 setCallback(IRecognitionStatusCallback callback)1121 synchronized void setCallback(IRecognitionStatusCallback callback) { 1122 mCallback = callback; 1123 } 1124 getCallback()1125 synchronized IRecognitionStatusCallback getCallback() { 1126 return mCallback; 1127 } 1128 isModelLoaded()1129 synchronized boolean isModelLoaded() { 1130 return (mModelState == MODEL_LOADED || mModelState == MODEL_STARTED); 1131 } 1132 isModelNotLoaded()1133 synchronized boolean isModelNotLoaded() { 1134 return mModelState == MODEL_NOTLOADED; 1135 } 1136 setStarted()1137 synchronized void setStarted() { 1138 mModelState = MODEL_STARTED; 1139 } 1140 setStopped()1141 synchronized void setStopped() { 1142 mModelState = MODEL_LOADED; 1143 } 1144 setLoaded()1145 synchronized void setLoaded() { 1146 mModelState = MODEL_LOADED; 1147 } 1148 isModelStarted()1149 synchronized boolean isModelStarted() { 1150 return mModelState == MODEL_STARTED; 1151 } 1152 clearState()1153 synchronized void clearState() { 1154 mModelState = MODEL_NOTLOADED; 1155 mModelHandle = INVALID_VALUE; 1156 mRecognitionConfig = null; 1157 mRequested = false; 1158 mCallback = null; 1159 } 1160 clearCallback()1161 synchronized void clearCallback() { 1162 mCallback = null; 1163 } 1164 setHandle(int handle)1165 synchronized void setHandle(int handle) { 1166 mModelHandle = handle; 1167 } 1168 setRecognitionConfig(RecognitionConfig config)1169 synchronized void setRecognitionConfig(RecognitionConfig config) { 1170 mRecognitionConfig = config; 1171 } 1172 getHandle()1173 synchronized int getHandle() { 1174 return mModelHandle; 1175 } 1176 getModelId()1177 synchronized UUID getModelId() { 1178 return mModelId; 1179 } 1180 getRecognitionConfig()1181 synchronized RecognitionConfig getRecognitionConfig() { 1182 return mRecognitionConfig; 1183 } 1184 1185 // Whether a start recognition was requested. isRequested()1186 synchronized boolean isRequested() { 1187 return mRequested; 1188 } 1189 setRequested(boolean requested)1190 synchronized void setRequested(boolean requested) { 1191 mRequested = requested; 1192 } 1193 setSoundModel(SoundModel soundModel)1194 synchronized void setSoundModel(SoundModel soundModel) { 1195 mSoundModel = soundModel; 1196 } 1197 getSoundModel()1198 synchronized SoundModel getSoundModel() { 1199 return mSoundModel; 1200 } 1201 getModelType()1202 synchronized int getModelType() { 1203 return mModelType; 1204 } 1205 isKeyphraseModel()1206 synchronized boolean isKeyphraseModel() { 1207 return mModelType == SoundModel.TYPE_KEYPHRASE; 1208 } 1209 isGenericModel()1210 synchronized boolean isGenericModel() { 1211 return mModelType == SoundModel.TYPE_GENERIC_SOUND; 1212 } 1213 stateToString()1214 synchronized String stateToString() { 1215 switch(mModelState) { 1216 case MODEL_NOTLOADED: return "NOT_LOADED"; 1217 case MODEL_LOADED: return "LOADED"; 1218 case MODEL_STARTED: return "STARTED"; 1219 } 1220 return "Unknown state"; 1221 } 1222 requestedToString()1223 synchronized String requestedToString() { 1224 return "Requested: " + (mRequested ? "Yes" : "No"); 1225 } 1226 callbackToString()1227 synchronized String callbackToString() { 1228 return "Callback: " + (mCallback != null ? mCallback.asBinder() : "null"); 1229 } 1230 uuidToString()1231 synchronized String uuidToString() { 1232 return "UUID: " + mModelId; 1233 } 1234 toString()1235 synchronized public String toString() { 1236 return "Handle: " + mModelHandle + "\n" + 1237 "ModelState: " + stateToString() + "\n" + 1238 requestedToString() + "\n" + 1239 callbackToString() + "\n" + 1240 uuidToString() + "\n" + modelTypeToString(); 1241 } 1242 modelTypeToString()1243 synchronized String modelTypeToString() { 1244 String type = null; 1245 switch (mModelType) { 1246 case SoundModel.TYPE_GENERIC_SOUND: type = "Generic"; break; 1247 case SoundModel.TYPE_UNKNOWN: type = "Unknown"; break; 1248 case SoundModel.TYPE_KEYPHRASE: type = "Keyphrase"; break; 1249 } 1250 return "Model type: " + type + "\n"; 1251 } 1252 } 1253 } 1254