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