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