blob: 9c4c0998b21a30078fdbdf52a1ea057f970137c3 [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;
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -080064import android.os.UserHandle;
65import android.provider.Settings;
Philip P. Moltmann7e25b3d2018-03-09 20:22:58 -080066import android.util.ArrayMap;
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -080067import android.util.ArraySet;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -080068import android.util.Slog;
69
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -080070import com.android.internal.annotations.GuardedBy;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -080071import com.android.internal.app.ISoundTriggerService;
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -080072import com.android.internal.util.Preconditions;
73import com.android.server.SystemService;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -080074
75import java.io.FileDescriptor;
76import java.io.PrintWriter;
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -080077import java.util.ArrayList;
Chris Thorntonba08b792017-06-08 22:34:37 -070078import java.util.TreeMap;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -080079import java.util.UUID;
Philip P. Moltmann7e25b3d2018-03-09 20:22:58 -080080import java.util.concurrent.TimeUnit;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -080081
82/**
83 * A single SystemService to manage all sound/voice-based sound models on the DSP.
84 * This services provides apis to manage sound trigger-based sound models via
85 * the ISoundTriggerService interface. This class also publishes a local interface encapsulating
86 * the functionality provided by {@link SoundTriggerHelper} for use by
87 * {@link VoiceInteractionManagerService}.
88 *
89 * @hide
90 */
91public class SoundTriggerService extends SystemService {
Arunesh Mishra3fff7f52016-02-09 12:15:19 -080092 private static final String TAG = "SoundTriggerService";
93 private static final boolean DEBUG = true;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -080094
95 final Context mContext;
Chris Thorntonba08b792017-06-08 22:34:37 -070096 private Object mLock;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -080097 private final SoundTriggerServiceStub mServiceStub;
98 private final LocalSoundTriggerService mLocalSoundTriggerService;
99 private SoundTriggerDbHelper mDbHelper;
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800100 private SoundTriggerHelper mSoundTriggerHelper;
Chris Thorntonba08b792017-06-08 22:34:37 -0700101 private final TreeMap<UUID, SoundModel> mLoadedModels;
Chris Thorntonae5fb992017-12-07 18:26:31 -0800102 private Object mCallbacksLock;
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800103 private final TreeMap<UUID, IRecognitionStatusCallback> mCallbacks;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800104
Philip P. Moltmann7e25b3d2018-03-09 20:22:58 -0800105 /** Number of ops run by the {@link RemoteSoundTriggerDetectionService} per package name */
106 @GuardedBy("mLock")
107 private final ArrayMap<String, NumOps> mNumOpsPerPackage = new ArrayMap<>();
108
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800109 public SoundTriggerService(Context context) {
110 super(context);
111 mContext = context;
112 mServiceStub = new SoundTriggerServiceStub();
113 mLocalSoundTriggerService = new LocalSoundTriggerService(context);
Chris Thorntonba08b792017-06-08 22:34:37 -0700114 mLoadedModels = new TreeMap<UUID, SoundModel>();
Chris Thorntonae5fb992017-12-07 18:26:31 -0800115 mCallbacksLock = new Object();
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800116 mCallbacks = new TreeMap<>();
Chris Thorntonba08b792017-06-08 22:34:37 -0700117 mLock = new Object();
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800118 }
119
120 @Override
121 public void onStart() {
122 publishBinderService(Context.SOUND_TRIGGER_SERVICE, mServiceStub);
123 publishLocalService(SoundTriggerInternal.class, mLocalSoundTriggerService);
124 }
125
126 @Override
127 public void onBootPhase(int phase) {
128 if (PHASE_SYSTEM_SERVICES_READY == phase) {
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800129 initSoundTriggerHelper();
130 mLocalSoundTriggerService.setSoundTriggerHelper(mSoundTriggerHelper);
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800131 } else if (PHASE_THIRD_PARTY_APPS_CAN_START == phase) {
132 mDbHelper = new SoundTriggerDbHelper(mContext);
133 }
134 }
135
136 @Override
137 public void onStartUser(int userHandle) {
138 }
139
140 @Override
141 public void onSwitchUser(int userHandle) {
142 }
143
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800144 private synchronized void initSoundTriggerHelper() {
145 if (mSoundTriggerHelper == null) {
146 mSoundTriggerHelper = new SoundTriggerHelper(mContext);
147 }
148 }
149
150 private synchronized boolean isInitialized() {
151 if (mSoundTriggerHelper == null ) {
152 Slog.e(TAG, "SoundTriggerHelper not initialized.");
153 return false;
154 }
155 return true;
156 }
157
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800158 class SoundTriggerServiceStub extends ISoundTriggerService.Stub {
159 @Override
160 public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
161 throws RemoteException {
162 try {
163 return super.onTransact(code, data, reply, flags);
164 } catch (RuntimeException e) {
165 // The activity manager only throws security exceptions, so let's
166 // log all others.
167 if (!(e instanceof SecurityException)) {
168 Slog.wtf(TAG, "SoundTriggerService Crash", e);
169 }
170 throw e;
171 }
172 }
173
174 @Override
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800175 public int startRecognition(ParcelUuid parcelUuid, IRecognitionStatusCallback callback,
176 RecognitionConfig config) {
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800177 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
Arunesh Mishra2d1de782016-02-21 18:10:28 -0800178 if (!isInitialized()) return STATUS_ERROR;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800179 if (DEBUG) {
180 Slog.i(TAG, "startRecognition(): Uuid : " + parcelUuid);
181 }
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800182
Jason Hsu1363f582019-04-16 15:35:55 +0800183 sEventLogger.log(new SoundTriggerLogger.StringEvent("startRecognition(): Uuid : "
184 + parcelUuid));
185
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800186 GenericSoundModel model = getSoundModel(parcelUuid);
187 if (model == null) {
188 Slog.e(TAG, "Null model in database for id: " + parcelUuid);
Jason Hsu1363f582019-04-16 15:35:55 +0800189
190 sEventLogger.log(new SoundTriggerLogger.StringEvent(
191 "startRecognition(): Null model in database for id: " + parcelUuid));
192
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800193 return STATUS_ERROR;
194 }
195
196 return mSoundTriggerHelper.startGenericRecognition(parcelUuid.getUuid(), model,
197 callback, config);
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800198 }
199
200 @Override
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800201 public int stopRecognition(ParcelUuid parcelUuid, IRecognitionStatusCallback callback) {
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800202 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
203 if (DEBUG) {
204 Slog.i(TAG, "stopRecognition(): Uuid : " + parcelUuid);
205 }
Jason Hsu1363f582019-04-16 15:35:55 +0800206
207 sEventLogger.log(new SoundTriggerLogger.StringEvent("stopRecognition(): Uuid : "
208 + parcelUuid));
209
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800210 if (!isInitialized()) return STATUS_ERROR;
211 return mSoundTriggerHelper.stopGenericRecognition(parcelUuid.getUuid(), callback);
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800212 }
213
214 @Override
Arunesh Mishrac722ec412016-01-27 13:29:12 -0800215 public SoundTrigger.GenericSoundModel getSoundModel(ParcelUuid soundModelId) {
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800216 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
217 if (DEBUG) {
218 Slog.i(TAG, "getSoundModel(): id = " + soundModelId);
219 }
Jason Hsu1363f582019-04-16 15:35:55 +0800220
221 sEventLogger.log(new SoundTriggerLogger.StringEvent("getSoundModel(): id = "
222 + soundModelId));
223
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800224 SoundTrigger.GenericSoundModel model = mDbHelper.getGenericSoundModel(
225 soundModelId.getUuid());
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800226 return model;
227 }
228
229 @Override
Arunesh Mishrac722ec412016-01-27 13:29:12 -0800230 public void updateSoundModel(SoundTrigger.GenericSoundModel soundModel) {
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800231 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
232 if (DEBUG) {
233 Slog.i(TAG, "updateSoundModel(): model = " + soundModel);
234 }
Jason Hsu1363f582019-04-16 15:35:55 +0800235
236 sEventLogger.log(new SoundTriggerLogger.StringEvent("updateSoundModel(): model = "
237 + soundModel));
238
Arunesh Mishrac722ec412016-01-27 13:29:12 -0800239 mDbHelper.updateGenericSoundModel(soundModel);
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800240 }
241
242 @Override
243 public void deleteSoundModel(ParcelUuid soundModelId) {
244 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
245 if (DEBUG) {
246 Slog.i(TAG, "deleteSoundModel(): id = " + soundModelId);
247 }
Jason Hsu1363f582019-04-16 15:35:55 +0800248
249 sEventLogger.log(new SoundTriggerLogger.StringEvent("deleteSoundModel(): id = "
250 + soundModelId));
251
Arunesh Mishra2d1de782016-02-21 18:10:28 -0800252 // Unload the model if it is loaded.
253 mSoundTriggerHelper.unloadGenericSoundModel(soundModelId.getUuid());
Arunesh Mishrac722ec412016-01-27 13:29:12 -0800254 mDbHelper.deleteGenericSoundModel(soundModelId.getUuid());
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800255 }
Chris Thorntonba08b792017-06-08 22:34:37 -0700256
257 @Override
258 public int loadGenericSoundModel(GenericSoundModel soundModel) {
259 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
260 if (!isInitialized()) return STATUS_ERROR;
261 if (soundModel == null || soundModel.uuid == null) {
262 Slog.e(TAG, "Invalid sound model");
Jason Hsu1363f582019-04-16 15:35:55 +0800263
264 sEventLogger.log(new SoundTriggerLogger.StringEvent(
265 "loadGenericSoundModel(): Invalid sound model"));
266
Chris Thorntonba08b792017-06-08 22:34:37 -0700267 return STATUS_ERROR;
268 }
269 if (DEBUG) {
270 Slog.i(TAG, "loadGenericSoundModel(): id = " + soundModel.uuid);
271 }
Jason Hsu1363f582019-04-16 15:35:55 +0800272
273 sEventLogger.log(new SoundTriggerLogger.StringEvent("loadGenericSoundModel(): id = "
274 + soundModel.uuid));
275
Chris Thorntonba08b792017-06-08 22:34:37 -0700276 synchronized (mLock) {
277 SoundModel oldModel = mLoadedModels.get(soundModel.uuid);
278 // If the model we're loading is actually different than what we had loaded, we
279 // should unload that other model now. We don't care about return codes since we
280 // don't know if the other model is loaded.
281 if (oldModel != null && !oldModel.equals(soundModel)) {
282 mSoundTriggerHelper.unloadGenericSoundModel(soundModel.uuid);
Chris Thorntonae5fb992017-12-07 18:26:31 -0800283 synchronized (mCallbacksLock) {
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800284 mCallbacks.remove(soundModel.uuid);
Chris Thorntonae5fb992017-12-07 18:26:31 -0800285 }
Chris Thorntonba08b792017-06-08 22:34:37 -0700286 }
287 mLoadedModels.put(soundModel.uuid, soundModel);
288 }
289 return STATUS_OK;
290 }
291
292 @Override
293 public int loadKeyphraseSoundModel(KeyphraseSoundModel soundModel) {
294 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
295 if (!isInitialized()) return STATUS_ERROR;
296 if (soundModel == null || soundModel.uuid == null) {
297 Slog.e(TAG, "Invalid sound model");
Jason Hsu1363f582019-04-16 15:35:55 +0800298
299 sEventLogger.log(new SoundTriggerLogger.StringEvent(
300 "loadKeyphraseSoundModel(): Invalid sound model"));
301
Chris Thorntonba08b792017-06-08 22:34:37 -0700302 return STATUS_ERROR;
303 }
304 if (soundModel.keyphrases == null || soundModel.keyphrases.length != 1) {
305 Slog.e(TAG, "Only one keyphrase per model is currently supported.");
Jason Hsu1363f582019-04-16 15:35:55 +0800306
307 sEventLogger.log(new SoundTriggerLogger.StringEvent(
308 "loadKeyphraseSoundModel(): Only one keyphrase per model"
309 + " is currently supported."));
310
Chris Thorntonba08b792017-06-08 22:34:37 -0700311 return STATUS_ERROR;
312 }
313 if (DEBUG) {
314 Slog.i(TAG, "loadKeyphraseSoundModel(): id = " + soundModel.uuid);
315 }
Jason Hsu1363f582019-04-16 15:35:55 +0800316
317 sEventLogger.log(new SoundTriggerLogger.StringEvent("loadKeyphraseSoundModel(): id = "
318 + soundModel.uuid));
319
Chris Thorntonba08b792017-06-08 22:34:37 -0700320 synchronized (mLock) {
321 SoundModel oldModel = mLoadedModels.get(soundModel.uuid);
322 // If the model we're loading is actually different than what we had loaded, we
323 // should unload that other model now. We don't care about return codes since we
324 // don't know if the other model is loaded.
325 if (oldModel != null && !oldModel.equals(soundModel)) {
326 mSoundTriggerHelper.unloadKeyphraseSoundModel(soundModel.keyphrases[0].id);
Chris Thorntonae5fb992017-12-07 18:26:31 -0800327 synchronized (mCallbacksLock) {
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800328 mCallbacks.remove(soundModel.uuid);
Chris Thorntonae5fb992017-12-07 18:26:31 -0800329 }
Chris Thorntonba08b792017-06-08 22:34:37 -0700330 }
331 mLoadedModels.put(soundModel.uuid, soundModel);
332 }
333 return STATUS_OK;
334 }
335
336 @Override
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800337 public int startRecognitionForService(ParcelUuid soundModelId, Bundle params,
338 ComponentName detectionService, SoundTrigger.RecognitionConfig config) {
339 Preconditions.checkNotNull(soundModelId);
340 Preconditions.checkNotNull(detectionService);
341 Preconditions.checkNotNull(config);
342
Chris Thorntonba08b792017-06-08 22:34:37 -0700343 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
Philip P. Moltmann19402f52018-03-27 14:38:03 -0700344
Chris Thorntonba08b792017-06-08 22:34:37 -0700345 if (!isInitialized()) return STATUS_ERROR;
346 if (DEBUG) {
347 Slog.i(TAG, "startRecognition(): id = " + soundModelId);
348 }
349
Jason Hsu1363f582019-04-16 15:35:55 +0800350 sEventLogger.log(new SoundTriggerLogger.StringEvent(
351 "startRecognitionForService(): id = " + soundModelId));
352
Philip P. Moltmann19402f52018-03-27 14:38:03 -0700353 IRecognitionStatusCallback callback =
354 new RemoteSoundTriggerDetectionService(soundModelId.getUuid(), params,
355 detectionService, Binder.getCallingUserHandle(), config);
356
Chris Thorntonba08b792017-06-08 22:34:37 -0700357 synchronized (mLock) {
358 SoundModel soundModel = mLoadedModels.get(soundModelId.getUuid());
359 if (soundModel == null) {
360 Slog.e(TAG, soundModelId + " is not loaded");
Jason Hsu1363f582019-04-16 15:35:55 +0800361
362 sEventLogger.log(new SoundTriggerLogger.StringEvent(
363 "startRecognitionForService():" + soundModelId + " is not loaded"));
364
Chris Thorntonba08b792017-06-08 22:34:37 -0700365 return STATUS_ERROR;
366 }
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800367 IRecognitionStatusCallback existingCallback = null;
Chris Thorntonae5fb992017-12-07 18:26:31 -0800368 synchronized (mCallbacksLock) {
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800369 existingCallback = mCallbacks.get(soundModelId.getUuid());
Chris Thorntonae5fb992017-12-07 18:26:31 -0800370 }
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800371 if (existingCallback != null) {
Chris Thorntonba08b792017-06-08 22:34:37 -0700372 Slog.e(TAG, soundModelId + " is already running");
Jason Hsu1363f582019-04-16 15:35:55 +0800373
374 sEventLogger.log(new SoundTriggerLogger.StringEvent(
375 "startRecognitionForService():"
376 + soundModelId + " is already running"));
377
Chris Thorntonba08b792017-06-08 22:34:37 -0700378 return STATUS_ERROR;
379 }
Chris Thorntonba08b792017-06-08 22:34:37 -0700380 int ret;
381 switch (soundModel.type) {
Chris Thorntonba08b792017-06-08 22:34:37 -0700382 case SoundModel.TYPE_GENERIC_SOUND:
383 ret = mSoundTriggerHelper.startGenericRecognition(soundModel.uuid,
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800384 (GenericSoundModel) soundModel, callback, config);
Chris Thorntonba08b792017-06-08 22:34:37 -0700385 break;
386 default:
387 Slog.e(TAG, "Unknown model type");
Jason Hsu1363f582019-04-16 15:35:55 +0800388
389 sEventLogger.log(new SoundTriggerLogger.StringEvent(
390 "startRecognitionForService(): Unknown model type"));
391
Chris Thorntonba08b792017-06-08 22:34:37 -0700392 return STATUS_ERROR;
393 }
394
395 if (ret != STATUS_OK) {
396 Slog.e(TAG, "Failed to start model: " + ret);
Jason Hsu1363f582019-04-16 15:35:55 +0800397
398 sEventLogger.log(new SoundTriggerLogger.StringEvent(
399 "startRecognitionForService(): Failed to start model:"));
400
Chris Thorntonba08b792017-06-08 22:34:37 -0700401 return ret;
402 }
Chris Thorntonae5fb992017-12-07 18:26:31 -0800403 synchronized (mCallbacksLock) {
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800404 mCallbacks.put(soundModelId.getUuid(), callback);
Chris Thorntonae5fb992017-12-07 18:26:31 -0800405 }
Chris Thorntonba08b792017-06-08 22:34:37 -0700406 }
407 return STATUS_OK;
408 }
409
410 @Override
Philip P. Moltmann19402f52018-03-27 14:38:03 -0700411 public int stopRecognitionForService(ParcelUuid soundModelId) {
Chris Thorntonba08b792017-06-08 22:34:37 -0700412 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
413 if (!isInitialized()) return STATUS_ERROR;
414 if (DEBUG) {
415 Slog.i(TAG, "stopRecognition(): id = " + soundModelId);
416 }
417
Jason Hsu1363f582019-04-16 15:35:55 +0800418 sEventLogger.log(new SoundTriggerLogger.StringEvent(
419 "stopRecognitionForService(): id = " + soundModelId));
420
Chris Thorntonba08b792017-06-08 22:34:37 -0700421 synchronized (mLock) {
422 SoundModel soundModel = mLoadedModels.get(soundModelId.getUuid());
423 if (soundModel == null) {
424 Slog.e(TAG, soundModelId + " is not loaded");
Jason Hsu1363f582019-04-16 15:35:55 +0800425
426 sEventLogger.log(new SoundTriggerLogger.StringEvent(
427 "stopRecognitionForService(): " + soundModelId
428 + " is not loaded"));
429
Chris Thorntonba08b792017-06-08 22:34:37 -0700430 return STATUS_ERROR;
431 }
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800432 IRecognitionStatusCallback callback = null;
Chris Thorntonae5fb992017-12-07 18:26:31 -0800433 synchronized (mCallbacksLock) {
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800434 callback = mCallbacks.get(soundModelId.getUuid());
Chris Thorntonae5fb992017-12-07 18:26:31 -0800435 }
Chris Thorntonba08b792017-06-08 22:34:37 -0700436 if (callback == null) {
437 Slog.e(TAG, soundModelId + " is not running");
Jason Hsu1363f582019-04-16 15:35:55 +0800438
439 sEventLogger.log(new SoundTriggerLogger.StringEvent(
440 "stopRecognitionForService(): " + soundModelId
441 + " is not running"));
442
Chris Thorntonba08b792017-06-08 22:34:37 -0700443 return STATUS_ERROR;
444 }
445 int ret;
446 switch (soundModel.type) {
Chris Thorntonba08b792017-06-08 22:34:37 -0700447 case SoundModel.TYPE_GENERIC_SOUND:
448 ret = mSoundTriggerHelper.stopGenericRecognition(soundModel.uuid, callback);
449 break;
450 default:
451 Slog.e(TAG, "Unknown model type");
Jason Hsu1363f582019-04-16 15:35:55 +0800452
453 sEventLogger.log(new SoundTriggerLogger.StringEvent(
454 "stopRecognitionForService(): Unknown model type"));
455
Chris Thorntonba08b792017-06-08 22:34:37 -0700456 return STATUS_ERROR;
457 }
458
459 if (ret != STATUS_OK) {
460 Slog.e(TAG, "Failed to stop model: " + ret);
Jason Hsu1363f582019-04-16 15:35:55 +0800461
462 sEventLogger.log(new SoundTriggerLogger.StringEvent(
463 "stopRecognitionForService(): Failed to stop model: " + ret));
464
Chris Thorntonba08b792017-06-08 22:34:37 -0700465 return ret;
466 }
Chris Thorntonae5fb992017-12-07 18:26:31 -0800467 synchronized (mCallbacksLock) {
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800468 mCallbacks.remove(soundModelId.getUuid());
Chris Thorntonae5fb992017-12-07 18:26:31 -0800469 }
Chris Thorntonba08b792017-06-08 22:34:37 -0700470 }
471 return STATUS_OK;
472 }
473
474 @Override
475 public int unloadSoundModel(ParcelUuid soundModelId) {
476 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
477 if (!isInitialized()) return STATUS_ERROR;
478 if (DEBUG) {
479 Slog.i(TAG, "unloadSoundModel(): id = " + soundModelId);
480 }
481
Jason Hsu1363f582019-04-16 15:35:55 +0800482 sEventLogger.log(new SoundTriggerLogger.StringEvent("unloadSoundModel(): id = "
483 + soundModelId));
484
Chris Thorntonba08b792017-06-08 22:34:37 -0700485 synchronized (mLock) {
486 SoundModel soundModel = mLoadedModels.get(soundModelId.getUuid());
487 if (soundModel == null) {
488 Slog.e(TAG, soundModelId + " is not loaded");
Jason Hsu1363f582019-04-16 15:35:55 +0800489
490 sEventLogger.log(new SoundTriggerLogger.StringEvent(
491 "unloadSoundModel(): " + soundModelId + " is not loaded"));
492
Chris Thorntonba08b792017-06-08 22:34:37 -0700493 return STATUS_ERROR;
494 }
495 int ret;
496 switch (soundModel.type) {
497 case SoundModel.TYPE_KEYPHRASE:
498 ret = mSoundTriggerHelper.unloadKeyphraseSoundModel(
499 ((KeyphraseSoundModel)soundModel).keyphrases[0].id);
500 break;
501 case SoundModel.TYPE_GENERIC_SOUND:
502 ret = mSoundTriggerHelper.unloadGenericSoundModel(soundModel.uuid);
503 break;
504 default:
505 Slog.e(TAG, "Unknown model type");
Jason Hsu1363f582019-04-16 15:35:55 +0800506
507 sEventLogger.log(new SoundTriggerLogger.StringEvent(
508 "unloadSoundModel(): Unknown model type"));
509
Chris Thorntonba08b792017-06-08 22:34:37 -0700510 return STATUS_ERROR;
511 }
512 if (ret != STATUS_OK) {
513 Slog.e(TAG, "Failed to unload model");
Jason Hsu1363f582019-04-16 15:35:55 +0800514
515 sEventLogger.log(new SoundTriggerLogger.StringEvent(
516 "unloadSoundModel(): Failed to unload model"));
517
Chris Thorntonba08b792017-06-08 22:34:37 -0700518 return ret;
519 }
520 mLoadedModels.remove(soundModelId.getUuid());
521 return STATUS_OK;
522 }
523 }
524
525 @Override
526 public boolean isRecognitionActive(ParcelUuid parcelUuid) {
527 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
528 if (!isInitialized()) return false;
Chris Thorntonae5fb992017-12-07 18:26:31 -0800529 synchronized (mCallbacksLock) {
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800530 IRecognitionStatusCallback callback = mCallbacks.get(parcelUuid.getUuid());
Chris Thorntonba08b792017-06-08 22:34:37 -0700531 if (callback == null) {
532 return false;
533 }
Chris Thorntonba08b792017-06-08 22:34:37 -0700534 }
Chris Thorntonae5fb992017-12-07 18:26:31 -0800535 return mSoundTriggerHelper.isRecognitionRequested(parcelUuid.getUuid());
Chris Thorntonba08b792017-06-08 22:34:37 -0700536 }
Michael Dooley291751e2018-10-16 19:53:29 +0000537
538 @Override
mike dooleyb2ab04a2018-11-07 15:48:54 +0100539 public int getModelState(ParcelUuid soundModelId) {
Michael Dooley291751e2018-10-16 19:53:29 +0000540 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
mike dooleyb2ab04a2018-11-07 15:48:54 +0100541 int ret = STATUS_ERROR;
542 if (!isInitialized()) return ret;
Michael Dooley291751e2018-10-16 19:53:29 +0000543 if (DEBUG) {
544 Slog.i(TAG, "getModelState(): id = " + soundModelId);
545 }
546
Jason Hsu1363f582019-04-16 15:35:55 +0800547 sEventLogger.log(new SoundTriggerLogger.StringEvent("getModelState(): id = "
548 + soundModelId));
549
Michael Dooley291751e2018-10-16 19:53:29 +0000550 synchronized (mLock) {
551 SoundModel soundModel = mLoadedModels.get(soundModelId.getUuid());
552 if (soundModel == null) {
553 Slog.e(TAG, soundModelId + " is not loaded");
Jason Hsu1363f582019-04-16 15:35:55 +0800554
555 sEventLogger.log(new SoundTriggerLogger.StringEvent("getModelState(): "
556 + soundModelId + " is not loaded"));
557
mike dooleyb2ab04a2018-11-07 15:48:54 +0100558 return ret;
Michael Dooley291751e2018-10-16 19:53:29 +0000559 }
Michael Dooley291751e2018-10-16 19:53:29 +0000560 switch (soundModel.type) {
561 case SoundModel.TYPE_KEYPHRASE:
562 ret = mSoundTriggerHelper.getKeyphraseModelState(soundModel.uuid);
563 break;
564 case SoundModel.TYPE_GENERIC_SOUND:
565 ret = mSoundTriggerHelper.getGenericModelState(soundModel.uuid);
566 break;
567 default:
568 Slog.e(TAG, "Unknown model type");
Jason Hsu1363f582019-04-16 15:35:55 +0800569
570 sEventLogger.log(new SoundTriggerLogger.StringEvent(
571 "getModelState(): Unknown model type"));
572
Michael Dooley291751e2018-10-16 19:53:29 +0000573 break;
574 }
Michael Dooley291751e2018-10-16 19:53:29 +0000575
576 return ret;
577 }
578 }
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800579 }
580
Philip P. Moltmann7e25b3d2018-03-09 20:22:58 -0800581 /**
582 * Counts the number of operations added in the last 24 hours.
583 */
584 private static class NumOps {
585 private final Object mLock = new Object();
586
587 @GuardedBy("mLock")
588 private int[] mNumOps = new int[24];
589 @GuardedBy("mLock")
590 private long mLastOpsHourSinceBoot;
591
592 /**
593 * Clear buckets of new hours that have elapsed since last operation.
594 *
595 * <p>I.e. when the last operation was triggered at 1:40 and the current operation was
596 * triggered at 4:03, the buckets "2, 3, and 4" are cleared.
597 *
598 * @param currentTime Current elapsed time since boot in ns
599 */
600 void clearOldOps(long currentTime) {
601 synchronized (mLock) {
602 long numHoursSinceBoot = TimeUnit.HOURS.convert(currentTime, TimeUnit.NANOSECONDS);
603
604 // Clear buckets of new hours that have elapsed since last operation
605 // I.e. when the last operation was triggered at 1:40 and the current
606 // operation was triggered at 4:03, the bucket "2, 3, and 4" is cleared
607 if (mLastOpsHourSinceBoot != 0) {
608 for (long hour = mLastOpsHourSinceBoot + 1; hour <= numHoursSinceBoot; hour++) {
609 mNumOps[(int) (hour % 24)] = 0;
610 }
611 }
612 }
613 }
614
615 /**
616 * Add a new operation.
617 *
618 * @param currentTime Current elapsed time since boot in ns
619 */
620 void addOp(long currentTime) {
621 synchronized (mLock) {
622 long numHoursSinceBoot = TimeUnit.HOURS.convert(currentTime, TimeUnit.NANOSECONDS);
623
624 mNumOps[(int) (numHoursSinceBoot % 24)]++;
625 mLastOpsHourSinceBoot = numHoursSinceBoot;
626 }
627 }
628
629 /**
630 * Get the total operations added in the last 24 hours.
631 *
632 * @return The total number of operations added in the last 24 hours
633 */
634 int getOpsAdded() {
635 synchronized (mLock) {
636 int totalOperationsInLastDay = 0;
637 for (int i = 0; i < 24; i++) {
638 totalOperationsInLastDay += mNumOps[i];
639 }
640
641 return totalOperationsInLastDay;
642 }
643 }
644 }
645
Philip P. Moltmanna5b44032018-05-04 13:59:45 -0700646 /**
647 * A single operation run in a {@link RemoteSoundTriggerDetectionService}.
648 *
649 * <p>Once the remote service is connected either setup + execute or setup + stop is executed.
650 */
651 private static class Operation {
652 private interface ExecuteOp {
653 void run(int opId, ISoundTriggerDetectionService service) throws RemoteException;
654 }
655
656 private final @Nullable Runnable mSetupOp;
657 private final @NonNull ExecuteOp mExecuteOp;
658 private final @Nullable Runnable mDropOp;
659
660 private Operation(@Nullable Runnable setupOp, @NonNull ExecuteOp executeOp,
661 @Nullable Runnable cancelOp) {
662 mSetupOp = setupOp;
663 mExecuteOp = executeOp;
664 mDropOp = cancelOp;
665 }
666
667 private void setup() {
668 if (mSetupOp != null) {
669 mSetupOp.run();
670 }
671 }
672
673 void run(int opId, @NonNull ISoundTriggerDetectionService service) throws RemoteException {
674 setup();
675 mExecuteOp.run(opId, service);
676 }
677
678 void drop() {
679 setup();
680
681 if (mDropOp != null) {
682 mDropOp.run();
683 }
684 }
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800685 }
686
687 /**
688 * Local end for a {@link SoundTriggerDetectionService}. Operations are queued up and executed
689 * when the service connects.
690 *
691 * <p>If operations take too long they are forcefully aborted.
692 *
693 * <p>This also limits the amount of operations in 24 hours.
694 */
695 private class RemoteSoundTriggerDetectionService
696 extends IRecognitionStatusCallback.Stub implements ServiceConnection {
697 private static final int MSG_STOP_ALL_PENDING_OPERATIONS = 1;
698
699 private final Object mRemoteServiceLock = new Object();
700
701 /** UUID of the model the service is started for */
702 private final @NonNull ParcelUuid mPuuid;
703 /** Params passed into the start method for the service */
704 private final @Nullable Bundle mParams;
705 /** Component name passed when starting the service */
706 private final @NonNull ComponentName mServiceName;
707 /** User that started the service */
708 private final @NonNull UserHandle mUser;
709 /** Configuration of the recognition the service is handling */
710 private final @NonNull RecognitionConfig mRecognitionConfig;
711 /** Wake lock keeping the remote service alive */
712 private final @NonNull PowerManager.WakeLock mRemoteServiceWakeLock;
713
714 private final @NonNull Handler mHandler;
715
716 /** Callbacks that are called by the service */
717 private final @NonNull ISoundTriggerDetectionServiceClient mClient;
718
719 /** Operations that are pending because the service is not yet connected */
720 @GuardedBy("mRemoteServiceLock")
721 private final ArrayList<Operation> mPendingOps = new ArrayList<>();
722 /** Operations that have been send to the service but have no yet finished */
723 @GuardedBy("mRemoteServiceLock")
724 private final ArraySet<Integer> mRunningOpIds = new ArraySet<>();
Philip P. Moltmann7e25b3d2018-03-09 20:22:58 -0800725 /** The number of operations executed in each of the last 24 hours */
726 private final NumOps mNumOps;
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800727
728 /** The service binder if connected */
729 @GuardedBy("mRemoteServiceLock")
730 private @Nullable ISoundTriggerDetectionService mService;
731 /** Whether the service has been bound */
732 @GuardedBy("mRemoteServiceLock")
733 private boolean mIsBound;
734 /** Whether the service has been destroyed */
735 @GuardedBy("mRemoteServiceLock")
736 private boolean mIsDestroyed;
737 /**
738 * Set once a final op is scheduled. No further ops can be added and the service is
739 * destroyed once the op finishes.
740 */
741 @GuardedBy("mRemoteServiceLock")
742 private boolean mDestroyOnceRunningOpsDone;
743
744 /** Total number of operations performed by this service */
745 @GuardedBy("mRemoteServiceLock")
746 private int mNumTotalOpsPerformed;
747
748 /**
749 * Create a new remote sound trigger detection service. This only binds to the service when
750 * operations are in flight. Each operation has a certain time it can run. Once no
751 * operations are allowed to run anymore, {@link #stopAllPendingOperations() all operations
752 * are aborted and stopped} and the service is disconnected.
753 *
754 * @param modelUuid The UUID of the model the recognition is for
755 * @param params The params passed to each method of the service
756 * @param serviceName The component name of the service
757 * @param user The user of the service
758 * @param config The configuration of the recognition
759 */
760 public RemoteSoundTriggerDetectionService(@NonNull UUID modelUuid,
761 @Nullable Bundle params, @NonNull ComponentName serviceName, @NonNull UserHandle user,
762 @NonNull RecognitionConfig config) {
763 mPuuid = new ParcelUuid(modelUuid);
764 mParams = params;
765 mServiceName = serviceName;
766 mUser = user;
767 mRecognitionConfig = config;
768 mHandler = new Handler(Looper.getMainLooper());
769
770 PowerManager pm = ((PowerManager) mContext.getSystemService(Context.POWER_SERVICE));
771 mRemoteServiceWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
772 "RemoteSoundTriggerDetectionService " + mServiceName.getPackageName() + ":"
773 + mServiceName.getClassName());
774
Philip P. Moltmann7e25b3d2018-03-09 20:22:58 -0800775 synchronized (mLock) {
776 NumOps numOps = mNumOpsPerPackage.get(mServiceName.getPackageName());
777 if (numOps == null) {
778 numOps = new NumOps();
779 mNumOpsPerPackage.put(mServiceName.getPackageName(), numOps);
780 }
781 mNumOps = numOps;
782 }
783
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800784 mClient = new ISoundTriggerDetectionServiceClient.Stub() {
785 @Override
786 public void onOpFinished(int opId) {
787 long token = Binder.clearCallingIdentity();
788 try {
789 synchronized (mRemoteServiceLock) {
790 mRunningOpIds.remove(opId);
791
792 if (mRunningOpIds.isEmpty() && mPendingOps.isEmpty()) {
793 if (mDestroyOnceRunningOpsDone) {
794 destroy();
795 } else {
796 disconnectLocked();
797 }
798 }
799 }
800 } finally {
801 Binder.restoreCallingIdentity(token);
802 }
803 }
804 };
805 }
806
807 @Override
808 public boolean pingBinder() {
809 return !(mIsDestroyed || mDestroyOnceRunningOpsDone);
810 }
811
812 /**
813 * Disconnect from the service, but allow to re-connect when new operations are triggered.
814 */
Andreas Gampe8ce7ed92018-09-05 16:53:00 -0700815 @GuardedBy("mRemoteServiceLock")
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800816 private void disconnectLocked() {
817 if (mService != null) {
818 try {
819 mService.removeClient(mPuuid);
820 } catch (Exception e) {
821 Slog.e(TAG, mPuuid + ": Cannot remove client", e);
Jason Hsu1363f582019-04-16 15:35:55 +0800822
823 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
824 + ": Cannot remove client"));
825
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800826 }
827
828 mService = null;
829 }
830
831 if (mIsBound) {
832 mContext.unbindService(RemoteSoundTriggerDetectionService.this);
833 mIsBound = false;
834
835 synchronized (mCallbacksLock) {
836 mRemoteServiceWakeLock.release();
837 }
838 }
839 }
840
841 /**
842 * Disconnect, do not allow to reconnect to the service. All further operations will be
843 * dropped.
844 */
845 private void destroy() {
846 if (DEBUG) Slog.v(TAG, mPuuid + ": destroy");
847
Jason Hsu1363f582019-04-16 15:35:55 +0800848 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid + ": destroy"));
849
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800850 synchronized (mRemoteServiceLock) {
851 disconnectLocked();
852
853 mIsDestroyed = true;
854 }
855
856 // The callback is removed before the flag is set
857 if (!mDestroyOnceRunningOpsDone) {
858 synchronized (mCallbacksLock) {
859 mCallbacks.remove(mPuuid.getUuid());
860 }
861 }
862 }
863
864 /**
865 * Stop all pending operations and then disconnect for the service.
866 */
867 private void stopAllPendingOperations() {
868 synchronized (mRemoteServiceLock) {
869 if (mIsDestroyed) {
870 return;
871 }
872
873 if (mService != null) {
874 int numOps = mRunningOpIds.size();
875 for (int i = 0; i < numOps; i++) {
876 try {
877 mService.onStopOperation(mPuuid, mRunningOpIds.valueAt(i));
878 } catch (Exception e) {
879 Slog.e(TAG, mPuuid + ": Could not stop operation "
880 + mRunningOpIds.valueAt(i), e);
Jason Hsu1363f582019-04-16 15:35:55 +0800881
882 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
883 + ": Could not stop operation " + mRunningOpIds.valueAt(i)));
884
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800885 }
886 }
887
888 mRunningOpIds.clear();
889 }
890
891 disconnectLocked();
892 }
893 }
894
895 /**
896 * Verify that the service has the expected properties and then bind to the service
897 */
898 private void bind() {
899 long token = Binder.clearCallingIdentity();
900 try {
901 Intent i = new Intent();
902 i.setComponent(mServiceName);
903
904 ResolveInfo ri = mContext.getPackageManager().resolveServiceAsUser(i,
905 GET_SERVICES | GET_META_DATA | MATCH_DEBUG_TRIAGED_MISSING,
906 mUser.getIdentifier());
907
908 if (ri == null) {
909 Slog.w(TAG, mPuuid + ": " + mServiceName + " not found");
Jason Hsu1363f582019-04-16 15:35:55 +0800910
911 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
912 + ": " + mServiceName + " not found"));
913
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800914 return;
915 }
916
917 if (!BIND_SOUND_TRIGGER_DETECTION_SERVICE
918 .equals(ri.serviceInfo.permission)) {
919 Slog.w(TAG, mPuuid + ": " + mServiceName + " does not require "
920 + BIND_SOUND_TRIGGER_DETECTION_SERVICE);
Jason Hsu1363f582019-04-16 15:35:55 +0800921
922 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
923 + ": " + mServiceName + " does not require "
924 + BIND_SOUND_TRIGGER_DETECTION_SERVICE));
925
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800926 return;
927 }
928
929 mIsBound = mContext.bindServiceAsUser(i, this,
930 BIND_AUTO_CREATE | BIND_FOREGROUND_SERVICE, mUser);
931
932 if (mIsBound) {
933 mRemoteServiceWakeLock.acquire();
934 } else {
935 Slog.w(TAG, mPuuid + ": Could not bind to " + mServiceName);
Jason Hsu1363f582019-04-16 15:35:55 +0800936
937 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
938 + ": Could not bind to " + mServiceName));
939
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800940 }
941 } finally {
942 Binder.restoreCallingIdentity(token);
943 }
944 }
945
946 /**
947 * Run an operation (i.e. send it do the service). If the service is not connected, this
948 * binds the service and then runs the operation once connected.
949 *
950 * @param op The operation to run
951 */
952 private void runOrAddOperation(Operation op) {
953 synchronized (mRemoteServiceLock) {
954 if (mIsDestroyed || mDestroyOnceRunningOpsDone) {
Philip P. Moltmanna5b44032018-05-04 13:59:45 -0700955 Slog.w(TAG, mPuuid + ": Dropped operation as already destroyed or marked for "
956 + "destruction");
957
Jason Hsu1363f582019-04-16 15:35:55 +0800958 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
959 + ":Dropped operation as already destroyed or marked for destruction"));
960
Philip P. Moltmanna5b44032018-05-04 13:59:45 -0700961 op.drop();
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800962 return;
963 }
964
965 if (mService == null) {
966 mPendingOps.add(op);
967
968 if (!mIsBound) {
969 bind();
970 }
971 } else {
Philip P. Moltmann7e25b3d2018-03-09 20:22:58 -0800972 long currentTime = System.nanoTime();
973 mNumOps.clearOldOps(currentTime);
974
975 // Drop operation if too many were executed in the last 24 hours.
976 int opsAllowed = Settings.Global.getInt(mContext.getContentResolver(),
977 MAX_SOUND_TRIGGER_DETECTION_SERVICE_OPS_PER_DAY,
978 Integer.MAX_VALUE);
979
Philip P. Moltmann3d1683b2018-05-07 15:53:51 -0700980 // As we currently cannot dropping an op safely, disable throttling
Philip P. Moltmann7e25b3d2018-03-09 20:22:58 -0800981 int opsAdded = mNumOps.getOpsAdded();
Philip P. Moltmann3d1683b2018-05-07 15:53:51 -0700982 if (false && mNumOps.getOpsAdded() >= opsAllowed) {
Philip P. Moltmanna5b44032018-05-04 13:59:45 -0700983 try {
984 if (DEBUG || opsAllowed + 10 > opsAdded) {
985 Slog.w(TAG, mPuuid + ": Dropped operation as too many operations "
986 + "were run in last 24 hours");
Jason Hsu1363f582019-04-16 15:35:55 +0800987
988 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
989 + ": Dropped operation as too many operations "
990 + "were run in last 24 hours"));
991
Philip P. Moltmanna5b44032018-05-04 13:59:45 -0700992 }
993
994 op.drop();
995 } catch (Exception e) {
996 Slog.e(TAG, mPuuid + ": Could not drop operation", e);
Jason Hsu1363f582019-04-16 15:35:55 +0800997
998 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
999 + ": Could not drop operation"));
1000
Philip P. Moltmann7e25b3d2018-03-09 20:22:58 -08001001 }
Philip P. Moltmann09dd8c42018-04-11 09:23:19 -07001002 } else {
1003 mNumOps.addOp(currentTime);
Philip P. Moltmann7e25b3d2018-03-09 20:22:58 -08001004
Philip P. Moltmann09dd8c42018-04-11 09:23:19 -07001005 // Find a free opID
1006 int opId = mNumTotalOpsPerformed;
1007 do {
1008 mNumTotalOpsPerformed++;
1009 } while (mRunningOpIds.contains(opId));
Philip P. Moltmann7e25b3d2018-03-09 20:22:58 -08001010
Philip P. Moltmann09dd8c42018-04-11 09:23:19 -07001011 // Run OP
1012 try {
1013 if (DEBUG) Slog.v(TAG, mPuuid + ": runOp " + opId);
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -08001014
Jason Hsu1363f582019-04-16 15:35:55 +08001015 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
1016 + ": runOp " + opId));
1017
Philip P. Moltmann09dd8c42018-04-11 09:23:19 -07001018 op.run(opId, mService);
1019 mRunningOpIds.add(opId);
1020 } catch (Exception e) {
1021 Slog.e(TAG, mPuuid + ": Could not run operation " + opId, e);
Jason Hsu1363f582019-04-16 15:35:55 +08001022
1023 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
1024 + ": Could not run operation " + opId));
1025
Philip P. Moltmann09dd8c42018-04-11 09:23:19 -07001026 }
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -08001027 }
1028
1029 // Unbind from service if no operations are left (i.e. if the operation failed)
1030 if (mPendingOps.isEmpty() && mRunningOpIds.isEmpty()) {
1031 if (mDestroyOnceRunningOpsDone) {
1032 destroy();
1033 } else {
1034 disconnectLocked();
1035 }
1036 } else {
1037 mHandler.removeMessages(MSG_STOP_ALL_PENDING_OPERATIONS);
1038 mHandler.sendMessageDelayed(obtainMessage(
1039 RemoteSoundTriggerDetectionService::stopAllPendingOperations, this)
1040 .setWhat(MSG_STOP_ALL_PENDING_OPERATIONS),
1041 Settings.Global.getLong(mContext.getContentResolver(),
1042 SOUND_TRIGGER_DETECTION_SERVICE_OP_TIMEOUT,
1043 Long.MAX_VALUE));
1044 }
1045 }
1046 }
1047 }
1048
1049 @Override
1050 public void onKeyphraseDetected(SoundTrigger.KeyphraseRecognitionEvent event) {
1051 Slog.w(TAG, mPuuid + "->" + mServiceName + ": IGNORED onKeyphraseDetected(" + event
1052 + ")");
Jason Hsu1363f582019-04-16 15:35:55 +08001053
1054 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid + "->" + mServiceName
1055 + ": IGNORED onKeyphraseDetected(" + event + ")"));
1056
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -08001057 }
1058
Philip P. Moltmanna5b44032018-05-04 13:59:45 -07001059 /**
1060 * Create an AudioRecord enough for starting and releasing the data buffered for the event.
1061 *
1062 * @param event The event that was received
1063 * @return The initialized AudioRecord
1064 */
1065 private @NonNull AudioRecord createAudioRecordForEvent(
1066 @NonNull SoundTrigger.GenericRecognitionEvent event) {
1067 AudioAttributes.Builder attributesBuilder = new AudioAttributes.Builder();
1068 attributesBuilder.setInternalCapturePreset(MediaRecorder.AudioSource.HOTWORD);
1069 AudioAttributes attributes = attributesBuilder.build();
1070
1071 // Use same AudioFormat processing as in RecognitionEvent.fromParcel
1072 AudioFormat originalFormat = event.getCaptureFormat();
1073 AudioFormat captureFormat = (new AudioFormat.Builder())
1074 .setChannelMask(originalFormat.getChannelMask())
1075 .setEncoding(originalFormat.getEncoding())
1076 .setSampleRate(originalFormat.getSampleRate())
1077 .build();
1078
1079 int bufferSize = AudioRecord.getMinBufferSize(
1080 captureFormat.getSampleRate() == AudioFormat.SAMPLE_RATE_UNSPECIFIED
1081 ? AudioFormat.SAMPLE_RATE_HZ_MAX
1082 : captureFormat.getSampleRate(),
1083 captureFormat.getChannelCount() == 2
1084 ? AudioFormat.CHANNEL_IN_STEREO
1085 : AudioFormat.CHANNEL_IN_MONO,
1086 captureFormat.getEncoding());
1087
Jason Hsu1363f582019-04-16 15:35:55 +08001088 sEventLogger.log(new SoundTriggerLogger.StringEvent("createAudioRecordForEvent"));
1089
Philip P. Moltmanna5b44032018-05-04 13:59:45 -07001090 return new AudioRecord(attributes, captureFormat, bufferSize,
1091 event.getCaptureSession());
1092 }
1093
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -08001094 @Override
1095 public void onGenericSoundTriggerDetected(SoundTrigger.GenericRecognitionEvent event) {
1096 if (DEBUG) Slog.v(TAG, mPuuid + ": Generic sound trigger event: " + event);
1097
Jason Hsu1363f582019-04-16 15:35:55 +08001098 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
1099 + ": Generic sound trigger event: " + event));
1100
Philip P. Moltmanna5b44032018-05-04 13:59:45 -07001101 runOrAddOperation(new Operation(
1102 // always execute:
1103 () -> {
mike dooley9b20c1c2019-01-23 10:38:17 +01001104 if (!mRecognitionConfig.allowMultipleTriggers) {
Philip P. Moltmanna5b44032018-05-04 13:59:45 -07001105 // Unregister this remoteService once op is done
1106 synchronized (mCallbacksLock) {
1107 mCallbacks.remove(mPuuid.getUuid());
1108 }
1109 mDestroyOnceRunningOpsDone = true;
1110 }
1111 },
1112 // execute if not throttled:
1113 (opId, service) -> service.onGenericRecognitionEvent(mPuuid, opId, event),
1114 // execute if throttled:
1115 () -> {
1116 if (event.isCaptureAvailable()) {
1117 AudioRecord capturedData = createAudioRecordForEvent(event);
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -08001118
Philip P. Moltmanna5b44032018-05-04 13:59:45 -07001119 // Currently we need to start and release the audio record to reset
1120 // the DSP even if we don't want to process the event
1121 capturedData.startRecording();
1122 capturedData.release();
1123 }
1124 }));
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -08001125 }
1126
1127 @Override
1128 public void onError(int status) {
1129 if (DEBUG) Slog.v(TAG, mPuuid + ": onError: " + status);
1130
Jason Hsu1363f582019-04-16 15:35:55 +08001131 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
1132 + ": onError: " + status));
1133
Philip P. Moltmanna5b44032018-05-04 13:59:45 -07001134 runOrAddOperation(
1135 new Operation(
1136 // always execute:
1137 () -> {
1138 // Unregister this remoteService once op is done
1139 synchronized (mCallbacksLock) {
1140 mCallbacks.remove(mPuuid.getUuid());
1141 }
1142 mDestroyOnceRunningOpsDone = true;
1143 },
1144 // execute if not throttled:
1145 (opId, service) -> service.onError(mPuuid, opId, status),
1146 // nothing to do if throttled
1147 null));
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -08001148 }
1149
1150 @Override
1151 public void onRecognitionPaused() {
1152 Slog.i(TAG, mPuuid + "->" + mServiceName + ": IGNORED onRecognitionPaused");
Jason Hsu1363f582019-04-16 15:35:55 +08001153
1154 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
1155 + "->" + mServiceName + ": IGNORED onRecognitionPaused"));
1156
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -08001157 }
1158
1159 @Override
1160 public void onRecognitionResumed() {
1161 Slog.i(TAG, mPuuid + "->" + mServiceName + ": IGNORED onRecognitionResumed");
Jason Hsu1363f582019-04-16 15:35:55 +08001162
1163 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
1164 + "->" + mServiceName + ": IGNORED onRecognitionResumed"));
1165
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -08001166 }
1167
1168 @Override
1169 public void onServiceConnected(ComponentName name, IBinder service) {
1170 if (DEBUG) Slog.v(TAG, mPuuid + ": onServiceConnected(" + service + ")");
1171
Jason Hsu1363f582019-04-16 15:35:55 +08001172 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
1173 + ": onServiceConnected(" + service + ")"));
1174
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -08001175 synchronized (mRemoteServiceLock) {
1176 mService = ISoundTriggerDetectionService.Stub.asInterface(service);
1177
1178 try {
1179 mService.setClient(mPuuid, mParams, mClient);
1180 } catch (Exception e) {
1181 Slog.e(TAG, mPuuid + ": Could not init " + mServiceName, e);
1182 return;
1183 }
1184
1185 while (!mPendingOps.isEmpty()) {
1186 runOrAddOperation(mPendingOps.remove(0));
1187 }
1188 }
1189 }
1190
1191 @Override
1192 public void onServiceDisconnected(ComponentName name) {
1193 if (DEBUG) Slog.v(TAG, mPuuid + ": onServiceDisconnected");
1194
Jason Hsu1363f582019-04-16 15:35:55 +08001195 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
1196 + ": onServiceDisconnected"));
1197
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -08001198 synchronized (mRemoteServiceLock) {
1199 mService = null;
1200 }
1201 }
1202
1203 @Override
1204 public void onBindingDied(ComponentName name) {
1205 if (DEBUG) Slog.v(TAG, mPuuid + ": onBindingDied");
1206
Jason Hsu1363f582019-04-16 15:35:55 +08001207 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
1208 + ": onBindingDied"));
1209
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -08001210 synchronized (mRemoteServiceLock) {
1211 destroy();
1212 }
1213 }
1214
1215 @Override
1216 public void onNullBinding(ComponentName name) {
1217 Slog.w(TAG, name + " for model " + mPuuid + " returned a null binding");
1218
Jason Hsu1363f582019-04-16 15:35:55 +08001219 sEventLogger.log(new SoundTriggerLogger.StringEvent(name + " for model "
1220 + mPuuid + " returned a null binding"));
1221
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -08001222 synchronized (mRemoteServiceLock) {
1223 disconnectLocked();
1224 }
1225 }
1226 }
1227
Arunesh Mishraa772e5f2016-01-25 10:33:11 -08001228 public final class LocalSoundTriggerService extends SoundTriggerInternal {
1229 private final Context mContext;
1230 private SoundTriggerHelper mSoundTriggerHelper;
1231
1232 LocalSoundTriggerService(Context context) {
1233 mContext = context;
1234 }
1235
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001236 synchronized void setSoundTriggerHelper(SoundTriggerHelper helper) {
1237 mSoundTriggerHelper = helper;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -08001238 }
1239
1240 @Override
1241 public int startRecognition(int keyphraseId, KeyphraseSoundModel soundModel,
1242 IRecognitionStatusCallback listener, RecognitionConfig recognitionConfig) {
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001243 if (!isInitialized()) return STATUS_ERROR;
1244 return mSoundTriggerHelper.startKeyphraseRecognition(keyphraseId, soundModel, listener,
Arunesh Mishraa772e5f2016-01-25 10:33:11 -08001245 recognitionConfig);
1246 }
1247
1248 @Override
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001249 public synchronized int stopRecognition(int keyphraseId, IRecognitionStatusCallback listener) {
1250 if (!isInitialized()) return STATUS_ERROR;
1251 return mSoundTriggerHelper.stopKeyphraseRecognition(keyphraseId, listener);
Arunesh Mishraa772e5f2016-01-25 10:33:11 -08001252 }
1253
1254 @Override
Arunesh Mishra55a9b002016-02-01 14:06:37 -08001255 public ModuleProperties getModuleProperties() {
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001256 if (!isInitialized()) return null;
Arunesh Mishra55a9b002016-02-01 14:06:37 -08001257 return mSoundTriggerHelper.getModuleProperties();
1258 }
1259
1260 @Override
Arunesh Mishra2d1de782016-02-21 18:10:28 -08001261 public int unloadKeyphraseModel(int keyphraseId) {
1262 if (!isInitialized()) return STATUS_ERROR;
1263 return mSoundTriggerHelper.unloadKeyphraseSoundModel(keyphraseId);
1264 }
1265
1266 @Override
Arunesh Mishraa772e5f2016-01-25 10:33:11 -08001267 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001268 if (!isInitialized()) return;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -08001269 mSoundTriggerHelper.dump(fd, pw, args);
Jason Hsu1363f582019-04-16 15:35:55 +08001270 // log
1271 sEventLogger.dump(pw);
Arunesh Mishraa772e5f2016-01-25 10:33:11 -08001272 }
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001273
1274 private synchronized boolean isInitialized() {
1275 if (mSoundTriggerHelper == null ) {
1276 Slog.e(TAG, "SoundTriggerHelper not initialized.");
Jason Hsu1363f582019-04-16 15:35:55 +08001277
1278 sEventLogger.log(new SoundTriggerLogger.StringEvent(
1279 "SoundTriggerHelper not initialized."));
1280
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001281 return false;
1282 }
1283 return true;
1284 }
Arunesh Mishraa772e5f2016-01-25 10:33:11 -08001285 }
1286
1287 private void enforceCallingPermission(String permission) {
1288 if (mContext.checkCallingOrSelfPermission(permission)
1289 != PackageManager.PERMISSION_GRANTED) {
1290 throw new SecurityException("Caller does not hold the permission " + permission);
1291 }
1292 }
Jason Hsu1363f582019-04-16 15:35:55 +08001293
1294 //=================================================================
1295 // For logging
1296
1297 private static final SoundTriggerLogger sEventLogger = new SoundTriggerLogger(200,
1298 "SoundTrigger activity");
1299
Arunesh Mishraa772e5f2016-01-25 10:33:11 -08001300}