blob: a8cafb33790d01164bd9883260e97191deb236c6 [file] [log] [blame]
Arunesh Mishraa772e5f2016-01-25 10:33:11 -08001/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.server.soundtrigger;
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -080018
19import static android.Manifest.permission.BIND_SOUND_TRIGGER_DETECTION_SERVICE;
20import static android.content.Context.BIND_AUTO_CREATE;
21import static android.content.Context.BIND_FOREGROUND_SERVICE;
22import static android.content.pm.PackageManager.GET_META_DATA;
23import static android.content.pm.PackageManager.GET_SERVICES;
24import static android.content.pm.PackageManager.MATCH_DEBUG_TRIAGED_MISSING;
Arunesh Mishra3fff7f52016-02-09 12:15:19 -080025import static android.hardware.soundtrigger.SoundTrigger.STATUS_ERROR;
Chris Thorntonba08b792017-06-08 22:34:37 -070026import static android.hardware.soundtrigger.SoundTrigger.STATUS_OK;
Philip P. Moltmann7e25b3d2018-03-09 20:22:58 -080027import static android.provider.Settings.Global.MAX_SOUND_TRIGGER_DETECTION_SERVICE_OPS_PER_DAY;
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -080028import static android.provider.Settings.Global.SOUND_TRIGGER_DETECTION_SERVICE_OP_TIMEOUT;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -080029
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -080030import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
31
32import android.Manifest;
33import android.annotation.NonNull;
34import android.annotation.Nullable;
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -080035import android.content.ComponentName;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -080036import android.content.Context;
Chris Thorntonba08b792017-06-08 22:34:37 -070037import android.content.Intent;
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -080038import android.content.ServiceConnection;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -080039import android.content.pm.PackageManager;
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -080040import android.content.pm.ResolveInfo;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -080041import android.hardware.soundtrigger.IRecognitionStatusCallback;
42import android.hardware.soundtrigger.SoundTrigger;
Arunesh Mishrac722ec412016-01-27 13:29:12 -080043import android.hardware.soundtrigger.SoundTrigger.GenericSoundModel;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -080044import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel;
Arunesh Mishra55a9b002016-02-01 14:06:37 -080045import android.hardware.soundtrigger.SoundTrigger.ModuleProperties;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -080046import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig;
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -080047import android.hardware.soundtrigger.SoundTrigger.SoundModel;
Philip P. Moltmanna5b44032018-05-04 13:59:45 -070048import android.media.AudioAttributes;
49import android.media.AudioFormat;
50import android.media.AudioRecord;
51import android.media.MediaRecorder;
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -080052import android.media.soundtrigger.ISoundTriggerDetectionService;
53import android.media.soundtrigger.ISoundTriggerDetectionServiceClient;
54import android.media.soundtrigger.SoundTriggerDetectionService;
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -080055import android.os.Binder;
Chris Thorntonba08b792017-06-08 22:34:37 -070056import android.os.Bundle;
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -080057import android.os.Handler;
58import android.os.IBinder;
59import android.os.Looper;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -080060import android.os.Parcel;
61import android.os.ParcelUuid;
Chris Thorntonba08b792017-06-08 22:34:37 -070062import android.os.PowerManager;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -080063import android.os.RemoteException;
Benjamin Schwartz9e7a0152019-06-06 17:39:22 -070064import android.os.SystemClock;
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -080065import android.os.UserHandle;
66import android.provider.Settings;
Philip P. Moltmann7e25b3d2018-03-09 20:22:58 -080067import android.util.ArrayMap;
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -080068import android.util.ArraySet;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -080069import android.util.Slog;
70
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -080071import com.android.internal.annotations.GuardedBy;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -080072import com.android.internal.app.ISoundTriggerService;
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -080073import com.android.internal.util.Preconditions;
74import com.android.server.SystemService;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -080075
76import java.io.FileDescriptor;
77import java.io.PrintWriter;
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -080078import java.util.ArrayList;
Benjamin Schwartz9e7a0152019-06-06 17:39:22 -070079import java.util.Map;
Chris Thorntonba08b792017-06-08 22:34:37 -070080import java.util.TreeMap;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -080081import java.util.UUID;
Philip P. Moltmann7e25b3d2018-03-09 20:22:58 -080082import java.util.concurrent.TimeUnit;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -080083
84/**
85 * A single SystemService to manage all sound/voice-based sound models on the DSP.
86 * This services provides apis to manage sound trigger-based sound models via
87 * the ISoundTriggerService interface. This class also publishes a local interface encapsulating
88 * the functionality provided by {@link SoundTriggerHelper} for use by
89 * {@link VoiceInteractionManagerService}.
90 *
91 * @hide
92 */
93public class SoundTriggerService extends SystemService {
Arunesh Mishra3fff7f52016-02-09 12:15:19 -080094 private static final String TAG = "SoundTriggerService";
95 private static final boolean DEBUG = true;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -080096
97 final Context mContext;
Chris Thorntonba08b792017-06-08 22:34:37 -070098 private Object mLock;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -080099 private final SoundTriggerServiceStub mServiceStub;
100 private final LocalSoundTriggerService mLocalSoundTriggerService;
101 private SoundTriggerDbHelper mDbHelper;
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800102 private SoundTriggerHelper mSoundTriggerHelper;
Chris Thorntonba08b792017-06-08 22:34:37 -0700103 private final TreeMap<UUID, SoundModel> mLoadedModels;
Chris Thorntonae5fb992017-12-07 18:26:31 -0800104 private Object mCallbacksLock;
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800105 private final TreeMap<UUID, IRecognitionStatusCallback> mCallbacks;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800106
Benjamin Schwartz9e7a0152019-06-06 17:39:22 -0700107 class SoundModelStatTracker {
108 private class SoundModelStat {
109 SoundModelStat() {
110 mStartCount = 0;
111 mTotalTimeMsec = 0;
112 mLastStartTimestampMsec = 0;
113 mLastStopTimestampMsec = 0;
114 mIsStarted = false;
115 }
116 long mStartCount; // Number of times that given model started
117 long mTotalTimeMsec; // Total time (msec) that given model was running since boot
118 long mLastStartTimestampMsec; // SystemClock.elapsedRealtime model was last started
119 long mLastStopTimestampMsec; // SystemClock.elapsedRealtime model was last stopped
120 boolean mIsStarted; // true if model is currently running
121 }
122 private final TreeMap<UUID, SoundModelStat> mModelStats;
123
124 SoundModelStatTracker() {
125 mModelStats = new TreeMap<UUID, SoundModelStat>();
126 }
127
128 public synchronized void onStart(UUID id) {
129 SoundModelStat stat = mModelStats.get(id);
130 if (stat == null) {
131 stat = new SoundModelStat();
132 mModelStats.put(id, stat);
133 }
134
135 if (stat.mIsStarted) {
136 Slog.e(TAG, "error onStart(): Model " + id + " already started");
137 return;
138 }
139
140 stat.mStartCount++;
141 stat.mLastStartTimestampMsec = SystemClock.elapsedRealtime();
142 stat.mIsStarted = true;
143 }
144
145 public synchronized void onStop(UUID id) {
146 SoundModelStat stat = mModelStats.get(id);
147 if (stat == null) {
148 Slog.e(TAG, "error onStop(): Model " + id + " has no stats available");
149 return;
150 }
151
152 if (!stat.mIsStarted) {
153 Slog.e(TAG, "error onStop(): Model " + id + " already stopped");
154 return;
155 }
156
157 stat.mLastStopTimestampMsec = SystemClock.elapsedRealtime();
158 stat.mTotalTimeMsec += stat.mLastStopTimestampMsec - stat.mLastStartTimestampMsec;
159 stat.mIsStarted = false;
160 }
161
162 public synchronized void dump(PrintWriter pw) {
163 long curTime = SystemClock.elapsedRealtime();
164 pw.println("Model Stats:");
165 for (Map.Entry<UUID, SoundModelStat> entry : mModelStats.entrySet()) {
166 UUID uuid = entry.getKey();
167 SoundModelStat stat = entry.getValue();
168 long totalTimeMsec = stat.mTotalTimeMsec;
169 if (stat.mIsStarted) {
170 totalTimeMsec += curTime - stat.mLastStartTimestampMsec;
171 }
172 pw.println(uuid + ", total_time(msec)=" + totalTimeMsec
173 + ", total_count=" + stat.mStartCount
174 + ", last_start=" + stat.mLastStartTimestampMsec
175 + ", last_stop=" + stat.mLastStopTimestampMsec);
176 }
177 }
178 }
179
180 private final SoundModelStatTracker mSoundModelStatTracker;
Philip P. Moltmann7e25b3d2018-03-09 20:22:58 -0800181 /** Number of ops run by the {@link RemoteSoundTriggerDetectionService} per package name */
182 @GuardedBy("mLock")
183 private final ArrayMap<String, NumOps> mNumOpsPerPackage = new ArrayMap<>();
184
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800185 public SoundTriggerService(Context context) {
186 super(context);
187 mContext = context;
188 mServiceStub = new SoundTriggerServiceStub();
189 mLocalSoundTriggerService = new LocalSoundTriggerService(context);
Chris Thorntonba08b792017-06-08 22:34:37 -0700190 mLoadedModels = new TreeMap<UUID, SoundModel>();
Chris Thorntonae5fb992017-12-07 18:26:31 -0800191 mCallbacksLock = new Object();
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800192 mCallbacks = new TreeMap<>();
Chris Thorntonba08b792017-06-08 22:34:37 -0700193 mLock = new Object();
Benjamin Schwartz9e7a0152019-06-06 17:39:22 -0700194 mSoundModelStatTracker = new SoundModelStatTracker();
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800195 }
196
197 @Override
198 public void onStart() {
199 publishBinderService(Context.SOUND_TRIGGER_SERVICE, mServiceStub);
200 publishLocalService(SoundTriggerInternal.class, mLocalSoundTriggerService);
201 }
202
203 @Override
204 public void onBootPhase(int phase) {
205 if (PHASE_SYSTEM_SERVICES_READY == phase) {
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800206 initSoundTriggerHelper();
207 mLocalSoundTriggerService.setSoundTriggerHelper(mSoundTriggerHelper);
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800208 } else if (PHASE_THIRD_PARTY_APPS_CAN_START == phase) {
209 mDbHelper = new SoundTriggerDbHelper(mContext);
210 }
211 }
212
213 @Override
214 public void onStartUser(int userHandle) {
215 }
216
217 @Override
218 public void onSwitchUser(int userHandle) {
219 }
220
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800221 private synchronized void initSoundTriggerHelper() {
222 if (mSoundTriggerHelper == null) {
223 mSoundTriggerHelper = new SoundTriggerHelper(mContext);
224 }
225 }
226
227 private synchronized boolean isInitialized() {
228 if (mSoundTriggerHelper == null ) {
229 Slog.e(TAG, "SoundTriggerHelper not initialized.");
230 return false;
231 }
232 return true;
233 }
234
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800235 class SoundTriggerServiceStub extends ISoundTriggerService.Stub {
236 @Override
237 public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
238 throws RemoteException {
239 try {
240 return super.onTransact(code, data, reply, flags);
241 } catch (RuntimeException e) {
242 // The activity manager only throws security exceptions, so let's
243 // log all others.
244 if (!(e instanceof SecurityException)) {
245 Slog.wtf(TAG, "SoundTriggerService Crash", e);
246 }
247 throw e;
248 }
249 }
250
251 @Override
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800252 public int startRecognition(ParcelUuid parcelUuid, IRecognitionStatusCallback callback,
253 RecognitionConfig config) {
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800254 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
Arunesh Mishra2d1de782016-02-21 18:10:28 -0800255 if (!isInitialized()) return STATUS_ERROR;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800256 if (DEBUG) {
257 Slog.i(TAG, "startRecognition(): Uuid : " + parcelUuid);
258 }
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800259
Jason Hsu1363f582019-04-16 15:35:55 +0800260 sEventLogger.log(new SoundTriggerLogger.StringEvent("startRecognition(): Uuid : "
261 + parcelUuid));
262
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800263 GenericSoundModel model = getSoundModel(parcelUuid);
264 if (model == null) {
265 Slog.e(TAG, "Null model in database for id: " + parcelUuid);
Jason Hsu1363f582019-04-16 15:35:55 +0800266
267 sEventLogger.log(new SoundTriggerLogger.StringEvent(
268 "startRecognition(): Null model in database for id: " + parcelUuid));
269
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800270 return STATUS_ERROR;
271 }
272
Benjamin Schwartz9e7a0152019-06-06 17:39:22 -0700273 int ret = mSoundTriggerHelper.startGenericRecognition(parcelUuid.getUuid(), model,
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800274 callback, config);
Benjamin Schwartz9e7a0152019-06-06 17:39:22 -0700275 if (ret == STATUS_OK) {
276 mSoundModelStatTracker.onStart(parcelUuid.getUuid());
277 }
278 return ret;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800279 }
280
281 @Override
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800282 public int stopRecognition(ParcelUuid parcelUuid, IRecognitionStatusCallback callback) {
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800283 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
284 if (DEBUG) {
285 Slog.i(TAG, "stopRecognition(): Uuid : " + parcelUuid);
286 }
Jason Hsu1363f582019-04-16 15:35:55 +0800287
288 sEventLogger.log(new SoundTriggerLogger.StringEvent("stopRecognition(): Uuid : "
289 + parcelUuid));
290
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800291 if (!isInitialized()) return STATUS_ERROR;
Benjamin Schwartz9e7a0152019-06-06 17:39:22 -0700292
293 int ret = mSoundTriggerHelper.stopGenericRecognition(parcelUuid.getUuid(), callback);
294 if (ret == STATUS_OK) {
295 mSoundModelStatTracker.onStop(parcelUuid.getUuid());
296 }
297 return ret;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800298 }
299
300 @Override
Arunesh Mishrac722ec412016-01-27 13:29:12 -0800301 public SoundTrigger.GenericSoundModel getSoundModel(ParcelUuid soundModelId) {
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800302 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
303 if (DEBUG) {
304 Slog.i(TAG, "getSoundModel(): id = " + soundModelId);
305 }
Jason Hsu1363f582019-04-16 15:35:55 +0800306
307 sEventLogger.log(new SoundTriggerLogger.StringEvent("getSoundModel(): id = "
308 + soundModelId));
309
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800310 SoundTrigger.GenericSoundModel model = mDbHelper.getGenericSoundModel(
311 soundModelId.getUuid());
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800312 return model;
313 }
314
315 @Override
Arunesh Mishrac722ec412016-01-27 13:29:12 -0800316 public void updateSoundModel(SoundTrigger.GenericSoundModel soundModel) {
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800317 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
318 if (DEBUG) {
319 Slog.i(TAG, "updateSoundModel(): model = " + soundModel);
320 }
Jason Hsu1363f582019-04-16 15:35:55 +0800321
322 sEventLogger.log(new SoundTriggerLogger.StringEvent("updateSoundModel(): model = "
323 + soundModel));
324
Arunesh Mishrac722ec412016-01-27 13:29:12 -0800325 mDbHelper.updateGenericSoundModel(soundModel);
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800326 }
327
328 @Override
329 public void deleteSoundModel(ParcelUuid soundModelId) {
330 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
331 if (DEBUG) {
332 Slog.i(TAG, "deleteSoundModel(): id = " + soundModelId);
333 }
Jason Hsu1363f582019-04-16 15:35:55 +0800334
335 sEventLogger.log(new SoundTriggerLogger.StringEvent("deleteSoundModel(): id = "
336 + soundModelId));
337
Arunesh Mishra2d1de782016-02-21 18:10:28 -0800338 // Unload the model if it is loaded.
339 mSoundTriggerHelper.unloadGenericSoundModel(soundModelId.getUuid());
Arunesh Mishrac722ec412016-01-27 13:29:12 -0800340 mDbHelper.deleteGenericSoundModel(soundModelId.getUuid());
Benjamin Schwartz9e7a0152019-06-06 17:39:22 -0700341
342 // Stop recognition if it is started.
343 mSoundModelStatTracker.onStop(soundModelId.getUuid());
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800344 }
Chris Thorntonba08b792017-06-08 22:34:37 -0700345
346 @Override
347 public int loadGenericSoundModel(GenericSoundModel soundModel) {
348 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
349 if (!isInitialized()) return STATUS_ERROR;
350 if (soundModel == null || soundModel.uuid == null) {
351 Slog.e(TAG, "Invalid sound model");
Jason Hsu1363f582019-04-16 15:35:55 +0800352
353 sEventLogger.log(new SoundTriggerLogger.StringEvent(
354 "loadGenericSoundModel(): Invalid sound model"));
355
Chris Thorntonba08b792017-06-08 22:34:37 -0700356 return STATUS_ERROR;
357 }
358 if (DEBUG) {
359 Slog.i(TAG, "loadGenericSoundModel(): id = " + soundModel.uuid);
360 }
Jason Hsu1363f582019-04-16 15:35:55 +0800361
362 sEventLogger.log(new SoundTriggerLogger.StringEvent("loadGenericSoundModel(): id = "
363 + soundModel.uuid));
364
Chris Thorntonba08b792017-06-08 22:34:37 -0700365 synchronized (mLock) {
366 SoundModel oldModel = mLoadedModels.get(soundModel.uuid);
367 // If the model we're loading is actually different than what we had loaded, we
368 // should unload that other model now. We don't care about return codes since we
369 // don't know if the other model is loaded.
370 if (oldModel != null && !oldModel.equals(soundModel)) {
371 mSoundTriggerHelper.unloadGenericSoundModel(soundModel.uuid);
Chris Thorntonae5fb992017-12-07 18:26:31 -0800372 synchronized (mCallbacksLock) {
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800373 mCallbacks.remove(soundModel.uuid);
Chris Thorntonae5fb992017-12-07 18:26:31 -0800374 }
Chris Thorntonba08b792017-06-08 22:34:37 -0700375 }
376 mLoadedModels.put(soundModel.uuid, soundModel);
377 }
378 return STATUS_OK;
379 }
380
381 @Override
382 public int loadKeyphraseSoundModel(KeyphraseSoundModel soundModel) {
383 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
384 if (!isInitialized()) return STATUS_ERROR;
385 if (soundModel == null || soundModel.uuid == null) {
386 Slog.e(TAG, "Invalid sound model");
Jason Hsu1363f582019-04-16 15:35:55 +0800387
388 sEventLogger.log(new SoundTriggerLogger.StringEvent(
389 "loadKeyphraseSoundModel(): Invalid sound model"));
390
Chris Thorntonba08b792017-06-08 22:34:37 -0700391 return STATUS_ERROR;
392 }
393 if (soundModel.keyphrases == null || soundModel.keyphrases.length != 1) {
394 Slog.e(TAG, "Only one keyphrase per model is currently supported.");
Jason Hsu1363f582019-04-16 15:35:55 +0800395
396 sEventLogger.log(new SoundTriggerLogger.StringEvent(
397 "loadKeyphraseSoundModel(): Only one keyphrase per model"
398 + " is currently supported."));
399
Chris Thorntonba08b792017-06-08 22:34:37 -0700400 return STATUS_ERROR;
401 }
402 if (DEBUG) {
403 Slog.i(TAG, "loadKeyphraseSoundModel(): id = " + soundModel.uuid);
404 }
Jason Hsu1363f582019-04-16 15:35:55 +0800405
406 sEventLogger.log(new SoundTriggerLogger.StringEvent("loadKeyphraseSoundModel(): id = "
407 + soundModel.uuid));
408
Chris Thorntonba08b792017-06-08 22:34:37 -0700409 synchronized (mLock) {
410 SoundModel oldModel = mLoadedModels.get(soundModel.uuid);
411 // If the model we're loading is actually different than what we had loaded, we
412 // should unload that other model now. We don't care about return codes since we
413 // don't know if the other model is loaded.
414 if (oldModel != null && !oldModel.equals(soundModel)) {
415 mSoundTriggerHelper.unloadKeyphraseSoundModel(soundModel.keyphrases[0].id);
Chris Thorntonae5fb992017-12-07 18:26:31 -0800416 synchronized (mCallbacksLock) {
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800417 mCallbacks.remove(soundModel.uuid);
Chris Thorntonae5fb992017-12-07 18:26:31 -0800418 }
Chris Thorntonba08b792017-06-08 22:34:37 -0700419 }
420 mLoadedModels.put(soundModel.uuid, soundModel);
421 }
422 return STATUS_OK;
423 }
424
425 @Override
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800426 public int startRecognitionForService(ParcelUuid soundModelId, Bundle params,
427 ComponentName detectionService, SoundTrigger.RecognitionConfig config) {
428 Preconditions.checkNotNull(soundModelId);
429 Preconditions.checkNotNull(detectionService);
430 Preconditions.checkNotNull(config);
431
Chris Thorntonba08b792017-06-08 22:34:37 -0700432 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
Philip P. Moltmann19402f52018-03-27 14:38:03 -0700433
Chris Thorntonba08b792017-06-08 22:34:37 -0700434 if (!isInitialized()) return STATUS_ERROR;
435 if (DEBUG) {
436 Slog.i(TAG, "startRecognition(): id = " + soundModelId);
437 }
438
Jason Hsu1363f582019-04-16 15:35:55 +0800439 sEventLogger.log(new SoundTriggerLogger.StringEvent(
440 "startRecognitionForService(): id = " + soundModelId));
441
Philip P. Moltmann19402f52018-03-27 14:38:03 -0700442 IRecognitionStatusCallback callback =
443 new RemoteSoundTriggerDetectionService(soundModelId.getUuid(), params,
444 detectionService, Binder.getCallingUserHandle(), config);
445
Chris Thorntonba08b792017-06-08 22:34:37 -0700446 synchronized (mLock) {
447 SoundModel soundModel = mLoadedModels.get(soundModelId.getUuid());
448 if (soundModel == null) {
449 Slog.e(TAG, soundModelId + " is not loaded");
Jason Hsu1363f582019-04-16 15:35:55 +0800450
451 sEventLogger.log(new SoundTriggerLogger.StringEvent(
452 "startRecognitionForService():" + soundModelId + " is not loaded"));
453
Chris Thorntonba08b792017-06-08 22:34:37 -0700454 return STATUS_ERROR;
455 }
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800456 IRecognitionStatusCallback existingCallback = null;
Chris Thorntonae5fb992017-12-07 18:26:31 -0800457 synchronized (mCallbacksLock) {
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800458 existingCallback = mCallbacks.get(soundModelId.getUuid());
Chris Thorntonae5fb992017-12-07 18:26:31 -0800459 }
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800460 if (existingCallback != null) {
Chris Thorntonba08b792017-06-08 22:34:37 -0700461 Slog.e(TAG, soundModelId + " is already running");
Jason Hsu1363f582019-04-16 15:35:55 +0800462
463 sEventLogger.log(new SoundTriggerLogger.StringEvent(
464 "startRecognitionForService():"
465 + soundModelId + " is already running"));
466
Chris Thorntonba08b792017-06-08 22:34:37 -0700467 return STATUS_ERROR;
468 }
Chris Thorntonba08b792017-06-08 22:34:37 -0700469 int ret;
470 switch (soundModel.type) {
Chris Thorntonba08b792017-06-08 22:34:37 -0700471 case SoundModel.TYPE_GENERIC_SOUND:
472 ret = mSoundTriggerHelper.startGenericRecognition(soundModel.uuid,
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800473 (GenericSoundModel) soundModel, callback, config);
Chris Thorntonba08b792017-06-08 22:34:37 -0700474 break;
475 default:
476 Slog.e(TAG, "Unknown model type");
Jason Hsu1363f582019-04-16 15:35:55 +0800477
478 sEventLogger.log(new SoundTriggerLogger.StringEvent(
479 "startRecognitionForService(): Unknown model type"));
480
Chris Thorntonba08b792017-06-08 22:34:37 -0700481 return STATUS_ERROR;
482 }
483
484 if (ret != STATUS_OK) {
485 Slog.e(TAG, "Failed to start model: " + ret);
Jason Hsu1363f582019-04-16 15:35:55 +0800486
487 sEventLogger.log(new SoundTriggerLogger.StringEvent(
488 "startRecognitionForService(): Failed to start model:"));
489
Chris Thorntonba08b792017-06-08 22:34:37 -0700490 return ret;
491 }
Chris Thorntonae5fb992017-12-07 18:26:31 -0800492 synchronized (mCallbacksLock) {
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800493 mCallbacks.put(soundModelId.getUuid(), callback);
Chris Thorntonae5fb992017-12-07 18:26:31 -0800494 }
Benjamin Schwartz9e7a0152019-06-06 17:39:22 -0700495
496 mSoundModelStatTracker.onStart(soundModelId.getUuid());
Chris Thorntonba08b792017-06-08 22:34:37 -0700497 }
498 return STATUS_OK;
499 }
500
501 @Override
Philip P. Moltmann19402f52018-03-27 14:38:03 -0700502 public int stopRecognitionForService(ParcelUuid soundModelId) {
Chris Thorntonba08b792017-06-08 22:34:37 -0700503 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
504 if (!isInitialized()) return STATUS_ERROR;
505 if (DEBUG) {
506 Slog.i(TAG, "stopRecognition(): id = " + soundModelId);
507 }
508
Jason Hsu1363f582019-04-16 15:35:55 +0800509 sEventLogger.log(new SoundTriggerLogger.StringEvent(
510 "stopRecognitionForService(): id = " + soundModelId));
511
Chris Thorntonba08b792017-06-08 22:34:37 -0700512 synchronized (mLock) {
513 SoundModel soundModel = mLoadedModels.get(soundModelId.getUuid());
514 if (soundModel == null) {
515 Slog.e(TAG, soundModelId + " is not loaded");
Jason Hsu1363f582019-04-16 15:35:55 +0800516
517 sEventLogger.log(new SoundTriggerLogger.StringEvent(
518 "stopRecognitionForService(): " + soundModelId
519 + " is not loaded"));
520
Chris Thorntonba08b792017-06-08 22:34:37 -0700521 return STATUS_ERROR;
522 }
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800523 IRecognitionStatusCallback callback = null;
Chris Thorntonae5fb992017-12-07 18:26:31 -0800524 synchronized (mCallbacksLock) {
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800525 callback = mCallbacks.get(soundModelId.getUuid());
Chris Thorntonae5fb992017-12-07 18:26:31 -0800526 }
Chris Thorntonba08b792017-06-08 22:34:37 -0700527 if (callback == null) {
528 Slog.e(TAG, soundModelId + " is not running");
Jason Hsu1363f582019-04-16 15:35:55 +0800529
530 sEventLogger.log(new SoundTriggerLogger.StringEvent(
531 "stopRecognitionForService(): " + soundModelId
532 + " is not running"));
533
Chris Thorntonba08b792017-06-08 22:34:37 -0700534 return STATUS_ERROR;
535 }
536 int ret;
537 switch (soundModel.type) {
Chris Thorntonba08b792017-06-08 22:34:37 -0700538 case SoundModel.TYPE_GENERIC_SOUND:
539 ret = mSoundTriggerHelper.stopGenericRecognition(soundModel.uuid, callback);
540 break;
541 default:
542 Slog.e(TAG, "Unknown model type");
Jason Hsu1363f582019-04-16 15:35:55 +0800543
544 sEventLogger.log(new SoundTriggerLogger.StringEvent(
545 "stopRecognitionForService(): Unknown model type"));
546
Chris Thorntonba08b792017-06-08 22:34:37 -0700547 return STATUS_ERROR;
548 }
549
550 if (ret != STATUS_OK) {
551 Slog.e(TAG, "Failed to stop model: " + ret);
Jason Hsu1363f582019-04-16 15:35:55 +0800552
553 sEventLogger.log(new SoundTriggerLogger.StringEvent(
554 "stopRecognitionForService(): Failed to stop model: " + ret));
555
Chris Thorntonba08b792017-06-08 22:34:37 -0700556 return ret;
557 }
Chris Thorntonae5fb992017-12-07 18:26:31 -0800558 synchronized (mCallbacksLock) {
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800559 mCallbacks.remove(soundModelId.getUuid());
Chris Thorntonae5fb992017-12-07 18:26:31 -0800560 }
Benjamin Schwartz9e7a0152019-06-06 17:39:22 -0700561
562 mSoundModelStatTracker.onStop(soundModelId.getUuid());
Chris Thorntonba08b792017-06-08 22:34:37 -0700563 }
564 return STATUS_OK;
565 }
566
567 @Override
568 public int unloadSoundModel(ParcelUuid soundModelId) {
569 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
570 if (!isInitialized()) return STATUS_ERROR;
571 if (DEBUG) {
572 Slog.i(TAG, "unloadSoundModel(): id = " + soundModelId);
573 }
574
Jason Hsu1363f582019-04-16 15:35:55 +0800575 sEventLogger.log(new SoundTriggerLogger.StringEvent("unloadSoundModel(): id = "
576 + soundModelId));
577
Chris Thorntonba08b792017-06-08 22:34:37 -0700578 synchronized (mLock) {
579 SoundModel soundModel = mLoadedModels.get(soundModelId.getUuid());
580 if (soundModel == null) {
581 Slog.e(TAG, soundModelId + " is not loaded");
Jason Hsu1363f582019-04-16 15:35:55 +0800582
583 sEventLogger.log(new SoundTriggerLogger.StringEvent(
584 "unloadSoundModel(): " + soundModelId + " is not loaded"));
585
Chris Thorntonba08b792017-06-08 22:34:37 -0700586 return STATUS_ERROR;
587 }
588 int ret;
589 switch (soundModel.type) {
590 case SoundModel.TYPE_KEYPHRASE:
591 ret = mSoundTriggerHelper.unloadKeyphraseSoundModel(
592 ((KeyphraseSoundModel)soundModel).keyphrases[0].id);
593 break;
594 case SoundModel.TYPE_GENERIC_SOUND:
595 ret = mSoundTriggerHelper.unloadGenericSoundModel(soundModel.uuid);
596 break;
597 default:
598 Slog.e(TAG, "Unknown model type");
Jason Hsu1363f582019-04-16 15:35:55 +0800599
600 sEventLogger.log(new SoundTriggerLogger.StringEvent(
601 "unloadSoundModel(): Unknown model type"));
602
Chris Thorntonba08b792017-06-08 22:34:37 -0700603 return STATUS_ERROR;
604 }
605 if (ret != STATUS_OK) {
606 Slog.e(TAG, "Failed to unload model");
Jason Hsu1363f582019-04-16 15:35:55 +0800607
608 sEventLogger.log(new SoundTriggerLogger.StringEvent(
609 "unloadSoundModel(): Failed to unload model"));
610
Chris Thorntonba08b792017-06-08 22:34:37 -0700611 return ret;
612 }
613 mLoadedModels.remove(soundModelId.getUuid());
614 return STATUS_OK;
615 }
616 }
617
618 @Override
619 public boolean isRecognitionActive(ParcelUuid parcelUuid) {
620 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
621 if (!isInitialized()) return false;
Chris Thorntonae5fb992017-12-07 18:26:31 -0800622 synchronized (mCallbacksLock) {
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800623 IRecognitionStatusCallback callback = mCallbacks.get(parcelUuid.getUuid());
Chris Thorntonba08b792017-06-08 22:34:37 -0700624 if (callback == null) {
625 return false;
626 }
Chris Thorntonba08b792017-06-08 22:34:37 -0700627 }
Chris Thorntonae5fb992017-12-07 18:26:31 -0800628 return mSoundTriggerHelper.isRecognitionRequested(parcelUuid.getUuid());
Chris Thorntonba08b792017-06-08 22:34:37 -0700629 }
Michael Dooley291751e2018-10-16 19:53:29 +0000630
631 @Override
mike dooleyb2ab04a2018-11-07 15:48:54 +0100632 public int getModelState(ParcelUuid soundModelId) {
Michael Dooley291751e2018-10-16 19:53:29 +0000633 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
mike dooleyb2ab04a2018-11-07 15:48:54 +0100634 int ret = STATUS_ERROR;
635 if (!isInitialized()) return ret;
Michael Dooley291751e2018-10-16 19:53:29 +0000636 if (DEBUG) {
637 Slog.i(TAG, "getModelState(): id = " + soundModelId);
638 }
639
Jason Hsu1363f582019-04-16 15:35:55 +0800640 sEventLogger.log(new SoundTriggerLogger.StringEvent("getModelState(): id = "
641 + soundModelId));
642
Michael Dooley291751e2018-10-16 19:53:29 +0000643 synchronized (mLock) {
644 SoundModel soundModel = mLoadedModels.get(soundModelId.getUuid());
645 if (soundModel == null) {
646 Slog.e(TAG, soundModelId + " is not loaded");
Jason Hsu1363f582019-04-16 15:35:55 +0800647
648 sEventLogger.log(new SoundTriggerLogger.StringEvent("getModelState(): "
649 + soundModelId + " is not loaded"));
650
mike dooleyb2ab04a2018-11-07 15:48:54 +0100651 return ret;
Michael Dooley291751e2018-10-16 19:53:29 +0000652 }
Michael Dooley291751e2018-10-16 19:53:29 +0000653 switch (soundModel.type) {
Michael Dooley291751e2018-10-16 19:53:29 +0000654 case SoundModel.TYPE_GENERIC_SOUND:
655 ret = mSoundTriggerHelper.getGenericModelState(soundModel.uuid);
656 break;
657 default:
mike dooleybaa22c72019-05-15 10:05:25 +0200658 // SoundModel.TYPE_KEYPHRASE is not supported to increase privacy.
659 Slog.e(TAG, "Unsupported model type, " + soundModel.type);
Jason Hsu1363f582019-04-16 15:35:55 +0800660 sEventLogger.log(new SoundTriggerLogger.StringEvent(
mike dooleybaa22c72019-05-15 10:05:25 +0200661 "getModelState(): Unsupported model type, "
662 + soundModel.type));
Michael Dooley291751e2018-10-16 19:53:29 +0000663 break;
664 }
Michael Dooley291751e2018-10-16 19:53:29 +0000665
666 return ret;
667 }
668 }
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800669 }
670
Philip P. Moltmann7e25b3d2018-03-09 20:22:58 -0800671 /**
672 * Counts the number of operations added in the last 24 hours.
673 */
674 private static class NumOps {
675 private final Object mLock = new Object();
676
677 @GuardedBy("mLock")
678 private int[] mNumOps = new int[24];
679 @GuardedBy("mLock")
680 private long mLastOpsHourSinceBoot;
681
682 /**
683 * Clear buckets of new hours that have elapsed since last operation.
684 *
685 * <p>I.e. when the last operation was triggered at 1:40 and the current operation was
686 * triggered at 4:03, the buckets "2, 3, and 4" are cleared.
687 *
688 * @param currentTime Current elapsed time since boot in ns
689 */
690 void clearOldOps(long currentTime) {
691 synchronized (mLock) {
692 long numHoursSinceBoot = TimeUnit.HOURS.convert(currentTime, TimeUnit.NANOSECONDS);
693
694 // Clear buckets of new hours that have elapsed since last operation
695 // I.e. when the last operation was triggered at 1:40 and the current
696 // operation was triggered at 4:03, the bucket "2, 3, and 4" is cleared
697 if (mLastOpsHourSinceBoot != 0) {
698 for (long hour = mLastOpsHourSinceBoot + 1; hour <= numHoursSinceBoot; hour++) {
699 mNumOps[(int) (hour % 24)] = 0;
700 }
701 }
702 }
703 }
704
705 /**
706 * Add a new operation.
707 *
708 * @param currentTime Current elapsed time since boot in ns
709 */
710 void addOp(long currentTime) {
711 synchronized (mLock) {
712 long numHoursSinceBoot = TimeUnit.HOURS.convert(currentTime, TimeUnit.NANOSECONDS);
713
714 mNumOps[(int) (numHoursSinceBoot % 24)]++;
715 mLastOpsHourSinceBoot = numHoursSinceBoot;
716 }
717 }
718
719 /**
720 * Get the total operations added in the last 24 hours.
721 *
722 * @return The total number of operations added in the last 24 hours
723 */
724 int getOpsAdded() {
725 synchronized (mLock) {
726 int totalOperationsInLastDay = 0;
727 for (int i = 0; i < 24; i++) {
728 totalOperationsInLastDay += mNumOps[i];
729 }
730
731 return totalOperationsInLastDay;
732 }
733 }
734 }
735
Philip P. Moltmanna5b44032018-05-04 13:59:45 -0700736 /**
737 * A single operation run in a {@link RemoteSoundTriggerDetectionService}.
738 *
739 * <p>Once the remote service is connected either setup + execute or setup + stop is executed.
740 */
741 private static class Operation {
742 private interface ExecuteOp {
743 void run(int opId, ISoundTriggerDetectionService service) throws RemoteException;
744 }
745
746 private final @Nullable Runnable mSetupOp;
747 private final @NonNull ExecuteOp mExecuteOp;
748 private final @Nullable Runnable mDropOp;
749
750 private Operation(@Nullable Runnable setupOp, @NonNull ExecuteOp executeOp,
751 @Nullable Runnable cancelOp) {
752 mSetupOp = setupOp;
753 mExecuteOp = executeOp;
754 mDropOp = cancelOp;
755 }
756
757 private void setup() {
758 if (mSetupOp != null) {
759 mSetupOp.run();
760 }
761 }
762
763 void run(int opId, @NonNull ISoundTriggerDetectionService service) throws RemoteException {
764 setup();
765 mExecuteOp.run(opId, service);
766 }
767
768 void drop() {
769 setup();
770
771 if (mDropOp != null) {
772 mDropOp.run();
773 }
774 }
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800775 }
776
777 /**
778 * Local end for a {@link SoundTriggerDetectionService}. Operations are queued up and executed
779 * when the service connects.
780 *
781 * <p>If operations take too long they are forcefully aborted.
782 *
783 * <p>This also limits the amount of operations in 24 hours.
784 */
785 private class RemoteSoundTriggerDetectionService
786 extends IRecognitionStatusCallback.Stub implements ServiceConnection {
787 private static final int MSG_STOP_ALL_PENDING_OPERATIONS = 1;
788
789 private final Object mRemoteServiceLock = new Object();
790
791 /** UUID of the model the service is started for */
792 private final @NonNull ParcelUuid mPuuid;
793 /** Params passed into the start method for the service */
794 private final @Nullable Bundle mParams;
795 /** Component name passed when starting the service */
796 private final @NonNull ComponentName mServiceName;
797 /** User that started the service */
798 private final @NonNull UserHandle mUser;
799 /** Configuration of the recognition the service is handling */
800 private final @NonNull RecognitionConfig mRecognitionConfig;
801 /** Wake lock keeping the remote service alive */
802 private final @NonNull PowerManager.WakeLock mRemoteServiceWakeLock;
803
804 private final @NonNull Handler mHandler;
805
806 /** Callbacks that are called by the service */
807 private final @NonNull ISoundTriggerDetectionServiceClient mClient;
808
809 /** Operations that are pending because the service is not yet connected */
810 @GuardedBy("mRemoteServiceLock")
811 private final ArrayList<Operation> mPendingOps = new ArrayList<>();
812 /** Operations that have been send to the service but have no yet finished */
813 @GuardedBy("mRemoteServiceLock")
814 private final ArraySet<Integer> mRunningOpIds = new ArraySet<>();
Philip P. Moltmann7e25b3d2018-03-09 20:22:58 -0800815 /** The number of operations executed in each of the last 24 hours */
816 private final NumOps mNumOps;
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800817
818 /** The service binder if connected */
819 @GuardedBy("mRemoteServiceLock")
820 private @Nullable ISoundTriggerDetectionService mService;
821 /** Whether the service has been bound */
822 @GuardedBy("mRemoteServiceLock")
823 private boolean mIsBound;
824 /** Whether the service has been destroyed */
825 @GuardedBy("mRemoteServiceLock")
826 private boolean mIsDestroyed;
827 /**
828 * Set once a final op is scheduled. No further ops can be added and the service is
829 * destroyed once the op finishes.
830 */
831 @GuardedBy("mRemoteServiceLock")
832 private boolean mDestroyOnceRunningOpsDone;
833
834 /** Total number of operations performed by this service */
835 @GuardedBy("mRemoteServiceLock")
836 private int mNumTotalOpsPerformed;
837
838 /**
839 * Create a new remote sound trigger detection service. This only binds to the service when
840 * operations are in flight. Each operation has a certain time it can run. Once no
841 * operations are allowed to run anymore, {@link #stopAllPendingOperations() all operations
842 * are aborted and stopped} and the service is disconnected.
843 *
844 * @param modelUuid The UUID of the model the recognition is for
845 * @param params The params passed to each method of the service
846 * @param serviceName The component name of the service
847 * @param user The user of the service
848 * @param config The configuration of the recognition
849 */
850 public RemoteSoundTriggerDetectionService(@NonNull UUID modelUuid,
851 @Nullable Bundle params, @NonNull ComponentName serviceName, @NonNull UserHandle user,
852 @NonNull RecognitionConfig config) {
853 mPuuid = new ParcelUuid(modelUuid);
854 mParams = params;
855 mServiceName = serviceName;
856 mUser = user;
857 mRecognitionConfig = config;
858 mHandler = new Handler(Looper.getMainLooper());
859
860 PowerManager pm = ((PowerManager) mContext.getSystemService(Context.POWER_SERVICE));
861 mRemoteServiceWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
862 "RemoteSoundTriggerDetectionService " + mServiceName.getPackageName() + ":"
863 + mServiceName.getClassName());
864
Philip P. Moltmann7e25b3d2018-03-09 20:22:58 -0800865 synchronized (mLock) {
866 NumOps numOps = mNumOpsPerPackage.get(mServiceName.getPackageName());
867 if (numOps == null) {
868 numOps = new NumOps();
869 mNumOpsPerPackage.put(mServiceName.getPackageName(), numOps);
870 }
871 mNumOps = numOps;
872 }
873
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800874 mClient = new ISoundTriggerDetectionServiceClient.Stub() {
875 @Override
876 public void onOpFinished(int opId) {
877 long token = Binder.clearCallingIdentity();
878 try {
879 synchronized (mRemoteServiceLock) {
880 mRunningOpIds.remove(opId);
881
882 if (mRunningOpIds.isEmpty() && mPendingOps.isEmpty()) {
883 if (mDestroyOnceRunningOpsDone) {
884 destroy();
885 } else {
886 disconnectLocked();
887 }
888 }
889 }
890 } finally {
891 Binder.restoreCallingIdentity(token);
892 }
893 }
894 };
895 }
896
897 @Override
898 public boolean pingBinder() {
899 return !(mIsDestroyed || mDestroyOnceRunningOpsDone);
900 }
901
902 /**
903 * Disconnect from the service, but allow to re-connect when new operations are triggered.
904 */
Andreas Gampe8ce7ed92018-09-05 16:53:00 -0700905 @GuardedBy("mRemoteServiceLock")
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800906 private void disconnectLocked() {
907 if (mService != null) {
908 try {
909 mService.removeClient(mPuuid);
910 } catch (Exception e) {
911 Slog.e(TAG, mPuuid + ": Cannot remove client", e);
Jason Hsu1363f582019-04-16 15:35:55 +0800912
913 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
914 + ": Cannot remove client"));
915
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800916 }
917
918 mService = null;
919 }
920
921 if (mIsBound) {
922 mContext.unbindService(RemoteSoundTriggerDetectionService.this);
923 mIsBound = false;
924
925 synchronized (mCallbacksLock) {
926 mRemoteServiceWakeLock.release();
927 }
928 }
929 }
930
931 /**
932 * Disconnect, do not allow to reconnect to the service. All further operations will be
933 * dropped.
934 */
935 private void destroy() {
936 if (DEBUG) Slog.v(TAG, mPuuid + ": destroy");
937
Jason Hsu1363f582019-04-16 15:35:55 +0800938 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid + ": destroy"));
939
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800940 synchronized (mRemoteServiceLock) {
941 disconnectLocked();
942
943 mIsDestroyed = true;
944 }
945
946 // The callback is removed before the flag is set
947 if (!mDestroyOnceRunningOpsDone) {
948 synchronized (mCallbacksLock) {
949 mCallbacks.remove(mPuuid.getUuid());
950 }
951 }
952 }
953
954 /**
955 * Stop all pending operations and then disconnect for the service.
956 */
957 private void stopAllPendingOperations() {
958 synchronized (mRemoteServiceLock) {
959 if (mIsDestroyed) {
960 return;
961 }
962
963 if (mService != null) {
964 int numOps = mRunningOpIds.size();
965 for (int i = 0; i < numOps; i++) {
966 try {
967 mService.onStopOperation(mPuuid, mRunningOpIds.valueAt(i));
968 } catch (Exception e) {
969 Slog.e(TAG, mPuuid + ": Could not stop operation "
970 + mRunningOpIds.valueAt(i), e);
Jason Hsu1363f582019-04-16 15:35:55 +0800971
972 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
973 + ": Could not stop operation " + mRunningOpIds.valueAt(i)));
974
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800975 }
976 }
977
978 mRunningOpIds.clear();
979 }
980
981 disconnectLocked();
982 }
983 }
984
985 /**
986 * Verify that the service has the expected properties and then bind to the service
987 */
988 private void bind() {
989 long token = Binder.clearCallingIdentity();
990 try {
991 Intent i = new Intent();
992 i.setComponent(mServiceName);
993
994 ResolveInfo ri = mContext.getPackageManager().resolveServiceAsUser(i,
995 GET_SERVICES | GET_META_DATA | MATCH_DEBUG_TRIAGED_MISSING,
996 mUser.getIdentifier());
997
998 if (ri == null) {
999 Slog.w(TAG, mPuuid + ": " + mServiceName + " not found");
Jason Hsu1363f582019-04-16 15:35:55 +08001000
1001 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
1002 + ": " + mServiceName + " not found"));
1003
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -08001004 return;
1005 }
1006
1007 if (!BIND_SOUND_TRIGGER_DETECTION_SERVICE
1008 .equals(ri.serviceInfo.permission)) {
1009 Slog.w(TAG, mPuuid + ": " + mServiceName + " does not require "
1010 + BIND_SOUND_TRIGGER_DETECTION_SERVICE);
Jason Hsu1363f582019-04-16 15:35:55 +08001011
1012 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
1013 + ": " + mServiceName + " does not require "
1014 + BIND_SOUND_TRIGGER_DETECTION_SERVICE));
1015
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -08001016 return;
1017 }
1018
1019 mIsBound = mContext.bindServiceAsUser(i, this,
1020 BIND_AUTO_CREATE | BIND_FOREGROUND_SERVICE, mUser);
1021
1022 if (mIsBound) {
1023 mRemoteServiceWakeLock.acquire();
1024 } else {
1025 Slog.w(TAG, mPuuid + ": Could not bind to " + mServiceName);
Jason Hsu1363f582019-04-16 15:35:55 +08001026
1027 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
1028 + ": Could not bind to " + mServiceName));
1029
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -08001030 }
1031 } finally {
1032 Binder.restoreCallingIdentity(token);
1033 }
1034 }
1035
1036 /**
1037 * Run an operation (i.e. send it do the service). If the service is not connected, this
1038 * binds the service and then runs the operation once connected.
1039 *
1040 * @param op The operation to run
1041 */
1042 private void runOrAddOperation(Operation op) {
1043 synchronized (mRemoteServiceLock) {
1044 if (mIsDestroyed || mDestroyOnceRunningOpsDone) {
Philip P. Moltmanna5b44032018-05-04 13:59:45 -07001045 Slog.w(TAG, mPuuid + ": Dropped operation as already destroyed or marked for "
1046 + "destruction");
1047
Jason Hsu1363f582019-04-16 15:35:55 +08001048 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
1049 + ":Dropped operation as already destroyed or marked for destruction"));
1050
Philip P. Moltmanna5b44032018-05-04 13:59:45 -07001051 op.drop();
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -08001052 return;
1053 }
1054
1055 if (mService == null) {
1056 mPendingOps.add(op);
1057
1058 if (!mIsBound) {
1059 bind();
1060 }
1061 } else {
Philip P. Moltmann7e25b3d2018-03-09 20:22:58 -08001062 long currentTime = System.nanoTime();
1063 mNumOps.clearOldOps(currentTime);
1064
1065 // Drop operation if too many were executed in the last 24 hours.
1066 int opsAllowed = Settings.Global.getInt(mContext.getContentResolver(),
1067 MAX_SOUND_TRIGGER_DETECTION_SERVICE_OPS_PER_DAY,
1068 Integer.MAX_VALUE);
1069
Philip P. Moltmann3d1683b2018-05-07 15:53:51 -07001070 // As we currently cannot dropping an op safely, disable throttling
Philip P. Moltmann7e25b3d2018-03-09 20:22:58 -08001071 int opsAdded = mNumOps.getOpsAdded();
Philip P. Moltmann3d1683b2018-05-07 15:53:51 -07001072 if (false && mNumOps.getOpsAdded() >= opsAllowed) {
Philip P. Moltmanna5b44032018-05-04 13:59:45 -07001073 try {
1074 if (DEBUG || opsAllowed + 10 > opsAdded) {
1075 Slog.w(TAG, mPuuid + ": Dropped operation as too many operations "
1076 + "were run in last 24 hours");
Jason Hsu1363f582019-04-16 15:35:55 +08001077
1078 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
1079 + ": Dropped operation as too many operations "
1080 + "were run in last 24 hours"));
1081
Philip P. Moltmanna5b44032018-05-04 13:59:45 -07001082 }
1083
1084 op.drop();
1085 } catch (Exception e) {
1086 Slog.e(TAG, mPuuid + ": Could not drop operation", e);
Jason Hsu1363f582019-04-16 15:35:55 +08001087
1088 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
1089 + ": Could not drop operation"));
1090
Philip P. Moltmann7e25b3d2018-03-09 20:22:58 -08001091 }
Philip P. Moltmann09dd8c42018-04-11 09:23:19 -07001092 } else {
1093 mNumOps.addOp(currentTime);
Philip P. Moltmann7e25b3d2018-03-09 20:22:58 -08001094
Philip P. Moltmann09dd8c42018-04-11 09:23:19 -07001095 // Find a free opID
1096 int opId = mNumTotalOpsPerformed;
1097 do {
1098 mNumTotalOpsPerformed++;
1099 } while (mRunningOpIds.contains(opId));
Philip P. Moltmann7e25b3d2018-03-09 20:22:58 -08001100
Philip P. Moltmann09dd8c42018-04-11 09:23:19 -07001101 // Run OP
1102 try {
1103 if (DEBUG) Slog.v(TAG, mPuuid + ": runOp " + opId);
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -08001104
Jason Hsu1363f582019-04-16 15:35:55 +08001105 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
1106 + ": runOp " + opId));
1107
Philip P. Moltmann09dd8c42018-04-11 09:23:19 -07001108 op.run(opId, mService);
1109 mRunningOpIds.add(opId);
1110 } catch (Exception e) {
1111 Slog.e(TAG, mPuuid + ": Could not run operation " + opId, e);
Jason Hsu1363f582019-04-16 15:35:55 +08001112
1113 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
1114 + ": Could not run operation " + opId));
1115
Philip P. Moltmann09dd8c42018-04-11 09:23:19 -07001116 }
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -08001117 }
1118
1119 // Unbind from service if no operations are left (i.e. if the operation failed)
1120 if (mPendingOps.isEmpty() && mRunningOpIds.isEmpty()) {
1121 if (mDestroyOnceRunningOpsDone) {
1122 destroy();
1123 } else {
1124 disconnectLocked();
1125 }
1126 } else {
1127 mHandler.removeMessages(MSG_STOP_ALL_PENDING_OPERATIONS);
1128 mHandler.sendMessageDelayed(obtainMessage(
1129 RemoteSoundTriggerDetectionService::stopAllPendingOperations, this)
1130 .setWhat(MSG_STOP_ALL_PENDING_OPERATIONS),
1131 Settings.Global.getLong(mContext.getContentResolver(),
1132 SOUND_TRIGGER_DETECTION_SERVICE_OP_TIMEOUT,
1133 Long.MAX_VALUE));
1134 }
1135 }
1136 }
1137 }
1138
1139 @Override
1140 public void onKeyphraseDetected(SoundTrigger.KeyphraseRecognitionEvent event) {
1141 Slog.w(TAG, mPuuid + "->" + mServiceName + ": IGNORED onKeyphraseDetected(" + event
1142 + ")");
Jason Hsu1363f582019-04-16 15:35:55 +08001143
1144 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid + "->" + mServiceName
1145 + ": IGNORED onKeyphraseDetected(" + event + ")"));
1146
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -08001147 }
1148
Philip P. Moltmanna5b44032018-05-04 13:59:45 -07001149 /**
1150 * Create an AudioRecord enough for starting and releasing the data buffered for the event.
1151 *
1152 * @param event The event that was received
1153 * @return The initialized AudioRecord
1154 */
1155 private @NonNull AudioRecord createAudioRecordForEvent(
1156 @NonNull SoundTrigger.GenericRecognitionEvent event) {
1157 AudioAttributes.Builder attributesBuilder = new AudioAttributes.Builder();
1158 attributesBuilder.setInternalCapturePreset(MediaRecorder.AudioSource.HOTWORD);
1159 AudioAttributes attributes = attributesBuilder.build();
1160
1161 // Use same AudioFormat processing as in RecognitionEvent.fromParcel
1162 AudioFormat originalFormat = event.getCaptureFormat();
1163 AudioFormat captureFormat = (new AudioFormat.Builder())
1164 .setChannelMask(originalFormat.getChannelMask())
1165 .setEncoding(originalFormat.getEncoding())
1166 .setSampleRate(originalFormat.getSampleRate())
1167 .build();
1168
1169 int bufferSize = AudioRecord.getMinBufferSize(
1170 captureFormat.getSampleRate() == AudioFormat.SAMPLE_RATE_UNSPECIFIED
1171 ? AudioFormat.SAMPLE_RATE_HZ_MAX
1172 : captureFormat.getSampleRate(),
1173 captureFormat.getChannelCount() == 2
1174 ? AudioFormat.CHANNEL_IN_STEREO
1175 : AudioFormat.CHANNEL_IN_MONO,
1176 captureFormat.getEncoding());
1177
Jason Hsu1363f582019-04-16 15:35:55 +08001178 sEventLogger.log(new SoundTriggerLogger.StringEvent("createAudioRecordForEvent"));
1179
Philip P. Moltmanna5b44032018-05-04 13:59:45 -07001180 return new AudioRecord(attributes, captureFormat, bufferSize,
1181 event.getCaptureSession());
1182 }
1183
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -08001184 @Override
1185 public void onGenericSoundTriggerDetected(SoundTrigger.GenericRecognitionEvent event) {
1186 if (DEBUG) Slog.v(TAG, mPuuid + ": Generic sound trigger event: " + event);
1187
Jason Hsu1363f582019-04-16 15:35:55 +08001188 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
1189 + ": Generic sound trigger event: " + event));
1190
Philip P. Moltmanna5b44032018-05-04 13:59:45 -07001191 runOrAddOperation(new Operation(
1192 // always execute:
1193 () -> {
mike dooley9b20c1c2019-01-23 10:38:17 +01001194 if (!mRecognitionConfig.allowMultipleTriggers) {
Philip P. Moltmanna5b44032018-05-04 13:59:45 -07001195 // Unregister this remoteService once op is done
1196 synchronized (mCallbacksLock) {
1197 mCallbacks.remove(mPuuid.getUuid());
1198 }
1199 mDestroyOnceRunningOpsDone = true;
1200 }
1201 },
1202 // execute if not throttled:
1203 (opId, service) -> service.onGenericRecognitionEvent(mPuuid, opId, event),
1204 // execute if throttled:
1205 () -> {
1206 if (event.isCaptureAvailable()) {
1207 AudioRecord capturedData = createAudioRecordForEvent(event);
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -08001208
Philip P. Moltmanna5b44032018-05-04 13:59:45 -07001209 // Currently we need to start and release the audio record to reset
1210 // the DSP even if we don't want to process the event
1211 capturedData.startRecording();
1212 capturedData.release();
1213 }
1214 }));
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -08001215 }
1216
1217 @Override
1218 public void onError(int status) {
1219 if (DEBUG) Slog.v(TAG, mPuuid + ": onError: " + status);
1220
Jason Hsu1363f582019-04-16 15:35:55 +08001221 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
1222 + ": onError: " + status));
1223
Philip P. Moltmanna5b44032018-05-04 13:59:45 -07001224 runOrAddOperation(
1225 new Operation(
1226 // always execute:
1227 () -> {
1228 // Unregister this remoteService once op is done
1229 synchronized (mCallbacksLock) {
1230 mCallbacks.remove(mPuuid.getUuid());
1231 }
1232 mDestroyOnceRunningOpsDone = true;
1233 },
1234 // execute if not throttled:
1235 (opId, service) -> service.onError(mPuuid, opId, status),
1236 // nothing to do if throttled
1237 null));
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -08001238 }
1239
1240 @Override
1241 public void onRecognitionPaused() {
1242 Slog.i(TAG, mPuuid + "->" + mServiceName + ": IGNORED onRecognitionPaused");
Jason Hsu1363f582019-04-16 15:35:55 +08001243
1244 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
1245 + "->" + mServiceName + ": IGNORED onRecognitionPaused"));
1246
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -08001247 }
1248
1249 @Override
1250 public void onRecognitionResumed() {
1251 Slog.i(TAG, mPuuid + "->" + mServiceName + ": IGNORED onRecognitionResumed");
Jason Hsu1363f582019-04-16 15:35:55 +08001252
1253 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
1254 + "->" + mServiceName + ": IGNORED onRecognitionResumed"));
1255
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -08001256 }
1257
1258 @Override
1259 public void onServiceConnected(ComponentName name, IBinder service) {
1260 if (DEBUG) Slog.v(TAG, mPuuid + ": onServiceConnected(" + service + ")");
1261
Jason Hsu1363f582019-04-16 15:35:55 +08001262 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
1263 + ": onServiceConnected(" + service + ")"));
1264
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -08001265 synchronized (mRemoteServiceLock) {
1266 mService = ISoundTriggerDetectionService.Stub.asInterface(service);
1267
1268 try {
1269 mService.setClient(mPuuid, mParams, mClient);
1270 } catch (Exception e) {
1271 Slog.e(TAG, mPuuid + ": Could not init " + mServiceName, e);
1272 return;
1273 }
1274
1275 while (!mPendingOps.isEmpty()) {
1276 runOrAddOperation(mPendingOps.remove(0));
1277 }
1278 }
1279 }
1280
1281 @Override
1282 public void onServiceDisconnected(ComponentName name) {
1283 if (DEBUG) Slog.v(TAG, mPuuid + ": onServiceDisconnected");
1284
Jason Hsu1363f582019-04-16 15:35:55 +08001285 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
1286 + ": onServiceDisconnected"));
1287
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -08001288 synchronized (mRemoteServiceLock) {
1289 mService = null;
1290 }
1291 }
1292
1293 @Override
1294 public void onBindingDied(ComponentName name) {
1295 if (DEBUG) Slog.v(TAG, mPuuid + ": onBindingDied");
1296
Jason Hsu1363f582019-04-16 15:35:55 +08001297 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
1298 + ": onBindingDied"));
1299
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -08001300 synchronized (mRemoteServiceLock) {
1301 destroy();
1302 }
1303 }
1304
1305 @Override
1306 public void onNullBinding(ComponentName name) {
1307 Slog.w(TAG, name + " for model " + mPuuid + " returned a null binding");
1308
Jason Hsu1363f582019-04-16 15:35:55 +08001309 sEventLogger.log(new SoundTriggerLogger.StringEvent(name + " for model "
1310 + mPuuid + " returned a null binding"));
1311
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -08001312 synchronized (mRemoteServiceLock) {
1313 disconnectLocked();
1314 }
1315 }
1316 }
1317
Arunesh Mishraa772e5f2016-01-25 10:33:11 -08001318 public final class LocalSoundTriggerService extends SoundTriggerInternal {
1319 private final Context mContext;
1320 private SoundTriggerHelper mSoundTriggerHelper;
1321
1322 LocalSoundTriggerService(Context context) {
1323 mContext = context;
1324 }
1325
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001326 synchronized void setSoundTriggerHelper(SoundTriggerHelper helper) {
1327 mSoundTriggerHelper = helper;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -08001328 }
1329
1330 @Override
1331 public int startRecognition(int keyphraseId, KeyphraseSoundModel soundModel,
1332 IRecognitionStatusCallback listener, RecognitionConfig recognitionConfig) {
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001333 if (!isInitialized()) return STATUS_ERROR;
1334 return mSoundTriggerHelper.startKeyphraseRecognition(keyphraseId, soundModel, listener,
Arunesh Mishraa772e5f2016-01-25 10:33:11 -08001335 recognitionConfig);
1336 }
1337
1338 @Override
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001339 public synchronized int stopRecognition(int keyphraseId, IRecognitionStatusCallback listener) {
1340 if (!isInitialized()) return STATUS_ERROR;
1341 return mSoundTriggerHelper.stopKeyphraseRecognition(keyphraseId, listener);
Arunesh Mishraa772e5f2016-01-25 10:33:11 -08001342 }
1343
1344 @Override
Arunesh Mishra55a9b002016-02-01 14:06:37 -08001345 public ModuleProperties getModuleProperties() {
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001346 if (!isInitialized()) return null;
Arunesh Mishra55a9b002016-02-01 14:06:37 -08001347 return mSoundTriggerHelper.getModuleProperties();
1348 }
1349
1350 @Override
Arunesh Mishra2d1de782016-02-21 18:10:28 -08001351 public int unloadKeyphraseModel(int keyphraseId) {
1352 if (!isInitialized()) return STATUS_ERROR;
1353 return mSoundTriggerHelper.unloadKeyphraseSoundModel(keyphraseId);
1354 }
1355
1356 @Override
Arunesh Mishraa772e5f2016-01-25 10:33:11 -08001357 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001358 if (!isInitialized()) return;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -08001359 mSoundTriggerHelper.dump(fd, pw, args);
Jason Hsu1363f582019-04-16 15:35:55 +08001360 // log
1361 sEventLogger.dump(pw);
Benjamin Schwartz9e7a0152019-06-06 17:39:22 -07001362
1363 // stats
1364 mSoundModelStatTracker.dump(pw);
Arunesh Mishraa772e5f2016-01-25 10:33:11 -08001365 }
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001366
1367 private synchronized boolean isInitialized() {
1368 if (mSoundTriggerHelper == null ) {
1369 Slog.e(TAG, "SoundTriggerHelper not initialized.");
Jason Hsu1363f582019-04-16 15:35:55 +08001370
1371 sEventLogger.log(new SoundTriggerLogger.StringEvent(
1372 "SoundTriggerHelper not initialized."));
1373
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001374 return false;
1375 }
1376 return true;
1377 }
Arunesh Mishraa772e5f2016-01-25 10:33:11 -08001378 }
1379
1380 private void enforceCallingPermission(String permission) {
1381 if (mContext.checkCallingOrSelfPermission(permission)
1382 != PackageManager.PERMISSION_GRANTED) {
1383 throw new SecurityException("Caller does not hold the permission " + permission);
1384 }
1385 }
Jason Hsu1363f582019-04-16 15:35:55 +08001386
1387 //=================================================================
1388 // For logging
1389
1390 private static final SoundTriggerLogger sEventLogger = new SoundTriggerLogger(200,
1391 "SoundTrigger activity");
1392
Arunesh Mishraa772e5f2016-01-25 10:33:11 -08001393}