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