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