blob: 7a834696387c1d4f33b0ed48dd5c945534458cd4 [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) {
Michael Dooley291751e2018-10-16 19:53:29 +0000561 case SoundModel.TYPE_GENERIC_SOUND:
562 ret = mSoundTriggerHelper.getGenericModelState(soundModel.uuid);
563 break;
564 default:
mike dooleybaa22c72019-05-15 10:05:25 +0200565 // SoundModel.TYPE_KEYPHRASE is not supported to increase privacy.
566 Slog.e(TAG, "Unsupported model type, " + soundModel.type);
Jason Hsu1363f582019-04-16 15:35:55 +0800567 sEventLogger.log(new SoundTriggerLogger.StringEvent(
mike dooleybaa22c72019-05-15 10:05:25 +0200568 "getModelState(): Unsupported model type, "
569 + soundModel.type));
Michael Dooley291751e2018-10-16 19:53:29 +0000570 break;
571 }
Michael Dooley291751e2018-10-16 19:53:29 +0000572
573 return ret;
574 }
575 }
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800576 }
577
Philip P. Moltmann7e25b3d2018-03-09 20:22:58 -0800578 /**
579 * Counts the number of operations added in the last 24 hours.
580 */
581 private static class NumOps {
582 private final Object mLock = new Object();
583
584 @GuardedBy("mLock")
585 private int[] mNumOps = new int[24];
586 @GuardedBy("mLock")
587 private long mLastOpsHourSinceBoot;
588
589 /**
590 * Clear buckets of new hours that have elapsed since last operation.
591 *
592 * <p>I.e. when the last operation was triggered at 1:40 and the current operation was
593 * triggered at 4:03, the buckets "2, 3, and 4" are cleared.
594 *
595 * @param currentTime Current elapsed time since boot in ns
596 */
597 void clearOldOps(long currentTime) {
598 synchronized (mLock) {
599 long numHoursSinceBoot = TimeUnit.HOURS.convert(currentTime, TimeUnit.NANOSECONDS);
600
601 // Clear buckets of new hours that have elapsed since last operation
602 // I.e. when the last operation was triggered at 1:40 and the current
603 // operation was triggered at 4:03, the bucket "2, 3, and 4" is cleared
604 if (mLastOpsHourSinceBoot != 0) {
605 for (long hour = mLastOpsHourSinceBoot + 1; hour <= numHoursSinceBoot; hour++) {
606 mNumOps[(int) (hour % 24)] = 0;
607 }
608 }
609 }
610 }
611
612 /**
613 * Add a new operation.
614 *
615 * @param currentTime Current elapsed time since boot in ns
616 */
617 void addOp(long currentTime) {
618 synchronized (mLock) {
619 long numHoursSinceBoot = TimeUnit.HOURS.convert(currentTime, TimeUnit.NANOSECONDS);
620
621 mNumOps[(int) (numHoursSinceBoot % 24)]++;
622 mLastOpsHourSinceBoot = numHoursSinceBoot;
623 }
624 }
625
626 /**
627 * Get the total operations added in the last 24 hours.
628 *
629 * @return The total number of operations added in the last 24 hours
630 */
631 int getOpsAdded() {
632 synchronized (mLock) {
633 int totalOperationsInLastDay = 0;
634 for (int i = 0; i < 24; i++) {
635 totalOperationsInLastDay += mNumOps[i];
636 }
637
638 return totalOperationsInLastDay;
639 }
640 }
641 }
642
Philip P. Moltmanna5b44032018-05-04 13:59:45 -0700643 /**
644 * A single operation run in a {@link RemoteSoundTriggerDetectionService}.
645 *
646 * <p>Once the remote service is connected either setup + execute or setup + stop is executed.
647 */
648 private static class Operation {
649 private interface ExecuteOp {
650 void run(int opId, ISoundTriggerDetectionService service) throws RemoteException;
651 }
652
653 private final @Nullable Runnable mSetupOp;
654 private final @NonNull ExecuteOp mExecuteOp;
655 private final @Nullable Runnable mDropOp;
656
657 private Operation(@Nullable Runnable setupOp, @NonNull ExecuteOp executeOp,
658 @Nullable Runnable cancelOp) {
659 mSetupOp = setupOp;
660 mExecuteOp = executeOp;
661 mDropOp = cancelOp;
662 }
663
664 private void setup() {
665 if (mSetupOp != null) {
666 mSetupOp.run();
667 }
668 }
669
670 void run(int opId, @NonNull ISoundTriggerDetectionService service) throws RemoteException {
671 setup();
672 mExecuteOp.run(opId, service);
673 }
674
675 void drop() {
676 setup();
677
678 if (mDropOp != null) {
679 mDropOp.run();
680 }
681 }
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800682 }
683
684 /**
685 * Local end for a {@link SoundTriggerDetectionService}. Operations are queued up and executed
686 * when the service connects.
687 *
688 * <p>If operations take too long they are forcefully aborted.
689 *
690 * <p>This also limits the amount of operations in 24 hours.
691 */
692 private class RemoteSoundTriggerDetectionService
693 extends IRecognitionStatusCallback.Stub implements ServiceConnection {
694 private static final int MSG_STOP_ALL_PENDING_OPERATIONS = 1;
695
696 private final Object mRemoteServiceLock = new Object();
697
698 /** UUID of the model the service is started for */
699 private final @NonNull ParcelUuid mPuuid;
700 /** Params passed into the start method for the service */
701 private final @Nullable Bundle mParams;
702 /** Component name passed when starting the service */
703 private final @NonNull ComponentName mServiceName;
704 /** User that started the service */
705 private final @NonNull UserHandle mUser;
706 /** Configuration of the recognition the service is handling */
707 private final @NonNull RecognitionConfig mRecognitionConfig;
708 /** Wake lock keeping the remote service alive */
709 private final @NonNull PowerManager.WakeLock mRemoteServiceWakeLock;
710
711 private final @NonNull Handler mHandler;
712
713 /** Callbacks that are called by the service */
714 private final @NonNull ISoundTriggerDetectionServiceClient mClient;
715
716 /** Operations that are pending because the service is not yet connected */
717 @GuardedBy("mRemoteServiceLock")
718 private final ArrayList<Operation> mPendingOps = new ArrayList<>();
719 /** Operations that have been send to the service but have no yet finished */
720 @GuardedBy("mRemoteServiceLock")
721 private final ArraySet<Integer> mRunningOpIds = new ArraySet<>();
Philip P. Moltmann7e25b3d2018-03-09 20:22:58 -0800722 /** The number of operations executed in each of the last 24 hours */
723 private final NumOps mNumOps;
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800724
725 /** The service binder if connected */
726 @GuardedBy("mRemoteServiceLock")
727 private @Nullable ISoundTriggerDetectionService mService;
728 /** Whether the service has been bound */
729 @GuardedBy("mRemoteServiceLock")
730 private boolean mIsBound;
731 /** Whether the service has been destroyed */
732 @GuardedBy("mRemoteServiceLock")
733 private boolean mIsDestroyed;
734 /**
735 * Set once a final op is scheduled. No further ops can be added and the service is
736 * destroyed once the op finishes.
737 */
738 @GuardedBy("mRemoteServiceLock")
739 private boolean mDestroyOnceRunningOpsDone;
740
741 /** Total number of operations performed by this service */
742 @GuardedBy("mRemoteServiceLock")
743 private int mNumTotalOpsPerformed;
744
745 /**
746 * Create a new remote sound trigger detection service. This only binds to the service when
747 * operations are in flight. Each operation has a certain time it can run. Once no
748 * operations are allowed to run anymore, {@link #stopAllPendingOperations() all operations
749 * are aborted and stopped} and the service is disconnected.
750 *
751 * @param modelUuid The UUID of the model the recognition is for
752 * @param params The params passed to each method of the service
753 * @param serviceName The component name of the service
754 * @param user The user of the service
755 * @param config The configuration of the recognition
756 */
757 public RemoteSoundTriggerDetectionService(@NonNull UUID modelUuid,
758 @Nullable Bundle params, @NonNull ComponentName serviceName, @NonNull UserHandle user,
759 @NonNull RecognitionConfig config) {
760 mPuuid = new ParcelUuid(modelUuid);
761 mParams = params;
762 mServiceName = serviceName;
763 mUser = user;
764 mRecognitionConfig = config;
765 mHandler = new Handler(Looper.getMainLooper());
766
767 PowerManager pm = ((PowerManager) mContext.getSystemService(Context.POWER_SERVICE));
768 mRemoteServiceWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
769 "RemoteSoundTriggerDetectionService " + mServiceName.getPackageName() + ":"
770 + mServiceName.getClassName());
771
Philip P. Moltmann7e25b3d2018-03-09 20:22:58 -0800772 synchronized (mLock) {
773 NumOps numOps = mNumOpsPerPackage.get(mServiceName.getPackageName());
774 if (numOps == null) {
775 numOps = new NumOps();
776 mNumOpsPerPackage.put(mServiceName.getPackageName(), numOps);
777 }
778 mNumOps = numOps;
779 }
780
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800781 mClient = new ISoundTriggerDetectionServiceClient.Stub() {
782 @Override
783 public void onOpFinished(int opId) {
784 long token = Binder.clearCallingIdentity();
785 try {
786 synchronized (mRemoteServiceLock) {
787 mRunningOpIds.remove(opId);
788
789 if (mRunningOpIds.isEmpty() && mPendingOps.isEmpty()) {
790 if (mDestroyOnceRunningOpsDone) {
791 destroy();
792 } else {
793 disconnectLocked();
794 }
795 }
796 }
797 } finally {
798 Binder.restoreCallingIdentity(token);
799 }
800 }
801 };
802 }
803
804 @Override
805 public boolean pingBinder() {
806 return !(mIsDestroyed || mDestroyOnceRunningOpsDone);
807 }
808
809 /**
810 * Disconnect from the service, but allow to re-connect when new operations are triggered.
811 */
Andreas Gampe8ce7ed92018-09-05 16:53:00 -0700812 @GuardedBy("mRemoteServiceLock")
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800813 private void disconnectLocked() {
814 if (mService != null) {
815 try {
816 mService.removeClient(mPuuid);
817 } catch (Exception e) {
818 Slog.e(TAG, mPuuid + ": Cannot remove client", e);
Jason Hsu1363f582019-04-16 15:35:55 +0800819
820 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
821 + ": Cannot remove client"));
822
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800823 }
824
825 mService = null;
826 }
827
828 if (mIsBound) {
829 mContext.unbindService(RemoteSoundTriggerDetectionService.this);
830 mIsBound = false;
831
832 synchronized (mCallbacksLock) {
833 mRemoteServiceWakeLock.release();
834 }
835 }
836 }
837
838 /**
839 * Disconnect, do not allow to reconnect to the service. All further operations will be
840 * dropped.
841 */
842 private void destroy() {
843 if (DEBUG) Slog.v(TAG, mPuuid + ": destroy");
844
Jason Hsu1363f582019-04-16 15:35:55 +0800845 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid + ": destroy"));
846
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800847 synchronized (mRemoteServiceLock) {
848 disconnectLocked();
849
850 mIsDestroyed = true;
851 }
852
853 // The callback is removed before the flag is set
854 if (!mDestroyOnceRunningOpsDone) {
855 synchronized (mCallbacksLock) {
856 mCallbacks.remove(mPuuid.getUuid());
857 }
858 }
859 }
860
861 /**
862 * Stop all pending operations and then disconnect for the service.
863 */
864 private void stopAllPendingOperations() {
865 synchronized (mRemoteServiceLock) {
866 if (mIsDestroyed) {
867 return;
868 }
869
870 if (mService != null) {
871 int numOps = mRunningOpIds.size();
872 for (int i = 0; i < numOps; i++) {
873 try {
874 mService.onStopOperation(mPuuid, mRunningOpIds.valueAt(i));
875 } catch (Exception e) {
876 Slog.e(TAG, mPuuid + ": Could not stop operation "
877 + mRunningOpIds.valueAt(i), e);
Jason Hsu1363f582019-04-16 15:35:55 +0800878
879 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
880 + ": Could not stop operation " + mRunningOpIds.valueAt(i)));
881
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800882 }
883 }
884
885 mRunningOpIds.clear();
886 }
887
888 disconnectLocked();
889 }
890 }
891
892 /**
893 * Verify that the service has the expected properties and then bind to the service
894 */
895 private void bind() {
896 long token = Binder.clearCallingIdentity();
897 try {
898 Intent i = new Intent();
899 i.setComponent(mServiceName);
900
901 ResolveInfo ri = mContext.getPackageManager().resolveServiceAsUser(i,
902 GET_SERVICES | GET_META_DATA | MATCH_DEBUG_TRIAGED_MISSING,
903 mUser.getIdentifier());
904
905 if (ri == null) {
906 Slog.w(TAG, mPuuid + ": " + mServiceName + " not found");
Jason Hsu1363f582019-04-16 15:35:55 +0800907
908 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
909 + ": " + mServiceName + " not found"));
910
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800911 return;
912 }
913
914 if (!BIND_SOUND_TRIGGER_DETECTION_SERVICE
915 .equals(ri.serviceInfo.permission)) {
916 Slog.w(TAG, mPuuid + ": " + mServiceName + " does not require "
917 + BIND_SOUND_TRIGGER_DETECTION_SERVICE);
Jason Hsu1363f582019-04-16 15:35:55 +0800918
919 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
920 + ": " + mServiceName + " does not require "
921 + BIND_SOUND_TRIGGER_DETECTION_SERVICE));
922
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800923 return;
924 }
925
926 mIsBound = mContext.bindServiceAsUser(i, this,
927 BIND_AUTO_CREATE | BIND_FOREGROUND_SERVICE, mUser);
928
929 if (mIsBound) {
930 mRemoteServiceWakeLock.acquire();
931 } else {
932 Slog.w(TAG, mPuuid + ": Could not bind to " + mServiceName);
Jason Hsu1363f582019-04-16 15:35:55 +0800933
934 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
935 + ": Could not bind to " + mServiceName));
936
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800937 }
938 } finally {
939 Binder.restoreCallingIdentity(token);
940 }
941 }
942
943 /**
944 * Run an operation (i.e. send it do the service). If the service is not connected, this
945 * binds the service and then runs the operation once connected.
946 *
947 * @param op The operation to run
948 */
949 private void runOrAddOperation(Operation op) {
950 synchronized (mRemoteServiceLock) {
951 if (mIsDestroyed || mDestroyOnceRunningOpsDone) {
Philip P. Moltmanna5b44032018-05-04 13:59:45 -0700952 Slog.w(TAG, mPuuid + ": Dropped operation as already destroyed or marked for "
953 + "destruction");
954
Jason Hsu1363f582019-04-16 15:35:55 +0800955 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
956 + ":Dropped operation as already destroyed or marked for destruction"));
957
Philip P. Moltmanna5b44032018-05-04 13:59:45 -0700958 op.drop();
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800959 return;
960 }
961
962 if (mService == null) {
963 mPendingOps.add(op);
964
965 if (!mIsBound) {
966 bind();
967 }
968 } else {
Philip P. Moltmann7e25b3d2018-03-09 20:22:58 -0800969 long currentTime = System.nanoTime();
970 mNumOps.clearOldOps(currentTime);
971
972 // Drop operation if too many were executed in the last 24 hours.
973 int opsAllowed = Settings.Global.getInt(mContext.getContentResolver(),
974 MAX_SOUND_TRIGGER_DETECTION_SERVICE_OPS_PER_DAY,
975 Integer.MAX_VALUE);
976
Philip P. Moltmann3d1683b2018-05-07 15:53:51 -0700977 // As we currently cannot dropping an op safely, disable throttling
Philip P. Moltmann7e25b3d2018-03-09 20:22:58 -0800978 int opsAdded = mNumOps.getOpsAdded();
Philip P. Moltmann3d1683b2018-05-07 15:53:51 -0700979 if (false && mNumOps.getOpsAdded() >= opsAllowed) {
Philip P. Moltmanna5b44032018-05-04 13:59:45 -0700980 try {
981 if (DEBUG || opsAllowed + 10 > opsAdded) {
982 Slog.w(TAG, mPuuid + ": Dropped operation as too many operations "
983 + "were run in last 24 hours");
Jason Hsu1363f582019-04-16 15:35:55 +0800984
985 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
986 + ": Dropped operation as too many operations "
987 + "were run in last 24 hours"));
988
Philip P. Moltmanna5b44032018-05-04 13:59:45 -0700989 }
990
991 op.drop();
992 } catch (Exception e) {
993 Slog.e(TAG, mPuuid + ": Could not drop operation", e);
Jason Hsu1363f582019-04-16 15:35:55 +0800994
995 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
996 + ": Could not drop operation"));
997
Philip P. Moltmann7e25b3d2018-03-09 20:22:58 -0800998 }
Philip P. Moltmann09dd8c42018-04-11 09:23:19 -0700999 } else {
1000 mNumOps.addOp(currentTime);
Philip P. Moltmann7e25b3d2018-03-09 20:22:58 -08001001
Philip P. Moltmann09dd8c42018-04-11 09:23:19 -07001002 // Find a free opID
1003 int opId = mNumTotalOpsPerformed;
1004 do {
1005 mNumTotalOpsPerformed++;
1006 } while (mRunningOpIds.contains(opId));
Philip P. Moltmann7e25b3d2018-03-09 20:22:58 -08001007
Philip P. Moltmann09dd8c42018-04-11 09:23:19 -07001008 // Run OP
1009 try {
1010 if (DEBUG) Slog.v(TAG, mPuuid + ": runOp " + opId);
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -08001011
Jason Hsu1363f582019-04-16 15:35:55 +08001012 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
1013 + ": runOp " + opId));
1014
Philip P. Moltmann09dd8c42018-04-11 09:23:19 -07001015 op.run(opId, mService);
1016 mRunningOpIds.add(opId);
1017 } catch (Exception e) {
1018 Slog.e(TAG, mPuuid + ": Could not run operation " + opId, e);
Jason Hsu1363f582019-04-16 15:35:55 +08001019
1020 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
1021 + ": Could not run operation " + opId));
1022
Philip P. Moltmann09dd8c42018-04-11 09:23:19 -07001023 }
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -08001024 }
1025
1026 // Unbind from service if no operations are left (i.e. if the operation failed)
1027 if (mPendingOps.isEmpty() && mRunningOpIds.isEmpty()) {
1028 if (mDestroyOnceRunningOpsDone) {
1029 destroy();
1030 } else {
1031 disconnectLocked();
1032 }
1033 } else {
1034 mHandler.removeMessages(MSG_STOP_ALL_PENDING_OPERATIONS);
1035 mHandler.sendMessageDelayed(obtainMessage(
1036 RemoteSoundTriggerDetectionService::stopAllPendingOperations, this)
1037 .setWhat(MSG_STOP_ALL_PENDING_OPERATIONS),
1038 Settings.Global.getLong(mContext.getContentResolver(),
1039 SOUND_TRIGGER_DETECTION_SERVICE_OP_TIMEOUT,
1040 Long.MAX_VALUE));
1041 }
1042 }
1043 }
1044 }
1045
1046 @Override
1047 public void onKeyphraseDetected(SoundTrigger.KeyphraseRecognitionEvent event) {
1048 Slog.w(TAG, mPuuid + "->" + mServiceName + ": IGNORED onKeyphraseDetected(" + event
1049 + ")");
Jason Hsu1363f582019-04-16 15:35:55 +08001050
1051 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid + "->" + mServiceName
1052 + ": IGNORED onKeyphraseDetected(" + event + ")"));
1053
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -08001054 }
1055
Philip P. Moltmanna5b44032018-05-04 13:59:45 -07001056 /**
1057 * Create an AudioRecord enough for starting and releasing the data buffered for the event.
1058 *
1059 * @param event The event that was received
1060 * @return The initialized AudioRecord
1061 */
1062 private @NonNull AudioRecord createAudioRecordForEvent(
1063 @NonNull SoundTrigger.GenericRecognitionEvent event) {
1064 AudioAttributes.Builder attributesBuilder = new AudioAttributes.Builder();
1065 attributesBuilder.setInternalCapturePreset(MediaRecorder.AudioSource.HOTWORD);
1066 AudioAttributes attributes = attributesBuilder.build();
1067
1068 // Use same AudioFormat processing as in RecognitionEvent.fromParcel
1069 AudioFormat originalFormat = event.getCaptureFormat();
1070 AudioFormat captureFormat = (new AudioFormat.Builder())
1071 .setChannelMask(originalFormat.getChannelMask())
1072 .setEncoding(originalFormat.getEncoding())
1073 .setSampleRate(originalFormat.getSampleRate())
1074 .build();
1075
1076 int bufferSize = AudioRecord.getMinBufferSize(
1077 captureFormat.getSampleRate() == AudioFormat.SAMPLE_RATE_UNSPECIFIED
1078 ? AudioFormat.SAMPLE_RATE_HZ_MAX
1079 : captureFormat.getSampleRate(),
1080 captureFormat.getChannelCount() == 2
1081 ? AudioFormat.CHANNEL_IN_STEREO
1082 : AudioFormat.CHANNEL_IN_MONO,
1083 captureFormat.getEncoding());
1084
Jason Hsu1363f582019-04-16 15:35:55 +08001085 sEventLogger.log(new SoundTriggerLogger.StringEvent("createAudioRecordForEvent"));
1086
Philip P. Moltmanna5b44032018-05-04 13:59:45 -07001087 return new AudioRecord(attributes, captureFormat, bufferSize,
1088 event.getCaptureSession());
1089 }
1090
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -08001091 @Override
1092 public void onGenericSoundTriggerDetected(SoundTrigger.GenericRecognitionEvent event) {
1093 if (DEBUG) Slog.v(TAG, mPuuid + ": Generic sound trigger event: " + event);
1094
Jason Hsu1363f582019-04-16 15:35:55 +08001095 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
1096 + ": Generic sound trigger event: " + event));
1097
Philip P. Moltmanna5b44032018-05-04 13:59:45 -07001098 runOrAddOperation(new Operation(
1099 // always execute:
1100 () -> {
mike dooley9b20c1c2019-01-23 10:38:17 +01001101 if (!mRecognitionConfig.allowMultipleTriggers) {
Philip P. Moltmanna5b44032018-05-04 13:59:45 -07001102 // Unregister this remoteService once op is done
1103 synchronized (mCallbacksLock) {
1104 mCallbacks.remove(mPuuid.getUuid());
1105 }
1106 mDestroyOnceRunningOpsDone = true;
1107 }
1108 },
1109 // execute if not throttled:
1110 (opId, service) -> service.onGenericRecognitionEvent(mPuuid, opId, event),
1111 // execute if throttled:
1112 () -> {
1113 if (event.isCaptureAvailable()) {
1114 AudioRecord capturedData = createAudioRecordForEvent(event);
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -08001115
Philip P. Moltmanna5b44032018-05-04 13:59:45 -07001116 // Currently we need to start and release the audio record to reset
1117 // the DSP even if we don't want to process the event
1118 capturedData.startRecording();
1119 capturedData.release();
1120 }
1121 }));
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -08001122 }
1123
1124 @Override
1125 public void onError(int status) {
1126 if (DEBUG) Slog.v(TAG, mPuuid + ": onError: " + status);
1127
Jason Hsu1363f582019-04-16 15:35:55 +08001128 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
1129 + ": onError: " + status));
1130
Philip P. Moltmanna5b44032018-05-04 13:59:45 -07001131 runOrAddOperation(
1132 new Operation(
1133 // always execute:
1134 () -> {
1135 // Unregister this remoteService once op is done
1136 synchronized (mCallbacksLock) {
1137 mCallbacks.remove(mPuuid.getUuid());
1138 }
1139 mDestroyOnceRunningOpsDone = true;
1140 },
1141 // execute if not throttled:
1142 (opId, service) -> service.onError(mPuuid, opId, status),
1143 // nothing to do if throttled
1144 null));
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -08001145 }
1146
1147 @Override
1148 public void onRecognitionPaused() {
1149 Slog.i(TAG, mPuuid + "->" + mServiceName + ": IGNORED onRecognitionPaused");
Jason Hsu1363f582019-04-16 15:35:55 +08001150
1151 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
1152 + "->" + mServiceName + ": IGNORED onRecognitionPaused"));
1153
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -08001154 }
1155
1156 @Override
1157 public void onRecognitionResumed() {
1158 Slog.i(TAG, mPuuid + "->" + mServiceName + ": IGNORED onRecognitionResumed");
Jason Hsu1363f582019-04-16 15:35:55 +08001159
1160 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
1161 + "->" + mServiceName + ": IGNORED onRecognitionResumed"));
1162
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -08001163 }
1164
1165 @Override
1166 public void onServiceConnected(ComponentName name, IBinder service) {
1167 if (DEBUG) Slog.v(TAG, mPuuid + ": onServiceConnected(" + service + ")");
1168
Jason Hsu1363f582019-04-16 15:35:55 +08001169 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
1170 + ": onServiceConnected(" + service + ")"));
1171
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -08001172 synchronized (mRemoteServiceLock) {
1173 mService = ISoundTriggerDetectionService.Stub.asInterface(service);
1174
1175 try {
1176 mService.setClient(mPuuid, mParams, mClient);
1177 } catch (Exception e) {
1178 Slog.e(TAG, mPuuid + ": Could not init " + mServiceName, e);
1179 return;
1180 }
1181
1182 while (!mPendingOps.isEmpty()) {
1183 runOrAddOperation(mPendingOps.remove(0));
1184 }
1185 }
1186 }
1187
1188 @Override
1189 public void onServiceDisconnected(ComponentName name) {
1190 if (DEBUG) Slog.v(TAG, mPuuid + ": onServiceDisconnected");
1191
Jason Hsu1363f582019-04-16 15:35:55 +08001192 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
1193 + ": onServiceDisconnected"));
1194
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -08001195 synchronized (mRemoteServiceLock) {
1196 mService = null;
1197 }
1198 }
1199
1200 @Override
1201 public void onBindingDied(ComponentName name) {
1202 if (DEBUG) Slog.v(TAG, mPuuid + ": onBindingDied");
1203
Jason Hsu1363f582019-04-16 15:35:55 +08001204 sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
1205 + ": onBindingDied"));
1206
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -08001207 synchronized (mRemoteServiceLock) {
1208 destroy();
1209 }
1210 }
1211
1212 @Override
1213 public void onNullBinding(ComponentName name) {
1214 Slog.w(TAG, name + " for model " + mPuuid + " returned a null binding");
1215
Jason Hsu1363f582019-04-16 15:35:55 +08001216 sEventLogger.log(new SoundTriggerLogger.StringEvent(name + " for model "
1217 + mPuuid + " returned a null binding"));
1218
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -08001219 synchronized (mRemoteServiceLock) {
1220 disconnectLocked();
1221 }
1222 }
1223 }
1224
Arunesh Mishraa772e5f2016-01-25 10:33:11 -08001225 public final class LocalSoundTriggerService extends SoundTriggerInternal {
1226 private final Context mContext;
1227 private SoundTriggerHelper mSoundTriggerHelper;
1228
1229 LocalSoundTriggerService(Context context) {
1230 mContext = context;
1231 }
1232
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001233 synchronized void setSoundTriggerHelper(SoundTriggerHelper helper) {
1234 mSoundTriggerHelper = helper;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -08001235 }
1236
1237 @Override
1238 public int startRecognition(int keyphraseId, KeyphraseSoundModel soundModel,
1239 IRecognitionStatusCallback listener, RecognitionConfig recognitionConfig) {
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001240 if (!isInitialized()) return STATUS_ERROR;
1241 return mSoundTriggerHelper.startKeyphraseRecognition(keyphraseId, soundModel, listener,
Arunesh Mishraa772e5f2016-01-25 10:33:11 -08001242 recognitionConfig);
1243 }
1244
1245 @Override
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001246 public synchronized int stopRecognition(int keyphraseId, IRecognitionStatusCallback listener) {
1247 if (!isInitialized()) return STATUS_ERROR;
1248 return mSoundTriggerHelper.stopKeyphraseRecognition(keyphraseId, listener);
Arunesh Mishraa772e5f2016-01-25 10:33:11 -08001249 }
1250
1251 @Override
Arunesh Mishra55a9b002016-02-01 14:06:37 -08001252 public ModuleProperties getModuleProperties() {
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001253 if (!isInitialized()) return null;
Arunesh Mishra55a9b002016-02-01 14:06:37 -08001254 return mSoundTriggerHelper.getModuleProperties();
1255 }
1256
1257 @Override
Arunesh Mishra2d1de782016-02-21 18:10:28 -08001258 public int unloadKeyphraseModel(int keyphraseId) {
1259 if (!isInitialized()) return STATUS_ERROR;
1260 return mSoundTriggerHelper.unloadKeyphraseSoundModel(keyphraseId);
1261 }
1262
1263 @Override
Arunesh Mishraa772e5f2016-01-25 10:33:11 -08001264 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001265 if (!isInitialized()) return;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -08001266 mSoundTriggerHelper.dump(fd, pw, args);
Jason Hsu1363f582019-04-16 15:35:55 +08001267 // log
1268 sEventLogger.dump(pw);
Arunesh Mishraa772e5f2016-01-25 10:33:11 -08001269 }
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001270
1271 private synchronized boolean isInitialized() {
1272 if (mSoundTriggerHelper == null ) {
1273 Slog.e(TAG, "SoundTriggerHelper not initialized.");
Jason Hsu1363f582019-04-16 15:35:55 +08001274
1275 sEventLogger.log(new SoundTriggerLogger.StringEvent(
1276 "SoundTriggerHelper not initialized."));
1277
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001278 return false;
1279 }
1280 return true;
1281 }
Arunesh Mishraa772e5f2016-01-25 10:33:11 -08001282 }
1283
1284 private void enforceCallingPermission(String permission) {
1285 if (mContext.checkCallingOrSelfPermission(permission)
1286 != PackageManager.PERMISSION_GRANTED) {
1287 throw new SecurityException("Caller does not hold the permission " + permission);
1288 }
1289 }
Jason Hsu1363f582019-04-16 15:35:55 +08001290
1291 //=================================================================
1292 // For logging
1293
1294 private static final SoundTriggerLogger sEventLogger = new SoundTriggerLogger(200,
1295 "SoundTrigger activity");
1296
Arunesh Mishraa772e5f2016-01-25 10:33:11 -08001297}