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.DeadObjectException; 38 import android.os.PowerManager; 39 import android.os.RemoteException; 40 import android.telephony.PhoneStateListener; 41 import android.telephony.TelephonyManager; 42 import android.util.Slog; 43 import com.android.internal.logging.MetricsLogger; 44 import com.android.server.power.BatterySaverPolicy.ServiceType; 45 46 import java.io.FileDescriptor; 47 import java.io.PrintWriter; 48 import java.util.ArrayList; 49 import java.util.HashMap; 50 import java.util.Iterator; 51 import java.util.Map; 52 import 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 */ 64 public 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 SoundTriggerHelper(Context context)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 */ startGenericRecognition(UUID modelId, GenericSoundModel soundModel, IRecognitionStatusCallback callback, RecognitionConfig recognitionConfig)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 */ startKeyphraseRecognition(int keyphraseId, KeyphraseSoundModel soundModel, IRecognitionStatusCallback callback, RecognitionConfig recognitionConfig)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 cleanUpExistingKeyphraseModelLocked(ModelData modelData)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 */ startRecognition(SoundModel soundModel, ModelData modelData, IRecognitionStatusCallback callback, RecognitionConfig recognitionConfig, int keyphraseId)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 // Load the model 304 int[] handle = new int[] { INVALID_VALUE }; 305 int status = mModule.loadSoundModel(soundModel, handle); 306 if (status != SoundTrigger.STATUS_OK) { 307 Slog.w(TAG, "loadSoundModel call failed with " + status); 308 return status; 309 } 310 if (handle[0] == INVALID_VALUE) { 311 Slog.w(TAG, "loadSoundModel call returned invalid sound model handle"); 312 return STATUS_ERROR; 313 } 314 modelData.setHandle(handle[0]); 315 modelData.setLoaded(); 316 Slog.d(TAG, "Sound model loaded with handle:" + handle[0]); 317 } 318 modelData.setCallback(callback); 319 modelData.setRequested(true); 320 modelData.setRecognitionConfig(recognitionConfig); 321 modelData.setSoundModel(soundModel); 322 323 return startRecognitionLocked(modelData, 324 false /* Don't notify for synchronous calls */); 325 } 326 } 327 328 /** 329 * Stops recognition for the given generic sound model. This is a wrapper for {@link 330 * #stopRecognition}. 331 * 332 * @param modelId The identifier of the generic sound model for which 333 * the recognition is to be stopped. 334 * @param callback The callback for the recognition events related to the given sound model. 335 * 336 * @return One of {@link #STATUS_ERROR} or {@link #STATUS_OK}. 337 */ stopGenericRecognition(UUID modelId, IRecognitionStatusCallback callback)338 int stopGenericRecognition(UUID modelId, IRecognitionStatusCallback callback) { 339 synchronized (mLock) { 340 MetricsLogger.count(mContext, "sth_stop_recognition", 1); 341 if (callback == null || modelId == null) { 342 Slog.e(TAG, "Null callbackreceived for stopGenericRecognition() for modelid:" + 343 modelId); 344 return STATUS_ERROR; 345 } 346 347 ModelData modelData = mModelDataMap.get(modelId); 348 if (modelData == null || !modelData.isGenericModel()) { 349 Slog.w(TAG, "Attempting stopRecognition on invalid model with id:" + modelId); 350 return STATUS_ERROR; 351 } 352 353 int status = stopRecognition(modelData, callback); 354 if (status != SoundTrigger.STATUS_OK) { 355 Slog.w(TAG, "stopGenericRecognition failed: " + status); 356 } 357 return status; 358 } 359 } 360 361 /** 362 * Stops recognition for the given {@link Keyphrase} if a recognition is 363 * currently active. This is a wrapper for {@link #stopRecognition()}. 364 * 365 * @param keyphraseId The identifier of the keyphrase for which 366 * the recognition is to be stopped. 367 * @param callback The callback for the recognition events related to the given keyphrase. 368 * 369 * @return One of {@link #STATUS_ERROR} or {@link #STATUS_OK}. 370 */ stopKeyphraseRecognition(int keyphraseId, IRecognitionStatusCallback callback)371 int stopKeyphraseRecognition(int keyphraseId, IRecognitionStatusCallback callback) { 372 synchronized (mLock) { 373 MetricsLogger.count(mContext, "sth_stop_recognition", 1); 374 if (callback == null) { 375 Slog.e(TAG, "Null callback received for stopKeyphraseRecognition() for keyphraseId:" + 376 keyphraseId); 377 return STATUS_ERROR; 378 } 379 380 ModelData modelData = getKeyphraseModelDataLocked(keyphraseId); 381 if (modelData == null || !modelData.isKeyphraseModel()) { 382 Slog.e(TAG, "No model exists for given keyphrase Id " + keyphraseId); 383 return STATUS_ERROR; 384 } 385 386 if (DBG) { 387 Slog.d(TAG, "stopRecognition for keyphraseId=" + keyphraseId + ", callback =" + 388 callback.asBinder()); 389 Slog.d(TAG, "current callback=" + (modelData == null ? "null" : 390 modelData.getCallback().asBinder())); 391 } 392 int status = stopRecognition(modelData, callback); 393 if (status != SoundTrigger.STATUS_OK) { 394 return status; 395 } 396 397 return status; 398 } 399 } 400 401 /** 402 * Stops recognition for the given ModelData instance. 403 * 404 * @param modelData Instance of {@link #ModelData} sound model. 405 * @param callback The callback for the recognition events related to the given keyphrase. 406 * @return One of {@link #STATUS_ERROR} or {@link #STATUS_OK}. 407 */ stopRecognition(ModelData modelData, IRecognitionStatusCallback callback)408 private int stopRecognition(ModelData modelData, IRecognitionStatusCallback callback) { 409 synchronized (mLock) { 410 if (callback == null) { 411 return STATUS_ERROR; 412 } 413 if (mModuleProperties == null || mModule == null) { 414 Slog.w(TAG, "Attempting stopRecognition without the capability"); 415 return STATUS_ERROR; 416 } 417 418 IRecognitionStatusCallback currentCallback = modelData.getCallback(); 419 if (modelData == null || currentCallback == null || 420 (!modelData.isRequested() && !modelData.isModelStarted())) { 421 // startGenericRecognition hasn't been called or it failed. 422 Slog.w(TAG, "Attempting stopRecognition without a successful startRecognition"); 423 return STATUS_ERROR; 424 } 425 426 if (currentCallback.asBinder() != callback.asBinder()) { 427 // We don't allow a different listener to stop the recognition than the one 428 // that started it. 429 Slog.w(TAG, "Attempting stopRecognition for another recognition"); 430 return STATUS_ERROR; 431 } 432 433 // Request stop recognition via the update() method. 434 modelData.setRequested(false); 435 int status = updateRecognitionLocked(modelData, isRecognitionAllowed(), 436 false /* don't notify for synchronous calls */); 437 if (status != SoundTrigger.STATUS_OK) { 438 return status; 439 } 440 441 // We leave the sound model loaded but not started, this helps us when we start back. 442 // Also clear the internal state once the recognition has been stopped. 443 modelData.setLoaded(); 444 modelData.clearCallback(); 445 modelData.setRecognitionConfig(null); 446 447 if (!computeRecognitionRunningLocked()) { 448 internalClearGlobalStateLocked(); 449 } 450 451 return status; 452 } 453 } 454 455 // Stop a previously started model if it was started. Optionally, unload if the previous model 456 // is stale and is about to be replaced. 457 // Needs to be called with the mLock held. tryStopAndUnloadLocked(ModelData modelData, boolean stopModel, boolean unloadModel)458 private int tryStopAndUnloadLocked(ModelData modelData, boolean stopModel, 459 boolean unloadModel) { 460 int status = STATUS_OK; 461 if (modelData.isModelNotLoaded()) { 462 return status; 463 } 464 if (stopModel && modelData.isModelStarted()) { 465 status = stopRecognitionLocked(modelData, 466 false /* don't notify for synchronous calls */); 467 if (status != SoundTrigger.STATUS_OK) { 468 Slog.w(TAG, "stopRecognition failed: " + status); 469 return status; 470 } 471 } 472 473 if (unloadModel && modelData.isModelLoaded()) { 474 Slog.d(TAG, "Unloading previously loaded stale model."); 475 status = mModule.unloadSoundModel(modelData.getHandle()); 476 MetricsLogger.count(mContext, "sth_unloading_stale_model", 1); 477 if (status != SoundTrigger.STATUS_OK) { 478 Slog.w(TAG, "unloadSoundModel call failed with " + status); 479 } else { 480 // Clear the ModelData state if successful. 481 modelData.clearState(); 482 } 483 } 484 return status; 485 } 486 getModuleProperties()487 public ModuleProperties getModuleProperties() { 488 return mModuleProperties; 489 } 490 unloadKeyphraseSoundModel(int keyphraseId)491 int unloadKeyphraseSoundModel(int keyphraseId) { 492 synchronized (mLock) { 493 MetricsLogger.count(mContext, "sth_unload_keyphrase_sound_model", 1); 494 ModelData modelData = getKeyphraseModelDataLocked(keyphraseId); 495 if (mModule == null || modelData == null || modelData.getHandle() == INVALID_VALUE || 496 !modelData.isKeyphraseModel()) { 497 return STATUS_ERROR; 498 } 499 500 // Stop recognition if it's the current one. 501 modelData.setRequested(false); 502 int status = updateRecognitionLocked(modelData, isRecognitionAllowed(), 503 false /* don't notify */); 504 if (status != SoundTrigger.STATUS_OK) { 505 Slog.w(TAG, "Stop recognition failed for keyphrase ID:" + status); 506 } 507 508 status = mModule.unloadSoundModel(modelData.getHandle()); 509 if (status != SoundTrigger.STATUS_OK) { 510 Slog.w(TAG, "unloadKeyphraseSoundModel call failed with " + status); 511 } 512 513 // Remove it from existence. 514 removeKeyphraseModelLocked(keyphraseId); 515 return status; 516 } 517 } 518 unloadGenericSoundModel(UUID modelId)519 int unloadGenericSoundModel(UUID modelId) { 520 synchronized (mLock) { 521 MetricsLogger.count(mContext, "sth_unload_generic_sound_model", 1); 522 if (modelId == null || mModule == null) { 523 return STATUS_ERROR; 524 } 525 ModelData modelData = mModelDataMap.get(modelId); 526 if (modelData == null || !modelData.isGenericModel()) { 527 Slog.w(TAG, "Unload error: Attempting unload invalid generic model with id:" + 528 modelId); 529 return STATUS_ERROR; 530 } 531 if (!modelData.isModelLoaded()) { 532 // Nothing to do here. 533 Slog.i(TAG, "Unload: Given generic model is not loaded:" + modelId); 534 return STATUS_OK; 535 } 536 if (modelData.isModelStarted()) { 537 int status = stopRecognitionLocked(modelData, 538 false /* don't notify for synchronous calls */); 539 if (status != SoundTrigger.STATUS_OK) { 540 Slog.w(TAG, "stopGenericRecognition failed: " + status); 541 } 542 } 543 544 int status = mModule.unloadSoundModel(modelData.getHandle()); 545 if (status != SoundTrigger.STATUS_OK) { 546 Slog.w(TAG, "unloadGenericSoundModel() call failed with " + status); 547 Slog.w(TAG, "unloadGenericSoundModel() force-marking model as unloaded."); 548 } 549 550 // Remove it from existence. 551 mModelDataMap.remove(modelId); 552 if (DBG) dumpModelStateLocked(); 553 return status; 554 } 555 } 556 557 //---- SoundTrigger.StatusListener methods 558 @Override onRecognition(RecognitionEvent event)559 public void onRecognition(RecognitionEvent event) { 560 if (event == null) { 561 Slog.w(TAG, "Null recognition event!"); 562 return; 563 } 564 565 if (!(event instanceof KeyphraseRecognitionEvent) && 566 !(event instanceof GenericRecognitionEvent)) { 567 Slog.w(TAG, "Invalid recognition event type (not one of generic or keyphrase)!"); 568 return; 569 } 570 571 if (DBG) Slog.d(TAG, "onRecognition: " + event); 572 synchronized (mLock) { 573 switch (event.status) { 574 case SoundTrigger.RECOGNITION_STATUS_ABORT: 575 onRecognitionAbortLocked(event); 576 break; 577 case SoundTrigger.RECOGNITION_STATUS_FAILURE: 578 // Fire failures to all listeners since it's not tied to a keyphrase. 579 onRecognitionFailureLocked(); 580 break; 581 case SoundTrigger.RECOGNITION_STATUS_SUCCESS: 582 if (isKeyphraseRecognitionEvent(event)) { 583 onKeyphraseRecognitionSuccessLocked((KeyphraseRecognitionEvent) event); 584 } else { 585 onGenericRecognitionSuccessLocked((GenericRecognitionEvent) event); 586 } 587 break; 588 } 589 } 590 } 591 isKeyphraseRecognitionEvent(RecognitionEvent event)592 private boolean isKeyphraseRecognitionEvent(RecognitionEvent event) { 593 return event instanceof KeyphraseRecognitionEvent; 594 } 595 onGenericRecognitionSuccessLocked(GenericRecognitionEvent event)596 private void onGenericRecognitionSuccessLocked(GenericRecognitionEvent event) { 597 MetricsLogger.count(mContext, "sth_generic_recognition_event", 1); 598 if (event.status != SoundTrigger.RECOGNITION_STATUS_SUCCESS) { 599 return; 600 } 601 ModelData model = getModelDataForLocked(event.soundModelHandle); 602 if (model == null || !model.isGenericModel()) { 603 Slog.w(TAG, "Generic recognition event: Model does not exist for handle: " + 604 event.soundModelHandle); 605 return; 606 } 607 608 IRecognitionStatusCallback callback = model.getCallback(); 609 if (callback == null) { 610 Slog.w(TAG, "Generic recognition event: Null callback for model handle: " + 611 event.soundModelHandle); 612 return; 613 } 614 615 model.setStopped(); 616 try { 617 callback.onGenericSoundTriggerDetected((GenericRecognitionEvent) event); 618 } catch (DeadObjectException e) { 619 forceStopAndUnloadModelLocked(model, e); 620 return; 621 } catch (RemoteException e) { 622 Slog.w(TAG, "RemoteException in onGenericSoundTriggerDetected", e); 623 } 624 625 RecognitionConfig config = model.getRecognitionConfig(); 626 if (config == null) { 627 Slog.w(TAG, "Generic recognition event: Null RecognitionConfig for model handle: " + 628 event.soundModelHandle); 629 return; 630 } 631 632 model.setRequested(config.allowMultipleTriggers); 633 // TODO: Remove this block if the lower layer supports multiple triggers. 634 if (model.isRequested()) { 635 updateRecognitionLocked(model, isRecognitionAllowed() /* isAllowed */, 636 true /* notify */); 637 } 638 } 639 640 @Override onSoundModelUpdate(SoundModelEvent event)641 public void onSoundModelUpdate(SoundModelEvent event) { 642 if (event == null) { 643 Slog.w(TAG, "Invalid sound model event!"); 644 return; 645 } 646 if (DBG) Slog.d(TAG, "onSoundModelUpdate: " + event); 647 synchronized (mLock) { 648 MetricsLogger.count(mContext, "sth_sound_model_updated", 1); 649 onSoundModelUpdatedLocked(event); 650 } 651 } 652 653 @Override onServiceStateChange(int state)654 public void onServiceStateChange(int state) { 655 if (DBG) Slog.d(TAG, "onServiceStateChange, state: " + state); 656 synchronized (mLock) { 657 onServiceStateChangedLocked(SoundTrigger.SERVICE_STATE_DISABLED == state); 658 } 659 } 660 661 @Override onServiceDied()662 public void onServiceDied() { 663 Slog.e(TAG, "onServiceDied!!"); 664 MetricsLogger.count(mContext, "sth_service_died", 1); 665 synchronized (mLock) { 666 onServiceDiedLocked(); 667 } 668 } 669 onCallStateChangedLocked(boolean callActive)670 private void onCallStateChangedLocked(boolean callActive) { 671 if (mCallActive == callActive) { 672 // We consider multiple call states as being active 673 // so we check if something really changed or not here. 674 return; 675 } 676 mCallActive = callActive; 677 updateAllRecognitionsLocked(true /* notify */); 678 } 679 onPowerSaveModeChangedLocked(boolean isPowerSaveMode)680 private void onPowerSaveModeChangedLocked(boolean isPowerSaveMode) { 681 if (mIsPowerSaveMode == isPowerSaveMode) { 682 return; 683 } 684 mIsPowerSaveMode = isPowerSaveMode; 685 updateAllRecognitionsLocked(true /* notify */); 686 } 687 onSoundModelUpdatedLocked(SoundModelEvent event)688 private void onSoundModelUpdatedLocked(SoundModelEvent event) { 689 // TODO: Handle sound model update here. 690 } 691 onServiceStateChangedLocked(boolean disabled)692 private void onServiceStateChangedLocked(boolean disabled) { 693 if (disabled == mServiceDisabled) { 694 return; 695 } 696 mServiceDisabled = disabled; 697 updateAllRecognitionsLocked(true /* notify */); 698 } 699 onRecognitionAbortLocked(RecognitionEvent event)700 private void onRecognitionAbortLocked(RecognitionEvent event) { 701 Slog.w(TAG, "Recognition aborted"); 702 MetricsLogger.count(mContext, "sth_recognition_aborted", 1); 703 ModelData modelData = getModelDataForLocked(event.soundModelHandle); 704 if (modelData != null && modelData.isModelStarted()) { 705 modelData.setStopped(); 706 try { 707 modelData.getCallback().onRecognitionPaused(); 708 } catch (DeadObjectException e) { 709 forceStopAndUnloadModelLocked(modelData, e); 710 } catch (RemoteException e) { 711 Slog.w(TAG, "RemoteException in onRecognitionPaused", e); 712 } 713 } 714 } 715 onRecognitionFailureLocked()716 private void onRecognitionFailureLocked() { 717 Slog.w(TAG, "Recognition failure"); 718 MetricsLogger.count(mContext, "sth_recognition_failure_event", 1); 719 try { 720 sendErrorCallbacksToAllLocked(STATUS_ERROR); 721 } finally { 722 internalClearModelStateLocked(); 723 internalClearGlobalStateLocked(); 724 } 725 } 726 getKeyphraseIdFromEvent(KeyphraseRecognitionEvent event)727 private int getKeyphraseIdFromEvent(KeyphraseRecognitionEvent event) { 728 if (event == null) { 729 Slog.w(TAG, "Null RecognitionEvent received."); 730 return INVALID_VALUE; 731 } 732 KeyphraseRecognitionExtra[] keyphraseExtras = 733 ((KeyphraseRecognitionEvent) event).keyphraseExtras; 734 if (keyphraseExtras == null || keyphraseExtras.length == 0) { 735 Slog.w(TAG, "Invalid keyphrase recognition event!"); 736 return INVALID_VALUE; 737 } 738 // TODO: Handle more than one keyphrase extras. 739 return keyphraseExtras[0].id; 740 } 741 onKeyphraseRecognitionSuccessLocked(KeyphraseRecognitionEvent event)742 private void onKeyphraseRecognitionSuccessLocked(KeyphraseRecognitionEvent event) { 743 Slog.i(TAG, "Recognition success"); 744 MetricsLogger.count(mContext, "sth_keyphrase_recognition_event", 1); 745 int keyphraseId = getKeyphraseIdFromEvent(event); 746 ModelData modelData = getKeyphraseModelDataLocked(keyphraseId); 747 748 if (modelData == null || !modelData.isKeyphraseModel()) { 749 Slog.e(TAG, "Keyphase model data does not exist for ID:" + keyphraseId); 750 return; 751 } 752 753 if (modelData.getCallback() == null) { 754 Slog.w(TAG, "Received onRecognition event without callback for keyphrase model."); 755 return; 756 } 757 modelData.setStopped(); 758 759 try { 760 modelData.getCallback().onKeyphraseDetected((KeyphraseRecognitionEvent) event); 761 } catch (DeadObjectException e) { 762 forceStopAndUnloadModelLocked(modelData, e); 763 return; 764 } catch (RemoteException e) { 765 Slog.w(TAG, "RemoteException in onKeyphraseDetected", e); 766 } 767 768 RecognitionConfig config = modelData.getRecognitionConfig(); 769 if (config != null) { 770 // Whether we should continue by starting this again. 771 modelData.setRequested(config.allowMultipleTriggers); 772 } 773 // TODO: Remove this block if the lower layer supports multiple triggers. 774 if (modelData.isRequested()) { 775 updateRecognitionLocked(modelData, isRecognitionAllowed(), true /* notify */); 776 } 777 } 778 updateAllRecognitionsLocked(boolean notify)779 private void updateAllRecognitionsLocked(boolean notify) { 780 boolean isAllowed = isRecognitionAllowed(); 781 // updateRecognitionLocked can possibly update the list of models 782 ArrayList<ModelData> modelDatas = new ArrayList<ModelData>(mModelDataMap.values()); 783 for (ModelData modelData : modelDatas) { 784 updateRecognitionLocked(modelData, isAllowed, notify); 785 } 786 } 787 updateRecognitionLocked(ModelData model, boolean isAllowed, boolean notify)788 private int updateRecognitionLocked(ModelData model, boolean isAllowed, 789 boolean notify) { 790 boolean start = model.isRequested() && isAllowed; 791 if (start == model.isModelStarted()) { 792 // No-op. 793 return STATUS_OK; 794 } 795 if (start) { 796 return startRecognitionLocked(model, notify); 797 } else { 798 return stopRecognitionLocked(model, notify); 799 } 800 } 801 onServiceDiedLocked()802 private void onServiceDiedLocked() { 803 try { 804 MetricsLogger.count(mContext, "sth_service_died", 1); 805 sendErrorCallbacksToAllLocked(SoundTrigger.STATUS_DEAD_OBJECT); 806 } finally { 807 internalClearModelStateLocked(); 808 internalClearGlobalStateLocked(); 809 if (mModule != null) { 810 mModule.detach(); 811 mModule = null; 812 } 813 } 814 } 815 816 // internalClearGlobalStateLocked() cleans up the telephony and power save listeners. internalClearGlobalStateLocked()817 private void internalClearGlobalStateLocked() { 818 // Unregister from call state changes. 819 mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE); 820 821 // Unregister from power save mode changes. 822 if (mPowerSaveModeListener != null) { 823 mContext.unregisterReceiver(mPowerSaveModeListener); 824 mPowerSaveModeListener = null; 825 } 826 } 827 828 // Clears state for all models (generic and keyphrase). internalClearModelStateLocked()829 private void internalClearModelStateLocked() { 830 for (ModelData modelData : mModelDataMap.values()) { 831 modelData.clearState(); 832 } 833 } 834 835 class MyCallStateListener extends PhoneStateListener { 836 @Override onCallStateChanged(int state, String arg1)837 public void onCallStateChanged(int state, String arg1) { 838 if (DBG) Slog.d(TAG, "onCallStateChanged: " + state); 839 synchronized (mLock) { 840 onCallStateChangedLocked(TelephonyManager.CALL_STATE_IDLE != state); 841 } 842 } 843 } 844 845 class PowerSaveModeListener extends BroadcastReceiver { 846 @Override onReceive(Context context, Intent intent)847 public void onReceive(Context context, Intent intent) { 848 if (!PowerManager.ACTION_POWER_SAVE_MODE_CHANGED.equals(intent.getAction())) { 849 return; 850 } 851 boolean active = mPowerManager.getPowerSaveState(ServiceType.SOUND) 852 .batterySaverEnabled; 853 if (DBG) Slog.d(TAG, "onPowerSaveModeChanged: " + active); 854 synchronized (mLock) { 855 onPowerSaveModeChangedLocked(active); 856 } 857 } 858 } 859 dump(FileDescriptor fd, PrintWriter pw, String[] args)860 void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 861 synchronized (mLock) { 862 pw.print(" module properties="); 863 pw.println(mModuleProperties == null ? "null" : mModuleProperties); 864 865 pw.print(" call active="); pw.println(mCallActive); 866 pw.print(" power save mode active="); pw.println(mIsPowerSaveMode); 867 pw.print(" service disabled="); pw.println(mServiceDisabled); 868 } 869 } 870 initializeTelephonyAndPowerStateListeners()871 private void initializeTelephonyAndPowerStateListeners() { 872 // Get the current call state synchronously for the first recognition. 873 mCallActive = mTelephonyManager.getCallState() != TelephonyManager.CALL_STATE_IDLE; 874 875 // Register for call state changes when the first call to start recognition occurs. 876 mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE); 877 878 // Register for power saver mode changes when the first call to start recognition 879 // occurs. 880 if (mPowerSaveModeListener == null) { 881 mPowerSaveModeListener = new PowerSaveModeListener(); 882 mContext.registerReceiver(mPowerSaveModeListener, 883 new IntentFilter(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED)); 884 } 885 mIsPowerSaveMode = mPowerManager.getPowerSaveState(ServiceType.SOUND) 886 .batterySaverEnabled; 887 } 888 889 // Sends an error callback to all models with a valid registered callback. sendErrorCallbacksToAllLocked(int errorCode)890 private void sendErrorCallbacksToAllLocked(int errorCode) { 891 for (ModelData modelData : mModelDataMap.values()) { 892 IRecognitionStatusCallback callback = modelData.getCallback(); 893 if (callback != null) { 894 try { 895 callback.onError(errorCode); 896 } catch (RemoteException e) { 897 Slog.w(TAG, "RemoteException sendErrorCallbacksToAllLocked for model handle " + 898 modelData.getHandle(), e); 899 } 900 } 901 } 902 } 903 forceStopAndUnloadModelLocked(ModelData modelData, Exception exception)904 private void forceStopAndUnloadModelLocked(ModelData modelData, Exception exception) { 905 if (exception != null) { 906 Slog.e(TAG, "forceStopAndUnloadModel", exception); 907 } 908 if (modelData.isModelStarted()) { 909 Slog.d(TAG, "Stopping previously started dangling model " + modelData.getHandle()); 910 if (mModule.stopRecognition(modelData.getHandle()) != STATUS_OK) { 911 modelData.setStopped(); 912 modelData.setRequested(false); 913 } else { 914 Slog.e(TAG, "Failed to stop model " + modelData.getHandle()); 915 } 916 } 917 if (modelData.isModelLoaded()) { 918 Slog.d(TAG, "Unloading previously loaded dangling model " + modelData.getHandle()); 919 if (mModule.unloadSoundModel(modelData.getHandle()) == STATUS_OK) { 920 // Remove the model data from existence. 921 mModelDataMap.remove(modelData.getModelId()); 922 Iterator it = mKeyphraseUuidMap.entrySet().iterator(); 923 while (it.hasNext()) { 924 Map.Entry pair = (Map.Entry) it.next(); 925 if (pair.getValue().equals(modelData.getModelId())) { 926 it.remove(); 927 } 928 } 929 modelData.clearState(); 930 } else { 931 Slog.e(TAG, "Failed to unload model " + modelData.getHandle()); 932 } 933 } 934 } 935 getOrCreateGenericModelDataLocked(UUID modelId)936 private ModelData getOrCreateGenericModelDataLocked(UUID modelId) { 937 ModelData modelData = mModelDataMap.get(modelId); 938 if (modelData == null) { 939 modelData = ModelData.createGenericModelData(modelId); 940 mModelDataMap.put(modelId, modelData); 941 } else if (!modelData.isGenericModel()) { 942 Slog.e(TAG, "UUID already used for non-generic model."); 943 return null; 944 } 945 return modelData; 946 } 947 removeKeyphraseModelLocked(int keyphraseId)948 private void removeKeyphraseModelLocked(int keyphraseId) { 949 UUID uuid = mKeyphraseUuidMap.get(keyphraseId); 950 if (uuid == null) { 951 return; 952 } 953 mModelDataMap.remove(uuid); 954 mKeyphraseUuidMap.remove(keyphraseId); 955 } 956 getKeyphraseModelDataLocked(int keyphraseId)957 private ModelData getKeyphraseModelDataLocked(int keyphraseId) { 958 UUID uuid = mKeyphraseUuidMap.get(keyphraseId); 959 if (uuid == null) { 960 return null; 961 } 962 return mModelDataMap.get(uuid); 963 } 964 965 // Use this to create a new ModelData entry for a keyphrase Id. It will overwrite existing 966 // mapping if one exists. createKeyphraseModelDataLocked(UUID modelId, int keyphraseId)967 private ModelData createKeyphraseModelDataLocked(UUID modelId, int keyphraseId) { 968 mKeyphraseUuidMap.remove(keyphraseId); 969 mModelDataMap.remove(modelId); 970 mKeyphraseUuidMap.put(keyphraseId, modelId); 971 ModelData modelData = ModelData.createKeyphraseModelData(modelId); 972 mModelDataMap.put(modelId, modelData); 973 return modelData; 974 } 975 976 // Instead of maintaining a second hashmap of modelHandle -> ModelData, we just 977 // iterate through to find the right object (since we don't expect 100s of models 978 // to be stored). getModelDataForLocked(int modelHandle)979 private ModelData getModelDataForLocked(int modelHandle) { 980 // Fetch ModelData object corresponding to the model handle. 981 for (ModelData model : mModelDataMap.values()) { 982 if (model.getHandle() == modelHandle) { 983 return model; 984 } 985 } 986 return null; 987 } 988 989 // Whether we are allowed to run any recognition at all. The conditions that let us run 990 // a recognition include: no active phone call or not being in a power save mode. Also, 991 // the native service should be enabled. isRecognitionAllowed()992 private boolean isRecognitionAllowed() { 993 return !mCallActive && !mServiceDisabled && !mIsPowerSaveMode; 994 } 995 996 // A single routine that implements the start recognition logic for both generic and keyphrase 997 // models. startRecognitionLocked(ModelData modelData, boolean notify)998 private int startRecognitionLocked(ModelData modelData, boolean notify) { 999 IRecognitionStatusCallback callback = modelData.getCallback(); 1000 int handle = modelData.getHandle(); 1001 RecognitionConfig config = modelData.getRecognitionConfig(); 1002 if (callback == null || handle == INVALID_VALUE || config == null) { 1003 // Nothing to do here. 1004 Slog.w(TAG, "startRecognition: Bad data passed in."); 1005 MetricsLogger.count(mContext, "sth_start_recognition_error", 1); 1006 return STATUS_ERROR; 1007 } 1008 1009 if (!isRecognitionAllowed()) { 1010 // Nothing to do here. 1011 Slog.w(TAG, "startRecognition requested but not allowed."); 1012 MetricsLogger.count(mContext, "sth_start_recognition_not_allowed", 1); 1013 return STATUS_OK; 1014 } 1015 1016 int status = mModule.startRecognition(handle, config); 1017 if (status != SoundTrigger.STATUS_OK) { 1018 Slog.w(TAG, "startRecognition failed with " + status); 1019 MetricsLogger.count(mContext, "sth_start_recognition_error", 1); 1020 // Notify of error if needed. 1021 if (notify) { 1022 try { 1023 callback.onError(status); 1024 } catch (DeadObjectException e) { 1025 forceStopAndUnloadModelLocked(modelData, e); 1026 } catch (RemoteException e) { 1027 Slog.w(TAG, "RemoteException in onError", e); 1028 } 1029 } 1030 } else { 1031 Slog.i(TAG, "startRecognition successful."); 1032 MetricsLogger.count(mContext, "sth_start_recognition_success", 1); 1033 modelData.setStarted(); 1034 // Notify of resume if needed. 1035 if (notify) { 1036 try { 1037 callback.onRecognitionResumed(); 1038 } catch (DeadObjectException e) { 1039 forceStopAndUnloadModelLocked(modelData, e); 1040 } catch (RemoteException e) { 1041 Slog.w(TAG, "RemoteException in onRecognitionResumed", e); 1042 } 1043 } 1044 } 1045 if (DBG) { 1046 Slog.d(TAG, "Model being started :" + modelData.toString()); 1047 } 1048 return status; 1049 } 1050 stopRecognitionLocked(ModelData modelData, boolean notify)1051 private int stopRecognitionLocked(ModelData modelData, boolean notify) { 1052 IRecognitionStatusCallback callback = modelData.getCallback(); 1053 1054 // Stop recognition. 1055 int status = STATUS_OK; 1056 1057 status = mModule.stopRecognition(modelData.getHandle()); 1058 1059 if (status != SoundTrigger.STATUS_OK) { 1060 Slog.w(TAG, "stopRecognition call failed with " + status); 1061 MetricsLogger.count(mContext, "sth_stop_recognition_error", 1); 1062 if (notify) { 1063 try { 1064 callback.onError(status); 1065 } catch (DeadObjectException e) { 1066 forceStopAndUnloadModelLocked(modelData, e); 1067 } catch (RemoteException e) { 1068 Slog.w(TAG, "RemoteException in onError", e); 1069 } 1070 } 1071 } else { 1072 modelData.setStopped(); 1073 MetricsLogger.count(mContext, "sth_stop_recognition_success", 1); 1074 // Notify of pause if needed. 1075 if (notify) { 1076 try { 1077 callback.onRecognitionPaused(); 1078 } catch (DeadObjectException e) { 1079 forceStopAndUnloadModelLocked(modelData, e); 1080 } catch (RemoteException e) { 1081 Slog.w(TAG, "RemoteException in onRecognitionPaused", e); 1082 } 1083 } 1084 } 1085 if (DBG) { 1086 Slog.d(TAG, "Model being stopped :" + modelData.toString()); 1087 } 1088 return status; 1089 } 1090 dumpModelStateLocked()1091 private void dumpModelStateLocked() { 1092 for (UUID modelId : mModelDataMap.keySet()) { 1093 ModelData modelData = mModelDataMap.get(modelId); 1094 Slog.i(TAG, "Model :" + modelData.toString()); 1095 } 1096 } 1097 1098 // Computes whether we have any recognition running at all (voice or generic). Sets 1099 // the mRecognitionRunning variable with the result. computeRecognitionRunningLocked()1100 private boolean computeRecognitionRunningLocked() { 1101 if (mModuleProperties == null || mModule == null) { 1102 mRecognitionRunning = false; 1103 return mRecognitionRunning; 1104 } 1105 for (ModelData modelData : mModelDataMap.values()) { 1106 if (modelData.isModelStarted()) { 1107 mRecognitionRunning = true; 1108 return mRecognitionRunning; 1109 } 1110 } 1111 mRecognitionRunning = false; 1112 return mRecognitionRunning; 1113 } 1114 1115 // This class encapsulates the callbacks, state, handles and any other information that 1116 // represents a model. 1117 private static class ModelData { 1118 // Model not loaded (and hence not started). 1119 static final int MODEL_NOTLOADED = 0; 1120 1121 // Loaded implies model was successfully loaded. Model not started yet. 1122 static final int MODEL_LOADED = 1; 1123 1124 // Started implies model was successfully loaded and start was called. 1125 static final int MODEL_STARTED = 2; 1126 1127 // One of MODEL_NOTLOADED, MODEL_LOADED, MODEL_STARTED (which implies loaded). 1128 private int mModelState; 1129 private UUID mModelId; 1130 1131 // mRequested captures the explicit intent that a start was requested for this model. We 1132 // continue to capture and retain this state even after the model gets started, so that we 1133 // know when a model gets stopped due to "other" reasons, that we should start it again. 1134 // This was the intended behavior of the "mRequested" variable in the previous version of 1135 // this code that we are replicating here. 1136 // 1137 // The "other" reasons include power save, abort being called from the lower layer (due 1138 // to concurrent capture not being supported) and phone call state. Once we recover from 1139 // these transient disruptions, we would start such models again where mRequested == true. 1140 // Thus, mRequested gets reset only when there is an explicit intent to stop the model 1141 // coming from the SoundTriggerService layer that uses this class (and thus eventually 1142 // from the app that manages this model). 1143 private boolean mRequested = false; 1144 1145 // One of SoundModel.TYPE_GENERIC or SoundModel.TYPE_KEYPHRASE. Initially set 1146 // to SoundModel.TYPE_UNKNOWN; 1147 private int mModelType = SoundModel.TYPE_UNKNOWN; 1148 1149 private IRecognitionStatusCallback mCallback = null; 1150 private RecognitionConfig mRecognitionConfig = null; 1151 1152 // Model handle is an integer used by the HAL as an identifier for sound 1153 // models. 1154 private int mModelHandle = INVALID_VALUE; 1155 1156 // The SoundModel instance, one of KeyphraseSoundModel or GenericSoundModel. 1157 private SoundModel mSoundModel = null; 1158 ModelData(UUID modelId, int modelType)1159 private ModelData(UUID modelId, int modelType) { 1160 mModelId = modelId; 1161 // Private constructor, since we require modelType to be one of TYPE_GENERIC, 1162 // TYPE_KEYPHRASE or TYPE_UNKNOWN. 1163 mModelType = modelType; 1164 } 1165 createKeyphraseModelData(UUID modelId)1166 static ModelData createKeyphraseModelData(UUID modelId) { 1167 return new ModelData(modelId, SoundModel.TYPE_KEYPHRASE); 1168 } 1169 createGenericModelData(UUID modelId)1170 static ModelData createGenericModelData(UUID modelId) { 1171 return new ModelData(modelId, SoundModel.TYPE_GENERIC_SOUND); 1172 } 1173 1174 // Note that most of the functionality in this Java class will not work for 1175 // SoundModel.TYPE_UNKNOWN nevertheless we have it since lower layers support it. createModelDataOfUnknownType(UUID modelId)1176 static ModelData createModelDataOfUnknownType(UUID modelId) { 1177 return new ModelData(modelId, SoundModel.TYPE_UNKNOWN); 1178 } 1179 setCallback(IRecognitionStatusCallback callback)1180 synchronized void setCallback(IRecognitionStatusCallback callback) { 1181 mCallback = callback; 1182 } 1183 getCallback()1184 synchronized IRecognitionStatusCallback getCallback() { 1185 return mCallback; 1186 } 1187 isModelLoaded()1188 synchronized boolean isModelLoaded() { 1189 return (mModelState == MODEL_LOADED || mModelState == MODEL_STARTED); 1190 } 1191 isModelNotLoaded()1192 synchronized boolean isModelNotLoaded() { 1193 return mModelState == MODEL_NOTLOADED; 1194 } 1195 setStarted()1196 synchronized void setStarted() { 1197 mModelState = MODEL_STARTED; 1198 } 1199 setStopped()1200 synchronized void setStopped() { 1201 mModelState = MODEL_LOADED; 1202 } 1203 setLoaded()1204 synchronized void setLoaded() { 1205 mModelState = MODEL_LOADED; 1206 } 1207 isModelStarted()1208 synchronized boolean isModelStarted() { 1209 return mModelState == MODEL_STARTED; 1210 } 1211 clearState()1212 synchronized void clearState() { 1213 mModelState = MODEL_NOTLOADED; 1214 mModelHandle = INVALID_VALUE; 1215 mRecognitionConfig = null; 1216 mRequested = false; 1217 mCallback = null; 1218 } 1219 clearCallback()1220 synchronized void clearCallback() { 1221 mCallback = null; 1222 } 1223 setHandle(int handle)1224 synchronized void setHandle(int handle) { 1225 mModelHandle = handle; 1226 } 1227 setRecognitionConfig(RecognitionConfig config)1228 synchronized void setRecognitionConfig(RecognitionConfig config) { 1229 mRecognitionConfig = config; 1230 } 1231 getHandle()1232 synchronized int getHandle() { 1233 return mModelHandle; 1234 } 1235 getModelId()1236 synchronized UUID getModelId() { 1237 return mModelId; 1238 } 1239 getRecognitionConfig()1240 synchronized RecognitionConfig getRecognitionConfig() { 1241 return mRecognitionConfig; 1242 } 1243 1244 // Whether a start recognition was requested. isRequested()1245 synchronized boolean isRequested() { 1246 return mRequested; 1247 } 1248 setRequested(boolean requested)1249 synchronized void setRequested(boolean requested) { 1250 mRequested = requested; 1251 } 1252 setSoundModel(SoundModel soundModel)1253 synchronized void setSoundModel(SoundModel soundModel) { 1254 mSoundModel = soundModel; 1255 } 1256 getSoundModel()1257 synchronized SoundModel getSoundModel() { 1258 return mSoundModel; 1259 } 1260 getModelType()1261 synchronized int getModelType() { 1262 return mModelType; 1263 } 1264 isKeyphraseModel()1265 synchronized boolean isKeyphraseModel() { 1266 return mModelType == SoundModel.TYPE_KEYPHRASE; 1267 } 1268 isGenericModel()1269 synchronized boolean isGenericModel() { 1270 return mModelType == SoundModel.TYPE_GENERIC_SOUND; 1271 } 1272 stateToString()1273 synchronized String stateToString() { 1274 switch(mModelState) { 1275 case MODEL_NOTLOADED: return "NOT_LOADED"; 1276 case MODEL_LOADED: return "LOADED"; 1277 case MODEL_STARTED: return "STARTED"; 1278 } 1279 return "Unknown state"; 1280 } 1281 requestedToString()1282 synchronized String requestedToString() { 1283 return "Requested: " + (mRequested ? "Yes" : "No"); 1284 } 1285 callbackToString()1286 synchronized String callbackToString() { 1287 return "Callback: " + (mCallback != null ? mCallback.asBinder() : "null"); 1288 } 1289 uuidToString()1290 synchronized String uuidToString() { 1291 return "UUID: " + mModelId; 1292 } 1293 toString()1294 synchronized public String toString() { 1295 return "Handle: " + mModelHandle + "\n" + 1296 "ModelState: " + stateToString() + "\n" + 1297 requestedToString() + "\n" + 1298 callbackToString() + "\n" + 1299 uuidToString() + "\n" + modelTypeToString(); 1300 } 1301 modelTypeToString()1302 synchronized String modelTypeToString() { 1303 String type = null; 1304 switch (mModelType) { 1305 case SoundModel.TYPE_GENERIC_SOUND: type = "Generic"; break; 1306 case SoundModel.TYPE_UNKNOWN: type = "Unknown"; break; 1307 case SoundModel.TYPE_KEYPHRASE: type = "Keyphrase"; break; 1308 } 1309 return "Model type: " + type + "\n"; 1310 } 1311 } 1312 } 1313