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