blob: 27189f1bc49075418acce10c19c1ae1c7bad2bf5 [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;
48import android.media.soundtrigger.ISoundTriggerDetectionService;
49import android.media.soundtrigger.ISoundTriggerDetectionServiceClient;
50import android.media.soundtrigger.SoundTriggerDetectionService;
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -080051import android.os.Binder;
Chris Thorntonba08b792017-06-08 22:34:37 -070052import android.os.Bundle;
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -080053import android.os.Handler;
54import android.os.IBinder;
55import android.os.Looper;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -080056import android.os.Parcel;
57import android.os.ParcelUuid;
Chris Thorntonba08b792017-06-08 22:34:37 -070058import android.os.PowerManager;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -080059import android.os.RemoteException;
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -080060import android.os.UserHandle;
61import android.provider.Settings;
Philip P. Moltmann7e25b3d2018-03-09 20:22:58 -080062import android.util.ArrayMap;
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -080063import android.util.ArraySet;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -080064import android.util.Slog;
65
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -080066import com.android.internal.annotations.GuardedBy;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -080067import com.android.internal.app.ISoundTriggerService;
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -080068import com.android.internal.util.Preconditions;
69import com.android.server.SystemService;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -080070
71import java.io.FileDescriptor;
72import java.io.PrintWriter;
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -080073import java.util.ArrayList;
Chris Thorntonba08b792017-06-08 22:34:37 -070074import java.util.TreeMap;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -080075import java.util.UUID;
Philip P. Moltmann7e25b3d2018-03-09 20:22:58 -080076import java.util.concurrent.TimeUnit;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -080077
78/**
79 * A single SystemService to manage all sound/voice-based sound models on the DSP.
80 * This services provides apis to manage sound trigger-based sound models via
81 * the ISoundTriggerService interface. This class also publishes a local interface encapsulating
82 * the functionality provided by {@link SoundTriggerHelper} for use by
83 * {@link VoiceInteractionManagerService}.
84 *
85 * @hide
86 */
87public class SoundTriggerService extends SystemService {
Arunesh Mishra3fff7f52016-02-09 12:15:19 -080088 private static final String TAG = "SoundTriggerService";
89 private static final boolean DEBUG = true;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -080090
91 final Context mContext;
Chris Thorntonba08b792017-06-08 22:34:37 -070092 private Object mLock;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -080093 private final SoundTriggerServiceStub mServiceStub;
94 private final LocalSoundTriggerService mLocalSoundTriggerService;
95 private SoundTriggerDbHelper mDbHelper;
Arunesh Mishra3fff7f52016-02-09 12:15:19 -080096 private SoundTriggerHelper mSoundTriggerHelper;
Chris Thorntonba08b792017-06-08 22:34:37 -070097 private final TreeMap<UUID, SoundModel> mLoadedModels;
Chris Thorntonae5fb992017-12-07 18:26:31 -080098 private Object mCallbacksLock;
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -080099 private final TreeMap<UUID, IRecognitionStatusCallback> mCallbacks;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800100
Philip P. Moltmann7e25b3d2018-03-09 20:22:58 -0800101 /** Number of ops run by the {@link RemoteSoundTriggerDetectionService} per package name */
102 @GuardedBy("mLock")
103 private final ArrayMap<String, NumOps> mNumOpsPerPackage = new ArrayMap<>();
104
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800105 public SoundTriggerService(Context context) {
106 super(context);
107 mContext = context;
108 mServiceStub = new SoundTriggerServiceStub();
109 mLocalSoundTriggerService = new LocalSoundTriggerService(context);
Chris Thorntonba08b792017-06-08 22:34:37 -0700110 mLoadedModels = new TreeMap<UUID, SoundModel>();
Chris Thorntonae5fb992017-12-07 18:26:31 -0800111 mCallbacksLock = new Object();
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800112 mCallbacks = new TreeMap<>();
Chris Thorntonba08b792017-06-08 22:34:37 -0700113 mLock = new Object();
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800114 }
115
116 @Override
117 public void onStart() {
118 publishBinderService(Context.SOUND_TRIGGER_SERVICE, mServiceStub);
119 publishLocalService(SoundTriggerInternal.class, mLocalSoundTriggerService);
120 }
121
122 @Override
123 public void onBootPhase(int phase) {
124 if (PHASE_SYSTEM_SERVICES_READY == phase) {
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800125 initSoundTriggerHelper();
126 mLocalSoundTriggerService.setSoundTriggerHelper(mSoundTriggerHelper);
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800127 } else if (PHASE_THIRD_PARTY_APPS_CAN_START == phase) {
128 mDbHelper = new SoundTriggerDbHelper(mContext);
129 }
130 }
131
132 @Override
133 public void onStartUser(int userHandle) {
134 }
135
136 @Override
137 public void onSwitchUser(int userHandle) {
138 }
139
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800140 private synchronized void initSoundTriggerHelper() {
141 if (mSoundTriggerHelper == null) {
142 mSoundTriggerHelper = new SoundTriggerHelper(mContext);
143 }
144 }
145
146 private synchronized boolean isInitialized() {
147 if (mSoundTriggerHelper == null ) {
148 Slog.e(TAG, "SoundTriggerHelper not initialized.");
149 return false;
150 }
151 return true;
152 }
153
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800154 class SoundTriggerServiceStub extends ISoundTriggerService.Stub {
155 @Override
156 public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
157 throws RemoteException {
158 try {
159 return super.onTransact(code, data, reply, flags);
160 } catch (RuntimeException e) {
161 // The activity manager only throws security exceptions, so let's
162 // log all others.
163 if (!(e instanceof SecurityException)) {
164 Slog.wtf(TAG, "SoundTriggerService Crash", e);
165 }
166 throw e;
167 }
168 }
169
170 @Override
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800171 public int startRecognition(ParcelUuid parcelUuid, IRecognitionStatusCallback callback,
172 RecognitionConfig config) {
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800173 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
Arunesh Mishra2d1de782016-02-21 18:10:28 -0800174 if (!isInitialized()) return STATUS_ERROR;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800175 if (DEBUG) {
176 Slog.i(TAG, "startRecognition(): Uuid : " + parcelUuid);
177 }
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800178
179 GenericSoundModel model = getSoundModel(parcelUuid);
180 if (model == null) {
181 Slog.e(TAG, "Null model in database for id: " + parcelUuid);
182 return STATUS_ERROR;
183 }
184
185 return mSoundTriggerHelper.startGenericRecognition(parcelUuid.getUuid(), model,
186 callback, config);
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800187 }
188
189 @Override
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800190 public int stopRecognition(ParcelUuid parcelUuid, IRecognitionStatusCallback callback) {
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800191 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
192 if (DEBUG) {
193 Slog.i(TAG, "stopRecognition(): Uuid : " + parcelUuid);
194 }
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800195 if (!isInitialized()) return STATUS_ERROR;
196 return mSoundTriggerHelper.stopGenericRecognition(parcelUuid.getUuid(), callback);
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800197 }
198
199 @Override
Arunesh Mishrac722ec412016-01-27 13:29:12 -0800200 public SoundTrigger.GenericSoundModel getSoundModel(ParcelUuid soundModelId) {
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800201 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
202 if (DEBUG) {
203 Slog.i(TAG, "getSoundModel(): id = " + soundModelId);
204 }
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800205 SoundTrigger.GenericSoundModel model = mDbHelper.getGenericSoundModel(
206 soundModelId.getUuid());
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800207 return model;
208 }
209
210 @Override
Arunesh Mishrac722ec412016-01-27 13:29:12 -0800211 public void updateSoundModel(SoundTrigger.GenericSoundModel soundModel) {
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800212 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
213 if (DEBUG) {
214 Slog.i(TAG, "updateSoundModel(): model = " + soundModel);
215 }
Arunesh Mishrac722ec412016-01-27 13:29:12 -0800216 mDbHelper.updateGenericSoundModel(soundModel);
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800217 }
218
219 @Override
220 public void deleteSoundModel(ParcelUuid soundModelId) {
221 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
222 if (DEBUG) {
223 Slog.i(TAG, "deleteSoundModel(): id = " + soundModelId);
224 }
Arunesh Mishra2d1de782016-02-21 18:10:28 -0800225 // Unload the model if it is loaded.
226 mSoundTriggerHelper.unloadGenericSoundModel(soundModelId.getUuid());
Arunesh Mishrac722ec412016-01-27 13:29:12 -0800227 mDbHelper.deleteGenericSoundModel(soundModelId.getUuid());
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800228 }
Chris Thorntonba08b792017-06-08 22:34:37 -0700229
230 @Override
231 public int loadGenericSoundModel(GenericSoundModel soundModel) {
232 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
233 if (!isInitialized()) return STATUS_ERROR;
234 if (soundModel == null || soundModel.uuid == null) {
235 Slog.e(TAG, "Invalid sound model");
236 return STATUS_ERROR;
237 }
238 if (DEBUG) {
239 Slog.i(TAG, "loadGenericSoundModel(): id = " + soundModel.uuid);
240 }
241 synchronized (mLock) {
242 SoundModel oldModel = mLoadedModels.get(soundModel.uuid);
243 // If the model we're loading is actually different than what we had loaded, we
244 // should unload that other model now. We don't care about return codes since we
245 // don't know if the other model is loaded.
246 if (oldModel != null && !oldModel.equals(soundModel)) {
247 mSoundTriggerHelper.unloadGenericSoundModel(soundModel.uuid);
Chris Thorntonae5fb992017-12-07 18:26:31 -0800248 synchronized (mCallbacksLock) {
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800249 mCallbacks.remove(soundModel.uuid);
Chris Thorntonae5fb992017-12-07 18:26:31 -0800250 }
Chris Thorntonba08b792017-06-08 22:34:37 -0700251 }
252 mLoadedModels.put(soundModel.uuid, soundModel);
253 }
254 return STATUS_OK;
255 }
256
257 @Override
258 public int loadKeyphraseSoundModel(KeyphraseSoundModel soundModel) {
259 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
260 if (!isInitialized()) return STATUS_ERROR;
261 if (soundModel == null || soundModel.uuid == null) {
262 Slog.e(TAG, "Invalid sound model");
263 return STATUS_ERROR;
264 }
265 if (soundModel.keyphrases == null || soundModel.keyphrases.length != 1) {
266 Slog.e(TAG, "Only one keyphrase per model is currently supported.");
267 return STATUS_ERROR;
268 }
269 if (DEBUG) {
270 Slog.i(TAG, "loadKeyphraseSoundModel(): id = " + soundModel.uuid);
271 }
272 synchronized (mLock) {
273 SoundModel oldModel = mLoadedModels.get(soundModel.uuid);
274 // If the model we're loading is actually different than what we had loaded, we
275 // should unload that other model now. We don't care about return codes since we
276 // don't know if the other model is loaded.
277 if (oldModel != null && !oldModel.equals(soundModel)) {
278 mSoundTriggerHelper.unloadKeyphraseSoundModel(soundModel.keyphrases[0].id);
Chris Thorntonae5fb992017-12-07 18:26:31 -0800279 synchronized (mCallbacksLock) {
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800280 mCallbacks.remove(soundModel.uuid);
Chris Thorntonae5fb992017-12-07 18:26:31 -0800281 }
Chris Thorntonba08b792017-06-08 22:34:37 -0700282 }
283 mLoadedModels.put(soundModel.uuid, soundModel);
284 }
285 return STATUS_OK;
286 }
287
288 @Override
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800289 public int startRecognitionForService(ParcelUuid soundModelId, Bundle params,
290 ComponentName detectionService, SoundTrigger.RecognitionConfig config) {
291 Preconditions.checkNotNull(soundModelId);
292 Preconditions.checkNotNull(detectionService);
293 Preconditions.checkNotNull(config);
294
Chris Thorntonba08b792017-06-08 22:34:37 -0700295 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
Philip P. Moltmann19402f52018-03-27 14:38:03 -0700296
Chris Thorntonba08b792017-06-08 22:34:37 -0700297 if (!isInitialized()) return STATUS_ERROR;
298 if (DEBUG) {
299 Slog.i(TAG, "startRecognition(): id = " + soundModelId);
300 }
301
Philip P. Moltmann19402f52018-03-27 14:38:03 -0700302 IRecognitionStatusCallback callback =
303 new RemoteSoundTriggerDetectionService(soundModelId.getUuid(), params,
304 detectionService, Binder.getCallingUserHandle(), config);
305
Chris Thorntonba08b792017-06-08 22:34:37 -0700306 synchronized (mLock) {
307 SoundModel soundModel = mLoadedModels.get(soundModelId.getUuid());
308 if (soundModel == null) {
309 Slog.e(TAG, soundModelId + " is not loaded");
310 return STATUS_ERROR;
311 }
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800312 IRecognitionStatusCallback existingCallback = null;
Chris Thorntonae5fb992017-12-07 18:26:31 -0800313 synchronized (mCallbacksLock) {
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800314 existingCallback = mCallbacks.get(soundModelId.getUuid());
Chris Thorntonae5fb992017-12-07 18:26:31 -0800315 }
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800316 if (existingCallback != null) {
Chris Thorntonba08b792017-06-08 22:34:37 -0700317 Slog.e(TAG, soundModelId + " is already running");
318 return STATUS_ERROR;
319 }
Chris Thorntonba08b792017-06-08 22:34:37 -0700320 int ret;
321 switch (soundModel.type) {
Chris Thorntonba08b792017-06-08 22:34:37 -0700322 case SoundModel.TYPE_GENERIC_SOUND:
323 ret = mSoundTriggerHelper.startGenericRecognition(soundModel.uuid,
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800324 (GenericSoundModel) soundModel, callback, config);
Chris Thorntonba08b792017-06-08 22:34:37 -0700325 break;
326 default:
327 Slog.e(TAG, "Unknown model type");
328 return STATUS_ERROR;
329 }
330
331 if (ret != STATUS_OK) {
332 Slog.e(TAG, "Failed to start model: " + ret);
333 return ret;
334 }
Chris Thorntonae5fb992017-12-07 18:26:31 -0800335 synchronized (mCallbacksLock) {
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800336 mCallbacks.put(soundModelId.getUuid(), callback);
Chris Thorntonae5fb992017-12-07 18:26:31 -0800337 }
Chris Thorntonba08b792017-06-08 22:34:37 -0700338 }
339 return STATUS_OK;
340 }
341
342 @Override
Philip P. Moltmann19402f52018-03-27 14:38:03 -0700343 public int stopRecognitionForService(ParcelUuid soundModelId) {
Chris Thorntonba08b792017-06-08 22:34:37 -0700344 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
345 if (!isInitialized()) return STATUS_ERROR;
346 if (DEBUG) {
347 Slog.i(TAG, "stopRecognition(): id = " + soundModelId);
348 }
349
350 synchronized (mLock) {
351 SoundModel soundModel = mLoadedModels.get(soundModelId.getUuid());
352 if (soundModel == null) {
353 Slog.e(TAG, soundModelId + " is not loaded");
354 return STATUS_ERROR;
355 }
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800356 IRecognitionStatusCallback callback = null;
Chris Thorntonae5fb992017-12-07 18:26:31 -0800357 synchronized (mCallbacksLock) {
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800358 callback = mCallbacks.get(soundModelId.getUuid());
Chris Thorntonae5fb992017-12-07 18:26:31 -0800359 }
Chris Thorntonba08b792017-06-08 22:34:37 -0700360 if (callback == null) {
361 Slog.e(TAG, soundModelId + " is not running");
362 return STATUS_ERROR;
363 }
364 int ret;
365 switch (soundModel.type) {
Chris Thorntonba08b792017-06-08 22:34:37 -0700366 case SoundModel.TYPE_GENERIC_SOUND:
367 ret = mSoundTriggerHelper.stopGenericRecognition(soundModel.uuid, callback);
368 break;
369 default:
370 Slog.e(TAG, "Unknown model type");
371 return STATUS_ERROR;
372 }
373
374 if (ret != STATUS_OK) {
375 Slog.e(TAG, "Failed to stop model: " + ret);
376 return ret;
377 }
Chris Thorntonae5fb992017-12-07 18:26:31 -0800378 synchronized (mCallbacksLock) {
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800379 mCallbacks.remove(soundModelId.getUuid());
Chris Thorntonae5fb992017-12-07 18:26:31 -0800380 }
Chris Thorntonba08b792017-06-08 22:34:37 -0700381 }
382 return STATUS_OK;
383 }
384
385 @Override
386 public int unloadSoundModel(ParcelUuid soundModelId) {
387 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
388 if (!isInitialized()) return STATUS_ERROR;
389 if (DEBUG) {
390 Slog.i(TAG, "unloadSoundModel(): id = " + soundModelId);
391 }
392
393 synchronized (mLock) {
394 SoundModel soundModel = mLoadedModels.get(soundModelId.getUuid());
395 if (soundModel == null) {
396 Slog.e(TAG, soundModelId + " is not loaded");
397 return STATUS_ERROR;
398 }
399 int ret;
400 switch (soundModel.type) {
401 case SoundModel.TYPE_KEYPHRASE:
402 ret = mSoundTriggerHelper.unloadKeyphraseSoundModel(
403 ((KeyphraseSoundModel)soundModel).keyphrases[0].id);
404 break;
405 case SoundModel.TYPE_GENERIC_SOUND:
406 ret = mSoundTriggerHelper.unloadGenericSoundModel(soundModel.uuid);
407 break;
408 default:
409 Slog.e(TAG, "Unknown model type");
410 return STATUS_ERROR;
411 }
412 if (ret != STATUS_OK) {
413 Slog.e(TAG, "Failed to unload model");
414 return ret;
415 }
416 mLoadedModels.remove(soundModelId.getUuid());
417 return STATUS_OK;
418 }
419 }
420
421 @Override
422 public boolean isRecognitionActive(ParcelUuid parcelUuid) {
423 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
424 if (!isInitialized()) return false;
Chris Thorntonae5fb992017-12-07 18:26:31 -0800425 synchronized (mCallbacksLock) {
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800426 IRecognitionStatusCallback callback = mCallbacks.get(parcelUuid.getUuid());
Chris Thorntonba08b792017-06-08 22:34:37 -0700427 if (callback == null) {
428 return false;
429 }
Chris Thorntonba08b792017-06-08 22:34:37 -0700430 }
Chris Thorntonae5fb992017-12-07 18:26:31 -0800431 return mSoundTriggerHelper.isRecognitionRequested(parcelUuid.getUuid());
Chris Thorntonba08b792017-06-08 22:34:37 -0700432 }
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800433 }
434
Philip P. Moltmann7e25b3d2018-03-09 20:22:58 -0800435 /**
436 * Counts the number of operations added in the last 24 hours.
437 */
438 private static class NumOps {
439 private final Object mLock = new Object();
440
441 @GuardedBy("mLock")
442 private int[] mNumOps = new int[24];
443 @GuardedBy("mLock")
444 private long mLastOpsHourSinceBoot;
445
446 /**
447 * Clear buckets of new hours that have elapsed since last operation.
448 *
449 * <p>I.e. when the last operation was triggered at 1:40 and the current operation was
450 * triggered at 4:03, the buckets "2, 3, and 4" are cleared.
451 *
452 * @param currentTime Current elapsed time since boot in ns
453 */
454 void clearOldOps(long currentTime) {
455 synchronized (mLock) {
456 long numHoursSinceBoot = TimeUnit.HOURS.convert(currentTime, TimeUnit.NANOSECONDS);
457
458 // Clear buckets of new hours that have elapsed since last operation
459 // I.e. when the last operation was triggered at 1:40 and the current
460 // operation was triggered at 4:03, the bucket "2, 3, and 4" is cleared
461 if (mLastOpsHourSinceBoot != 0) {
462 for (long hour = mLastOpsHourSinceBoot + 1; hour <= numHoursSinceBoot; hour++) {
463 mNumOps[(int) (hour % 24)] = 0;
464 }
465 }
466 }
467 }
468
469 /**
470 * Add a new operation.
471 *
472 * @param currentTime Current elapsed time since boot in ns
473 */
474 void addOp(long currentTime) {
475 synchronized (mLock) {
476 long numHoursSinceBoot = TimeUnit.HOURS.convert(currentTime, TimeUnit.NANOSECONDS);
477
478 mNumOps[(int) (numHoursSinceBoot % 24)]++;
479 mLastOpsHourSinceBoot = numHoursSinceBoot;
480 }
481 }
482
483 /**
484 * Get the total operations added in the last 24 hours.
485 *
486 * @return The total number of operations added in the last 24 hours
487 */
488 int getOpsAdded() {
489 synchronized (mLock) {
490 int totalOperationsInLastDay = 0;
491 for (int i = 0; i < 24; i++) {
492 totalOperationsInLastDay += mNumOps[i];
493 }
494
495 return totalOperationsInLastDay;
496 }
497 }
498 }
499
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800500 private interface Operation {
501 void run(int opId, ISoundTriggerDetectionService service) throws RemoteException;
502 }
503
504 /**
505 * Local end for a {@link SoundTriggerDetectionService}. Operations are queued up and executed
506 * when the service connects.
507 *
508 * <p>If operations take too long they are forcefully aborted.
509 *
510 * <p>This also limits the amount of operations in 24 hours.
511 */
512 private class RemoteSoundTriggerDetectionService
513 extends IRecognitionStatusCallback.Stub implements ServiceConnection {
514 private static final int MSG_STOP_ALL_PENDING_OPERATIONS = 1;
515
516 private final Object mRemoteServiceLock = new Object();
517
518 /** UUID of the model the service is started for */
519 private final @NonNull ParcelUuid mPuuid;
520 /** Params passed into the start method for the service */
521 private final @Nullable Bundle mParams;
522 /** Component name passed when starting the service */
523 private final @NonNull ComponentName mServiceName;
524 /** User that started the service */
525 private final @NonNull UserHandle mUser;
526 /** Configuration of the recognition the service is handling */
527 private final @NonNull RecognitionConfig mRecognitionConfig;
528 /** Wake lock keeping the remote service alive */
529 private final @NonNull PowerManager.WakeLock mRemoteServiceWakeLock;
530
531 private final @NonNull Handler mHandler;
532
533 /** Callbacks that are called by the service */
534 private final @NonNull ISoundTriggerDetectionServiceClient mClient;
535
536 /** Operations that are pending because the service is not yet connected */
537 @GuardedBy("mRemoteServiceLock")
538 private final ArrayList<Operation> mPendingOps = new ArrayList<>();
539 /** Operations that have been send to the service but have no yet finished */
540 @GuardedBy("mRemoteServiceLock")
541 private final ArraySet<Integer> mRunningOpIds = new ArraySet<>();
Philip P. Moltmann7e25b3d2018-03-09 20:22:58 -0800542 /** The number of operations executed in each of the last 24 hours */
543 private final NumOps mNumOps;
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800544
545 /** The service binder if connected */
546 @GuardedBy("mRemoteServiceLock")
547 private @Nullable ISoundTriggerDetectionService mService;
548 /** Whether the service has been bound */
549 @GuardedBy("mRemoteServiceLock")
550 private boolean mIsBound;
551 /** Whether the service has been destroyed */
552 @GuardedBy("mRemoteServiceLock")
553 private boolean mIsDestroyed;
554 /**
555 * Set once a final op is scheduled. No further ops can be added and the service is
556 * destroyed once the op finishes.
557 */
558 @GuardedBy("mRemoteServiceLock")
559 private boolean mDestroyOnceRunningOpsDone;
560
561 /** Total number of operations performed by this service */
562 @GuardedBy("mRemoteServiceLock")
563 private int mNumTotalOpsPerformed;
564
565 /**
566 * Create a new remote sound trigger detection service. This only binds to the service when
567 * operations are in flight. Each operation has a certain time it can run. Once no
568 * operations are allowed to run anymore, {@link #stopAllPendingOperations() all operations
569 * are aborted and stopped} and the service is disconnected.
570 *
571 * @param modelUuid The UUID of the model the recognition is for
572 * @param params The params passed to each method of the service
573 * @param serviceName The component name of the service
574 * @param user The user of the service
575 * @param config The configuration of the recognition
576 */
577 public RemoteSoundTriggerDetectionService(@NonNull UUID modelUuid,
578 @Nullable Bundle params, @NonNull ComponentName serviceName, @NonNull UserHandle user,
579 @NonNull RecognitionConfig config) {
580 mPuuid = new ParcelUuid(modelUuid);
581 mParams = params;
582 mServiceName = serviceName;
583 mUser = user;
584 mRecognitionConfig = config;
585 mHandler = new Handler(Looper.getMainLooper());
586
587 PowerManager pm = ((PowerManager) mContext.getSystemService(Context.POWER_SERVICE));
588 mRemoteServiceWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
589 "RemoteSoundTriggerDetectionService " + mServiceName.getPackageName() + ":"
590 + mServiceName.getClassName());
591
Philip P. Moltmann7e25b3d2018-03-09 20:22:58 -0800592 synchronized (mLock) {
593 NumOps numOps = mNumOpsPerPackage.get(mServiceName.getPackageName());
594 if (numOps == null) {
595 numOps = new NumOps();
596 mNumOpsPerPackage.put(mServiceName.getPackageName(), numOps);
597 }
598 mNumOps = numOps;
599 }
600
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800601 mClient = new ISoundTriggerDetectionServiceClient.Stub() {
602 @Override
603 public void onOpFinished(int opId) {
604 long token = Binder.clearCallingIdentity();
605 try {
606 synchronized (mRemoteServiceLock) {
607 mRunningOpIds.remove(opId);
608
609 if (mRunningOpIds.isEmpty() && mPendingOps.isEmpty()) {
610 if (mDestroyOnceRunningOpsDone) {
611 destroy();
612 } else {
613 disconnectLocked();
614 }
615 }
616 }
617 } finally {
618 Binder.restoreCallingIdentity(token);
619 }
620 }
621 };
622 }
623
624 @Override
625 public boolean pingBinder() {
626 return !(mIsDestroyed || mDestroyOnceRunningOpsDone);
627 }
628
629 /**
630 * Disconnect from the service, but allow to re-connect when new operations are triggered.
631 */
632 private void disconnectLocked() {
633 if (mService != null) {
634 try {
635 mService.removeClient(mPuuid);
636 } catch (Exception e) {
637 Slog.e(TAG, mPuuid + ": Cannot remove client", e);
638 }
639
640 mService = null;
641 }
642
643 if (mIsBound) {
644 mContext.unbindService(RemoteSoundTriggerDetectionService.this);
645 mIsBound = false;
646
647 synchronized (mCallbacksLock) {
648 mRemoteServiceWakeLock.release();
649 }
650 }
651 }
652
653 /**
654 * Disconnect, do not allow to reconnect to the service. All further operations will be
655 * dropped.
656 */
657 private void destroy() {
658 if (DEBUG) Slog.v(TAG, mPuuid + ": destroy");
659
660 synchronized (mRemoteServiceLock) {
661 disconnectLocked();
662
663 mIsDestroyed = true;
664 }
665
666 // The callback is removed before the flag is set
667 if (!mDestroyOnceRunningOpsDone) {
668 synchronized (mCallbacksLock) {
669 mCallbacks.remove(mPuuid.getUuid());
670 }
671 }
672 }
673
674 /**
675 * Stop all pending operations and then disconnect for the service.
676 */
677 private void stopAllPendingOperations() {
678 synchronized (mRemoteServiceLock) {
679 if (mIsDestroyed) {
680 return;
681 }
682
683 if (mService != null) {
684 int numOps = mRunningOpIds.size();
685 for (int i = 0; i < numOps; i++) {
686 try {
687 mService.onStopOperation(mPuuid, mRunningOpIds.valueAt(i));
688 } catch (Exception e) {
689 Slog.e(TAG, mPuuid + ": Could not stop operation "
690 + mRunningOpIds.valueAt(i), e);
691 }
692 }
693
694 mRunningOpIds.clear();
695 }
696
697 disconnectLocked();
698 }
699 }
700
701 /**
702 * Verify that the service has the expected properties and then bind to the service
703 */
704 private void bind() {
705 long token = Binder.clearCallingIdentity();
706 try {
707 Intent i = new Intent();
708 i.setComponent(mServiceName);
709
710 ResolveInfo ri = mContext.getPackageManager().resolveServiceAsUser(i,
711 GET_SERVICES | GET_META_DATA | MATCH_DEBUG_TRIAGED_MISSING,
712 mUser.getIdentifier());
713
714 if (ri == null) {
715 Slog.w(TAG, mPuuid + ": " + mServiceName + " not found");
716 return;
717 }
718
719 if (!BIND_SOUND_TRIGGER_DETECTION_SERVICE
720 .equals(ri.serviceInfo.permission)) {
721 Slog.w(TAG, mPuuid + ": " + mServiceName + " does not require "
722 + BIND_SOUND_TRIGGER_DETECTION_SERVICE);
723 return;
724 }
725
726 mIsBound = mContext.bindServiceAsUser(i, this,
727 BIND_AUTO_CREATE | BIND_FOREGROUND_SERVICE, mUser);
728
729 if (mIsBound) {
730 mRemoteServiceWakeLock.acquire();
731 } else {
732 Slog.w(TAG, mPuuid + ": Could not bind to " + mServiceName);
733 }
734 } finally {
735 Binder.restoreCallingIdentity(token);
736 }
737 }
738
739 /**
740 * Run an operation (i.e. send it do the service). If the service is not connected, this
741 * binds the service and then runs the operation once connected.
742 *
743 * @param op The operation to run
744 */
745 private void runOrAddOperation(Operation op) {
746 synchronized (mRemoteServiceLock) {
747 if (mIsDestroyed || mDestroyOnceRunningOpsDone) {
748 return;
749 }
750
751 if (mService == null) {
752 mPendingOps.add(op);
753
754 if (!mIsBound) {
755 bind();
756 }
757 } else {
Philip P. Moltmann7e25b3d2018-03-09 20:22:58 -0800758 long currentTime = System.nanoTime();
759 mNumOps.clearOldOps(currentTime);
760
761 // Drop operation if too many were executed in the last 24 hours.
762 int opsAllowed = Settings.Global.getInt(mContext.getContentResolver(),
763 MAX_SOUND_TRIGGER_DETECTION_SERVICE_OPS_PER_DAY,
764 Integer.MAX_VALUE);
765
766 int opsAdded = mNumOps.getOpsAdded();
767 if (mNumOps.getOpsAdded() >= opsAllowed) {
768 if (DEBUG || opsAllowed + 10 > opsAdded) {
769 Slog.w(TAG, mPuuid + ": Dropped operation as too many operations were "
770 + "run in last 24 hours");
771 }
772 return;
773 }
774
775 mNumOps.addOp(currentTime);
776
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800777 // Find a free opID
778 int opId = mNumTotalOpsPerformed;
779 do {
780 mNumTotalOpsPerformed++;
781 } while (mRunningOpIds.contains(opId));
782
783 // Run OP
784 try {
785 if (DEBUG) Slog.v(TAG, mPuuid + ": runOp " + opId);
786
787 op.run(opId, mService);
788 mRunningOpIds.add(opId);
789 } catch (Exception e) {
790 Slog.e(TAG, mPuuid + ": Could not run operation " + opId, e);
791 }
792
793 // Unbind from service if no operations are left (i.e. if the operation failed)
794 if (mPendingOps.isEmpty() && mRunningOpIds.isEmpty()) {
795 if (mDestroyOnceRunningOpsDone) {
796 destroy();
797 } else {
798 disconnectLocked();
799 }
800 } else {
801 mHandler.removeMessages(MSG_STOP_ALL_PENDING_OPERATIONS);
802 mHandler.sendMessageDelayed(obtainMessage(
803 RemoteSoundTriggerDetectionService::stopAllPendingOperations, this)
804 .setWhat(MSG_STOP_ALL_PENDING_OPERATIONS),
805 Settings.Global.getLong(mContext.getContentResolver(),
806 SOUND_TRIGGER_DETECTION_SERVICE_OP_TIMEOUT,
807 Long.MAX_VALUE));
808 }
809 }
810 }
811 }
812
813 @Override
814 public void onKeyphraseDetected(SoundTrigger.KeyphraseRecognitionEvent event) {
815 Slog.w(TAG, mPuuid + "->" + mServiceName + ": IGNORED onKeyphraseDetected(" + event
816 + ")");
817 }
818
819 @Override
820 public void onGenericSoundTriggerDetected(SoundTrigger.GenericRecognitionEvent event) {
821 if (DEBUG) Slog.v(TAG, mPuuid + ": Generic sound trigger event: " + event);
822
823 runOrAddOperation((opId, service) -> {
824 if (!mRecognitionConfig.allowMultipleTriggers) {
825 synchronized (mCallbacksLock) {
826 mCallbacks.remove(mPuuid.getUuid());
827 }
828 mDestroyOnceRunningOpsDone = true;
829 }
830
831 service.onGenericRecognitionEvent(mPuuid, opId, event);
832 });
833 }
834
835 @Override
836 public void onError(int status) {
837 if (DEBUG) Slog.v(TAG, mPuuid + ": onError: " + status);
838
839 runOrAddOperation((opId, service) -> {
840 synchronized (mCallbacksLock) {
841 mCallbacks.remove(mPuuid.getUuid());
842 }
843 mDestroyOnceRunningOpsDone = true;
844
845 service.onError(mPuuid, opId, status);
846 });
847 }
848
849 @Override
850 public void onRecognitionPaused() {
851 Slog.i(TAG, mPuuid + "->" + mServiceName + ": IGNORED onRecognitionPaused");
852 }
853
854 @Override
855 public void onRecognitionResumed() {
856 Slog.i(TAG, mPuuid + "->" + mServiceName + ": IGNORED onRecognitionResumed");
857 }
858
859 @Override
860 public void onServiceConnected(ComponentName name, IBinder service) {
861 if (DEBUG) Slog.v(TAG, mPuuid + ": onServiceConnected(" + service + ")");
862
863 synchronized (mRemoteServiceLock) {
864 mService = ISoundTriggerDetectionService.Stub.asInterface(service);
865
866 try {
867 mService.setClient(mPuuid, mParams, mClient);
868 } catch (Exception e) {
869 Slog.e(TAG, mPuuid + ": Could not init " + mServiceName, e);
870 return;
871 }
872
873 while (!mPendingOps.isEmpty()) {
874 runOrAddOperation(mPendingOps.remove(0));
875 }
876 }
877 }
878
879 @Override
880 public void onServiceDisconnected(ComponentName name) {
881 if (DEBUG) Slog.v(TAG, mPuuid + ": onServiceDisconnected");
882
883 synchronized (mRemoteServiceLock) {
884 mService = null;
885 }
886 }
887
888 @Override
889 public void onBindingDied(ComponentName name) {
890 if (DEBUG) Slog.v(TAG, mPuuid + ": onBindingDied");
891
892 synchronized (mRemoteServiceLock) {
893 destroy();
894 }
895 }
896
897 @Override
898 public void onNullBinding(ComponentName name) {
899 Slog.w(TAG, name + " for model " + mPuuid + " returned a null binding");
900
901 synchronized (mRemoteServiceLock) {
902 disconnectLocked();
903 }
904 }
905 }
906
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800907 public final class LocalSoundTriggerService extends SoundTriggerInternal {
908 private final Context mContext;
909 private SoundTriggerHelper mSoundTriggerHelper;
910
911 LocalSoundTriggerService(Context context) {
912 mContext = context;
913 }
914
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800915 synchronized void setSoundTriggerHelper(SoundTriggerHelper helper) {
916 mSoundTriggerHelper = helper;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800917 }
918
919 @Override
920 public int startRecognition(int keyphraseId, KeyphraseSoundModel soundModel,
921 IRecognitionStatusCallback listener, RecognitionConfig recognitionConfig) {
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800922 if (!isInitialized()) return STATUS_ERROR;
923 return mSoundTriggerHelper.startKeyphraseRecognition(keyphraseId, soundModel, listener,
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800924 recognitionConfig);
925 }
926
927 @Override
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800928 public synchronized int stopRecognition(int keyphraseId, IRecognitionStatusCallback listener) {
929 if (!isInitialized()) return STATUS_ERROR;
930 return mSoundTriggerHelper.stopKeyphraseRecognition(keyphraseId, listener);
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800931 }
932
933 @Override
Arunesh Mishra55a9b002016-02-01 14:06:37 -0800934 public ModuleProperties getModuleProperties() {
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800935 if (!isInitialized()) return null;
Arunesh Mishra55a9b002016-02-01 14:06:37 -0800936 return mSoundTriggerHelper.getModuleProperties();
937 }
938
939 @Override
Arunesh Mishra2d1de782016-02-21 18:10:28 -0800940 public int unloadKeyphraseModel(int keyphraseId) {
941 if (!isInitialized()) return STATUS_ERROR;
942 return mSoundTriggerHelper.unloadKeyphraseSoundModel(keyphraseId);
943 }
944
945 @Override
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800946 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800947 if (!isInitialized()) return;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800948 mSoundTriggerHelper.dump(fd, pw, args);
949 }
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800950
951 private synchronized boolean isInitialized() {
952 if (mSoundTriggerHelper == null ) {
953 Slog.e(TAG, "SoundTriggerHelper not initialized.");
954 return false;
955 }
956 return true;
957 }
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800958 }
959
960 private void enforceCallingPermission(String permission) {
961 if (mContext.checkCallingOrSelfPermission(permission)
962 != PackageManager.PERMISSION_GRANTED) {
963 throw new SecurityException("Caller does not hold the permission " + permission);
964 }
965 }
966}