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