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