blob: 170bee841e74a12f8d0b032c4313cfc26d743869 [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;
Nick Moukhine67b5e382020-03-16 17:51:37 +010022import static android.content.Context.BIND_INCLUDE_CAPABILITIES;
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -080023import static android.content.pm.PackageManager.GET_META_DATA;
24import static android.content.pm.PackageManager.GET_SERVICES;
25import static android.content.pm.PackageManager.MATCH_DEBUG_TRIAGED_MISSING;
Nicholas Ambura0be6be2019-10-01 10:11:39 -070026import static android.hardware.soundtrigger.SoundTrigger.STATUS_BAD_VALUE;
Arunesh Mishra3fff7f52016-02-09 12:15:19 -080027import static android.hardware.soundtrigger.SoundTrigger.STATUS_ERROR;
Nicholas Ambura0be6be2019-10-01 10:11:39 -070028import static android.hardware.soundtrigger.SoundTrigger.STATUS_NO_INIT;
Chris Thorntonba08b792017-06-08 22:34:37 -070029import static android.hardware.soundtrigger.SoundTrigger.STATUS_OK;
Philip P. Moltmann7e25b3d2018-03-09 20:22:58 -080030import static android.provider.Settings.Global.MAX_SOUND_TRIGGER_DETECTION_SERVICE_OPS_PER_DAY;
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -080031import static android.provider.Settings.Global.SOUND_TRIGGER_DETECTION_SERVICE_OP_TIMEOUT;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -080032
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -080033import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
34
35import android.Manifest;
36import android.annotation.NonNull;
37import android.annotation.Nullable;
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -080038import android.content.ComponentName;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -080039import android.content.Context;
Chris Thorntonba08b792017-06-08 22:34:37 -070040import android.content.Intent;
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -080041import android.content.ServiceConnection;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -080042import android.content.pm.PackageManager;
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -080043import android.content.pm.ResolveInfo;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -080044import android.hardware.soundtrigger.IRecognitionStatusCallback;
Nicholas Ambura0be6be2019-10-01 10:11:39 -070045import android.hardware.soundtrigger.ModelParams;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -080046import android.hardware.soundtrigger.SoundTrigger;
Arunesh Mishrac722ec412016-01-27 13:29:12 -080047import android.hardware.soundtrigger.SoundTrigger.GenericSoundModel;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -080048import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel;
Nicholas Ambura0be6be2019-10-01 10:11:39 -070049import android.hardware.soundtrigger.SoundTrigger.ModelParamRange;
Arunesh Mishra55a9b002016-02-01 14:06:37 -080050import android.hardware.soundtrigger.SoundTrigger.ModuleProperties;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -080051import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig;
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -080052import android.hardware.soundtrigger.SoundTrigger.SoundModel;
Philip P. Moltmanna5b44032018-05-04 13:59:45 -070053import android.media.AudioAttributes;
54import android.media.AudioFormat;
55import android.media.AudioRecord;
56import android.media.MediaRecorder;
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -080057import android.media.soundtrigger.ISoundTriggerDetectionService;
58import android.media.soundtrigger.ISoundTriggerDetectionServiceClient;
59import android.media.soundtrigger.SoundTriggerDetectionService;
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -080060import android.os.Binder;
Chris Thorntonba08b792017-06-08 22:34:37 -070061import android.os.Bundle;
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -080062import android.os.Handler;
63import android.os.IBinder;
64import android.os.Looper;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -080065import android.os.Parcel;
66import android.os.ParcelUuid;
Chris Thorntonba08b792017-06-08 22:34:37 -070067import android.os.PowerManager;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -080068import android.os.RemoteException;
Benjamin Schwartz9e7a0152019-06-06 17:39:22 -070069import android.os.SystemClock;
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -080070import android.os.UserHandle;
71import android.provider.Settings;
Philip P. Moltmann7e25b3d2018-03-09 20:22:58 -080072import android.util.ArrayMap;
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -080073import android.util.ArraySet;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -080074import android.util.Slog;
75
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -080076import com.android.internal.annotations.GuardedBy;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -080077import com.android.internal.app.ISoundTriggerService;
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -080078import com.android.server.SystemService;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -080079
80import java.io.FileDescriptor;
81import java.io.PrintWriter;
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -080082import java.util.ArrayList;
Benjamin Schwartz9e7a0152019-06-06 17:39:22 -070083import java.util.Map;
Daulet Zhanguzine789eee2019-12-20 17:19:33 +000084import java.util.Objects;
Chris Thorntonba08b792017-06-08 22:34:37 -070085import java.util.TreeMap;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -080086import java.util.UUID;
Philip P. Moltmann7e25b3d2018-03-09 20:22:58 -080087import java.util.concurrent.TimeUnit;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -080088
89/**
90 * A single SystemService to manage all sound/voice-based sound models on the DSP.
91 * This services provides apis to manage sound trigger-based sound models via
92 * the ISoundTriggerService interface. This class also publishes a local interface encapsulating
93 * the functionality provided by {@link SoundTriggerHelper} for use by
94 * {@link VoiceInteractionManagerService}.
95 *
96 * @hide
97 */
98public class SoundTriggerService extends SystemService {
Arunesh Mishra3fff7f52016-02-09 12:15:19 -080099 private static final String TAG = "SoundTriggerService";
100 private static final boolean DEBUG = true;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800101
102 final Context mContext;
Chris Thorntonba08b792017-06-08 22:34:37 -0700103 private Object mLock;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800104 private final SoundTriggerServiceStub mServiceStub;
105 private final LocalSoundTriggerService mLocalSoundTriggerService;
106 private SoundTriggerDbHelper mDbHelper;
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800107 private SoundTriggerHelper mSoundTriggerHelper;
Chris Thorntonba08b792017-06-08 22:34:37 -0700108 private final TreeMap<UUID, SoundModel> mLoadedModels;
Chris Thorntonae5fb992017-12-07 18:26:31 -0800109 private Object mCallbacksLock;
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800110 private final TreeMap<UUID, IRecognitionStatusCallback> mCallbacks;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800111
Benjamin Schwartz9e7a0152019-06-06 17:39:22 -0700112 class SoundModelStatTracker {
113 private class SoundModelStat {
114 SoundModelStat() {
115 mStartCount = 0;
116 mTotalTimeMsec = 0;
117 mLastStartTimestampMsec = 0;
118 mLastStopTimestampMsec = 0;
119 mIsStarted = false;
120 }
121 long mStartCount; // Number of times that given model started
122 long mTotalTimeMsec; // Total time (msec) that given model was running since boot
123 long mLastStartTimestampMsec; // SystemClock.elapsedRealtime model was last started
124 long mLastStopTimestampMsec; // SystemClock.elapsedRealtime model was last stopped
125 boolean mIsStarted; // true if model is currently running
126 }
127 private final TreeMap<UUID, SoundModelStat> mModelStats;
128
129 SoundModelStatTracker() {
130 mModelStats = new TreeMap<UUID, SoundModelStat>();
131 }
132
133 public synchronized void onStart(UUID id) {
134 SoundModelStat stat = mModelStats.get(id);
135 if (stat == null) {
136 stat = new SoundModelStat();
137 mModelStats.put(id, stat);
138 }
139
140 if (stat.mIsStarted) {
141 Slog.e(TAG, "error onStart(): Model " + id + " already started");
142 return;
143 }
144
145 stat.mStartCount++;
146 stat.mLastStartTimestampMsec = SystemClock.elapsedRealtime();
147 stat.mIsStarted = true;
148 }
149
150 public synchronized void onStop(UUID id) {
151 SoundModelStat stat = mModelStats.get(id);
152 if (stat == null) {
153 Slog.e(TAG, "error onStop(): Model " + id + " has no stats available");
154 return;
155 }
156
157 if (!stat.mIsStarted) {
158 Slog.e(TAG, "error onStop(): Model " + id + " already stopped");
159 return;
160 }
161
162 stat.mLastStopTimestampMsec = SystemClock.elapsedRealtime();
163 stat.mTotalTimeMsec += stat.mLastStopTimestampMsec - stat.mLastStartTimestampMsec;
164 stat.mIsStarted = false;
165 }
166
167 public synchronized void dump(PrintWriter pw) {
168 long curTime = SystemClock.elapsedRealtime();
169 pw.println("Model Stats:");
170 for (Map.Entry<UUID, SoundModelStat> entry : mModelStats.entrySet()) {
171 UUID uuid = entry.getKey();
172 SoundModelStat stat = entry.getValue();
173 long totalTimeMsec = stat.mTotalTimeMsec;
174 if (stat.mIsStarted) {
175 totalTimeMsec += curTime - stat.mLastStartTimestampMsec;
176 }
177 pw.println(uuid + ", total_time(msec)=" + totalTimeMsec
178 + ", total_count=" + stat.mStartCount
179 + ", last_start=" + stat.mLastStartTimestampMsec
180 + ", last_stop=" + stat.mLastStopTimestampMsec);
181 }
182 }
183 }
184
185 private final SoundModelStatTracker mSoundModelStatTracker;
Philip P. Moltmann7e25b3d2018-03-09 20:22:58 -0800186 /** Number of ops run by the {@link RemoteSoundTriggerDetectionService} per package name */
187 @GuardedBy("mLock")
188 private final ArrayMap<String, NumOps> mNumOpsPerPackage = new ArrayMap<>();
189
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800190 public SoundTriggerService(Context context) {
191 super(context);
192 mContext = context;
193 mServiceStub = new SoundTriggerServiceStub();
194 mLocalSoundTriggerService = new LocalSoundTriggerService(context);
Chris Thorntonba08b792017-06-08 22:34:37 -0700195 mLoadedModels = new TreeMap<UUID, SoundModel>();
Chris Thorntonae5fb992017-12-07 18:26:31 -0800196 mCallbacksLock = new Object();
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800197 mCallbacks = new TreeMap<>();
Chris Thorntonba08b792017-06-08 22:34:37 -0700198 mLock = new Object();
Benjamin Schwartz9e7a0152019-06-06 17:39:22 -0700199 mSoundModelStatTracker = new SoundModelStatTracker();
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800200 }
201
202 @Override
203 public void onStart() {
204 publishBinderService(Context.SOUND_TRIGGER_SERVICE, mServiceStub);
205 publishLocalService(SoundTriggerInternal.class, mLocalSoundTriggerService);
206 }
207
208 @Override
209 public void onBootPhase(int phase) {
Nicholas Ambur78a22682020-03-23 15:42:21 -0700210 Slog.d(TAG, "onBootPhase: " + phase + " : " + isSafeMode());
211 if (PHASE_DEVICE_SPECIFIC_SERVICES_READY == phase) {
212 if (isSafeMode()) {
213 Slog.w(TAG, "not enabling SoundTriggerService in safe mode");
214 return;
215 }
216
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800217 initSoundTriggerHelper();
218 mLocalSoundTriggerService.setSoundTriggerHelper(mSoundTriggerHelper);
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800219 } else if (PHASE_THIRD_PARTY_APPS_CAN_START == phase) {
220 mDbHelper = new SoundTriggerDbHelper(mContext);
221 }
222 }
223
224 @Override
225 public void onStartUser(int userHandle) {
226 }
227
228 @Override
229 public void onSwitchUser(int userHandle) {
230 }
231
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800232 private synchronized void initSoundTriggerHelper() {
233 if (mSoundTriggerHelper == null) {
234 mSoundTriggerHelper = new SoundTriggerHelper(mContext);
235 }
236 }
237
238 private synchronized boolean isInitialized() {
239 if (mSoundTriggerHelper == null ) {
240 Slog.e(TAG, "SoundTriggerHelper not initialized.");
241 return false;
242 }
243 return true;
244 }
245
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800246 class SoundTriggerServiceStub extends ISoundTriggerService.Stub {
247 @Override
248 public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
249 throws RemoteException {
250 try {
251 return super.onTransact(code, data, reply, flags);
252 } catch (RuntimeException e) {
253 // The activity manager only throws security exceptions, so let's
254 // log all others.
255 if (!(e instanceof SecurityException)) {
256 Slog.wtf(TAG, "SoundTriggerService Crash", e);
257 }
258 throw e;
259 }
260 }
261
262 @Override
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800263 public int startRecognition(ParcelUuid parcelUuid, IRecognitionStatusCallback callback,
264 RecognitionConfig config) {
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800265 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
Arunesh Mishra2d1de782016-02-21 18:10:28 -0800266 if (!isInitialized()) return STATUS_ERROR;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800267 if (DEBUG) {
268 Slog.i(TAG, "startRecognition(): Uuid : " + parcelUuid);
269 }
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800270
Jason Hsu1363f582019-04-16 15:35:55 +0800271 sEventLogger.log(new SoundTriggerLogger.StringEvent("startRecognition(): Uuid : "
272 + parcelUuid));
273
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800274 GenericSoundModel model = getSoundModel(parcelUuid);
275 if (model == null) {
276 Slog.e(TAG, "Null model in database for id: " + parcelUuid);
Jason Hsu1363f582019-04-16 15:35:55 +0800277
278 sEventLogger.log(new SoundTriggerLogger.StringEvent(
279 "startRecognition(): Null model in database for id: " + parcelUuid));
280
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800281 return STATUS_ERROR;
282 }
283
Benjamin Schwartz9e7a0152019-06-06 17:39:22 -0700284 int ret = mSoundTriggerHelper.startGenericRecognition(parcelUuid.getUuid(), model,
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800285 callback, config);
Benjamin Schwartz9e7a0152019-06-06 17:39:22 -0700286 if (ret == STATUS_OK) {
287 mSoundModelStatTracker.onStart(parcelUuid.getUuid());
288 }
289 return ret;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800290 }
291
292 @Override
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800293 public int stopRecognition(ParcelUuid parcelUuid, IRecognitionStatusCallback callback) {
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800294 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
295 if (DEBUG) {
296 Slog.i(TAG, "stopRecognition(): Uuid : " + parcelUuid);
297 }
Jason Hsu1363f582019-04-16 15:35:55 +0800298
299 sEventLogger.log(new SoundTriggerLogger.StringEvent("stopRecognition(): Uuid : "
300 + parcelUuid));
301
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800302 if (!isInitialized()) return STATUS_ERROR;
Benjamin Schwartz9e7a0152019-06-06 17:39:22 -0700303
304 int ret = mSoundTriggerHelper.stopGenericRecognition(parcelUuid.getUuid(), callback);
305 if (ret == STATUS_OK) {
306 mSoundModelStatTracker.onStop(parcelUuid.getUuid());
307 }
308 return ret;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800309 }
310
311 @Override
Arunesh Mishrac722ec412016-01-27 13:29:12 -0800312 public SoundTrigger.GenericSoundModel getSoundModel(ParcelUuid soundModelId) {
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800313 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
314 if (DEBUG) {
315 Slog.i(TAG, "getSoundModel(): id = " + soundModelId);
316 }
Jason Hsu1363f582019-04-16 15:35:55 +0800317
318 sEventLogger.log(new SoundTriggerLogger.StringEvent("getSoundModel(): id = "
319 + soundModelId));
320
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800321 SoundTrigger.GenericSoundModel model = mDbHelper.getGenericSoundModel(
322 soundModelId.getUuid());
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800323 return model;
324 }
325
326 @Override
Arunesh Mishrac722ec412016-01-27 13:29:12 -0800327 public void updateSoundModel(SoundTrigger.GenericSoundModel soundModel) {
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800328 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
329 if (DEBUG) {
330 Slog.i(TAG, "updateSoundModel(): model = " + soundModel);
331 }
Jason Hsu1363f582019-04-16 15:35:55 +0800332
333 sEventLogger.log(new SoundTriggerLogger.StringEvent("updateSoundModel(): model = "
334 + soundModel));
335
Arunesh Mishrac722ec412016-01-27 13:29:12 -0800336 mDbHelper.updateGenericSoundModel(soundModel);
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800337 }
338
339 @Override
340 public void deleteSoundModel(ParcelUuid soundModelId) {
341 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
342 if (DEBUG) {
343 Slog.i(TAG, "deleteSoundModel(): id = " + soundModelId);
344 }
Jason Hsu1363f582019-04-16 15:35:55 +0800345
346 sEventLogger.log(new SoundTriggerLogger.StringEvent("deleteSoundModel(): id = "
347 + soundModelId));
348
Arunesh Mishra2d1de782016-02-21 18:10:28 -0800349 // Unload the model if it is loaded.
350 mSoundTriggerHelper.unloadGenericSoundModel(soundModelId.getUuid());
Arunesh Mishrac722ec412016-01-27 13:29:12 -0800351 mDbHelper.deleteGenericSoundModel(soundModelId.getUuid());
Benjamin Schwartz9e7a0152019-06-06 17:39:22 -0700352
353 // Stop recognition if it is started.
354 mSoundModelStatTracker.onStop(soundModelId.getUuid());
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800355 }
Chris Thorntonba08b792017-06-08 22:34:37 -0700356
357 @Override
358 public int loadGenericSoundModel(GenericSoundModel soundModel) {
359 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
360 if (!isInitialized()) return STATUS_ERROR;
Nicholas Ambur1e437672020-03-24 17:09:58 -0700361 if (soundModel == null || soundModel.getUuid() == null) {
Chris Thorntonba08b792017-06-08 22:34:37 -0700362 Slog.e(TAG, "Invalid sound model");
Jason Hsu1363f582019-04-16 15:35:55 +0800363
364 sEventLogger.log(new SoundTriggerLogger.StringEvent(
365 "loadGenericSoundModel(): Invalid sound model"));
366
Chris Thorntonba08b792017-06-08 22:34:37 -0700367 return STATUS_ERROR;
368 }
369 if (DEBUG) {
Nicholas Ambur1e437672020-03-24 17:09:58 -0700370 Slog.i(TAG, "loadGenericSoundModel(): id = " + soundModel.getUuid());
Chris Thorntonba08b792017-06-08 22:34:37 -0700371 }
Jason Hsu1363f582019-04-16 15:35:55 +0800372
373 sEventLogger.log(new SoundTriggerLogger.StringEvent("loadGenericSoundModel(): id = "
Nicholas Ambur1e437672020-03-24 17:09:58 -0700374 + soundModel.getUuid()));
Jason Hsu1363f582019-04-16 15:35:55 +0800375
Chris Thorntonba08b792017-06-08 22:34:37 -0700376 synchronized (mLock) {
Nicholas Ambur1e437672020-03-24 17:09:58 -0700377 SoundModel oldModel = mLoadedModels.get(soundModel.getUuid());
Chris Thorntonba08b792017-06-08 22:34:37 -0700378 // If the model we're loading is actually different than what we had loaded, we
379 // should unload that other model now. We don't care about return codes since we
380 // don't know if the other model is loaded.
381 if (oldModel != null && !oldModel.equals(soundModel)) {
Nicholas Ambur1e437672020-03-24 17:09:58 -0700382 mSoundTriggerHelper.unloadGenericSoundModel(soundModel.getUuid());
Chris Thorntonae5fb992017-12-07 18:26:31 -0800383 synchronized (mCallbacksLock) {
Nicholas Ambur1e437672020-03-24 17:09:58 -0700384 mCallbacks.remove(soundModel.getUuid());
Chris Thorntonae5fb992017-12-07 18:26:31 -0800385 }
Chris Thorntonba08b792017-06-08 22:34:37 -0700386 }
Nicholas Ambur1e437672020-03-24 17:09:58 -0700387 mLoadedModels.put(soundModel.getUuid(), soundModel);
Chris Thorntonba08b792017-06-08 22:34:37 -0700388 }
389 return STATUS_OK;
390 }
391
392 @Override
393 public int loadKeyphraseSoundModel(KeyphraseSoundModel soundModel) {
394 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
395 if (!isInitialized()) return STATUS_ERROR;
Nicholas Ambur1e437672020-03-24 17:09:58 -0700396 if (soundModel == null || soundModel.getUuid() == null) {
Chris Thorntonba08b792017-06-08 22:34:37 -0700397 Slog.e(TAG, "Invalid sound model");
Jason Hsu1363f582019-04-16 15:35:55 +0800398
399 sEventLogger.log(new SoundTriggerLogger.StringEvent(
400 "loadKeyphraseSoundModel(): Invalid sound model"));
401
Chris Thorntonba08b792017-06-08 22:34:37 -0700402 return STATUS_ERROR;
403 }
Nicholas Ambur1e437672020-03-24 17:09:58 -0700404 if (soundModel.getKeyphrases() == null || soundModel.getKeyphrases().length != 1) {
Chris Thorntonba08b792017-06-08 22:34:37 -0700405 Slog.e(TAG, "Only one keyphrase per model is currently supported.");
Jason Hsu1363f582019-04-16 15:35:55 +0800406
407 sEventLogger.log(new SoundTriggerLogger.StringEvent(
408 "loadKeyphraseSoundModel(): Only one keyphrase per model"
409 + " is currently supported."));
410
Chris Thorntonba08b792017-06-08 22:34:37 -0700411 return STATUS_ERROR;
412 }
413 if (DEBUG) {
Nicholas Ambur1e437672020-03-24 17:09:58 -0700414 Slog.i(TAG, "loadKeyphraseSoundModel(): id = " + soundModel.getUuid());
Chris Thorntonba08b792017-06-08 22:34:37 -0700415 }
Jason Hsu1363f582019-04-16 15:35:55 +0800416
417 sEventLogger.log(new SoundTriggerLogger.StringEvent("loadKeyphraseSoundModel(): id = "
Nicholas Ambur1e437672020-03-24 17:09:58 -0700418 + soundModel.getUuid()));
Jason Hsu1363f582019-04-16 15:35:55 +0800419
Chris Thorntonba08b792017-06-08 22:34:37 -0700420 synchronized (mLock) {
Nicholas Ambur1e437672020-03-24 17:09:58 -0700421 SoundModel oldModel = mLoadedModels.get(soundModel.getUuid());
Chris Thorntonba08b792017-06-08 22:34:37 -0700422 // If the model we're loading is actually different than what we had loaded, we
423 // should unload that other model now. We don't care about return codes since we
424 // don't know if the other model is loaded.
425 if (oldModel != null && !oldModel.equals(soundModel)) {
Nicholas Ambur1e437672020-03-24 17:09:58 -0700426 mSoundTriggerHelper.unloadKeyphraseSoundModel(
427 soundModel.getKeyphrases()[0].getId());
Chris Thorntonae5fb992017-12-07 18:26:31 -0800428 synchronized (mCallbacksLock) {
Nicholas Ambur1e437672020-03-24 17:09:58 -0700429 mCallbacks.remove(soundModel.getUuid());
Chris Thorntonae5fb992017-12-07 18:26:31 -0800430 }
Chris Thorntonba08b792017-06-08 22:34:37 -0700431 }
Nicholas Ambur1e437672020-03-24 17:09:58 -0700432 mLoadedModels.put(soundModel.getUuid(), soundModel);
Chris Thorntonba08b792017-06-08 22:34:37 -0700433 }
434 return STATUS_OK;
435 }
436
437 @Override
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800438 public int startRecognitionForService(ParcelUuid soundModelId, Bundle params,
439 ComponentName detectionService, SoundTrigger.RecognitionConfig config) {
Daulet Zhanguzine789eee2019-12-20 17:19:33 +0000440 Objects.requireNonNull(soundModelId);
441 Objects.requireNonNull(detectionService);
442 Objects.requireNonNull(config);
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800443
Chris Thorntonba08b792017-06-08 22:34:37 -0700444 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
Philip P. Moltmann19402f52018-03-27 14:38:03 -0700445
Chris Thorntonba08b792017-06-08 22:34:37 -0700446 if (!isInitialized()) return STATUS_ERROR;
447 if (DEBUG) {
448 Slog.i(TAG, "startRecognition(): id = " + soundModelId);
449 }
450
Jason Hsu1363f582019-04-16 15:35:55 +0800451 sEventLogger.log(new SoundTriggerLogger.StringEvent(
452 "startRecognitionForService(): id = " + soundModelId));
453
Philip P. Moltmann19402f52018-03-27 14:38:03 -0700454 IRecognitionStatusCallback callback =
455 new RemoteSoundTriggerDetectionService(soundModelId.getUuid(), params,
456 detectionService, Binder.getCallingUserHandle(), config);
457
Chris Thorntonba08b792017-06-08 22:34:37 -0700458 synchronized (mLock) {
459 SoundModel soundModel = mLoadedModels.get(soundModelId.getUuid());
460 if (soundModel == null) {
461 Slog.e(TAG, soundModelId + " is not loaded");
Jason Hsu1363f582019-04-16 15:35:55 +0800462
463 sEventLogger.log(new SoundTriggerLogger.StringEvent(
464 "startRecognitionForService():" + soundModelId + " is not loaded"));
465
Chris Thorntonba08b792017-06-08 22:34:37 -0700466 return STATUS_ERROR;
467 }
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800468 IRecognitionStatusCallback existingCallback = null;
Chris Thorntonae5fb992017-12-07 18:26:31 -0800469 synchronized (mCallbacksLock) {
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800470 existingCallback = mCallbacks.get(soundModelId.getUuid());
Chris Thorntonae5fb992017-12-07 18:26:31 -0800471 }
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800472 if (existingCallback != null) {
Chris Thorntonba08b792017-06-08 22:34:37 -0700473 Slog.e(TAG, soundModelId + " is already running");
Jason Hsu1363f582019-04-16 15:35:55 +0800474
475 sEventLogger.log(new SoundTriggerLogger.StringEvent(
476 "startRecognitionForService():"
477 + soundModelId + " is already running"));
478
Chris Thorntonba08b792017-06-08 22:34:37 -0700479 return STATUS_ERROR;
480 }
Chris Thorntonba08b792017-06-08 22:34:37 -0700481 int ret;
Nicholas Ambur1e437672020-03-24 17:09:58 -0700482 switch (soundModel.getType()) {
Chris Thorntonba08b792017-06-08 22:34:37 -0700483 case SoundModel.TYPE_GENERIC_SOUND:
Nicholas Ambur1e437672020-03-24 17:09:58 -0700484 ret = mSoundTriggerHelper.startGenericRecognition(soundModel.getUuid(),
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800485 (GenericSoundModel) soundModel, callback, config);
Chris Thorntonba08b792017-06-08 22:34:37 -0700486 break;
487 default:
488 Slog.e(TAG, "Unknown model type");
Jason Hsu1363f582019-04-16 15:35:55 +0800489
490 sEventLogger.log(new SoundTriggerLogger.StringEvent(
491 "startRecognitionForService(): Unknown model type"));
492
Chris Thorntonba08b792017-06-08 22:34:37 -0700493 return STATUS_ERROR;
494 }
495
496 if (ret != STATUS_OK) {
497 Slog.e(TAG, "Failed to start model: " + ret);
Jason Hsu1363f582019-04-16 15:35:55 +0800498
499 sEventLogger.log(new SoundTriggerLogger.StringEvent(
500 "startRecognitionForService(): Failed to start model:"));
501
Chris Thorntonba08b792017-06-08 22:34:37 -0700502 return ret;
503 }
Chris Thorntonae5fb992017-12-07 18:26:31 -0800504 synchronized (mCallbacksLock) {
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800505 mCallbacks.put(soundModelId.getUuid(), callback);
Chris Thorntonae5fb992017-12-07 18:26:31 -0800506 }
Benjamin Schwartz9e7a0152019-06-06 17:39:22 -0700507
508 mSoundModelStatTracker.onStart(soundModelId.getUuid());
Chris Thorntonba08b792017-06-08 22:34:37 -0700509 }
510 return STATUS_OK;
511 }
512
513 @Override
Philip P. Moltmann19402f52018-03-27 14:38:03 -0700514 public int stopRecognitionForService(ParcelUuid soundModelId) {
Chris Thorntonba08b792017-06-08 22:34:37 -0700515 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
516 if (!isInitialized()) return STATUS_ERROR;
517 if (DEBUG) {
518 Slog.i(TAG, "stopRecognition(): id = " + soundModelId);
519 }
520
Jason Hsu1363f582019-04-16 15:35:55 +0800521 sEventLogger.log(new SoundTriggerLogger.StringEvent(
522 "stopRecognitionForService(): id = " + soundModelId));
523
Chris Thorntonba08b792017-06-08 22:34:37 -0700524 synchronized (mLock) {
525 SoundModel soundModel = mLoadedModels.get(soundModelId.getUuid());
526 if (soundModel == null) {
527 Slog.e(TAG, soundModelId + " is not loaded");
Jason Hsu1363f582019-04-16 15:35:55 +0800528
529 sEventLogger.log(new SoundTriggerLogger.StringEvent(
530 "stopRecognitionForService(): " + soundModelId
531 + " is not loaded"));
532
Chris Thorntonba08b792017-06-08 22:34:37 -0700533 return STATUS_ERROR;
534 }
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800535 IRecognitionStatusCallback callback = null;
Chris Thorntonae5fb992017-12-07 18:26:31 -0800536 synchronized (mCallbacksLock) {
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800537 callback = mCallbacks.get(soundModelId.getUuid());
Chris Thorntonae5fb992017-12-07 18:26:31 -0800538 }
Chris Thorntonba08b792017-06-08 22:34:37 -0700539 if (callback == null) {
540 Slog.e(TAG, soundModelId + " is not running");
Jason Hsu1363f582019-04-16 15:35:55 +0800541
542 sEventLogger.log(new SoundTriggerLogger.StringEvent(
543 "stopRecognitionForService(): " + soundModelId
544 + " is not running"));
545
Chris Thorntonba08b792017-06-08 22:34:37 -0700546 return STATUS_ERROR;
547 }
548 int ret;
Nicholas Ambur1e437672020-03-24 17:09:58 -0700549 switch (soundModel.getType()) {
Chris Thorntonba08b792017-06-08 22:34:37 -0700550 case SoundModel.TYPE_GENERIC_SOUND:
Nicholas Ambur1e437672020-03-24 17:09:58 -0700551 ret = mSoundTriggerHelper.stopGenericRecognition(
552 soundModel.getUuid(), callback);
Chris Thorntonba08b792017-06-08 22:34:37 -0700553 break;
554 default:
555 Slog.e(TAG, "Unknown model type");
Jason Hsu1363f582019-04-16 15:35:55 +0800556
557 sEventLogger.log(new SoundTriggerLogger.StringEvent(
558 "stopRecognitionForService(): Unknown model type"));
559
Chris Thorntonba08b792017-06-08 22:34:37 -0700560 return STATUS_ERROR;
561 }
562
563 if (ret != STATUS_OK) {
564 Slog.e(TAG, "Failed to stop model: " + ret);
Jason Hsu1363f582019-04-16 15:35:55 +0800565
566 sEventLogger.log(new SoundTriggerLogger.StringEvent(
567 "stopRecognitionForService(): Failed to stop model: " + ret));
568
Chris Thorntonba08b792017-06-08 22:34:37 -0700569 return ret;
570 }
Chris Thorntonae5fb992017-12-07 18:26:31 -0800571 synchronized (mCallbacksLock) {
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800572 mCallbacks.remove(soundModelId.getUuid());
Chris Thorntonae5fb992017-12-07 18:26:31 -0800573 }
Benjamin Schwartz9e7a0152019-06-06 17:39:22 -0700574
575 mSoundModelStatTracker.onStop(soundModelId.getUuid());
Chris Thorntonba08b792017-06-08 22:34:37 -0700576 }
577 return STATUS_OK;
578 }
579
580 @Override
581 public int unloadSoundModel(ParcelUuid soundModelId) {
582 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
583 if (!isInitialized()) return STATUS_ERROR;
584 if (DEBUG) {
585 Slog.i(TAG, "unloadSoundModel(): id = " + soundModelId);
586 }
587
Jason Hsu1363f582019-04-16 15:35:55 +0800588 sEventLogger.log(new SoundTriggerLogger.StringEvent("unloadSoundModel(): id = "
589 + soundModelId));
590
Chris Thorntonba08b792017-06-08 22:34:37 -0700591 synchronized (mLock) {
592 SoundModel soundModel = mLoadedModels.get(soundModelId.getUuid());
593 if (soundModel == null) {
594 Slog.e(TAG, soundModelId + " is not loaded");
Jason Hsu1363f582019-04-16 15:35:55 +0800595
596 sEventLogger.log(new SoundTriggerLogger.StringEvent(
597 "unloadSoundModel(): " + soundModelId + " is not loaded"));
598
Chris Thorntonba08b792017-06-08 22:34:37 -0700599 return STATUS_ERROR;
600 }
601 int ret;
Nicholas Ambur1e437672020-03-24 17:09:58 -0700602 switch (soundModel.getType()) {
Chris Thorntonba08b792017-06-08 22:34:37 -0700603 case SoundModel.TYPE_KEYPHRASE:
604 ret = mSoundTriggerHelper.unloadKeyphraseSoundModel(
Nicholas Ambur1e437672020-03-24 17:09:58 -0700605 ((KeyphraseSoundModel) soundModel).getKeyphrases()[0].getId());
Chris Thorntonba08b792017-06-08 22:34:37 -0700606 break;
607 case SoundModel.TYPE_GENERIC_SOUND:
Nicholas Ambur1e437672020-03-24 17:09:58 -0700608 ret = mSoundTriggerHelper.unloadGenericSoundModel(soundModel.getUuid());
Chris Thorntonba08b792017-06-08 22:34:37 -0700609 break;
610 default:
611 Slog.e(TAG, "Unknown model type");
Jason Hsu1363f582019-04-16 15:35:55 +0800612
613 sEventLogger.log(new SoundTriggerLogger.StringEvent(
614 "unloadSoundModel(): Unknown model type"));
615
Chris Thorntonba08b792017-06-08 22:34:37 -0700616 return STATUS_ERROR;
617 }
618 if (ret != STATUS_OK) {
619 Slog.e(TAG, "Failed to unload model");
Jason Hsu1363f582019-04-16 15:35:55 +0800620
621 sEventLogger.log(new SoundTriggerLogger.StringEvent(
622 "unloadSoundModel(): Failed to unload model"));
623
Chris Thorntonba08b792017-06-08 22:34:37 -0700624 return ret;
625 }
626 mLoadedModels.remove(soundModelId.getUuid());
627 return STATUS_OK;
628 }
629 }
630
631 @Override
632 public boolean isRecognitionActive(ParcelUuid parcelUuid) {
633 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
634 if (!isInitialized()) return false;
Chris Thorntonae5fb992017-12-07 18:26:31 -0800635 synchronized (mCallbacksLock) {
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800636 IRecognitionStatusCallback callback = mCallbacks.get(parcelUuid.getUuid());
Chris Thorntonba08b792017-06-08 22:34:37 -0700637 if (callback == null) {
638 return false;
639 }
Chris Thorntonba08b792017-06-08 22:34:37 -0700640 }
Chris Thorntonae5fb992017-12-07 18:26:31 -0800641 return mSoundTriggerHelper.isRecognitionRequested(parcelUuid.getUuid());
Chris Thorntonba08b792017-06-08 22:34:37 -0700642 }
Michael Dooley291751e2018-10-16 19:53:29 +0000643
644 @Override
mike dooleyb2ab04a2018-11-07 15:48:54 +0100645 public int getModelState(ParcelUuid soundModelId) {
Michael Dooley291751e2018-10-16 19:53:29 +0000646 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
mike dooleyb2ab04a2018-11-07 15:48:54 +0100647 int ret = STATUS_ERROR;
648 if (!isInitialized()) return ret;
Michael Dooley291751e2018-10-16 19:53:29 +0000649 if (DEBUG) {
650 Slog.i(TAG, "getModelState(): id = " + soundModelId);
651 }
652
Jason Hsu1363f582019-04-16 15:35:55 +0800653 sEventLogger.log(new SoundTriggerLogger.StringEvent("getModelState(): id = "
654 + soundModelId));
655
Michael Dooley291751e2018-10-16 19:53:29 +0000656 synchronized (mLock) {
657 SoundModel soundModel = mLoadedModels.get(soundModelId.getUuid());
658 if (soundModel == null) {
659 Slog.e(TAG, soundModelId + " is not loaded");
Jason Hsu1363f582019-04-16 15:35:55 +0800660
661 sEventLogger.log(new SoundTriggerLogger.StringEvent("getModelState(): "
662 + soundModelId + " is not loaded"));
663
mike dooleyb2ab04a2018-11-07 15:48:54 +0100664 return ret;
Michael Dooley291751e2018-10-16 19:53:29 +0000665 }
Nicholas Ambur1e437672020-03-24 17:09:58 -0700666 switch (soundModel.getType()) {
Michael Dooley291751e2018-10-16 19:53:29 +0000667 case SoundModel.TYPE_GENERIC_SOUND:
Nicholas Ambur1e437672020-03-24 17:09:58 -0700668 ret = mSoundTriggerHelper.getGenericModelState(soundModel.getUuid());
Michael Dooley291751e2018-10-16 19:53:29 +0000669 break;
670 default:
mike dooleybaa22c72019-05-15 10:05:25 +0200671 // SoundModel.TYPE_KEYPHRASE is not supported to increase privacy.
Nicholas Ambur1e437672020-03-24 17:09:58 -0700672 Slog.e(TAG, "Unsupported model type, " + soundModel.getType());
Jason Hsu1363f582019-04-16 15:35:55 +0800673 sEventLogger.log(new SoundTriggerLogger.StringEvent(
mike dooleybaa22c72019-05-15 10:05:25 +0200674 "getModelState(): Unsupported model type, "
Nicholas Ambur1e437672020-03-24 17:09:58 -0700675 + soundModel.getType()));
Michael Dooley291751e2018-10-16 19:53:29 +0000676 break;
677 }
Michael Dooley291751e2018-10-16 19:53:29 +0000678
679 return ret;
680 }
681 }
Nicholas Ambur1aa4b4b2019-08-22 12:13:29 -0700682
683 @Override
684 @Nullable
685 public ModuleProperties getModuleProperties() {
686 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
687 if (!isInitialized()) return null;
688 if (DEBUG) {
689 Slog.i(TAG, "getModuleProperties()");
690 }
691
692 synchronized (mLock) {
693 ModuleProperties properties = mSoundTriggerHelper.getModuleProperties();
694 sEventLogger.log(new SoundTriggerLogger.StringEvent(
695 "getModuleProperties(): " + properties.toString()));
696 return properties;
697 }
698 }
Nicholas Ambura0be6be2019-10-01 10:11:39 -0700699
700 @Override
701 public int setParameter(ParcelUuid soundModelId,
702 @ModelParams int modelParam, int value) {
703 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
704 if (!isInitialized()) return STATUS_NO_INIT;
705 if (DEBUG) {
706 Slog.d(TAG, "setParameter(): id=" + soundModelId
707 + ", param=" + modelParam
708 + ", value=" + value);
709 }
710
711 sEventLogger.log(new SoundTriggerLogger.StringEvent(
712 "setParameter(): id=" + soundModelId
713 + ", param=" + modelParam
714 + ", value=" + value));
715
716 synchronized (mLock) {
717 SoundModel soundModel = mLoadedModels.get(soundModelId.getUuid());
718 if (soundModel == null) {
719 Slog.e(TAG, soundModelId + " is not loaded. Loaded models: "
720 + mLoadedModels.toString());
721
722 sEventLogger.log(new SoundTriggerLogger.StringEvent("setParameter(): "
723 + soundModelId + " is not loaded"));
724
725 return STATUS_BAD_VALUE;
726 }
727
Nicholas Ambur1e437672020-03-24 17:09:58 -0700728 return mSoundTriggerHelper.setParameter(soundModel.getUuid(), modelParam, value);
Nicholas Ambura0be6be2019-10-01 10:11:39 -0700729 }
730 }
731
732 @Override
733 public int getParameter(@NonNull ParcelUuid soundModelId,
734 @ModelParams int modelParam)
735 throws UnsupportedOperationException, IllegalArgumentException {
736 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
737 if (!isInitialized()) {
738 throw new UnsupportedOperationException("SoundTriggerHelper not initialized");
739 }
740 if (DEBUG) {
741 Slog.d(TAG, "getParameter(): id=" + soundModelId
742 + ", param=" + modelParam);
743 }
744
745 sEventLogger.log(new SoundTriggerLogger.StringEvent(
746 "getParameter(): id=" + soundModelId
747 + ", param=" + modelParam));
748
749 synchronized (mLock) {
750 SoundModel soundModel = mLoadedModels.get(soundModelId.getUuid());
751 if (soundModel == null) {
752 Slog.e(TAG, soundModelId + " is not loaded");
753
754 sEventLogger.log(new SoundTriggerLogger.StringEvent("getParameter(): "
755 + soundModelId + " is not loaded"));
756
757 throw new IllegalArgumentException("sound model is not loaded");
758 }
759
Nicholas Ambur1e437672020-03-24 17:09:58 -0700760 return mSoundTriggerHelper.getParameter(soundModel.getUuid(), modelParam);
Nicholas Ambura0be6be2019-10-01 10:11:39 -0700761 }
762 }
763
764 @Override
765 @Nullable
766 public ModelParamRange queryParameter(@NonNull ParcelUuid soundModelId,
767 @ModelParams int modelParam) {
768 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
769 if (!isInitialized()) return null;
770 if (DEBUG) {
771 Slog.d(TAG, "queryParameter(): id=" + soundModelId
772 + ", param=" + modelParam);
773 }
774
775 sEventLogger.log(new SoundTriggerLogger.StringEvent(
776 "queryParameter(): id=" + soundModelId
777 + ", param=" + modelParam));
778
779 synchronized (mLock) {
780 SoundModel soundModel = mLoadedModels.get(soundModelId.getUuid());
781 if (soundModel == null) {
782 Slog.e(TAG, soundModelId + " is not loaded");
783
784 sEventLogger.log(new SoundTriggerLogger.StringEvent(
785 "queryParameter(): "
786 + soundModelId + " is not loaded"));
787
788 return null;
789 }
790
Nicholas Ambur1e437672020-03-24 17:09:58 -0700791 return mSoundTriggerHelper.queryParameter(soundModel.getUuid(), modelParam);
Nicholas Ambura0be6be2019-10-01 10:11:39 -0700792 }
793 }
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800794 }
795
Philip P. Moltmann7e25b3d2018-03-09 20:22:58 -0800796 /**
797 * Counts the number of operations added in the last 24 hours.
798 */
799 private static class NumOps {
800 private final Object mLock = new Object();
801
802 @GuardedBy("mLock")
803 private int[] mNumOps = new int[24];
804 @GuardedBy("mLock")
805 private long mLastOpsHourSinceBoot;
806
807 /**
808 * Clear buckets of new hours that have elapsed since last operation.
809 *
810 * <p>I.e. when the last operation was triggered at 1:40 and the current operation was
811 * triggered at 4:03, the buckets "2, 3, and 4" are cleared.
812 *
813 * @param currentTime Current elapsed time since boot in ns
814 */
815 void clearOldOps(long currentTime) {
816 synchronized (mLock) {
817 long numHoursSinceBoot = TimeUnit.HOURS.convert(currentTime, TimeUnit.NANOSECONDS);
818
819 // Clear buckets of new hours that have elapsed since last operation
820 // I.e. when the last operation was triggered at 1:40 and the current
821 // operation was triggered at 4:03, the bucket "2, 3, and 4" is cleared
822 if (mLastOpsHourSinceBoot != 0) {
823 for (long hour = mLastOpsHourSinceBoot + 1; hour <= numHoursSinceBoot; hour++) {
824 mNumOps[(int) (hour % 24)] = 0;
825 }
826 }
827 }
828 }
829
830 /**
831 * Add a new operation.
832 *
833 * @param currentTime Current elapsed time since boot in ns
834 */
835 void addOp(long currentTime) {
836 synchronized (mLock) {
837 long numHoursSinceBoot = TimeUnit.HOURS.convert(currentTime, TimeUnit.NANOSECONDS);
838
839 mNumOps[(int) (numHoursSinceBoot % 24)]++;
840 mLastOpsHourSinceBoot = numHoursSinceBoot;
841 }
842 }
843
844 /**
845 * Get the total operations added in the last 24 hours.
846 *
847 * @return The total number of operations added in the last 24 hours
848 */
849 int getOpsAdded() {
850 synchronized (mLock) {
851 int totalOperationsInLastDay = 0;
852 for (int i = 0; i < 24; i++) {
853 totalOperationsInLastDay += mNumOps[i];
854 }
855
856 return totalOperationsInLastDay;
857 }
858 }
859 }
860
Philip P. Moltmanna5b44032018-05-04 13:59:45 -0700861 /**
862 * A single operation run in a {@link RemoteSoundTriggerDetectionService}.
863 *
864 * <p>Once the remote service is connected either setup + execute or setup + stop is executed.
865 */
866 private static class Operation {
867 private interface ExecuteOp {
868 void run(int opId, ISoundTriggerDetectionService service) throws RemoteException;
869 }
870
871 private final @Nullable Runnable mSetupOp;
872 private final @NonNull ExecuteOp mExecuteOp;
873 private final @Nullable Runnable mDropOp;
874
875 private Operation(@Nullable Runnable setupOp, @NonNull ExecuteOp executeOp,
876 @Nullable Runnable cancelOp) {
877 mSetupOp = setupOp;
878 mExecuteOp = executeOp;
879 mDropOp = cancelOp;
880 }
881
882 private void setup() {
883 if (mSetupOp != null) {
884 mSetupOp.run();
885 }
886 }
887
888 void run(int opId, @NonNull ISoundTriggerDetectionService service) throws RemoteException {
889 setup();
890 mExecuteOp.run(opId, service);
891 }
892
893 void drop() {
894 setup();
895
896 if (mDropOp != null) {
897 mDropOp.run();
898 }
899 }
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800900 }
901
902 /**
903 * Local end for a {@link SoundTriggerDetectionService}. Operations are queued up and executed
904 * when the service connects.
905 *
906 * <p>If operations take too long they are forcefully aborted.
907 *
908 * <p>This also limits the amount of operations in 24 hours.
909 */
910 private class RemoteSoundTriggerDetectionService
911 extends IRecognitionStatusCallback.Stub implements ServiceConnection {
912 private static final int MSG_STOP_ALL_PENDING_OPERATIONS = 1;
913
914 private final Object mRemoteServiceLock = new Object();
915
916 /** UUID of the model the service is started for */
917 private final @NonNull ParcelUuid mPuuid;
918 /** Params passed into the start method for the service */
919 private final @Nullable Bundle mParams;
920 /** Component name passed when starting the service */
921 private final @NonNull ComponentName mServiceName;
922 /** User that started the service */
923 private final @NonNull UserHandle mUser;
924 /** Configuration of the recognition the service is handling */
925 private final @NonNull RecognitionConfig mRecognitionConfig;
926 /** Wake lock keeping the remote service alive */
927 private final @NonNull PowerManager.WakeLock mRemoteServiceWakeLock;
928
929 private final @NonNull Handler mHandler;
930
931 /** Callbacks that are called by the service */
932 private final @NonNull ISoundTriggerDetectionServiceClient mClient;
933
934 /** Operations that are pending because the service is not yet connected */
935 @GuardedBy("mRemoteServiceLock")
936 private final ArrayList<Operation> mPendingOps = new ArrayList<>();
937 /** Operations that have been send to the service but have no yet finished */
938 @GuardedBy("mRemoteServiceLock")
939 private final ArraySet<Integer> mRunningOpIds = new ArraySet<>();
Philip P. Moltmann7e25b3d2018-03-09 20:22:58 -0800940 /** The number of operations executed in each of the last 24 hours */
941 private final NumOps mNumOps;
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800942
943 /** The service binder if connected */
944 @GuardedBy("mRemoteServiceLock")
945 private @Nullable ISoundTriggerDetectionService mService;
946 /** Whether the service has been bound */
947 @GuardedBy("mRemoteServiceLock")
948 private boolean mIsBound;
949 /** Whether the service has been destroyed */
950 @GuardedBy("mRemoteServiceLock")
951 private boolean mIsDestroyed;
952 /**
953 * Set once a final op is scheduled. No further ops can be added and the service is
954 * destroyed once the op finishes.
955 */
956 @GuardedBy("mRemoteServiceLock")
957 private boolean mDestroyOnceRunningOpsDone;
958
959 /** Total number of operations performed by this service */
960 @GuardedBy("mRemoteServiceLock")
961 private int mNumTotalOpsPerformed;
962
963 /**
964 * Create a new remote sound trigger detection service. This only binds to the service when
965 * operations are in flight. Each operation has a certain time it can run. Once no
966 * operations are allowed to run anymore, {@link #stopAllPendingOperations() all operations
967 * are aborted and stopped} and the service is disconnected.
968 *
969 * @param modelUuid The UUID of the model the recognition is for
970 * @param params The params passed to each method of the service
971 * @param serviceName The component name of the service
972 * @param user The user of the service
973 * @param config The configuration of the recognition
974 */
975 public RemoteSoundTriggerDetectionService(@NonNull UUID modelUuid,
976 @Nullable Bundle params, @NonNull ComponentName serviceName, @NonNull UserHandle user,
977 @NonNull RecognitionConfig config) {
978 mPuuid = new ParcelUuid(modelUuid);
979 mParams = params;
980 mServiceName = serviceName;
981 mUser = user;
982 mRecognitionConfig = config;
983 mHandler = new Handler(Looper.getMainLooper());
984
985 PowerManager pm = ((PowerManager) mContext.getSystemService(Context.POWER_SERVICE));
986 mRemoteServiceWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
987 "RemoteSoundTriggerDetectionService " + mServiceName.getPackageName() + ":"
988 + mServiceName.getClassName());
989
Philip P. Moltmann7e25b3d2018-03-09 20:22:58 -0800990 synchronized (mLock) {
991 NumOps numOps = mNumOpsPerPackage.get(mServiceName.getPackageName());
992 if (numOps == null) {
993 numOps = new NumOps();
994 mNumOpsPerPackage.put(mServiceName.getPackageName(), numOps);
995 }
996 mNumOps = numOps;
997 }
998
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800999 mClient = new ISoundTriggerDetectionServiceClient.Stub() {
1000 @Override
1001 public void onOpFinished(int opId) {
1002 long token = Binder.clearCallingIdentity();
1003 try {
1004 synchronized (mRemoteServiceLock) {
1005 mRunningOpIds.remove(opId);
1006
1007 if (mRunningOpIds.isEmpty() && mPendingOps.isEmpty()) {
1008 if (mDestroyOnceRunningOpsDone) {
1009 destroy();
1010 } else {
1011 disconnectLocked();
1012 }
1013 }
1014 }
1015 } finally {
1016 Binder.restoreCallingIdentity(token);
1017 }
1018 }
1019 };
1020 }
1021
1022 @Override
1023 public boolean pingBinder() {
1024 return !(mIsDestroyed || mDestroyOnceRunningOpsDone);
1025 }
1026
1027 /**
1028 * Disconnect from the service, but allow to re-connect when new operations are triggered.
1029 */
Andreas Gampe8ce7ed92018-09-05 16:53:00 -07001030 @GuardedBy("mRemoteServiceLock")
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -08001031 private void disconnectLocked() {
1032 if (mService != null) {
1033 try {
1034 mService.removeClient(mPuuid);
1035 } catch (Exception e) {
1036 Slog.e(TAG, mPuuid + ": Cannot remove client", e);
Jason Hsu1363f582019-04-16 15:35:55 +08001037
1038 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
1039 + ": Cannot remove client"));
1040
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -08001041 }
1042
1043 mService = null;
1044 }
1045
1046 if (mIsBound) {
1047 mContext.unbindService(RemoteSoundTriggerDetectionService.this);
1048 mIsBound = false;
1049
1050 synchronized (mCallbacksLock) {
1051 mRemoteServiceWakeLock.release();
1052 }
1053 }
1054 }
1055
1056 /**
1057 * Disconnect, do not allow to reconnect to the service. All further operations will be
1058 * dropped.
1059 */
1060 private void destroy() {
1061 if (DEBUG) Slog.v(TAG, mPuuid + ": destroy");
1062
Jason Hsu1363f582019-04-16 15:35:55 +08001063 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid + ": destroy"));
1064
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -08001065 synchronized (mRemoteServiceLock) {
1066 disconnectLocked();
1067
1068 mIsDestroyed = true;
1069 }
1070
1071 // The callback is removed before the flag is set
1072 if (!mDestroyOnceRunningOpsDone) {
1073 synchronized (mCallbacksLock) {
1074 mCallbacks.remove(mPuuid.getUuid());
1075 }
1076 }
1077 }
1078
1079 /**
1080 * Stop all pending operations and then disconnect for the service.
1081 */
1082 private void stopAllPendingOperations() {
1083 synchronized (mRemoteServiceLock) {
1084 if (mIsDestroyed) {
1085 return;
1086 }
1087
1088 if (mService != null) {
1089 int numOps = mRunningOpIds.size();
1090 for (int i = 0; i < numOps; i++) {
1091 try {
1092 mService.onStopOperation(mPuuid, mRunningOpIds.valueAt(i));
1093 } catch (Exception e) {
1094 Slog.e(TAG, mPuuid + ": Could not stop operation "
1095 + mRunningOpIds.valueAt(i), e);
Jason Hsu1363f582019-04-16 15:35:55 +08001096
1097 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
1098 + ": Could not stop operation " + mRunningOpIds.valueAt(i)));
1099
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -08001100 }
1101 }
1102
1103 mRunningOpIds.clear();
1104 }
1105
1106 disconnectLocked();
1107 }
1108 }
1109
1110 /**
1111 * Verify that the service has the expected properties and then bind to the service
1112 */
1113 private void bind() {
1114 long token = Binder.clearCallingIdentity();
1115 try {
1116 Intent i = new Intent();
1117 i.setComponent(mServiceName);
1118
1119 ResolveInfo ri = mContext.getPackageManager().resolveServiceAsUser(i,
1120 GET_SERVICES | GET_META_DATA | MATCH_DEBUG_TRIAGED_MISSING,
1121 mUser.getIdentifier());
1122
1123 if (ri == null) {
1124 Slog.w(TAG, mPuuid + ": " + mServiceName + " not found");
Jason Hsu1363f582019-04-16 15:35:55 +08001125
1126 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
1127 + ": " + mServiceName + " not found"));
1128
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -08001129 return;
1130 }
1131
1132 if (!BIND_SOUND_TRIGGER_DETECTION_SERVICE
1133 .equals(ri.serviceInfo.permission)) {
1134 Slog.w(TAG, mPuuid + ": " + mServiceName + " does not require "
1135 + BIND_SOUND_TRIGGER_DETECTION_SERVICE);
Jason Hsu1363f582019-04-16 15:35:55 +08001136
1137 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
1138 + ": " + mServiceName + " does not require "
1139 + BIND_SOUND_TRIGGER_DETECTION_SERVICE));
1140
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -08001141 return;
1142 }
1143
1144 mIsBound = mContext.bindServiceAsUser(i, this,
Nick Moukhine67b5e382020-03-16 17:51:37 +01001145 BIND_AUTO_CREATE | BIND_FOREGROUND_SERVICE | BIND_INCLUDE_CAPABILITIES,
1146 mUser);
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -08001147
1148 if (mIsBound) {
1149 mRemoteServiceWakeLock.acquire();
1150 } else {
1151 Slog.w(TAG, mPuuid + ": Could not bind to " + mServiceName);
Jason Hsu1363f582019-04-16 15:35:55 +08001152
1153 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
1154 + ": Could not bind to " + mServiceName));
1155
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -08001156 }
1157 } finally {
1158 Binder.restoreCallingIdentity(token);
1159 }
1160 }
1161
1162 /**
1163 * Run an operation (i.e. send it do the service). If the service is not connected, this
1164 * binds the service and then runs the operation once connected.
1165 *
1166 * @param op The operation to run
1167 */
1168 private void runOrAddOperation(Operation op) {
1169 synchronized (mRemoteServiceLock) {
1170 if (mIsDestroyed || mDestroyOnceRunningOpsDone) {
Philip P. Moltmanna5b44032018-05-04 13:59:45 -07001171 Slog.w(TAG, mPuuid + ": Dropped operation as already destroyed or marked for "
1172 + "destruction");
1173
Jason Hsu1363f582019-04-16 15:35:55 +08001174 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
1175 + ":Dropped operation as already destroyed or marked for destruction"));
1176
Philip P. Moltmanna5b44032018-05-04 13:59:45 -07001177 op.drop();
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -08001178 return;
1179 }
1180
1181 if (mService == null) {
1182 mPendingOps.add(op);
1183
1184 if (!mIsBound) {
1185 bind();
1186 }
1187 } else {
Philip P. Moltmann7e25b3d2018-03-09 20:22:58 -08001188 long currentTime = System.nanoTime();
1189 mNumOps.clearOldOps(currentTime);
1190
1191 // Drop operation if too many were executed in the last 24 hours.
1192 int opsAllowed = Settings.Global.getInt(mContext.getContentResolver(),
1193 MAX_SOUND_TRIGGER_DETECTION_SERVICE_OPS_PER_DAY,
1194 Integer.MAX_VALUE);
1195
Philip P. Moltmann3d1683b2018-05-07 15:53:51 -07001196 // As we currently cannot dropping an op safely, disable throttling
Philip P. Moltmann7e25b3d2018-03-09 20:22:58 -08001197 int opsAdded = mNumOps.getOpsAdded();
Philip P. Moltmann3d1683b2018-05-07 15:53:51 -07001198 if (false && mNumOps.getOpsAdded() >= opsAllowed) {
Philip P. Moltmanna5b44032018-05-04 13:59:45 -07001199 try {
1200 if (DEBUG || opsAllowed + 10 > opsAdded) {
1201 Slog.w(TAG, mPuuid + ": Dropped operation as too many operations "
1202 + "were run in last 24 hours");
Jason Hsu1363f582019-04-16 15:35:55 +08001203
1204 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
1205 + ": Dropped operation as too many operations "
1206 + "were run in last 24 hours"));
1207
Philip P. Moltmanna5b44032018-05-04 13:59:45 -07001208 }
1209
1210 op.drop();
1211 } catch (Exception e) {
1212 Slog.e(TAG, mPuuid + ": Could not drop operation", e);
Jason Hsu1363f582019-04-16 15:35:55 +08001213
1214 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
1215 + ": Could not drop operation"));
1216
Philip P. Moltmann7e25b3d2018-03-09 20:22:58 -08001217 }
Philip P. Moltmann09dd8c42018-04-11 09:23:19 -07001218 } else {
1219 mNumOps.addOp(currentTime);
Philip P. Moltmann7e25b3d2018-03-09 20:22:58 -08001220
Philip P. Moltmann09dd8c42018-04-11 09:23:19 -07001221 // Find a free opID
1222 int opId = mNumTotalOpsPerformed;
1223 do {
1224 mNumTotalOpsPerformed++;
1225 } while (mRunningOpIds.contains(opId));
Philip P. Moltmann7e25b3d2018-03-09 20:22:58 -08001226
Philip P. Moltmann09dd8c42018-04-11 09:23:19 -07001227 // Run OP
1228 try {
1229 if (DEBUG) Slog.v(TAG, mPuuid + ": runOp " + opId);
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -08001230
Jason Hsu1363f582019-04-16 15:35:55 +08001231 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
1232 + ": runOp " + opId));
1233
Philip P. Moltmann09dd8c42018-04-11 09:23:19 -07001234 op.run(opId, mService);
1235 mRunningOpIds.add(opId);
1236 } catch (Exception e) {
1237 Slog.e(TAG, mPuuid + ": Could not run operation " + opId, e);
Jason Hsu1363f582019-04-16 15:35:55 +08001238
1239 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
1240 + ": Could not run operation " + opId));
1241
Philip P. Moltmann09dd8c42018-04-11 09:23:19 -07001242 }
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -08001243 }
1244
1245 // Unbind from service if no operations are left (i.e. if the operation failed)
1246 if (mPendingOps.isEmpty() && mRunningOpIds.isEmpty()) {
1247 if (mDestroyOnceRunningOpsDone) {
1248 destroy();
1249 } else {
1250 disconnectLocked();
1251 }
1252 } else {
1253 mHandler.removeMessages(MSG_STOP_ALL_PENDING_OPERATIONS);
1254 mHandler.sendMessageDelayed(obtainMessage(
1255 RemoteSoundTriggerDetectionService::stopAllPendingOperations, this)
1256 .setWhat(MSG_STOP_ALL_PENDING_OPERATIONS),
1257 Settings.Global.getLong(mContext.getContentResolver(),
1258 SOUND_TRIGGER_DETECTION_SERVICE_OP_TIMEOUT,
1259 Long.MAX_VALUE));
1260 }
1261 }
1262 }
1263 }
1264
1265 @Override
1266 public void onKeyphraseDetected(SoundTrigger.KeyphraseRecognitionEvent event) {
1267 Slog.w(TAG, mPuuid + "->" + mServiceName + ": IGNORED onKeyphraseDetected(" + event
1268 + ")");
Jason Hsu1363f582019-04-16 15:35:55 +08001269
1270 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid + "->" + mServiceName
1271 + ": IGNORED onKeyphraseDetected(" + event + ")"));
1272
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -08001273 }
1274
Philip P. Moltmanna5b44032018-05-04 13:59:45 -07001275 /**
1276 * Create an AudioRecord enough for starting and releasing the data buffered for the event.
1277 *
1278 * @param event The event that was received
1279 * @return The initialized AudioRecord
1280 */
1281 private @NonNull AudioRecord createAudioRecordForEvent(
1282 @NonNull SoundTrigger.GenericRecognitionEvent event) {
1283 AudioAttributes.Builder attributesBuilder = new AudioAttributes.Builder();
1284 attributesBuilder.setInternalCapturePreset(MediaRecorder.AudioSource.HOTWORD);
1285 AudioAttributes attributes = attributesBuilder.build();
1286
1287 // Use same AudioFormat processing as in RecognitionEvent.fromParcel
1288 AudioFormat originalFormat = event.getCaptureFormat();
1289 AudioFormat captureFormat = (new AudioFormat.Builder())
1290 .setChannelMask(originalFormat.getChannelMask())
1291 .setEncoding(originalFormat.getEncoding())
1292 .setSampleRate(originalFormat.getSampleRate())
1293 .build();
1294
1295 int bufferSize = AudioRecord.getMinBufferSize(
1296 captureFormat.getSampleRate() == AudioFormat.SAMPLE_RATE_UNSPECIFIED
1297 ? AudioFormat.SAMPLE_RATE_HZ_MAX
1298 : captureFormat.getSampleRate(),
1299 captureFormat.getChannelCount() == 2
1300 ? AudioFormat.CHANNEL_IN_STEREO
1301 : AudioFormat.CHANNEL_IN_MONO,
1302 captureFormat.getEncoding());
1303
Jason Hsu1363f582019-04-16 15:35:55 +08001304 sEventLogger.log(new SoundTriggerLogger.StringEvent("createAudioRecordForEvent"));
1305
Philip P. Moltmanna5b44032018-05-04 13:59:45 -07001306 return new AudioRecord(attributes, captureFormat, bufferSize,
1307 event.getCaptureSession());
1308 }
1309
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -08001310 @Override
1311 public void onGenericSoundTriggerDetected(SoundTrigger.GenericRecognitionEvent event) {
1312 if (DEBUG) Slog.v(TAG, mPuuid + ": Generic sound trigger event: " + event);
1313
Jason Hsu1363f582019-04-16 15:35:55 +08001314 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
1315 + ": Generic sound trigger event: " + event));
1316
Philip P. Moltmanna5b44032018-05-04 13:59:45 -07001317 runOrAddOperation(new Operation(
1318 // always execute:
1319 () -> {
mike dooley9b20c1c2019-01-23 10:38:17 +01001320 if (!mRecognitionConfig.allowMultipleTriggers) {
Philip P. Moltmanna5b44032018-05-04 13:59:45 -07001321 // Unregister this remoteService once op is done
1322 synchronized (mCallbacksLock) {
1323 mCallbacks.remove(mPuuid.getUuid());
1324 }
1325 mDestroyOnceRunningOpsDone = true;
1326 }
1327 },
1328 // execute if not throttled:
1329 (opId, service) -> service.onGenericRecognitionEvent(mPuuid, opId, event),
1330 // execute if throttled:
1331 () -> {
1332 if (event.isCaptureAvailable()) {
1333 AudioRecord capturedData = createAudioRecordForEvent(event);
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -08001334
Philip P. Moltmanna5b44032018-05-04 13:59:45 -07001335 // Currently we need to start and release the audio record to reset
1336 // the DSP even if we don't want to process the event
1337 capturedData.startRecording();
1338 capturedData.release();
1339 }
1340 }));
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -08001341 }
1342
1343 @Override
1344 public void onError(int status) {
1345 if (DEBUG) Slog.v(TAG, mPuuid + ": onError: " + status);
1346
Jason Hsu1363f582019-04-16 15:35:55 +08001347 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
1348 + ": onError: " + status));
1349
Philip P. Moltmanna5b44032018-05-04 13:59:45 -07001350 runOrAddOperation(
1351 new Operation(
1352 // always execute:
1353 () -> {
1354 // Unregister this remoteService once op is done
1355 synchronized (mCallbacksLock) {
1356 mCallbacks.remove(mPuuid.getUuid());
1357 }
1358 mDestroyOnceRunningOpsDone = true;
1359 },
1360 // execute if not throttled:
1361 (opId, service) -> service.onError(mPuuid, opId, status),
1362 // nothing to do if throttled
1363 null));
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -08001364 }
1365
1366 @Override
1367 public void onRecognitionPaused() {
1368 Slog.i(TAG, mPuuid + "->" + mServiceName + ": IGNORED onRecognitionPaused");
Jason Hsu1363f582019-04-16 15:35:55 +08001369
1370 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
1371 + "->" + mServiceName + ": IGNORED onRecognitionPaused"));
1372
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -08001373 }
1374
1375 @Override
1376 public void onRecognitionResumed() {
1377 Slog.i(TAG, mPuuid + "->" + mServiceName + ": IGNORED onRecognitionResumed");
Jason Hsu1363f582019-04-16 15:35:55 +08001378
1379 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
1380 + "->" + mServiceName + ": IGNORED onRecognitionResumed"));
1381
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -08001382 }
1383
1384 @Override
1385 public void onServiceConnected(ComponentName name, IBinder service) {
1386 if (DEBUG) Slog.v(TAG, mPuuid + ": onServiceConnected(" + service + ")");
1387
Jason Hsu1363f582019-04-16 15:35:55 +08001388 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
1389 + ": onServiceConnected(" + service + ")"));
1390
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -08001391 synchronized (mRemoteServiceLock) {
1392 mService = ISoundTriggerDetectionService.Stub.asInterface(service);
1393
1394 try {
1395 mService.setClient(mPuuid, mParams, mClient);
1396 } catch (Exception e) {
1397 Slog.e(TAG, mPuuid + ": Could not init " + mServiceName, e);
1398 return;
1399 }
1400
1401 while (!mPendingOps.isEmpty()) {
1402 runOrAddOperation(mPendingOps.remove(0));
1403 }
1404 }
1405 }
1406
1407 @Override
1408 public void onServiceDisconnected(ComponentName name) {
1409 if (DEBUG) Slog.v(TAG, mPuuid + ": onServiceDisconnected");
1410
Jason Hsu1363f582019-04-16 15:35:55 +08001411 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
1412 + ": onServiceDisconnected"));
1413
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -08001414 synchronized (mRemoteServiceLock) {
1415 mService = null;
1416 }
1417 }
1418
1419 @Override
1420 public void onBindingDied(ComponentName name) {
1421 if (DEBUG) Slog.v(TAG, mPuuid + ": onBindingDied");
1422
Jason Hsu1363f582019-04-16 15:35:55 +08001423 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
1424 + ": onBindingDied"));
1425
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -08001426 synchronized (mRemoteServiceLock) {
1427 destroy();
1428 }
1429 }
1430
1431 @Override
1432 public void onNullBinding(ComponentName name) {
1433 Slog.w(TAG, name + " for model " + mPuuid + " returned a null binding");
1434
Jason Hsu1363f582019-04-16 15:35:55 +08001435 sEventLogger.log(new SoundTriggerLogger.StringEvent(name + " for model "
1436 + mPuuid + " returned a null binding"));
1437
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -08001438 synchronized (mRemoteServiceLock) {
1439 disconnectLocked();
1440 }
1441 }
1442 }
1443
Arunesh Mishraa772e5f2016-01-25 10:33:11 -08001444 public final class LocalSoundTriggerService extends SoundTriggerInternal {
1445 private final Context mContext;
1446 private SoundTriggerHelper mSoundTriggerHelper;
1447
1448 LocalSoundTriggerService(Context context) {
1449 mContext = context;
1450 }
1451
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001452 synchronized void setSoundTriggerHelper(SoundTriggerHelper helper) {
1453 mSoundTriggerHelper = helper;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -08001454 }
1455
1456 @Override
1457 public int startRecognition(int keyphraseId, KeyphraseSoundModel soundModel,
1458 IRecognitionStatusCallback listener, RecognitionConfig recognitionConfig) {
Nicholas Amburf94db1c2019-12-08 19:04:06 -08001459 if (!isInitialized()) throw new UnsupportedOperationException();
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001460 return mSoundTriggerHelper.startKeyphraseRecognition(keyphraseId, soundModel, listener,
Arunesh Mishraa772e5f2016-01-25 10:33:11 -08001461 recognitionConfig);
1462 }
1463
1464 @Override
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001465 public synchronized int stopRecognition(int keyphraseId, IRecognitionStatusCallback listener) {
Nicholas Amburf94db1c2019-12-08 19:04:06 -08001466 if (!isInitialized()) throw new UnsupportedOperationException();
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001467 return mSoundTriggerHelper.stopKeyphraseRecognition(keyphraseId, listener);
Arunesh Mishraa772e5f2016-01-25 10:33:11 -08001468 }
1469
1470 @Override
Arunesh Mishra55a9b002016-02-01 14:06:37 -08001471 public ModuleProperties getModuleProperties() {
Nicholas Amburf94db1c2019-12-08 19:04:06 -08001472 if (!isInitialized()) throw new UnsupportedOperationException();
Arunesh Mishra55a9b002016-02-01 14:06:37 -08001473 return mSoundTriggerHelper.getModuleProperties();
1474 }
1475
1476 @Override
Nicholas Amburf94db1c2019-12-08 19:04:06 -08001477 public int setParameter(int keyphraseId, @ModelParams int modelParam, int value) {
1478 if (!isInitialized()) throw new UnsupportedOperationException();
1479 return mSoundTriggerHelper.setKeyphraseParameter(keyphraseId, modelParam, value);
1480 }
1481
1482 @Override
1483 public int getParameter(int keyphraseId, @ModelParams int modelParam) {
1484 if (!isInitialized()) throw new UnsupportedOperationException();
1485 return mSoundTriggerHelper.getKeyphraseParameter(keyphraseId, modelParam);
1486 }
1487
1488 @Override
1489 @Nullable
1490 public ModelParamRange queryParameter(int keyphraseId, @ModelParams int modelParam) {
1491 if (!isInitialized()) throw new UnsupportedOperationException();
1492 return mSoundTriggerHelper.queryKeyphraseParameter(keyphraseId, modelParam);
1493 }
1494
1495 @Override
Arunesh Mishra2d1de782016-02-21 18:10:28 -08001496 public int unloadKeyphraseModel(int keyphraseId) {
Nicholas Amburf94db1c2019-12-08 19:04:06 -08001497 if (!isInitialized()) throw new UnsupportedOperationException();
Arunesh Mishra2d1de782016-02-21 18:10:28 -08001498 return mSoundTriggerHelper.unloadKeyphraseSoundModel(keyphraseId);
1499 }
1500
1501 @Override
Arunesh Mishraa772e5f2016-01-25 10:33:11 -08001502 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001503 if (!isInitialized()) return;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -08001504 mSoundTriggerHelper.dump(fd, pw, args);
Jason Hsu1363f582019-04-16 15:35:55 +08001505 // log
1506 sEventLogger.dump(pw);
Benjamin Schwartz9e7a0152019-06-06 17:39:22 -07001507
Nicholas Amburd3ec82f2020-01-03 17:44:19 -08001508 // enrolled models
1509 mDbHelper.dump(pw);
1510
Benjamin Schwartz9e7a0152019-06-06 17:39:22 -07001511 // stats
1512 mSoundModelStatTracker.dump(pw);
Arunesh Mishraa772e5f2016-01-25 10:33:11 -08001513 }
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001514
1515 private synchronized boolean isInitialized() {
1516 if (mSoundTriggerHelper == null ) {
1517 Slog.e(TAG, "SoundTriggerHelper not initialized.");
Jason Hsu1363f582019-04-16 15:35:55 +08001518
1519 sEventLogger.log(new SoundTriggerLogger.StringEvent(
1520 "SoundTriggerHelper not initialized."));
1521
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001522 return false;
1523 }
1524 return true;
1525 }
Arunesh Mishraa772e5f2016-01-25 10:33:11 -08001526 }
1527
1528 private void enforceCallingPermission(String permission) {
1529 if (mContext.checkCallingOrSelfPermission(permission)
1530 != PackageManager.PERMISSION_GRANTED) {
1531 throw new SecurityException("Caller does not hold the permission " + permission);
1532 }
1533 }
Jason Hsu1363f582019-04-16 15:35:55 +08001534
1535 //=================================================================
1536 // For logging
1537
1538 private static final SoundTriggerLogger sEventLogger = new SoundTriggerLogger(200,
1539 "SoundTrigger activity");
1540
Arunesh Mishraa772e5f2016-01-25 10:33:11 -08001541}