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 static android.Manifest.permission.BIND_SOUND_TRIGGER_DETECTION_SERVICE; 20 import static android.content.Context.BIND_AUTO_CREATE; 21 import static android.content.Context.BIND_FOREGROUND_SERVICE; 22 import static android.content.pm.PackageManager.GET_META_DATA; 23 import static android.content.pm.PackageManager.GET_SERVICES; 24 import static android.content.pm.PackageManager.MATCH_DEBUG_TRIAGED_MISSING; 25 import static android.hardware.soundtrigger.SoundTrigger.STATUS_ERROR; 26 import static android.hardware.soundtrigger.SoundTrigger.STATUS_OK; 27 import static android.provider.Settings.Global.MAX_SOUND_TRIGGER_DETECTION_SERVICE_OPS_PER_DAY; 28 import static android.provider.Settings.Global.SOUND_TRIGGER_DETECTION_SERVICE_OP_TIMEOUT; 29 30 import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage; 31 32 import android.Manifest; 33 import android.annotation.NonNull; 34 import android.annotation.Nullable; 35 import android.content.ComponentName; 36 import android.content.Context; 37 import android.content.Intent; 38 import android.content.ServiceConnection; 39 import android.content.pm.PackageManager; 40 import android.content.pm.ResolveInfo; 41 import android.hardware.soundtrigger.IRecognitionStatusCallback; 42 import android.hardware.soundtrigger.SoundTrigger; 43 import android.hardware.soundtrigger.SoundTrigger.GenericSoundModel; 44 import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel; 45 import android.hardware.soundtrigger.SoundTrigger.ModuleProperties; 46 import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig; 47 import android.hardware.soundtrigger.SoundTrigger.SoundModel; 48 import android.media.AudioAttributes; 49 import android.media.AudioFormat; 50 import android.media.AudioRecord; 51 import android.media.MediaRecorder; 52 import android.media.soundtrigger.ISoundTriggerDetectionService; 53 import android.media.soundtrigger.ISoundTriggerDetectionServiceClient; 54 import android.media.soundtrigger.SoundTriggerDetectionService; 55 import android.os.Binder; 56 import android.os.Bundle; 57 import android.os.Handler; 58 import android.os.IBinder; 59 import android.os.Looper; 60 import android.os.Parcel; 61 import android.os.ParcelUuid; 62 import android.os.PowerManager; 63 import android.os.RemoteException; 64 import android.os.UserHandle; 65 import android.provider.Settings; 66 import android.util.ArrayMap; 67 import android.util.ArraySet; 68 import android.util.Slog; 69 70 import com.android.internal.annotations.GuardedBy; 71 import com.android.internal.app.ISoundTriggerService; 72 import com.android.internal.util.Preconditions; 73 import com.android.server.SystemService; 74 75 import java.io.FileDescriptor; 76 import java.io.PrintWriter; 77 import java.util.ArrayList; 78 import java.util.TreeMap; 79 import java.util.UUID; 80 import java.util.concurrent.TimeUnit; 81 82 /** 83 * A single SystemService to manage all sound/voice-based sound models on the DSP. 84 * This services provides apis to manage sound trigger-based sound models via 85 * the ISoundTriggerService interface. This class also publishes a local interface encapsulating 86 * the functionality provided by {@link SoundTriggerHelper} for use by 87 * {@link VoiceInteractionManagerService}. 88 * 89 * @hide 90 */ 91 public class SoundTriggerService extends SystemService { 92 private static final String TAG = "SoundTriggerService"; 93 private static final boolean DEBUG = true; 94 95 final Context mContext; 96 private Object mLock; 97 private final SoundTriggerServiceStub mServiceStub; 98 private final LocalSoundTriggerService mLocalSoundTriggerService; 99 private SoundTriggerDbHelper mDbHelper; 100 private SoundTriggerHelper mSoundTriggerHelper; 101 private final TreeMap<UUID, SoundModel> mLoadedModels; 102 private Object mCallbacksLock; 103 private final TreeMap<UUID, IRecognitionStatusCallback> mCallbacks; 104 105 /** Number of ops run by the {@link RemoteSoundTriggerDetectionService} per package name */ 106 @GuardedBy("mLock") 107 private final ArrayMap<String, NumOps> mNumOpsPerPackage = new ArrayMap<>(); 108 SoundTriggerService(Context context)109 public SoundTriggerService(Context context) { 110 super(context); 111 mContext = context; 112 mServiceStub = new SoundTriggerServiceStub(); 113 mLocalSoundTriggerService = new LocalSoundTriggerService(context); 114 mLoadedModels = new TreeMap<UUID, SoundModel>(); 115 mCallbacksLock = new Object(); 116 mCallbacks = new TreeMap<>(); 117 mLock = new Object(); 118 } 119 120 @Override onStart()121 public void onStart() { 122 publishBinderService(Context.SOUND_TRIGGER_SERVICE, mServiceStub); 123 publishLocalService(SoundTriggerInternal.class, mLocalSoundTriggerService); 124 } 125 126 @Override onBootPhase(int phase)127 public void onBootPhase(int phase) { 128 if (PHASE_SYSTEM_SERVICES_READY == phase) { 129 initSoundTriggerHelper(); 130 mLocalSoundTriggerService.setSoundTriggerHelper(mSoundTriggerHelper); 131 } else if (PHASE_THIRD_PARTY_APPS_CAN_START == phase) { 132 mDbHelper = new SoundTriggerDbHelper(mContext); 133 } 134 } 135 136 @Override onStartUser(int userHandle)137 public void onStartUser(int userHandle) { 138 } 139 140 @Override onSwitchUser(int userHandle)141 public void onSwitchUser(int userHandle) { 142 } 143 initSoundTriggerHelper()144 private synchronized void initSoundTriggerHelper() { 145 if (mSoundTriggerHelper == null) { 146 mSoundTriggerHelper = new SoundTriggerHelper(mContext); 147 } 148 } 149 isInitialized()150 private synchronized boolean isInitialized() { 151 if (mSoundTriggerHelper == null ) { 152 Slog.e(TAG, "SoundTriggerHelper not initialized."); 153 return false; 154 } 155 return true; 156 } 157 158 class SoundTriggerServiceStub extends ISoundTriggerService.Stub { 159 @Override onTransact(int code, Parcel data, Parcel reply, int flags)160 public boolean onTransact(int code, Parcel data, Parcel reply, int flags) 161 throws RemoteException { 162 try { 163 return super.onTransact(code, data, reply, flags); 164 } catch (RuntimeException e) { 165 // The activity manager only throws security exceptions, so let's 166 // log all others. 167 if (!(e instanceof SecurityException)) { 168 Slog.wtf(TAG, "SoundTriggerService Crash", e); 169 } 170 throw e; 171 } 172 } 173 174 @Override startRecognition(ParcelUuid parcelUuid, IRecognitionStatusCallback callback, RecognitionConfig config)175 public int startRecognition(ParcelUuid parcelUuid, IRecognitionStatusCallback callback, 176 RecognitionConfig config) { 177 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER); 178 if (!isInitialized()) return STATUS_ERROR; 179 if (DEBUG) { 180 Slog.i(TAG, "startRecognition(): Uuid : " + parcelUuid); 181 } 182 183 sEventLogger.log(new SoundTriggerLogger.StringEvent("startRecognition(): Uuid : " 184 + parcelUuid)); 185 186 GenericSoundModel model = getSoundModel(parcelUuid); 187 if (model == null) { 188 Slog.e(TAG, "Null model in database for id: " + parcelUuid); 189 190 sEventLogger.log(new SoundTriggerLogger.StringEvent( 191 "startRecognition(): Null model in database for id: " + parcelUuid)); 192 193 return STATUS_ERROR; 194 } 195 196 return mSoundTriggerHelper.startGenericRecognition(parcelUuid.getUuid(), model, 197 callback, config); 198 } 199 200 @Override stopRecognition(ParcelUuid parcelUuid, IRecognitionStatusCallback callback)201 public int stopRecognition(ParcelUuid parcelUuid, IRecognitionStatusCallback callback) { 202 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER); 203 if (DEBUG) { 204 Slog.i(TAG, "stopRecognition(): Uuid : " + parcelUuid); 205 } 206 207 sEventLogger.log(new SoundTriggerLogger.StringEvent("stopRecognition(): Uuid : " 208 + parcelUuid)); 209 210 if (!isInitialized()) return STATUS_ERROR; 211 return mSoundTriggerHelper.stopGenericRecognition(parcelUuid.getUuid(), callback); 212 } 213 214 @Override getSoundModel(ParcelUuid soundModelId)215 public SoundTrigger.GenericSoundModel getSoundModel(ParcelUuid soundModelId) { 216 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER); 217 if (DEBUG) { 218 Slog.i(TAG, "getSoundModel(): id = " + soundModelId); 219 } 220 221 sEventLogger.log(new SoundTriggerLogger.StringEvent("getSoundModel(): id = " 222 + soundModelId)); 223 224 SoundTrigger.GenericSoundModel model = mDbHelper.getGenericSoundModel( 225 soundModelId.getUuid()); 226 return model; 227 } 228 229 @Override updateSoundModel(SoundTrigger.GenericSoundModel soundModel)230 public void updateSoundModel(SoundTrigger.GenericSoundModel soundModel) { 231 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER); 232 if (DEBUG) { 233 Slog.i(TAG, "updateSoundModel(): model = " + soundModel); 234 } 235 236 sEventLogger.log(new SoundTriggerLogger.StringEvent("updateSoundModel(): model = " 237 + soundModel)); 238 239 mDbHelper.updateGenericSoundModel(soundModel); 240 } 241 242 @Override deleteSoundModel(ParcelUuid soundModelId)243 public void deleteSoundModel(ParcelUuid soundModelId) { 244 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER); 245 if (DEBUG) { 246 Slog.i(TAG, "deleteSoundModel(): id = " + soundModelId); 247 } 248 249 sEventLogger.log(new SoundTriggerLogger.StringEvent("deleteSoundModel(): id = " 250 + soundModelId)); 251 252 // Unload the model if it is loaded. 253 mSoundTriggerHelper.unloadGenericSoundModel(soundModelId.getUuid()); 254 mDbHelper.deleteGenericSoundModel(soundModelId.getUuid()); 255 } 256 257 @Override loadGenericSoundModel(GenericSoundModel soundModel)258 public int loadGenericSoundModel(GenericSoundModel soundModel) { 259 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER); 260 if (!isInitialized()) return STATUS_ERROR; 261 if (soundModel == null || soundModel.uuid == null) { 262 Slog.e(TAG, "Invalid sound model"); 263 264 sEventLogger.log(new SoundTriggerLogger.StringEvent( 265 "loadGenericSoundModel(): Invalid sound model")); 266 267 return STATUS_ERROR; 268 } 269 if (DEBUG) { 270 Slog.i(TAG, "loadGenericSoundModel(): id = " + soundModel.uuid); 271 } 272 273 sEventLogger.log(new SoundTriggerLogger.StringEvent("loadGenericSoundModel(): id = " 274 + soundModel.uuid)); 275 276 synchronized (mLock) { 277 SoundModel oldModel = mLoadedModels.get(soundModel.uuid); 278 // If the model we're loading is actually different than what we had loaded, we 279 // should unload that other model now. We don't care about return codes since we 280 // don't know if the other model is loaded. 281 if (oldModel != null && !oldModel.equals(soundModel)) { 282 mSoundTriggerHelper.unloadGenericSoundModel(soundModel.uuid); 283 synchronized (mCallbacksLock) { 284 mCallbacks.remove(soundModel.uuid); 285 } 286 } 287 mLoadedModels.put(soundModel.uuid, soundModel); 288 } 289 return STATUS_OK; 290 } 291 292 @Override loadKeyphraseSoundModel(KeyphraseSoundModel soundModel)293 public int loadKeyphraseSoundModel(KeyphraseSoundModel soundModel) { 294 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER); 295 if (!isInitialized()) return STATUS_ERROR; 296 if (soundModel == null || soundModel.uuid == null) { 297 Slog.e(TAG, "Invalid sound model"); 298 299 sEventLogger.log(new SoundTriggerLogger.StringEvent( 300 "loadKeyphraseSoundModel(): Invalid sound model")); 301 302 return STATUS_ERROR; 303 } 304 if (soundModel.keyphrases == null || soundModel.keyphrases.length != 1) { 305 Slog.e(TAG, "Only one keyphrase per model is currently supported."); 306 307 sEventLogger.log(new SoundTriggerLogger.StringEvent( 308 "loadKeyphraseSoundModel(): Only one keyphrase per model" 309 + " is currently supported.")); 310 311 return STATUS_ERROR; 312 } 313 if (DEBUG) { 314 Slog.i(TAG, "loadKeyphraseSoundModel(): id = " + soundModel.uuid); 315 } 316 317 sEventLogger.log(new SoundTriggerLogger.StringEvent("loadKeyphraseSoundModel(): id = " 318 + soundModel.uuid)); 319 320 synchronized (mLock) { 321 SoundModel oldModel = mLoadedModels.get(soundModel.uuid); 322 // If the model we're loading is actually different than what we had loaded, we 323 // should unload that other model now. We don't care about return codes since we 324 // don't know if the other model is loaded. 325 if (oldModel != null && !oldModel.equals(soundModel)) { 326 mSoundTriggerHelper.unloadKeyphraseSoundModel(soundModel.keyphrases[0].id); 327 synchronized (mCallbacksLock) { 328 mCallbacks.remove(soundModel.uuid); 329 } 330 } 331 mLoadedModels.put(soundModel.uuid, soundModel); 332 } 333 return STATUS_OK; 334 } 335 336 @Override startRecognitionForService(ParcelUuid soundModelId, Bundle params, ComponentName detectionService, SoundTrigger.RecognitionConfig config)337 public int startRecognitionForService(ParcelUuid soundModelId, Bundle params, 338 ComponentName detectionService, SoundTrigger.RecognitionConfig config) { 339 Preconditions.checkNotNull(soundModelId); 340 Preconditions.checkNotNull(detectionService); 341 Preconditions.checkNotNull(config); 342 343 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER); 344 345 if (!isInitialized()) return STATUS_ERROR; 346 if (DEBUG) { 347 Slog.i(TAG, "startRecognition(): id = " + soundModelId); 348 } 349 350 sEventLogger.log(new SoundTriggerLogger.StringEvent( 351 "startRecognitionForService(): id = " + soundModelId)); 352 353 IRecognitionStatusCallback callback = 354 new RemoteSoundTriggerDetectionService(soundModelId.getUuid(), params, 355 detectionService, Binder.getCallingUserHandle(), config); 356 357 synchronized (mLock) { 358 SoundModel soundModel = mLoadedModels.get(soundModelId.getUuid()); 359 if (soundModel == null) { 360 Slog.e(TAG, soundModelId + " is not loaded"); 361 362 sEventLogger.log(new SoundTriggerLogger.StringEvent( 363 "startRecognitionForService():" + soundModelId + " is not loaded")); 364 365 return STATUS_ERROR; 366 } 367 IRecognitionStatusCallback existingCallback = null; 368 synchronized (mCallbacksLock) { 369 existingCallback = mCallbacks.get(soundModelId.getUuid()); 370 } 371 if (existingCallback != null) { 372 Slog.e(TAG, soundModelId + " is already running"); 373 374 sEventLogger.log(new SoundTriggerLogger.StringEvent( 375 "startRecognitionForService():" 376 + soundModelId + " is already running")); 377 378 return STATUS_ERROR; 379 } 380 int ret; 381 switch (soundModel.type) { 382 case SoundModel.TYPE_GENERIC_SOUND: 383 ret = mSoundTriggerHelper.startGenericRecognition(soundModel.uuid, 384 (GenericSoundModel) soundModel, callback, config); 385 break; 386 default: 387 Slog.e(TAG, "Unknown model type"); 388 389 sEventLogger.log(new SoundTriggerLogger.StringEvent( 390 "startRecognitionForService(): Unknown model type")); 391 392 return STATUS_ERROR; 393 } 394 395 if (ret != STATUS_OK) { 396 Slog.e(TAG, "Failed to start model: " + ret); 397 398 sEventLogger.log(new SoundTriggerLogger.StringEvent( 399 "startRecognitionForService(): Failed to start model:")); 400 401 return ret; 402 } 403 synchronized (mCallbacksLock) { 404 mCallbacks.put(soundModelId.getUuid(), callback); 405 } 406 } 407 return STATUS_OK; 408 } 409 410 @Override stopRecognitionForService(ParcelUuid soundModelId)411 public int stopRecognitionForService(ParcelUuid soundModelId) { 412 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER); 413 if (!isInitialized()) return STATUS_ERROR; 414 if (DEBUG) { 415 Slog.i(TAG, "stopRecognition(): id = " + soundModelId); 416 } 417 418 sEventLogger.log(new SoundTriggerLogger.StringEvent( 419 "stopRecognitionForService(): id = " + soundModelId)); 420 421 synchronized (mLock) { 422 SoundModel soundModel = mLoadedModels.get(soundModelId.getUuid()); 423 if (soundModel == null) { 424 Slog.e(TAG, soundModelId + " is not loaded"); 425 426 sEventLogger.log(new SoundTriggerLogger.StringEvent( 427 "stopRecognitionForService(): " + soundModelId 428 + " is not loaded")); 429 430 return STATUS_ERROR; 431 } 432 IRecognitionStatusCallback callback = null; 433 synchronized (mCallbacksLock) { 434 callback = mCallbacks.get(soundModelId.getUuid()); 435 } 436 if (callback == null) { 437 Slog.e(TAG, soundModelId + " is not running"); 438 439 sEventLogger.log(new SoundTriggerLogger.StringEvent( 440 "stopRecognitionForService(): " + soundModelId 441 + " is not running")); 442 443 return STATUS_ERROR; 444 } 445 int ret; 446 switch (soundModel.type) { 447 case SoundModel.TYPE_GENERIC_SOUND: 448 ret = mSoundTriggerHelper.stopGenericRecognition(soundModel.uuid, callback); 449 break; 450 default: 451 Slog.e(TAG, "Unknown model type"); 452 453 sEventLogger.log(new SoundTriggerLogger.StringEvent( 454 "stopRecognitionForService(): Unknown model type")); 455 456 return STATUS_ERROR; 457 } 458 459 if (ret != STATUS_OK) { 460 Slog.e(TAG, "Failed to stop model: " + ret); 461 462 sEventLogger.log(new SoundTriggerLogger.StringEvent( 463 "stopRecognitionForService(): Failed to stop model: " + ret)); 464 465 return ret; 466 } 467 synchronized (mCallbacksLock) { 468 mCallbacks.remove(soundModelId.getUuid()); 469 } 470 } 471 return STATUS_OK; 472 } 473 474 @Override unloadSoundModel(ParcelUuid soundModelId)475 public int unloadSoundModel(ParcelUuid soundModelId) { 476 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER); 477 if (!isInitialized()) return STATUS_ERROR; 478 if (DEBUG) { 479 Slog.i(TAG, "unloadSoundModel(): id = " + soundModelId); 480 } 481 482 sEventLogger.log(new SoundTriggerLogger.StringEvent("unloadSoundModel(): id = " 483 + soundModelId)); 484 485 synchronized (mLock) { 486 SoundModel soundModel = mLoadedModels.get(soundModelId.getUuid()); 487 if (soundModel == null) { 488 Slog.e(TAG, soundModelId + " is not loaded"); 489 490 sEventLogger.log(new SoundTriggerLogger.StringEvent( 491 "unloadSoundModel(): " + soundModelId + " is not loaded")); 492 493 return STATUS_ERROR; 494 } 495 int ret; 496 switch (soundModel.type) { 497 case SoundModel.TYPE_KEYPHRASE: 498 ret = mSoundTriggerHelper.unloadKeyphraseSoundModel( 499 ((KeyphraseSoundModel)soundModel).keyphrases[0].id); 500 break; 501 case SoundModel.TYPE_GENERIC_SOUND: 502 ret = mSoundTriggerHelper.unloadGenericSoundModel(soundModel.uuid); 503 break; 504 default: 505 Slog.e(TAG, "Unknown model type"); 506 507 sEventLogger.log(new SoundTriggerLogger.StringEvent( 508 "unloadSoundModel(): Unknown model type")); 509 510 return STATUS_ERROR; 511 } 512 if (ret != STATUS_OK) { 513 Slog.e(TAG, "Failed to unload model"); 514 515 sEventLogger.log(new SoundTriggerLogger.StringEvent( 516 "unloadSoundModel(): Failed to unload model")); 517 518 return ret; 519 } 520 mLoadedModels.remove(soundModelId.getUuid()); 521 return STATUS_OK; 522 } 523 } 524 525 @Override isRecognitionActive(ParcelUuid parcelUuid)526 public boolean isRecognitionActive(ParcelUuid parcelUuid) { 527 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER); 528 if (!isInitialized()) return false; 529 synchronized (mCallbacksLock) { 530 IRecognitionStatusCallback callback = mCallbacks.get(parcelUuid.getUuid()); 531 if (callback == null) { 532 return false; 533 } 534 } 535 return mSoundTriggerHelper.isRecognitionRequested(parcelUuid.getUuid()); 536 } 537 538 @Override getModelState(ParcelUuid soundModelId)539 public int getModelState(ParcelUuid soundModelId) { 540 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER); 541 int ret = STATUS_ERROR; 542 if (!isInitialized()) return ret; 543 if (DEBUG) { 544 Slog.i(TAG, "getModelState(): id = " + soundModelId); 545 } 546 547 sEventLogger.log(new SoundTriggerLogger.StringEvent("getModelState(): id = " 548 + soundModelId)); 549 550 synchronized (mLock) { 551 SoundModel soundModel = mLoadedModels.get(soundModelId.getUuid()); 552 if (soundModel == null) { 553 Slog.e(TAG, soundModelId + " is not loaded"); 554 555 sEventLogger.log(new SoundTriggerLogger.StringEvent("getModelState(): " 556 + soundModelId + " is not loaded")); 557 558 return ret; 559 } 560 switch (soundModel.type) { 561 case SoundModel.TYPE_GENERIC_SOUND: 562 ret = mSoundTriggerHelper.getGenericModelState(soundModel.uuid); 563 break; 564 default: 565 // SoundModel.TYPE_KEYPHRASE is not supported to increase privacy. 566 Slog.e(TAG, "Unsupported model type, " + soundModel.type); 567 sEventLogger.log(new SoundTriggerLogger.StringEvent( 568 "getModelState(): Unsupported model type, " 569 + soundModel.type)); 570 break; 571 } 572 573 return ret; 574 } 575 } 576 } 577 578 /** 579 * Counts the number of operations added in the last 24 hours. 580 */ 581 private static class NumOps { 582 private final Object mLock = new Object(); 583 584 @GuardedBy("mLock") 585 private int[] mNumOps = new int[24]; 586 @GuardedBy("mLock") 587 private long mLastOpsHourSinceBoot; 588 589 /** 590 * Clear buckets of new hours that have elapsed since last operation. 591 * 592 * <p>I.e. when the last operation was triggered at 1:40 and the current operation was 593 * triggered at 4:03, the buckets "2, 3, and 4" are cleared. 594 * 595 * @param currentTime Current elapsed time since boot in ns 596 */ clearOldOps(long currentTime)597 void clearOldOps(long currentTime) { 598 synchronized (mLock) { 599 long numHoursSinceBoot = TimeUnit.HOURS.convert(currentTime, TimeUnit.NANOSECONDS); 600 601 // Clear buckets of new hours that have elapsed since last operation 602 // I.e. when the last operation was triggered at 1:40 and the current 603 // operation was triggered at 4:03, the bucket "2, 3, and 4" is cleared 604 if (mLastOpsHourSinceBoot != 0) { 605 for (long hour = mLastOpsHourSinceBoot + 1; hour <= numHoursSinceBoot; hour++) { 606 mNumOps[(int) (hour % 24)] = 0; 607 } 608 } 609 } 610 } 611 612 /** 613 * Add a new operation. 614 * 615 * @param currentTime Current elapsed time since boot in ns 616 */ addOp(long currentTime)617 void addOp(long currentTime) { 618 synchronized (mLock) { 619 long numHoursSinceBoot = TimeUnit.HOURS.convert(currentTime, TimeUnit.NANOSECONDS); 620 621 mNumOps[(int) (numHoursSinceBoot % 24)]++; 622 mLastOpsHourSinceBoot = numHoursSinceBoot; 623 } 624 } 625 626 /** 627 * Get the total operations added in the last 24 hours. 628 * 629 * @return The total number of operations added in the last 24 hours 630 */ getOpsAdded()631 int getOpsAdded() { 632 synchronized (mLock) { 633 int totalOperationsInLastDay = 0; 634 for (int i = 0; i < 24; i++) { 635 totalOperationsInLastDay += mNumOps[i]; 636 } 637 638 return totalOperationsInLastDay; 639 } 640 } 641 } 642 643 /** 644 * A single operation run in a {@link RemoteSoundTriggerDetectionService}. 645 * 646 * <p>Once the remote service is connected either setup + execute or setup + stop is executed. 647 */ 648 private static class Operation { 649 private interface ExecuteOp { run(int opId, ISoundTriggerDetectionService service)650 void run(int opId, ISoundTriggerDetectionService service) throws RemoteException; 651 } 652 653 private final @Nullable Runnable mSetupOp; 654 private final @NonNull ExecuteOp mExecuteOp; 655 private final @Nullable Runnable mDropOp; 656 Operation(@ullable Runnable setupOp, @NonNull ExecuteOp executeOp, @Nullable Runnable cancelOp)657 private Operation(@Nullable Runnable setupOp, @NonNull ExecuteOp executeOp, 658 @Nullable Runnable cancelOp) { 659 mSetupOp = setupOp; 660 mExecuteOp = executeOp; 661 mDropOp = cancelOp; 662 } 663 setup()664 private void setup() { 665 if (mSetupOp != null) { 666 mSetupOp.run(); 667 } 668 } 669 run(int opId, @NonNull ISoundTriggerDetectionService service)670 void run(int opId, @NonNull ISoundTriggerDetectionService service) throws RemoteException { 671 setup(); 672 mExecuteOp.run(opId, service); 673 } 674 drop()675 void drop() { 676 setup(); 677 678 if (mDropOp != null) { 679 mDropOp.run(); 680 } 681 } 682 } 683 684 /** 685 * Local end for a {@link SoundTriggerDetectionService}. Operations are queued up and executed 686 * when the service connects. 687 * 688 * <p>If operations take too long they are forcefully aborted. 689 * 690 * <p>This also limits the amount of operations in 24 hours. 691 */ 692 private class RemoteSoundTriggerDetectionService 693 extends IRecognitionStatusCallback.Stub implements ServiceConnection { 694 private static final int MSG_STOP_ALL_PENDING_OPERATIONS = 1; 695 696 private final Object mRemoteServiceLock = new Object(); 697 698 /** UUID of the model the service is started for */ 699 private final @NonNull ParcelUuid mPuuid; 700 /** Params passed into the start method for the service */ 701 private final @Nullable Bundle mParams; 702 /** Component name passed when starting the service */ 703 private final @NonNull ComponentName mServiceName; 704 /** User that started the service */ 705 private final @NonNull UserHandle mUser; 706 /** Configuration of the recognition the service is handling */ 707 private final @NonNull RecognitionConfig mRecognitionConfig; 708 /** Wake lock keeping the remote service alive */ 709 private final @NonNull PowerManager.WakeLock mRemoteServiceWakeLock; 710 711 private final @NonNull Handler mHandler; 712 713 /** Callbacks that are called by the service */ 714 private final @NonNull ISoundTriggerDetectionServiceClient mClient; 715 716 /** Operations that are pending because the service is not yet connected */ 717 @GuardedBy("mRemoteServiceLock") 718 private final ArrayList<Operation> mPendingOps = new ArrayList<>(); 719 /** Operations that have been send to the service but have no yet finished */ 720 @GuardedBy("mRemoteServiceLock") 721 private final ArraySet<Integer> mRunningOpIds = new ArraySet<>(); 722 /** The number of operations executed in each of the last 24 hours */ 723 private final NumOps mNumOps; 724 725 /** The service binder if connected */ 726 @GuardedBy("mRemoteServiceLock") 727 private @Nullable ISoundTriggerDetectionService mService; 728 /** Whether the service has been bound */ 729 @GuardedBy("mRemoteServiceLock") 730 private boolean mIsBound; 731 /** Whether the service has been destroyed */ 732 @GuardedBy("mRemoteServiceLock") 733 private boolean mIsDestroyed; 734 /** 735 * Set once a final op is scheduled. No further ops can be added and the service is 736 * destroyed once the op finishes. 737 */ 738 @GuardedBy("mRemoteServiceLock") 739 private boolean mDestroyOnceRunningOpsDone; 740 741 /** Total number of operations performed by this service */ 742 @GuardedBy("mRemoteServiceLock") 743 private int mNumTotalOpsPerformed; 744 745 /** 746 * Create a new remote sound trigger detection service. This only binds to the service when 747 * operations are in flight. Each operation has a certain time it can run. Once no 748 * operations are allowed to run anymore, {@link #stopAllPendingOperations() all operations 749 * are aborted and stopped} and the service is disconnected. 750 * 751 * @param modelUuid The UUID of the model the recognition is for 752 * @param params The params passed to each method of the service 753 * @param serviceName The component name of the service 754 * @param user The user of the service 755 * @param config The configuration of the recognition 756 */ RemoteSoundTriggerDetectionService(@onNull UUID modelUuid, @Nullable Bundle params, @NonNull ComponentName serviceName, @NonNull UserHandle user, @NonNull RecognitionConfig config)757 public RemoteSoundTriggerDetectionService(@NonNull UUID modelUuid, 758 @Nullable Bundle params, @NonNull ComponentName serviceName, @NonNull UserHandle user, 759 @NonNull RecognitionConfig config) { 760 mPuuid = new ParcelUuid(modelUuid); 761 mParams = params; 762 mServiceName = serviceName; 763 mUser = user; 764 mRecognitionConfig = config; 765 mHandler = new Handler(Looper.getMainLooper()); 766 767 PowerManager pm = ((PowerManager) mContext.getSystemService(Context.POWER_SERVICE)); 768 mRemoteServiceWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, 769 "RemoteSoundTriggerDetectionService " + mServiceName.getPackageName() + ":" 770 + mServiceName.getClassName()); 771 772 synchronized (mLock) { 773 NumOps numOps = mNumOpsPerPackage.get(mServiceName.getPackageName()); 774 if (numOps == null) { 775 numOps = new NumOps(); 776 mNumOpsPerPackage.put(mServiceName.getPackageName(), numOps); 777 } 778 mNumOps = numOps; 779 } 780 781 mClient = new ISoundTriggerDetectionServiceClient.Stub() { 782 @Override 783 public void onOpFinished(int opId) { 784 long token = Binder.clearCallingIdentity(); 785 try { 786 synchronized (mRemoteServiceLock) { 787 mRunningOpIds.remove(opId); 788 789 if (mRunningOpIds.isEmpty() && mPendingOps.isEmpty()) { 790 if (mDestroyOnceRunningOpsDone) { 791 destroy(); 792 } else { 793 disconnectLocked(); 794 } 795 } 796 } 797 } finally { 798 Binder.restoreCallingIdentity(token); 799 } 800 } 801 }; 802 } 803 804 @Override pingBinder()805 public boolean pingBinder() { 806 return !(mIsDestroyed || mDestroyOnceRunningOpsDone); 807 } 808 809 /** 810 * Disconnect from the service, but allow to re-connect when new operations are triggered. 811 */ 812 @GuardedBy("mRemoteServiceLock") disconnectLocked()813 private void disconnectLocked() { 814 if (mService != null) { 815 try { 816 mService.removeClient(mPuuid); 817 } catch (Exception e) { 818 Slog.e(TAG, mPuuid + ": Cannot remove client", e); 819 820 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid 821 + ": Cannot remove client")); 822 823 } 824 825 mService = null; 826 } 827 828 if (mIsBound) { 829 mContext.unbindService(RemoteSoundTriggerDetectionService.this); 830 mIsBound = false; 831 832 synchronized (mCallbacksLock) { 833 mRemoteServiceWakeLock.release(); 834 } 835 } 836 } 837 838 /** 839 * Disconnect, do not allow to reconnect to the service. All further operations will be 840 * dropped. 841 */ destroy()842 private void destroy() { 843 if (DEBUG) Slog.v(TAG, mPuuid + ": destroy"); 844 845 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid + ": destroy")); 846 847 synchronized (mRemoteServiceLock) { 848 disconnectLocked(); 849 850 mIsDestroyed = true; 851 } 852 853 // The callback is removed before the flag is set 854 if (!mDestroyOnceRunningOpsDone) { 855 synchronized (mCallbacksLock) { 856 mCallbacks.remove(mPuuid.getUuid()); 857 } 858 } 859 } 860 861 /** 862 * Stop all pending operations and then disconnect for the service. 863 */ stopAllPendingOperations()864 private void stopAllPendingOperations() { 865 synchronized (mRemoteServiceLock) { 866 if (mIsDestroyed) { 867 return; 868 } 869 870 if (mService != null) { 871 int numOps = mRunningOpIds.size(); 872 for (int i = 0; i < numOps; i++) { 873 try { 874 mService.onStopOperation(mPuuid, mRunningOpIds.valueAt(i)); 875 } catch (Exception e) { 876 Slog.e(TAG, mPuuid + ": Could not stop operation " 877 + mRunningOpIds.valueAt(i), e); 878 879 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid 880 + ": Could not stop operation " + mRunningOpIds.valueAt(i))); 881 882 } 883 } 884 885 mRunningOpIds.clear(); 886 } 887 888 disconnectLocked(); 889 } 890 } 891 892 /** 893 * Verify that the service has the expected properties and then bind to the service 894 */ bind()895 private void bind() { 896 long token = Binder.clearCallingIdentity(); 897 try { 898 Intent i = new Intent(); 899 i.setComponent(mServiceName); 900 901 ResolveInfo ri = mContext.getPackageManager().resolveServiceAsUser(i, 902 GET_SERVICES | GET_META_DATA | MATCH_DEBUG_TRIAGED_MISSING, 903 mUser.getIdentifier()); 904 905 if (ri == null) { 906 Slog.w(TAG, mPuuid + ": " + mServiceName + " not found"); 907 908 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid 909 + ": " + mServiceName + " not found")); 910 911 return; 912 } 913 914 if (!BIND_SOUND_TRIGGER_DETECTION_SERVICE 915 .equals(ri.serviceInfo.permission)) { 916 Slog.w(TAG, mPuuid + ": " + mServiceName + " does not require " 917 + BIND_SOUND_TRIGGER_DETECTION_SERVICE); 918 919 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid 920 + ": " + mServiceName + " does not require " 921 + BIND_SOUND_TRIGGER_DETECTION_SERVICE)); 922 923 return; 924 } 925 926 mIsBound = mContext.bindServiceAsUser(i, this, 927 BIND_AUTO_CREATE | BIND_FOREGROUND_SERVICE, mUser); 928 929 if (mIsBound) { 930 mRemoteServiceWakeLock.acquire(); 931 } else { 932 Slog.w(TAG, mPuuid + ": Could not bind to " + mServiceName); 933 934 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid 935 + ": Could not bind to " + mServiceName)); 936 937 } 938 } finally { 939 Binder.restoreCallingIdentity(token); 940 } 941 } 942 943 /** 944 * Run an operation (i.e. send it do the service). If the service is not connected, this 945 * binds the service and then runs the operation once connected. 946 * 947 * @param op The operation to run 948 */ runOrAddOperation(Operation op)949 private void runOrAddOperation(Operation op) { 950 synchronized (mRemoteServiceLock) { 951 if (mIsDestroyed || mDestroyOnceRunningOpsDone) { 952 Slog.w(TAG, mPuuid + ": Dropped operation as already destroyed or marked for " 953 + "destruction"); 954 955 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid 956 + ":Dropped operation as already destroyed or marked for destruction")); 957 958 op.drop(); 959 return; 960 } 961 962 if (mService == null) { 963 mPendingOps.add(op); 964 965 if (!mIsBound) { 966 bind(); 967 } 968 } else { 969 long currentTime = System.nanoTime(); 970 mNumOps.clearOldOps(currentTime); 971 972 // Drop operation if too many were executed in the last 24 hours. 973 int opsAllowed = Settings.Global.getInt(mContext.getContentResolver(), 974 MAX_SOUND_TRIGGER_DETECTION_SERVICE_OPS_PER_DAY, 975 Integer.MAX_VALUE); 976 977 // As we currently cannot dropping an op safely, disable throttling 978 int opsAdded = mNumOps.getOpsAdded(); 979 if (false && mNumOps.getOpsAdded() >= opsAllowed) { 980 try { 981 if (DEBUG || opsAllowed + 10 > opsAdded) { 982 Slog.w(TAG, mPuuid + ": Dropped operation as too many operations " 983 + "were run in last 24 hours"); 984 985 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid 986 + ": Dropped operation as too many operations " 987 + "were run in last 24 hours")); 988 989 } 990 991 op.drop(); 992 } catch (Exception e) { 993 Slog.e(TAG, mPuuid + ": Could not drop operation", e); 994 995 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid 996 + ": Could not drop operation")); 997 998 } 999 } else { 1000 mNumOps.addOp(currentTime); 1001 1002 // Find a free opID 1003 int opId = mNumTotalOpsPerformed; 1004 do { 1005 mNumTotalOpsPerformed++; 1006 } while (mRunningOpIds.contains(opId)); 1007 1008 // Run OP 1009 try { 1010 if (DEBUG) Slog.v(TAG, mPuuid + ": runOp " + opId); 1011 1012 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid 1013 + ": runOp " + opId)); 1014 1015 op.run(opId, mService); 1016 mRunningOpIds.add(opId); 1017 } catch (Exception e) { 1018 Slog.e(TAG, mPuuid + ": Could not run operation " + opId, e); 1019 1020 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid 1021 + ": Could not run operation " + opId)); 1022 1023 } 1024 } 1025 1026 // Unbind from service if no operations are left (i.e. if the operation failed) 1027 if (mPendingOps.isEmpty() && mRunningOpIds.isEmpty()) { 1028 if (mDestroyOnceRunningOpsDone) { 1029 destroy(); 1030 } else { 1031 disconnectLocked(); 1032 } 1033 } else { 1034 mHandler.removeMessages(MSG_STOP_ALL_PENDING_OPERATIONS); 1035 mHandler.sendMessageDelayed(obtainMessage( 1036 RemoteSoundTriggerDetectionService::stopAllPendingOperations, this) 1037 .setWhat(MSG_STOP_ALL_PENDING_OPERATIONS), 1038 Settings.Global.getLong(mContext.getContentResolver(), 1039 SOUND_TRIGGER_DETECTION_SERVICE_OP_TIMEOUT, 1040 Long.MAX_VALUE)); 1041 } 1042 } 1043 } 1044 } 1045 1046 @Override onKeyphraseDetected(SoundTrigger.KeyphraseRecognitionEvent event)1047 public void onKeyphraseDetected(SoundTrigger.KeyphraseRecognitionEvent event) { 1048 Slog.w(TAG, mPuuid + "->" + mServiceName + ": IGNORED onKeyphraseDetected(" + event 1049 + ")"); 1050 1051 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid + "->" + mServiceName 1052 + ": IGNORED onKeyphraseDetected(" + event + ")")); 1053 1054 } 1055 1056 /** 1057 * Create an AudioRecord enough for starting and releasing the data buffered for the event. 1058 * 1059 * @param event The event that was received 1060 * @return The initialized AudioRecord 1061 */ createAudioRecordForEvent( @onNull SoundTrigger.GenericRecognitionEvent event)1062 private @NonNull AudioRecord createAudioRecordForEvent( 1063 @NonNull SoundTrigger.GenericRecognitionEvent event) { 1064 AudioAttributes.Builder attributesBuilder = new AudioAttributes.Builder(); 1065 attributesBuilder.setInternalCapturePreset(MediaRecorder.AudioSource.HOTWORD); 1066 AudioAttributes attributes = attributesBuilder.build(); 1067 1068 // Use same AudioFormat processing as in RecognitionEvent.fromParcel 1069 AudioFormat originalFormat = event.getCaptureFormat(); 1070 AudioFormat captureFormat = (new AudioFormat.Builder()) 1071 .setChannelMask(originalFormat.getChannelMask()) 1072 .setEncoding(originalFormat.getEncoding()) 1073 .setSampleRate(originalFormat.getSampleRate()) 1074 .build(); 1075 1076 int bufferSize = AudioRecord.getMinBufferSize( 1077 captureFormat.getSampleRate() == AudioFormat.SAMPLE_RATE_UNSPECIFIED 1078 ? AudioFormat.SAMPLE_RATE_HZ_MAX 1079 : captureFormat.getSampleRate(), 1080 captureFormat.getChannelCount() == 2 1081 ? AudioFormat.CHANNEL_IN_STEREO 1082 : AudioFormat.CHANNEL_IN_MONO, 1083 captureFormat.getEncoding()); 1084 1085 sEventLogger.log(new SoundTriggerLogger.StringEvent("createAudioRecordForEvent")); 1086 1087 return new AudioRecord(attributes, captureFormat, bufferSize, 1088 event.getCaptureSession()); 1089 } 1090 1091 @Override onGenericSoundTriggerDetected(SoundTrigger.GenericRecognitionEvent event)1092 public void onGenericSoundTriggerDetected(SoundTrigger.GenericRecognitionEvent event) { 1093 if (DEBUG) Slog.v(TAG, mPuuid + ": Generic sound trigger event: " + event); 1094 1095 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid 1096 + ": Generic sound trigger event: " + event)); 1097 1098 runOrAddOperation(new Operation( 1099 // always execute: 1100 () -> { 1101 if (!mRecognitionConfig.allowMultipleTriggers) { 1102 // Unregister this remoteService once op is done 1103 synchronized (mCallbacksLock) { 1104 mCallbacks.remove(mPuuid.getUuid()); 1105 } 1106 mDestroyOnceRunningOpsDone = true; 1107 } 1108 }, 1109 // execute if not throttled: 1110 (opId, service) -> service.onGenericRecognitionEvent(mPuuid, opId, event), 1111 // execute if throttled: 1112 () -> { 1113 if (event.isCaptureAvailable()) { 1114 AudioRecord capturedData = createAudioRecordForEvent(event); 1115 1116 // Currently we need to start and release the audio record to reset 1117 // the DSP even if we don't want to process the event 1118 capturedData.startRecording(); 1119 capturedData.release(); 1120 } 1121 })); 1122 } 1123 1124 @Override onError(int status)1125 public void onError(int status) { 1126 if (DEBUG) Slog.v(TAG, mPuuid + ": onError: " + status); 1127 1128 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid 1129 + ": onError: " + status)); 1130 1131 runOrAddOperation( 1132 new Operation( 1133 // always execute: 1134 () -> { 1135 // Unregister this remoteService once op is done 1136 synchronized (mCallbacksLock) { 1137 mCallbacks.remove(mPuuid.getUuid()); 1138 } 1139 mDestroyOnceRunningOpsDone = true; 1140 }, 1141 // execute if not throttled: 1142 (opId, service) -> service.onError(mPuuid, opId, status), 1143 // nothing to do if throttled 1144 null)); 1145 } 1146 1147 @Override onRecognitionPaused()1148 public void onRecognitionPaused() { 1149 Slog.i(TAG, mPuuid + "->" + mServiceName + ": IGNORED onRecognitionPaused"); 1150 1151 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid 1152 + "->" + mServiceName + ": IGNORED onRecognitionPaused")); 1153 1154 } 1155 1156 @Override onRecognitionResumed()1157 public void onRecognitionResumed() { 1158 Slog.i(TAG, mPuuid + "->" + mServiceName + ": IGNORED onRecognitionResumed"); 1159 1160 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid 1161 + "->" + mServiceName + ": IGNORED onRecognitionResumed")); 1162 1163 } 1164 1165 @Override onServiceConnected(ComponentName name, IBinder service)1166 public void onServiceConnected(ComponentName name, IBinder service) { 1167 if (DEBUG) Slog.v(TAG, mPuuid + ": onServiceConnected(" + service + ")"); 1168 1169 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid 1170 + ": onServiceConnected(" + service + ")")); 1171 1172 synchronized (mRemoteServiceLock) { 1173 mService = ISoundTriggerDetectionService.Stub.asInterface(service); 1174 1175 try { 1176 mService.setClient(mPuuid, mParams, mClient); 1177 } catch (Exception e) { 1178 Slog.e(TAG, mPuuid + ": Could not init " + mServiceName, e); 1179 return; 1180 } 1181 1182 while (!mPendingOps.isEmpty()) { 1183 runOrAddOperation(mPendingOps.remove(0)); 1184 } 1185 } 1186 } 1187 1188 @Override onServiceDisconnected(ComponentName name)1189 public void onServiceDisconnected(ComponentName name) { 1190 if (DEBUG) Slog.v(TAG, mPuuid + ": onServiceDisconnected"); 1191 1192 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid 1193 + ": onServiceDisconnected")); 1194 1195 synchronized (mRemoteServiceLock) { 1196 mService = null; 1197 } 1198 } 1199 1200 @Override onBindingDied(ComponentName name)1201 public void onBindingDied(ComponentName name) { 1202 if (DEBUG) Slog.v(TAG, mPuuid + ": onBindingDied"); 1203 1204 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid 1205 + ": onBindingDied")); 1206 1207 synchronized (mRemoteServiceLock) { 1208 destroy(); 1209 } 1210 } 1211 1212 @Override onNullBinding(ComponentName name)1213 public void onNullBinding(ComponentName name) { 1214 Slog.w(TAG, name + " for model " + mPuuid + " returned a null binding"); 1215 1216 sEventLogger.log(new SoundTriggerLogger.StringEvent(name + " for model " 1217 + mPuuid + " returned a null binding")); 1218 1219 synchronized (mRemoteServiceLock) { 1220 disconnectLocked(); 1221 } 1222 } 1223 } 1224 1225 public final class LocalSoundTriggerService extends SoundTriggerInternal { 1226 private final Context mContext; 1227 private SoundTriggerHelper mSoundTriggerHelper; 1228 LocalSoundTriggerService(Context context)1229 LocalSoundTriggerService(Context context) { 1230 mContext = context; 1231 } 1232 setSoundTriggerHelper(SoundTriggerHelper helper)1233 synchronized void setSoundTriggerHelper(SoundTriggerHelper helper) { 1234 mSoundTriggerHelper = helper; 1235 } 1236 1237 @Override startRecognition(int keyphraseId, KeyphraseSoundModel soundModel, IRecognitionStatusCallback listener, RecognitionConfig recognitionConfig)1238 public int startRecognition(int keyphraseId, KeyphraseSoundModel soundModel, 1239 IRecognitionStatusCallback listener, RecognitionConfig recognitionConfig) { 1240 if (!isInitialized()) return STATUS_ERROR; 1241 return mSoundTriggerHelper.startKeyphraseRecognition(keyphraseId, soundModel, listener, 1242 recognitionConfig); 1243 } 1244 1245 @Override stopRecognition(int keyphraseId, IRecognitionStatusCallback listener)1246 public synchronized int stopRecognition(int keyphraseId, IRecognitionStatusCallback listener) { 1247 if (!isInitialized()) return STATUS_ERROR; 1248 return mSoundTriggerHelper.stopKeyphraseRecognition(keyphraseId, listener); 1249 } 1250 1251 @Override getModuleProperties()1252 public ModuleProperties getModuleProperties() { 1253 if (!isInitialized()) return null; 1254 return mSoundTriggerHelper.getModuleProperties(); 1255 } 1256 1257 @Override unloadKeyphraseModel(int keyphraseId)1258 public int unloadKeyphraseModel(int keyphraseId) { 1259 if (!isInitialized()) return STATUS_ERROR; 1260 return mSoundTriggerHelper.unloadKeyphraseSoundModel(keyphraseId); 1261 } 1262 1263 @Override dump(FileDescriptor fd, PrintWriter pw, String[] args)1264 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 1265 if (!isInitialized()) return; 1266 mSoundTriggerHelper.dump(fd, pw, args); 1267 // log 1268 sEventLogger.dump(pw); 1269 } 1270 isInitialized()1271 private synchronized boolean isInitialized() { 1272 if (mSoundTriggerHelper == null ) { 1273 Slog.e(TAG, "SoundTriggerHelper not initialized."); 1274 1275 sEventLogger.log(new SoundTriggerLogger.StringEvent( 1276 "SoundTriggerHelper not initialized.")); 1277 1278 return false; 1279 } 1280 return true; 1281 } 1282 } 1283 enforceCallingPermission(String permission)1284 private void enforceCallingPermission(String permission) { 1285 if (mContext.checkCallingOrSelfPermission(permission) 1286 != PackageManager.PERMISSION_GRANTED) { 1287 throw new SecurityException("Caller does not hold the permission " + permission); 1288 } 1289 } 1290 1291 //================================================================= 1292 // For logging 1293 1294 private static final SoundTriggerLogger sEventLogger = new SoundTriggerLogger(200, 1295 "SoundTrigger activity"); 1296 1297 } 1298