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