blob: 9cf63695b12910313a35e5c3604547529b45f147 [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) {
210 if (PHASE_SYSTEM_SERVICES_READY == phase) {
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800211 initSoundTriggerHelper();
212 mLocalSoundTriggerService.setSoundTriggerHelper(mSoundTriggerHelper);
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800213 } else if (PHASE_THIRD_PARTY_APPS_CAN_START == phase) {
214 mDbHelper = new SoundTriggerDbHelper(mContext);
215 }
216 }
217
218 @Override
219 public void onStartUser(int userHandle) {
220 }
221
222 @Override
223 public void onSwitchUser(int userHandle) {
224 }
225
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800226 private synchronized void initSoundTriggerHelper() {
227 if (mSoundTriggerHelper == null) {
228 mSoundTriggerHelper = new SoundTriggerHelper(mContext);
229 }
230 }
231
232 private synchronized boolean isInitialized() {
233 if (mSoundTriggerHelper == null ) {
234 Slog.e(TAG, "SoundTriggerHelper not initialized.");
235 return false;
236 }
237 return true;
238 }
239
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800240 class SoundTriggerServiceStub extends ISoundTriggerService.Stub {
241 @Override
242 public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
243 throws RemoteException {
244 try {
245 return super.onTransact(code, data, reply, flags);
246 } catch (RuntimeException e) {
247 // The activity manager only throws security exceptions, so let's
248 // log all others.
249 if (!(e instanceof SecurityException)) {
250 Slog.wtf(TAG, "SoundTriggerService Crash", e);
251 }
252 throw e;
253 }
254 }
255
256 @Override
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800257 public int startRecognition(ParcelUuid parcelUuid, IRecognitionStatusCallback callback,
258 RecognitionConfig config) {
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800259 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
Arunesh Mishra2d1de782016-02-21 18:10:28 -0800260 if (!isInitialized()) return STATUS_ERROR;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800261 if (DEBUG) {
262 Slog.i(TAG, "startRecognition(): Uuid : " + parcelUuid);
263 }
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800264
Jason Hsu1363f582019-04-16 15:35:55 +0800265 sEventLogger.log(new SoundTriggerLogger.StringEvent("startRecognition(): Uuid : "
266 + parcelUuid));
267
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800268 GenericSoundModel model = getSoundModel(parcelUuid);
269 if (model == null) {
270 Slog.e(TAG, "Null model in database for id: " + parcelUuid);
Jason Hsu1363f582019-04-16 15:35:55 +0800271
272 sEventLogger.log(new SoundTriggerLogger.StringEvent(
273 "startRecognition(): Null model in database for id: " + parcelUuid));
274
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800275 return STATUS_ERROR;
276 }
277
Benjamin Schwartz9e7a0152019-06-06 17:39:22 -0700278 int ret = mSoundTriggerHelper.startGenericRecognition(parcelUuid.getUuid(), model,
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800279 callback, config);
Benjamin Schwartz9e7a0152019-06-06 17:39:22 -0700280 if (ret == STATUS_OK) {
281 mSoundModelStatTracker.onStart(parcelUuid.getUuid());
282 }
283 return ret;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800284 }
285
286 @Override
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800287 public int stopRecognition(ParcelUuid parcelUuid, IRecognitionStatusCallback callback) {
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800288 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
289 if (DEBUG) {
290 Slog.i(TAG, "stopRecognition(): Uuid : " + parcelUuid);
291 }
Jason Hsu1363f582019-04-16 15:35:55 +0800292
293 sEventLogger.log(new SoundTriggerLogger.StringEvent("stopRecognition(): Uuid : "
294 + parcelUuid));
295
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800296 if (!isInitialized()) return STATUS_ERROR;
Benjamin Schwartz9e7a0152019-06-06 17:39:22 -0700297
298 int ret = mSoundTriggerHelper.stopGenericRecognition(parcelUuid.getUuid(), callback);
299 if (ret == STATUS_OK) {
300 mSoundModelStatTracker.onStop(parcelUuid.getUuid());
301 }
302 return ret;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800303 }
304
305 @Override
Arunesh Mishrac722ec412016-01-27 13:29:12 -0800306 public SoundTrigger.GenericSoundModel getSoundModel(ParcelUuid soundModelId) {
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800307 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
308 if (DEBUG) {
309 Slog.i(TAG, "getSoundModel(): id = " + soundModelId);
310 }
Jason Hsu1363f582019-04-16 15:35:55 +0800311
312 sEventLogger.log(new SoundTriggerLogger.StringEvent("getSoundModel(): id = "
313 + soundModelId));
314
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800315 SoundTrigger.GenericSoundModel model = mDbHelper.getGenericSoundModel(
316 soundModelId.getUuid());
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800317 return model;
318 }
319
320 @Override
Arunesh Mishrac722ec412016-01-27 13:29:12 -0800321 public void updateSoundModel(SoundTrigger.GenericSoundModel soundModel) {
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800322 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
323 if (DEBUG) {
324 Slog.i(TAG, "updateSoundModel(): model = " + soundModel);
325 }
Jason Hsu1363f582019-04-16 15:35:55 +0800326
327 sEventLogger.log(new SoundTriggerLogger.StringEvent("updateSoundModel(): model = "
328 + soundModel));
329
Arunesh Mishrac722ec412016-01-27 13:29:12 -0800330 mDbHelper.updateGenericSoundModel(soundModel);
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800331 }
332
333 @Override
334 public void deleteSoundModel(ParcelUuid soundModelId) {
335 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
336 if (DEBUG) {
337 Slog.i(TAG, "deleteSoundModel(): id = " + soundModelId);
338 }
Jason Hsu1363f582019-04-16 15:35:55 +0800339
340 sEventLogger.log(new SoundTriggerLogger.StringEvent("deleteSoundModel(): id = "
341 + soundModelId));
342
Arunesh Mishra2d1de782016-02-21 18:10:28 -0800343 // Unload the model if it is loaded.
344 mSoundTriggerHelper.unloadGenericSoundModel(soundModelId.getUuid());
Arunesh Mishrac722ec412016-01-27 13:29:12 -0800345 mDbHelper.deleteGenericSoundModel(soundModelId.getUuid());
Benjamin Schwartz9e7a0152019-06-06 17:39:22 -0700346
347 // Stop recognition if it is started.
348 mSoundModelStatTracker.onStop(soundModelId.getUuid());
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800349 }
Chris Thorntonba08b792017-06-08 22:34:37 -0700350
351 @Override
352 public int loadGenericSoundModel(GenericSoundModel soundModel) {
353 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
354 if (!isInitialized()) return STATUS_ERROR;
Nicholas Ambur1e437672020-03-24 17:09:58 -0700355 if (soundModel == null || soundModel.getUuid() == null) {
Chris Thorntonba08b792017-06-08 22:34:37 -0700356 Slog.e(TAG, "Invalid sound model");
Jason Hsu1363f582019-04-16 15:35:55 +0800357
358 sEventLogger.log(new SoundTriggerLogger.StringEvent(
359 "loadGenericSoundModel(): Invalid sound model"));
360
Chris Thorntonba08b792017-06-08 22:34:37 -0700361 return STATUS_ERROR;
362 }
363 if (DEBUG) {
Nicholas Ambur1e437672020-03-24 17:09:58 -0700364 Slog.i(TAG, "loadGenericSoundModel(): id = " + soundModel.getUuid());
Chris Thorntonba08b792017-06-08 22:34:37 -0700365 }
Jason Hsu1363f582019-04-16 15:35:55 +0800366
367 sEventLogger.log(new SoundTriggerLogger.StringEvent("loadGenericSoundModel(): id = "
Nicholas Ambur1e437672020-03-24 17:09:58 -0700368 + soundModel.getUuid()));
Jason Hsu1363f582019-04-16 15:35:55 +0800369
Chris Thorntonba08b792017-06-08 22:34:37 -0700370 synchronized (mLock) {
Nicholas Ambur1e437672020-03-24 17:09:58 -0700371 SoundModel oldModel = mLoadedModels.get(soundModel.getUuid());
Chris Thorntonba08b792017-06-08 22:34:37 -0700372 // If the model we're loading is actually different than what we had loaded, we
373 // should unload that other model now. We don't care about return codes since we
374 // don't know if the other model is loaded.
375 if (oldModel != null && !oldModel.equals(soundModel)) {
Nicholas Ambur1e437672020-03-24 17:09:58 -0700376 mSoundTriggerHelper.unloadGenericSoundModel(soundModel.getUuid());
Chris Thorntonae5fb992017-12-07 18:26:31 -0800377 synchronized (mCallbacksLock) {
Nicholas Ambur1e437672020-03-24 17:09:58 -0700378 mCallbacks.remove(soundModel.getUuid());
Chris Thorntonae5fb992017-12-07 18:26:31 -0800379 }
Chris Thorntonba08b792017-06-08 22:34:37 -0700380 }
Nicholas Ambur1e437672020-03-24 17:09:58 -0700381 mLoadedModels.put(soundModel.getUuid(), soundModel);
Chris Thorntonba08b792017-06-08 22:34:37 -0700382 }
383 return STATUS_OK;
384 }
385
386 @Override
387 public int loadKeyphraseSoundModel(KeyphraseSoundModel soundModel) {
388 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
389 if (!isInitialized()) return STATUS_ERROR;
Nicholas Ambur1e437672020-03-24 17:09:58 -0700390 if (soundModel == null || soundModel.getUuid() == null) {
Chris Thorntonba08b792017-06-08 22:34:37 -0700391 Slog.e(TAG, "Invalid sound model");
Jason Hsu1363f582019-04-16 15:35:55 +0800392
393 sEventLogger.log(new SoundTriggerLogger.StringEvent(
394 "loadKeyphraseSoundModel(): Invalid sound model"));
395
Chris Thorntonba08b792017-06-08 22:34:37 -0700396 return STATUS_ERROR;
397 }
Nicholas Ambur1e437672020-03-24 17:09:58 -0700398 if (soundModel.getKeyphrases() == null || soundModel.getKeyphrases().length != 1) {
Chris Thorntonba08b792017-06-08 22:34:37 -0700399 Slog.e(TAG, "Only one keyphrase per model is currently supported.");
Jason Hsu1363f582019-04-16 15:35:55 +0800400
401 sEventLogger.log(new SoundTriggerLogger.StringEvent(
402 "loadKeyphraseSoundModel(): Only one keyphrase per model"
403 + " is currently supported."));
404
Chris Thorntonba08b792017-06-08 22:34:37 -0700405 return STATUS_ERROR;
406 }
407 if (DEBUG) {
Nicholas Ambur1e437672020-03-24 17:09:58 -0700408 Slog.i(TAG, "loadKeyphraseSoundModel(): id = " + soundModel.getUuid());
Chris Thorntonba08b792017-06-08 22:34:37 -0700409 }
Jason Hsu1363f582019-04-16 15:35:55 +0800410
411 sEventLogger.log(new SoundTriggerLogger.StringEvent("loadKeyphraseSoundModel(): id = "
Nicholas Ambur1e437672020-03-24 17:09:58 -0700412 + soundModel.getUuid()));
Jason Hsu1363f582019-04-16 15:35:55 +0800413
Chris Thorntonba08b792017-06-08 22:34:37 -0700414 synchronized (mLock) {
Nicholas Ambur1e437672020-03-24 17:09:58 -0700415 SoundModel oldModel = mLoadedModels.get(soundModel.getUuid());
Chris Thorntonba08b792017-06-08 22:34:37 -0700416 // If the model we're loading is actually different than what we had loaded, we
417 // should unload that other model now. We don't care about return codes since we
418 // don't know if the other model is loaded.
419 if (oldModel != null && !oldModel.equals(soundModel)) {
Nicholas Ambur1e437672020-03-24 17:09:58 -0700420 mSoundTriggerHelper.unloadKeyphraseSoundModel(
421 soundModel.getKeyphrases()[0].getId());
Chris Thorntonae5fb992017-12-07 18:26:31 -0800422 synchronized (mCallbacksLock) {
Nicholas Ambur1e437672020-03-24 17:09:58 -0700423 mCallbacks.remove(soundModel.getUuid());
Chris Thorntonae5fb992017-12-07 18:26:31 -0800424 }
Chris Thorntonba08b792017-06-08 22:34:37 -0700425 }
Nicholas Ambur1e437672020-03-24 17:09:58 -0700426 mLoadedModels.put(soundModel.getUuid(), soundModel);
Chris Thorntonba08b792017-06-08 22:34:37 -0700427 }
428 return STATUS_OK;
429 }
430
431 @Override
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800432 public int startRecognitionForService(ParcelUuid soundModelId, Bundle params,
433 ComponentName detectionService, SoundTrigger.RecognitionConfig config) {
Daulet Zhanguzine789eee2019-12-20 17:19:33 +0000434 Objects.requireNonNull(soundModelId);
435 Objects.requireNonNull(detectionService);
436 Objects.requireNonNull(config);
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800437
Chris Thorntonba08b792017-06-08 22:34:37 -0700438 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
Philip P. Moltmann19402f52018-03-27 14:38:03 -0700439
Chris Thorntonba08b792017-06-08 22:34:37 -0700440 if (!isInitialized()) return STATUS_ERROR;
441 if (DEBUG) {
442 Slog.i(TAG, "startRecognition(): id = " + soundModelId);
443 }
444
Jason Hsu1363f582019-04-16 15:35:55 +0800445 sEventLogger.log(new SoundTriggerLogger.StringEvent(
446 "startRecognitionForService(): id = " + soundModelId));
447
Philip P. Moltmann19402f52018-03-27 14:38:03 -0700448 IRecognitionStatusCallback callback =
449 new RemoteSoundTriggerDetectionService(soundModelId.getUuid(), params,
450 detectionService, Binder.getCallingUserHandle(), config);
451
Chris Thorntonba08b792017-06-08 22:34:37 -0700452 synchronized (mLock) {
453 SoundModel soundModel = mLoadedModels.get(soundModelId.getUuid());
454 if (soundModel == null) {
455 Slog.e(TAG, soundModelId + " is not loaded");
Jason Hsu1363f582019-04-16 15:35:55 +0800456
457 sEventLogger.log(new SoundTriggerLogger.StringEvent(
458 "startRecognitionForService():" + soundModelId + " is not loaded"));
459
Chris Thorntonba08b792017-06-08 22:34:37 -0700460 return STATUS_ERROR;
461 }
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800462 IRecognitionStatusCallback existingCallback = null;
Chris Thorntonae5fb992017-12-07 18:26:31 -0800463 synchronized (mCallbacksLock) {
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800464 existingCallback = mCallbacks.get(soundModelId.getUuid());
Chris Thorntonae5fb992017-12-07 18:26:31 -0800465 }
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800466 if (existingCallback != null) {
Chris Thorntonba08b792017-06-08 22:34:37 -0700467 Slog.e(TAG, soundModelId + " is already running");
Jason Hsu1363f582019-04-16 15:35:55 +0800468
469 sEventLogger.log(new SoundTriggerLogger.StringEvent(
470 "startRecognitionForService():"
471 + soundModelId + " is already running"));
472
Chris Thorntonba08b792017-06-08 22:34:37 -0700473 return STATUS_ERROR;
474 }
Chris Thorntonba08b792017-06-08 22:34:37 -0700475 int ret;
Nicholas Ambur1e437672020-03-24 17:09:58 -0700476 switch (soundModel.getType()) {
Chris Thorntonba08b792017-06-08 22:34:37 -0700477 case SoundModel.TYPE_GENERIC_SOUND:
Nicholas Ambur1e437672020-03-24 17:09:58 -0700478 ret = mSoundTriggerHelper.startGenericRecognition(soundModel.getUuid(),
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800479 (GenericSoundModel) soundModel, callback, config);
Chris Thorntonba08b792017-06-08 22:34:37 -0700480 break;
481 default:
482 Slog.e(TAG, "Unknown model type");
Jason Hsu1363f582019-04-16 15:35:55 +0800483
484 sEventLogger.log(new SoundTriggerLogger.StringEvent(
485 "startRecognitionForService(): Unknown model type"));
486
Chris Thorntonba08b792017-06-08 22:34:37 -0700487 return STATUS_ERROR;
488 }
489
490 if (ret != STATUS_OK) {
491 Slog.e(TAG, "Failed to start model: " + ret);
Jason Hsu1363f582019-04-16 15:35:55 +0800492
493 sEventLogger.log(new SoundTriggerLogger.StringEvent(
494 "startRecognitionForService(): Failed to start model:"));
495
Chris Thorntonba08b792017-06-08 22:34:37 -0700496 return ret;
497 }
Chris Thorntonae5fb992017-12-07 18:26:31 -0800498 synchronized (mCallbacksLock) {
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800499 mCallbacks.put(soundModelId.getUuid(), callback);
Chris Thorntonae5fb992017-12-07 18:26:31 -0800500 }
Benjamin Schwartz9e7a0152019-06-06 17:39:22 -0700501
502 mSoundModelStatTracker.onStart(soundModelId.getUuid());
Chris Thorntonba08b792017-06-08 22:34:37 -0700503 }
504 return STATUS_OK;
505 }
506
507 @Override
Philip P. Moltmann19402f52018-03-27 14:38:03 -0700508 public int stopRecognitionForService(ParcelUuid soundModelId) {
Chris Thorntonba08b792017-06-08 22:34:37 -0700509 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
510 if (!isInitialized()) return STATUS_ERROR;
511 if (DEBUG) {
512 Slog.i(TAG, "stopRecognition(): id = " + soundModelId);
513 }
514
Jason Hsu1363f582019-04-16 15:35:55 +0800515 sEventLogger.log(new SoundTriggerLogger.StringEvent(
516 "stopRecognitionForService(): id = " + soundModelId));
517
Chris Thorntonba08b792017-06-08 22:34:37 -0700518 synchronized (mLock) {
519 SoundModel soundModel = mLoadedModels.get(soundModelId.getUuid());
520 if (soundModel == null) {
521 Slog.e(TAG, soundModelId + " is not loaded");
Jason Hsu1363f582019-04-16 15:35:55 +0800522
523 sEventLogger.log(new SoundTriggerLogger.StringEvent(
524 "stopRecognitionForService(): " + soundModelId
525 + " is not loaded"));
526
Chris Thorntonba08b792017-06-08 22:34:37 -0700527 return STATUS_ERROR;
528 }
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800529 IRecognitionStatusCallback callback = null;
Chris Thorntonae5fb992017-12-07 18:26:31 -0800530 synchronized (mCallbacksLock) {
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800531 callback = mCallbacks.get(soundModelId.getUuid());
Chris Thorntonae5fb992017-12-07 18:26:31 -0800532 }
Chris Thorntonba08b792017-06-08 22:34:37 -0700533 if (callback == null) {
534 Slog.e(TAG, soundModelId + " is not running");
Jason Hsu1363f582019-04-16 15:35:55 +0800535
536 sEventLogger.log(new SoundTriggerLogger.StringEvent(
537 "stopRecognitionForService(): " + soundModelId
538 + " is not running"));
539
Chris Thorntonba08b792017-06-08 22:34:37 -0700540 return STATUS_ERROR;
541 }
542 int ret;
Nicholas Ambur1e437672020-03-24 17:09:58 -0700543 switch (soundModel.getType()) {
Chris Thorntonba08b792017-06-08 22:34:37 -0700544 case SoundModel.TYPE_GENERIC_SOUND:
Nicholas Ambur1e437672020-03-24 17:09:58 -0700545 ret = mSoundTriggerHelper.stopGenericRecognition(
546 soundModel.getUuid(), callback);
Chris Thorntonba08b792017-06-08 22:34:37 -0700547 break;
548 default:
549 Slog.e(TAG, "Unknown model type");
Jason Hsu1363f582019-04-16 15:35:55 +0800550
551 sEventLogger.log(new SoundTriggerLogger.StringEvent(
552 "stopRecognitionForService(): Unknown model type"));
553
Chris Thorntonba08b792017-06-08 22:34:37 -0700554 return STATUS_ERROR;
555 }
556
557 if (ret != STATUS_OK) {
558 Slog.e(TAG, "Failed to stop model: " + ret);
Jason Hsu1363f582019-04-16 15:35:55 +0800559
560 sEventLogger.log(new SoundTriggerLogger.StringEvent(
561 "stopRecognitionForService(): Failed to stop model: " + ret));
562
Chris Thorntonba08b792017-06-08 22:34:37 -0700563 return ret;
564 }
Chris Thorntonae5fb992017-12-07 18:26:31 -0800565 synchronized (mCallbacksLock) {
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800566 mCallbacks.remove(soundModelId.getUuid());
Chris Thorntonae5fb992017-12-07 18:26:31 -0800567 }
Benjamin Schwartz9e7a0152019-06-06 17:39:22 -0700568
569 mSoundModelStatTracker.onStop(soundModelId.getUuid());
Chris Thorntonba08b792017-06-08 22:34:37 -0700570 }
571 return STATUS_OK;
572 }
573
574 @Override
575 public int unloadSoundModel(ParcelUuid soundModelId) {
576 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
577 if (!isInitialized()) return STATUS_ERROR;
578 if (DEBUG) {
579 Slog.i(TAG, "unloadSoundModel(): id = " + soundModelId);
580 }
581
Jason Hsu1363f582019-04-16 15:35:55 +0800582 sEventLogger.log(new SoundTriggerLogger.StringEvent("unloadSoundModel(): id = "
583 + soundModelId));
584
Chris Thorntonba08b792017-06-08 22:34:37 -0700585 synchronized (mLock) {
586 SoundModel soundModel = mLoadedModels.get(soundModelId.getUuid());
587 if (soundModel == null) {
588 Slog.e(TAG, soundModelId + " is not loaded");
Jason Hsu1363f582019-04-16 15:35:55 +0800589
590 sEventLogger.log(new SoundTriggerLogger.StringEvent(
591 "unloadSoundModel(): " + soundModelId + " is not loaded"));
592
Chris Thorntonba08b792017-06-08 22:34:37 -0700593 return STATUS_ERROR;
594 }
595 int ret;
Nicholas Ambur1e437672020-03-24 17:09:58 -0700596 switch (soundModel.getType()) {
Chris Thorntonba08b792017-06-08 22:34:37 -0700597 case SoundModel.TYPE_KEYPHRASE:
598 ret = mSoundTriggerHelper.unloadKeyphraseSoundModel(
Nicholas Ambur1e437672020-03-24 17:09:58 -0700599 ((KeyphraseSoundModel) soundModel).getKeyphrases()[0].getId());
Chris Thorntonba08b792017-06-08 22:34:37 -0700600 break;
601 case SoundModel.TYPE_GENERIC_SOUND:
Nicholas Ambur1e437672020-03-24 17:09:58 -0700602 ret = mSoundTriggerHelper.unloadGenericSoundModel(soundModel.getUuid());
Chris Thorntonba08b792017-06-08 22:34:37 -0700603 break;
604 default:
605 Slog.e(TAG, "Unknown model type");
Jason Hsu1363f582019-04-16 15:35:55 +0800606
607 sEventLogger.log(new SoundTriggerLogger.StringEvent(
608 "unloadSoundModel(): Unknown model type"));
609
Chris Thorntonba08b792017-06-08 22:34:37 -0700610 return STATUS_ERROR;
611 }
612 if (ret != STATUS_OK) {
613 Slog.e(TAG, "Failed to unload model");
Jason Hsu1363f582019-04-16 15:35:55 +0800614
615 sEventLogger.log(new SoundTriggerLogger.StringEvent(
616 "unloadSoundModel(): Failed to unload model"));
617
Chris Thorntonba08b792017-06-08 22:34:37 -0700618 return ret;
619 }
620 mLoadedModels.remove(soundModelId.getUuid());
621 return STATUS_OK;
622 }
623 }
624
625 @Override
626 public boolean isRecognitionActive(ParcelUuid parcelUuid) {
627 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
628 if (!isInitialized()) return false;
Chris Thorntonae5fb992017-12-07 18:26:31 -0800629 synchronized (mCallbacksLock) {
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800630 IRecognitionStatusCallback callback = mCallbacks.get(parcelUuid.getUuid());
Chris Thorntonba08b792017-06-08 22:34:37 -0700631 if (callback == null) {
632 return false;
633 }
Chris Thorntonba08b792017-06-08 22:34:37 -0700634 }
Chris Thorntonae5fb992017-12-07 18:26:31 -0800635 return mSoundTriggerHelper.isRecognitionRequested(parcelUuid.getUuid());
Chris Thorntonba08b792017-06-08 22:34:37 -0700636 }
Michael Dooley291751e2018-10-16 19:53:29 +0000637
638 @Override
mike dooleyb2ab04a2018-11-07 15:48:54 +0100639 public int getModelState(ParcelUuid soundModelId) {
Michael Dooley291751e2018-10-16 19:53:29 +0000640 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
mike dooleyb2ab04a2018-11-07 15:48:54 +0100641 int ret = STATUS_ERROR;
642 if (!isInitialized()) return ret;
Michael Dooley291751e2018-10-16 19:53:29 +0000643 if (DEBUG) {
644 Slog.i(TAG, "getModelState(): id = " + soundModelId);
645 }
646
Jason Hsu1363f582019-04-16 15:35:55 +0800647 sEventLogger.log(new SoundTriggerLogger.StringEvent("getModelState(): id = "
648 + soundModelId));
649
Michael Dooley291751e2018-10-16 19:53:29 +0000650 synchronized (mLock) {
651 SoundModel soundModel = mLoadedModels.get(soundModelId.getUuid());
652 if (soundModel == null) {
653 Slog.e(TAG, soundModelId + " is not loaded");
Jason Hsu1363f582019-04-16 15:35:55 +0800654
655 sEventLogger.log(new SoundTriggerLogger.StringEvent("getModelState(): "
656 + soundModelId + " is not loaded"));
657
mike dooleyb2ab04a2018-11-07 15:48:54 +0100658 return ret;
Michael Dooley291751e2018-10-16 19:53:29 +0000659 }
Nicholas Ambur1e437672020-03-24 17:09:58 -0700660 switch (soundModel.getType()) {
Michael Dooley291751e2018-10-16 19:53:29 +0000661 case SoundModel.TYPE_GENERIC_SOUND:
Nicholas Ambur1e437672020-03-24 17:09:58 -0700662 ret = mSoundTriggerHelper.getGenericModelState(soundModel.getUuid());
Michael Dooley291751e2018-10-16 19:53:29 +0000663 break;
664 default:
mike dooleybaa22c72019-05-15 10:05:25 +0200665 // SoundModel.TYPE_KEYPHRASE is not supported to increase privacy.
Nicholas Ambur1e437672020-03-24 17:09:58 -0700666 Slog.e(TAG, "Unsupported model type, " + soundModel.getType());
Jason Hsu1363f582019-04-16 15:35:55 +0800667 sEventLogger.log(new SoundTriggerLogger.StringEvent(
mike dooleybaa22c72019-05-15 10:05:25 +0200668 "getModelState(): Unsupported model type, "
Nicholas Ambur1e437672020-03-24 17:09:58 -0700669 + soundModel.getType()));
Michael Dooley291751e2018-10-16 19:53:29 +0000670 break;
671 }
Michael Dooley291751e2018-10-16 19:53:29 +0000672
673 return ret;
674 }
675 }
Nicholas Ambur1aa4b4b2019-08-22 12:13:29 -0700676
677 @Override
678 @Nullable
679 public ModuleProperties getModuleProperties() {
680 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
681 if (!isInitialized()) return null;
682 if (DEBUG) {
683 Slog.i(TAG, "getModuleProperties()");
684 }
685
686 synchronized (mLock) {
687 ModuleProperties properties = mSoundTriggerHelper.getModuleProperties();
688 sEventLogger.log(new SoundTriggerLogger.StringEvent(
689 "getModuleProperties(): " + properties.toString()));
690 return properties;
691 }
692 }
Nicholas Ambura0be6be2019-10-01 10:11:39 -0700693
694 @Override
695 public int setParameter(ParcelUuid soundModelId,
696 @ModelParams int modelParam, int value) {
697 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
698 if (!isInitialized()) return STATUS_NO_INIT;
699 if (DEBUG) {
700 Slog.d(TAG, "setParameter(): id=" + soundModelId
701 + ", param=" + modelParam
702 + ", value=" + value);
703 }
704
705 sEventLogger.log(new SoundTriggerLogger.StringEvent(
706 "setParameter(): id=" + soundModelId
707 + ", param=" + modelParam
708 + ", value=" + value));
709
710 synchronized (mLock) {
711 SoundModel soundModel = mLoadedModels.get(soundModelId.getUuid());
712 if (soundModel == null) {
713 Slog.e(TAG, soundModelId + " is not loaded. Loaded models: "
714 + mLoadedModels.toString());
715
716 sEventLogger.log(new SoundTriggerLogger.StringEvent("setParameter(): "
717 + soundModelId + " is not loaded"));
718
719 return STATUS_BAD_VALUE;
720 }
721
Nicholas Ambur1e437672020-03-24 17:09:58 -0700722 return mSoundTriggerHelper.setParameter(soundModel.getUuid(), modelParam, value);
Nicholas Ambura0be6be2019-10-01 10:11:39 -0700723 }
724 }
725
726 @Override
727 public int getParameter(@NonNull ParcelUuid soundModelId,
728 @ModelParams int modelParam)
729 throws UnsupportedOperationException, IllegalArgumentException {
730 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
731 if (!isInitialized()) {
732 throw new UnsupportedOperationException("SoundTriggerHelper not initialized");
733 }
734 if (DEBUG) {
735 Slog.d(TAG, "getParameter(): id=" + soundModelId
736 + ", param=" + modelParam);
737 }
738
739 sEventLogger.log(new SoundTriggerLogger.StringEvent(
740 "getParameter(): id=" + soundModelId
741 + ", param=" + modelParam));
742
743 synchronized (mLock) {
744 SoundModel soundModel = mLoadedModels.get(soundModelId.getUuid());
745 if (soundModel == null) {
746 Slog.e(TAG, soundModelId + " is not loaded");
747
748 sEventLogger.log(new SoundTriggerLogger.StringEvent("getParameter(): "
749 + soundModelId + " is not loaded"));
750
751 throw new IllegalArgumentException("sound model is not loaded");
752 }
753
Nicholas Ambur1e437672020-03-24 17:09:58 -0700754 return mSoundTriggerHelper.getParameter(soundModel.getUuid(), modelParam);
Nicholas Ambura0be6be2019-10-01 10:11:39 -0700755 }
756 }
757
758 @Override
759 @Nullable
760 public ModelParamRange queryParameter(@NonNull ParcelUuid soundModelId,
761 @ModelParams int modelParam) {
762 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
763 if (!isInitialized()) return null;
764 if (DEBUG) {
765 Slog.d(TAG, "queryParameter(): id=" + soundModelId
766 + ", param=" + modelParam);
767 }
768
769 sEventLogger.log(new SoundTriggerLogger.StringEvent(
770 "queryParameter(): id=" + soundModelId
771 + ", param=" + modelParam));
772
773 synchronized (mLock) {
774 SoundModel soundModel = mLoadedModels.get(soundModelId.getUuid());
775 if (soundModel == null) {
776 Slog.e(TAG, soundModelId + " is not loaded");
777
778 sEventLogger.log(new SoundTriggerLogger.StringEvent(
779 "queryParameter(): "
780 + soundModelId + " is not loaded"));
781
782 return null;
783 }
784
Nicholas Ambur1e437672020-03-24 17:09:58 -0700785 return mSoundTriggerHelper.queryParameter(soundModel.getUuid(), modelParam);
Nicholas Ambura0be6be2019-10-01 10:11:39 -0700786 }
787 }
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800788 }
789
Philip P. Moltmann7e25b3d2018-03-09 20:22:58 -0800790 /**
791 * Counts the number of operations added in the last 24 hours.
792 */
793 private static class NumOps {
794 private final Object mLock = new Object();
795
796 @GuardedBy("mLock")
797 private int[] mNumOps = new int[24];
798 @GuardedBy("mLock")
799 private long mLastOpsHourSinceBoot;
800
801 /**
802 * Clear buckets of new hours that have elapsed since last operation.
803 *
804 * <p>I.e. when the last operation was triggered at 1:40 and the current operation was
805 * triggered at 4:03, the buckets "2, 3, and 4" are cleared.
806 *
807 * @param currentTime Current elapsed time since boot in ns
808 */
809 void clearOldOps(long currentTime) {
810 synchronized (mLock) {
811 long numHoursSinceBoot = TimeUnit.HOURS.convert(currentTime, TimeUnit.NANOSECONDS);
812
813 // Clear buckets of new hours that have elapsed since last operation
814 // I.e. when the last operation was triggered at 1:40 and the current
815 // operation was triggered at 4:03, the bucket "2, 3, and 4" is cleared
816 if (mLastOpsHourSinceBoot != 0) {
817 for (long hour = mLastOpsHourSinceBoot + 1; hour <= numHoursSinceBoot; hour++) {
818 mNumOps[(int) (hour % 24)] = 0;
819 }
820 }
821 }
822 }
823
824 /**
825 * Add a new operation.
826 *
827 * @param currentTime Current elapsed time since boot in ns
828 */
829 void addOp(long currentTime) {
830 synchronized (mLock) {
831 long numHoursSinceBoot = TimeUnit.HOURS.convert(currentTime, TimeUnit.NANOSECONDS);
832
833 mNumOps[(int) (numHoursSinceBoot % 24)]++;
834 mLastOpsHourSinceBoot = numHoursSinceBoot;
835 }
836 }
837
838 /**
839 * Get the total operations added in the last 24 hours.
840 *
841 * @return The total number of operations added in the last 24 hours
842 */
843 int getOpsAdded() {
844 synchronized (mLock) {
845 int totalOperationsInLastDay = 0;
846 for (int i = 0; i < 24; i++) {
847 totalOperationsInLastDay += mNumOps[i];
848 }
849
850 return totalOperationsInLastDay;
851 }
852 }
853 }
854
Philip P. Moltmanna5b44032018-05-04 13:59:45 -0700855 /**
856 * A single operation run in a {@link RemoteSoundTriggerDetectionService}.
857 *
858 * <p>Once the remote service is connected either setup + execute or setup + stop is executed.
859 */
860 private static class Operation {
861 private interface ExecuteOp {
862 void run(int opId, ISoundTriggerDetectionService service) throws RemoteException;
863 }
864
865 private final @Nullable Runnable mSetupOp;
866 private final @NonNull ExecuteOp mExecuteOp;
867 private final @Nullable Runnable mDropOp;
868
869 private Operation(@Nullable Runnable setupOp, @NonNull ExecuteOp executeOp,
870 @Nullable Runnable cancelOp) {
871 mSetupOp = setupOp;
872 mExecuteOp = executeOp;
873 mDropOp = cancelOp;
874 }
875
876 private void setup() {
877 if (mSetupOp != null) {
878 mSetupOp.run();
879 }
880 }
881
882 void run(int opId, @NonNull ISoundTriggerDetectionService service) throws RemoteException {
883 setup();
884 mExecuteOp.run(opId, service);
885 }
886
887 void drop() {
888 setup();
889
890 if (mDropOp != null) {
891 mDropOp.run();
892 }
893 }
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800894 }
895
896 /**
897 * Local end for a {@link SoundTriggerDetectionService}. Operations are queued up and executed
898 * when the service connects.
899 *
900 * <p>If operations take too long they are forcefully aborted.
901 *
902 * <p>This also limits the amount of operations in 24 hours.
903 */
904 private class RemoteSoundTriggerDetectionService
905 extends IRecognitionStatusCallback.Stub implements ServiceConnection {
906 private static final int MSG_STOP_ALL_PENDING_OPERATIONS = 1;
907
908 private final Object mRemoteServiceLock = new Object();
909
910 /** UUID of the model the service is started for */
911 private final @NonNull ParcelUuid mPuuid;
912 /** Params passed into the start method for the service */
913 private final @Nullable Bundle mParams;
914 /** Component name passed when starting the service */
915 private final @NonNull ComponentName mServiceName;
916 /** User that started the service */
917 private final @NonNull UserHandle mUser;
918 /** Configuration of the recognition the service is handling */
919 private final @NonNull RecognitionConfig mRecognitionConfig;
920 /** Wake lock keeping the remote service alive */
921 private final @NonNull PowerManager.WakeLock mRemoteServiceWakeLock;
922
923 private final @NonNull Handler mHandler;
924
925 /** Callbacks that are called by the service */
926 private final @NonNull ISoundTriggerDetectionServiceClient mClient;
927
928 /** Operations that are pending because the service is not yet connected */
929 @GuardedBy("mRemoteServiceLock")
930 private final ArrayList<Operation> mPendingOps = new ArrayList<>();
931 /** Operations that have been send to the service but have no yet finished */
932 @GuardedBy("mRemoteServiceLock")
933 private final ArraySet<Integer> mRunningOpIds = new ArraySet<>();
Philip P. Moltmann7e25b3d2018-03-09 20:22:58 -0800934 /** The number of operations executed in each of the last 24 hours */
935 private final NumOps mNumOps;
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800936
937 /** The service binder if connected */
938 @GuardedBy("mRemoteServiceLock")
939 private @Nullable ISoundTriggerDetectionService mService;
940 /** Whether the service has been bound */
941 @GuardedBy("mRemoteServiceLock")
942 private boolean mIsBound;
943 /** Whether the service has been destroyed */
944 @GuardedBy("mRemoteServiceLock")
945 private boolean mIsDestroyed;
946 /**
947 * Set once a final op is scheduled. No further ops can be added and the service is
948 * destroyed once the op finishes.
949 */
950 @GuardedBy("mRemoteServiceLock")
951 private boolean mDestroyOnceRunningOpsDone;
952
953 /** Total number of operations performed by this service */
954 @GuardedBy("mRemoteServiceLock")
955 private int mNumTotalOpsPerformed;
956
957 /**
958 * Create a new remote sound trigger detection service. This only binds to the service when
959 * operations are in flight. Each operation has a certain time it can run. Once no
960 * operations are allowed to run anymore, {@link #stopAllPendingOperations() all operations
961 * are aborted and stopped} and the service is disconnected.
962 *
963 * @param modelUuid The UUID of the model the recognition is for
964 * @param params The params passed to each method of the service
965 * @param serviceName The component name of the service
966 * @param user The user of the service
967 * @param config The configuration of the recognition
968 */
969 public RemoteSoundTriggerDetectionService(@NonNull UUID modelUuid,
970 @Nullable Bundle params, @NonNull ComponentName serviceName, @NonNull UserHandle user,
971 @NonNull RecognitionConfig config) {
972 mPuuid = new ParcelUuid(modelUuid);
973 mParams = params;
974 mServiceName = serviceName;
975 mUser = user;
976 mRecognitionConfig = config;
977 mHandler = new Handler(Looper.getMainLooper());
978
979 PowerManager pm = ((PowerManager) mContext.getSystemService(Context.POWER_SERVICE));
980 mRemoteServiceWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
981 "RemoteSoundTriggerDetectionService " + mServiceName.getPackageName() + ":"
982 + mServiceName.getClassName());
983
Philip P. Moltmann7e25b3d2018-03-09 20:22:58 -0800984 synchronized (mLock) {
985 NumOps numOps = mNumOpsPerPackage.get(mServiceName.getPackageName());
986 if (numOps == null) {
987 numOps = new NumOps();
988 mNumOpsPerPackage.put(mServiceName.getPackageName(), numOps);
989 }
990 mNumOps = numOps;
991 }
992
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800993 mClient = new ISoundTriggerDetectionServiceClient.Stub() {
994 @Override
995 public void onOpFinished(int opId) {
996 long token = Binder.clearCallingIdentity();
997 try {
998 synchronized (mRemoteServiceLock) {
999 mRunningOpIds.remove(opId);
1000
1001 if (mRunningOpIds.isEmpty() && mPendingOps.isEmpty()) {
1002 if (mDestroyOnceRunningOpsDone) {
1003 destroy();
1004 } else {
1005 disconnectLocked();
1006 }
1007 }
1008 }
1009 } finally {
1010 Binder.restoreCallingIdentity(token);
1011 }
1012 }
1013 };
1014 }
1015
1016 @Override
1017 public boolean pingBinder() {
1018 return !(mIsDestroyed || mDestroyOnceRunningOpsDone);
1019 }
1020
1021 /**
1022 * Disconnect from the service, but allow to re-connect when new operations are triggered.
1023 */
Andreas Gampe8ce7ed92018-09-05 16:53:00 -07001024 @GuardedBy("mRemoteServiceLock")
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -08001025 private void disconnectLocked() {
1026 if (mService != null) {
1027 try {
1028 mService.removeClient(mPuuid);
1029 } catch (Exception e) {
1030 Slog.e(TAG, mPuuid + ": Cannot remove client", e);
Jason Hsu1363f582019-04-16 15:35:55 +08001031
1032 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
1033 + ": Cannot remove client"));
1034
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -08001035 }
1036
1037 mService = null;
1038 }
1039
1040 if (mIsBound) {
1041 mContext.unbindService(RemoteSoundTriggerDetectionService.this);
1042 mIsBound = false;
1043
1044 synchronized (mCallbacksLock) {
1045 mRemoteServiceWakeLock.release();
1046 }
1047 }
1048 }
1049
1050 /**
1051 * Disconnect, do not allow to reconnect to the service. All further operations will be
1052 * dropped.
1053 */
1054 private void destroy() {
1055 if (DEBUG) Slog.v(TAG, mPuuid + ": destroy");
1056
Jason Hsu1363f582019-04-16 15:35:55 +08001057 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid + ": destroy"));
1058
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -08001059 synchronized (mRemoteServiceLock) {
1060 disconnectLocked();
1061
1062 mIsDestroyed = true;
1063 }
1064
1065 // The callback is removed before the flag is set
1066 if (!mDestroyOnceRunningOpsDone) {
1067 synchronized (mCallbacksLock) {
1068 mCallbacks.remove(mPuuid.getUuid());
1069 }
1070 }
1071 }
1072
1073 /**
1074 * Stop all pending operations and then disconnect for the service.
1075 */
1076 private void stopAllPendingOperations() {
1077 synchronized (mRemoteServiceLock) {
1078 if (mIsDestroyed) {
1079 return;
1080 }
1081
1082 if (mService != null) {
1083 int numOps = mRunningOpIds.size();
1084 for (int i = 0; i < numOps; i++) {
1085 try {
1086 mService.onStopOperation(mPuuid, mRunningOpIds.valueAt(i));
1087 } catch (Exception e) {
1088 Slog.e(TAG, mPuuid + ": Could not stop operation "
1089 + mRunningOpIds.valueAt(i), e);
Jason Hsu1363f582019-04-16 15:35:55 +08001090
1091 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
1092 + ": Could not stop operation " + mRunningOpIds.valueAt(i)));
1093
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -08001094 }
1095 }
1096
1097 mRunningOpIds.clear();
1098 }
1099
1100 disconnectLocked();
1101 }
1102 }
1103
1104 /**
1105 * Verify that the service has the expected properties and then bind to the service
1106 */
1107 private void bind() {
1108 long token = Binder.clearCallingIdentity();
1109 try {
1110 Intent i = new Intent();
1111 i.setComponent(mServiceName);
1112
1113 ResolveInfo ri = mContext.getPackageManager().resolveServiceAsUser(i,
1114 GET_SERVICES | GET_META_DATA | MATCH_DEBUG_TRIAGED_MISSING,
1115 mUser.getIdentifier());
1116
1117 if (ri == null) {
1118 Slog.w(TAG, mPuuid + ": " + mServiceName + " not found");
Jason Hsu1363f582019-04-16 15:35:55 +08001119
1120 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
1121 + ": " + mServiceName + " not found"));
1122
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -08001123 return;
1124 }
1125
1126 if (!BIND_SOUND_TRIGGER_DETECTION_SERVICE
1127 .equals(ri.serviceInfo.permission)) {
1128 Slog.w(TAG, mPuuid + ": " + mServiceName + " does not require "
1129 + BIND_SOUND_TRIGGER_DETECTION_SERVICE);
Jason Hsu1363f582019-04-16 15:35:55 +08001130
1131 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
1132 + ": " + mServiceName + " does not require "
1133 + BIND_SOUND_TRIGGER_DETECTION_SERVICE));
1134
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -08001135 return;
1136 }
1137
1138 mIsBound = mContext.bindServiceAsUser(i, this,
Nick Moukhine67b5e382020-03-16 17:51:37 +01001139 BIND_AUTO_CREATE | BIND_FOREGROUND_SERVICE | BIND_INCLUDE_CAPABILITIES,
1140 mUser);
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -08001141
1142 if (mIsBound) {
1143 mRemoteServiceWakeLock.acquire();
1144 } else {
1145 Slog.w(TAG, mPuuid + ": Could not bind to " + mServiceName);
Jason Hsu1363f582019-04-16 15:35:55 +08001146
1147 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
1148 + ": Could not bind to " + mServiceName));
1149
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -08001150 }
1151 } finally {
1152 Binder.restoreCallingIdentity(token);
1153 }
1154 }
1155
1156 /**
1157 * Run an operation (i.e. send it do the service). If the service is not connected, this
1158 * binds the service and then runs the operation once connected.
1159 *
1160 * @param op The operation to run
1161 */
1162 private void runOrAddOperation(Operation op) {
1163 synchronized (mRemoteServiceLock) {
1164 if (mIsDestroyed || mDestroyOnceRunningOpsDone) {
Philip P. Moltmanna5b44032018-05-04 13:59:45 -07001165 Slog.w(TAG, mPuuid + ": Dropped operation as already destroyed or marked for "
1166 + "destruction");
1167
Jason Hsu1363f582019-04-16 15:35:55 +08001168 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
1169 + ":Dropped operation as already destroyed or marked for destruction"));
1170
Philip P. Moltmanna5b44032018-05-04 13:59:45 -07001171 op.drop();
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -08001172 return;
1173 }
1174
1175 if (mService == null) {
1176 mPendingOps.add(op);
1177
1178 if (!mIsBound) {
1179 bind();
1180 }
1181 } else {
Philip P. Moltmann7e25b3d2018-03-09 20:22:58 -08001182 long currentTime = System.nanoTime();
1183 mNumOps.clearOldOps(currentTime);
1184
1185 // Drop operation if too many were executed in the last 24 hours.
1186 int opsAllowed = Settings.Global.getInt(mContext.getContentResolver(),
1187 MAX_SOUND_TRIGGER_DETECTION_SERVICE_OPS_PER_DAY,
1188 Integer.MAX_VALUE);
1189
Philip P. Moltmann3d1683b2018-05-07 15:53:51 -07001190 // As we currently cannot dropping an op safely, disable throttling
Philip P. Moltmann7e25b3d2018-03-09 20:22:58 -08001191 int opsAdded = mNumOps.getOpsAdded();
Philip P. Moltmann3d1683b2018-05-07 15:53:51 -07001192 if (false && mNumOps.getOpsAdded() >= opsAllowed) {
Philip P. Moltmanna5b44032018-05-04 13:59:45 -07001193 try {
1194 if (DEBUG || opsAllowed + 10 > opsAdded) {
1195 Slog.w(TAG, mPuuid + ": Dropped operation as too many operations "
1196 + "were run in last 24 hours");
Jason Hsu1363f582019-04-16 15:35:55 +08001197
1198 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
1199 + ": Dropped operation as too many operations "
1200 + "were run in last 24 hours"));
1201
Philip P. Moltmanna5b44032018-05-04 13:59:45 -07001202 }
1203
1204 op.drop();
1205 } catch (Exception e) {
1206 Slog.e(TAG, mPuuid + ": Could not drop operation", e);
Jason Hsu1363f582019-04-16 15:35:55 +08001207
1208 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
1209 + ": Could not drop operation"));
1210
Philip P. Moltmann7e25b3d2018-03-09 20:22:58 -08001211 }
Philip P. Moltmann09dd8c42018-04-11 09:23:19 -07001212 } else {
1213 mNumOps.addOp(currentTime);
Philip P. Moltmann7e25b3d2018-03-09 20:22:58 -08001214
Philip P. Moltmann09dd8c42018-04-11 09:23:19 -07001215 // Find a free opID
1216 int opId = mNumTotalOpsPerformed;
1217 do {
1218 mNumTotalOpsPerformed++;
1219 } while (mRunningOpIds.contains(opId));
Philip P. Moltmann7e25b3d2018-03-09 20:22:58 -08001220
Philip P. Moltmann09dd8c42018-04-11 09:23:19 -07001221 // Run OP
1222 try {
1223 if (DEBUG) Slog.v(TAG, mPuuid + ": runOp " + opId);
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -08001224
Jason Hsu1363f582019-04-16 15:35:55 +08001225 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
1226 + ": runOp " + opId));
1227
Philip P. Moltmann09dd8c42018-04-11 09:23:19 -07001228 op.run(opId, mService);
1229 mRunningOpIds.add(opId);
1230 } catch (Exception e) {
1231 Slog.e(TAG, mPuuid + ": Could not run operation " + opId, e);
Jason Hsu1363f582019-04-16 15:35:55 +08001232
1233 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
1234 + ": Could not run operation " + opId));
1235
Philip P. Moltmann09dd8c42018-04-11 09:23:19 -07001236 }
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -08001237 }
1238
1239 // Unbind from service if no operations are left (i.e. if the operation failed)
1240 if (mPendingOps.isEmpty() && mRunningOpIds.isEmpty()) {
1241 if (mDestroyOnceRunningOpsDone) {
1242 destroy();
1243 } else {
1244 disconnectLocked();
1245 }
1246 } else {
1247 mHandler.removeMessages(MSG_STOP_ALL_PENDING_OPERATIONS);
1248 mHandler.sendMessageDelayed(obtainMessage(
1249 RemoteSoundTriggerDetectionService::stopAllPendingOperations, this)
1250 .setWhat(MSG_STOP_ALL_PENDING_OPERATIONS),
1251 Settings.Global.getLong(mContext.getContentResolver(),
1252 SOUND_TRIGGER_DETECTION_SERVICE_OP_TIMEOUT,
1253 Long.MAX_VALUE));
1254 }
1255 }
1256 }
1257 }
1258
1259 @Override
1260 public void onKeyphraseDetected(SoundTrigger.KeyphraseRecognitionEvent event) {
1261 Slog.w(TAG, mPuuid + "->" + mServiceName + ": IGNORED onKeyphraseDetected(" + event
1262 + ")");
Jason Hsu1363f582019-04-16 15:35:55 +08001263
1264 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid + "->" + mServiceName
1265 + ": IGNORED onKeyphraseDetected(" + event + ")"));
1266
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -08001267 }
1268
Philip P. Moltmanna5b44032018-05-04 13:59:45 -07001269 /**
1270 * Create an AudioRecord enough for starting and releasing the data buffered for the event.
1271 *
1272 * @param event The event that was received
1273 * @return The initialized AudioRecord
1274 */
1275 private @NonNull AudioRecord createAudioRecordForEvent(
1276 @NonNull SoundTrigger.GenericRecognitionEvent event) {
1277 AudioAttributes.Builder attributesBuilder = new AudioAttributes.Builder();
1278 attributesBuilder.setInternalCapturePreset(MediaRecorder.AudioSource.HOTWORD);
1279 AudioAttributes attributes = attributesBuilder.build();
1280
1281 // Use same AudioFormat processing as in RecognitionEvent.fromParcel
1282 AudioFormat originalFormat = event.getCaptureFormat();
1283 AudioFormat captureFormat = (new AudioFormat.Builder())
1284 .setChannelMask(originalFormat.getChannelMask())
1285 .setEncoding(originalFormat.getEncoding())
1286 .setSampleRate(originalFormat.getSampleRate())
1287 .build();
1288
1289 int bufferSize = AudioRecord.getMinBufferSize(
1290 captureFormat.getSampleRate() == AudioFormat.SAMPLE_RATE_UNSPECIFIED
1291 ? AudioFormat.SAMPLE_RATE_HZ_MAX
1292 : captureFormat.getSampleRate(),
1293 captureFormat.getChannelCount() == 2
1294 ? AudioFormat.CHANNEL_IN_STEREO
1295 : AudioFormat.CHANNEL_IN_MONO,
1296 captureFormat.getEncoding());
1297
Jason Hsu1363f582019-04-16 15:35:55 +08001298 sEventLogger.log(new SoundTriggerLogger.StringEvent("createAudioRecordForEvent"));
1299
Philip P. Moltmanna5b44032018-05-04 13:59:45 -07001300 return new AudioRecord(attributes, captureFormat, bufferSize,
1301 event.getCaptureSession());
1302 }
1303
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -08001304 @Override
1305 public void onGenericSoundTriggerDetected(SoundTrigger.GenericRecognitionEvent event) {
1306 if (DEBUG) Slog.v(TAG, mPuuid + ": Generic sound trigger event: " + event);
1307
Jason Hsu1363f582019-04-16 15:35:55 +08001308 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
1309 + ": Generic sound trigger event: " + event));
1310
Philip P. Moltmanna5b44032018-05-04 13:59:45 -07001311 runOrAddOperation(new Operation(
1312 // always execute:
1313 () -> {
mike dooley9b20c1c2019-01-23 10:38:17 +01001314 if (!mRecognitionConfig.allowMultipleTriggers) {
Philip P. Moltmanna5b44032018-05-04 13:59:45 -07001315 // Unregister this remoteService once op is done
1316 synchronized (mCallbacksLock) {
1317 mCallbacks.remove(mPuuid.getUuid());
1318 }
1319 mDestroyOnceRunningOpsDone = true;
1320 }
1321 },
1322 // execute if not throttled:
1323 (opId, service) -> service.onGenericRecognitionEvent(mPuuid, opId, event),
1324 // execute if throttled:
1325 () -> {
1326 if (event.isCaptureAvailable()) {
1327 AudioRecord capturedData = createAudioRecordForEvent(event);
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -08001328
Philip P. Moltmanna5b44032018-05-04 13:59:45 -07001329 // Currently we need to start and release the audio record to reset
1330 // the DSP even if we don't want to process the event
1331 capturedData.startRecording();
1332 capturedData.release();
1333 }
1334 }));
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -08001335 }
1336
1337 @Override
1338 public void onError(int status) {
1339 if (DEBUG) Slog.v(TAG, mPuuid + ": onError: " + status);
1340
Jason Hsu1363f582019-04-16 15:35:55 +08001341 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
1342 + ": onError: " + status));
1343
Philip P. Moltmanna5b44032018-05-04 13:59:45 -07001344 runOrAddOperation(
1345 new Operation(
1346 // always execute:
1347 () -> {
1348 // Unregister this remoteService once op is done
1349 synchronized (mCallbacksLock) {
1350 mCallbacks.remove(mPuuid.getUuid());
1351 }
1352 mDestroyOnceRunningOpsDone = true;
1353 },
1354 // execute if not throttled:
1355 (opId, service) -> service.onError(mPuuid, opId, status),
1356 // nothing to do if throttled
1357 null));
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -08001358 }
1359
1360 @Override
1361 public void onRecognitionPaused() {
1362 Slog.i(TAG, mPuuid + "->" + mServiceName + ": IGNORED onRecognitionPaused");
Jason Hsu1363f582019-04-16 15:35:55 +08001363
1364 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
1365 + "->" + mServiceName + ": IGNORED onRecognitionPaused"));
1366
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -08001367 }
1368
1369 @Override
1370 public void onRecognitionResumed() {
1371 Slog.i(TAG, mPuuid + "->" + mServiceName + ": IGNORED onRecognitionResumed");
Jason Hsu1363f582019-04-16 15:35:55 +08001372
1373 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
1374 + "->" + mServiceName + ": IGNORED onRecognitionResumed"));
1375
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -08001376 }
1377
1378 @Override
1379 public void onServiceConnected(ComponentName name, IBinder service) {
1380 if (DEBUG) Slog.v(TAG, mPuuid + ": onServiceConnected(" + service + ")");
1381
Jason Hsu1363f582019-04-16 15:35:55 +08001382 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
1383 + ": onServiceConnected(" + service + ")"));
1384
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -08001385 synchronized (mRemoteServiceLock) {
1386 mService = ISoundTriggerDetectionService.Stub.asInterface(service);
1387
1388 try {
1389 mService.setClient(mPuuid, mParams, mClient);
1390 } catch (Exception e) {
1391 Slog.e(TAG, mPuuid + ": Could not init " + mServiceName, e);
1392 return;
1393 }
1394
1395 while (!mPendingOps.isEmpty()) {
1396 runOrAddOperation(mPendingOps.remove(0));
1397 }
1398 }
1399 }
1400
1401 @Override
1402 public void onServiceDisconnected(ComponentName name) {
1403 if (DEBUG) Slog.v(TAG, mPuuid + ": onServiceDisconnected");
1404
Jason Hsu1363f582019-04-16 15:35:55 +08001405 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
1406 + ": onServiceDisconnected"));
1407
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -08001408 synchronized (mRemoteServiceLock) {
1409 mService = null;
1410 }
1411 }
1412
1413 @Override
1414 public void onBindingDied(ComponentName name) {
1415 if (DEBUG) Slog.v(TAG, mPuuid + ": onBindingDied");
1416
Jason Hsu1363f582019-04-16 15:35:55 +08001417 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
1418 + ": onBindingDied"));
1419
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -08001420 synchronized (mRemoteServiceLock) {
1421 destroy();
1422 }
1423 }
1424
1425 @Override
1426 public void onNullBinding(ComponentName name) {
1427 Slog.w(TAG, name + " for model " + mPuuid + " returned a null binding");
1428
Jason Hsu1363f582019-04-16 15:35:55 +08001429 sEventLogger.log(new SoundTriggerLogger.StringEvent(name + " for model "
1430 + mPuuid + " returned a null binding"));
1431
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -08001432 synchronized (mRemoteServiceLock) {
1433 disconnectLocked();
1434 }
1435 }
1436 }
1437
Arunesh Mishraa772e5f2016-01-25 10:33:11 -08001438 public final class LocalSoundTriggerService extends SoundTriggerInternal {
1439 private final Context mContext;
1440 private SoundTriggerHelper mSoundTriggerHelper;
1441
1442 LocalSoundTriggerService(Context context) {
1443 mContext = context;
1444 }
1445
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001446 synchronized void setSoundTriggerHelper(SoundTriggerHelper helper) {
1447 mSoundTriggerHelper = helper;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -08001448 }
1449
1450 @Override
1451 public int startRecognition(int keyphraseId, KeyphraseSoundModel soundModel,
1452 IRecognitionStatusCallback listener, RecognitionConfig recognitionConfig) {
Nicholas Amburf94db1c2019-12-08 19:04:06 -08001453 if (!isInitialized()) throw new UnsupportedOperationException();
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001454 return mSoundTriggerHelper.startKeyphraseRecognition(keyphraseId, soundModel, listener,
Arunesh Mishraa772e5f2016-01-25 10:33:11 -08001455 recognitionConfig);
1456 }
1457
1458 @Override
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001459 public synchronized int stopRecognition(int keyphraseId, IRecognitionStatusCallback listener) {
Nicholas Amburf94db1c2019-12-08 19:04:06 -08001460 if (!isInitialized()) throw new UnsupportedOperationException();
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001461 return mSoundTriggerHelper.stopKeyphraseRecognition(keyphraseId, listener);
Arunesh Mishraa772e5f2016-01-25 10:33:11 -08001462 }
1463
1464 @Override
Arunesh Mishra55a9b002016-02-01 14:06:37 -08001465 public ModuleProperties getModuleProperties() {
Nicholas Amburf94db1c2019-12-08 19:04:06 -08001466 if (!isInitialized()) throw new UnsupportedOperationException();
Arunesh Mishra55a9b002016-02-01 14:06:37 -08001467 return mSoundTriggerHelper.getModuleProperties();
1468 }
1469
1470 @Override
Nicholas Amburf94db1c2019-12-08 19:04:06 -08001471 public int setParameter(int keyphraseId, @ModelParams int modelParam, int value) {
1472 if (!isInitialized()) throw new UnsupportedOperationException();
1473 return mSoundTriggerHelper.setKeyphraseParameter(keyphraseId, modelParam, value);
1474 }
1475
1476 @Override
1477 public int getParameter(int keyphraseId, @ModelParams int modelParam) {
1478 if (!isInitialized()) throw new UnsupportedOperationException();
1479 return mSoundTriggerHelper.getKeyphraseParameter(keyphraseId, modelParam);
1480 }
1481
1482 @Override
1483 @Nullable
1484 public ModelParamRange queryParameter(int keyphraseId, @ModelParams int modelParam) {
1485 if (!isInitialized()) throw new UnsupportedOperationException();
1486 return mSoundTriggerHelper.queryKeyphraseParameter(keyphraseId, modelParam);
1487 }
1488
1489 @Override
Arunesh Mishra2d1de782016-02-21 18:10:28 -08001490 public int unloadKeyphraseModel(int keyphraseId) {
Nicholas Amburf94db1c2019-12-08 19:04:06 -08001491 if (!isInitialized()) throw new UnsupportedOperationException();
Arunesh Mishra2d1de782016-02-21 18:10:28 -08001492 return mSoundTriggerHelper.unloadKeyphraseSoundModel(keyphraseId);
1493 }
1494
1495 @Override
Arunesh Mishraa772e5f2016-01-25 10:33:11 -08001496 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001497 if (!isInitialized()) return;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -08001498 mSoundTriggerHelper.dump(fd, pw, args);
Jason Hsu1363f582019-04-16 15:35:55 +08001499 // log
1500 sEventLogger.dump(pw);
Benjamin Schwartz9e7a0152019-06-06 17:39:22 -07001501
Nicholas Amburd3ec82f2020-01-03 17:44:19 -08001502 // enrolled models
1503 mDbHelper.dump(pw);
1504
Benjamin Schwartz9e7a0152019-06-06 17:39:22 -07001505 // stats
1506 mSoundModelStatTracker.dump(pw);
Arunesh Mishraa772e5f2016-01-25 10:33:11 -08001507 }
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001508
1509 private synchronized boolean isInitialized() {
1510 if (mSoundTriggerHelper == null ) {
1511 Slog.e(TAG, "SoundTriggerHelper not initialized.");
Jason Hsu1363f582019-04-16 15:35:55 +08001512
1513 sEventLogger.log(new SoundTriggerLogger.StringEvent(
1514 "SoundTriggerHelper not initialized."));
1515
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001516 return false;
1517 }
1518 return true;
1519 }
Arunesh Mishraa772e5f2016-01-25 10:33:11 -08001520 }
1521
1522 private void enforceCallingPermission(String permission) {
1523 if (mContext.checkCallingOrSelfPermission(permission)
1524 != PackageManager.PERMISSION_GRANTED) {
1525 throw new SecurityException("Caller does not hold the permission " + permission);
1526 }
1527 }
Jason Hsu1363f582019-04-16 15:35:55 +08001528
1529 //=================================================================
1530 // For logging
1531
1532 private static final SoundTriggerLogger sEventLogger = new SoundTriggerLogger(200,
1533 "SoundTrigger activity");
1534
Arunesh Mishraa772e5f2016-01-25 10:33:11 -08001535}