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