OSDN Git Service

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