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