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