blob: 8c82cc835ed983cfc232f411fff7be88d8f76c48 [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
183 GenericSoundModel model = getSoundModel(parcelUuid);
184 if (model == null) {
185 Slog.e(TAG, "Null model in database for id: " + parcelUuid);
186 return STATUS_ERROR;
187 }
188
189 return mSoundTriggerHelper.startGenericRecognition(parcelUuid.getUuid(), model,
190 callback, config);
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800191 }
192
193 @Override
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800194 public int stopRecognition(ParcelUuid parcelUuid, IRecognitionStatusCallback callback) {
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800195 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
196 if (DEBUG) {
197 Slog.i(TAG, "stopRecognition(): Uuid : " + parcelUuid);
198 }
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800199 if (!isInitialized()) return STATUS_ERROR;
200 return mSoundTriggerHelper.stopGenericRecognition(parcelUuid.getUuid(), callback);
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800201 }
202
203 @Override
Arunesh Mishrac722ec412016-01-27 13:29:12 -0800204 public SoundTrigger.GenericSoundModel getSoundModel(ParcelUuid soundModelId) {
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800205 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
206 if (DEBUG) {
207 Slog.i(TAG, "getSoundModel(): id = " + soundModelId);
208 }
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800209 SoundTrigger.GenericSoundModel model = mDbHelper.getGenericSoundModel(
210 soundModelId.getUuid());
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800211 return model;
212 }
213
214 @Override
Arunesh Mishrac722ec412016-01-27 13:29:12 -0800215 public void updateSoundModel(SoundTrigger.GenericSoundModel soundModel) {
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800216 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
217 if (DEBUG) {
218 Slog.i(TAG, "updateSoundModel(): model = " + soundModel);
219 }
Arunesh Mishrac722ec412016-01-27 13:29:12 -0800220 mDbHelper.updateGenericSoundModel(soundModel);
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800221 }
222
223 @Override
224 public void deleteSoundModel(ParcelUuid soundModelId) {
225 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
226 if (DEBUG) {
227 Slog.i(TAG, "deleteSoundModel(): id = " + soundModelId);
228 }
Arunesh Mishra2d1de782016-02-21 18:10:28 -0800229 // Unload the model if it is loaded.
230 mSoundTriggerHelper.unloadGenericSoundModel(soundModelId.getUuid());
Arunesh Mishrac722ec412016-01-27 13:29:12 -0800231 mDbHelper.deleteGenericSoundModel(soundModelId.getUuid());
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800232 }
Chris Thorntonba08b792017-06-08 22:34:37 -0700233
234 @Override
235 public int loadGenericSoundModel(GenericSoundModel soundModel) {
236 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
237 if (!isInitialized()) return STATUS_ERROR;
238 if (soundModel == null || soundModel.uuid == null) {
239 Slog.e(TAG, "Invalid sound model");
240 return STATUS_ERROR;
241 }
242 if (DEBUG) {
243 Slog.i(TAG, "loadGenericSoundModel(): id = " + soundModel.uuid);
244 }
245 synchronized (mLock) {
246 SoundModel oldModel = mLoadedModels.get(soundModel.uuid);
247 // If the model we're loading is actually different than what we had loaded, we
248 // should unload that other model now. We don't care about return codes since we
249 // don't know if the other model is loaded.
250 if (oldModel != null && !oldModel.equals(soundModel)) {
251 mSoundTriggerHelper.unloadGenericSoundModel(soundModel.uuid);
Chris Thorntonae5fb992017-12-07 18:26:31 -0800252 synchronized (mCallbacksLock) {
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800253 mCallbacks.remove(soundModel.uuid);
Chris Thorntonae5fb992017-12-07 18:26:31 -0800254 }
Chris Thorntonba08b792017-06-08 22:34:37 -0700255 }
256 mLoadedModels.put(soundModel.uuid, soundModel);
257 }
258 return STATUS_OK;
259 }
260
261 @Override
262 public int loadKeyphraseSoundModel(KeyphraseSoundModel soundModel) {
263 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
264 if (!isInitialized()) return STATUS_ERROR;
265 if (soundModel == null || soundModel.uuid == null) {
266 Slog.e(TAG, "Invalid sound model");
267 return STATUS_ERROR;
268 }
269 if (soundModel.keyphrases == null || soundModel.keyphrases.length != 1) {
270 Slog.e(TAG, "Only one keyphrase per model is currently supported.");
271 return STATUS_ERROR;
272 }
273 if (DEBUG) {
274 Slog.i(TAG, "loadKeyphraseSoundModel(): id = " + soundModel.uuid);
275 }
276 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.unloadKeyphraseSoundModel(soundModel.keyphrases[0].id);
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
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800293 public int startRecognitionForService(ParcelUuid soundModelId, Bundle params,
294 ComponentName detectionService, SoundTrigger.RecognitionConfig config) {
295 Preconditions.checkNotNull(soundModelId);
296 Preconditions.checkNotNull(detectionService);
297 Preconditions.checkNotNull(config);
298
Chris Thorntonba08b792017-06-08 22:34:37 -0700299 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
Philip P. Moltmann19402f52018-03-27 14:38:03 -0700300
Chris Thorntonba08b792017-06-08 22:34:37 -0700301 if (!isInitialized()) return STATUS_ERROR;
302 if (DEBUG) {
303 Slog.i(TAG, "startRecognition(): id = " + soundModelId);
304 }
305
Philip P. Moltmann19402f52018-03-27 14:38:03 -0700306 IRecognitionStatusCallback callback =
307 new RemoteSoundTriggerDetectionService(soundModelId.getUuid(), params,
308 detectionService, Binder.getCallingUserHandle(), config);
309
Chris Thorntonba08b792017-06-08 22:34:37 -0700310 synchronized (mLock) {
311 SoundModel soundModel = mLoadedModels.get(soundModelId.getUuid());
312 if (soundModel == null) {
313 Slog.e(TAG, soundModelId + " is not loaded");
314 return STATUS_ERROR;
315 }
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800316 IRecognitionStatusCallback existingCallback = null;
Chris Thorntonae5fb992017-12-07 18:26:31 -0800317 synchronized (mCallbacksLock) {
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800318 existingCallback = mCallbacks.get(soundModelId.getUuid());
Chris Thorntonae5fb992017-12-07 18:26:31 -0800319 }
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800320 if (existingCallback != null) {
Chris Thorntonba08b792017-06-08 22:34:37 -0700321 Slog.e(TAG, soundModelId + " is already running");
322 return STATUS_ERROR;
323 }
Chris Thorntonba08b792017-06-08 22:34:37 -0700324 int ret;
325 switch (soundModel.type) {
Chris Thorntonba08b792017-06-08 22:34:37 -0700326 case SoundModel.TYPE_GENERIC_SOUND:
327 ret = mSoundTriggerHelper.startGenericRecognition(soundModel.uuid,
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800328 (GenericSoundModel) soundModel, callback, config);
Chris Thorntonba08b792017-06-08 22:34:37 -0700329 break;
330 default:
331 Slog.e(TAG, "Unknown model type");
332 return STATUS_ERROR;
333 }
334
335 if (ret != STATUS_OK) {
336 Slog.e(TAG, "Failed to start model: " + ret);
337 return ret;
338 }
Chris Thorntonae5fb992017-12-07 18:26:31 -0800339 synchronized (mCallbacksLock) {
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800340 mCallbacks.put(soundModelId.getUuid(), callback);
Chris Thorntonae5fb992017-12-07 18:26:31 -0800341 }
Chris Thorntonba08b792017-06-08 22:34:37 -0700342 }
343 return STATUS_OK;
344 }
345
346 @Override
Philip P. Moltmann19402f52018-03-27 14:38:03 -0700347 public int stopRecognitionForService(ParcelUuid soundModelId) {
Chris Thorntonba08b792017-06-08 22:34:37 -0700348 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
349 if (!isInitialized()) return STATUS_ERROR;
350 if (DEBUG) {
351 Slog.i(TAG, "stopRecognition(): id = " + soundModelId);
352 }
353
354 synchronized (mLock) {
355 SoundModel soundModel = mLoadedModels.get(soundModelId.getUuid());
356 if (soundModel == null) {
357 Slog.e(TAG, soundModelId + " is not loaded");
358 return STATUS_ERROR;
359 }
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800360 IRecognitionStatusCallback callback = null;
Chris Thorntonae5fb992017-12-07 18:26:31 -0800361 synchronized (mCallbacksLock) {
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800362 callback = mCallbacks.get(soundModelId.getUuid());
Chris Thorntonae5fb992017-12-07 18:26:31 -0800363 }
Chris Thorntonba08b792017-06-08 22:34:37 -0700364 if (callback == null) {
365 Slog.e(TAG, soundModelId + " is not running");
366 return STATUS_ERROR;
367 }
368 int ret;
369 switch (soundModel.type) {
Chris Thorntonba08b792017-06-08 22:34:37 -0700370 case SoundModel.TYPE_GENERIC_SOUND:
371 ret = mSoundTriggerHelper.stopGenericRecognition(soundModel.uuid, callback);
372 break;
373 default:
374 Slog.e(TAG, "Unknown model type");
375 return STATUS_ERROR;
376 }
377
378 if (ret != STATUS_OK) {
379 Slog.e(TAG, "Failed to stop model: " + ret);
380 return ret;
381 }
Chris Thorntonae5fb992017-12-07 18:26:31 -0800382 synchronized (mCallbacksLock) {
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800383 mCallbacks.remove(soundModelId.getUuid());
Chris Thorntonae5fb992017-12-07 18:26:31 -0800384 }
Chris Thorntonba08b792017-06-08 22:34:37 -0700385 }
386 return STATUS_OK;
387 }
388
389 @Override
390 public int unloadSoundModel(ParcelUuid soundModelId) {
391 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
392 if (!isInitialized()) return STATUS_ERROR;
393 if (DEBUG) {
394 Slog.i(TAG, "unloadSoundModel(): id = " + soundModelId);
395 }
396
397 synchronized (mLock) {
398 SoundModel soundModel = mLoadedModels.get(soundModelId.getUuid());
399 if (soundModel == null) {
400 Slog.e(TAG, soundModelId + " is not loaded");
401 return STATUS_ERROR;
402 }
403 int ret;
404 switch (soundModel.type) {
405 case SoundModel.TYPE_KEYPHRASE:
406 ret = mSoundTriggerHelper.unloadKeyphraseSoundModel(
407 ((KeyphraseSoundModel)soundModel).keyphrases[0].id);
408 break;
409 case SoundModel.TYPE_GENERIC_SOUND:
410 ret = mSoundTriggerHelper.unloadGenericSoundModel(soundModel.uuid);
411 break;
412 default:
413 Slog.e(TAG, "Unknown model type");
414 return STATUS_ERROR;
415 }
416 if (ret != STATUS_OK) {
417 Slog.e(TAG, "Failed to unload model");
418 return ret;
419 }
420 mLoadedModels.remove(soundModelId.getUuid());
421 return STATUS_OK;
422 }
423 }
424
425 @Override
426 public boolean isRecognitionActive(ParcelUuid parcelUuid) {
427 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
428 if (!isInitialized()) return false;
Chris Thorntonae5fb992017-12-07 18:26:31 -0800429 synchronized (mCallbacksLock) {
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800430 IRecognitionStatusCallback callback = mCallbacks.get(parcelUuid.getUuid());
Chris Thorntonba08b792017-06-08 22:34:37 -0700431 if (callback == null) {
432 return false;
433 }
Chris Thorntonba08b792017-06-08 22:34:37 -0700434 }
Chris Thorntonae5fb992017-12-07 18:26:31 -0800435 return mSoundTriggerHelper.isRecognitionRequested(parcelUuid.getUuid());
Chris Thorntonba08b792017-06-08 22:34:37 -0700436 }
Michael Dooley291751e2018-10-16 19:53:29 +0000437
438 @Override
mike dooleyb2ab04a2018-11-07 15:48:54 +0100439 public int getModelState(ParcelUuid soundModelId) {
Michael Dooley291751e2018-10-16 19:53:29 +0000440 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
mike dooleyb2ab04a2018-11-07 15:48:54 +0100441 int ret = STATUS_ERROR;
442 if (!isInitialized()) return ret;
Michael Dooley291751e2018-10-16 19:53:29 +0000443 if (DEBUG) {
444 Slog.i(TAG, "getModelState(): id = " + soundModelId);
445 }
446
447 synchronized (mLock) {
448 SoundModel soundModel = mLoadedModels.get(soundModelId.getUuid());
449 if (soundModel == null) {
450 Slog.e(TAG, soundModelId + " is not loaded");
mike dooleyb2ab04a2018-11-07 15:48:54 +0100451 return ret;
Michael Dooley291751e2018-10-16 19:53:29 +0000452 }
Michael Dooley291751e2018-10-16 19:53:29 +0000453 switch (soundModel.type) {
454 case SoundModel.TYPE_KEYPHRASE:
455 ret = mSoundTriggerHelper.getKeyphraseModelState(soundModel.uuid);
456 break;
457 case SoundModel.TYPE_GENERIC_SOUND:
458 ret = mSoundTriggerHelper.getGenericModelState(soundModel.uuid);
459 break;
460 default:
461 Slog.e(TAG, "Unknown model type");
462 break;
463 }
Michael Dooley291751e2018-10-16 19:53:29 +0000464
465 return ret;
466 }
467 }
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800468 }
469
Philip P. Moltmann7e25b3d2018-03-09 20:22:58 -0800470 /**
471 * Counts the number of operations added in the last 24 hours.
472 */
473 private static class NumOps {
474 private final Object mLock = new Object();
475
476 @GuardedBy("mLock")
477 private int[] mNumOps = new int[24];
478 @GuardedBy("mLock")
479 private long mLastOpsHourSinceBoot;
480
481 /**
482 * Clear buckets of new hours that have elapsed since last operation.
483 *
484 * <p>I.e. when the last operation was triggered at 1:40 and the current operation was
485 * triggered at 4:03, the buckets "2, 3, and 4" are cleared.
486 *
487 * @param currentTime Current elapsed time since boot in ns
488 */
489 void clearOldOps(long currentTime) {
490 synchronized (mLock) {
491 long numHoursSinceBoot = TimeUnit.HOURS.convert(currentTime, TimeUnit.NANOSECONDS);
492
493 // Clear buckets of new hours that have elapsed since last operation
494 // I.e. when the last operation was triggered at 1:40 and the current
495 // operation was triggered at 4:03, the bucket "2, 3, and 4" is cleared
496 if (mLastOpsHourSinceBoot != 0) {
497 for (long hour = mLastOpsHourSinceBoot + 1; hour <= numHoursSinceBoot; hour++) {
498 mNumOps[(int) (hour % 24)] = 0;
499 }
500 }
501 }
502 }
503
504 /**
505 * Add a new operation.
506 *
507 * @param currentTime Current elapsed time since boot in ns
508 */
509 void addOp(long currentTime) {
510 synchronized (mLock) {
511 long numHoursSinceBoot = TimeUnit.HOURS.convert(currentTime, TimeUnit.NANOSECONDS);
512
513 mNumOps[(int) (numHoursSinceBoot % 24)]++;
514 mLastOpsHourSinceBoot = numHoursSinceBoot;
515 }
516 }
517
518 /**
519 * Get the total operations added in the last 24 hours.
520 *
521 * @return The total number of operations added in the last 24 hours
522 */
523 int getOpsAdded() {
524 synchronized (mLock) {
525 int totalOperationsInLastDay = 0;
526 for (int i = 0; i < 24; i++) {
527 totalOperationsInLastDay += mNumOps[i];
528 }
529
530 return totalOperationsInLastDay;
531 }
532 }
533 }
534
Philip P. Moltmanna5b44032018-05-04 13:59:45 -0700535 /**
536 * A single operation run in a {@link RemoteSoundTriggerDetectionService}.
537 *
538 * <p>Once the remote service is connected either setup + execute or setup + stop is executed.
539 */
540 private static class Operation {
541 private interface ExecuteOp {
542 void run(int opId, ISoundTriggerDetectionService service) throws RemoteException;
543 }
544
545 private final @Nullable Runnable mSetupOp;
546 private final @NonNull ExecuteOp mExecuteOp;
547 private final @Nullable Runnable mDropOp;
548
549 private Operation(@Nullable Runnable setupOp, @NonNull ExecuteOp executeOp,
550 @Nullable Runnable cancelOp) {
551 mSetupOp = setupOp;
552 mExecuteOp = executeOp;
553 mDropOp = cancelOp;
554 }
555
556 private void setup() {
557 if (mSetupOp != null) {
558 mSetupOp.run();
559 }
560 }
561
562 void run(int opId, @NonNull ISoundTriggerDetectionService service) throws RemoteException {
563 setup();
564 mExecuteOp.run(opId, service);
565 }
566
567 void drop() {
568 setup();
569
570 if (mDropOp != null) {
571 mDropOp.run();
572 }
573 }
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800574 }
575
576 /**
577 * Local end for a {@link SoundTriggerDetectionService}. Operations are queued up and executed
578 * when the service connects.
579 *
580 * <p>If operations take too long they are forcefully aborted.
581 *
582 * <p>This also limits the amount of operations in 24 hours.
583 */
584 private class RemoteSoundTriggerDetectionService
585 extends IRecognitionStatusCallback.Stub implements ServiceConnection {
586 private static final int MSG_STOP_ALL_PENDING_OPERATIONS = 1;
587
588 private final Object mRemoteServiceLock = new Object();
589
590 /** UUID of the model the service is started for */
591 private final @NonNull ParcelUuid mPuuid;
592 /** Params passed into the start method for the service */
593 private final @Nullable Bundle mParams;
594 /** Component name passed when starting the service */
595 private final @NonNull ComponentName mServiceName;
596 /** User that started the service */
597 private final @NonNull UserHandle mUser;
598 /** Configuration of the recognition the service is handling */
599 private final @NonNull RecognitionConfig mRecognitionConfig;
600 /** Wake lock keeping the remote service alive */
601 private final @NonNull PowerManager.WakeLock mRemoteServiceWakeLock;
602
603 private final @NonNull Handler mHandler;
604
605 /** Callbacks that are called by the service */
606 private final @NonNull ISoundTriggerDetectionServiceClient mClient;
607
608 /** Operations that are pending because the service is not yet connected */
609 @GuardedBy("mRemoteServiceLock")
610 private final ArrayList<Operation> mPendingOps = new ArrayList<>();
611 /** Operations that have been send to the service but have no yet finished */
612 @GuardedBy("mRemoteServiceLock")
613 private final ArraySet<Integer> mRunningOpIds = new ArraySet<>();
Philip P. Moltmann7e25b3d2018-03-09 20:22:58 -0800614 /** The number of operations executed in each of the last 24 hours */
615 private final NumOps mNumOps;
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800616
617 /** The service binder if connected */
618 @GuardedBy("mRemoteServiceLock")
619 private @Nullable ISoundTriggerDetectionService mService;
620 /** Whether the service has been bound */
621 @GuardedBy("mRemoteServiceLock")
622 private boolean mIsBound;
623 /** Whether the service has been destroyed */
624 @GuardedBy("mRemoteServiceLock")
625 private boolean mIsDestroyed;
626 /**
627 * Set once a final op is scheduled. No further ops can be added and the service is
628 * destroyed once the op finishes.
629 */
630 @GuardedBy("mRemoteServiceLock")
631 private boolean mDestroyOnceRunningOpsDone;
632
633 /** Total number of operations performed by this service */
634 @GuardedBy("mRemoteServiceLock")
635 private int mNumTotalOpsPerformed;
636
637 /**
638 * Create a new remote sound trigger detection service. This only binds to the service when
639 * operations are in flight. Each operation has a certain time it can run. Once no
640 * operations are allowed to run anymore, {@link #stopAllPendingOperations() all operations
641 * are aborted and stopped} and the service is disconnected.
642 *
643 * @param modelUuid The UUID of the model the recognition is for
644 * @param params The params passed to each method of the service
645 * @param serviceName The component name of the service
646 * @param user The user of the service
647 * @param config The configuration of the recognition
648 */
649 public RemoteSoundTriggerDetectionService(@NonNull UUID modelUuid,
650 @Nullable Bundle params, @NonNull ComponentName serviceName, @NonNull UserHandle user,
651 @NonNull RecognitionConfig config) {
652 mPuuid = new ParcelUuid(modelUuid);
653 mParams = params;
654 mServiceName = serviceName;
655 mUser = user;
656 mRecognitionConfig = config;
657 mHandler = new Handler(Looper.getMainLooper());
658
659 PowerManager pm = ((PowerManager) mContext.getSystemService(Context.POWER_SERVICE));
660 mRemoteServiceWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
661 "RemoteSoundTriggerDetectionService " + mServiceName.getPackageName() + ":"
662 + mServiceName.getClassName());
663
Philip P. Moltmann7e25b3d2018-03-09 20:22:58 -0800664 synchronized (mLock) {
665 NumOps numOps = mNumOpsPerPackage.get(mServiceName.getPackageName());
666 if (numOps == null) {
667 numOps = new NumOps();
668 mNumOpsPerPackage.put(mServiceName.getPackageName(), numOps);
669 }
670 mNumOps = numOps;
671 }
672
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800673 mClient = new ISoundTriggerDetectionServiceClient.Stub() {
674 @Override
675 public void onOpFinished(int opId) {
676 long token = Binder.clearCallingIdentity();
677 try {
678 synchronized (mRemoteServiceLock) {
679 mRunningOpIds.remove(opId);
680
681 if (mRunningOpIds.isEmpty() && mPendingOps.isEmpty()) {
682 if (mDestroyOnceRunningOpsDone) {
683 destroy();
684 } else {
685 disconnectLocked();
686 }
687 }
688 }
689 } finally {
690 Binder.restoreCallingIdentity(token);
691 }
692 }
693 };
694 }
695
696 @Override
697 public boolean pingBinder() {
698 return !(mIsDestroyed || mDestroyOnceRunningOpsDone);
699 }
700
701 /**
702 * Disconnect from the service, but allow to re-connect when new operations are triggered.
703 */
Andreas Gampe8ce7ed92018-09-05 16:53:00 -0700704 @GuardedBy("mRemoteServiceLock")
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800705 private void disconnectLocked() {
706 if (mService != null) {
707 try {
708 mService.removeClient(mPuuid);
709 } catch (Exception e) {
710 Slog.e(TAG, mPuuid + ": Cannot remove client", e);
711 }
712
713 mService = null;
714 }
715
716 if (mIsBound) {
717 mContext.unbindService(RemoteSoundTriggerDetectionService.this);
718 mIsBound = false;
719
720 synchronized (mCallbacksLock) {
721 mRemoteServiceWakeLock.release();
722 }
723 }
724 }
725
726 /**
727 * Disconnect, do not allow to reconnect to the service. All further operations will be
728 * dropped.
729 */
730 private void destroy() {
731 if (DEBUG) Slog.v(TAG, mPuuid + ": destroy");
732
733 synchronized (mRemoteServiceLock) {
734 disconnectLocked();
735
736 mIsDestroyed = true;
737 }
738
739 // The callback is removed before the flag is set
740 if (!mDestroyOnceRunningOpsDone) {
741 synchronized (mCallbacksLock) {
742 mCallbacks.remove(mPuuid.getUuid());
743 }
744 }
745 }
746
747 /**
748 * Stop all pending operations and then disconnect for the service.
749 */
750 private void stopAllPendingOperations() {
751 synchronized (mRemoteServiceLock) {
752 if (mIsDestroyed) {
753 return;
754 }
755
756 if (mService != null) {
757 int numOps = mRunningOpIds.size();
758 for (int i = 0; i < numOps; i++) {
759 try {
760 mService.onStopOperation(mPuuid, mRunningOpIds.valueAt(i));
761 } catch (Exception e) {
762 Slog.e(TAG, mPuuid + ": Could not stop operation "
763 + mRunningOpIds.valueAt(i), e);
764 }
765 }
766
767 mRunningOpIds.clear();
768 }
769
770 disconnectLocked();
771 }
772 }
773
774 /**
775 * Verify that the service has the expected properties and then bind to the service
776 */
777 private void bind() {
778 long token = Binder.clearCallingIdentity();
779 try {
780 Intent i = new Intent();
781 i.setComponent(mServiceName);
782
783 ResolveInfo ri = mContext.getPackageManager().resolveServiceAsUser(i,
784 GET_SERVICES | GET_META_DATA | MATCH_DEBUG_TRIAGED_MISSING,
785 mUser.getIdentifier());
786
787 if (ri == null) {
788 Slog.w(TAG, mPuuid + ": " + mServiceName + " not found");
789 return;
790 }
791
792 if (!BIND_SOUND_TRIGGER_DETECTION_SERVICE
793 .equals(ri.serviceInfo.permission)) {
794 Slog.w(TAG, mPuuid + ": " + mServiceName + " does not require "
795 + BIND_SOUND_TRIGGER_DETECTION_SERVICE);
796 return;
797 }
798
799 mIsBound = mContext.bindServiceAsUser(i, this,
800 BIND_AUTO_CREATE | BIND_FOREGROUND_SERVICE, mUser);
801
802 if (mIsBound) {
803 mRemoteServiceWakeLock.acquire();
804 } else {
805 Slog.w(TAG, mPuuid + ": Could not bind to " + mServiceName);
806 }
807 } finally {
808 Binder.restoreCallingIdentity(token);
809 }
810 }
811
812 /**
813 * Run an operation (i.e. send it do the service). If the service is not connected, this
814 * binds the service and then runs the operation once connected.
815 *
816 * @param op The operation to run
817 */
818 private void runOrAddOperation(Operation op) {
819 synchronized (mRemoteServiceLock) {
820 if (mIsDestroyed || mDestroyOnceRunningOpsDone) {
Philip P. Moltmanna5b44032018-05-04 13:59:45 -0700821 Slog.w(TAG, mPuuid + ": Dropped operation as already destroyed or marked for "
822 + "destruction");
823
824 op.drop();
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800825 return;
826 }
827
828 if (mService == null) {
829 mPendingOps.add(op);
830
831 if (!mIsBound) {
832 bind();
833 }
834 } else {
Philip P. Moltmann7e25b3d2018-03-09 20:22:58 -0800835 long currentTime = System.nanoTime();
836 mNumOps.clearOldOps(currentTime);
837
838 // Drop operation if too many were executed in the last 24 hours.
839 int opsAllowed = Settings.Global.getInt(mContext.getContentResolver(),
840 MAX_SOUND_TRIGGER_DETECTION_SERVICE_OPS_PER_DAY,
841 Integer.MAX_VALUE);
842
Philip P. Moltmann3d1683b2018-05-07 15:53:51 -0700843 // As we currently cannot dropping an op safely, disable throttling
Philip P. Moltmann7e25b3d2018-03-09 20:22:58 -0800844 int opsAdded = mNumOps.getOpsAdded();
Philip P. Moltmann3d1683b2018-05-07 15:53:51 -0700845 if (false && mNumOps.getOpsAdded() >= opsAllowed) {
Philip P. Moltmanna5b44032018-05-04 13:59:45 -0700846 try {
847 if (DEBUG || opsAllowed + 10 > opsAdded) {
848 Slog.w(TAG, mPuuid + ": Dropped operation as too many operations "
849 + "were run in last 24 hours");
850 }
851
852 op.drop();
853 } catch (Exception e) {
854 Slog.e(TAG, mPuuid + ": Could not drop operation", e);
Philip P. Moltmann7e25b3d2018-03-09 20:22:58 -0800855 }
Philip P. Moltmann09dd8c42018-04-11 09:23:19 -0700856 } else {
857 mNumOps.addOp(currentTime);
Philip P. Moltmann7e25b3d2018-03-09 20:22:58 -0800858
Philip P. Moltmann09dd8c42018-04-11 09:23:19 -0700859 // Find a free opID
860 int opId = mNumTotalOpsPerformed;
861 do {
862 mNumTotalOpsPerformed++;
863 } while (mRunningOpIds.contains(opId));
Philip P. Moltmann7e25b3d2018-03-09 20:22:58 -0800864
Philip P. Moltmann09dd8c42018-04-11 09:23:19 -0700865 // Run OP
866 try {
867 if (DEBUG) Slog.v(TAG, mPuuid + ": runOp " + opId);
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800868
Philip P. Moltmann09dd8c42018-04-11 09:23:19 -0700869 op.run(opId, mService);
870 mRunningOpIds.add(opId);
871 } catch (Exception e) {
872 Slog.e(TAG, mPuuid + ": Could not run operation " + opId, e);
873 }
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800874 }
875
876 // Unbind from service if no operations are left (i.e. if the operation failed)
877 if (mPendingOps.isEmpty() && mRunningOpIds.isEmpty()) {
878 if (mDestroyOnceRunningOpsDone) {
879 destroy();
880 } else {
881 disconnectLocked();
882 }
883 } else {
884 mHandler.removeMessages(MSG_STOP_ALL_PENDING_OPERATIONS);
885 mHandler.sendMessageDelayed(obtainMessage(
886 RemoteSoundTriggerDetectionService::stopAllPendingOperations, this)
887 .setWhat(MSG_STOP_ALL_PENDING_OPERATIONS),
888 Settings.Global.getLong(mContext.getContentResolver(),
889 SOUND_TRIGGER_DETECTION_SERVICE_OP_TIMEOUT,
890 Long.MAX_VALUE));
891 }
892 }
893 }
894 }
895
896 @Override
897 public void onKeyphraseDetected(SoundTrigger.KeyphraseRecognitionEvent event) {
898 Slog.w(TAG, mPuuid + "->" + mServiceName + ": IGNORED onKeyphraseDetected(" + event
899 + ")");
900 }
901
Philip P. Moltmanna5b44032018-05-04 13:59:45 -0700902 /**
903 * Create an AudioRecord enough for starting and releasing the data buffered for the event.
904 *
905 * @param event The event that was received
906 * @return The initialized AudioRecord
907 */
908 private @NonNull AudioRecord createAudioRecordForEvent(
909 @NonNull SoundTrigger.GenericRecognitionEvent event) {
910 AudioAttributes.Builder attributesBuilder = new AudioAttributes.Builder();
911 attributesBuilder.setInternalCapturePreset(MediaRecorder.AudioSource.HOTWORD);
912 AudioAttributes attributes = attributesBuilder.build();
913
914 // Use same AudioFormat processing as in RecognitionEvent.fromParcel
915 AudioFormat originalFormat = event.getCaptureFormat();
916 AudioFormat captureFormat = (new AudioFormat.Builder())
917 .setChannelMask(originalFormat.getChannelMask())
918 .setEncoding(originalFormat.getEncoding())
919 .setSampleRate(originalFormat.getSampleRate())
920 .build();
921
922 int bufferSize = AudioRecord.getMinBufferSize(
923 captureFormat.getSampleRate() == AudioFormat.SAMPLE_RATE_UNSPECIFIED
924 ? AudioFormat.SAMPLE_RATE_HZ_MAX
925 : captureFormat.getSampleRate(),
926 captureFormat.getChannelCount() == 2
927 ? AudioFormat.CHANNEL_IN_STEREO
928 : AudioFormat.CHANNEL_IN_MONO,
929 captureFormat.getEncoding());
930
931 return new AudioRecord(attributes, captureFormat, bufferSize,
932 event.getCaptureSession());
933 }
934
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800935 @Override
936 public void onGenericSoundTriggerDetected(SoundTrigger.GenericRecognitionEvent event) {
937 if (DEBUG) Slog.v(TAG, mPuuid + ": Generic sound trigger event: " + event);
938
Philip P. Moltmanna5b44032018-05-04 13:59:45 -0700939 runOrAddOperation(new Operation(
940 // always execute:
941 () -> {
mike dooleyb2ab04a2018-11-07 15:48:54 +0100942 // Don't remove the callback if multiple triggers are allowed or
943 // if this event was triggered by a getModelState request
944 if (!mRecognitionConfig.allowMultipleTriggers
945 && event.status
946 != SoundTrigger.RECOGNITION_STATUS_GET_STATE_RESPONSE) {
Philip P. Moltmanna5b44032018-05-04 13:59:45 -0700947 // Unregister this remoteService once op is done
948 synchronized (mCallbacksLock) {
949 mCallbacks.remove(mPuuid.getUuid());
950 }
951 mDestroyOnceRunningOpsDone = true;
952 }
953 },
954 // execute if not throttled:
955 (opId, service) -> service.onGenericRecognitionEvent(mPuuid, opId, event),
956 // execute if throttled:
957 () -> {
958 if (event.isCaptureAvailable()) {
959 AudioRecord capturedData = createAudioRecordForEvent(event);
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800960
Philip P. Moltmanna5b44032018-05-04 13:59:45 -0700961 // Currently we need to start and release the audio record to reset
962 // the DSP even if we don't want to process the event
963 capturedData.startRecording();
964 capturedData.release();
965 }
966 }));
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800967 }
968
969 @Override
970 public void onError(int status) {
971 if (DEBUG) Slog.v(TAG, mPuuid + ": onError: " + status);
972
Philip P. Moltmanna5b44032018-05-04 13:59:45 -0700973 runOrAddOperation(
974 new Operation(
975 // always execute:
976 () -> {
977 // Unregister this remoteService once op is done
978 synchronized (mCallbacksLock) {
979 mCallbacks.remove(mPuuid.getUuid());
980 }
981 mDestroyOnceRunningOpsDone = true;
982 },
983 // execute if not throttled:
984 (opId, service) -> service.onError(mPuuid, opId, status),
985 // nothing to do if throttled
986 null));
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800987 }
988
989 @Override
990 public void onRecognitionPaused() {
991 Slog.i(TAG, mPuuid + "->" + mServiceName + ": IGNORED onRecognitionPaused");
992 }
993
994 @Override
995 public void onRecognitionResumed() {
996 Slog.i(TAG, mPuuid + "->" + mServiceName + ": IGNORED onRecognitionResumed");
997 }
998
999 @Override
1000 public void onServiceConnected(ComponentName name, IBinder service) {
1001 if (DEBUG) Slog.v(TAG, mPuuid + ": onServiceConnected(" + service + ")");
1002
1003 synchronized (mRemoteServiceLock) {
1004 mService = ISoundTriggerDetectionService.Stub.asInterface(service);
1005
1006 try {
1007 mService.setClient(mPuuid, mParams, mClient);
1008 } catch (Exception e) {
1009 Slog.e(TAG, mPuuid + ": Could not init " + mServiceName, e);
1010 return;
1011 }
1012
1013 while (!mPendingOps.isEmpty()) {
1014 runOrAddOperation(mPendingOps.remove(0));
1015 }
1016 }
1017 }
1018
1019 @Override
1020 public void onServiceDisconnected(ComponentName name) {
1021 if (DEBUG) Slog.v(TAG, mPuuid + ": onServiceDisconnected");
1022
1023 synchronized (mRemoteServiceLock) {
1024 mService = null;
1025 }
1026 }
1027
1028 @Override
1029 public void onBindingDied(ComponentName name) {
1030 if (DEBUG) Slog.v(TAG, mPuuid + ": onBindingDied");
1031
1032 synchronized (mRemoteServiceLock) {
1033 destroy();
1034 }
1035 }
1036
1037 @Override
1038 public void onNullBinding(ComponentName name) {
1039 Slog.w(TAG, name + " for model " + mPuuid + " returned a null binding");
1040
1041 synchronized (mRemoteServiceLock) {
1042 disconnectLocked();
1043 }
1044 }
1045 }
1046
Arunesh Mishraa772e5f2016-01-25 10:33:11 -08001047 public final class LocalSoundTriggerService extends SoundTriggerInternal {
1048 private final Context mContext;
1049 private SoundTriggerHelper mSoundTriggerHelper;
1050
1051 LocalSoundTriggerService(Context context) {
1052 mContext = context;
1053 }
1054
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001055 synchronized void setSoundTriggerHelper(SoundTriggerHelper helper) {
1056 mSoundTriggerHelper = helper;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -08001057 }
1058
1059 @Override
1060 public int startRecognition(int keyphraseId, KeyphraseSoundModel soundModel,
1061 IRecognitionStatusCallback listener, RecognitionConfig recognitionConfig) {
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001062 if (!isInitialized()) return STATUS_ERROR;
1063 return mSoundTriggerHelper.startKeyphraseRecognition(keyphraseId, soundModel, listener,
Arunesh Mishraa772e5f2016-01-25 10:33:11 -08001064 recognitionConfig);
1065 }
1066
1067 @Override
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001068 public synchronized int stopRecognition(int keyphraseId, IRecognitionStatusCallback listener) {
1069 if (!isInitialized()) return STATUS_ERROR;
1070 return mSoundTriggerHelper.stopKeyphraseRecognition(keyphraseId, listener);
Arunesh Mishraa772e5f2016-01-25 10:33:11 -08001071 }
1072
1073 @Override
Arunesh Mishra55a9b002016-02-01 14:06:37 -08001074 public ModuleProperties getModuleProperties() {
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001075 if (!isInitialized()) return null;
Arunesh Mishra55a9b002016-02-01 14:06:37 -08001076 return mSoundTriggerHelper.getModuleProperties();
1077 }
1078
1079 @Override
Arunesh Mishra2d1de782016-02-21 18:10:28 -08001080 public int unloadKeyphraseModel(int keyphraseId) {
1081 if (!isInitialized()) return STATUS_ERROR;
1082 return mSoundTriggerHelper.unloadKeyphraseSoundModel(keyphraseId);
1083 }
1084
1085 @Override
Arunesh Mishraa772e5f2016-01-25 10:33:11 -08001086 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001087 if (!isInitialized()) return;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -08001088 mSoundTriggerHelper.dump(fd, pw, args);
1089 }
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001090
1091 private synchronized boolean isInitialized() {
1092 if (mSoundTriggerHelper == null ) {
1093 Slog.e(TAG, "SoundTriggerHelper not initialized.");
1094 return false;
1095 }
1096 return true;
1097 }
Arunesh Mishraa772e5f2016-01-25 10:33:11 -08001098 }
1099
1100 private void enforceCallingPermission(String permission) {
1101 if (mContext.checkCallingOrSelfPermission(permission)
1102 != PackageManager.PERMISSION_GRANTED) {
1103 throw new SecurityException("Caller does not hold the permission " + permission);
1104 }
1105 }
1106}