blob: 767010ae821a30a10936965a1c62b9777fef80eb [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;
Nicholas Ambura0be6be2019-10-01 10:11:39 -070025import static android.hardware.soundtrigger.SoundTrigger.STATUS_BAD_VALUE;
Arunesh Mishra3fff7f52016-02-09 12:15:19 -080026import static android.hardware.soundtrigger.SoundTrigger.STATUS_ERROR;
Nicholas Ambura0be6be2019-10-01 10:11:39 -070027import static android.hardware.soundtrigger.SoundTrigger.STATUS_NO_INIT;
Chris Thorntonba08b792017-06-08 22:34:37 -070028import static android.hardware.soundtrigger.SoundTrigger.STATUS_OK;
Philip P. Moltmann7e25b3d2018-03-09 20:22:58 -080029import static android.provider.Settings.Global.MAX_SOUND_TRIGGER_DETECTION_SERVICE_OPS_PER_DAY;
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -080030import static android.provider.Settings.Global.SOUND_TRIGGER_DETECTION_SERVICE_OP_TIMEOUT;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -080031
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -080032import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
33
34import android.Manifest;
35import android.annotation.NonNull;
36import android.annotation.Nullable;
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -080037import android.content.ComponentName;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -080038import android.content.Context;
Chris Thorntonba08b792017-06-08 22:34:37 -070039import android.content.Intent;
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -080040import android.content.ServiceConnection;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -080041import android.content.pm.PackageManager;
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -080042import android.content.pm.ResolveInfo;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -080043import android.hardware.soundtrigger.IRecognitionStatusCallback;
Nicholas Ambura0be6be2019-10-01 10:11:39 -070044import android.hardware.soundtrigger.ModelParams;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -080045import android.hardware.soundtrigger.SoundTrigger;
Arunesh Mishrac722ec412016-01-27 13:29:12 -080046import android.hardware.soundtrigger.SoundTrigger.GenericSoundModel;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -080047import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel;
Nicholas Ambura0be6be2019-10-01 10:11:39 -070048import android.hardware.soundtrigger.SoundTrigger.ModelParamRange;
Arunesh Mishra55a9b002016-02-01 14:06:37 -080049import android.hardware.soundtrigger.SoundTrigger.ModuleProperties;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -080050import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig;
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -080051import android.hardware.soundtrigger.SoundTrigger.SoundModel;
Philip P. Moltmanna5b44032018-05-04 13:59:45 -070052import android.media.AudioAttributes;
53import android.media.AudioFormat;
54import android.media.AudioRecord;
55import android.media.MediaRecorder;
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -080056import android.media.soundtrigger.ISoundTriggerDetectionService;
57import android.media.soundtrigger.ISoundTriggerDetectionServiceClient;
58import android.media.soundtrigger.SoundTriggerDetectionService;
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -080059import android.os.Binder;
Chris Thorntonba08b792017-06-08 22:34:37 -070060import android.os.Bundle;
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -080061import android.os.Handler;
62import android.os.IBinder;
63import android.os.Looper;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -080064import android.os.Parcel;
65import android.os.ParcelUuid;
Chris Thorntonba08b792017-06-08 22:34:37 -070066import android.os.PowerManager;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -080067import android.os.RemoteException;
Benjamin Schwartz9e7a0152019-06-06 17:39:22 -070068import android.os.SystemClock;
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -080069import android.os.UserHandle;
70import android.provider.Settings;
Philip P. Moltmann7e25b3d2018-03-09 20:22:58 -080071import android.util.ArrayMap;
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -080072import android.util.ArraySet;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -080073import android.util.Slog;
74
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -080075import com.android.internal.annotations.GuardedBy;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -080076import com.android.internal.app.ISoundTriggerService;
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -080077import com.android.server.SystemService;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -080078
79import java.io.FileDescriptor;
80import java.io.PrintWriter;
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -080081import java.util.ArrayList;
Benjamin Schwartz9e7a0152019-06-06 17:39:22 -070082import java.util.Map;
Daulet Zhanguzine789eee2019-12-20 17:19:33 +000083import java.util.Objects;
Chris Thorntonba08b792017-06-08 22:34:37 -070084import java.util.TreeMap;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -080085import java.util.UUID;
Philip P. Moltmann7e25b3d2018-03-09 20:22:58 -080086import java.util.concurrent.TimeUnit;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -080087
88/**
89 * A single SystemService to manage all sound/voice-based sound models on the DSP.
90 * This services provides apis to manage sound trigger-based sound models via
91 * the ISoundTriggerService interface. This class also publishes a local interface encapsulating
92 * the functionality provided by {@link SoundTriggerHelper} for use by
93 * {@link VoiceInteractionManagerService}.
94 *
95 * @hide
96 */
97public class SoundTriggerService extends SystemService {
Arunesh Mishra3fff7f52016-02-09 12:15:19 -080098 private static final String TAG = "SoundTriggerService";
99 private static final boolean DEBUG = true;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800100
101 final Context mContext;
Chris Thorntonba08b792017-06-08 22:34:37 -0700102 private Object mLock;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800103 private final SoundTriggerServiceStub mServiceStub;
104 private final LocalSoundTriggerService mLocalSoundTriggerService;
105 private SoundTriggerDbHelper mDbHelper;
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800106 private SoundTriggerHelper mSoundTriggerHelper;
Chris Thorntonba08b792017-06-08 22:34:37 -0700107 private final TreeMap<UUID, SoundModel> mLoadedModels;
Chris Thorntonae5fb992017-12-07 18:26:31 -0800108 private Object mCallbacksLock;
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800109 private final TreeMap<UUID, IRecognitionStatusCallback> mCallbacks;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800110
Benjamin Schwartz9e7a0152019-06-06 17:39:22 -0700111 class SoundModelStatTracker {
112 private class SoundModelStat {
113 SoundModelStat() {
114 mStartCount = 0;
115 mTotalTimeMsec = 0;
116 mLastStartTimestampMsec = 0;
117 mLastStopTimestampMsec = 0;
118 mIsStarted = false;
119 }
120 long mStartCount; // Number of times that given model started
121 long mTotalTimeMsec; // Total time (msec) that given model was running since boot
122 long mLastStartTimestampMsec; // SystemClock.elapsedRealtime model was last started
123 long mLastStopTimestampMsec; // SystemClock.elapsedRealtime model was last stopped
124 boolean mIsStarted; // true if model is currently running
125 }
126 private final TreeMap<UUID, SoundModelStat> mModelStats;
127
128 SoundModelStatTracker() {
129 mModelStats = new TreeMap<UUID, SoundModelStat>();
130 }
131
132 public synchronized void onStart(UUID id) {
133 SoundModelStat stat = mModelStats.get(id);
134 if (stat == null) {
135 stat = new SoundModelStat();
136 mModelStats.put(id, stat);
137 }
138
139 if (stat.mIsStarted) {
140 Slog.e(TAG, "error onStart(): Model " + id + " already started");
141 return;
142 }
143
144 stat.mStartCount++;
145 stat.mLastStartTimestampMsec = SystemClock.elapsedRealtime();
146 stat.mIsStarted = true;
147 }
148
149 public synchronized void onStop(UUID id) {
150 SoundModelStat stat = mModelStats.get(id);
151 if (stat == null) {
152 Slog.e(TAG, "error onStop(): Model " + id + " has no stats available");
153 return;
154 }
155
156 if (!stat.mIsStarted) {
157 Slog.e(TAG, "error onStop(): Model " + id + " already stopped");
158 return;
159 }
160
161 stat.mLastStopTimestampMsec = SystemClock.elapsedRealtime();
162 stat.mTotalTimeMsec += stat.mLastStopTimestampMsec - stat.mLastStartTimestampMsec;
163 stat.mIsStarted = false;
164 }
165
166 public synchronized void dump(PrintWriter pw) {
167 long curTime = SystemClock.elapsedRealtime();
168 pw.println("Model Stats:");
169 for (Map.Entry<UUID, SoundModelStat> entry : mModelStats.entrySet()) {
170 UUID uuid = entry.getKey();
171 SoundModelStat stat = entry.getValue();
172 long totalTimeMsec = stat.mTotalTimeMsec;
173 if (stat.mIsStarted) {
174 totalTimeMsec += curTime - stat.mLastStartTimestampMsec;
175 }
176 pw.println(uuid + ", total_time(msec)=" + totalTimeMsec
177 + ", total_count=" + stat.mStartCount
178 + ", last_start=" + stat.mLastStartTimestampMsec
179 + ", last_stop=" + stat.mLastStopTimestampMsec);
180 }
181 }
182 }
183
184 private final SoundModelStatTracker mSoundModelStatTracker;
Philip P. Moltmann7e25b3d2018-03-09 20:22:58 -0800185 /** Number of ops run by the {@link RemoteSoundTriggerDetectionService} per package name */
186 @GuardedBy("mLock")
187 private final ArrayMap<String, NumOps> mNumOpsPerPackage = new ArrayMap<>();
188
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800189 public SoundTriggerService(Context context) {
190 super(context);
191 mContext = context;
192 mServiceStub = new SoundTriggerServiceStub();
193 mLocalSoundTriggerService = new LocalSoundTriggerService(context);
Chris Thorntonba08b792017-06-08 22:34:37 -0700194 mLoadedModels = new TreeMap<UUID, SoundModel>();
Chris Thorntonae5fb992017-12-07 18:26:31 -0800195 mCallbacksLock = new Object();
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800196 mCallbacks = new TreeMap<>();
Chris Thorntonba08b792017-06-08 22:34:37 -0700197 mLock = new Object();
Benjamin Schwartz9e7a0152019-06-06 17:39:22 -0700198 mSoundModelStatTracker = new SoundModelStatTracker();
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800199 }
200
201 @Override
202 public void onStart() {
203 publishBinderService(Context.SOUND_TRIGGER_SERVICE, mServiceStub);
204 publishLocalService(SoundTriggerInternal.class, mLocalSoundTriggerService);
205 }
206
207 @Override
208 public void onBootPhase(int phase) {
209 if (PHASE_SYSTEM_SERVICES_READY == phase) {
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800210 initSoundTriggerHelper();
211 mLocalSoundTriggerService.setSoundTriggerHelper(mSoundTriggerHelper);
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800212 } else if (PHASE_THIRD_PARTY_APPS_CAN_START == phase) {
213 mDbHelper = new SoundTriggerDbHelper(mContext);
214 }
215 }
216
217 @Override
218 public void onStartUser(int userHandle) {
219 }
220
221 @Override
222 public void onSwitchUser(int userHandle) {
223 }
224
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800225 private synchronized void initSoundTriggerHelper() {
226 if (mSoundTriggerHelper == null) {
227 mSoundTriggerHelper = new SoundTriggerHelper(mContext);
228 }
229 }
230
231 private synchronized boolean isInitialized() {
232 if (mSoundTriggerHelper == null ) {
233 Slog.e(TAG, "SoundTriggerHelper not initialized.");
234 return false;
235 }
236 return true;
237 }
238
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800239 class SoundTriggerServiceStub extends ISoundTriggerService.Stub {
240 @Override
241 public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
242 throws RemoteException {
243 try {
244 return super.onTransact(code, data, reply, flags);
245 } catch (RuntimeException e) {
246 // The activity manager only throws security exceptions, so let's
247 // log all others.
248 if (!(e instanceof SecurityException)) {
249 Slog.wtf(TAG, "SoundTriggerService Crash", e);
250 }
251 throw e;
252 }
253 }
254
255 @Override
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800256 public int startRecognition(ParcelUuid parcelUuid, IRecognitionStatusCallback callback,
257 RecognitionConfig config) {
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800258 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
Arunesh Mishra2d1de782016-02-21 18:10:28 -0800259 if (!isInitialized()) return STATUS_ERROR;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800260 if (DEBUG) {
261 Slog.i(TAG, "startRecognition(): Uuid : " + parcelUuid);
262 }
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800263
Jason Hsu1363f582019-04-16 15:35:55 +0800264 sEventLogger.log(new SoundTriggerLogger.StringEvent("startRecognition(): Uuid : "
265 + parcelUuid));
266
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800267 GenericSoundModel model = getSoundModel(parcelUuid);
268 if (model == null) {
269 Slog.e(TAG, "Null model in database for id: " + parcelUuid);
Jason Hsu1363f582019-04-16 15:35:55 +0800270
271 sEventLogger.log(new SoundTriggerLogger.StringEvent(
272 "startRecognition(): Null model in database for id: " + parcelUuid));
273
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800274 return STATUS_ERROR;
275 }
276
Benjamin Schwartz9e7a0152019-06-06 17:39:22 -0700277 int ret = mSoundTriggerHelper.startGenericRecognition(parcelUuid.getUuid(), model,
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800278 callback, config);
Benjamin Schwartz9e7a0152019-06-06 17:39:22 -0700279 if (ret == STATUS_OK) {
280 mSoundModelStatTracker.onStart(parcelUuid.getUuid());
281 }
282 return ret;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800283 }
284
285 @Override
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800286 public int stopRecognition(ParcelUuid parcelUuid, IRecognitionStatusCallback callback) {
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800287 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
288 if (DEBUG) {
289 Slog.i(TAG, "stopRecognition(): Uuid : " + parcelUuid);
290 }
Jason Hsu1363f582019-04-16 15:35:55 +0800291
292 sEventLogger.log(new SoundTriggerLogger.StringEvent("stopRecognition(): Uuid : "
293 + parcelUuid));
294
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800295 if (!isInitialized()) return STATUS_ERROR;
Benjamin Schwartz9e7a0152019-06-06 17:39:22 -0700296
297 int ret = mSoundTriggerHelper.stopGenericRecognition(parcelUuid.getUuid(), callback);
298 if (ret == STATUS_OK) {
299 mSoundModelStatTracker.onStop(parcelUuid.getUuid());
300 }
301 return ret;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800302 }
303
304 @Override
Arunesh Mishrac722ec412016-01-27 13:29:12 -0800305 public SoundTrigger.GenericSoundModel getSoundModel(ParcelUuid soundModelId) {
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800306 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
307 if (DEBUG) {
308 Slog.i(TAG, "getSoundModel(): id = " + soundModelId);
309 }
Jason Hsu1363f582019-04-16 15:35:55 +0800310
311 sEventLogger.log(new SoundTriggerLogger.StringEvent("getSoundModel(): id = "
312 + soundModelId));
313
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800314 SoundTrigger.GenericSoundModel model = mDbHelper.getGenericSoundModel(
315 soundModelId.getUuid());
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800316 return model;
317 }
318
319 @Override
Arunesh Mishrac722ec412016-01-27 13:29:12 -0800320 public void updateSoundModel(SoundTrigger.GenericSoundModel soundModel) {
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800321 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
322 if (DEBUG) {
323 Slog.i(TAG, "updateSoundModel(): model = " + soundModel);
324 }
Jason Hsu1363f582019-04-16 15:35:55 +0800325
326 sEventLogger.log(new SoundTriggerLogger.StringEvent("updateSoundModel(): model = "
327 + soundModel));
328
Arunesh Mishrac722ec412016-01-27 13:29:12 -0800329 mDbHelper.updateGenericSoundModel(soundModel);
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800330 }
331
332 @Override
333 public void deleteSoundModel(ParcelUuid soundModelId) {
334 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
335 if (DEBUG) {
336 Slog.i(TAG, "deleteSoundModel(): id = " + soundModelId);
337 }
Jason Hsu1363f582019-04-16 15:35:55 +0800338
339 sEventLogger.log(new SoundTriggerLogger.StringEvent("deleteSoundModel(): id = "
340 + soundModelId));
341
Arunesh Mishra2d1de782016-02-21 18:10:28 -0800342 // Unload the model if it is loaded.
343 mSoundTriggerHelper.unloadGenericSoundModel(soundModelId.getUuid());
Arunesh Mishrac722ec412016-01-27 13:29:12 -0800344 mDbHelper.deleteGenericSoundModel(soundModelId.getUuid());
Benjamin Schwartz9e7a0152019-06-06 17:39:22 -0700345
346 // Stop recognition if it is started.
347 mSoundModelStatTracker.onStop(soundModelId.getUuid());
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800348 }
Chris Thorntonba08b792017-06-08 22:34:37 -0700349
350 @Override
351 public int loadGenericSoundModel(GenericSoundModel soundModel) {
352 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
353 if (!isInitialized()) return STATUS_ERROR;
354 if (soundModel == null || soundModel.uuid == null) {
355 Slog.e(TAG, "Invalid sound model");
Jason Hsu1363f582019-04-16 15:35:55 +0800356
357 sEventLogger.log(new SoundTriggerLogger.StringEvent(
358 "loadGenericSoundModel(): Invalid sound model"));
359
Chris Thorntonba08b792017-06-08 22:34:37 -0700360 return STATUS_ERROR;
361 }
362 if (DEBUG) {
363 Slog.i(TAG, "loadGenericSoundModel(): id = " + soundModel.uuid);
364 }
Jason Hsu1363f582019-04-16 15:35:55 +0800365
366 sEventLogger.log(new SoundTriggerLogger.StringEvent("loadGenericSoundModel(): id = "
367 + soundModel.uuid));
368
Chris Thorntonba08b792017-06-08 22:34:37 -0700369 synchronized (mLock) {
370 SoundModel oldModel = mLoadedModels.get(soundModel.uuid);
371 // If the model we're loading is actually different than what we had loaded, we
372 // should unload that other model now. We don't care about return codes since we
373 // don't know if the other model is loaded.
374 if (oldModel != null && !oldModel.equals(soundModel)) {
375 mSoundTriggerHelper.unloadGenericSoundModel(soundModel.uuid);
Chris Thorntonae5fb992017-12-07 18:26:31 -0800376 synchronized (mCallbacksLock) {
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800377 mCallbacks.remove(soundModel.uuid);
Chris Thorntonae5fb992017-12-07 18:26:31 -0800378 }
Chris Thorntonba08b792017-06-08 22:34:37 -0700379 }
380 mLoadedModels.put(soundModel.uuid, soundModel);
381 }
382 return STATUS_OK;
383 }
384
385 @Override
386 public int loadKeyphraseSoundModel(KeyphraseSoundModel soundModel) {
387 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
388 if (!isInitialized()) return STATUS_ERROR;
389 if (soundModel == null || soundModel.uuid == null) {
390 Slog.e(TAG, "Invalid sound model");
Jason Hsu1363f582019-04-16 15:35:55 +0800391
392 sEventLogger.log(new SoundTriggerLogger.StringEvent(
393 "loadKeyphraseSoundModel(): Invalid sound model"));
394
Chris Thorntonba08b792017-06-08 22:34:37 -0700395 return STATUS_ERROR;
396 }
397 if (soundModel.keyphrases == null || soundModel.keyphrases.length != 1) {
398 Slog.e(TAG, "Only one keyphrase per model is currently supported.");
Jason Hsu1363f582019-04-16 15:35:55 +0800399
400 sEventLogger.log(new SoundTriggerLogger.StringEvent(
401 "loadKeyphraseSoundModel(): Only one keyphrase per model"
402 + " is currently supported."));
403
Chris Thorntonba08b792017-06-08 22:34:37 -0700404 return STATUS_ERROR;
405 }
406 if (DEBUG) {
407 Slog.i(TAG, "loadKeyphraseSoundModel(): id = " + soundModel.uuid);
408 }
Jason Hsu1363f582019-04-16 15:35:55 +0800409
410 sEventLogger.log(new SoundTriggerLogger.StringEvent("loadKeyphraseSoundModel(): id = "
411 + soundModel.uuid));
412
Chris Thorntonba08b792017-06-08 22:34:37 -0700413 synchronized (mLock) {
414 SoundModel oldModel = mLoadedModels.get(soundModel.uuid);
415 // If the model we're loading is actually different than what we had loaded, we
416 // should unload that other model now. We don't care about return codes since we
417 // don't know if the other model is loaded.
418 if (oldModel != null && !oldModel.equals(soundModel)) {
419 mSoundTriggerHelper.unloadKeyphraseSoundModel(soundModel.keyphrases[0].id);
Chris Thorntonae5fb992017-12-07 18:26:31 -0800420 synchronized (mCallbacksLock) {
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800421 mCallbacks.remove(soundModel.uuid);
Chris Thorntonae5fb992017-12-07 18:26:31 -0800422 }
Chris Thorntonba08b792017-06-08 22:34:37 -0700423 }
424 mLoadedModels.put(soundModel.uuid, soundModel);
425 }
426 return STATUS_OK;
427 }
428
429 @Override
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800430 public int startRecognitionForService(ParcelUuid soundModelId, Bundle params,
431 ComponentName detectionService, SoundTrigger.RecognitionConfig config) {
Daulet Zhanguzine789eee2019-12-20 17:19:33 +0000432 Objects.requireNonNull(soundModelId);
433 Objects.requireNonNull(detectionService);
434 Objects.requireNonNull(config);
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800435
Chris Thorntonba08b792017-06-08 22:34:37 -0700436 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
Philip P. Moltmann19402f52018-03-27 14:38:03 -0700437
Chris Thorntonba08b792017-06-08 22:34:37 -0700438 if (!isInitialized()) return STATUS_ERROR;
439 if (DEBUG) {
440 Slog.i(TAG, "startRecognition(): id = " + soundModelId);
441 }
442
Jason Hsu1363f582019-04-16 15:35:55 +0800443 sEventLogger.log(new SoundTriggerLogger.StringEvent(
444 "startRecognitionForService(): id = " + soundModelId));
445
Philip P. Moltmann19402f52018-03-27 14:38:03 -0700446 IRecognitionStatusCallback callback =
447 new RemoteSoundTriggerDetectionService(soundModelId.getUuid(), params,
448 detectionService, Binder.getCallingUserHandle(), config);
449
Chris Thorntonba08b792017-06-08 22:34:37 -0700450 synchronized (mLock) {
451 SoundModel soundModel = mLoadedModels.get(soundModelId.getUuid());
452 if (soundModel == null) {
453 Slog.e(TAG, soundModelId + " is not loaded");
Jason Hsu1363f582019-04-16 15:35:55 +0800454
455 sEventLogger.log(new SoundTriggerLogger.StringEvent(
456 "startRecognitionForService():" + soundModelId + " is not loaded"));
457
Chris Thorntonba08b792017-06-08 22:34:37 -0700458 return STATUS_ERROR;
459 }
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800460 IRecognitionStatusCallback existingCallback = null;
Chris Thorntonae5fb992017-12-07 18:26:31 -0800461 synchronized (mCallbacksLock) {
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800462 existingCallback = mCallbacks.get(soundModelId.getUuid());
Chris Thorntonae5fb992017-12-07 18:26:31 -0800463 }
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800464 if (existingCallback != null) {
Chris Thorntonba08b792017-06-08 22:34:37 -0700465 Slog.e(TAG, soundModelId + " is already running");
Jason Hsu1363f582019-04-16 15:35:55 +0800466
467 sEventLogger.log(new SoundTriggerLogger.StringEvent(
468 "startRecognitionForService():"
469 + soundModelId + " is already running"));
470
Chris Thorntonba08b792017-06-08 22:34:37 -0700471 return STATUS_ERROR;
472 }
Chris Thorntonba08b792017-06-08 22:34:37 -0700473 int ret;
474 switch (soundModel.type) {
Chris Thorntonba08b792017-06-08 22:34:37 -0700475 case SoundModel.TYPE_GENERIC_SOUND:
476 ret = mSoundTriggerHelper.startGenericRecognition(soundModel.uuid,
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800477 (GenericSoundModel) soundModel, callback, config);
Chris Thorntonba08b792017-06-08 22:34:37 -0700478 break;
479 default:
480 Slog.e(TAG, "Unknown model type");
Jason Hsu1363f582019-04-16 15:35:55 +0800481
482 sEventLogger.log(new SoundTriggerLogger.StringEvent(
483 "startRecognitionForService(): Unknown model type"));
484
Chris Thorntonba08b792017-06-08 22:34:37 -0700485 return STATUS_ERROR;
486 }
487
488 if (ret != STATUS_OK) {
489 Slog.e(TAG, "Failed to start model: " + ret);
Jason Hsu1363f582019-04-16 15:35:55 +0800490
491 sEventLogger.log(new SoundTriggerLogger.StringEvent(
492 "startRecognitionForService(): Failed to start model:"));
493
Chris Thorntonba08b792017-06-08 22:34:37 -0700494 return ret;
495 }
Chris Thorntonae5fb992017-12-07 18:26:31 -0800496 synchronized (mCallbacksLock) {
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800497 mCallbacks.put(soundModelId.getUuid(), callback);
Chris Thorntonae5fb992017-12-07 18:26:31 -0800498 }
Benjamin Schwartz9e7a0152019-06-06 17:39:22 -0700499
500 mSoundModelStatTracker.onStart(soundModelId.getUuid());
Chris Thorntonba08b792017-06-08 22:34:37 -0700501 }
502 return STATUS_OK;
503 }
504
505 @Override
Philip P. Moltmann19402f52018-03-27 14:38:03 -0700506 public int stopRecognitionForService(ParcelUuid soundModelId) {
Chris Thorntonba08b792017-06-08 22:34:37 -0700507 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
508 if (!isInitialized()) return STATUS_ERROR;
509 if (DEBUG) {
510 Slog.i(TAG, "stopRecognition(): id = " + soundModelId);
511 }
512
Jason Hsu1363f582019-04-16 15:35:55 +0800513 sEventLogger.log(new SoundTriggerLogger.StringEvent(
514 "stopRecognitionForService(): id = " + soundModelId));
515
Chris Thorntonba08b792017-06-08 22:34:37 -0700516 synchronized (mLock) {
517 SoundModel soundModel = mLoadedModels.get(soundModelId.getUuid());
518 if (soundModel == null) {
519 Slog.e(TAG, soundModelId + " is not loaded");
Jason Hsu1363f582019-04-16 15:35:55 +0800520
521 sEventLogger.log(new SoundTriggerLogger.StringEvent(
522 "stopRecognitionForService(): " + soundModelId
523 + " is not loaded"));
524
Chris Thorntonba08b792017-06-08 22:34:37 -0700525 return STATUS_ERROR;
526 }
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800527 IRecognitionStatusCallback callback = null;
Chris Thorntonae5fb992017-12-07 18:26:31 -0800528 synchronized (mCallbacksLock) {
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800529 callback = mCallbacks.get(soundModelId.getUuid());
Chris Thorntonae5fb992017-12-07 18:26:31 -0800530 }
Chris Thorntonba08b792017-06-08 22:34:37 -0700531 if (callback == null) {
532 Slog.e(TAG, soundModelId + " is not running");
Jason Hsu1363f582019-04-16 15:35:55 +0800533
534 sEventLogger.log(new SoundTriggerLogger.StringEvent(
535 "stopRecognitionForService(): " + soundModelId
536 + " is not running"));
537
Chris Thorntonba08b792017-06-08 22:34:37 -0700538 return STATUS_ERROR;
539 }
540 int ret;
541 switch (soundModel.type) {
Chris Thorntonba08b792017-06-08 22:34:37 -0700542 case SoundModel.TYPE_GENERIC_SOUND:
543 ret = mSoundTriggerHelper.stopGenericRecognition(soundModel.uuid, callback);
544 break;
545 default:
546 Slog.e(TAG, "Unknown model type");
Jason Hsu1363f582019-04-16 15:35:55 +0800547
548 sEventLogger.log(new SoundTriggerLogger.StringEvent(
549 "stopRecognitionForService(): Unknown model type"));
550
Chris Thorntonba08b792017-06-08 22:34:37 -0700551 return STATUS_ERROR;
552 }
553
554 if (ret != STATUS_OK) {
555 Slog.e(TAG, "Failed to stop model: " + ret);
Jason Hsu1363f582019-04-16 15:35:55 +0800556
557 sEventLogger.log(new SoundTriggerLogger.StringEvent(
558 "stopRecognitionForService(): Failed to stop model: " + ret));
559
Chris Thorntonba08b792017-06-08 22:34:37 -0700560 return ret;
561 }
Chris Thorntonae5fb992017-12-07 18:26:31 -0800562 synchronized (mCallbacksLock) {
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800563 mCallbacks.remove(soundModelId.getUuid());
Chris Thorntonae5fb992017-12-07 18:26:31 -0800564 }
Benjamin Schwartz9e7a0152019-06-06 17:39:22 -0700565
566 mSoundModelStatTracker.onStop(soundModelId.getUuid());
Chris Thorntonba08b792017-06-08 22:34:37 -0700567 }
568 return STATUS_OK;
569 }
570
571 @Override
572 public int unloadSoundModel(ParcelUuid soundModelId) {
573 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
574 if (!isInitialized()) return STATUS_ERROR;
575 if (DEBUG) {
576 Slog.i(TAG, "unloadSoundModel(): id = " + soundModelId);
577 }
578
Jason Hsu1363f582019-04-16 15:35:55 +0800579 sEventLogger.log(new SoundTriggerLogger.StringEvent("unloadSoundModel(): id = "
580 + soundModelId));
581
Chris Thorntonba08b792017-06-08 22:34:37 -0700582 synchronized (mLock) {
583 SoundModel soundModel = mLoadedModels.get(soundModelId.getUuid());
584 if (soundModel == null) {
585 Slog.e(TAG, soundModelId + " is not loaded");
Jason Hsu1363f582019-04-16 15:35:55 +0800586
587 sEventLogger.log(new SoundTriggerLogger.StringEvent(
588 "unloadSoundModel(): " + soundModelId + " is not loaded"));
589
Chris Thorntonba08b792017-06-08 22:34:37 -0700590 return STATUS_ERROR;
591 }
592 int ret;
593 switch (soundModel.type) {
594 case SoundModel.TYPE_KEYPHRASE:
595 ret = mSoundTriggerHelper.unloadKeyphraseSoundModel(
596 ((KeyphraseSoundModel)soundModel).keyphrases[0].id);
597 break;
598 case SoundModel.TYPE_GENERIC_SOUND:
599 ret = mSoundTriggerHelper.unloadGenericSoundModel(soundModel.uuid);
600 break;
601 default:
602 Slog.e(TAG, "Unknown model type");
Jason Hsu1363f582019-04-16 15:35:55 +0800603
604 sEventLogger.log(new SoundTriggerLogger.StringEvent(
605 "unloadSoundModel(): Unknown model type"));
606
Chris Thorntonba08b792017-06-08 22:34:37 -0700607 return STATUS_ERROR;
608 }
609 if (ret != STATUS_OK) {
610 Slog.e(TAG, "Failed to unload model");
Jason Hsu1363f582019-04-16 15:35:55 +0800611
612 sEventLogger.log(new SoundTriggerLogger.StringEvent(
613 "unloadSoundModel(): Failed to unload model"));
614
Chris Thorntonba08b792017-06-08 22:34:37 -0700615 return ret;
616 }
617 mLoadedModels.remove(soundModelId.getUuid());
618 return STATUS_OK;
619 }
620 }
621
622 @Override
623 public boolean isRecognitionActive(ParcelUuid parcelUuid) {
624 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
625 if (!isInitialized()) return false;
Chris Thorntonae5fb992017-12-07 18:26:31 -0800626 synchronized (mCallbacksLock) {
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800627 IRecognitionStatusCallback callback = mCallbacks.get(parcelUuid.getUuid());
Chris Thorntonba08b792017-06-08 22:34:37 -0700628 if (callback == null) {
629 return false;
630 }
Chris Thorntonba08b792017-06-08 22:34:37 -0700631 }
Chris Thorntonae5fb992017-12-07 18:26:31 -0800632 return mSoundTriggerHelper.isRecognitionRequested(parcelUuid.getUuid());
Chris Thorntonba08b792017-06-08 22:34:37 -0700633 }
Michael Dooley291751e2018-10-16 19:53:29 +0000634
635 @Override
mike dooleyb2ab04a2018-11-07 15:48:54 +0100636 public int getModelState(ParcelUuid soundModelId) {
Michael Dooley291751e2018-10-16 19:53:29 +0000637 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
mike dooleyb2ab04a2018-11-07 15:48:54 +0100638 int ret = STATUS_ERROR;
639 if (!isInitialized()) return ret;
Michael Dooley291751e2018-10-16 19:53:29 +0000640 if (DEBUG) {
641 Slog.i(TAG, "getModelState(): id = " + soundModelId);
642 }
643
Jason Hsu1363f582019-04-16 15:35:55 +0800644 sEventLogger.log(new SoundTriggerLogger.StringEvent("getModelState(): id = "
645 + soundModelId));
646
Michael Dooley291751e2018-10-16 19:53:29 +0000647 synchronized (mLock) {
648 SoundModel soundModel = mLoadedModels.get(soundModelId.getUuid());
649 if (soundModel == null) {
650 Slog.e(TAG, soundModelId + " is not loaded");
Jason Hsu1363f582019-04-16 15:35:55 +0800651
652 sEventLogger.log(new SoundTriggerLogger.StringEvent("getModelState(): "
653 + soundModelId + " is not loaded"));
654
mike dooleyb2ab04a2018-11-07 15:48:54 +0100655 return ret;
Michael Dooley291751e2018-10-16 19:53:29 +0000656 }
Michael Dooley291751e2018-10-16 19:53:29 +0000657 switch (soundModel.type) {
Michael Dooley291751e2018-10-16 19:53:29 +0000658 case SoundModel.TYPE_GENERIC_SOUND:
659 ret = mSoundTriggerHelper.getGenericModelState(soundModel.uuid);
660 break;
661 default:
mike dooleybaa22c72019-05-15 10:05:25 +0200662 // SoundModel.TYPE_KEYPHRASE is not supported to increase privacy.
663 Slog.e(TAG, "Unsupported model type, " + soundModel.type);
Jason Hsu1363f582019-04-16 15:35:55 +0800664 sEventLogger.log(new SoundTriggerLogger.StringEvent(
mike dooleybaa22c72019-05-15 10:05:25 +0200665 "getModelState(): Unsupported model type, "
666 + soundModel.type));
Michael Dooley291751e2018-10-16 19:53:29 +0000667 break;
668 }
Michael Dooley291751e2018-10-16 19:53:29 +0000669
670 return ret;
671 }
672 }
Nicholas Ambur1aa4b4b2019-08-22 12:13:29 -0700673
674 @Override
675 @Nullable
676 public ModuleProperties getModuleProperties() {
677 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
678 if (!isInitialized()) return null;
679 if (DEBUG) {
680 Slog.i(TAG, "getModuleProperties()");
681 }
682
683 synchronized (mLock) {
684 ModuleProperties properties = mSoundTriggerHelper.getModuleProperties();
685 sEventLogger.log(new SoundTriggerLogger.StringEvent(
686 "getModuleProperties(): " + properties.toString()));
687 return properties;
688 }
689 }
Nicholas Ambura0be6be2019-10-01 10:11:39 -0700690
691 @Override
692 public int setParameter(ParcelUuid soundModelId,
693 @ModelParams int modelParam, int value) {
694 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
695 if (!isInitialized()) return STATUS_NO_INIT;
696 if (DEBUG) {
697 Slog.d(TAG, "setParameter(): id=" + soundModelId
698 + ", param=" + modelParam
699 + ", value=" + value);
700 }
701
702 sEventLogger.log(new SoundTriggerLogger.StringEvent(
703 "setParameter(): id=" + soundModelId
704 + ", param=" + modelParam
705 + ", value=" + value));
706
707 synchronized (mLock) {
708 SoundModel soundModel = mLoadedModels.get(soundModelId.getUuid());
709 if (soundModel == null) {
710 Slog.e(TAG, soundModelId + " is not loaded. Loaded models: "
711 + mLoadedModels.toString());
712
713 sEventLogger.log(new SoundTriggerLogger.StringEvent("setParameter(): "
714 + soundModelId + " is not loaded"));
715
716 return STATUS_BAD_VALUE;
717 }
718
719 return mSoundTriggerHelper.setParameter(soundModel.uuid, modelParam, value);
720 }
721 }
722
723 @Override
724 public int getParameter(@NonNull ParcelUuid soundModelId,
725 @ModelParams int modelParam)
726 throws UnsupportedOperationException, IllegalArgumentException {
727 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
728 if (!isInitialized()) {
729 throw new UnsupportedOperationException("SoundTriggerHelper not initialized");
730 }
731 if (DEBUG) {
732 Slog.d(TAG, "getParameter(): id=" + soundModelId
733 + ", param=" + modelParam);
734 }
735
736 sEventLogger.log(new SoundTriggerLogger.StringEvent(
737 "getParameter(): id=" + soundModelId
738 + ", param=" + modelParam));
739
740 synchronized (mLock) {
741 SoundModel soundModel = mLoadedModels.get(soundModelId.getUuid());
742 if (soundModel == null) {
743 Slog.e(TAG, soundModelId + " is not loaded");
744
745 sEventLogger.log(new SoundTriggerLogger.StringEvent("getParameter(): "
746 + soundModelId + " is not loaded"));
747
748 throw new IllegalArgumentException("sound model is not loaded");
749 }
750
751 return mSoundTriggerHelper.getParameter(soundModel.uuid, modelParam);
752 }
753 }
754
755 @Override
756 @Nullable
757 public ModelParamRange queryParameter(@NonNull ParcelUuid soundModelId,
758 @ModelParams int modelParam) {
759 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
760 if (!isInitialized()) return null;
761 if (DEBUG) {
762 Slog.d(TAG, "queryParameter(): id=" + soundModelId
763 + ", param=" + modelParam);
764 }
765
766 sEventLogger.log(new SoundTriggerLogger.StringEvent(
767 "queryParameter(): id=" + soundModelId
768 + ", param=" + modelParam));
769
770 synchronized (mLock) {
771 SoundModel soundModel = mLoadedModels.get(soundModelId.getUuid());
772 if (soundModel == null) {
773 Slog.e(TAG, soundModelId + " is not loaded");
774
775 sEventLogger.log(new SoundTriggerLogger.StringEvent(
776 "queryParameter(): "
777 + soundModelId + " is not loaded"));
778
779 return null;
780 }
781
782 return mSoundTriggerHelper.queryParameter(soundModel.uuid, modelParam);
783 }
784 }
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800785 }
786
Philip P. Moltmann7e25b3d2018-03-09 20:22:58 -0800787 /**
788 * Counts the number of operations added in the last 24 hours.
789 */
790 private static class NumOps {
791 private final Object mLock = new Object();
792
793 @GuardedBy("mLock")
794 private int[] mNumOps = new int[24];
795 @GuardedBy("mLock")
796 private long mLastOpsHourSinceBoot;
797
798 /**
799 * Clear buckets of new hours that have elapsed since last operation.
800 *
801 * <p>I.e. when the last operation was triggered at 1:40 and the current operation was
802 * triggered at 4:03, the buckets "2, 3, and 4" are cleared.
803 *
804 * @param currentTime Current elapsed time since boot in ns
805 */
806 void clearOldOps(long currentTime) {
807 synchronized (mLock) {
808 long numHoursSinceBoot = TimeUnit.HOURS.convert(currentTime, TimeUnit.NANOSECONDS);
809
810 // Clear buckets of new hours that have elapsed since last operation
811 // I.e. when the last operation was triggered at 1:40 and the current
812 // operation was triggered at 4:03, the bucket "2, 3, and 4" is cleared
813 if (mLastOpsHourSinceBoot != 0) {
814 for (long hour = mLastOpsHourSinceBoot + 1; hour <= numHoursSinceBoot; hour++) {
815 mNumOps[(int) (hour % 24)] = 0;
816 }
817 }
818 }
819 }
820
821 /**
822 * Add a new operation.
823 *
824 * @param currentTime Current elapsed time since boot in ns
825 */
826 void addOp(long currentTime) {
827 synchronized (mLock) {
828 long numHoursSinceBoot = TimeUnit.HOURS.convert(currentTime, TimeUnit.NANOSECONDS);
829
830 mNumOps[(int) (numHoursSinceBoot % 24)]++;
831 mLastOpsHourSinceBoot = numHoursSinceBoot;
832 }
833 }
834
835 /**
836 * Get the total operations added in the last 24 hours.
837 *
838 * @return The total number of operations added in the last 24 hours
839 */
840 int getOpsAdded() {
841 synchronized (mLock) {
842 int totalOperationsInLastDay = 0;
843 for (int i = 0; i < 24; i++) {
844 totalOperationsInLastDay += mNumOps[i];
845 }
846
847 return totalOperationsInLastDay;
848 }
849 }
850 }
851
Philip P. Moltmanna5b44032018-05-04 13:59:45 -0700852 /**
853 * A single operation run in a {@link RemoteSoundTriggerDetectionService}.
854 *
855 * <p>Once the remote service is connected either setup + execute or setup + stop is executed.
856 */
857 private static class Operation {
858 private interface ExecuteOp {
859 void run(int opId, ISoundTriggerDetectionService service) throws RemoteException;
860 }
861
862 private final @Nullable Runnable mSetupOp;
863 private final @NonNull ExecuteOp mExecuteOp;
864 private final @Nullable Runnable mDropOp;
865
866 private Operation(@Nullable Runnable setupOp, @NonNull ExecuteOp executeOp,
867 @Nullable Runnable cancelOp) {
868 mSetupOp = setupOp;
869 mExecuteOp = executeOp;
870 mDropOp = cancelOp;
871 }
872
873 private void setup() {
874 if (mSetupOp != null) {
875 mSetupOp.run();
876 }
877 }
878
879 void run(int opId, @NonNull ISoundTriggerDetectionService service) throws RemoteException {
880 setup();
881 mExecuteOp.run(opId, service);
882 }
883
884 void drop() {
885 setup();
886
887 if (mDropOp != null) {
888 mDropOp.run();
889 }
890 }
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800891 }
892
893 /**
894 * Local end for a {@link SoundTriggerDetectionService}. Operations are queued up and executed
895 * when the service connects.
896 *
897 * <p>If operations take too long they are forcefully aborted.
898 *
899 * <p>This also limits the amount of operations in 24 hours.
900 */
901 private class RemoteSoundTriggerDetectionService
902 extends IRecognitionStatusCallback.Stub implements ServiceConnection {
903 private static final int MSG_STOP_ALL_PENDING_OPERATIONS = 1;
904
905 private final Object mRemoteServiceLock = new Object();
906
907 /** UUID of the model the service is started for */
908 private final @NonNull ParcelUuid mPuuid;
909 /** Params passed into the start method for the service */
910 private final @Nullable Bundle mParams;
911 /** Component name passed when starting the service */
912 private final @NonNull ComponentName mServiceName;
913 /** User that started the service */
914 private final @NonNull UserHandle mUser;
915 /** Configuration of the recognition the service is handling */
916 private final @NonNull RecognitionConfig mRecognitionConfig;
917 /** Wake lock keeping the remote service alive */
918 private final @NonNull PowerManager.WakeLock mRemoteServiceWakeLock;
919
920 private final @NonNull Handler mHandler;
921
922 /** Callbacks that are called by the service */
923 private final @NonNull ISoundTriggerDetectionServiceClient mClient;
924
925 /** Operations that are pending because the service is not yet connected */
926 @GuardedBy("mRemoteServiceLock")
927 private final ArrayList<Operation> mPendingOps = new ArrayList<>();
928 /** Operations that have been send to the service but have no yet finished */
929 @GuardedBy("mRemoteServiceLock")
930 private final ArraySet<Integer> mRunningOpIds = new ArraySet<>();
Philip P. Moltmann7e25b3d2018-03-09 20:22:58 -0800931 /** The number of operations executed in each of the last 24 hours */
932 private final NumOps mNumOps;
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800933
934 /** The service binder if connected */
935 @GuardedBy("mRemoteServiceLock")
936 private @Nullable ISoundTriggerDetectionService mService;
937 /** Whether the service has been bound */
938 @GuardedBy("mRemoteServiceLock")
939 private boolean mIsBound;
940 /** Whether the service has been destroyed */
941 @GuardedBy("mRemoteServiceLock")
942 private boolean mIsDestroyed;
943 /**
944 * Set once a final op is scheduled. No further ops can be added and the service is
945 * destroyed once the op finishes.
946 */
947 @GuardedBy("mRemoteServiceLock")
948 private boolean mDestroyOnceRunningOpsDone;
949
950 /** Total number of operations performed by this service */
951 @GuardedBy("mRemoteServiceLock")
952 private int mNumTotalOpsPerformed;
953
954 /**
955 * Create a new remote sound trigger detection service. This only binds to the service when
956 * operations are in flight. Each operation has a certain time it can run. Once no
957 * operations are allowed to run anymore, {@link #stopAllPendingOperations() all operations
958 * are aborted and stopped} and the service is disconnected.
959 *
960 * @param modelUuid The UUID of the model the recognition is for
961 * @param params The params passed to each method of the service
962 * @param serviceName The component name of the service
963 * @param user The user of the service
964 * @param config The configuration of the recognition
965 */
966 public RemoteSoundTriggerDetectionService(@NonNull UUID modelUuid,
967 @Nullable Bundle params, @NonNull ComponentName serviceName, @NonNull UserHandle user,
968 @NonNull RecognitionConfig config) {
969 mPuuid = new ParcelUuid(modelUuid);
970 mParams = params;
971 mServiceName = serviceName;
972 mUser = user;
973 mRecognitionConfig = config;
974 mHandler = new Handler(Looper.getMainLooper());
975
976 PowerManager pm = ((PowerManager) mContext.getSystemService(Context.POWER_SERVICE));
977 mRemoteServiceWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
978 "RemoteSoundTriggerDetectionService " + mServiceName.getPackageName() + ":"
979 + mServiceName.getClassName());
980
Philip P. Moltmann7e25b3d2018-03-09 20:22:58 -0800981 synchronized (mLock) {
982 NumOps numOps = mNumOpsPerPackage.get(mServiceName.getPackageName());
983 if (numOps == null) {
984 numOps = new NumOps();
985 mNumOpsPerPackage.put(mServiceName.getPackageName(), numOps);
986 }
987 mNumOps = numOps;
988 }
989
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800990 mClient = new ISoundTriggerDetectionServiceClient.Stub() {
991 @Override
992 public void onOpFinished(int opId) {
993 long token = Binder.clearCallingIdentity();
994 try {
995 synchronized (mRemoteServiceLock) {
996 mRunningOpIds.remove(opId);
997
998 if (mRunningOpIds.isEmpty() && mPendingOps.isEmpty()) {
999 if (mDestroyOnceRunningOpsDone) {
1000 destroy();
1001 } else {
1002 disconnectLocked();
1003 }
1004 }
1005 }
1006 } finally {
1007 Binder.restoreCallingIdentity(token);
1008 }
1009 }
1010 };
1011 }
1012
1013 @Override
1014 public boolean pingBinder() {
1015 return !(mIsDestroyed || mDestroyOnceRunningOpsDone);
1016 }
1017
1018 /**
1019 * Disconnect from the service, but allow to re-connect when new operations are triggered.
1020 */
Andreas Gampe8ce7ed92018-09-05 16:53:00 -07001021 @GuardedBy("mRemoteServiceLock")
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -08001022 private void disconnectLocked() {
1023 if (mService != null) {
1024 try {
1025 mService.removeClient(mPuuid);
1026 } catch (Exception e) {
1027 Slog.e(TAG, mPuuid + ": Cannot remove client", e);
Jason Hsu1363f582019-04-16 15:35:55 +08001028
1029 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
1030 + ": Cannot remove client"));
1031
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -08001032 }
1033
1034 mService = null;
1035 }
1036
1037 if (mIsBound) {
1038 mContext.unbindService(RemoteSoundTriggerDetectionService.this);
1039 mIsBound = false;
1040
1041 synchronized (mCallbacksLock) {
1042 mRemoteServiceWakeLock.release();
1043 }
1044 }
1045 }
1046
1047 /**
1048 * Disconnect, do not allow to reconnect to the service. All further operations will be
1049 * dropped.
1050 */
1051 private void destroy() {
1052 if (DEBUG) Slog.v(TAG, mPuuid + ": destroy");
1053
Jason Hsu1363f582019-04-16 15:35:55 +08001054 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid + ": destroy"));
1055
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -08001056 synchronized (mRemoteServiceLock) {
1057 disconnectLocked();
1058
1059 mIsDestroyed = true;
1060 }
1061
1062 // The callback is removed before the flag is set
1063 if (!mDestroyOnceRunningOpsDone) {
1064 synchronized (mCallbacksLock) {
1065 mCallbacks.remove(mPuuid.getUuid());
1066 }
1067 }
1068 }
1069
1070 /**
1071 * Stop all pending operations and then disconnect for the service.
1072 */
1073 private void stopAllPendingOperations() {
1074 synchronized (mRemoteServiceLock) {
1075 if (mIsDestroyed) {
1076 return;
1077 }
1078
1079 if (mService != null) {
1080 int numOps = mRunningOpIds.size();
1081 for (int i = 0; i < numOps; i++) {
1082 try {
1083 mService.onStopOperation(mPuuid, mRunningOpIds.valueAt(i));
1084 } catch (Exception e) {
1085 Slog.e(TAG, mPuuid + ": Could not stop operation "
1086 + mRunningOpIds.valueAt(i), e);
Jason Hsu1363f582019-04-16 15:35:55 +08001087
1088 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
1089 + ": Could not stop operation " + mRunningOpIds.valueAt(i)));
1090
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -08001091 }
1092 }
1093
1094 mRunningOpIds.clear();
1095 }
1096
1097 disconnectLocked();
1098 }
1099 }
1100
1101 /**
1102 * Verify that the service has the expected properties and then bind to the service
1103 */
1104 private void bind() {
1105 long token = Binder.clearCallingIdentity();
1106 try {
1107 Intent i = new Intent();
1108 i.setComponent(mServiceName);
1109
1110 ResolveInfo ri = mContext.getPackageManager().resolveServiceAsUser(i,
1111 GET_SERVICES | GET_META_DATA | MATCH_DEBUG_TRIAGED_MISSING,
1112 mUser.getIdentifier());
1113
1114 if (ri == null) {
1115 Slog.w(TAG, mPuuid + ": " + mServiceName + " not found");
Jason Hsu1363f582019-04-16 15:35:55 +08001116
1117 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
1118 + ": " + mServiceName + " not found"));
1119
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -08001120 return;
1121 }
1122
1123 if (!BIND_SOUND_TRIGGER_DETECTION_SERVICE
1124 .equals(ri.serviceInfo.permission)) {
1125 Slog.w(TAG, mPuuid + ": " + mServiceName + " does not require "
1126 + BIND_SOUND_TRIGGER_DETECTION_SERVICE);
Jason Hsu1363f582019-04-16 15:35:55 +08001127
1128 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
1129 + ": " + mServiceName + " does not require "
1130 + BIND_SOUND_TRIGGER_DETECTION_SERVICE));
1131
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -08001132 return;
1133 }
1134
1135 mIsBound = mContext.bindServiceAsUser(i, this,
1136 BIND_AUTO_CREATE | BIND_FOREGROUND_SERVICE, mUser);
1137
1138 if (mIsBound) {
1139 mRemoteServiceWakeLock.acquire();
1140 } else {
1141 Slog.w(TAG, mPuuid + ": Could not bind to " + mServiceName);
Jason Hsu1363f582019-04-16 15:35:55 +08001142
1143 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
1144 + ": Could not bind to " + mServiceName));
1145
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -08001146 }
1147 } finally {
1148 Binder.restoreCallingIdentity(token);
1149 }
1150 }
1151
1152 /**
1153 * Run an operation (i.e. send it do the service). If the service is not connected, this
1154 * binds the service and then runs the operation once connected.
1155 *
1156 * @param op The operation to run
1157 */
1158 private void runOrAddOperation(Operation op) {
1159 synchronized (mRemoteServiceLock) {
1160 if (mIsDestroyed || mDestroyOnceRunningOpsDone) {
Philip P. Moltmanna5b44032018-05-04 13:59:45 -07001161 Slog.w(TAG, mPuuid + ": Dropped operation as already destroyed or marked for "
1162 + "destruction");
1163
Jason Hsu1363f582019-04-16 15:35:55 +08001164 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
1165 + ":Dropped operation as already destroyed or marked for destruction"));
1166
Philip P. Moltmanna5b44032018-05-04 13:59:45 -07001167 op.drop();
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -08001168 return;
1169 }
1170
1171 if (mService == null) {
1172 mPendingOps.add(op);
1173
1174 if (!mIsBound) {
1175 bind();
1176 }
1177 } else {
Philip P. Moltmann7e25b3d2018-03-09 20:22:58 -08001178 long currentTime = System.nanoTime();
1179 mNumOps.clearOldOps(currentTime);
1180
1181 // Drop operation if too many were executed in the last 24 hours.
1182 int opsAllowed = Settings.Global.getInt(mContext.getContentResolver(),
1183 MAX_SOUND_TRIGGER_DETECTION_SERVICE_OPS_PER_DAY,
1184 Integer.MAX_VALUE);
1185
Philip P. Moltmann3d1683b2018-05-07 15:53:51 -07001186 // As we currently cannot dropping an op safely, disable throttling
Philip P. Moltmann7e25b3d2018-03-09 20:22:58 -08001187 int opsAdded = mNumOps.getOpsAdded();
Philip P. Moltmann3d1683b2018-05-07 15:53:51 -07001188 if (false && mNumOps.getOpsAdded() >= opsAllowed) {
Philip P. Moltmanna5b44032018-05-04 13:59:45 -07001189 try {
1190 if (DEBUG || opsAllowed + 10 > opsAdded) {
1191 Slog.w(TAG, mPuuid + ": Dropped operation as too many operations "
1192 + "were run in last 24 hours");
Jason Hsu1363f582019-04-16 15:35:55 +08001193
1194 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
1195 + ": Dropped operation as too many operations "
1196 + "were run in last 24 hours"));
1197
Philip P. Moltmanna5b44032018-05-04 13:59:45 -07001198 }
1199
1200 op.drop();
1201 } catch (Exception e) {
1202 Slog.e(TAG, mPuuid + ": Could not drop operation", e);
Jason Hsu1363f582019-04-16 15:35:55 +08001203
1204 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
1205 + ": Could not drop operation"));
1206
Philip P. Moltmann7e25b3d2018-03-09 20:22:58 -08001207 }
Philip P. Moltmann09dd8c42018-04-11 09:23:19 -07001208 } else {
1209 mNumOps.addOp(currentTime);
Philip P. Moltmann7e25b3d2018-03-09 20:22:58 -08001210
Philip P. Moltmann09dd8c42018-04-11 09:23:19 -07001211 // Find a free opID
1212 int opId = mNumTotalOpsPerformed;
1213 do {
1214 mNumTotalOpsPerformed++;
1215 } while (mRunningOpIds.contains(opId));
Philip P. Moltmann7e25b3d2018-03-09 20:22:58 -08001216
Philip P. Moltmann09dd8c42018-04-11 09:23:19 -07001217 // Run OP
1218 try {
1219 if (DEBUG) Slog.v(TAG, mPuuid + ": runOp " + opId);
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -08001220
Jason Hsu1363f582019-04-16 15:35:55 +08001221 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
1222 + ": runOp " + opId));
1223
Philip P. Moltmann09dd8c42018-04-11 09:23:19 -07001224 op.run(opId, mService);
1225 mRunningOpIds.add(opId);
1226 } catch (Exception e) {
1227 Slog.e(TAG, mPuuid + ": Could not run operation " + opId, e);
Jason Hsu1363f582019-04-16 15:35:55 +08001228
1229 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
1230 + ": Could not run operation " + opId));
1231
Philip P. Moltmann09dd8c42018-04-11 09:23:19 -07001232 }
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -08001233 }
1234
1235 // Unbind from service if no operations are left (i.e. if the operation failed)
1236 if (mPendingOps.isEmpty() && mRunningOpIds.isEmpty()) {
1237 if (mDestroyOnceRunningOpsDone) {
1238 destroy();
1239 } else {
1240 disconnectLocked();
1241 }
1242 } else {
1243 mHandler.removeMessages(MSG_STOP_ALL_PENDING_OPERATIONS);
1244 mHandler.sendMessageDelayed(obtainMessage(
1245 RemoteSoundTriggerDetectionService::stopAllPendingOperations, this)
1246 .setWhat(MSG_STOP_ALL_PENDING_OPERATIONS),
1247 Settings.Global.getLong(mContext.getContentResolver(),
1248 SOUND_TRIGGER_DETECTION_SERVICE_OP_TIMEOUT,
1249 Long.MAX_VALUE));
1250 }
1251 }
1252 }
1253 }
1254
1255 @Override
1256 public void onKeyphraseDetected(SoundTrigger.KeyphraseRecognitionEvent event) {
1257 Slog.w(TAG, mPuuid + "->" + mServiceName + ": IGNORED onKeyphraseDetected(" + event
1258 + ")");
Jason Hsu1363f582019-04-16 15:35:55 +08001259
1260 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid + "->" + mServiceName
1261 + ": IGNORED onKeyphraseDetected(" + event + ")"));
1262
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -08001263 }
1264
Philip P. Moltmanna5b44032018-05-04 13:59:45 -07001265 /**
1266 * Create an AudioRecord enough for starting and releasing the data buffered for the event.
1267 *
1268 * @param event The event that was received
1269 * @return The initialized AudioRecord
1270 */
1271 private @NonNull AudioRecord createAudioRecordForEvent(
1272 @NonNull SoundTrigger.GenericRecognitionEvent event) {
1273 AudioAttributes.Builder attributesBuilder = new AudioAttributes.Builder();
1274 attributesBuilder.setInternalCapturePreset(MediaRecorder.AudioSource.HOTWORD);
1275 AudioAttributes attributes = attributesBuilder.build();
1276
1277 // Use same AudioFormat processing as in RecognitionEvent.fromParcel
1278 AudioFormat originalFormat = event.getCaptureFormat();
1279 AudioFormat captureFormat = (new AudioFormat.Builder())
1280 .setChannelMask(originalFormat.getChannelMask())
1281 .setEncoding(originalFormat.getEncoding())
1282 .setSampleRate(originalFormat.getSampleRate())
1283 .build();
1284
1285 int bufferSize = AudioRecord.getMinBufferSize(
1286 captureFormat.getSampleRate() == AudioFormat.SAMPLE_RATE_UNSPECIFIED
1287 ? AudioFormat.SAMPLE_RATE_HZ_MAX
1288 : captureFormat.getSampleRate(),
1289 captureFormat.getChannelCount() == 2
1290 ? AudioFormat.CHANNEL_IN_STEREO
1291 : AudioFormat.CHANNEL_IN_MONO,
1292 captureFormat.getEncoding());
1293
Jason Hsu1363f582019-04-16 15:35:55 +08001294 sEventLogger.log(new SoundTriggerLogger.StringEvent("createAudioRecordForEvent"));
1295
Philip P. Moltmanna5b44032018-05-04 13:59:45 -07001296 return new AudioRecord(attributes, captureFormat, bufferSize,
1297 event.getCaptureSession());
1298 }
1299
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -08001300 @Override
1301 public void onGenericSoundTriggerDetected(SoundTrigger.GenericRecognitionEvent event) {
1302 if (DEBUG) Slog.v(TAG, mPuuid + ": Generic sound trigger event: " + event);
1303
Jason Hsu1363f582019-04-16 15:35:55 +08001304 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
1305 + ": Generic sound trigger event: " + event));
1306
Philip P. Moltmanna5b44032018-05-04 13:59:45 -07001307 runOrAddOperation(new Operation(
1308 // always execute:
1309 () -> {
mike dooley9b20c1c2019-01-23 10:38:17 +01001310 if (!mRecognitionConfig.allowMultipleTriggers) {
Philip P. Moltmanna5b44032018-05-04 13:59:45 -07001311 // Unregister this remoteService once op is done
1312 synchronized (mCallbacksLock) {
1313 mCallbacks.remove(mPuuid.getUuid());
1314 }
1315 mDestroyOnceRunningOpsDone = true;
1316 }
1317 },
1318 // execute if not throttled:
1319 (opId, service) -> service.onGenericRecognitionEvent(mPuuid, opId, event),
1320 // execute if throttled:
1321 () -> {
1322 if (event.isCaptureAvailable()) {
1323 AudioRecord capturedData = createAudioRecordForEvent(event);
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -08001324
Philip P. Moltmanna5b44032018-05-04 13:59:45 -07001325 // Currently we need to start and release the audio record to reset
1326 // the DSP even if we don't want to process the event
1327 capturedData.startRecording();
1328 capturedData.release();
1329 }
1330 }));
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -08001331 }
1332
1333 @Override
1334 public void onError(int status) {
1335 if (DEBUG) Slog.v(TAG, mPuuid + ": onError: " + status);
1336
Jason Hsu1363f582019-04-16 15:35:55 +08001337 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
1338 + ": onError: " + status));
1339
Philip P. Moltmanna5b44032018-05-04 13:59:45 -07001340 runOrAddOperation(
1341 new Operation(
1342 // always execute:
1343 () -> {
1344 // Unregister this remoteService once op is done
1345 synchronized (mCallbacksLock) {
1346 mCallbacks.remove(mPuuid.getUuid());
1347 }
1348 mDestroyOnceRunningOpsDone = true;
1349 },
1350 // execute if not throttled:
1351 (opId, service) -> service.onError(mPuuid, opId, status),
1352 // nothing to do if throttled
1353 null));
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -08001354 }
1355
1356 @Override
1357 public void onRecognitionPaused() {
1358 Slog.i(TAG, mPuuid + "->" + mServiceName + ": IGNORED onRecognitionPaused");
Jason Hsu1363f582019-04-16 15:35:55 +08001359
1360 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
1361 + "->" + mServiceName + ": IGNORED onRecognitionPaused"));
1362
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -08001363 }
1364
1365 @Override
1366 public void onRecognitionResumed() {
1367 Slog.i(TAG, mPuuid + "->" + mServiceName + ": IGNORED onRecognitionResumed");
Jason Hsu1363f582019-04-16 15:35:55 +08001368
1369 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
1370 + "->" + mServiceName + ": IGNORED onRecognitionResumed"));
1371
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -08001372 }
1373
1374 @Override
1375 public void onServiceConnected(ComponentName name, IBinder service) {
1376 if (DEBUG) Slog.v(TAG, mPuuid + ": onServiceConnected(" + service + ")");
1377
Jason Hsu1363f582019-04-16 15:35:55 +08001378 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
1379 + ": onServiceConnected(" + service + ")"));
1380
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -08001381 synchronized (mRemoteServiceLock) {
1382 mService = ISoundTriggerDetectionService.Stub.asInterface(service);
1383
1384 try {
1385 mService.setClient(mPuuid, mParams, mClient);
1386 } catch (Exception e) {
1387 Slog.e(TAG, mPuuid + ": Could not init " + mServiceName, e);
1388 return;
1389 }
1390
1391 while (!mPendingOps.isEmpty()) {
1392 runOrAddOperation(mPendingOps.remove(0));
1393 }
1394 }
1395 }
1396
1397 @Override
1398 public void onServiceDisconnected(ComponentName name) {
1399 if (DEBUG) Slog.v(TAG, mPuuid + ": onServiceDisconnected");
1400
Jason Hsu1363f582019-04-16 15:35:55 +08001401 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
1402 + ": onServiceDisconnected"));
1403
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -08001404 synchronized (mRemoteServiceLock) {
1405 mService = null;
1406 }
1407 }
1408
1409 @Override
1410 public void onBindingDied(ComponentName name) {
1411 if (DEBUG) Slog.v(TAG, mPuuid + ": onBindingDied");
1412
Jason Hsu1363f582019-04-16 15:35:55 +08001413 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
1414 + ": onBindingDied"));
1415
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -08001416 synchronized (mRemoteServiceLock) {
1417 destroy();
1418 }
1419 }
1420
1421 @Override
1422 public void onNullBinding(ComponentName name) {
1423 Slog.w(TAG, name + " for model " + mPuuid + " returned a null binding");
1424
Jason Hsu1363f582019-04-16 15:35:55 +08001425 sEventLogger.log(new SoundTriggerLogger.StringEvent(name + " for model "
1426 + mPuuid + " returned a null binding"));
1427
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -08001428 synchronized (mRemoteServiceLock) {
1429 disconnectLocked();
1430 }
1431 }
1432 }
1433
Arunesh Mishraa772e5f2016-01-25 10:33:11 -08001434 public final class LocalSoundTriggerService extends SoundTriggerInternal {
1435 private final Context mContext;
1436 private SoundTriggerHelper mSoundTriggerHelper;
1437
1438 LocalSoundTriggerService(Context context) {
1439 mContext = context;
1440 }
1441
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001442 synchronized void setSoundTriggerHelper(SoundTriggerHelper helper) {
1443 mSoundTriggerHelper = helper;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -08001444 }
1445
1446 @Override
1447 public int startRecognition(int keyphraseId, KeyphraseSoundModel soundModel,
1448 IRecognitionStatusCallback listener, RecognitionConfig recognitionConfig) {
Nicholas Amburf94db1c2019-12-08 19:04:06 -08001449 if (!isInitialized()) throw new UnsupportedOperationException();
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001450 return mSoundTriggerHelper.startKeyphraseRecognition(keyphraseId, soundModel, listener,
Arunesh Mishraa772e5f2016-01-25 10:33:11 -08001451 recognitionConfig);
1452 }
1453
1454 @Override
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001455 public synchronized int stopRecognition(int keyphraseId, IRecognitionStatusCallback listener) {
Nicholas Amburf94db1c2019-12-08 19:04:06 -08001456 if (!isInitialized()) throw new UnsupportedOperationException();
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001457 return mSoundTriggerHelper.stopKeyphraseRecognition(keyphraseId, listener);
Arunesh Mishraa772e5f2016-01-25 10:33:11 -08001458 }
1459
1460 @Override
Arunesh Mishra55a9b002016-02-01 14:06:37 -08001461 public ModuleProperties getModuleProperties() {
Nicholas Amburf94db1c2019-12-08 19:04:06 -08001462 if (!isInitialized()) throw new UnsupportedOperationException();
Arunesh Mishra55a9b002016-02-01 14:06:37 -08001463 return mSoundTriggerHelper.getModuleProperties();
1464 }
1465
1466 @Override
Nicholas Amburf94db1c2019-12-08 19:04:06 -08001467 public int setParameter(int keyphraseId, @ModelParams int modelParam, int value) {
1468 if (!isInitialized()) throw new UnsupportedOperationException();
1469 return mSoundTriggerHelper.setKeyphraseParameter(keyphraseId, modelParam, value);
1470 }
1471
1472 @Override
1473 public int getParameter(int keyphraseId, @ModelParams int modelParam) {
1474 if (!isInitialized()) throw new UnsupportedOperationException();
1475 return mSoundTriggerHelper.getKeyphraseParameter(keyphraseId, modelParam);
1476 }
1477
1478 @Override
1479 @Nullable
1480 public ModelParamRange queryParameter(int keyphraseId, @ModelParams int modelParam) {
1481 if (!isInitialized()) throw new UnsupportedOperationException();
1482 return mSoundTriggerHelper.queryKeyphraseParameter(keyphraseId, modelParam);
1483 }
1484
1485 @Override
Arunesh Mishra2d1de782016-02-21 18:10:28 -08001486 public int unloadKeyphraseModel(int keyphraseId) {
Nicholas Amburf94db1c2019-12-08 19:04:06 -08001487 if (!isInitialized()) throw new UnsupportedOperationException();
Arunesh Mishra2d1de782016-02-21 18:10:28 -08001488 return mSoundTriggerHelper.unloadKeyphraseSoundModel(keyphraseId);
1489 }
1490
1491 @Override
Arunesh Mishraa772e5f2016-01-25 10:33:11 -08001492 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001493 if (!isInitialized()) return;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -08001494 mSoundTriggerHelper.dump(fd, pw, args);
Jason Hsu1363f582019-04-16 15:35:55 +08001495 // log
1496 sEventLogger.dump(pw);
Benjamin Schwartz9e7a0152019-06-06 17:39:22 -07001497
Nicholas Amburd3ec82f2020-01-03 17:44:19 -08001498 // enrolled models
1499 mDbHelper.dump(pw);
1500
Benjamin Schwartz9e7a0152019-06-06 17:39:22 -07001501 // stats
1502 mSoundModelStatTracker.dump(pw);
Arunesh Mishraa772e5f2016-01-25 10:33:11 -08001503 }
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001504
1505 private synchronized boolean isInitialized() {
1506 if (mSoundTriggerHelper == null ) {
1507 Slog.e(TAG, "SoundTriggerHelper not initialized.");
Jason Hsu1363f582019-04-16 15:35:55 +08001508
1509 sEventLogger.log(new SoundTriggerLogger.StringEvent(
1510 "SoundTriggerHelper not initialized."));
1511
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001512 return false;
1513 }
1514 return true;
1515 }
Arunesh Mishraa772e5f2016-01-25 10:33:11 -08001516 }
1517
1518 private void enforceCallingPermission(String permission) {
1519 if (mContext.checkCallingOrSelfPermission(permission)
1520 != PackageManager.PERMISSION_GRANTED) {
1521 throw new SecurityException("Caller does not hold the permission " + permission);
1522 }
1523 }
Jason Hsu1363f582019-04-16 15:35:55 +08001524
1525 //=================================================================
1526 // For logging
1527
1528 private static final SoundTriggerLogger sEventLogger = new SoundTriggerLogger(200,
1529 "SoundTrigger activity");
1530
Arunesh Mishraa772e5f2016-01-25 10:33:11 -08001531}