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