SoundTriggerHelper.java revision 55a9b0089c12dd657a8dc8551c79cf3e0f25d7e4
1/** 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.server.soundtrigger; 18 19import android.content.BroadcastReceiver; 20import android.content.Context; 21import android.content.Intent; 22import android.content.IntentFilter; 23import android.hardware.soundtrigger.IRecognitionStatusCallback; 24import android.hardware.soundtrigger.SoundTrigger; 25import android.hardware.soundtrigger.SoundTrigger.Keyphrase; 26import android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionEvent; 27import android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra; 28import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel; 29import android.hardware.soundtrigger.SoundTrigger.ModuleProperties; 30import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig; 31import android.hardware.soundtrigger.SoundTrigger.RecognitionEvent; 32import android.hardware.soundtrigger.SoundTrigger.SoundModelEvent; 33import android.hardware.soundtrigger.SoundTriggerModule; 34import android.os.PowerManager; 35import android.os.RemoteException; 36import android.telephony.PhoneStateListener; 37import android.telephony.TelephonyManager; 38import android.util.Slog; 39 40import java.io.FileDescriptor; 41import java.io.PrintWriter; 42import java.util.ArrayList; 43 44/** 45 * Helper for {@link SoundTrigger} APIs. 46 * Currently this just acts as an abstraction over all SoundTrigger API calls. 47 * 48 * @hide 49 */ 50public class SoundTriggerHelper implements SoundTrigger.StatusListener { 51 static final String TAG = "SoundTriggerHelper"; 52 static final boolean DBG = false; 53 54 /** 55 * Return codes for {@link #startRecognition(int, KeyphraseSoundModel, 56 * IRecognitionStatusCallback, RecognitionConfig)}, 57 * {@link #stopRecognition(int, IRecognitionStatusCallback)} 58 */ 59 public static final int STATUS_ERROR = SoundTrigger.STATUS_ERROR; 60 public static final int STATUS_OK = SoundTrigger.STATUS_OK; 61 62 private static final int INVALID_VALUE = Integer.MIN_VALUE; 63 64 /** The {@link ModuleProperties} for the system, or null if none exists. */ 65 final ModuleProperties moduleProperties; 66 67 /** The properties for the DSP module */ 68 private SoundTriggerModule mModule; 69 private final Object mLock = new Object(); 70 private final Context mContext; 71 private final TelephonyManager mTelephonyManager; 72 private final PhoneStateListener mPhoneStateListener; 73 private final PowerManager mPowerManager; 74 75 // TODO: Since many layers currently only deal with one recognition 76 // we simplify things by assuming one listener here too. 77 private IRecognitionStatusCallback mActiveListener; 78 private int mKeyphraseId = INVALID_VALUE; 79 private int mCurrentSoundModelHandle = INVALID_VALUE; 80 private KeyphraseSoundModel mCurrentSoundModel = null; 81 // FIXME: Ideally this should not be stored if allowMultipleTriggers happens at a lower layer. 82 private RecognitionConfig mRecognitionConfig = null; 83 private boolean mRequested = false; 84 private boolean mCallActive = false; 85 private boolean mIsPowerSaveMode = false; 86 // Indicates if the native sound trigger service is disabled or not. 87 // This is an indirect indication of the microphone being open in some other application. 88 private boolean mServiceDisabled = false; 89 private boolean mStarted = false; 90 private boolean mRecognitionAborted = false; 91 private PowerSaveModeListener mPowerSaveModeListener; 92 93 SoundTriggerHelper(Context context) { 94 ArrayList <ModuleProperties> modules = new ArrayList<>(); 95 int status = SoundTrigger.listModules(modules); 96 mContext = context; 97 mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); 98 mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE); 99 mPhoneStateListener = new MyCallStateListener(); 100 if (status != SoundTrigger.STATUS_OK || modules.size() == 0) { 101 Slog.w(TAG, "listModules status=" + status + ", # of modules=" + modules.size()); 102 moduleProperties = null; 103 mModule = null; 104 } else { 105 // TODO: Figure out how to determine which module corresponds to the DSP hardware. 106 moduleProperties = modules.get(0); 107 } 108 } 109 110 /** 111 * Starts recognition for the given keyphraseId. 112 * 113 * @param keyphraseId The identifier of the keyphrase for which 114 * the recognition is to be started. 115 * @param soundModel The sound model to use for recognition. 116 * @param listener The listener for the recognition events related to the given keyphrase. 117 * @return One of {@link #STATUS_ERROR} or {@link #STATUS_OK}. 118 */ 119 int startRecognition(int keyphraseId, 120 KeyphraseSoundModel soundModel, 121 IRecognitionStatusCallback listener, 122 RecognitionConfig recognitionConfig) { 123 if (soundModel == null || listener == null || recognitionConfig == null) { 124 return STATUS_ERROR; 125 } 126 127 synchronized (mLock) { 128 if (DBG) { 129 Slog.d(TAG, "startRecognition for keyphraseId=" + keyphraseId 130 + " soundModel=" + soundModel + ", listener=" + listener.asBinder() 131 + ", recognitionConfig=" + recognitionConfig); 132 Slog.d(TAG, "moduleProperties=" + moduleProperties); 133 Slog.d(TAG, "current listener=" 134 + (mActiveListener == null ? "null" : mActiveListener.asBinder())); 135 Slog.d(TAG, "current SoundModel handle=" + mCurrentSoundModelHandle); 136 Slog.d(TAG, "current SoundModel UUID=" 137 + (mCurrentSoundModel == null ? null : mCurrentSoundModel.uuid)); 138 } 139 140 if (!mStarted) { 141 // Get the current call state synchronously for the first recognition. 142 mCallActive = mTelephonyManager.getCallState() != TelephonyManager.CALL_STATE_IDLE; 143 // Register for call state changes when the first call to start recognition occurs. 144 mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE); 145 146 // Register for power saver mode changes when the first call to start recognition 147 // occurs. 148 if (mPowerSaveModeListener == null) { 149 mPowerSaveModeListener = new PowerSaveModeListener(); 150 mContext.registerReceiver(mPowerSaveModeListener, 151 new IntentFilter(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED)); 152 } 153 mIsPowerSaveMode = mPowerManager.isPowerSaveMode(); 154 } 155 156 if (moduleProperties == null) { 157 Slog.w(TAG, "Attempting startRecognition without the capability"); 158 return STATUS_ERROR; 159 } 160 if (mModule == null) { 161 mModule = SoundTrigger.attachModule(moduleProperties.id, this, null); 162 if (mModule == null) { 163 Slog.w(TAG, "startRecognition cannot attach to sound trigger module"); 164 return STATUS_ERROR; 165 } 166 } 167 168 // Unload the previous model if the current one isn't invalid 169 // and, it's not the same as the new one. 170 // This helps use cache and reuse the model and just start/stop it when necessary. 171 if (mCurrentSoundModelHandle != INVALID_VALUE 172 && !soundModel.equals(mCurrentSoundModel)) { 173 Slog.w(TAG, "Unloading previous sound model"); 174 int status = mModule.unloadSoundModel(mCurrentSoundModelHandle); 175 if (status != SoundTrigger.STATUS_OK) { 176 Slog.w(TAG, "unloadSoundModel call failed with " + status); 177 } 178 internalClearSoundModelLocked(); 179 mStarted = false; 180 } 181 182 // If the previous recognition was by a different listener, 183 // Notify them that it was stopped. 184 if (mActiveListener != null && mActiveListener.asBinder() != listener.asBinder()) { 185 Slog.w(TAG, "Canceling previous recognition"); 186 try { 187 mActiveListener.onError(STATUS_ERROR); 188 } catch (RemoteException e) { 189 Slog.w(TAG, "RemoteException in onDetectionStopped", e); 190 } 191 mActiveListener = null; 192 } 193 194 // Load the sound model if the current one is null. 195 int soundModelHandle = mCurrentSoundModelHandle; 196 if (mCurrentSoundModelHandle == INVALID_VALUE 197 || mCurrentSoundModel == null) { 198 int[] handle = new int[] { INVALID_VALUE }; 199 int status = mModule.loadSoundModel(soundModel, handle); 200 if (status != SoundTrigger.STATUS_OK) { 201 Slog.w(TAG, "loadSoundModel call failed with " + status); 202 return status; 203 } 204 if (handle[0] == INVALID_VALUE) { 205 Slog.w(TAG, "loadSoundModel call returned invalid sound model handle"); 206 return STATUS_ERROR; 207 } 208 soundModelHandle = handle[0]; 209 } else { 210 if (DBG) Slog.d(TAG, "Reusing previously loaded sound model"); 211 } 212 213 // Start the recognition. 214 mRequested = true; 215 mKeyphraseId = keyphraseId; 216 mCurrentSoundModelHandle = soundModelHandle; 217 mCurrentSoundModel = soundModel; 218 mRecognitionConfig = recognitionConfig; 219 // Register the new listener. This replaces the old one. 220 // There can only be a maximum of one active listener at any given time. 221 mActiveListener = listener; 222 223 return updateRecognitionLocked(false /* don't notify for synchronous calls */); 224 } 225 } 226 227 /** 228 * Stops recognition for the given {@link Keyphrase} if a recognition is 229 * currently active. 230 * 231 * @param keyphraseId The identifier of the keyphrase for which 232 * the recognition is to be stopped. 233 * @param listener The listener for the recognition events related to the given keyphrase. 234 * 235 * @return One of {@link #STATUS_ERROR} or {@link #STATUS_OK}. 236 */ 237 int stopRecognition(int keyphraseId, IRecognitionStatusCallback listener) { 238 if (listener == null) { 239 return STATUS_ERROR; 240 } 241 242 synchronized (mLock) { 243 if (DBG) { 244 Slog.d(TAG, "stopRecognition for keyphraseId=" + keyphraseId 245 + ", listener=" + listener.asBinder()); 246 Slog.d(TAG, "current listener=" 247 + (mActiveListener == null ? "null" : mActiveListener.asBinder())); 248 } 249 250 if (moduleProperties == null || mModule == null) { 251 Slog.w(TAG, "Attempting stopRecognition without the capability"); 252 return STATUS_ERROR; 253 } 254 255 if (mActiveListener == null) { 256 // startRecognition hasn't been called or it failed. 257 Slog.w(TAG, "Attempting stopRecognition without a successful startRecognition"); 258 return STATUS_ERROR; 259 } 260 if (mActiveListener.asBinder() != listener.asBinder()) { 261 // We don't allow a different listener to stop the recognition than the one 262 // that started it. 263 Slog.w(TAG, "Attempting stopRecognition for another recognition"); 264 return STATUS_ERROR; 265 } 266 267 // Stop recognition if it's the current one, ignore otherwise. 268 mRequested = false; 269 int status = updateRecognitionLocked(false /* don't notify for synchronous calls */); 270 if (status != SoundTrigger.STATUS_OK) { 271 return status; 272 } 273 274 // We leave the sound model loaded but not started, this helps us when we start 275 // back. 276 // Also clear the internal state once the recognition has been stopped. 277 internalClearStateLocked(); 278 return status; 279 } 280 } 281 282 /** 283 * Stops all recognitions active currently and clears the internal state. 284 */ 285 void stopAllRecognitions() { 286 synchronized (mLock) { 287 if (moduleProperties == null || mModule == null) { 288 return; 289 } 290 291 if (mCurrentSoundModelHandle == INVALID_VALUE) { 292 return; 293 } 294 295 mRequested = false; 296 int status = updateRecognitionLocked(false /* don't notify for synchronous calls */); 297 internalClearStateLocked(); 298 } 299 } 300 301 public ModuleProperties getModuleProperties() { 302 return moduleProperties; 303 } 304 305 //---- SoundTrigger.StatusListener methods 306 @Override 307 public void onRecognition(RecognitionEvent event) { 308 if (event == null || !(event instanceof KeyphraseRecognitionEvent)) { 309 Slog.w(TAG, "Invalid recognition event!"); 310 return; 311 } 312 313 if (DBG) Slog.d(TAG, "onRecognition: " + event); 314 synchronized (mLock) { 315 if (mActiveListener == null) { 316 Slog.w(TAG, "received onRecognition event without any listener for it"); 317 return; 318 } 319 switch (event.status) { 320 // Fire aborts/failures to all listeners since it's not tied to a keyphrase. 321 case SoundTrigger.RECOGNITION_STATUS_ABORT: 322 onRecognitionAbortLocked(); 323 break; 324 case SoundTrigger.RECOGNITION_STATUS_FAILURE: 325 onRecognitionFailureLocked(); 326 break; 327 case SoundTrigger.RECOGNITION_STATUS_SUCCESS: 328 onRecognitionSuccessLocked((KeyphraseRecognitionEvent) event); 329 break; 330 } 331 } 332 } 333 334 @Override 335 public void onSoundModelUpdate(SoundModelEvent event) { 336 if (event == null) { 337 Slog.w(TAG, "Invalid sound model event!"); 338 return; 339 } 340 if (DBG) Slog.d(TAG, "onSoundModelUpdate: " + event); 341 synchronized (mLock) { 342 onSoundModelUpdatedLocked(event); 343 } 344 } 345 346 @Override 347 public void onServiceStateChange(int state) { 348 if (DBG) Slog.d(TAG, "onServiceStateChange, state: " + state); 349 synchronized (mLock) { 350 onServiceStateChangedLocked(SoundTrigger.SERVICE_STATE_DISABLED == state); 351 } 352 } 353 354 @Override 355 public void onServiceDied() { 356 Slog.e(TAG, "onServiceDied!!"); 357 synchronized (mLock) { 358 onServiceDiedLocked(); 359 } 360 } 361 362 private void onCallStateChangedLocked(boolean callActive) { 363 if (mCallActive == callActive) { 364 // We consider multiple call states as being active 365 // so we check if something really changed or not here. 366 return; 367 } 368 mCallActive = callActive; 369 updateRecognitionLocked(true /* notify */); 370 } 371 372 private void onPowerSaveModeChangedLocked(boolean isPowerSaveMode) { 373 if (mIsPowerSaveMode == isPowerSaveMode) { 374 return; 375 } 376 mIsPowerSaveMode = isPowerSaveMode; 377 updateRecognitionLocked(true /* notify */); 378 } 379 380 private void onSoundModelUpdatedLocked(SoundModelEvent event) { 381 // TODO: Handle sound model update here. 382 } 383 384 private void onServiceStateChangedLocked(boolean disabled) { 385 if (disabled == mServiceDisabled) { 386 return; 387 } 388 mServiceDisabled = disabled; 389 updateRecognitionLocked(true /* notify */); 390 } 391 392 private void onRecognitionAbortLocked() { 393 Slog.w(TAG, "Recognition aborted"); 394 // If abort has been called, the hardware has already stopped recognition, so we shouldn't 395 // call it again when we process the state change. 396 mRecognitionAborted = true; 397 } 398 399 private void onRecognitionFailureLocked() { 400 Slog.w(TAG, "Recognition failure"); 401 try { 402 if (mActiveListener != null) { 403 mActiveListener.onError(STATUS_ERROR); 404 } 405 } catch (RemoteException e) { 406 Slog.w(TAG, "RemoteException in onError", e); 407 } finally { 408 internalClearStateLocked(); 409 } 410 } 411 412 private void onRecognitionSuccessLocked(KeyphraseRecognitionEvent event) { 413 Slog.i(TAG, "Recognition success"); 414 KeyphraseRecognitionExtra[] keyphraseExtras = 415 ((KeyphraseRecognitionEvent) event).keyphraseExtras; 416 if (keyphraseExtras == null || keyphraseExtras.length == 0) { 417 Slog.w(TAG, "Invalid keyphrase recognition event!"); 418 return; 419 } 420 // TODO: Handle more than one keyphrase extras. 421 if (mKeyphraseId != keyphraseExtras[0].id) { 422 Slog.w(TAG, "received onRecognition event for a different keyphrase"); 423 return; 424 } 425 426 try { 427 if (mActiveListener != null) { 428 mActiveListener.onDetected((KeyphraseRecognitionEvent) event); 429 } 430 } catch (RemoteException e) { 431 Slog.w(TAG, "RemoteException in onDetected", e); 432 } 433 434 mStarted = false; 435 mRequested = mRecognitionConfig.allowMultipleTriggers; 436 // TODO: Remove this block if the lower layer supports multiple triggers. 437 if (mRequested) { 438 updateRecognitionLocked(true /* notify */); 439 } 440 } 441 442 private void onServiceDiedLocked() { 443 try { 444 if (mActiveListener != null) { 445 mActiveListener.onError(SoundTrigger.STATUS_DEAD_OBJECT); 446 } 447 } catch (RemoteException e) { 448 Slog.w(TAG, "RemoteException in onError", e); 449 } finally { 450 internalClearSoundModelLocked(); 451 internalClearStateLocked(); 452 if (mModule != null) { 453 mModule.detach(); 454 mModule = null; 455 } 456 } 457 } 458 459 private int updateRecognitionLocked(boolean notify) { 460 if (mModule == null || moduleProperties == null 461 || mCurrentSoundModelHandle == INVALID_VALUE || mActiveListener == null) { 462 // Nothing to do here. 463 return STATUS_OK; 464 } 465 466 boolean start = mRequested && !mCallActive && !mServiceDisabled && !mIsPowerSaveMode; 467 if (start == mStarted) { 468 // No-op. 469 return STATUS_OK; 470 } 471 472 // See if the recognition needs to be started. 473 if (start) { 474 // Start recognition. 475 int status = mModule.startRecognition(mCurrentSoundModelHandle, mRecognitionConfig); 476 if (status != SoundTrigger.STATUS_OK) { 477 Slog.w(TAG, "startRecognition failed with " + status); 478 // Notify of error if needed. 479 if (notify) { 480 try { 481 mActiveListener.onError(status); 482 } catch (RemoteException e) { 483 Slog.w(TAG, "RemoteException in onError", e); 484 } 485 } 486 } else { 487 mStarted = true; 488 // Notify of resume if needed. 489 if (notify) { 490 try { 491 mActiveListener.onRecognitionResumed(); 492 } catch (RemoteException e) { 493 Slog.w(TAG, "RemoteException in onRecognitionResumed", e); 494 } 495 } 496 } 497 return status; 498 } else { 499 // Stop recognition (only if we haven't been aborted). 500 int status = STATUS_OK; 501 if (!mRecognitionAborted) { 502 status = mModule.stopRecognition(mCurrentSoundModelHandle); 503 } else { 504 mRecognitionAborted = false; 505 } 506 if (status != SoundTrigger.STATUS_OK) { 507 Slog.w(TAG, "stopRecognition call failed with " + status); 508 if (notify) { 509 try { 510 mActiveListener.onError(status); 511 } catch (RemoteException e) { 512 Slog.w(TAG, "RemoteException in onError", e); 513 } 514 } 515 } else { 516 mStarted = false; 517 // Notify of pause if needed. 518 if (notify) { 519 try { 520 mActiveListener.onRecognitionPaused(); 521 } catch (RemoteException e) { 522 Slog.w(TAG, "RemoteException in onRecognitionPaused", e); 523 } 524 } 525 } 526 return status; 527 } 528 } 529 530 private void internalClearStateLocked() { 531 mStarted = false; 532 mRequested = false; 533 534 mKeyphraseId = INVALID_VALUE; 535 mRecognitionConfig = null; 536 mActiveListener = null; 537 538 // Unregister from call state changes. 539 mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE); 540 541 // Unregister from power save mode changes. 542 if (mPowerSaveModeListener != null) { 543 mContext.unregisterReceiver(mPowerSaveModeListener); 544 mPowerSaveModeListener = null; 545 } 546 } 547 548 private void internalClearSoundModelLocked() { 549 mCurrentSoundModelHandle = INVALID_VALUE; 550 mCurrentSoundModel = null; 551 } 552 553 class MyCallStateListener extends PhoneStateListener { 554 @Override 555 public void onCallStateChanged(int state, String arg1) { 556 if (DBG) Slog.d(TAG, "onCallStateChanged: " + state); 557 synchronized (mLock) { 558 onCallStateChangedLocked(TelephonyManager.CALL_STATE_IDLE != state); 559 } 560 } 561 } 562 563 class PowerSaveModeListener extends BroadcastReceiver { 564 @Override 565 public void onReceive(Context context, Intent intent) { 566 if (!PowerManager.ACTION_POWER_SAVE_MODE_CHANGED.equals(intent.getAction())) { 567 return; 568 } 569 boolean active = mPowerManager.isPowerSaveMode(); 570 if (DBG) Slog.d(TAG, "onPowerSaveModeChanged: " + active); 571 synchronized (mLock) { 572 onPowerSaveModeChangedLocked(active); 573 } 574 } 575 } 576 577 void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 578 synchronized (mLock) { 579 pw.print(" module properties="); 580 pw.println(moduleProperties == null ? "null" : moduleProperties); 581 pw.print(" keyphrase ID="); pw.println(mKeyphraseId); 582 pw.print(" sound model handle="); pw.println(mCurrentSoundModelHandle); 583 pw.print(" sound model UUID="); 584 pw.println(mCurrentSoundModel == null ? "null" : mCurrentSoundModel.uuid); 585 pw.print(" current listener="); 586 pw.println(mActiveListener == null ? "null" : mActiveListener.asBinder()); 587 588 pw.print(" requested="); pw.println(mRequested); 589 pw.print(" started="); pw.println(mStarted); 590 pw.print(" call active="); pw.println(mCallActive); 591 pw.print(" power save mode active="); pw.println(mIsPowerSaveMode); 592 pw.print(" service disabled="); pw.println(mServiceDisabled); 593 } 594 } 595} 596