blob: 13e3069b302b92b4910d14bfe55df456ed34ea27 [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;
Chris Thorntonba08b792017-06-08 22:34:37 -070035import android.app.PendingIntent;
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -080036import android.content.ComponentName;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -080037import android.content.Context;
Chris Thorntonba08b792017-06-08 22:34:37 -070038import android.content.Intent;
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -080039import android.content.ServiceConnection;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -080040import android.content.pm.PackageManager;
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -080041import android.content.pm.ResolveInfo;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -080042import android.hardware.soundtrigger.IRecognitionStatusCallback;
43import android.hardware.soundtrigger.SoundTrigger;
Arunesh Mishrac722ec412016-01-27 13:29:12 -080044import android.hardware.soundtrigger.SoundTrigger.GenericSoundModel;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -080045import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel;
Arunesh Mishra55a9b002016-02-01 14:06:37 -080046import android.hardware.soundtrigger.SoundTrigger.ModuleProperties;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -080047import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig;
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -080048import android.hardware.soundtrigger.SoundTrigger.SoundModel;
Philip P. Moltmanna5b44032018-05-04 13:59:45 -070049import android.media.AudioAttributes;
50import android.media.AudioFormat;
51import android.media.AudioRecord;
52import android.media.MediaRecorder;
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -080053import android.media.soundtrigger.ISoundTriggerDetectionService;
54import android.media.soundtrigger.ISoundTriggerDetectionServiceClient;
55import android.media.soundtrigger.SoundTriggerDetectionService;
Chris Thorntonba08b792017-06-08 22:34:37 -070056import android.media.soundtrigger.SoundTriggerManager;
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -080057import android.os.Binder;
Chris Thorntonba08b792017-06-08 22:34:37 -070058import android.os.Bundle;
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -080059import android.os.Handler;
60import android.os.IBinder;
61import android.os.Looper;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -080062import android.os.Parcel;
63import android.os.ParcelUuid;
Chris Thorntonba08b792017-06-08 22:34:37 -070064import android.os.PowerManager;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -080065import android.os.RemoteException;
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -080066import android.os.UserHandle;
67import android.provider.Settings;
Philip P. Moltmann7e25b3d2018-03-09 20:22:58 -080068import android.util.ArrayMap;
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -080069import android.util.ArraySet;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -080070import android.util.Slog;
71
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -080072import com.android.internal.annotations.GuardedBy;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -080073import com.android.internal.app.ISoundTriggerService;
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -080074import com.android.internal.util.DumpUtils;
75import com.android.internal.util.Preconditions;
76import com.android.server.SystemService;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -080077
78import java.io.FileDescriptor;
79import java.io.PrintWriter;
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -080080import java.util.ArrayList;
Chris Thorntonba08b792017-06-08 22:34:37 -070081import java.util.TreeMap;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -080082import java.util.UUID;
Philip P. Moltmann7e25b3d2018-03-09 20:22:58 -080083import java.util.concurrent.TimeUnit;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -080084
85/**
86 * A single SystemService to manage all sound/voice-based sound models on the DSP.
87 * This services provides apis to manage sound trigger-based sound models via
88 * the ISoundTriggerService interface. This class also publishes a local interface encapsulating
89 * the functionality provided by {@link SoundTriggerHelper} for use by
90 * {@link VoiceInteractionManagerService}.
91 *
92 * @hide
93 */
94public class SoundTriggerService extends SystemService {
Arunesh Mishra3fff7f52016-02-09 12:15:19 -080095 private static final String TAG = "SoundTriggerService";
96 private static final boolean DEBUG = true;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -080097
98 final Context mContext;
Chris Thorntonba08b792017-06-08 22:34:37 -070099 private Object mLock;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800100 private final SoundTriggerServiceStub mServiceStub;
101 private final LocalSoundTriggerService mLocalSoundTriggerService;
102 private SoundTriggerDbHelper mDbHelper;
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800103 private SoundTriggerHelper mSoundTriggerHelper;
Chris Thorntonba08b792017-06-08 22:34:37 -0700104 private final TreeMap<UUID, SoundModel> mLoadedModels;
Chris Thorntonae5fb992017-12-07 18:26:31 -0800105 private Object mCallbacksLock;
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800106 private final TreeMap<UUID, IRecognitionStatusCallback> mCallbacks;
Chris Thorntonba08b792017-06-08 22:34:37 -0700107 private PowerManager.WakeLock mWakelock;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800108
Philip P. Moltmann7e25b3d2018-03-09 20:22:58 -0800109 /** Number of ops run by the {@link RemoteSoundTriggerDetectionService} per package name */
110 @GuardedBy("mLock")
111 private final ArrayMap<String, NumOps> mNumOpsPerPackage = new ArrayMap<>();
112
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800113 public SoundTriggerService(Context context) {
114 super(context);
115 mContext = context;
116 mServiceStub = new SoundTriggerServiceStub();
117 mLocalSoundTriggerService = new LocalSoundTriggerService(context);
Chris Thorntonba08b792017-06-08 22:34:37 -0700118 mLoadedModels = new TreeMap<UUID, SoundModel>();
Chris Thorntonae5fb992017-12-07 18:26:31 -0800119 mCallbacksLock = new Object();
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800120 mCallbacks = new TreeMap<>();
Chris Thorntonba08b792017-06-08 22:34:37 -0700121 mLock = new Object();
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800122 }
123
124 @Override
125 public void onStart() {
126 publishBinderService(Context.SOUND_TRIGGER_SERVICE, mServiceStub);
127 publishLocalService(SoundTriggerInternal.class, mLocalSoundTriggerService);
128 }
129
130 @Override
131 public void onBootPhase(int phase) {
132 if (PHASE_SYSTEM_SERVICES_READY == phase) {
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800133 initSoundTriggerHelper();
134 mLocalSoundTriggerService.setSoundTriggerHelper(mSoundTriggerHelper);
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800135 } else if (PHASE_THIRD_PARTY_APPS_CAN_START == phase) {
136 mDbHelper = new SoundTriggerDbHelper(mContext);
137 }
138 }
139
140 @Override
141 public void onStartUser(int userHandle) {
142 }
143
144 @Override
145 public void onSwitchUser(int userHandle) {
146 }
147
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800148 private synchronized void initSoundTriggerHelper() {
149 if (mSoundTriggerHelper == null) {
150 mSoundTriggerHelper = new SoundTriggerHelper(mContext);
151 }
152 }
153
154 private synchronized boolean isInitialized() {
155 if (mSoundTriggerHelper == null ) {
156 Slog.e(TAG, "SoundTriggerHelper not initialized.");
157 return false;
158 }
159 return true;
160 }
161
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800162 class SoundTriggerServiceStub extends ISoundTriggerService.Stub {
163 @Override
164 public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
165 throws RemoteException {
166 try {
167 return super.onTransact(code, data, reply, flags);
168 } catch (RuntimeException e) {
169 // The activity manager only throws security exceptions, so let's
170 // log all others.
171 if (!(e instanceof SecurityException)) {
172 Slog.wtf(TAG, "SoundTriggerService Crash", e);
173 }
174 throw e;
175 }
176 }
177
178 @Override
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800179 public int startRecognition(ParcelUuid parcelUuid, IRecognitionStatusCallback callback,
180 RecognitionConfig config) {
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800181 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
Arunesh Mishra2d1de782016-02-21 18:10:28 -0800182 if (!isInitialized()) return STATUS_ERROR;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800183 if (DEBUG) {
184 Slog.i(TAG, "startRecognition(): Uuid : " + parcelUuid);
185 }
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800186
187 GenericSoundModel model = getSoundModel(parcelUuid);
188 if (model == null) {
189 Slog.e(TAG, "Null model in database for id: " + parcelUuid);
190 return STATUS_ERROR;
191 }
192
193 return mSoundTriggerHelper.startGenericRecognition(parcelUuid.getUuid(), model,
194 callback, config);
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800195 }
196
197 @Override
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800198 public int stopRecognition(ParcelUuid parcelUuid, IRecognitionStatusCallback callback) {
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800199 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
200 if (DEBUG) {
201 Slog.i(TAG, "stopRecognition(): Uuid : " + parcelUuid);
202 }
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800203 if (!isInitialized()) return STATUS_ERROR;
204 return mSoundTriggerHelper.stopGenericRecognition(parcelUuid.getUuid(), callback);
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800205 }
206
207 @Override
Arunesh Mishrac722ec412016-01-27 13:29:12 -0800208 public SoundTrigger.GenericSoundModel getSoundModel(ParcelUuid soundModelId) {
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800209 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
210 if (DEBUG) {
211 Slog.i(TAG, "getSoundModel(): id = " + soundModelId);
212 }
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800213 SoundTrigger.GenericSoundModel model = mDbHelper.getGenericSoundModel(
214 soundModelId.getUuid());
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800215 return model;
216 }
217
218 @Override
Arunesh Mishrac722ec412016-01-27 13:29:12 -0800219 public void updateSoundModel(SoundTrigger.GenericSoundModel soundModel) {
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800220 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
221 if (DEBUG) {
222 Slog.i(TAG, "updateSoundModel(): model = " + soundModel);
223 }
Arunesh Mishrac722ec412016-01-27 13:29:12 -0800224 mDbHelper.updateGenericSoundModel(soundModel);
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800225 }
226
227 @Override
228 public void deleteSoundModel(ParcelUuid soundModelId) {
229 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
230 if (DEBUG) {
231 Slog.i(TAG, "deleteSoundModel(): id = " + soundModelId);
232 }
Arunesh Mishra2d1de782016-02-21 18:10:28 -0800233 // Unload the model if it is loaded.
234 mSoundTriggerHelper.unloadGenericSoundModel(soundModelId.getUuid());
Arunesh Mishrac722ec412016-01-27 13:29:12 -0800235 mDbHelper.deleteGenericSoundModel(soundModelId.getUuid());
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800236 }
Chris Thorntonba08b792017-06-08 22:34:37 -0700237
238 @Override
239 public int loadGenericSoundModel(GenericSoundModel soundModel) {
240 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
241 if (!isInitialized()) return STATUS_ERROR;
242 if (soundModel == null || soundModel.uuid == null) {
243 Slog.e(TAG, "Invalid sound model");
244 return STATUS_ERROR;
245 }
246 if (DEBUG) {
247 Slog.i(TAG, "loadGenericSoundModel(): id = " + soundModel.uuid);
248 }
249 synchronized (mLock) {
250 SoundModel oldModel = mLoadedModels.get(soundModel.uuid);
251 // If the model we're loading is actually different than what we had loaded, we
252 // should unload that other model now. We don't care about return codes since we
253 // don't know if the other model is loaded.
254 if (oldModel != null && !oldModel.equals(soundModel)) {
255 mSoundTriggerHelper.unloadGenericSoundModel(soundModel.uuid);
Chris Thorntonae5fb992017-12-07 18:26:31 -0800256 synchronized (mCallbacksLock) {
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800257 mCallbacks.remove(soundModel.uuid);
Chris Thorntonae5fb992017-12-07 18:26:31 -0800258 }
Chris Thorntonba08b792017-06-08 22:34:37 -0700259 }
260 mLoadedModels.put(soundModel.uuid, soundModel);
261 }
262 return STATUS_OK;
263 }
264
265 @Override
266 public int loadKeyphraseSoundModel(KeyphraseSoundModel soundModel) {
267 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
268 if (!isInitialized()) return STATUS_ERROR;
269 if (soundModel == null || soundModel.uuid == null) {
270 Slog.e(TAG, "Invalid sound model");
271 return STATUS_ERROR;
272 }
273 if (soundModel.keyphrases == null || soundModel.keyphrases.length != 1) {
274 Slog.e(TAG, "Only one keyphrase per model is currently supported.");
275 return STATUS_ERROR;
276 }
277 if (DEBUG) {
278 Slog.i(TAG, "loadKeyphraseSoundModel(): id = " + soundModel.uuid);
279 }
280 synchronized (mLock) {
281 SoundModel oldModel = mLoadedModels.get(soundModel.uuid);
282 // If the model we're loading is actually different than what we had loaded, we
283 // should unload that other model now. We don't care about return codes since we
284 // don't know if the other model is loaded.
285 if (oldModel != null && !oldModel.equals(soundModel)) {
286 mSoundTriggerHelper.unloadKeyphraseSoundModel(soundModel.keyphrases[0].id);
Chris Thorntonae5fb992017-12-07 18:26:31 -0800287 synchronized (mCallbacksLock) {
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800288 mCallbacks.remove(soundModel.uuid);
Chris Thorntonae5fb992017-12-07 18:26:31 -0800289 }
Chris Thorntonba08b792017-06-08 22:34:37 -0700290 }
291 mLoadedModels.put(soundModel.uuid, soundModel);
292 }
293 return STATUS_OK;
294 }
295
296 @Override
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800297 public int startRecognitionForService(ParcelUuid soundModelId, Bundle params,
298 ComponentName detectionService, SoundTrigger.RecognitionConfig config) {
299 Preconditions.checkNotNull(soundModelId);
300 Preconditions.checkNotNull(detectionService);
301 Preconditions.checkNotNull(config);
302
303 return startRecognitionForInt(soundModelId,
304 new RemoteSoundTriggerDetectionService(soundModelId.getUuid(),
305 params, detectionService, Binder.getCallingUserHandle(), config), config);
306
307 }
308
309 @Override
Chris Thorntonba08b792017-06-08 22:34:37 -0700310 public int startRecognitionForIntent(ParcelUuid soundModelId, PendingIntent callbackIntent,
311 SoundTrigger.RecognitionConfig config) {
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800312 return startRecognitionForInt(soundModelId,
313 new LocalSoundTriggerRecognitionStatusIntentCallback(soundModelId.getUuid(),
314 callbackIntent, config), config);
315 }
316
317 private int startRecognitionForInt(ParcelUuid soundModelId,
318 IRecognitionStatusCallback callback, SoundTrigger.RecognitionConfig config) {
Chris Thorntonba08b792017-06-08 22:34:37 -0700319 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
320 if (!isInitialized()) return STATUS_ERROR;
321 if (DEBUG) {
322 Slog.i(TAG, "startRecognition(): id = " + soundModelId);
323 }
324
325 synchronized (mLock) {
326 SoundModel soundModel = mLoadedModels.get(soundModelId.getUuid());
327 if (soundModel == null) {
328 Slog.e(TAG, soundModelId + " is not loaded");
329 return STATUS_ERROR;
330 }
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800331 IRecognitionStatusCallback existingCallback = null;
Chris Thorntonae5fb992017-12-07 18:26:31 -0800332 synchronized (mCallbacksLock) {
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800333 existingCallback = mCallbacks.get(soundModelId.getUuid());
Chris Thorntonae5fb992017-12-07 18:26:31 -0800334 }
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800335 if (existingCallback != null) {
Chris Thorntonba08b792017-06-08 22:34:37 -0700336 Slog.e(TAG, soundModelId + " is already running");
337 return STATUS_ERROR;
338 }
Chris Thorntonba08b792017-06-08 22:34:37 -0700339 int ret;
340 switch (soundModel.type) {
341 case SoundModel.TYPE_KEYPHRASE: {
342 KeyphraseSoundModel keyphraseSoundModel = (KeyphraseSoundModel) soundModel;
343 ret = mSoundTriggerHelper.startKeyphraseRecognition(
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800344 keyphraseSoundModel.keyphrases[0].id, keyphraseSoundModel, callback,
345 config);
Chris Thorntonba08b792017-06-08 22:34:37 -0700346 } break;
347 case SoundModel.TYPE_GENERIC_SOUND:
348 ret = mSoundTriggerHelper.startGenericRecognition(soundModel.uuid,
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800349 (GenericSoundModel) soundModel, callback, config);
Chris Thorntonba08b792017-06-08 22:34:37 -0700350 break;
351 default:
352 Slog.e(TAG, "Unknown model type");
353 return STATUS_ERROR;
354 }
355
356 if (ret != STATUS_OK) {
357 Slog.e(TAG, "Failed to start model: " + ret);
358 return ret;
359 }
Chris Thorntonae5fb992017-12-07 18:26:31 -0800360 synchronized (mCallbacksLock) {
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800361 mCallbacks.put(soundModelId.getUuid(), callback);
Chris Thorntonae5fb992017-12-07 18:26:31 -0800362 }
Chris Thorntonba08b792017-06-08 22:34:37 -0700363 }
364 return STATUS_OK;
365 }
366
367 @Override
368 public int stopRecognitionForIntent(ParcelUuid soundModelId) {
369 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
370 if (!isInitialized()) return STATUS_ERROR;
371 if (DEBUG) {
372 Slog.i(TAG, "stopRecognition(): id = " + soundModelId);
373 }
374
375 synchronized (mLock) {
376 SoundModel soundModel = mLoadedModels.get(soundModelId.getUuid());
377 if (soundModel == null) {
378 Slog.e(TAG, soundModelId + " is not loaded");
379 return STATUS_ERROR;
380 }
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800381 IRecognitionStatusCallback callback = null;
Chris Thorntonae5fb992017-12-07 18:26:31 -0800382 synchronized (mCallbacksLock) {
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800383 callback = mCallbacks.get(soundModelId.getUuid());
Chris Thorntonae5fb992017-12-07 18:26:31 -0800384 }
Chris Thorntonba08b792017-06-08 22:34:37 -0700385 if (callback == null) {
386 Slog.e(TAG, soundModelId + " is not running");
387 return STATUS_ERROR;
388 }
389 int ret;
390 switch (soundModel.type) {
391 case SoundModel.TYPE_KEYPHRASE:
392 ret = mSoundTriggerHelper.stopKeyphraseRecognition(
393 ((KeyphraseSoundModel)soundModel).keyphrases[0].id, callback);
394 break;
395 case SoundModel.TYPE_GENERIC_SOUND:
396 ret = mSoundTriggerHelper.stopGenericRecognition(soundModel.uuid, callback);
397 break;
398 default:
399 Slog.e(TAG, "Unknown model type");
400 return STATUS_ERROR;
401 }
402
403 if (ret != STATUS_OK) {
404 Slog.e(TAG, "Failed to stop model: " + ret);
405 return ret;
406 }
Chris Thorntonae5fb992017-12-07 18:26:31 -0800407 synchronized (mCallbacksLock) {
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800408 mCallbacks.remove(soundModelId.getUuid());
Chris Thorntonae5fb992017-12-07 18:26:31 -0800409 }
Chris Thorntonba08b792017-06-08 22:34:37 -0700410 }
411 return STATUS_OK;
412 }
413
414 @Override
415 public int unloadSoundModel(ParcelUuid soundModelId) {
416 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
417 if (!isInitialized()) return STATUS_ERROR;
418 if (DEBUG) {
419 Slog.i(TAG, "unloadSoundModel(): id = " + soundModelId);
420 }
421
422 synchronized (mLock) {
423 SoundModel soundModel = mLoadedModels.get(soundModelId.getUuid());
424 if (soundModel == null) {
425 Slog.e(TAG, soundModelId + " is not loaded");
426 return STATUS_ERROR;
427 }
428 int ret;
429 switch (soundModel.type) {
430 case SoundModel.TYPE_KEYPHRASE:
431 ret = mSoundTriggerHelper.unloadKeyphraseSoundModel(
432 ((KeyphraseSoundModel)soundModel).keyphrases[0].id);
433 break;
434 case SoundModel.TYPE_GENERIC_SOUND:
435 ret = mSoundTriggerHelper.unloadGenericSoundModel(soundModel.uuid);
436 break;
437 default:
438 Slog.e(TAG, "Unknown model type");
439 return STATUS_ERROR;
440 }
441 if (ret != STATUS_OK) {
442 Slog.e(TAG, "Failed to unload model");
443 return ret;
444 }
445 mLoadedModels.remove(soundModelId.getUuid());
446 return STATUS_OK;
447 }
448 }
449
450 @Override
451 public boolean isRecognitionActive(ParcelUuid parcelUuid) {
452 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
453 if (!isInitialized()) return false;
Chris Thorntonae5fb992017-12-07 18:26:31 -0800454 synchronized (mCallbacksLock) {
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800455 IRecognitionStatusCallback callback = mCallbacks.get(parcelUuid.getUuid());
Chris Thorntonba08b792017-06-08 22:34:37 -0700456 if (callback == null) {
457 return false;
458 }
Chris Thorntonba08b792017-06-08 22:34:37 -0700459 }
Chris Thorntonae5fb992017-12-07 18:26:31 -0800460 return mSoundTriggerHelper.isRecognitionRequested(parcelUuid.getUuid());
Chris Thorntonba08b792017-06-08 22:34:37 -0700461 }
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800462 }
463
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800464 private final class LocalSoundTriggerRecognitionStatusIntentCallback
Chris Thorntonba08b792017-06-08 22:34:37 -0700465 extends IRecognitionStatusCallback.Stub {
466 private UUID mUuid;
467 private PendingIntent mCallbackIntent;
468 private RecognitionConfig mRecognitionConfig;
469
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800470 public LocalSoundTriggerRecognitionStatusIntentCallback(UUID modelUuid,
Chris Thorntonba08b792017-06-08 22:34:37 -0700471 PendingIntent callbackIntent,
472 RecognitionConfig config) {
473 mUuid = modelUuid;
474 mCallbackIntent = callbackIntent;
475 mRecognitionConfig = config;
476 }
477
478 @Override
479 public boolean pingBinder() {
480 return mCallbackIntent != null;
481 }
482
483 @Override
484 public void onKeyphraseDetected(SoundTrigger.KeyphraseRecognitionEvent event) {
485 if (mCallbackIntent == null) {
486 return;
487 }
488 grabWakeLock();
489
490 Slog.w(TAG, "Keyphrase sound trigger event: " + event);
491 Intent extras = new Intent();
492 extras.putExtra(SoundTriggerManager.EXTRA_MESSAGE_TYPE,
493 SoundTriggerManager.FLAG_MESSAGE_TYPE_RECOGNITION_EVENT);
494 extras.putExtra(SoundTriggerManager.EXTRA_RECOGNITION_EVENT, event);
495 try {
496 mCallbackIntent.send(mContext, 0, extras, mCallbackCompletedHandler, null);
497 if (!mRecognitionConfig.allowMultipleTriggers) {
498 removeCallback(/*releaseWakeLock=*/false);
499 }
500 } catch (PendingIntent.CanceledException e) {
501 removeCallback(/*releaseWakeLock=*/true);
502 }
503 }
504
505 @Override
506 public void onGenericSoundTriggerDetected(SoundTrigger.GenericRecognitionEvent event) {
507 if (mCallbackIntent == null) {
508 return;
509 }
510 grabWakeLock();
511
512 Slog.w(TAG, "Generic sound trigger event: " + event);
513 Intent extras = new Intent();
514 extras.putExtra(SoundTriggerManager.EXTRA_MESSAGE_TYPE,
515 SoundTriggerManager.FLAG_MESSAGE_TYPE_RECOGNITION_EVENT);
516 extras.putExtra(SoundTriggerManager.EXTRA_RECOGNITION_EVENT, event);
517 try {
518 mCallbackIntent.send(mContext, 0, extras, mCallbackCompletedHandler, null);
519 if (!mRecognitionConfig.allowMultipleTriggers) {
520 removeCallback(/*releaseWakeLock=*/false);
521 }
522 } catch (PendingIntent.CanceledException e) {
523 removeCallback(/*releaseWakeLock=*/true);
524 }
525 }
526
527 @Override
528 public void onError(int status) {
529 if (mCallbackIntent == null) {
530 return;
531 }
532 grabWakeLock();
533
534 Slog.i(TAG, "onError: " + status);
535 Intent extras = new Intent();
536 extras.putExtra(SoundTriggerManager.EXTRA_MESSAGE_TYPE,
537 SoundTriggerManager.FLAG_MESSAGE_TYPE_RECOGNITION_ERROR);
538 extras.putExtra(SoundTriggerManager.EXTRA_STATUS, status);
539 try {
540 mCallbackIntent.send(mContext, 0, extras, mCallbackCompletedHandler, null);
541 // Remove the callback, but wait for the intent to finish before we let go of the
542 // wake lock
543 removeCallback(/*releaseWakeLock=*/false);
544 } catch (PendingIntent.CanceledException e) {
545 removeCallback(/*releaseWakeLock=*/true);
546 }
547 }
548
549 @Override
550 public void onRecognitionPaused() {
551 if (mCallbackIntent == null) {
552 return;
553 }
554 grabWakeLock();
555
556 Slog.i(TAG, "onRecognitionPaused");
557 Intent extras = new Intent();
558 extras.putExtra(SoundTriggerManager.EXTRA_MESSAGE_TYPE,
559 SoundTriggerManager.FLAG_MESSAGE_TYPE_RECOGNITION_PAUSED);
560 try {
561 mCallbackIntent.send(mContext, 0, extras, mCallbackCompletedHandler, null);
562 } catch (PendingIntent.CanceledException e) {
563 removeCallback(/*releaseWakeLock=*/true);
564 }
565 }
566
567 @Override
568 public void onRecognitionResumed() {
569 if (mCallbackIntent == null) {
570 return;
571 }
572 grabWakeLock();
573
574 Slog.i(TAG, "onRecognitionResumed");
575 Intent extras = new Intent();
576 extras.putExtra(SoundTriggerManager.EXTRA_MESSAGE_TYPE,
577 SoundTriggerManager.FLAG_MESSAGE_TYPE_RECOGNITION_RESUMED);
578 try {
579 mCallbackIntent.send(mContext, 0, extras, mCallbackCompletedHandler, null);
580 } catch (PendingIntent.CanceledException e) {
581 removeCallback(/*releaseWakeLock=*/true);
582 }
583 }
584
585 private void removeCallback(boolean releaseWakeLock) {
586 mCallbackIntent = null;
Chris Thorntonae5fb992017-12-07 18:26:31 -0800587 synchronized (mCallbacksLock) {
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800588 mCallbacks.remove(mUuid);
Chris Thorntonba08b792017-06-08 22:34:37 -0700589 if (releaseWakeLock) {
590 mWakelock.release();
591 }
592 }
593 }
594 }
595
Philip P. Moltmann7e25b3d2018-03-09 20:22:58 -0800596 /**
597 * Counts the number of operations added in the last 24 hours.
598 */
599 private static class NumOps {
600 private final Object mLock = new Object();
601
602 @GuardedBy("mLock")
603 private int[] mNumOps = new int[24];
604 @GuardedBy("mLock")
605 private long mLastOpsHourSinceBoot;
606
607 /**
608 * Clear buckets of new hours that have elapsed since last operation.
609 *
610 * <p>I.e. when the last operation was triggered at 1:40 and the current operation was
611 * triggered at 4:03, the buckets "2, 3, and 4" are cleared.
612 *
613 * @param currentTime Current elapsed time since boot in ns
614 */
615 void clearOldOps(long currentTime) {
616 synchronized (mLock) {
617 long numHoursSinceBoot = TimeUnit.HOURS.convert(currentTime, TimeUnit.NANOSECONDS);
618
619 // Clear buckets of new hours that have elapsed since last operation
620 // I.e. when the last operation was triggered at 1:40 and the current
621 // operation was triggered at 4:03, the bucket "2, 3, and 4" is cleared
622 if (mLastOpsHourSinceBoot != 0) {
623 for (long hour = mLastOpsHourSinceBoot + 1; hour <= numHoursSinceBoot; hour++) {
624 mNumOps[(int) (hour % 24)] = 0;
625 }
626 }
627 }
628 }
629
630 /**
631 * Add a new operation.
632 *
633 * @param currentTime Current elapsed time since boot in ns
634 */
635 void addOp(long currentTime) {
636 synchronized (mLock) {
637 long numHoursSinceBoot = TimeUnit.HOURS.convert(currentTime, TimeUnit.NANOSECONDS);
638
639 mNumOps[(int) (numHoursSinceBoot % 24)]++;
640 mLastOpsHourSinceBoot = numHoursSinceBoot;
641 }
642 }
643
644 /**
645 * Get the total operations added in the last 24 hours.
646 *
647 * @return The total number of operations added in the last 24 hours
648 */
649 int getOpsAdded() {
650 synchronized (mLock) {
651 int totalOperationsInLastDay = 0;
652 for (int i = 0; i < 24; i++) {
653 totalOperationsInLastDay += mNumOps[i];
654 }
655
656 return totalOperationsInLastDay;
657 }
658 }
659 }
660
Philip P. Moltmanna5b44032018-05-04 13:59:45 -0700661 /**
662 * A single operation run in a {@link RemoteSoundTriggerDetectionService}.
663 *
664 * <p>Once the remote service is connected either setup + execute or setup + stop is executed.
665 */
666 private static class Operation {
667 private interface ExecuteOp {
668 void run(int opId, ISoundTriggerDetectionService service) throws RemoteException;
669 }
670
671 private final @Nullable Runnable mSetupOp;
672 private final @NonNull ExecuteOp mExecuteOp;
673 private final @Nullable Runnable mDropOp;
674
675 private Operation(@Nullable Runnable setupOp, @NonNull ExecuteOp executeOp,
676 @Nullable Runnable cancelOp) {
677 mSetupOp = setupOp;
678 mExecuteOp = executeOp;
679 mDropOp = cancelOp;
680 }
681
682 private void setup() {
683 if (mSetupOp != null) {
684 mSetupOp.run();
685 }
686 }
687
688 void run(int opId, @NonNull ISoundTriggerDetectionService service) throws RemoteException {
689 setup();
690 mExecuteOp.run(opId, service);
691 }
692
693 void drop() {
694 setup();
695
696 if (mDropOp != null) {
697 mDropOp.run();
698 }
699 }
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800700 }
701
702 /**
703 * Local end for a {@link SoundTriggerDetectionService}. Operations are queued up and executed
704 * when the service connects.
705 *
706 * <p>If operations take too long they are forcefully aborted.
707 *
708 * <p>This also limits the amount of operations in 24 hours.
709 */
710 private class RemoteSoundTriggerDetectionService
711 extends IRecognitionStatusCallback.Stub implements ServiceConnection {
712 private static final int MSG_STOP_ALL_PENDING_OPERATIONS = 1;
713
714 private final Object mRemoteServiceLock = new Object();
715
716 /** UUID of the model the service is started for */
717 private final @NonNull ParcelUuid mPuuid;
718 /** Params passed into the start method for the service */
719 private final @Nullable Bundle mParams;
720 /** Component name passed when starting the service */
721 private final @NonNull ComponentName mServiceName;
722 /** User that started the service */
723 private final @NonNull UserHandle mUser;
724 /** Configuration of the recognition the service is handling */
725 private final @NonNull RecognitionConfig mRecognitionConfig;
726 /** Wake lock keeping the remote service alive */
727 private final @NonNull PowerManager.WakeLock mRemoteServiceWakeLock;
728
729 private final @NonNull Handler mHandler;
730
731 /** Callbacks that are called by the service */
732 private final @NonNull ISoundTriggerDetectionServiceClient mClient;
733
734 /** Operations that are pending because the service is not yet connected */
735 @GuardedBy("mRemoteServiceLock")
736 private final ArrayList<Operation> mPendingOps = new ArrayList<>();
737 /** Operations that have been send to the service but have no yet finished */
738 @GuardedBy("mRemoteServiceLock")
739 private final ArraySet<Integer> mRunningOpIds = new ArraySet<>();
Philip P. Moltmann7e25b3d2018-03-09 20:22:58 -0800740 /** The number of operations executed in each of the last 24 hours */
741 private final NumOps mNumOps;
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800742
743 /** The service binder if connected */
744 @GuardedBy("mRemoteServiceLock")
745 private @Nullable ISoundTriggerDetectionService mService;
746 /** Whether the service has been bound */
747 @GuardedBy("mRemoteServiceLock")
748 private boolean mIsBound;
749 /** Whether the service has been destroyed */
750 @GuardedBy("mRemoteServiceLock")
751 private boolean mIsDestroyed;
752 /**
753 * Set once a final op is scheduled. No further ops can be added and the service is
754 * destroyed once the op finishes.
755 */
756 @GuardedBy("mRemoteServiceLock")
757 private boolean mDestroyOnceRunningOpsDone;
758
759 /** Total number of operations performed by this service */
760 @GuardedBy("mRemoteServiceLock")
761 private int mNumTotalOpsPerformed;
762
763 /**
764 * Create a new remote sound trigger detection service. This only binds to the service when
765 * operations are in flight. Each operation has a certain time it can run. Once no
766 * operations are allowed to run anymore, {@link #stopAllPendingOperations() all operations
767 * are aborted and stopped} and the service is disconnected.
768 *
769 * @param modelUuid The UUID of the model the recognition is for
770 * @param params The params passed to each method of the service
771 * @param serviceName The component name of the service
772 * @param user The user of the service
773 * @param config The configuration of the recognition
774 */
775 public RemoteSoundTriggerDetectionService(@NonNull UUID modelUuid,
776 @Nullable Bundle params, @NonNull ComponentName serviceName, @NonNull UserHandle user,
777 @NonNull RecognitionConfig config) {
778 mPuuid = new ParcelUuid(modelUuid);
779 mParams = params;
780 mServiceName = serviceName;
781 mUser = user;
782 mRecognitionConfig = config;
783 mHandler = new Handler(Looper.getMainLooper());
784
785 PowerManager pm = ((PowerManager) mContext.getSystemService(Context.POWER_SERVICE));
786 mRemoteServiceWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
787 "RemoteSoundTriggerDetectionService " + mServiceName.getPackageName() + ":"
788 + mServiceName.getClassName());
789
Philip P. Moltmann7e25b3d2018-03-09 20:22:58 -0800790 synchronized (mLock) {
791 NumOps numOps = mNumOpsPerPackage.get(mServiceName.getPackageName());
792 if (numOps == null) {
793 numOps = new NumOps();
794 mNumOpsPerPackage.put(mServiceName.getPackageName(), numOps);
795 }
796 mNumOps = numOps;
797 }
798
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800799 mClient = new ISoundTriggerDetectionServiceClient.Stub() {
800 @Override
801 public void onOpFinished(int opId) {
802 long token = Binder.clearCallingIdentity();
803 try {
804 synchronized (mRemoteServiceLock) {
805 mRunningOpIds.remove(opId);
806
807 if (mRunningOpIds.isEmpty() && mPendingOps.isEmpty()) {
808 if (mDestroyOnceRunningOpsDone) {
809 destroy();
810 } else {
811 disconnectLocked();
812 }
813 }
814 }
815 } finally {
816 Binder.restoreCallingIdentity(token);
817 }
818 }
819 };
820 }
821
822 @Override
823 public boolean pingBinder() {
824 return !(mIsDestroyed || mDestroyOnceRunningOpsDone);
825 }
826
827 /**
828 * Disconnect from the service, but allow to re-connect when new operations are triggered.
829 */
830 private void disconnectLocked() {
831 if (mService != null) {
832 try {
833 mService.removeClient(mPuuid);
834 } catch (Exception e) {
835 Slog.e(TAG, mPuuid + ": Cannot remove client", e);
836 }
837
838 mService = null;
839 }
840
841 if (mIsBound) {
842 mContext.unbindService(RemoteSoundTriggerDetectionService.this);
843 mIsBound = false;
844
845 synchronized (mCallbacksLock) {
846 mRemoteServiceWakeLock.release();
847 }
848 }
849 }
850
851 /**
852 * Disconnect, do not allow to reconnect to the service. All further operations will be
853 * dropped.
854 */
855 private void destroy() {
856 if (DEBUG) Slog.v(TAG, mPuuid + ": destroy");
857
858 synchronized (mRemoteServiceLock) {
859 disconnectLocked();
860
861 mIsDestroyed = true;
862 }
863
864 // The callback is removed before the flag is set
865 if (!mDestroyOnceRunningOpsDone) {
866 synchronized (mCallbacksLock) {
867 mCallbacks.remove(mPuuid.getUuid());
868 }
869 }
870 }
871
872 /**
873 * Stop all pending operations and then disconnect for the service.
874 */
875 private void stopAllPendingOperations() {
876 synchronized (mRemoteServiceLock) {
877 if (mIsDestroyed) {
878 return;
879 }
880
881 if (mService != null) {
882 int numOps = mRunningOpIds.size();
883 for (int i = 0; i < numOps; i++) {
884 try {
885 mService.onStopOperation(mPuuid, mRunningOpIds.valueAt(i));
886 } catch (Exception e) {
887 Slog.e(TAG, mPuuid + ": Could not stop operation "
888 + mRunningOpIds.valueAt(i), e);
889 }
890 }
891
892 mRunningOpIds.clear();
893 }
894
895 disconnectLocked();
896 }
897 }
898
899 /**
900 * Verify that the service has the expected properties and then bind to the service
901 */
902 private void bind() {
903 long token = Binder.clearCallingIdentity();
904 try {
905 Intent i = new Intent();
906 i.setComponent(mServiceName);
907
908 ResolveInfo ri = mContext.getPackageManager().resolveServiceAsUser(i,
909 GET_SERVICES | GET_META_DATA | MATCH_DEBUG_TRIAGED_MISSING,
910 mUser.getIdentifier());
911
912 if (ri == null) {
913 Slog.w(TAG, mPuuid + ": " + mServiceName + " not found");
914 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);
921 return;
922 }
923
924 mIsBound = mContext.bindServiceAsUser(i, this,
925 BIND_AUTO_CREATE | BIND_FOREGROUND_SERVICE, mUser);
926
927 if (mIsBound) {
928 mRemoteServiceWakeLock.acquire();
929 } else {
930 Slog.w(TAG, mPuuid + ": Could not bind to " + mServiceName);
931 }
932 } finally {
933 Binder.restoreCallingIdentity(token);
934 }
935 }
936
937 /**
938 * Run an operation (i.e. send it do the service). If the service is not connected, this
939 * binds the service and then runs the operation once connected.
940 *
941 * @param op The operation to run
942 */
943 private void runOrAddOperation(Operation op) {
944 synchronized (mRemoteServiceLock) {
945 if (mIsDestroyed || mDestroyOnceRunningOpsDone) {
Philip P. Moltmanna5b44032018-05-04 13:59:45 -0700946 Slog.w(TAG, mPuuid + ": Dropped operation as already destroyed or marked for "
947 + "destruction");
948
949 op.drop();
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800950 return;
951 }
952
953 if (mService == null) {
954 mPendingOps.add(op);
955
956 if (!mIsBound) {
957 bind();
958 }
959 } else {
Philip P. Moltmann7e25b3d2018-03-09 20:22:58 -0800960 long currentTime = System.nanoTime();
961 mNumOps.clearOldOps(currentTime);
962
963 // Drop operation if too many were executed in the last 24 hours.
964 int opsAllowed = Settings.Global.getInt(mContext.getContentResolver(),
965 MAX_SOUND_TRIGGER_DETECTION_SERVICE_OPS_PER_DAY,
966 Integer.MAX_VALUE);
967
968 int opsAdded = mNumOps.getOpsAdded();
969 if (mNumOps.getOpsAdded() >= opsAllowed) {
Philip P. Moltmanna5b44032018-05-04 13:59:45 -0700970 try {
971 if (DEBUG || opsAllowed + 10 > opsAdded) {
972 Slog.w(TAG, mPuuid + ": Dropped operation as too many operations "
973 + "were run in last 24 hours");
974 }
975
976 op.drop();
977 } catch (Exception e) {
978 Slog.e(TAG, mPuuid + ": Could not drop operation", e);
Philip P. Moltmann7e25b3d2018-03-09 20:22:58 -0800979 }
Philip P. Moltmann09dd8c42018-04-11 09:23:19 -0700980 } else {
981 mNumOps.addOp(currentTime);
Philip P. Moltmann7e25b3d2018-03-09 20:22:58 -0800982
Philip P. Moltmann09dd8c42018-04-11 09:23:19 -0700983 // Find a free opID
984 int opId = mNumTotalOpsPerformed;
985 do {
986 mNumTotalOpsPerformed++;
987 } while (mRunningOpIds.contains(opId));
Philip P. Moltmann7e25b3d2018-03-09 20:22:58 -0800988
Philip P. Moltmann09dd8c42018-04-11 09:23:19 -0700989 // Run OP
990 try {
991 if (DEBUG) Slog.v(TAG, mPuuid + ": runOp " + opId);
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800992
Philip P. Moltmann09dd8c42018-04-11 09:23:19 -0700993 op.run(opId, mService);
994 mRunningOpIds.add(opId);
995 } catch (Exception e) {
996 Slog.e(TAG, mPuuid + ": Could not run operation " + opId, e);
997 }
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800998 }
999
1000 // Unbind from service if no operations are left (i.e. if the operation failed)
1001 if (mPendingOps.isEmpty() && mRunningOpIds.isEmpty()) {
1002 if (mDestroyOnceRunningOpsDone) {
1003 destroy();
1004 } else {
1005 disconnectLocked();
1006 }
1007 } else {
1008 mHandler.removeMessages(MSG_STOP_ALL_PENDING_OPERATIONS);
1009 mHandler.sendMessageDelayed(obtainMessage(
1010 RemoteSoundTriggerDetectionService::stopAllPendingOperations, this)
1011 .setWhat(MSG_STOP_ALL_PENDING_OPERATIONS),
1012 Settings.Global.getLong(mContext.getContentResolver(),
1013 SOUND_TRIGGER_DETECTION_SERVICE_OP_TIMEOUT,
1014 Long.MAX_VALUE));
1015 }
1016 }
1017 }
1018 }
1019
1020 @Override
1021 public void onKeyphraseDetected(SoundTrigger.KeyphraseRecognitionEvent event) {
1022 Slog.w(TAG, mPuuid + "->" + mServiceName + ": IGNORED onKeyphraseDetected(" + event
1023 + ")");
1024 }
1025
Philip P. Moltmanna5b44032018-05-04 13:59:45 -07001026 /**
1027 * Create an AudioRecord enough for starting and releasing the data buffered for the event.
1028 *
1029 * @param event The event that was received
1030 * @return The initialized AudioRecord
1031 */
1032 private @NonNull AudioRecord createAudioRecordForEvent(
1033 @NonNull SoundTrigger.GenericRecognitionEvent event) {
1034 AudioAttributes.Builder attributesBuilder = new AudioAttributes.Builder();
1035 attributesBuilder.setInternalCapturePreset(MediaRecorder.AudioSource.HOTWORD);
1036 AudioAttributes attributes = attributesBuilder.build();
1037
1038 // Use same AudioFormat processing as in RecognitionEvent.fromParcel
1039 AudioFormat originalFormat = event.getCaptureFormat();
1040 AudioFormat captureFormat = (new AudioFormat.Builder())
1041 .setChannelMask(originalFormat.getChannelMask())
1042 .setEncoding(originalFormat.getEncoding())
1043 .setSampleRate(originalFormat.getSampleRate())
1044 .build();
1045
1046 int bufferSize = AudioRecord.getMinBufferSize(
1047 captureFormat.getSampleRate() == AudioFormat.SAMPLE_RATE_UNSPECIFIED
1048 ? AudioFormat.SAMPLE_RATE_HZ_MAX
1049 : captureFormat.getSampleRate(),
1050 captureFormat.getChannelCount() == 2
1051 ? AudioFormat.CHANNEL_IN_STEREO
1052 : AudioFormat.CHANNEL_IN_MONO,
1053 captureFormat.getEncoding());
1054
1055 return new AudioRecord(attributes, captureFormat, bufferSize,
1056 event.getCaptureSession());
1057 }
1058
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -08001059 @Override
1060 public void onGenericSoundTriggerDetected(SoundTrigger.GenericRecognitionEvent event) {
1061 if (DEBUG) Slog.v(TAG, mPuuid + ": Generic sound trigger event: " + event);
1062
Philip P. Moltmanna5b44032018-05-04 13:59:45 -07001063 runOrAddOperation(new Operation(
1064 // always execute:
1065 () -> {
1066 if (!mRecognitionConfig.allowMultipleTriggers) {
1067 // Unregister this remoteService once op is done
1068 synchronized (mCallbacksLock) {
1069 mCallbacks.remove(mPuuid.getUuid());
1070 }
1071 mDestroyOnceRunningOpsDone = true;
1072 }
1073 },
1074 // execute if not throttled:
1075 (opId, service) -> service.onGenericRecognitionEvent(mPuuid, opId, event),
1076 // execute if throttled:
1077 () -> {
1078 if (event.isCaptureAvailable()) {
1079 AudioRecord capturedData = createAudioRecordForEvent(event);
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -08001080
Philip P. Moltmanna5b44032018-05-04 13:59:45 -07001081 // Currently we need to start and release the audio record to reset
1082 // the DSP even if we don't want to process the event
1083 capturedData.startRecording();
1084 capturedData.release();
1085 }
1086 }));
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -08001087 }
1088
1089 @Override
1090 public void onError(int status) {
1091 if (DEBUG) Slog.v(TAG, mPuuid + ": onError: " + status);
1092
Philip P. Moltmanna5b44032018-05-04 13:59:45 -07001093 runOrAddOperation(
1094 new Operation(
1095 // always execute:
1096 () -> {
1097 // Unregister this remoteService once op is done
1098 synchronized (mCallbacksLock) {
1099 mCallbacks.remove(mPuuid.getUuid());
1100 }
1101 mDestroyOnceRunningOpsDone = true;
1102 },
1103 // execute if not throttled:
1104 (opId, service) -> service.onError(mPuuid, opId, status),
1105 // nothing to do if throttled
1106 null));
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -08001107 }
1108
1109 @Override
1110 public void onRecognitionPaused() {
1111 Slog.i(TAG, mPuuid + "->" + mServiceName + ": IGNORED onRecognitionPaused");
1112 }
1113
1114 @Override
1115 public void onRecognitionResumed() {
1116 Slog.i(TAG, mPuuid + "->" + mServiceName + ": IGNORED onRecognitionResumed");
1117 }
1118
1119 @Override
1120 public void onServiceConnected(ComponentName name, IBinder service) {
1121 if (DEBUG) Slog.v(TAG, mPuuid + ": onServiceConnected(" + service + ")");
1122
1123 synchronized (mRemoteServiceLock) {
1124 mService = ISoundTriggerDetectionService.Stub.asInterface(service);
1125
1126 try {
1127 mService.setClient(mPuuid, mParams, mClient);
1128 } catch (Exception e) {
1129 Slog.e(TAG, mPuuid + ": Could not init " + mServiceName, e);
1130 return;
1131 }
1132
1133 while (!mPendingOps.isEmpty()) {
1134 runOrAddOperation(mPendingOps.remove(0));
1135 }
1136 }
1137 }
1138
1139 @Override
1140 public void onServiceDisconnected(ComponentName name) {
1141 if (DEBUG) Slog.v(TAG, mPuuid + ": onServiceDisconnected");
1142
1143 synchronized (mRemoteServiceLock) {
1144 mService = null;
1145 }
1146 }
1147
1148 @Override
1149 public void onBindingDied(ComponentName name) {
1150 if (DEBUG) Slog.v(TAG, mPuuid + ": onBindingDied");
1151
1152 synchronized (mRemoteServiceLock) {
1153 destroy();
1154 }
1155 }
1156
1157 @Override
1158 public void onNullBinding(ComponentName name) {
1159 Slog.w(TAG, name + " for model " + mPuuid + " returned a null binding");
1160
1161 synchronized (mRemoteServiceLock) {
1162 disconnectLocked();
1163 }
1164 }
1165 }
1166
Chris Thorntonba08b792017-06-08 22:34:37 -07001167 private void grabWakeLock() {
Chris Thorntonae5fb992017-12-07 18:26:31 -08001168 synchronized (mCallbacksLock) {
Chris Thorntonba08b792017-06-08 22:34:37 -07001169 if (mWakelock == null) {
1170 PowerManager pm = ((PowerManager) mContext.getSystemService(Context.POWER_SERVICE));
1171 mWakelock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
1172 }
1173 mWakelock.acquire();
1174 }
1175 }
1176
1177 private PendingIntent.OnFinished mCallbackCompletedHandler = new PendingIntent.OnFinished() {
1178 @Override
1179 public void onSendFinished(PendingIntent pendingIntent, Intent intent, int resultCode,
1180 String resultData, Bundle resultExtras) {
1181 // We're only ever invoked when the callback is done, so release the lock.
Chris Thorntonae5fb992017-12-07 18:26:31 -08001182 synchronized (mCallbacksLock) {
Chris Thorntonba08b792017-06-08 22:34:37 -07001183 mWakelock.release();
1184 }
1185 }
1186 };
1187
Arunesh Mishraa772e5f2016-01-25 10:33:11 -08001188 public final class LocalSoundTriggerService extends SoundTriggerInternal {
1189 private final Context mContext;
1190 private SoundTriggerHelper mSoundTriggerHelper;
1191
1192 LocalSoundTriggerService(Context context) {
1193 mContext = context;
1194 }
1195
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001196 synchronized void setSoundTriggerHelper(SoundTriggerHelper helper) {
1197 mSoundTriggerHelper = helper;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -08001198 }
1199
1200 @Override
1201 public int startRecognition(int keyphraseId, KeyphraseSoundModel soundModel,
1202 IRecognitionStatusCallback listener, RecognitionConfig recognitionConfig) {
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001203 if (!isInitialized()) return STATUS_ERROR;
1204 return mSoundTriggerHelper.startKeyphraseRecognition(keyphraseId, soundModel, listener,
Arunesh Mishraa772e5f2016-01-25 10:33:11 -08001205 recognitionConfig);
1206 }
1207
1208 @Override
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001209 public synchronized int stopRecognition(int keyphraseId, IRecognitionStatusCallback listener) {
1210 if (!isInitialized()) return STATUS_ERROR;
1211 return mSoundTriggerHelper.stopKeyphraseRecognition(keyphraseId, listener);
Arunesh Mishraa772e5f2016-01-25 10:33:11 -08001212 }
1213
1214 @Override
Arunesh Mishra55a9b002016-02-01 14:06:37 -08001215 public ModuleProperties getModuleProperties() {
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001216 if (!isInitialized()) return null;
Arunesh Mishra55a9b002016-02-01 14:06:37 -08001217 return mSoundTriggerHelper.getModuleProperties();
1218 }
1219
1220 @Override
Arunesh Mishra2d1de782016-02-21 18:10:28 -08001221 public int unloadKeyphraseModel(int keyphraseId) {
1222 if (!isInitialized()) return STATUS_ERROR;
1223 return mSoundTriggerHelper.unloadKeyphraseSoundModel(keyphraseId);
1224 }
1225
1226 @Override
Arunesh Mishraa772e5f2016-01-25 10:33:11 -08001227 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001228 if (!isInitialized()) return;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -08001229 mSoundTriggerHelper.dump(fd, pw, args);
1230 }
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001231
1232 private synchronized boolean isInitialized() {
1233 if (mSoundTriggerHelper == null ) {
1234 Slog.e(TAG, "SoundTriggerHelper not initialized.");
1235 return false;
1236 }
1237 return true;
1238 }
Arunesh Mishraa772e5f2016-01-25 10:33:11 -08001239 }
1240
1241 private void enforceCallingPermission(String permission) {
1242 if (mContext.checkCallingOrSelfPermission(permission)
1243 != PackageManager.PERMISSION_GRANTED) {
1244 throw new SecurityException("Caller does not hold the permission " + permission);
1245 }
1246 }
1247}