blob: 1dd3972b56b4452fca85f8952c9f9827049b2b0b [file] [log] [blame]
Arunesh Mishraa772e5f2016-01-25 10:33:11 -08001/*
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
17package com.android.server.soundtrigger;
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -080018
19import static android.Manifest.permission.BIND_SOUND_TRIGGER_DETECTION_SERVICE;
20import static android.content.Context.BIND_AUTO_CREATE;
21import static android.content.Context.BIND_FOREGROUND_SERVICE;
22import static android.content.pm.PackageManager.GET_META_DATA;
23import static android.content.pm.PackageManager.GET_SERVICES;
24import static android.content.pm.PackageManager.MATCH_DEBUG_TRIAGED_MISSING;
Arunesh Mishra3fff7f52016-02-09 12:15:19 -080025import static android.hardware.soundtrigger.SoundTrigger.STATUS_ERROR;
Chris Thorntonba08b792017-06-08 22:34:37 -070026import static android.hardware.soundtrigger.SoundTrigger.STATUS_OK;
Philip P. Moltmann7e25b3d2018-03-09 20:22:58 -080027import static android.provider.Settings.Global.MAX_SOUND_TRIGGER_DETECTION_SERVICE_OPS_PER_DAY;
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -080028import static android.provider.Settings.Global.SOUND_TRIGGER_DETECTION_SERVICE_OP_TIMEOUT;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -080029
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -080030import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
31
32import android.Manifest;
33import android.annotation.NonNull;
34import android.annotation.Nullable;
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -080035import android.content.ComponentName;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -080036import android.content.Context;
Chris Thorntonba08b792017-06-08 22:34:37 -070037import android.content.Intent;
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -080038import android.content.ServiceConnection;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -080039import android.content.pm.PackageManager;
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -080040import android.content.pm.ResolveInfo;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -080041import android.hardware.soundtrigger.IRecognitionStatusCallback;
42import android.hardware.soundtrigger.SoundTrigger;
Arunesh Mishrac722ec412016-01-27 13:29:12 -080043import android.hardware.soundtrigger.SoundTrigger.GenericSoundModel;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -080044import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel;
Arunesh Mishra55a9b002016-02-01 14:06:37 -080045import android.hardware.soundtrigger.SoundTrigger.ModuleProperties;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -080046import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig;
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -080047import android.hardware.soundtrigger.SoundTrigger.SoundModel;
Philip P. Moltmanna5b44032018-05-04 13:59:45 -070048import android.media.AudioAttributes;
49import android.media.AudioFormat;
50import android.media.AudioRecord;
51import android.media.MediaRecorder;
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -080052import android.media.soundtrigger.ISoundTriggerDetectionService;
53import android.media.soundtrigger.ISoundTriggerDetectionServiceClient;
54import android.media.soundtrigger.SoundTriggerDetectionService;
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -080055import android.os.Binder;
Chris Thorntonba08b792017-06-08 22:34:37 -070056import android.os.Bundle;
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -080057import android.os.Handler;
58import android.os.IBinder;
59import android.os.Looper;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -080060import android.os.Parcel;
61import android.os.ParcelUuid;
Chris Thorntonba08b792017-06-08 22:34:37 -070062import android.os.PowerManager;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -080063import android.os.RemoteException;
Benjamin Schwartz9e7a0152019-06-06 17:39:22 -070064import android.os.SystemClock;
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -080065import android.os.UserHandle;
66import android.provider.Settings;
Philip P. Moltmann7e25b3d2018-03-09 20:22:58 -080067import android.util.ArrayMap;
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -080068import android.util.ArraySet;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -080069import android.util.Slog;
70
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -080071import com.android.internal.annotations.GuardedBy;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -080072import com.android.internal.app.ISoundTriggerService;
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -080073import com.android.internal.util.Preconditions;
74import com.android.server.SystemService;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -080075
76import java.io.FileDescriptor;
77import java.io.PrintWriter;
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -080078import java.util.ArrayList;
Benjamin Schwartz9e7a0152019-06-06 17:39:22 -070079import java.util.Map;
Chris Thorntonba08b792017-06-08 22:34:37 -070080import java.util.TreeMap;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -080081import java.util.UUID;
Philip P. Moltmann7e25b3d2018-03-09 20:22:58 -080082import java.util.concurrent.TimeUnit;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -080083
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 */
93public class SoundTriggerService extends SystemService {
Arunesh Mishra3fff7f52016-02-09 12:15:19 -080094 private static final String TAG = "SoundTriggerService";
95 private static final boolean DEBUG = true;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -080096
97 final Context mContext;
Chris Thorntonba08b792017-06-08 22:34:37 -070098 private Object mLock;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -080099 private final SoundTriggerServiceStub mServiceStub;
100 private final LocalSoundTriggerService mLocalSoundTriggerService;
101 private SoundTriggerDbHelper mDbHelper;
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800102 private SoundTriggerHelper mSoundTriggerHelper;
Chris Thorntonba08b792017-06-08 22:34:37 -0700103 private final TreeMap<UUID, SoundModel> mLoadedModels;
Chris Thorntonae5fb992017-12-07 18:26:31 -0800104 private Object mCallbacksLock;
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800105 private final TreeMap<UUID, IRecognitionStatusCallback> mCallbacks;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800106
Benjamin Schwartz9e7a0152019-06-06 17:39:22 -0700107 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;
Philip P. Moltmann7e25b3d2018-03-09 20:22:58 -0800181 /** Number of ops run by the {@link RemoteSoundTriggerDetectionService} per package name */
182 @GuardedBy("mLock")
183 private final ArrayMap<String, NumOps> mNumOpsPerPackage = new ArrayMap<>();
184
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800185 public SoundTriggerService(Context context) {
186 super(context);
187 mContext = context;
188 mServiceStub = new SoundTriggerServiceStub();
189 mLocalSoundTriggerService = new LocalSoundTriggerService(context);
Chris Thorntonba08b792017-06-08 22:34:37 -0700190 mLoadedModels = new TreeMap<UUID, SoundModel>();
Chris Thorntonae5fb992017-12-07 18:26:31 -0800191 mCallbacksLock = new Object();
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800192 mCallbacks = new TreeMap<>();
Chris Thorntonba08b792017-06-08 22:34:37 -0700193 mLock = new Object();
Benjamin Schwartz9e7a0152019-06-06 17:39:22 -0700194 mSoundModelStatTracker = new SoundModelStatTracker();
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800195 }
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) {
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800206 initSoundTriggerHelper();
207 mLocalSoundTriggerService.setSoundTriggerHelper(mSoundTriggerHelper);
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800208 } 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
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800221 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
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800235 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
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800252 public int startRecognition(ParcelUuid parcelUuid, IRecognitionStatusCallback callback,
253 RecognitionConfig config) {
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800254 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
Arunesh Mishra2d1de782016-02-21 18:10:28 -0800255 if (!isInitialized()) return STATUS_ERROR;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800256 if (DEBUG) {
257 Slog.i(TAG, "startRecognition(): Uuid : " + parcelUuid);
258 }
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800259
Jason Hsu1363f582019-04-16 15:35:55 +0800260 sEventLogger.log(new SoundTriggerLogger.StringEvent("startRecognition(): Uuid : "
261 + parcelUuid));
262
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800263 GenericSoundModel model = getSoundModel(parcelUuid);
264 if (model == null) {
265 Slog.e(TAG, "Null model in database for id: " + parcelUuid);
Jason Hsu1363f582019-04-16 15:35:55 +0800266
267 sEventLogger.log(new SoundTriggerLogger.StringEvent(
268 "startRecognition(): Null model in database for id: " + parcelUuid));
269
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800270 return STATUS_ERROR;
271 }
272
Benjamin Schwartz9e7a0152019-06-06 17:39:22 -0700273 int ret = mSoundTriggerHelper.startGenericRecognition(parcelUuid.getUuid(), model,
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800274 callback, config);
Benjamin Schwartz9e7a0152019-06-06 17:39:22 -0700275 if (ret == STATUS_OK) {
276 mSoundModelStatTracker.onStart(parcelUuid.getUuid());
277 }
278 return ret;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800279 }
280
281 @Override
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800282 public int stopRecognition(ParcelUuid parcelUuid, IRecognitionStatusCallback callback) {
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800283 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
284 if (DEBUG) {
285 Slog.i(TAG, "stopRecognition(): Uuid : " + parcelUuid);
286 }
Jason Hsu1363f582019-04-16 15:35:55 +0800287
288 sEventLogger.log(new SoundTriggerLogger.StringEvent("stopRecognition(): Uuid : "
289 + parcelUuid));
290
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800291 if (!isInitialized()) return STATUS_ERROR;
Benjamin Schwartz9e7a0152019-06-06 17:39:22 -0700292
293 int ret = mSoundTriggerHelper.stopGenericRecognition(parcelUuid.getUuid(), callback);
294 if (ret == STATUS_OK) {
295 mSoundModelStatTracker.onStop(parcelUuid.getUuid());
296 }
297 return ret;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800298 }
299
300 @Override
Arunesh Mishrac722ec412016-01-27 13:29:12 -0800301 public SoundTrigger.GenericSoundModel getSoundModel(ParcelUuid soundModelId) {
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800302 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
303 if (DEBUG) {
304 Slog.i(TAG, "getSoundModel(): id = " + soundModelId);
305 }
Jason Hsu1363f582019-04-16 15:35:55 +0800306
307 sEventLogger.log(new SoundTriggerLogger.StringEvent("getSoundModel(): id = "
308 + soundModelId));
309
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800310 SoundTrigger.GenericSoundModel model = mDbHelper.getGenericSoundModel(
311 soundModelId.getUuid());
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800312 return model;
313 }
314
315 @Override
Arunesh Mishrac722ec412016-01-27 13:29:12 -0800316 public void updateSoundModel(SoundTrigger.GenericSoundModel soundModel) {
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800317 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
318 if (DEBUG) {
319 Slog.i(TAG, "updateSoundModel(): model = " + soundModel);
320 }
Jason Hsu1363f582019-04-16 15:35:55 +0800321
322 sEventLogger.log(new SoundTriggerLogger.StringEvent("updateSoundModel(): model = "
323 + soundModel));
324
Arunesh Mishrac722ec412016-01-27 13:29:12 -0800325 mDbHelper.updateGenericSoundModel(soundModel);
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800326 }
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 }
Jason Hsu1363f582019-04-16 15:35:55 +0800334
335 sEventLogger.log(new SoundTriggerLogger.StringEvent("deleteSoundModel(): id = "
336 + soundModelId));
337
Arunesh Mishra2d1de782016-02-21 18:10:28 -0800338 // Unload the model if it is loaded.
339 mSoundTriggerHelper.unloadGenericSoundModel(soundModelId.getUuid());
Arunesh Mishrac722ec412016-01-27 13:29:12 -0800340 mDbHelper.deleteGenericSoundModel(soundModelId.getUuid());
Benjamin Schwartz9e7a0152019-06-06 17:39:22 -0700341
342 // Stop recognition if it is started.
343 mSoundModelStatTracker.onStop(soundModelId.getUuid());
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800344 }
Chris Thorntonba08b792017-06-08 22:34:37 -0700345
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");
Jason Hsu1363f582019-04-16 15:35:55 +0800352
353 sEventLogger.log(new SoundTriggerLogger.StringEvent(
354 "loadGenericSoundModel(): Invalid sound model"));
355
Chris Thorntonba08b792017-06-08 22:34:37 -0700356 return STATUS_ERROR;
357 }
358 if (DEBUG) {
359 Slog.i(TAG, "loadGenericSoundModel(): id = " + soundModel.uuid);
360 }
Jason Hsu1363f582019-04-16 15:35:55 +0800361
362 sEventLogger.log(new SoundTriggerLogger.StringEvent("loadGenericSoundModel(): id = "
363 + soundModel.uuid));
364
Chris Thorntonba08b792017-06-08 22:34:37 -0700365 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);
Chris Thorntonae5fb992017-12-07 18:26:31 -0800372 synchronized (mCallbacksLock) {
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800373 mCallbacks.remove(soundModel.uuid);
Chris Thorntonae5fb992017-12-07 18:26:31 -0800374 }
Chris Thorntonba08b792017-06-08 22:34:37 -0700375 }
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");
Jason Hsu1363f582019-04-16 15:35:55 +0800387
388 sEventLogger.log(new SoundTriggerLogger.StringEvent(
389 "loadKeyphraseSoundModel(): Invalid sound model"));
390
Chris Thorntonba08b792017-06-08 22:34:37 -0700391 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.");
Jason Hsu1363f582019-04-16 15:35:55 +0800395
396 sEventLogger.log(new SoundTriggerLogger.StringEvent(
397 "loadKeyphraseSoundModel(): Only one keyphrase per model"
398 + " is currently supported."));
399
Chris Thorntonba08b792017-06-08 22:34:37 -0700400 return STATUS_ERROR;
401 }
402 if (DEBUG) {
403 Slog.i(TAG, "loadKeyphraseSoundModel(): id = " + soundModel.uuid);
404 }
Jason Hsu1363f582019-04-16 15:35:55 +0800405
406 sEventLogger.log(new SoundTriggerLogger.StringEvent("loadKeyphraseSoundModel(): id = "
407 + soundModel.uuid));
408
Chris Thorntonba08b792017-06-08 22:34:37 -0700409 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);
Chris Thorntonae5fb992017-12-07 18:26:31 -0800416 synchronized (mCallbacksLock) {
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800417 mCallbacks.remove(soundModel.uuid);
Chris Thorntonae5fb992017-12-07 18:26:31 -0800418 }
Chris Thorntonba08b792017-06-08 22:34:37 -0700419 }
420 mLoadedModels.put(soundModel.uuid, soundModel);
421 }
422 return STATUS_OK;
423 }
424
425 @Override
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800426 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
Chris Thorntonba08b792017-06-08 22:34:37 -0700432 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
Philip P. Moltmann19402f52018-03-27 14:38:03 -0700433
Chris Thorntonba08b792017-06-08 22:34:37 -0700434 if (!isInitialized()) return STATUS_ERROR;
435 if (DEBUG) {
436 Slog.i(TAG, "startRecognition(): id = " + soundModelId);
437 }
438
Jason Hsu1363f582019-04-16 15:35:55 +0800439 sEventLogger.log(new SoundTriggerLogger.StringEvent(
440 "startRecognitionForService(): id = " + soundModelId));
441
Philip P. Moltmann19402f52018-03-27 14:38:03 -0700442 IRecognitionStatusCallback callback =
443 new RemoteSoundTriggerDetectionService(soundModelId.getUuid(), params,
444 detectionService, Binder.getCallingUserHandle(), config);
445
Chris Thorntonba08b792017-06-08 22:34:37 -0700446 synchronized (mLock) {
447 SoundModel soundModel = mLoadedModels.get(soundModelId.getUuid());
448 if (soundModel == null) {
449 Slog.e(TAG, soundModelId + " is not loaded");
Jason Hsu1363f582019-04-16 15:35:55 +0800450
451 sEventLogger.log(new SoundTriggerLogger.StringEvent(
452 "startRecognitionForService():" + soundModelId + " is not loaded"));
453
Chris Thorntonba08b792017-06-08 22:34:37 -0700454 return STATUS_ERROR;
455 }
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800456 IRecognitionStatusCallback existingCallback = null;
Chris Thorntonae5fb992017-12-07 18:26:31 -0800457 synchronized (mCallbacksLock) {
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800458 existingCallback = mCallbacks.get(soundModelId.getUuid());
Chris Thorntonae5fb992017-12-07 18:26:31 -0800459 }
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800460 if (existingCallback != null) {
Chris Thorntonba08b792017-06-08 22:34:37 -0700461 Slog.e(TAG, soundModelId + " is already running");
Jason Hsu1363f582019-04-16 15:35:55 +0800462
463 sEventLogger.log(new SoundTriggerLogger.StringEvent(
464 "startRecognitionForService():"
465 + soundModelId + " is already running"));
466
Chris Thorntonba08b792017-06-08 22:34:37 -0700467 return STATUS_ERROR;
468 }
Chris Thorntonba08b792017-06-08 22:34:37 -0700469 int ret;
470 switch (soundModel.type) {
Chris Thorntonba08b792017-06-08 22:34:37 -0700471 case SoundModel.TYPE_GENERIC_SOUND:
472 ret = mSoundTriggerHelper.startGenericRecognition(soundModel.uuid,
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800473 (GenericSoundModel) soundModel, callback, config);
Chris Thorntonba08b792017-06-08 22:34:37 -0700474 break;
475 default:
476 Slog.e(TAG, "Unknown model type");
Jason Hsu1363f582019-04-16 15:35:55 +0800477
478 sEventLogger.log(new SoundTriggerLogger.StringEvent(
479 "startRecognitionForService(): Unknown model type"));
480
Chris Thorntonba08b792017-06-08 22:34:37 -0700481 return STATUS_ERROR;
482 }
483
484 if (ret != STATUS_OK) {
485 Slog.e(TAG, "Failed to start model: " + ret);
Jason Hsu1363f582019-04-16 15:35:55 +0800486
487 sEventLogger.log(new SoundTriggerLogger.StringEvent(
488 "startRecognitionForService(): Failed to start model:"));
489
Chris Thorntonba08b792017-06-08 22:34:37 -0700490 return ret;
491 }
Chris Thorntonae5fb992017-12-07 18:26:31 -0800492 synchronized (mCallbacksLock) {
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800493 mCallbacks.put(soundModelId.getUuid(), callback);
Chris Thorntonae5fb992017-12-07 18:26:31 -0800494 }
Benjamin Schwartz9e7a0152019-06-06 17:39:22 -0700495
496 mSoundModelStatTracker.onStart(soundModelId.getUuid());
Chris Thorntonba08b792017-06-08 22:34:37 -0700497 }
498 return STATUS_OK;
499 }
500
501 @Override
Philip P. Moltmann19402f52018-03-27 14:38:03 -0700502 public int stopRecognitionForService(ParcelUuid soundModelId) {
Chris Thorntonba08b792017-06-08 22:34:37 -0700503 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
504 if (!isInitialized()) return STATUS_ERROR;
505 if (DEBUG) {
506 Slog.i(TAG, "stopRecognition(): id = " + soundModelId);
507 }
508
Jason Hsu1363f582019-04-16 15:35:55 +0800509 sEventLogger.log(new SoundTriggerLogger.StringEvent(
510 "stopRecognitionForService(): id = " + soundModelId));
511
Chris Thorntonba08b792017-06-08 22:34:37 -0700512 synchronized (mLock) {
513 SoundModel soundModel = mLoadedModels.get(soundModelId.getUuid());
514 if (soundModel == null) {
515 Slog.e(TAG, soundModelId + " is not loaded");
Jason Hsu1363f582019-04-16 15:35:55 +0800516
517 sEventLogger.log(new SoundTriggerLogger.StringEvent(
518 "stopRecognitionForService(): " + soundModelId
519 + " is not loaded"));
520
Chris Thorntonba08b792017-06-08 22:34:37 -0700521 return STATUS_ERROR;
522 }
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800523 IRecognitionStatusCallback callback = null;
Chris Thorntonae5fb992017-12-07 18:26:31 -0800524 synchronized (mCallbacksLock) {
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800525 callback = mCallbacks.get(soundModelId.getUuid());
Chris Thorntonae5fb992017-12-07 18:26:31 -0800526 }
Chris Thorntonba08b792017-06-08 22:34:37 -0700527 if (callback == null) {
528 Slog.e(TAG, soundModelId + " is not running");
Jason Hsu1363f582019-04-16 15:35:55 +0800529
530 sEventLogger.log(new SoundTriggerLogger.StringEvent(
531 "stopRecognitionForService(): " + soundModelId
532 + " is not running"));
533
Chris Thorntonba08b792017-06-08 22:34:37 -0700534 return STATUS_ERROR;
535 }
536 int ret;
537 switch (soundModel.type) {
Chris Thorntonba08b792017-06-08 22:34:37 -0700538 case SoundModel.TYPE_GENERIC_SOUND:
539 ret = mSoundTriggerHelper.stopGenericRecognition(soundModel.uuid, callback);
540 break;
541 default:
542 Slog.e(TAG, "Unknown model type");
Jason Hsu1363f582019-04-16 15:35:55 +0800543
544 sEventLogger.log(new SoundTriggerLogger.StringEvent(
545 "stopRecognitionForService(): Unknown model type"));
546
Chris Thorntonba08b792017-06-08 22:34:37 -0700547 return STATUS_ERROR;
548 }
549
550 if (ret != STATUS_OK) {
551 Slog.e(TAG, "Failed to stop model: " + ret);
Jason Hsu1363f582019-04-16 15:35:55 +0800552
553 sEventLogger.log(new SoundTriggerLogger.StringEvent(
554 "stopRecognitionForService(): Failed to stop model: " + ret));
555
Chris Thorntonba08b792017-06-08 22:34:37 -0700556 return ret;
557 }
Chris Thorntonae5fb992017-12-07 18:26:31 -0800558 synchronized (mCallbacksLock) {
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800559 mCallbacks.remove(soundModelId.getUuid());
Chris Thorntonae5fb992017-12-07 18:26:31 -0800560 }
Benjamin Schwartz9e7a0152019-06-06 17:39:22 -0700561
562 mSoundModelStatTracker.onStop(soundModelId.getUuid());
Chris Thorntonba08b792017-06-08 22:34:37 -0700563 }
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
Jason Hsu1363f582019-04-16 15:35:55 +0800575 sEventLogger.log(new SoundTriggerLogger.StringEvent("unloadSoundModel(): id = "
576 + soundModelId));
577
Chris Thorntonba08b792017-06-08 22:34:37 -0700578 synchronized (mLock) {
579 SoundModel soundModel = mLoadedModels.get(soundModelId.getUuid());
580 if (soundModel == null) {
581 Slog.e(TAG, soundModelId + " is not loaded");
Jason Hsu1363f582019-04-16 15:35:55 +0800582
583 sEventLogger.log(new SoundTriggerLogger.StringEvent(
584 "unloadSoundModel(): " + soundModelId + " is not loaded"));
585
Chris Thorntonba08b792017-06-08 22:34:37 -0700586 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");
Jason Hsu1363f582019-04-16 15:35:55 +0800599
600 sEventLogger.log(new SoundTriggerLogger.StringEvent(
601 "unloadSoundModel(): Unknown model type"));
602
Chris Thorntonba08b792017-06-08 22:34:37 -0700603 return STATUS_ERROR;
604 }
605 if (ret != STATUS_OK) {
606 Slog.e(TAG, "Failed to unload model");
Jason Hsu1363f582019-04-16 15:35:55 +0800607
608 sEventLogger.log(new SoundTriggerLogger.StringEvent(
609 "unloadSoundModel(): Failed to unload model"));
610
Chris Thorntonba08b792017-06-08 22:34:37 -0700611 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;
Chris Thorntonae5fb992017-12-07 18:26:31 -0800622 synchronized (mCallbacksLock) {
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800623 IRecognitionStatusCallback callback = mCallbacks.get(parcelUuid.getUuid());
Chris Thorntonba08b792017-06-08 22:34:37 -0700624 if (callback == null) {
625 return false;
626 }
Chris Thorntonba08b792017-06-08 22:34:37 -0700627 }
Chris Thorntonae5fb992017-12-07 18:26:31 -0800628 return mSoundTriggerHelper.isRecognitionRequested(parcelUuid.getUuid());
Chris Thorntonba08b792017-06-08 22:34:37 -0700629 }
Michael Dooley291751e2018-10-16 19:53:29 +0000630
631 @Override
mike dooleyb2ab04a2018-11-07 15:48:54 +0100632 public int getModelState(ParcelUuid soundModelId) {
Michael Dooley291751e2018-10-16 19:53:29 +0000633 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
mike dooleyb2ab04a2018-11-07 15:48:54 +0100634 int ret = STATUS_ERROR;
635 if (!isInitialized()) return ret;
Michael Dooley291751e2018-10-16 19:53:29 +0000636 if (DEBUG) {
637 Slog.i(TAG, "getModelState(): id = " + soundModelId);
638 }
639
Jason Hsu1363f582019-04-16 15:35:55 +0800640 sEventLogger.log(new SoundTriggerLogger.StringEvent("getModelState(): id = "
641 + soundModelId));
642
Michael Dooley291751e2018-10-16 19:53:29 +0000643 synchronized (mLock) {
644 SoundModel soundModel = mLoadedModels.get(soundModelId.getUuid());
645 if (soundModel == null) {
646 Slog.e(TAG, soundModelId + " is not loaded");
Jason Hsu1363f582019-04-16 15:35:55 +0800647
648 sEventLogger.log(new SoundTriggerLogger.StringEvent("getModelState(): "
649 + soundModelId + " is not loaded"));
650
mike dooleyb2ab04a2018-11-07 15:48:54 +0100651 return ret;
Michael Dooley291751e2018-10-16 19:53:29 +0000652 }
Michael Dooley291751e2018-10-16 19:53:29 +0000653 switch (soundModel.type) {
Michael Dooley291751e2018-10-16 19:53:29 +0000654 case SoundModel.TYPE_GENERIC_SOUND:
655 ret = mSoundTriggerHelper.getGenericModelState(soundModel.uuid);
656 break;
657 default:
mike dooleybaa22c72019-05-15 10:05:25 +0200658 // SoundModel.TYPE_KEYPHRASE is not supported to increase privacy.
659 Slog.e(TAG, "Unsupported model type, " + soundModel.type);
Jason Hsu1363f582019-04-16 15:35:55 +0800660 sEventLogger.log(new SoundTriggerLogger.StringEvent(
mike dooleybaa22c72019-05-15 10:05:25 +0200661 "getModelState(): Unsupported model type, "
662 + soundModel.type));
Michael Dooley291751e2018-10-16 19:53:29 +0000663 break;
664 }
Michael Dooley291751e2018-10-16 19:53:29 +0000665
666 return ret;
667 }
668 }
Nicholas Ambur1aa4b4b2019-08-22 12:13:29 -0700669
670 @Override
671 @Nullable
672 public ModuleProperties getModuleProperties() {
673 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
674 if (!isInitialized()) return null;
675 if (DEBUG) {
676 Slog.i(TAG, "getModuleProperties()");
677 }
678
679 synchronized (mLock) {
680 ModuleProperties properties = mSoundTriggerHelper.getModuleProperties();
681 sEventLogger.log(new SoundTriggerLogger.StringEvent(
682 "getModuleProperties(): " + properties.toString()));
683 return properties;
684 }
685 }
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800686 }
687
Philip P. Moltmann7e25b3d2018-03-09 20:22:58 -0800688 /**
689 * Counts the number of operations added in the last 24 hours.
690 */
691 private static class NumOps {
692 private final Object mLock = new Object();
693
694 @GuardedBy("mLock")
695 private int[] mNumOps = new int[24];
696 @GuardedBy("mLock")
697 private long mLastOpsHourSinceBoot;
698
699 /**
700 * Clear buckets of new hours that have elapsed since last operation.
701 *
702 * <p>I.e. when the last operation was triggered at 1:40 and the current operation was
703 * triggered at 4:03, the buckets "2, 3, and 4" are cleared.
704 *
705 * @param currentTime Current elapsed time since boot in ns
706 */
707 void clearOldOps(long currentTime) {
708 synchronized (mLock) {
709 long numHoursSinceBoot = TimeUnit.HOURS.convert(currentTime, TimeUnit.NANOSECONDS);
710
711 // Clear buckets of new hours that have elapsed since last operation
712 // I.e. when the last operation was triggered at 1:40 and the current
713 // operation was triggered at 4:03, the bucket "2, 3, and 4" is cleared
714 if (mLastOpsHourSinceBoot != 0) {
715 for (long hour = mLastOpsHourSinceBoot + 1; hour <= numHoursSinceBoot; hour++) {
716 mNumOps[(int) (hour % 24)] = 0;
717 }
718 }
719 }
720 }
721
722 /**
723 * Add a new operation.
724 *
725 * @param currentTime Current elapsed time since boot in ns
726 */
727 void addOp(long currentTime) {
728 synchronized (mLock) {
729 long numHoursSinceBoot = TimeUnit.HOURS.convert(currentTime, TimeUnit.NANOSECONDS);
730
731 mNumOps[(int) (numHoursSinceBoot % 24)]++;
732 mLastOpsHourSinceBoot = numHoursSinceBoot;
733 }
734 }
735
736 /**
737 * Get the total operations added in the last 24 hours.
738 *
739 * @return The total number of operations added in the last 24 hours
740 */
741 int getOpsAdded() {
742 synchronized (mLock) {
743 int totalOperationsInLastDay = 0;
744 for (int i = 0; i < 24; i++) {
745 totalOperationsInLastDay += mNumOps[i];
746 }
747
748 return totalOperationsInLastDay;
749 }
750 }
751 }
752
Philip P. Moltmanna5b44032018-05-04 13:59:45 -0700753 /**
754 * A single operation run in a {@link RemoteSoundTriggerDetectionService}.
755 *
756 * <p>Once the remote service is connected either setup + execute or setup + stop is executed.
757 */
758 private static class Operation {
759 private interface ExecuteOp {
760 void run(int opId, ISoundTriggerDetectionService service) throws RemoteException;
761 }
762
763 private final @Nullable Runnable mSetupOp;
764 private final @NonNull ExecuteOp mExecuteOp;
765 private final @Nullable Runnable mDropOp;
766
767 private Operation(@Nullable Runnable setupOp, @NonNull ExecuteOp executeOp,
768 @Nullable Runnable cancelOp) {
769 mSetupOp = setupOp;
770 mExecuteOp = executeOp;
771 mDropOp = cancelOp;
772 }
773
774 private void setup() {
775 if (mSetupOp != null) {
776 mSetupOp.run();
777 }
778 }
779
780 void run(int opId, @NonNull ISoundTriggerDetectionService service) throws RemoteException {
781 setup();
782 mExecuteOp.run(opId, service);
783 }
784
785 void drop() {
786 setup();
787
788 if (mDropOp != null) {
789 mDropOp.run();
790 }
791 }
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800792 }
793
794 /**
795 * Local end for a {@link SoundTriggerDetectionService}. Operations are queued up and executed
796 * when the service connects.
797 *
798 * <p>If operations take too long they are forcefully aborted.
799 *
800 * <p>This also limits the amount of operations in 24 hours.
801 */
802 private class RemoteSoundTriggerDetectionService
803 extends IRecognitionStatusCallback.Stub implements ServiceConnection {
804 private static final int MSG_STOP_ALL_PENDING_OPERATIONS = 1;
805
806 private final Object mRemoteServiceLock = new Object();
807
808 /** UUID of the model the service is started for */
809 private final @NonNull ParcelUuid mPuuid;
810 /** Params passed into the start method for the service */
811 private final @Nullable Bundle mParams;
812 /** Component name passed when starting the service */
813 private final @NonNull ComponentName mServiceName;
814 /** User that started the service */
815 private final @NonNull UserHandle mUser;
816 /** Configuration of the recognition the service is handling */
817 private final @NonNull RecognitionConfig mRecognitionConfig;
818 /** Wake lock keeping the remote service alive */
819 private final @NonNull PowerManager.WakeLock mRemoteServiceWakeLock;
820
821 private final @NonNull Handler mHandler;
822
823 /** Callbacks that are called by the service */
824 private final @NonNull ISoundTriggerDetectionServiceClient mClient;
825
826 /** Operations that are pending because the service is not yet connected */
827 @GuardedBy("mRemoteServiceLock")
828 private final ArrayList<Operation> mPendingOps = new ArrayList<>();
829 /** Operations that have been send to the service but have no yet finished */
830 @GuardedBy("mRemoteServiceLock")
831 private final ArraySet<Integer> mRunningOpIds = new ArraySet<>();
Philip P. Moltmann7e25b3d2018-03-09 20:22:58 -0800832 /** The number of operations executed in each of the last 24 hours */
833 private final NumOps mNumOps;
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800834
835 /** The service binder if connected */
836 @GuardedBy("mRemoteServiceLock")
837 private @Nullable ISoundTriggerDetectionService mService;
838 /** Whether the service has been bound */
839 @GuardedBy("mRemoteServiceLock")
840 private boolean mIsBound;
841 /** Whether the service has been destroyed */
842 @GuardedBy("mRemoteServiceLock")
843 private boolean mIsDestroyed;
844 /**
845 * Set once a final op is scheduled. No further ops can be added and the service is
846 * destroyed once the op finishes.
847 */
848 @GuardedBy("mRemoteServiceLock")
849 private boolean mDestroyOnceRunningOpsDone;
850
851 /** Total number of operations performed by this service */
852 @GuardedBy("mRemoteServiceLock")
853 private int mNumTotalOpsPerformed;
854
855 /**
856 * Create a new remote sound trigger detection service. This only binds to the service when
857 * operations are in flight. Each operation has a certain time it can run. Once no
858 * operations are allowed to run anymore, {@link #stopAllPendingOperations() all operations
859 * are aborted and stopped} and the service is disconnected.
860 *
861 * @param modelUuid The UUID of the model the recognition is for
862 * @param params The params passed to each method of the service
863 * @param serviceName The component name of the service
864 * @param user The user of the service
865 * @param config The configuration of the recognition
866 */
867 public RemoteSoundTriggerDetectionService(@NonNull UUID modelUuid,
868 @Nullable Bundle params, @NonNull ComponentName serviceName, @NonNull UserHandle user,
869 @NonNull RecognitionConfig config) {
870 mPuuid = new ParcelUuid(modelUuid);
871 mParams = params;
872 mServiceName = serviceName;
873 mUser = user;
874 mRecognitionConfig = config;
875 mHandler = new Handler(Looper.getMainLooper());
876
877 PowerManager pm = ((PowerManager) mContext.getSystemService(Context.POWER_SERVICE));
878 mRemoteServiceWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
879 "RemoteSoundTriggerDetectionService " + mServiceName.getPackageName() + ":"
880 + mServiceName.getClassName());
881
Philip P. Moltmann7e25b3d2018-03-09 20:22:58 -0800882 synchronized (mLock) {
883 NumOps numOps = mNumOpsPerPackage.get(mServiceName.getPackageName());
884 if (numOps == null) {
885 numOps = new NumOps();
886 mNumOpsPerPackage.put(mServiceName.getPackageName(), numOps);
887 }
888 mNumOps = numOps;
889 }
890
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800891 mClient = new ISoundTriggerDetectionServiceClient.Stub() {
892 @Override
893 public void onOpFinished(int opId) {
894 long token = Binder.clearCallingIdentity();
895 try {
896 synchronized (mRemoteServiceLock) {
897 mRunningOpIds.remove(opId);
898
899 if (mRunningOpIds.isEmpty() && mPendingOps.isEmpty()) {
900 if (mDestroyOnceRunningOpsDone) {
901 destroy();
902 } else {
903 disconnectLocked();
904 }
905 }
906 }
907 } finally {
908 Binder.restoreCallingIdentity(token);
909 }
910 }
911 };
912 }
913
914 @Override
915 public boolean pingBinder() {
916 return !(mIsDestroyed || mDestroyOnceRunningOpsDone);
917 }
918
919 /**
920 * Disconnect from the service, but allow to re-connect when new operations are triggered.
921 */
Andreas Gampe8ce7ed92018-09-05 16:53:00 -0700922 @GuardedBy("mRemoteServiceLock")
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800923 private void disconnectLocked() {
924 if (mService != null) {
925 try {
926 mService.removeClient(mPuuid);
927 } catch (Exception e) {
928 Slog.e(TAG, mPuuid + ": Cannot remove client", e);
Jason Hsu1363f582019-04-16 15:35:55 +0800929
930 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
931 + ": Cannot remove client"));
932
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800933 }
934
935 mService = null;
936 }
937
938 if (mIsBound) {
939 mContext.unbindService(RemoteSoundTriggerDetectionService.this);
940 mIsBound = false;
941
942 synchronized (mCallbacksLock) {
943 mRemoteServiceWakeLock.release();
944 }
945 }
946 }
947
948 /**
949 * Disconnect, do not allow to reconnect to the service. All further operations will be
950 * dropped.
951 */
952 private void destroy() {
953 if (DEBUG) Slog.v(TAG, mPuuid + ": destroy");
954
Jason Hsu1363f582019-04-16 15:35:55 +0800955 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid + ": destroy"));
956
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800957 synchronized (mRemoteServiceLock) {
958 disconnectLocked();
959
960 mIsDestroyed = true;
961 }
962
963 // The callback is removed before the flag is set
964 if (!mDestroyOnceRunningOpsDone) {
965 synchronized (mCallbacksLock) {
966 mCallbacks.remove(mPuuid.getUuid());
967 }
968 }
969 }
970
971 /**
972 * Stop all pending operations and then disconnect for the service.
973 */
974 private void stopAllPendingOperations() {
975 synchronized (mRemoteServiceLock) {
976 if (mIsDestroyed) {
977 return;
978 }
979
980 if (mService != null) {
981 int numOps = mRunningOpIds.size();
982 for (int i = 0; i < numOps; i++) {
983 try {
984 mService.onStopOperation(mPuuid, mRunningOpIds.valueAt(i));
985 } catch (Exception e) {
986 Slog.e(TAG, mPuuid + ": Could not stop operation "
987 + mRunningOpIds.valueAt(i), e);
Jason Hsu1363f582019-04-16 15:35:55 +0800988
989 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
990 + ": Could not stop operation " + mRunningOpIds.valueAt(i)));
991
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800992 }
993 }
994
995 mRunningOpIds.clear();
996 }
997
998 disconnectLocked();
999 }
1000 }
1001
1002 /**
1003 * Verify that the service has the expected properties and then bind to the service
1004 */
1005 private void bind() {
1006 long token = Binder.clearCallingIdentity();
1007 try {
1008 Intent i = new Intent();
1009 i.setComponent(mServiceName);
1010
1011 ResolveInfo ri = mContext.getPackageManager().resolveServiceAsUser(i,
1012 GET_SERVICES | GET_META_DATA | MATCH_DEBUG_TRIAGED_MISSING,
1013 mUser.getIdentifier());
1014
1015 if (ri == null) {
1016 Slog.w(TAG, mPuuid + ": " + mServiceName + " not found");
Jason Hsu1363f582019-04-16 15:35:55 +08001017
1018 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
1019 + ": " + mServiceName + " not found"));
1020
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -08001021 return;
1022 }
1023
1024 if (!BIND_SOUND_TRIGGER_DETECTION_SERVICE
1025 .equals(ri.serviceInfo.permission)) {
1026 Slog.w(TAG, mPuuid + ": " + mServiceName + " does not require "
1027 + BIND_SOUND_TRIGGER_DETECTION_SERVICE);
Jason Hsu1363f582019-04-16 15:35:55 +08001028
1029 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
1030 + ": " + mServiceName + " does not require "
1031 + BIND_SOUND_TRIGGER_DETECTION_SERVICE));
1032
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -08001033 return;
1034 }
1035
1036 mIsBound = mContext.bindServiceAsUser(i, this,
1037 BIND_AUTO_CREATE | BIND_FOREGROUND_SERVICE, mUser);
1038
1039 if (mIsBound) {
1040 mRemoteServiceWakeLock.acquire();
1041 } else {
1042 Slog.w(TAG, mPuuid + ": Could not bind to " + mServiceName);
Jason Hsu1363f582019-04-16 15:35:55 +08001043
1044 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
1045 + ": Could not bind to " + mServiceName));
1046
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -08001047 }
1048 } finally {
1049 Binder.restoreCallingIdentity(token);
1050 }
1051 }
1052
1053 /**
1054 * Run an operation (i.e. send it do the service). If the service is not connected, this
1055 * binds the service and then runs the operation once connected.
1056 *
1057 * @param op The operation to run
1058 */
1059 private void runOrAddOperation(Operation op) {
1060 synchronized (mRemoteServiceLock) {
1061 if (mIsDestroyed || mDestroyOnceRunningOpsDone) {
Philip P. Moltmanna5b44032018-05-04 13:59:45 -07001062 Slog.w(TAG, mPuuid + ": Dropped operation as already destroyed or marked for "
1063 + "destruction");
1064
Jason Hsu1363f582019-04-16 15:35:55 +08001065 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
1066 + ":Dropped operation as already destroyed or marked for destruction"));
1067
Philip P. Moltmanna5b44032018-05-04 13:59:45 -07001068 op.drop();
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -08001069 return;
1070 }
1071
1072 if (mService == null) {
1073 mPendingOps.add(op);
1074
1075 if (!mIsBound) {
1076 bind();
1077 }
1078 } else {
Philip P. Moltmann7e25b3d2018-03-09 20:22:58 -08001079 long currentTime = System.nanoTime();
1080 mNumOps.clearOldOps(currentTime);
1081
1082 // Drop operation if too many were executed in the last 24 hours.
1083 int opsAllowed = Settings.Global.getInt(mContext.getContentResolver(),
1084 MAX_SOUND_TRIGGER_DETECTION_SERVICE_OPS_PER_DAY,
1085 Integer.MAX_VALUE);
1086
Philip P. Moltmann3d1683b2018-05-07 15:53:51 -07001087 // As we currently cannot dropping an op safely, disable throttling
Philip P. Moltmann7e25b3d2018-03-09 20:22:58 -08001088 int opsAdded = mNumOps.getOpsAdded();
Philip P. Moltmann3d1683b2018-05-07 15:53:51 -07001089 if (false && mNumOps.getOpsAdded() >= opsAllowed) {
Philip P. Moltmanna5b44032018-05-04 13:59:45 -07001090 try {
1091 if (DEBUG || opsAllowed + 10 > opsAdded) {
1092 Slog.w(TAG, mPuuid + ": Dropped operation as too many operations "
1093 + "were run in last 24 hours");
Jason Hsu1363f582019-04-16 15:35:55 +08001094
1095 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
1096 + ": Dropped operation as too many operations "
1097 + "were run in last 24 hours"));
1098
Philip P. Moltmanna5b44032018-05-04 13:59:45 -07001099 }
1100
1101 op.drop();
1102 } catch (Exception e) {
1103 Slog.e(TAG, mPuuid + ": Could not drop operation", e);
Jason Hsu1363f582019-04-16 15:35:55 +08001104
1105 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
1106 + ": Could not drop operation"));
1107
Philip P. Moltmann7e25b3d2018-03-09 20:22:58 -08001108 }
Philip P. Moltmann09dd8c42018-04-11 09:23:19 -07001109 } else {
1110 mNumOps.addOp(currentTime);
Philip P. Moltmann7e25b3d2018-03-09 20:22:58 -08001111
Philip P. Moltmann09dd8c42018-04-11 09:23:19 -07001112 // Find a free opID
1113 int opId = mNumTotalOpsPerformed;
1114 do {
1115 mNumTotalOpsPerformed++;
1116 } while (mRunningOpIds.contains(opId));
Philip P. Moltmann7e25b3d2018-03-09 20:22:58 -08001117
Philip P. Moltmann09dd8c42018-04-11 09:23:19 -07001118 // Run OP
1119 try {
1120 if (DEBUG) Slog.v(TAG, mPuuid + ": runOp " + opId);
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -08001121
Jason Hsu1363f582019-04-16 15:35:55 +08001122 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
1123 + ": runOp " + opId));
1124
Philip P. Moltmann09dd8c42018-04-11 09:23:19 -07001125 op.run(opId, mService);
1126 mRunningOpIds.add(opId);
1127 } catch (Exception e) {
1128 Slog.e(TAG, mPuuid + ": Could not run operation " + opId, e);
Jason Hsu1363f582019-04-16 15:35:55 +08001129
1130 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
1131 + ": Could not run operation " + opId));
1132
Philip P. Moltmann09dd8c42018-04-11 09:23:19 -07001133 }
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -08001134 }
1135
1136 // Unbind from service if no operations are left (i.e. if the operation failed)
1137 if (mPendingOps.isEmpty() && mRunningOpIds.isEmpty()) {
1138 if (mDestroyOnceRunningOpsDone) {
1139 destroy();
1140 } else {
1141 disconnectLocked();
1142 }
1143 } else {
1144 mHandler.removeMessages(MSG_STOP_ALL_PENDING_OPERATIONS);
1145 mHandler.sendMessageDelayed(obtainMessage(
1146 RemoteSoundTriggerDetectionService::stopAllPendingOperations, this)
1147 .setWhat(MSG_STOP_ALL_PENDING_OPERATIONS),
1148 Settings.Global.getLong(mContext.getContentResolver(),
1149 SOUND_TRIGGER_DETECTION_SERVICE_OP_TIMEOUT,
1150 Long.MAX_VALUE));
1151 }
1152 }
1153 }
1154 }
1155
1156 @Override
1157 public void onKeyphraseDetected(SoundTrigger.KeyphraseRecognitionEvent event) {
1158 Slog.w(TAG, mPuuid + "->" + mServiceName + ": IGNORED onKeyphraseDetected(" + event
1159 + ")");
Jason Hsu1363f582019-04-16 15:35:55 +08001160
1161 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid + "->" + mServiceName
1162 + ": IGNORED onKeyphraseDetected(" + event + ")"));
1163
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -08001164 }
1165
Philip P. Moltmanna5b44032018-05-04 13:59:45 -07001166 /**
1167 * Create an AudioRecord enough for starting and releasing the data buffered for the event.
1168 *
1169 * @param event The event that was received
1170 * @return The initialized AudioRecord
1171 */
1172 private @NonNull AudioRecord createAudioRecordForEvent(
1173 @NonNull SoundTrigger.GenericRecognitionEvent event) {
1174 AudioAttributes.Builder attributesBuilder = new AudioAttributes.Builder();
1175 attributesBuilder.setInternalCapturePreset(MediaRecorder.AudioSource.HOTWORD);
1176 AudioAttributes attributes = attributesBuilder.build();
1177
1178 // Use same AudioFormat processing as in RecognitionEvent.fromParcel
1179 AudioFormat originalFormat = event.getCaptureFormat();
1180 AudioFormat captureFormat = (new AudioFormat.Builder())
1181 .setChannelMask(originalFormat.getChannelMask())
1182 .setEncoding(originalFormat.getEncoding())
1183 .setSampleRate(originalFormat.getSampleRate())
1184 .build();
1185
1186 int bufferSize = AudioRecord.getMinBufferSize(
1187 captureFormat.getSampleRate() == AudioFormat.SAMPLE_RATE_UNSPECIFIED
1188 ? AudioFormat.SAMPLE_RATE_HZ_MAX
1189 : captureFormat.getSampleRate(),
1190 captureFormat.getChannelCount() == 2
1191 ? AudioFormat.CHANNEL_IN_STEREO
1192 : AudioFormat.CHANNEL_IN_MONO,
1193 captureFormat.getEncoding());
1194
Jason Hsu1363f582019-04-16 15:35:55 +08001195 sEventLogger.log(new SoundTriggerLogger.StringEvent("createAudioRecordForEvent"));
1196
Philip P. Moltmanna5b44032018-05-04 13:59:45 -07001197 return new AudioRecord(attributes, captureFormat, bufferSize,
1198 event.getCaptureSession());
1199 }
1200
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -08001201 @Override
1202 public void onGenericSoundTriggerDetected(SoundTrigger.GenericRecognitionEvent event) {
1203 if (DEBUG) Slog.v(TAG, mPuuid + ": Generic sound trigger event: " + event);
1204
Jason Hsu1363f582019-04-16 15:35:55 +08001205 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
1206 + ": Generic sound trigger event: " + event));
1207
Philip P. Moltmanna5b44032018-05-04 13:59:45 -07001208 runOrAddOperation(new Operation(
1209 // always execute:
1210 () -> {
mike dooley9b20c1c2019-01-23 10:38:17 +01001211 if (!mRecognitionConfig.allowMultipleTriggers) {
Philip P. Moltmanna5b44032018-05-04 13:59:45 -07001212 // Unregister this remoteService once op is done
1213 synchronized (mCallbacksLock) {
1214 mCallbacks.remove(mPuuid.getUuid());
1215 }
1216 mDestroyOnceRunningOpsDone = true;
1217 }
1218 },
1219 // execute if not throttled:
1220 (opId, service) -> service.onGenericRecognitionEvent(mPuuid, opId, event),
1221 // execute if throttled:
1222 () -> {
1223 if (event.isCaptureAvailable()) {
1224 AudioRecord capturedData = createAudioRecordForEvent(event);
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -08001225
Philip P. Moltmanna5b44032018-05-04 13:59:45 -07001226 // Currently we need to start and release the audio record to reset
1227 // the DSP even if we don't want to process the event
1228 capturedData.startRecording();
1229 capturedData.release();
1230 }
1231 }));
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -08001232 }
1233
1234 @Override
1235 public void onError(int status) {
1236 if (DEBUG) Slog.v(TAG, mPuuid + ": onError: " + status);
1237
Jason Hsu1363f582019-04-16 15:35:55 +08001238 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
1239 + ": onError: " + status));
1240
Philip P. Moltmanna5b44032018-05-04 13:59:45 -07001241 runOrAddOperation(
1242 new Operation(
1243 // always execute:
1244 () -> {
1245 // Unregister this remoteService once op is done
1246 synchronized (mCallbacksLock) {
1247 mCallbacks.remove(mPuuid.getUuid());
1248 }
1249 mDestroyOnceRunningOpsDone = true;
1250 },
1251 // execute if not throttled:
1252 (opId, service) -> service.onError(mPuuid, opId, status),
1253 // nothing to do if throttled
1254 null));
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -08001255 }
1256
1257 @Override
1258 public void onRecognitionPaused() {
1259 Slog.i(TAG, mPuuid + "->" + mServiceName + ": IGNORED onRecognitionPaused");
Jason Hsu1363f582019-04-16 15:35:55 +08001260
1261 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
1262 + "->" + mServiceName + ": IGNORED onRecognitionPaused"));
1263
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -08001264 }
1265
1266 @Override
1267 public void onRecognitionResumed() {
1268 Slog.i(TAG, mPuuid + "->" + mServiceName + ": IGNORED onRecognitionResumed");
Jason Hsu1363f582019-04-16 15:35:55 +08001269
1270 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
1271 + "->" + mServiceName + ": IGNORED onRecognitionResumed"));
1272
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -08001273 }
1274
1275 @Override
1276 public void onServiceConnected(ComponentName name, IBinder service) {
1277 if (DEBUG) Slog.v(TAG, mPuuid + ": onServiceConnected(" + service + ")");
1278
Jason Hsu1363f582019-04-16 15:35:55 +08001279 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
1280 + ": onServiceConnected(" + service + ")"));
1281
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -08001282 synchronized (mRemoteServiceLock) {
1283 mService = ISoundTriggerDetectionService.Stub.asInterface(service);
1284
1285 try {
1286 mService.setClient(mPuuid, mParams, mClient);
1287 } catch (Exception e) {
1288 Slog.e(TAG, mPuuid + ": Could not init " + mServiceName, e);
1289 return;
1290 }
1291
1292 while (!mPendingOps.isEmpty()) {
1293 runOrAddOperation(mPendingOps.remove(0));
1294 }
1295 }
1296 }
1297
1298 @Override
1299 public void onServiceDisconnected(ComponentName name) {
1300 if (DEBUG) Slog.v(TAG, mPuuid + ": onServiceDisconnected");
1301
Jason Hsu1363f582019-04-16 15:35:55 +08001302 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
1303 + ": onServiceDisconnected"));
1304
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -08001305 synchronized (mRemoteServiceLock) {
1306 mService = null;
1307 }
1308 }
1309
1310 @Override
1311 public void onBindingDied(ComponentName name) {
1312 if (DEBUG) Slog.v(TAG, mPuuid + ": onBindingDied");
1313
Jason Hsu1363f582019-04-16 15:35:55 +08001314 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
1315 + ": onBindingDied"));
1316
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -08001317 synchronized (mRemoteServiceLock) {
1318 destroy();
1319 }
1320 }
1321
1322 @Override
1323 public void onNullBinding(ComponentName name) {
1324 Slog.w(TAG, name + " for model " + mPuuid + " returned a null binding");
1325
Jason Hsu1363f582019-04-16 15:35:55 +08001326 sEventLogger.log(new SoundTriggerLogger.StringEvent(name + " for model "
1327 + mPuuid + " returned a null binding"));
1328
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -08001329 synchronized (mRemoteServiceLock) {
1330 disconnectLocked();
1331 }
1332 }
1333 }
1334
Arunesh Mishraa772e5f2016-01-25 10:33:11 -08001335 public final class LocalSoundTriggerService extends SoundTriggerInternal {
1336 private final Context mContext;
1337 private SoundTriggerHelper mSoundTriggerHelper;
1338
1339 LocalSoundTriggerService(Context context) {
1340 mContext = context;
1341 }
1342
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001343 synchronized void setSoundTriggerHelper(SoundTriggerHelper helper) {
1344 mSoundTriggerHelper = helper;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -08001345 }
1346
1347 @Override
1348 public int startRecognition(int keyphraseId, KeyphraseSoundModel soundModel,
1349 IRecognitionStatusCallback listener, RecognitionConfig recognitionConfig) {
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001350 if (!isInitialized()) return STATUS_ERROR;
1351 return mSoundTriggerHelper.startKeyphraseRecognition(keyphraseId, soundModel, listener,
Arunesh Mishraa772e5f2016-01-25 10:33:11 -08001352 recognitionConfig);
1353 }
1354
1355 @Override
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001356 public synchronized int stopRecognition(int keyphraseId, IRecognitionStatusCallback listener) {
1357 if (!isInitialized()) return STATUS_ERROR;
1358 return mSoundTriggerHelper.stopKeyphraseRecognition(keyphraseId, listener);
Arunesh Mishraa772e5f2016-01-25 10:33:11 -08001359 }
1360
1361 @Override
Arunesh Mishra55a9b002016-02-01 14:06:37 -08001362 public ModuleProperties getModuleProperties() {
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001363 if (!isInitialized()) return null;
Arunesh Mishra55a9b002016-02-01 14:06:37 -08001364 return mSoundTriggerHelper.getModuleProperties();
1365 }
1366
1367 @Override
Arunesh Mishra2d1de782016-02-21 18:10:28 -08001368 public int unloadKeyphraseModel(int keyphraseId) {
1369 if (!isInitialized()) return STATUS_ERROR;
1370 return mSoundTriggerHelper.unloadKeyphraseSoundModel(keyphraseId);
1371 }
1372
1373 @Override
Arunesh Mishraa772e5f2016-01-25 10:33:11 -08001374 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001375 if (!isInitialized()) return;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -08001376 mSoundTriggerHelper.dump(fd, pw, args);
Jason Hsu1363f582019-04-16 15:35:55 +08001377 // log
1378 sEventLogger.dump(pw);
Benjamin Schwartz9e7a0152019-06-06 17:39:22 -07001379
1380 // stats
1381 mSoundModelStatTracker.dump(pw);
Arunesh Mishraa772e5f2016-01-25 10:33:11 -08001382 }
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001383
1384 private synchronized boolean isInitialized() {
1385 if (mSoundTriggerHelper == null ) {
1386 Slog.e(TAG, "SoundTriggerHelper not initialized.");
Jason Hsu1363f582019-04-16 15:35:55 +08001387
1388 sEventLogger.log(new SoundTriggerLogger.StringEvent(
1389 "SoundTriggerHelper not initialized."));
1390
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001391 return false;
1392 }
1393 return true;
1394 }
Arunesh Mishraa772e5f2016-01-25 10:33:11 -08001395 }
1396
1397 private void enforceCallingPermission(String permission) {
1398 if (mContext.checkCallingOrSelfPermission(permission)
1399 != PackageManager.PERMISSION_GRANTED) {
1400 throw new SecurityException("Caller does not hold the permission " + permission);
1401 }
1402 }
Jason Hsu1363f582019-04-16 15:35:55 +08001403
1404 //=================================================================
1405 // For logging
1406
1407 private static final SoundTriggerLogger sEventLogger = new SoundTriggerLogger(200,
1408 "SoundTrigger activity");
1409
Arunesh Mishraa772e5f2016-01-25 10:33:11 -08001410}