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