1 /** 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server.soundtrigger; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.content.BroadcastReceiver; 22 import android.content.Context; 23 import android.content.Intent; 24 import android.content.IntentFilter; 25 import android.hardware.soundtrigger.IRecognitionStatusCallback; 26 import android.hardware.soundtrigger.ModelParams; 27 import android.hardware.soundtrigger.SoundTrigger; 28 import android.hardware.soundtrigger.SoundTrigger.GenericRecognitionEvent; 29 import android.hardware.soundtrigger.SoundTrigger.GenericSoundModel; 30 import android.hardware.soundtrigger.SoundTrigger.Keyphrase; 31 import android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionEvent; 32 import android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra; 33 import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel; 34 import android.hardware.soundtrigger.SoundTrigger.ModelParamRange; 35 import android.hardware.soundtrigger.SoundTrigger.ModuleProperties; 36 import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig; 37 import android.hardware.soundtrigger.SoundTrigger.RecognitionEvent; 38 import android.hardware.soundtrigger.SoundTrigger.SoundModel; 39 import android.hardware.soundtrigger.SoundTrigger.SoundModelEvent; 40 import android.hardware.soundtrigger.SoundTriggerModule; 41 import android.os.Binder; 42 import android.os.DeadObjectException; 43 import android.os.Handler; 44 import android.os.Looper; 45 import android.os.Message; 46 import android.os.PowerManager; 47 import android.os.PowerManager.SoundTriggerPowerSaveMode; 48 import android.os.RemoteException; 49 import android.telephony.PhoneStateListener; 50 import android.telephony.TelephonyManager; 51 import android.util.Slog; 52 53 import com.android.internal.logging.MetricsLogger; 54 55 import java.io.FileDescriptor; 56 import java.io.PrintWriter; 57 import java.util.ArrayList; 58 import java.util.HashMap; 59 import java.util.Iterator; 60 import java.util.Map; 61 import java.util.Objects; 62 import java.util.UUID; 63 64 /** 65 * Helper for {@link SoundTrigger} APIs. Supports two types of models: 66 * (i) A voice model which is exported via the {@link VoiceInteractionService}. There can only be 67 * a single voice model running on the DSP at any given time. 68 * 69 * (ii) Generic sound-trigger models: Supports multiple of these. 70 * 71 * Currently this just acts as an abstraction over all SoundTrigger API calls. 72 * @hide 73 */ 74 public class SoundTriggerHelper implements SoundTrigger.StatusListener { 75 static final String TAG = "SoundTriggerHelper"; 76 static final boolean DBG = false; 77 78 /** 79 * Return codes for {@link #startRecognition(int, KeyphraseSoundModel, 80 * IRecognitionStatusCallback, RecognitionConfig)}, 81 * {@link #stopRecognition(int, IRecognitionStatusCallback)} 82 */ 83 public static final int STATUS_ERROR = SoundTrigger.STATUS_ERROR; 84 public static final int STATUS_OK = SoundTrigger.STATUS_OK; 85 86 private static final int INVALID_VALUE = Integer.MIN_VALUE; 87 88 /** The {@link ModuleProperties} for the system, or null if none exists. */ 89 final ModuleProperties mModuleProperties; 90 91 /** The properties for the DSP module */ 92 private SoundTriggerModule mModule; 93 private final Object mLock = new Object(); 94 private final Context mContext; 95 private final TelephonyManager mTelephonyManager; 96 private final PhoneStateListener mPhoneStateListener; 97 private final PowerManager mPowerManager; 98 99 // The SoundTriggerManager layer handles multiple recognition models of type generic and 100 // keyphrase. We store the ModelData here in a hashmap. 101 private final HashMap<UUID, ModelData> mModelDataMap; 102 103 // An index of keyphrase sound models so that we can reach them easily. We support indexing 104 // keyphrase sound models with a keyphrase ID. Sound model with the same keyphrase ID will 105 // replace an existing model, thus there is a 1:1 mapping from keyphrase ID to a voice 106 // sound model. 107 private HashMap<Integer, UUID> mKeyphraseUuidMap; 108 109 private boolean mCallActive = false; 110 private @SoundTriggerPowerSaveMode int mSoundTriggerPowerSaveMode = 111 PowerManager.SOUND_TRIGGER_MODE_ALL_ENABLED; 112 // Indicates if the native sound trigger service is disabled or not. 113 // This is an indirect indication of the microphone being open in some other application. 114 private boolean mServiceDisabled = false; 115 116 // Whether ANY recognition (keyphrase or generic) has been requested. 117 private boolean mRecognitionRequested = false; 118 119 private PowerSaveModeListener mPowerSaveModeListener; 120 121 private final SoundTriggerModuleProvider mModuleProvider; 122 123 // Handler to process call state changes will delay to allow time for the audio 124 // and sound trigger HALs to process the end of call notifications 125 // before we re enable pending recognition requests. 126 private final Handler mHandler; 127 private static final int MSG_CALL_STATE_CHANGED = 0; 128 private static final int CALL_INACTIVE_MSG_DELAY_MS = 1000; 129 130 /** 131 * Provider interface for retrieving SoundTriggerModule instances 132 */ 133 public interface SoundTriggerModuleProvider { 134 /** 135 * Populate module properties for all available modules 136 * 137 * @param modules List of ModuleProperties to be populated 138 * @return Status int 0 on success. 139 */ listModuleProperties(@onNull ArrayList<SoundTrigger.ModuleProperties> modules)140 int listModuleProperties(@NonNull ArrayList<SoundTrigger.ModuleProperties> modules); 141 142 /** 143 * Get SoundTriggerModule based on {@link SoundTrigger.ModuleProperties#getId()} 144 * 145 * @param moduleId Module ID 146 * @param statusListener Client listener to be associated with the returned module 147 * @return Module associated with moduleId 148 */ getModule(int moduleId, SoundTrigger.StatusListener statusListener)149 SoundTriggerModule getModule(int moduleId, SoundTrigger.StatusListener statusListener); 150 } 151 SoundTriggerHelper(Context context, SoundTriggerModuleProvider moduleProvider)152 SoundTriggerHelper(Context context, SoundTriggerModuleProvider moduleProvider) { 153 ArrayList <ModuleProperties> modules = new ArrayList<>(); 154 mModuleProvider = moduleProvider; 155 int status = mModuleProvider.listModuleProperties(modules); 156 mContext = context; 157 mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); 158 mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE); 159 mModelDataMap = new HashMap<UUID, ModelData>(); 160 mKeyphraseUuidMap = new HashMap<Integer, UUID>(); 161 if (status != SoundTrigger.STATUS_OK || modules.size() == 0) { 162 Slog.w(TAG, "listModules status=" + status + ", # of modules=" + modules.size()); 163 mModuleProperties = null; 164 mModule = null; 165 } else { 166 // TODO: Figure out how to determine which module corresponds to the DSP hardware. 167 mModuleProperties = modules.get(0); 168 } 169 170 Looper looper = Looper.myLooper(); 171 if (looper == null) { 172 looper = Looper.getMainLooper(); 173 } 174 mPhoneStateListener = new MyCallStateListener(looper); 175 if (looper != null) { 176 mHandler = new Handler(looper) { 177 @Override 178 public void handleMessage(Message msg) { 179 switch (msg.what) { 180 case MSG_CALL_STATE_CHANGED: 181 synchronized (mLock) { 182 onCallStateChangedLocked( 183 TelephonyManager.CALL_STATE_OFFHOOK == msg.arg1); 184 } 185 break; 186 default: 187 Slog.e(TAG, "unknown message in handler:" + msg.what); 188 break; 189 } 190 } 191 }; 192 } else { 193 mHandler = null; 194 } 195 } 196 197 /** 198 * Starts recognition for the given generic sound model ID. This is a wrapper around {@link 199 * startRecognition()}. 200 * 201 * @param modelId UUID of the sound model. 202 * @param soundModel The generic sound model to use for recognition. 203 * @param callback Callack for the recognition events related to the given keyphrase. 204 * @param recognitionConfig Instance of RecognitionConfig containing the parameters for the 205 * recognition. 206 * @return One of {@link #STATUS_ERROR} or {@link #STATUS_OK}. 207 */ startGenericRecognition(UUID modelId, GenericSoundModel soundModel, IRecognitionStatusCallback callback, RecognitionConfig recognitionConfig, boolean runInBatterySaverMode)208 int startGenericRecognition(UUID modelId, GenericSoundModel soundModel, 209 IRecognitionStatusCallback callback, RecognitionConfig recognitionConfig, 210 boolean runInBatterySaverMode) { 211 MetricsLogger.count(mContext, "sth_start_recognition", 1); 212 if (modelId == null || soundModel == null || callback == null || 213 recognitionConfig == null) { 214 Slog.w(TAG, "Passed in bad data to startGenericRecognition()."); 215 return STATUS_ERROR; 216 } 217 218 synchronized (mLock) { 219 ModelData modelData = getOrCreateGenericModelDataLocked(modelId); 220 if (modelData == null) { 221 Slog.w(TAG, "Irrecoverable error occurred, check UUID / sound model data."); 222 return STATUS_ERROR; 223 } 224 return startRecognition(soundModel, modelData, callback, recognitionConfig, 225 INVALID_VALUE /* keyphraseId */, runInBatterySaverMode); 226 } 227 } 228 229 /** 230 * Starts recognition for the given keyphraseId. 231 * 232 * @param keyphraseId The identifier of the keyphrase for which 233 * the recognition is to be started. 234 * @param soundModel The sound model to use for recognition. 235 * @param callback The callback for the recognition events related to the given keyphrase. 236 * @return One of {@link #STATUS_ERROR} or {@link #STATUS_OK}. 237 */ startKeyphraseRecognition(int keyphraseId, KeyphraseSoundModel soundModel, IRecognitionStatusCallback callback, RecognitionConfig recognitionConfig, boolean runInBatterySaverMode)238 int startKeyphraseRecognition(int keyphraseId, KeyphraseSoundModel soundModel, 239 IRecognitionStatusCallback callback, RecognitionConfig recognitionConfig, 240 boolean runInBatterySaverMode) { 241 synchronized (mLock) { 242 MetricsLogger.count(mContext, "sth_start_recognition", 1); 243 if (soundModel == null || callback == null || recognitionConfig == null) { 244 return STATUS_ERROR; 245 } 246 247 if (DBG) { 248 Slog.d(TAG, "startKeyphraseRecognition for keyphraseId=" + keyphraseId 249 + " soundModel=" + soundModel + ", callback=" + callback.asBinder() 250 + ", recognitionConfig=" + recognitionConfig 251 + ", runInBatterySaverMode=" + runInBatterySaverMode); 252 Slog.d(TAG, "moduleProperties=" + mModuleProperties); 253 dumpModelStateLocked(); 254 } 255 256 ModelData model = getKeyphraseModelDataLocked(keyphraseId); 257 if (model != null && !model.isKeyphraseModel()) { 258 Slog.e(TAG, "Generic model with same UUID exists."); 259 return STATUS_ERROR; 260 } 261 262 // Process existing model first. 263 if (model != null && !model.getModelId().equals(soundModel.getUuid())) { 264 // The existing model has a different UUID, should be replaced. 265 int status = cleanUpExistingKeyphraseModelLocked(model); 266 if (status != STATUS_OK) { 267 return status; 268 } 269 removeKeyphraseModelLocked(keyphraseId); 270 model = null; 271 } 272 273 // We need to create a new one: either no previous models existed for given keyphrase id 274 // or the existing model had a different UUID and was cleaned up. 275 if (model == null) { 276 model = createKeyphraseModelDataLocked(soundModel.getUuid(), keyphraseId); 277 } 278 279 return startRecognition(soundModel, model, callback, recognitionConfig, 280 keyphraseId, runInBatterySaverMode); 281 } 282 } 283 cleanUpExistingKeyphraseModelLocked(ModelData modelData)284 private int cleanUpExistingKeyphraseModelLocked(ModelData modelData) { 285 // Stop and clean up a previous ModelData if one exists. This usually is used when the 286 // previous model has a different UUID for the same keyphrase ID. 287 int status = tryStopAndUnloadLocked(modelData, true /* stop */, true /* unload */); 288 if (status != STATUS_OK) { 289 Slog.w(TAG, "Unable to stop or unload previous model: " + 290 modelData.toString()); 291 } 292 return status; 293 } 294 prepareForRecognition(ModelData modelData)295 private int prepareForRecognition(ModelData modelData) { 296 if (mModule == null) { 297 mModule = mModuleProvider.getModule(mModuleProperties.getId(), this); 298 if (mModule == null) { 299 Slog.w(TAG, "prepareForRecognition: cannot attach to sound trigger module"); 300 return STATUS_ERROR; 301 } 302 } 303 // Load the model if it is not loaded. 304 if (!modelData.isModelLoaded()) { 305 // Before we try and load this model, we should first make sure that any other 306 // models that don't have an active recognition/dead callback are unloaded. Since 307 // there is a finite limit on the number of models that the hardware may be able to 308 // have loaded, we want to make sure there's room for our model. 309 stopAndUnloadDeadModelsLocked(); 310 int[] handle = new int[] { 0 }; 311 int status = mModule.loadSoundModel(modelData.getSoundModel(), handle); 312 if (status != SoundTrigger.STATUS_OK) { 313 Slog.w(TAG, "prepareForRecognition: loadSoundModel failed with status: " + status); 314 return status; 315 } 316 modelData.setHandle(handle[0]); 317 modelData.setLoaded(); 318 if (DBG) { 319 Slog.d(TAG, "prepareForRecognition: Sound model loaded with handle:" + handle[0]); 320 } 321 } 322 return STATUS_OK; 323 } 324 325 326 /** 327 * Starts recognition for the given sound model. A single routine for both keyphrase and 328 * generic sound models. 329 * 330 * @param soundModel The sound model to use for recognition. 331 * @param modelData Instance of {@link #ModelData} for the given model. 332 * @param callback Callback for the recognition events related to the given keyphrase. 333 * @param recognitionConfig Instance of {@link RecognitionConfig} containing the parameters 334 * @param keyphraseId Keyphrase ID for keyphrase models only. Pass in INVALID_VALUE for other 335 * models. 336 * for the recognition. 337 * @return One of {@link #STATUS_ERROR} or {@link #STATUS_OK}. 338 */ startRecognition(SoundModel soundModel, ModelData modelData, IRecognitionStatusCallback callback, RecognitionConfig recognitionConfig, int keyphraseId, boolean runInBatterySaverMode)339 int startRecognition(SoundModel soundModel, ModelData modelData, 340 IRecognitionStatusCallback callback, RecognitionConfig recognitionConfig, 341 int keyphraseId, boolean runInBatterySaverMode) { 342 synchronized (mLock) { 343 if (mModuleProperties == null) { 344 Slog.w(TAG, "Attempting startRecognition without the capability"); 345 return STATUS_ERROR; 346 } 347 348 IRecognitionStatusCallback oldCallback = modelData.getCallback(); 349 if (oldCallback != null && oldCallback.asBinder() != callback.asBinder()) { 350 Slog.w(TAG, "Canceling previous recognition for model id: " 351 + modelData.getModelId()); 352 try { 353 oldCallback.onError(STATUS_ERROR); 354 } catch (RemoteException e) { 355 Slog.w(TAG, "RemoteException in onDetectionStopped", e); 356 } 357 modelData.clearCallback(); 358 } 359 360 // If the existing SoundModel is different (for the same UUID for Generic and same 361 // keyphrase ID for voice), ensure that it is unloaded and stopped before proceeding. 362 // This works for both keyphrase and generic models. This logic also ensures that a 363 // previously loaded (or started) model is appropriately stopped. Since this is a 364 // generalization of the previous logic with a single keyphrase model, we should have 365 // no regression with the previous version of this code as was given in the 366 // startKeyphrase() routine. 367 if (modelData.getSoundModel() != null) { 368 boolean stopModel = false; // Stop the model after checking that it is started. 369 boolean unloadModel = false; 370 if (modelData.getSoundModel().equals(soundModel) && modelData.isModelStarted()) { 371 // The model has not changed, but the previous model is "started". 372 // Stop the previously running model. 373 stopModel = true; 374 unloadModel = false; // No need to unload if the model hasn't changed. 375 } else if (!modelData.getSoundModel().equals(soundModel)) { 376 // We have a different model for this UUID. Stop and unload if needed. This 377 // helps maintain the singleton restriction for keyphrase sound models. 378 stopModel = modelData.isModelStarted(); 379 unloadModel = modelData.isModelLoaded(); 380 } 381 if (stopModel || unloadModel) { 382 int status = tryStopAndUnloadLocked(modelData, stopModel, unloadModel); 383 if (status != STATUS_OK) { 384 Slog.w(TAG, "Unable to stop or unload previous model: " + 385 modelData.toString()); 386 return status; 387 } 388 } 389 } 390 391 modelData.setCallback(callback); 392 modelData.setRequested(true); 393 modelData.setRecognitionConfig(recognitionConfig); 394 modelData.setRunInBatterySaverMode(runInBatterySaverMode); 395 modelData.setSoundModel(soundModel); 396 397 if (!isRecognitionAllowedByDeviceState(modelData)) { 398 initializeDeviceStateListeners(); 399 return STATUS_OK; 400 } 401 402 int status = prepareForRecognition(modelData); 403 if (status != STATUS_OK) { 404 Slog.w(TAG, "startRecognition failed to prepare model for recognition"); 405 return status; 406 } 407 status = startRecognitionLocked(modelData, 408 false /* Don't notify for synchronous calls */); 409 410 // Initialize power save, call active state monitoring logic. 411 if (status == STATUS_OK) { 412 initializeDeviceStateListeners(); 413 } 414 415 return status; 416 } 417 } 418 419 /** 420 * Stops recognition for the given generic sound model. This is a wrapper for {@link 421 * #stopRecognition}. 422 * 423 * @param modelId The identifier of the generic sound model for which 424 * the recognition is to be stopped. 425 * @param callback The callback for the recognition events related to the given sound model. 426 * 427 * @return One of {@link #STATUS_ERROR} or {@link #STATUS_OK}. 428 */ stopGenericRecognition(UUID modelId, IRecognitionStatusCallback callback)429 int stopGenericRecognition(UUID modelId, IRecognitionStatusCallback callback) { 430 synchronized (mLock) { 431 MetricsLogger.count(mContext, "sth_stop_recognition", 1); 432 if (callback == null || modelId == null) { 433 Slog.e(TAG, "Null callbackreceived for stopGenericRecognition() for modelid:" + 434 modelId); 435 return STATUS_ERROR; 436 } 437 438 ModelData modelData = mModelDataMap.get(modelId); 439 if (modelData == null || !modelData.isGenericModel()) { 440 Slog.w(TAG, "Attempting stopRecognition on invalid model with id:" + modelId); 441 return STATUS_ERROR; 442 } 443 444 int status = stopRecognition(modelData, callback); 445 if (status != SoundTrigger.STATUS_OK) { 446 Slog.w(TAG, "stopGenericRecognition failed: " + status); 447 } 448 return status; 449 } 450 } 451 452 /** 453 * Stops recognition for the given {@link Keyphrase} if a recognition is 454 * currently active. This is a wrapper for {@link #stopRecognition()}. 455 * 456 * @param keyphraseId The identifier of the keyphrase for which 457 * the recognition is to be stopped. 458 * @param callback The callback for the recognition events related to the given keyphrase. 459 * 460 * @return One of {@link #STATUS_ERROR} or {@link #STATUS_OK}. 461 */ stopKeyphraseRecognition(int keyphraseId, IRecognitionStatusCallback callback)462 int stopKeyphraseRecognition(int keyphraseId, IRecognitionStatusCallback callback) { 463 synchronized (mLock) { 464 MetricsLogger.count(mContext, "sth_stop_recognition", 1); 465 if (callback == null) { 466 Slog.e(TAG, "Null callback received for stopKeyphraseRecognition() for keyphraseId:" + 467 keyphraseId); 468 return STATUS_ERROR; 469 } 470 471 ModelData modelData = getKeyphraseModelDataLocked(keyphraseId); 472 if (modelData == null || !modelData.isKeyphraseModel()) { 473 Slog.w(TAG, "No model exists for given keyphrase Id " + keyphraseId); 474 return STATUS_ERROR; 475 } 476 477 if (DBG) { 478 Slog.d(TAG, "stopRecognition for keyphraseId=" + keyphraseId + ", callback =" + 479 callback.asBinder()); 480 Slog.d(TAG, "current callback=" 481 + ((modelData == null || modelData.getCallback() == null) ? "null" : 482 modelData.getCallback().asBinder())); 483 } 484 int status = stopRecognition(modelData, callback); 485 if (status != SoundTrigger.STATUS_OK) { 486 return status; 487 } 488 489 return status; 490 } 491 } 492 493 /** 494 * Stops recognition for the given ModelData instance. 495 * 496 * @param modelData Instance of {@link #ModelData} sound model. 497 * @param callback The callback for the recognition events related to the given keyphrase. 498 * @return One of {@link #STATUS_ERROR} or {@link #STATUS_OK}. 499 */ stopRecognition(ModelData modelData, IRecognitionStatusCallback callback)500 private int stopRecognition(ModelData modelData, IRecognitionStatusCallback callback) { 501 synchronized (mLock) { 502 if (callback == null) { 503 return STATUS_ERROR; 504 } 505 if (mModuleProperties == null || mModule == null) { 506 Slog.w(TAG, "Attempting stopRecognition without the capability"); 507 return STATUS_ERROR; 508 } 509 510 IRecognitionStatusCallback currentCallback = modelData.getCallback(); 511 if (modelData == null || currentCallback == null || 512 (!modelData.isRequested() && !modelData.isModelStarted())) { 513 // startGenericRecognition hasn't been called or it failed. 514 Slog.w(TAG, "Attempting stopRecognition without a successful startRecognition"); 515 return STATUS_ERROR; 516 } 517 518 if (currentCallback.asBinder() != callback.asBinder()) { 519 // We don't allow a different listener to stop the recognition than the one 520 // that started it. 521 Slog.w(TAG, "Attempting stopRecognition for another recognition"); 522 return STATUS_ERROR; 523 } 524 525 // Request stop recognition via the update() method. 526 modelData.setRequested(false); 527 int status = updateRecognitionLocked(modelData, false); 528 if (status != SoundTrigger.STATUS_OK) { 529 return status; 530 } 531 532 // We leave the sound model loaded but not started, this helps us when we start back. 533 // Also clear the internal state once the recognition has been stopped. 534 modelData.setLoaded(); 535 modelData.clearCallback(); 536 modelData.setRecognitionConfig(null); 537 538 if (!computeRecognitionRequestedLocked()) { 539 internalClearGlobalStateLocked(); 540 } 541 542 return status; 543 } 544 } 545 546 // Stop a previously started model if it was started. Optionally, unload if the previous model 547 // is stale and is about to be replaced. 548 // Needs to be called with the mLock held. tryStopAndUnloadLocked(ModelData modelData, boolean stopModel, boolean unloadModel)549 private int tryStopAndUnloadLocked(ModelData modelData, boolean stopModel, 550 boolean unloadModel) { 551 int status = STATUS_OK; 552 if (modelData.isModelNotLoaded()) { 553 return status; 554 } 555 if (stopModel && modelData.isModelStarted()) { 556 status = stopRecognitionLocked(modelData, 557 false /* don't notify for synchronous calls */); 558 if (status != SoundTrigger.STATUS_OK) { 559 Slog.w(TAG, "stopRecognition failed: " + status); 560 return status; 561 } 562 } 563 564 if (unloadModel && modelData.isModelLoaded()) { 565 Slog.d(TAG, "Unloading previously loaded stale model."); 566 if (mModule == null) { 567 return STATUS_ERROR; 568 } 569 status = mModule.unloadSoundModel(modelData.getHandle()); 570 MetricsLogger.count(mContext, "sth_unloading_stale_model", 1); 571 if (status != SoundTrigger.STATUS_OK) { 572 Slog.w(TAG, "unloadSoundModel call failed with " + status); 573 } else { 574 // Clear the ModelData state if successful. 575 modelData.clearState(); 576 } 577 } 578 return status; 579 } 580 getModuleProperties()581 public ModuleProperties getModuleProperties() { 582 return mModuleProperties; 583 } 584 unloadKeyphraseSoundModel(int keyphraseId)585 int unloadKeyphraseSoundModel(int keyphraseId) { 586 synchronized (mLock) { 587 MetricsLogger.count(mContext, "sth_unload_keyphrase_sound_model", 1); 588 ModelData modelData = getKeyphraseModelDataLocked(keyphraseId); 589 if (mModule == null || modelData == null || !modelData.isModelLoaded() 590 || !modelData.isKeyphraseModel()) { 591 return STATUS_ERROR; 592 } 593 594 // Stop recognition if it's the current one. 595 modelData.setRequested(false); 596 int status = updateRecognitionLocked(modelData, false); 597 if (status != SoundTrigger.STATUS_OK) { 598 Slog.w(TAG, "Stop recognition failed for keyphrase ID:" + status); 599 } 600 601 status = mModule.unloadSoundModel(modelData.getHandle()); 602 if (status != SoundTrigger.STATUS_OK) { 603 Slog.w(TAG, "unloadKeyphraseSoundModel call failed with " + status); 604 } 605 606 // Remove it from existence. 607 removeKeyphraseModelLocked(keyphraseId); 608 return status; 609 } 610 } 611 unloadGenericSoundModel(UUID modelId)612 int unloadGenericSoundModel(UUID modelId) { 613 synchronized (mLock) { 614 MetricsLogger.count(mContext, "sth_unload_generic_sound_model", 1); 615 if (modelId == null || mModule == null) { 616 return STATUS_ERROR; 617 } 618 ModelData modelData = mModelDataMap.get(modelId); 619 if (modelData == null || !modelData.isGenericModel()) { 620 Slog.w(TAG, "Unload error: Attempting unload invalid generic model with id:" + 621 modelId); 622 return STATUS_ERROR; 623 } 624 if (!modelData.isModelLoaded()) { 625 // Nothing to do here. 626 Slog.i(TAG, "Unload: Given generic model is not loaded:" + modelId); 627 return STATUS_OK; 628 } 629 if (modelData.isModelStarted()) { 630 int status = stopRecognitionLocked(modelData, 631 false /* don't notify for synchronous calls */); 632 if (status != SoundTrigger.STATUS_OK) { 633 Slog.w(TAG, "stopGenericRecognition failed: " + status); 634 } 635 } 636 637 if (mModule == null) { 638 return STATUS_ERROR; 639 } 640 int status = mModule.unloadSoundModel(modelData.getHandle()); 641 if (status != SoundTrigger.STATUS_OK) { 642 Slog.w(TAG, "unloadGenericSoundModel() call failed with " + status); 643 Slog.w(TAG, "unloadGenericSoundModel() force-marking model as unloaded."); 644 } 645 646 // Remove it from existence. 647 mModelDataMap.remove(modelId); 648 if (DBG) dumpModelStateLocked(); 649 return status; 650 } 651 } 652 isRecognitionRequested(UUID modelId)653 boolean isRecognitionRequested(UUID modelId) { 654 synchronized (mLock) { 655 ModelData modelData = mModelDataMap.get(modelId); 656 return modelData != null && modelData.isRequested(); 657 } 658 } 659 getGenericModelState(UUID modelId)660 int getGenericModelState(UUID modelId) { 661 synchronized (mLock) { 662 MetricsLogger.count(mContext, "sth_get_generic_model_state", 1); 663 if (modelId == null || mModule == null) { 664 return STATUS_ERROR; 665 } 666 ModelData modelData = mModelDataMap.get(modelId); 667 if (modelData == null || !modelData.isGenericModel()) { 668 Slog.w(TAG, "GetGenericModelState error: Invalid generic model id:" + 669 modelId); 670 return STATUS_ERROR; 671 } 672 if (!modelData.isModelLoaded()) { 673 Slog.i(TAG, "GetGenericModelState: Given generic model is not loaded:" + modelId); 674 return STATUS_ERROR; 675 } 676 if (!modelData.isModelStarted()) { 677 Slog.i(TAG, "GetGenericModelState: Given generic model is not started:" + modelId); 678 return STATUS_ERROR; 679 } 680 681 return mModule.getModelState(modelData.getHandle()); 682 } 683 } 684 getKeyphraseModelState(UUID modelId)685 int getKeyphraseModelState(UUID modelId) { 686 Slog.w(TAG, "GetKeyphraseModelState error: Not implemented"); 687 return STATUS_ERROR; 688 } 689 setParameter(UUID modelId, @ModelParams int modelParam, int value)690 int setParameter(UUID modelId, @ModelParams int modelParam, int value) { 691 synchronized (mLock) { 692 return setParameterLocked(mModelDataMap.get(modelId), modelParam, value); 693 } 694 } 695 setKeyphraseParameter(int keyphraseId, @ModelParams int modelParam, int value)696 int setKeyphraseParameter(int keyphraseId, @ModelParams int modelParam, int value) { 697 synchronized (mLock) { 698 return setParameterLocked(getKeyphraseModelDataLocked(keyphraseId), modelParam, value); 699 } 700 } 701 setParameterLocked(@ullable ModelData modelData, @ModelParams int modelParam, int value)702 private int setParameterLocked(@Nullable ModelData modelData, @ModelParams int modelParam, 703 int value) { 704 MetricsLogger.count(mContext, "sth_set_parameter", 1); 705 if (mModule == null) { 706 return SoundTrigger.STATUS_NO_INIT; 707 } 708 if (modelData == null || !modelData.isModelLoaded()) { 709 Slog.i(TAG, "SetParameter: Given model is not loaded:" + modelData); 710 return SoundTrigger.STATUS_BAD_VALUE; 711 } 712 713 return mModule.setParameter(modelData.getHandle(), modelParam, value); 714 } 715 getParameter(@onNull UUID modelId, @ModelParams int modelParam)716 int getParameter(@NonNull UUID modelId, @ModelParams int modelParam) { 717 synchronized (mLock) { 718 return getParameterLocked(mModelDataMap.get(modelId), modelParam); 719 } 720 } 721 getKeyphraseParameter(int keyphraseId, @ModelParams int modelParam)722 int getKeyphraseParameter(int keyphraseId, @ModelParams int modelParam) { 723 synchronized (mLock) { 724 return getParameterLocked(getKeyphraseModelDataLocked(keyphraseId), modelParam); 725 } 726 } 727 getParameterLocked(@ullable ModelData modelData, @ModelParams int modelParam)728 private int getParameterLocked(@Nullable ModelData modelData, @ModelParams int modelParam) { 729 MetricsLogger.count(mContext, "sth_get_parameter", 1); 730 if (mModule == null) { 731 throw new UnsupportedOperationException("SoundTriggerModule not initialized"); 732 } 733 734 if (modelData == null) { 735 throw new IllegalArgumentException("Invalid model id"); 736 } 737 if (!modelData.isModelLoaded()) { 738 throw new UnsupportedOperationException("Given model is not loaded:" + modelData); 739 } 740 741 return mModule.getParameter(modelData.getHandle(), modelParam); 742 } 743 744 @Nullable queryParameter(@onNull UUID modelId, @ModelParams int modelParam)745 ModelParamRange queryParameter(@NonNull UUID modelId, @ModelParams int modelParam) { 746 synchronized (mLock) { 747 return queryParameterLocked(mModelDataMap.get(modelId), modelParam); 748 } 749 } 750 751 @Nullable queryKeyphraseParameter(int keyphraseId, @ModelParams int modelParam)752 ModelParamRange queryKeyphraseParameter(int keyphraseId, @ModelParams int modelParam) { 753 synchronized (mLock) { 754 return queryParameterLocked(getKeyphraseModelDataLocked(keyphraseId), modelParam); 755 } 756 } 757 758 @Nullable queryParameterLocked(@ullable ModelData modelData, @ModelParams int modelParam)759 private ModelParamRange queryParameterLocked(@Nullable ModelData modelData, 760 @ModelParams int modelParam) { 761 MetricsLogger.count(mContext, "sth_query_parameter", 1); 762 if (mModule == null) { 763 return null; 764 } 765 if (modelData == null) { 766 Slog.w(TAG, "queryParameter: Invalid model id"); 767 return null; 768 } 769 if (!modelData.isModelLoaded()) { 770 Slog.i(TAG, "queryParameter: Given model is not loaded:" + modelData); 771 return null; 772 } 773 774 return mModule.queryParameter(modelData.getHandle(), modelParam); 775 } 776 777 //---- SoundTrigger.StatusListener methods 778 @Override onRecognition(RecognitionEvent event)779 public void onRecognition(RecognitionEvent event) { 780 if (event == null) { 781 Slog.w(TAG, "Null recognition event!"); 782 return; 783 } 784 785 if (!(event instanceof KeyphraseRecognitionEvent) && 786 !(event instanceof GenericRecognitionEvent)) { 787 Slog.w(TAG, "Invalid recognition event type (not one of generic or keyphrase)!"); 788 return; 789 } 790 791 if (DBG) Slog.d(TAG, "onRecognition: " + event); 792 synchronized (mLock) { 793 switch (event.status) { 794 case SoundTrigger.RECOGNITION_STATUS_ABORT: 795 onRecognitionAbortLocked(event); 796 break; 797 case SoundTrigger.RECOGNITION_STATUS_FAILURE: 798 // Fire failures to all listeners since it's not tied to a keyphrase. 799 onRecognitionFailureLocked(); 800 break; 801 case SoundTrigger.RECOGNITION_STATUS_SUCCESS: 802 case SoundTrigger.RECOGNITION_STATUS_GET_STATE_RESPONSE: 803 if (isKeyphraseRecognitionEvent(event)) { 804 onKeyphraseRecognitionSuccessLocked((KeyphraseRecognitionEvent) event); 805 } else { 806 onGenericRecognitionSuccessLocked((GenericRecognitionEvent) event); 807 } 808 break; 809 } 810 } 811 } 812 isKeyphraseRecognitionEvent(RecognitionEvent event)813 private boolean isKeyphraseRecognitionEvent(RecognitionEvent event) { 814 return event instanceof KeyphraseRecognitionEvent; 815 } 816 onGenericRecognitionSuccessLocked(GenericRecognitionEvent event)817 private void onGenericRecognitionSuccessLocked(GenericRecognitionEvent event) { 818 MetricsLogger.count(mContext, "sth_generic_recognition_event", 1); 819 if (event.status != SoundTrigger.RECOGNITION_STATUS_SUCCESS 820 && event.status != SoundTrigger.RECOGNITION_STATUS_GET_STATE_RESPONSE) { 821 return; 822 } 823 ModelData model = getModelDataForLocked(event.soundModelHandle); 824 if (model == null || !model.isGenericModel()) { 825 Slog.w(TAG, "Generic recognition event: Model does not exist for handle: " 826 + event.soundModelHandle); 827 return; 828 } 829 830 IRecognitionStatusCallback callback = model.getCallback(); 831 if (callback == null) { 832 Slog.w(TAG, "Generic recognition event: Null callback for model handle: " 833 + event.soundModelHandle); 834 return; 835 } 836 837 if (event.status != SoundTrigger.RECOGNITION_STATUS_GET_STATE_RESPONSE) { 838 model.setStopped(); 839 } 840 841 try { 842 callback.onGenericSoundTriggerDetected((GenericRecognitionEvent) event); 843 } catch (DeadObjectException e) { 844 forceStopAndUnloadModelLocked(model, e); 845 return; 846 } catch (RemoteException e) { 847 Slog.w(TAG, "RemoteException in onGenericSoundTriggerDetected", e); 848 } 849 850 RecognitionConfig config = model.getRecognitionConfig(); 851 if (config == null) { 852 Slog.w(TAG, "Generic recognition event: Null RecognitionConfig for model handle: " 853 + event.soundModelHandle); 854 return; 855 } 856 857 model.setRequested(config.allowMultipleTriggers); 858 // TODO: Remove this block if the lower layer supports multiple triggers. 859 if (model.isRequested()) { 860 updateRecognitionLocked(model, true); 861 } 862 } 863 864 @Override onSoundModelUpdate(SoundModelEvent event)865 public void onSoundModelUpdate(SoundModelEvent event) { 866 if (event == null) { 867 Slog.w(TAG, "Invalid sound model event!"); 868 return; 869 } 870 if (DBG) Slog.d(TAG, "onSoundModelUpdate: " + event); 871 synchronized (mLock) { 872 MetricsLogger.count(mContext, "sth_sound_model_updated", 1); 873 onSoundModelUpdatedLocked(event); 874 } 875 } 876 877 @Override onServiceStateChange(int state)878 public void onServiceStateChange(int state) { 879 if (DBG) Slog.d(TAG, "onServiceStateChange, state: " + state); 880 synchronized (mLock) { 881 onServiceStateChangedLocked(SoundTrigger.SERVICE_STATE_DISABLED == state); 882 } 883 } 884 885 @Override onServiceDied()886 public void onServiceDied() { 887 Slog.e(TAG, "onServiceDied!!"); 888 MetricsLogger.count(mContext, "sth_service_died", 1); 889 synchronized (mLock) { 890 onServiceDiedLocked(); 891 } 892 } 893 onCallStateChangedLocked(boolean callActive)894 private void onCallStateChangedLocked(boolean callActive) { 895 if (mCallActive == callActive) { 896 // We consider multiple call states as being active 897 // so we check if something really changed or not here. 898 return; 899 } 900 mCallActive = callActive; 901 updateAllRecognitionsLocked(); 902 } 903 onPowerSaveModeChangedLocked( @oundTriggerPowerSaveMode int soundTriggerPowerSaveMode)904 private void onPowerSaveModeChangedLocked( 905 @SoundTriggerPowerSaveMode int soundTriggerPowerSaveMode) { 906 if (mSoundTriggerPowerSaveMode == soundTriggerPowerSaveMode) { 907 return; 908 } 909 mSoundTriggerPowerSaveMode = soundTriggerPowerSaveMode; 910 updateAllRecognitionsLocked(); 911 } 912 onSoundModelUpdatedLocked(SoundModelEvent event)913 private void onSoundModelUpdatedLocked(SoundModelEvent event) { 914 // TODO: Handle sound model update here. 915 } 916 onServiceStateChangedLocked(boolean disabled)917 private void onServiceStateChangedLocked(boolean disabled) { 918 if (disabled == mServiceDisabled) { 919 return; 920 } 921 mServiceDisabled = disabled; 922 updateAllRecognitionsLocked(); 923 } 924 onRecognitionAbortLocked(RecognitionEvent event)925 private void onRecognitionAbortLocked(RecognitionEvent event) { 926 Slog.w(TAG, "Recognition aborted"); 927 MetricsLogger.count(mContext, "sth_recognition_aborted", 1); 928 ModelData modelData = getModelDataForLocked(event.soundModelHandle); 929 if (modelData != null && modelData.isModelStarted()) { 930 modelData.setStopped(); 931 try { 932 modelData.getCallback().onRecognitionPaused(); 933 } catch (DeadObjectException e) { 934 forceStopAndUnloadModelLocked(modelData, e); 935 } catch (RemoteException e) { 936 Slog.w(TAG, "RemoteException in onRecognitionPaused", e); 937 } 938 } 939 } 940 onRecognitionFailureLocked()941 private void onRecognitionFailureLocked() { 942 Slog.w(TAG, "Recognition failure"); 943 MetricsLogger.count(mContext, "sth_recognition_failure_event", 1); 944 try { 945 sendErrorCallbacksToAllLocked(STATUS_ERROR); 946 } finally { 947 internalClearModelStateLocked(); 948 internalClearGlobalStateLocked(); 949 } 950 } 951 getKeyphraseIdFromEvent(KeyphraseRecognitionEvent event)952 private int getKeyphraseIdFromEvent(KeyphraseRecognitionEvent event) { 953 if (event == null) { 954 Slog.w(TAG, "Null RecognitionEvent received."); 955 return INVALID_VALUE; 956 } 957 KeyphraseRecognitionExtra[] keyphraseExtras = 958 ((KeyphraseRecognitionEvent) event).keyphraseExtras; 959 if (keyphraseExtras == null || keyphraseExtras.length == 0) { 960 Slog.w(TAG, "Invalid keyphrase recognition event!"); 961 return INVALID_VALUE; 962 } 963 // TODO: Handle more than one keyphrase extras. 964 return keyphraseExtras[0].id; 965 } 966 onKeyphraseRecognitionSuccessLocked(KeyphraseRecognitionEvent event)967 private void onKeyphraseRecognitionSuccessLocked(KeyphraseRecognitionEvent event) { 968 Slog.i(TAG, "Recognition success"); 969 MetricsLogger.count(mContext, "sth_keyphrase_recognition_event", 1); 970 int keyphraseId = getKeyphraseIdFromEvent(event); 971 ModelData modelData = getKeyphraseModelDataLocked(keyphraseId); 972 973 if (modelData == null || !modelData.isKeyphraseModel()) { 974 Slog.e(TAG, "Keyphase model data does not exist for ID:" + keyphraseId); 975 return; 976 } 977 978 if (modelData.getCallback() == null) { 979 Slog.w(TAG, "Received onRecognition event without callback for keyphrase model."); 980 return; 981 } 982 983 if (event.status != SoundTrigger.RECOGNITION_STATUS_GET_STATE_RESPONSE) { 984 modelData.setStopped(); 985 } 986 987 try { 988 modelData.getCallback().onKeyphraseDetected((KeyphraseRecognitionEvent) event); 989 } catch (DeadObjectException e) { 990 forceStopAndUnloadModelLocked(modelData, e); 991 return; 992 } catch (RemoteException e) { 993 Slog.w(TAG, "RemoteException in onKeyphraseDetected", e); 994 } 995 996 RecognitionConfig config = modelData.getRecognitionConfig(); 997 if (config != null) { 998 // Whether we should continue by starting this again. 999 modelData.setRequested(config.allowMultipleTriggers); 1000 } 1001 // TODO: Remove this block if the lower layer supports multiple triggers. 1002 if (modelData.isRequested()) { 1003 updateRecognitionLocked(modelData, true); 1004 } 1005 } 1006 updateAllRecognitionsLocked()1007 private void updateAllRecognitionsLocked() { 1008 // updateRecognitionLocked can possibly update the list of models 1009 ArrayList<ModelData> modelDatas = new ArrayList<ModelData>(mModelDataMap.values()); 1010 for (ModelData modelData : modelDatas) { 1011 updateRecognitionLocked(modelData, true); 1012 } 1013 } 1014 updateRecognitionLocked(ModelData model, boolean notifyClientOnError)1015 private int updateRecognitionLocked(ModelData model, boolean notifyClientOnError) { 1016 boolean shouldStartModel = model.isRequested() && isRecognitionAllowedByDeviceState(model); 1017 if (shouldStartModel == model.isModelStarted()) { 1018 // No-op. 1019 return STATUS_OK; 1020 } 1021 if (shouldStartModel) { 1022 int status = prepareForRecognition(model); 1023 if (status != STATUS_OK) { 1024 return status; 1025 } 1026 return startRecognitionLocked(model, notifyClientOnError); 1027 } else { 1028 return stopRecognitionLocked(model, notifyClientOnError); 1029 } 1030 } 1031 onServiceDiedLocked()1032 private void onServiceDiedLocked() { 1033 try { 1034 MetricsLogger.count(mContext, "sth_service_died", 1); 1035 sendErrorCallbacksToAllLocked(SoundTrigger.STATUS_DEAD_OBJECT); 1036 } finally { 1037 internalClearModelStateLocked(); 1038 internalClearGlobalStateLocked(); 1039 if (mModule != null) { 1040 mModule.detach(); 1041 mModule = null; 1042 mServiceDisabled = false; 1043 } 1044 } 1045 } 1046 1047 // internalClearGlobalStateLocked() cleans up the telephony and power save listeners. internalClearGlobalStateLocked()1048 private void internalClearGlobalStateLocked() { 1049 // Unregister from call state changes. 1050 final long token = Binder.clearCallingIdentity(); 1051 try { 1052 mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE); 1053 } finally { 1054 Binder.restoreCallingIdentity(token); 1055 } 1056 1057 // Unregister from power save mode changes. 1058 if (mPowerSaveModeListener != null) { 1059 mContext.unregisterReceiver(mPowerSaveModeListener); 1060 mPowerSaveModeListener = null; 1061 } 1062 mRecognitionRequested = false; 1063 } 1064 1065 // Clears state for all models (generic and keyphrase). internalClearModelStateLocked()1066 private void internalClearModelStateLocked() { 1067 for (ModelData modelData : mModelDataMap.values()) { 1068 modelData.clearState(); 1069 } 1070 } 1071 1072 class MyCallStateListener extends PhoneStateListener { MyCallStateListener(@onNull Looper looper)1073 MyCallStateListener(@NonNull Looper looper) { 1074 super(Objects.requireNonNull(looper)); 1075 } 1076 1077 @Override onCallStateChanged(int state, String arg1)1078 public void onCallStateChanged(int state, String arg1) { 1079 if (DBG) Slog.d(TAG, "onCallStateChanged: " + state); 1080 1081 if (mHandler != null) { 1082 synchronized (mLock) { 1083 mHandler.removeMessages(MSG_CALL_STATE_CHANGED); 1084 Message msg = mHandler.obtainMessage(MSG_CALL_STATE_CHANGED, state, 0); 1085 mHandler.sendMessageDelayed( 1086 msg, (TelephonyManager.CALL_STATE_OFFHOOK == state) ? 0 1087 : CALL_INACTIVE_MSG_DELAY_MS); 1088 } 1089 } 1090 } 1091 } 1092 1093 class PowerSaveModeListener extends BroadcastReceiver { 1094 @Override onReceive(Context context, Intent intent)1095 public void onReceive(Context context, Intent intent) { 1096 if (!PowerManager.ACTION_POWER_SAVE_MODE_CHANGED.equals(intent.getAction())) { 1097 return; 1098 } 1099 @SoundTriggerPowerSaveMode int soundTriggerPowerSaveMode = 1100 mPowerManager.getSoundTriggerPowerSaveMode(); 1101 if (DBG) { 1102 Slog.d(TAG, "onPowerSaveModeChanged: " + soundTriggerPowerSaveMode); 1103 } 1104 synchronized (mLock) { 1105 onPowerSaveModeChangedLocked(soundTriggerPowerSaveMode); 1106 } 1107 } 1108 } 1109 dump(FileDescriptor fd, PrintWriter pw, String[] args)1110 void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 1111 synchronized (mLock) { 1112 pw.print(" module properties="); 1113 pw.println(mModuleProperties == null ? "null" : mModuleProperties); 1114 pw.print(" call active="); 1115 pw.println(mCallActive); 1116 pw.println(" SoundTrigger Power State=" + mSoundTriggerPowerSaveMode); 1117 pw.print(" service disabled="); 1118 pw.println(mServiceDisabled); 1119 } 1120 } 1121 initializeDeviceStateListeners()1122 private void initializeDeviceStateListeners() { 1123 if (mRecognitionRequested) { 1124 return; 1125 } 1126 final long token = Binder.clearCallingIdentity(); 1127 try { 1128 // Get the current call state synchronously for the first recognition. 1129 mCallActive = mTelephonyManager.getCallState() == TelephonyManager.CALL_STATE_OFFHOOK; 1130 1131 // Register for call state changes when the first call to start recognition occurs. 1132 mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE); 1133 1134 // Register for power saver mode changes when the first call to start recognition 1135 // occurs. 1136 if (mPowerSaveModeListener == null) { 1137 mPowerSaveModeListener = new PowerSaveModeListener(); 1138 mContext.registerReceiver(mPowerSaveModeListener, 1139 new IntentFilter(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED)); 1140 } 1141 mSoundTriggerPowerSaveMode = mPowerManager.getSoundTriggerPowerSaveMode(); 1142 1143 mRecognitionRequested = true; 1144 } finally { 1145 Binder.restoreCallingIdentity(token); 1146 } 1147 } 1148 1149 // Sends an error callback to all models with a valid registered callback. sendErrorCallbacksToAllLocked(int errorCode)1150 private void sendErrorCallbacksToAllLocked(int errorCode) { 1151 for (ModelData modelData : mModelDataMap.values()) { 1152 IRecognitionStatusCallback callback = modelData.getCallback(); 1153 if (callback != null) { 1154 try { 1155 callback.onError(errorCode); 1156 } catch (RemoteException e) { 1157 Slog.w(TAG, "RemoteException sendErrorCallbacksToAllLocked for model handle " + 1158 modelData.getHandle(), e); 1159 } 1160 } 1161 } 1162 } 1163 1164 /** 1165 * Stops and unloads all models. This is intended as a clean-up call with the expectation that 1166 * this instance is not used after. 1167 * @hide 1168 */ detach()1169 public void detach() { 1170 synchronized (mLock) { 1171 for (ModelData model : mModelDataMap.values()) { 1172 forceStopAndUnloadModelLocked(model, null); 1173 } 1174 mModelDataMap.clear(); 1175 internalClearGlobalStateLocked(); 1176 if (mModule != null) { 1177 mModule.detach(); 1178 mModule = null; 1179 } 1180 } 1181 } 1182 1183 /** 1184 * Stops and unloads a sound model, and removes any reference to the model if successful. 1185 * 1186 * @param modelData The model data to remove. 1187 * @param exception Optional exception to print in logcat. May be null. 1188 */ forceStopAndUnloadModelLocked(ModelData modelData, Exception exception)1189 private void forceStopAndUnloadModelLocked(ModelData modelData, Exception exception) { 1190 forceStopAndUnloadModelLocked(modelData, exception, null /* modelDataIterator */); 1191 } 1192 1193 /** 1194 * Stops and unloads a sound model, and removes any reference to the model if successful. 1195 * 1196 * @param modelData The model data to remove. 1197 * @param exception Optional exception to print in logcat. May be null. 1198 * @param modelDataIterator If this function is to be used while iterating over the 1199 * mModelDataMap, you can provide the iterator for the current model data to be used to 1200 * remove the modelData from the map. This avoids generating a 1201 * ConcurrentModificationException, since this function will try and remove the model 1202 * data from the mModelDataMap when it can successfully unload the model. 1203 */ forceStopAndUnloadModelLocked(ModelData modelData, Exception exception, Iterator modelDataIterator)1204 private void forceStopAndUnloadModelLocked(ModelData modelData, Exception exception, 1205 Iterator modelDataIterator) { 1206 if (exception != null) { 1207 Slog.e(TAG, "forceStopAndUnloadModel", exception); 1208 } 1209 if (mModule == null) { 1210 return; 1211 } 1212 if (modelData.isModelStarted()) { 1213 Slog.d(TAG, "Stopping previously started dangling model " + modelData.getHandle()); 1214 if (mModule.stopRecognition(modelData.getHandle()) == STATUS_OK) { 1215 modelData.setStopped(); 1216 modelData.setRequested(false); 1217 } else { 1218 Slog.e(TAG, "Failed to stop model " + modelData.getHandle()); 1219 } 1220 } 1221 if (modelData.isModelLoaded()) { 1222 Slog.d(TAG, "Unloading previously loaded dangling model " + modelData.getHandle()); 1223 if (mModule.unloadSoundModel(modelData.getHandle()) == STATUS_OK) { 1224 // Remove the model data from existence. 1225 if (modelDataIterator != null) { 1226 modelDataIterator.remove(); 1227 } else { 1228 mModelDataMap.remove(modelData.getModelId()); 1229 } 1230 Iterator it = mKeyphraseUuidMap.entrySet().iterator(); 1231 while (it.hasNext()) { 1232 Map.Entry pair = (Map.Entry) it.next(); 1233 if (pair.getValue().equals(modelData.getModelId())) { 1234 it.remove(); 1235 } 1236 } 1237 modelData.clearState(); 1238 } else { 1239 Slog.e(TAG, "Failed to unload model " + modelData.getHandle()); 1240 } 1241 } 1242 } 1243 stopAndUnloadDeadModelsLocked()1244 private void stopAndUnloadDeadModelsLocked() { 1245 Iterator it = mModelDataMap.entrySet().iterator(); 1246 while (it.hasNext()) { 1247 ModelData modelData = (ModelData) ((Map.Entry) it.next()).getValue(); 1248 if (!modelData.isModelLoaded()) { 1249 continue; 1250 } 1251 if (modelData.getCallback() == null 1252 || (modelData.getCallback().asBinder() != null 1253 && !modelData.getCallback().asBinder().pingBinder())) { 1254 // No one is listening on this model, so we might as well evict it. 1255 Slog.w(TAG, "Removing model " + modelData.getHandle() + " that has no clients"); 1256 forceStopAndUnloadModelLocked(modelData, null /* exception */, it); 1257 } 1258 } 1259 } 1260 getOrCreateGenericModelDataLocked(UUID modelId)1261 private ModelData getOrCreateGenericModelDataLocked(UUID modelId) { 1262 ModelData modelData = mModelDataMap.get(modelId); 1263 if (modelData == null) { 1264 modelData = ModelData.createGenericModelData(modelId); 1265 mModelDataMap.put(modelId, modelData); 1266 } else if (!modelData.isGenericModel()) { 1267 Slog.e(TAG, "UUID already used for non-generic model."); 1268 return null; 1269 } 1270 return modelData; 1271 } 1272 removeKeyphraseModelLocked(int keyphraseId)1273 private void removeKeyphraseModelLocked(int keyphraseId) { 1274 UUID uuid = mKeyphraseUuidMap.get(keyphraseId); 1275 if (uuid == null) { 1276 return; 1277 } 1278 mModelDataMap.remove(uuid); 1279 mKeyphraseUuidMap.remove(keyphraseId); 1280 } 1281 getKeyphraseModelDataLocked(int keyphraseId)1282 private ModelData getKeyphraseModelDataLocked(int keyphraseId) { 1283 UUID uuid = mKeyphraseUuidMap.get(keyphraseId); 1284 if (uuid == null) { 1285 return null; 1286 } 1287 return mModelDataMap.get(uuid); 1288 } 1289 1290 // Use this to create a new ModelData entry for a keyphrase Id. It will overwrite existing 1291 // mapping if one exists. createKeyphraseModelDataLocked(UUID modelId, int keyphraseId)1292 private ModelData createKeyphraseModelDataLocked(UUID modelId, int keyphraseId) { 1293 mKeyphraseUuidMap.remove(keyphraseId); 1294 mModelDataMap.remove(modelId); 1295 mKeyphraseUuidMap.put(keyphraseId, modelId); 1296 ModelData modelData = ModelData.createKeyphraseModelData(modelId); 1297 mModelDataMap.put(modelId, modelData); 1298 return modelData; 1299 } 1300 1301 // Instead of maintaining a second hashmap of modelHandle -> ModelData, we just 1302 // iterate through to find the right object (since we don't expect 100s of models 1303 // to be stored). getModelDataForLocked(int modelHandle)1304 private ModelData getModelDataForLocked(int modelHandle) { 1305 // Fetch ModelData object corresponding to the model handle. 1306 for (ModelData model : mModelDataMap.values()) { 1307 if (model.getHandle() == modelHandle) { 1308 return model; 1309 } 1310 } 1311 return null; 1312 } 1313 1314 /** 1315 * Determines if recognition is allowed at all based on device state 1316 * 1317 * <p>Depending on the state of the SoundTrigger service, whether a call is active, or if 1318 * battery saver mode is enabled, a specific model may or may not be able to run. The result 1319 * of this check is not permanent, and the state of the device can change at any time. 1320 * 1321 * @param modelData Model data to be used for recognition 1322 * @return True if recognition is allowed to run at this time. False if not. 1323 */ isRecognitionAllowedByDeviceState(ModelData modelData)1324 private boolean isRecognitionAllowedByDeviceState(ModelData modelData) { 1325 // if mRecognitionRequested is false, call and power state listeners are not registered so 1326 // we read current state directly from services 1327 if (!mRecognitionRequested) { 1328 mCallActive = mTelephonyManager.getCallState() == TelephonyManager.CALL_STATE_OFFHOOK; 1329 mSoundTriggerPowerSaveMode = mPowerManager.getSoundTriggerPowerSaveMode(); 1330 } 1331 1332 return !mCallActive && !mServiceDisabled 1333 && isRecognitionAllowedByPowerState( 1334 modelData); 1335 } 1336 1337 /** 1338 * Helper function to validate if a recognition should run based on the current power state 1339 * 1340 * @param modelData Model data to be used for recognition 1341 * @return True if device state allows recognition to run, false if not. 1342 */ isRecognitionAllowedByPowerState(ModelData modelData)1343 boolean isRecognitionAllowedByPowerState(ModelData modelData) { 1344 return mSoundTriggerPowerSaveMode == PowerManager.SOUND_TRIGGER_MODE_ALL_ENABLED 1345 || (mSoundTriggerPowerSaveMode == PowerManager.SOUND_TRIGGER_MODE_CRITICAL_ONLY 1346 && modelData.shouldRunInBatterySaverMode()); 1347 } 1348 1349 // A single routine that implements the start recognition logic for both generic and keyphrase 1350 // models. startRecognitionLocked(ModelData modelData, boolean notifyClientOnError)1351 private int startRecognitionLocked(ModelData modelData, boolean notifyClientOnError) { 1352 IRecognitionStatusCallback callback = modelData.getCallback(); 1353 RecognitionConfig config = modelData.getRecognitionConfig(); 1354 if (callback == null || !modelData.isModelLoaded() || config == null) { 1355 // Nothing to do here. 1356 Slog.w(TAG, "startRecognition: Bad data passed in."); 1357 MetricsLogger.count(mContext, "sth_start_recognition_error", 1); 1358 return STATUS_ERROR; 1359 } 1360 1361 if (!isRecognitionAllowedByDeviceState(modelData)) { 1362 // Nothing to do here. 1363 Slog.w(TAG, "startRecognition requested but not allowed."); 1364 MetricsLogger.count(mContext, "sth_start_recognition_not_allowed", 1); 1365 return STATUS_OK; 1366 } 1367 1368 if (mModule == null) { 1369 return STATUS_ERROR; 1370 } 1371 int status = mModule.startRecognition(modelData.getHandle(), config); 1372 if (status != SoundTrigger.STATUS_OK) { 1373 Slog.w(TAG, "startRecognition failed with " + status); 1374 MetricsLogger.count(mContext, "sth_start_recognition_error", 1); 1375 // Notify of error if needed. 1376 if (notifyClientOnError) { 1377 try { 1378 callback.onError(status); 1379 } catch (DeadObjectException e) { 1380 forceStopAndUnloadModelLocked(modelData, e); 1381 } catch (RemoteException e) { 1382 Slog.w(TAG, "RemoteException in onError", e); 1383 } 1384 } 1385 } else { 1386 Slog.i(TAG, "startRecognition successful."); 1387 MetricsLogger.count(mContext, "sth_start_recognition_success", 1); 1388 modelData.setStarted(); 1389 // Notify of resume if needed. 1390 if (notifyClientOnError) { 1391 try { 1392 callback.onRecognitionResumed(); 1393 } catch (DeadObjectException e) { 1394 forceStopAndUnloadModelLocked(modelData, e); 1395 } catch (RemoteException e) { 1396 Slog.w(TAG, "RemoteException in onRecognitionResumed", e); 1397 } 1398 } 1399 } 1400 if (DBG) { 1401 Slog.d(TAG, "Model being started :" + modelData.toString()); 1402 } 1403 return status; 1404 } 1405 stopRecognitionLocked(ModelData modelData, boolean notify)1406 private int stopRecognitionLocked(ModelData modelData, boolean notify) { 1407 if (mModule == null) { 1408 return STATUS_ERROR; 1409 } 1410 1411 IRecognitionStatusCallback callback = modelData.getCallback(); 1412 // Stop recognition. 1413 int status = STATUS_OK; 1414 1415 status = mModule.stopRecognition(modelData.getHandle()); 1416 1417 if (status != SoundTrigger.STATUS_OK) { 1418 Slog.w(TAG, "stopRecognition call failed with " + status); 1419 MetricsLogger.count(mContext, "sth_stop_recognition_error", 1); 1420 if (notify) { 1421 try { 1422 callback.onError(status); 1423 } catch (DeadObjectException e) { 1424 forceStopAndUnloadModelLocked(modelData, e); 1425 } catch (RemoteException e) { 1426 Slog.w(TAG, "RemoteException in onError", e); 1427 } 1428 } 1429 } else { 1430 modelData.setStopped(); 1431 MetricsLogger.count(mContext, "sth_stop_recognition_success", 1); 1432 // Notify of pause if needed. 1433 if (notify) { 1434 try { 1435 callback.onRecognitionPaused(); 1436 } catch (DeadObjectException e) { 1437 forceStopAndUnloadModelLocked(modelData, e); 1438 } catch (RemoteException e) { 1439 Slog.w(TAG, "RemoteException in onRecognitionPaused", e); 1440 } 1441 } 1442 } 1443 if (DBG) { 1444 Slog.d(TAG, "Model being stopped :" + modelData.toString()); 1445 } 1446 return status; 1447 } 1448 dumpModelStateLocked()1449 private void dumpModelStateLocked() { 1450 for (UUID modelId : mModelDataMap.keySet()) { 1451 ModelData modelData = mModelDataMap.get(modelId); 1452 Slog.i(TAG, "Model :" + modelData.toString()); 1453 } 1454 } 1455 1456 // Computes whether we have any recognition running at all (voice or generic). Sets 1457 // the mRecognitionRequested variable with the result. computeRecognitionRequestedLocked()1458 private boolean computeRecognitionRequestedLocked() { 1459 if (mModuleProperties == null || mModule == null) { 1460 mRecognitionRequested = false; 1461 return mRecognitionRequested; 1462 } 1463 for (ModelData modelData : mModelDataMap.values()) { 1464 if (modelData.isRequested()) { 1465 mRecognitionRequested = true; 1466 return mRecognitionRequested; 1467 } 1468 } 1469 mRecognitionRequested = false; 1470 return mRecognitionRequested; 1471 } 1472 1473 // This class encapsulates the callbacks, state, handles and any other information that 1474 // represents a model. 1475 private static class ModelData { 1476 // Model not loaded (and hence not started). 1477 static final int MODEL_NOTLOADED = 0; 1478 1479 // Loaded implies model was successfully loaded. Model not started yet. 1480 static final int MODEL_LOADED = 1; 1481 1482 // Started implies model was successfully loaded and start was called. 1483 static final int MODEL_STARTED = 2; 1484 1485 // One of MODEL_NOTLOADED, MODEL_LOADED, MODEL_STARTED (which implies loaded). 1486 private int mModelState; 1487 private UUID mModelId; 1488 1489 // mRequested captures the explicit intent that a start was requested for this model. We 1490 // continue to capture and retain this state even after the model gets started, so that we 1491 // know when a model gets stopped due to "other" reasons, that we should start it again. 1492 // This was the intended behavior of the "mRequested" variable in the previous version of 1493 // this code that we are replicating here. 1494 // 1495 // The "other" reasons include power save, abort being called from the lower layer (due 1496 // to concurrent capture not being supported) and phone call state. Once we recover from 1497 // these transient disruptions, we would start such models again where mRequested == true. 1498 // Thus, mRequested gets reset only when there is an explicit intent to stop the model 1499 // coming from the SoundTriggerService layer that uses this class (and thus eventually 1500 // from the app that manages this model). 1501 private boolean mRequested = false; 1502 1503 // One of SoundModel.TYPE_GENERIC or SoundModel.TYPE_KEYPHRASE. Initially set 1504 // to SoundModel.TYPE_UNKNOWN; 1505 private int mModelType = SoundModel.TYPE_UNKNOWN; 1506 1507 private IRecognitionStatusCallback mCallback = null; 1508 private RecognitionConfig mRecognitionConfig = null; 1509 1510 // Model handle is an integer used by the HAL as an identifier for sound 1511 // models. 1512 private int mModelHandle; 1513 1514 /** 1515 * True if the service should continue listening when battery saver mode is enabled. 1516 * Having this flag set requires the client calling 1517 * {@link SoundTriggerModule#startRecognition(int, RecognitionConfig)} to be granted 1518 * {@link android.Manifest.permission#SOUND_TRIGGER_RUN_IN_BATTERY_SAVER}. 1519 */ 1520 public boolean mRunInBatterySaverMode = false; 1521 1522 // The SoundModel instance, one of KeyphraseSoundModel or GenericSoundModel. 1523 private SoundModel mSoundModel = null; 1524 ModelData(UUID modelId, int modelType)1525 private ModelData(UUID modelId, int modelType) { 1526 mModelId = modelId; 1527 // Private constructor, since we require modelType to be one of TYPE_GENERIC, 1528 // TYPE_KEYPHRASE or TYPE_UNKNOWN. 1529 mModelType = modelType; 1530 } 1531 createKeyphraseModelData(UUID modelId)1532 static ModelData createKeyphraseModelData(UUID modelId) { 1533 return new ModelData(modelId, SoundModel.TYPE_KEYPHRASE); 1534 } 1535 createGenericModelData(UUID modelId)1536 static ModelData createGenericModelData(UUID modelId) { 1537 return new ModelData(modelId, SoundModel.TYPE_GENERIC_SOUND); 1538 } 1539 1540 // Note that most of the functionality in this Java class will not work for 1541 // SoundModel.TYPE_UNKNOWN nevertheless we have it since lower layers support it. createModelDataOfUnknownType(UUID modelId)1542 static ModelData createModelDataOfUnknownType(UUID modelId) { 1543 return new ModelData(modelId, SoundModel.TYPE_UNKNOWN); 1544 } 1545 setCallback(IRecognitionStatusCallback callback)1546 synchronized void setCallback(IRecognitionStatusCallback callback) { 1547 mCallback = callback; 1548 } 1549 getCallback()1550 synchronized IRecognitionStatusCallback getCallback() { 1551 return mCallback; 1552 } 1553 isModelLoaded()1554 synchronized boolean isModelLoaded() { 1555 return (mModelState == MODEL_LOADED || mModelState == MODEL_STARTED); 1556 } 1557 isModelNotLoaded()1558 synchronized boolean isModelNotLoaded() { 1559 return mModelState == MODEL_NOTLOADED; 1560 } 1561 setStarted()1562 synchronized void setStarted() { 1563 mModelState = MODEL_STARTED; 1564 } 1565 setStopped()1566 synchronized void setStopped() { 1567 mModelState = MODEL_LOADED; 1568 } 1569 setLoaded()1570 synchronized void setLoaded() { 1571 mModelState = MODEL_LOADED; 1572 } 1573 isModelStarted()1574 synchronized boolean isModelStarted() { 1575 return mModelState == MODEL_STARTED; 1576 } 1577 clearState()1578 synchronized void clearState() { 1579 mModelState = MODEL_NOTLOADED; 1580 mRecognitionConfig = null; 1581 mRequested = false; 1582 mCallback = null; 1583 } 1584 clearCallback()1585 synchronized void clearCallback() { 1586 mCallback = null; 1587 } 1588 setHandle(int handle)1589 synchronized void setHandle(int handle) { 1590 mModelHandle = handle; 1591 } 1592 setRecognitionConfig(RecognitionConfig config)1593 synchronized void setRecognitionConfig(RecognitionConfig config) { 1594 mRecognitionConfig = config; 1595 } 1596 setRunInBatterySaverMode(boolean runInBatterySaverMode)1597 synchronized void setRunInBatterySaverMode(boolean runInBatterySaverMode) { 1598 mRunInBatterySaverMode = runInBatterySaverMode; 1599 } 1600 shouldRunInBatterySaverMode()1601 synchronized boolean shouldRunInBatterySaverMode() { 1602 return mRunInBatterySaverMode; 1603 } 1604 getHandle()1605 synchronized int getHandle() { 1606 return mModelHandle; 1607 } 1608 getModelId()1609 synchronized UUID getModelId() { 1610 return mModelId; 1611 } 1612 getRecognitionConfig()1613 synchronized RecognitionConfig getRecognitionConfig() { 1614 return mRecognitionConfig; 1615 } 1616 1617 // Whether a start recognition was requested. isRequested()1618 synchronized boolean isRequested() { 1619 return mRequested; 1620 } 1621 setRequested(boolean requested)1622 synchronized void setRequested(boolean requested) { 1623 mRequested = requested; 1624 } 1625 setSoundModel(SoundModel soundModel)1626 synchronized void setSoundModel(SoundModel soundModel) { 1627 mSoundModel = soundModel; 1628 } 1629 getSoundModel()1630 synchronized SoundModel getSoundModel() { 1631 return mSoundModel; 1632 } 1633 getModelType()1634 synchronized int getModelType() { 1635 return mModelType; 1636 } 1637 isKeyphraseModel()1638 synchronized boolean isKeyphraseModel() { 1639 return mModelType == SoundModel.TYPE_KEYPHRASE; 1640 } 1641 isGenericModel()1642 synchronized boolean isGenericModel() { 1643 return mModelType == SoundModel.TYPE_GENERIC_SOUND; 1644 } 1645 stateToString()1646 synchronized String stateToString() { 1647 switch(mModelState) { 1648 case MODEL_NOTLOADED: return "NOT_LOADED"; 1649 case MODEL_LOADED: return "LOADED"; 1650 case MODEL_STARTED: return "STARTED"; 1651 } 1652 return "Unknown state"; 1653 } 1654 requestedToString()1655 synchronized String requestedToString() { 1656 return "Requested: " + (mRequested ? "Yes" : "No"); 1657 } 1658 callbackToString()1659 synchronized String callbackToString() { 1660 return "Callback: " + (mCallback != null ? mCallback.asBinder() : "null"); 1661 } 1662 uuidToString()1663 synchronized String uuidToString() { 1664 return "UUID: " + mModelId; 1665 } 1666 toString()1667 synchronized public String toString() { 1668 return "Handle: " + mModelHandle + "\n" + 1669 "ModelState: " + stateToString() + "\n" + 1670 requestedToString() + "\n" + 1671 callbackToString() + "\n" + 1672 uuidToString() + "\n" + 1673 modelTypeToString() + 1674 "RunInBatterySaverMode=" + mRunInBatterySaverMode; 1675 } 1676 modelTypeToString()1677 synchronized String modelTypeToString() { 1678 String type = null; 1679 switch (mModelType) { 1680 case SoundModel.TYPE_GENERIC_SOUND: type = "Generic"; break; 1681 case SoundModel.TYPE_UNKNOWN: type = "Unknown"; break; 1682 case SoundModel.TYPE_KEYPHRASE: type = "Keyphrase"; break; 1683 } 1684 return "Model type: " + type + "\n"; 1685 } 1686 } 1687 } 1688