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