blob: 11609435205f2078d512db425f10b8c03f6d1928 [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;
49import android.media.soundtrigger.ISoundTriggerDetectionService;
50import android.media.soundtrigger.ISoundTriggerDetectionServiceClient;
51import android.media.soundtrigger.SoundTriggerDetectionService;
Chris Thorntonba08b792017-06-08 22:34:37 -070052import android.media.soundtrigger.SoundTriggerManager;
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -080053import android.os.Binder;
Chris Thorntonba08b792017-06-08 22:34:37 -070054import android.os.Bundle;
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -080055import android.os.Handler;
56import android.os.IBinder;
57import android.os.Looper;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -080058import android.os.Parcel;
59import android.os.ParcelUuid;
Chris Thorntonba08b792017-06-08 22:34:37 -070060import android.os.PowerManager;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -080061import android.os.RemoteException;
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -080062import android.os.UserHandle;
63import android.provider.Settings;
Philip P. Moltmann7e25b3d2018-03-09 20:22:58 -080064import android.util.ArrayMap;
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -080065import android.util.ArraySet;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -080066import android.util.Slog;
67
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -080068import com.android.internal.annotations.GuardedBy;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -080069import com.android.internal.app.ISoundTriggerService;
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -080070import com.android.internal.util.DumpUtils;
71import com.android.internal.util.Preconditions;
72import com.android.server.SystemService;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -080073
74import java.io.FileDescriptor;
75import java.io.PrintWriter;
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -080076import java.util.ArrayList;
Chris Thorntonba08b792017-06-08 22:34:37 -070077import java.util.TreeMap;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -080078import java.util.UUID;
Philip P. Moltmann7e25b3d2018-03-09 20:22:58 -080079import java.util.concurrent.TimeUnit;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -080080
81/**
82 * A single SystemService to manage all sound/voice-based sound models on the DSP.
83 * This services provides apis to manage sound trigger-based sound models via
84 * the ISoundTriggerService interface. This class also publishes a local interface encapsulating
85 * the functionality provided by {@link SoundTriggerHelper} for use by
86 * {@link VoiceInteractionManagerService}.
87 *
88 * @hide
89 */
90public class SoundTriggerService extends SystemService {
Arunesh Mishra3fff7f52016-02-09 12:15:19 -080091 private static final String TAG = "SoundTriggerService";
92 private static final boolean DEBUG = true;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -080093
94 final Context mContext;
Chris Thorntonba08b792017-06-08 22:34:37 -070095 private Object mLock;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -080096 private final SoundTriggerServiceStub mServiceStub;
97 private final LocalSoundTriggerService mLocalSoundTriggerService;
98 private SoundTriggerDbHelper mDbHelper;
Arunesh Mishra3fff7f52016-02-09 12:15:19 -080099 private SoundTriggerHelper mSoundTriggerHelper;
Chris Thorntonba08b792017-06-08 22:34:37 -0700100 private final TreeMap<UUID, SoundModel> mLoadedModels;
Chris Thorntonae5fb992017-12-07 18:26:31 -0800101 private Object mCallbacksLock;
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800102 private final TreeMap<UUID, IRecognitionStatusCallback> mCallbacks;
Chris Thorntonba08b792017-06-08 22:34:37 -0700103 private PowerManager.WakeLock mWakelock;
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
299 return startRecognitionForInt(soundModelId,
300 new RemoteSoundTriggerDetectionService(soundModelId.getUuid(),
301 params, detectionService, Binder.getCallingUserHandle(), config), config);
302
303 }
304
305 @Override
Chris Thorntonba08b792017-06-08 22:34:37 -0700306 public int startRecognitionForIntent(ParcelUuid soundModelId, PendingIntent callbackIntent,
307 SoundTrigger.RecognitionConfig config) {
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800308 return startRecognitionForInt(soundModelId,
309 new LocalSoundTriggerRecognitionStatusIntentCallback(soundModelId.getUuid(),
310 callbackIntent, config), config);
311 }
312
313 private int startRecognitionForInt(ParcelUuid soundModelId,
314 IRecognitionStatusCallback callback, SoundTrigger.RecognitionConfig config) {
Chris Thorntonba08b792017-06-08 22:34:37 -0700315 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
316 if (!isInitialized()) return STATUS_ERROR;
317 if (DEBUG) {
318 Slog.i(TAG, "startRecognition(): id = " + soundModelId);
319 }
320
321 synchronized (mLock) {
322 SoundModel soundModel = mLoadedModels.get(soundModelId.getUuid());
323 if (soundModel == null) {
324 Slog.e(TAG, soundModelId + " is not loaded");
325 return STATUS_ERROR;
326 }
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800327 IRecognitionStatusCallback existingCallback = null;
Chris Thorntonae5fb992017-12-07 18:26:31 -0800328 synchronized (mCallbacksLock) {
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800329 existingCallback = mCallbacks.get(soundModelId.getUuid());
Chris Thorntonae5fb992017-12-07 18:26:31 -0800330 }
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800331 if (existingCallback != null) {
Chris Thorntonba08b792017-06-08 22:34:37 -0700332 Slog.e(TAG, soundModelId + " is already running");
333 return STATUS_ERROR;
334 }
Chris Thorntonba08b792017-06-08 22:34:37 -0700335 int ret;
336 switch (soundModel.type) {
337 case SoundModel.TYPE_KEYPHRASE: {
338 KeyphraseSoundModel keyphraseSoundModel = (KeyphraseSoundModel) soundModel;
339 ret = mSoundTriggerHelper.startKeyphraseRecognition(
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800340 keyphraseSoundModel.keyphrases[0].id, keyphraseSoundModel, callback,
341 config);
Chris Thorntonba08b792017-06-08 22:34:37 -0700342 } break;
343 case SoundModel.TYPE_GENERIC_SOUND:
344 ret = mSoundTriggerHelper.startGenericRecognition(soundModel.uuid,
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800345 (GenericSoundModel) soundModel, callback, config);
Chris Thorntonba08b792017-06-08 22:34:37 -0700346 break;
347 default:
348 Slog.e(TAG, "Unknown model type");
349 return STATUS_ERROR;
350 }
351
352 if (ret != STATUS_OK) {
353 Slog.e(TAG, "Failed to start model: " + ret);
354 return ret;
355 }
Chris Thorntonae5fb992017-12-07 18:26:31 -0800356 synchronized (mCallbacksLock) {
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800357 mCallbacks.put(soundModelId.getUuid(), callback);
Chris Thorntonae5fb992017-12-07 18:26:31 -0800358 }
Chris Thorntonba08b792017-06-08 22:34:37 -0700359 }
360 return STATUS_OK;
361 }
362
363 @Override
364 public int stopRecognitionForIntent(ParcelUuid soundModelId) {
365 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
366 if (!isInitialized()) return STATUS_ERROR;
367 if (DEBUG) {
368 Slog.i(TAG, "stopRecognition(): id = " + soundModelId);
369 }
370
371 synchronized (mLock) {
372 SoundModel soundModel = mLoadedModels.get(soundModelId.getUuid());
373 if (soundModel == null) {
374 Slog.e(TAG, soundModelId + " is not loaded");
375 return STATUS_ERROR;
376 }
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800377 IRecognitionStatusCallback callback = null;
Chris Thorntonae5fb992017-12-07 18:26:31 -0800378 synchronized (mCallbacksLock) {
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800379 callback = mCallbacks.get(soundModelId.getUuid());
Chris Thorntonae5fb992017-12-07 18:26:31 -0800380 }
Chris Thorntonba08b792017-06-08 22:34:37 -0700381 if (callback == null) {
382 Slog.e(TAG, soundModelId + " is not running");
383 return STATUS_ERROR;
384 }
385 int ret;
386 switch (soundModel.type) {
387 case SoundModel.TYPE_KEYPHRASE:
388 ret = mSoundTriggerHelper.stopKeyphraseRecognition(
389 ((KeyphraseSoundModel)soundModel).keyphrases[0].id, callback);
390 break;
391 case SoundModel.TYPE_GENERIC_SOUND:
392 ret = mSoundTriggerHelper.stopGenericRecognition(soundModel.uuid, callback);
393 break;
394 default:
395 Slog.e(TAG, "Unknown model type");
396 return STATUS_ERROR;
397 }
398
399 if (ret != STATUS_OK) {
400 Slog.e(TAG, "Failed to stop model: " + ret);
401 return ret;
402 }
Chris Thorntonae5fb992017-12-07 18:26:31 -0800403 synchronized (mCallbacksLock) {
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800404 mCallbacks.remove(soundModelId.getUuid());
Chris Thorntonae5fb992017-12-07 18:26:31 -0800405 }
Chris Thorntonba08b792017-06-08 22:34:37 -0700406 }
407 return STATUS_OK;
408 }
409
410 @Override
411 public int unloadSoundModel(ParcelUuid soundModelId) {
412 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
413 if (!isInitialized()) return STATUS_ERROR;
414 if (DEBUG) {
415 Slog.i(TAG, "unloadSoundModel(): id = " + soundModelId);
416 }
417
418 synchronized (mLock) {
419 SoundModel soundModel = mLoadedModels.get(soundModelId.getUuid());
420 if (soundModel == null) {
421 Slog.e(TAG, soundModelId + " is not loaded");
422 return STATUS_ERROR;
423 }
424 int ret;
425 switch (soundModel.type) {
426 case SoundModel.TYPE_KEYPHRASE:
427 ret = mSoundTriggerHelper.unloadKeyphraseSoundModel(
428 ((KeyphraseSoundModel)soundModel).keyphrases[0].id);
429 break;
430 case SoundModel.TYPE_GENERIC_SOUND:
431 ret = mSoundTriggerHelper.unloadGenericSoundModel(soundModel.uuid);
432 break;
433 default:
434 Slog.e(TAG, "Unknown model type");
435 return STATUS_ERROR;
436 }
437 if (ret != STATUS_OK) {
438 Slog.e(TAG, "Failed to unload model");
439 return ret;
440 }
441 mLoadedModels.remove(soundModelId.getUuid());
442 return STATUS_OK;
443 }
444 }
445
446 @Override
447 public boolean isRecognitionActive(ParcelUuid parcelUuid) {
448 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
449 if (!isInitialized()) return false;
Chris Thorntonae5fb992017-12-07 18:26:31 -0800450 synchronized (mCallbacksLock) {
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800451 IRecognitionStatusCallback callback = mCallbacks.get(parcelUuid.getUuid());
Chris Thorntonba08b792017-06-08 22:34:37 -0700452 if (callback == null) {
453 return false;
454 }
Chris Thorntonba08b792017-06-08 22:34:37 -0700455 }
Chris Thorntonae5fb992017-12-07 18:26:31 -0800456 return mSoundTriggerHelper.isRecognitionRequested(parcelUuid.getUuid());
Chris Thorntonba08b792017-06-08 22:34:37 -0700457 }
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800458 }
459
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800460 private final class LocalSoundTriggerRecognitionStatusIntentCallback
Chris Thorntonba08b792017-06-08 22:34:37 -0700461 extends IRecognitionStatusCallback.Stub {
462 private UUID mUuid;
463 private PendingIntent mCallbackIntent;
464 private RecognitionConfig mRecognitionConfig;
465
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800466 public LocalSoundTriggerRecognitionStatusIntentCallback(UUID modelUuid,
Chris Thorntonba08b792017-06-08 22:34:37 -0700467 PendingIntent callbackIntent,
468 RecognitionConfig config) {
469 mUuid = modelUuid;
470 mCallbackIntent = callbackIntent;
471 mRecognitionConfig = config;
472 }
473
474 @Override
475 public boolean pingBinder() {
476 return mCallbackIntent != null;
477 }
478
479 @Override
480 public void onKeyphraseDetected(SoundTrigger.KeyphraseRecognitionEvent event) {
481 if (mCallbackIntent == null) {
482 return;
483 }
484 grabWakeLock();
485
486 Slog.w(TAG, "Keyphrase sound trigger event: " + event);
487 Intent extras = new Intent();
488 extras.putExtra(SoundTriggerManager.EXTRA_MESSAGE_TYPE,
489 SoundTriggerManager.FLAG_MESSAGE_TYPE_RECOGNITION_EVENT);
490 extras.putExtra(SoundTriggerManager.EXTRA_RECOGNITION_EVENT, event);
491 try {
492 mCallbackIntent.send(mContext, 0, extras, mCallbackCompletedHandler, null);
493 if (!mRecognitionConfig.allowMultipleTriggers) {
494 removeCallback(/*releaseWakeLock=*/false);
495 }
496 } catch (PendingIntent.CanceledException e) {
497 removeCallback(/*releaseWakeLock=*/true);
498 }
499 }
500
501 @Override
502 public void onGenericSoundTriggerDetected(SoundTrigger.GenericRecognitionEvent event) {
503 if (mCallbackIntent == null) {
504 return;
505 }
506 grabWakeLock();
507
508 Slog.w(TAG, "Generic sound trigger event: " + event);
509 Intent extras = new Intent();
510 extras.putExtra(SoundTriggerManager.EXTRA_MESSAGE_TYPE,
511 SoundTriggerManager.FLAG_MESSAGE_TYPE_RECOGNITION_EVENT);
512 extras.putExtra(SoundTriggerManager.EXTRA_RECOGNITION_EVENT, event);
513 try {
514 mCallbackIntent.send(mContext, 0, extras, mCallbackCompletedHandler, null);
515 if (!mRecognitionConfig.allowMultipleTriggers) {
516 removeCallback(/*releaseWakeLock=*/false);
517 }
518 } catch (PendingIntent.CanceledException e) {
519 removeCallback(/*releaseWakeLock=*/true);
520 }
521 }
522
523 @Override
524 public void onError(int status) {
525 if (mCallbackIntent == null) {
526 return;
527 }
528 grabWakeLock();
529
530 Slog.i(TAG, "onError: " + status);
531 Intent extras = new Intent();
532 extras.putExtra(SoundTriggerManager.EXTRA_MESSAGE_TYPE,
533 SoundTriggerManager.FLAG_MESSAGE_TYPE_RECOGNITION_ERROR);
534 extras.putExtra(SoundTriggerManager.EXTRA_STATUS, status);
535 try {
536 mCallbackIntent.send(mContext, 0, extras, mCallbackCompletedHandler, null);
537 // Remove the callback, but wait for the intent to finish before we let go of the
538 // wake lock
539 removeCallback(/*releaseWakeLock=*/false);
540 } catch (PendingIntent.CanceledException e) {
541 removeCallback(/*releaseWakeLock=*/true);
542 }
543 }
544
545 @Override
546 public void onRecognitionPaused() {
547 if (mCallbackIntent == null) {
548 return;
549 }
550 grabWakeLock();
551
552 Slog.i(TAG, "onRecognitionPaused");
553 Intent extras = new Intent();
554 extras.putExtra(SoundTriggerManager.EXTRA_MESSAGE_TYPE,
555 SoundTriggerManager.FLAG_MESSAGE_TYPE_RECOGNITION_PAUSED);
556 try {
557 mCallbackIntent.send(mContext, 0, extras, mCallbackCompletedHandler, null);
558 } catch (PendingIntent.CanceledException e) {
559 removeCallback(/*releaseWakeLock=*/true);
560 }
561 }
562
563 @Override
564 public void onRecognitionResumed() {
565 if (mCallbackIntent == null) {
566 return;
567 }
568 grabWakeLock();
569
570 Slog.i(TAG, "onRecognitionResumed");
571 Intent extras = new Intent();
572 extras.putExtra(SoundTriggerManager.EXTRA_MESSAGE_TYPE,
573 SoundTriggerManager.FLAG_MESSAGE_TYPE_RECOGNITION_RESUMED);
574 try {
575 mCallbackIntent.send(mContext, 0, extras, mCallbackCompletedHandler, null);
576 } catch (PendingIntent.CanceledException e) {
577 removeCallback(/*releaseWakeLock=*/true);
578 }
579 }
580
581 private void removeCallback(boolean releaseWakeLock) {
582 mCallbackIntent = null;
Chris Thorntonae5fb992017-12-07 18:26:31 -0800583 synchronized (mCallbacksLock) {
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800584 mCallbacks.remove(mUuid);
Chris Thorntonba08b792017-06-08 22:34:37 -0700585 if (releaseWakeLock) {
586 mWakelock.release();
587 }
588 }
589 }
590 }
591
Philip P. Moltmann7e25b3d2018-03-09 20:22:58 -0800592 /**
593 * Counts the number of operations added in the last 24 hours.
594 */
595 private static class NumOps {
596 private final Object mLock = new Object();
597
598 @GuardedBy("mLock")
599 private int[] mNumOps = new int[24];
600 @GuardedBy("mLock")
601 private long mLastOpsHourSinceBoot;
602
603 /**
604 * Clear buckets of new hours that have elapsed since last operation.
605 *
606 * <p>I.e. when the last operation was triggered at 1:40 and the current operation was
607 * triggered at 4:03, the buckets "2, 3, and 4" are cleared.
608 *
609 * @param currentTime Current elapsed time since boot in ns
610 */
611 void clearOldOps(long currentTime) {
612 synchronized (mLock) {
613 long numHoursSinceBoot = TimeUnit.HOURS.convert(currentTime, TimeUnit.NANOSECONDS);
614
615 // Clear buckets of new hours that have elapsed since last operation
616 // I.e. when the last operation was triggered at 1:40 and the current
617 // operation was triggered at 4:03, the bucket "2, 3, and 4" is cleared
618 if (mLastOpsHourSinceBoot != 0) {
619 for (long hour = mLastOpsHourSinceBoot + 1; hour <= numHoursSinceBoot; hour++) {
620 mNumOps[(int) (hour % 24)] = 0;
621 }
622 }
623 }
624 }
625
626 /**
627 * Add a new operation.
628 *
629 * @param currentTime Current elapsed time since boot in ns
630 */
631 void addOp(long currentTime) {
632 synchronized (mLock) {
633 long numHoursSinceBoot = TimeUnit.HOURS.convert(currentTime, TimeUnit.NANOSECONDS);
634
635 mNumOps[(int) (numHoursSinceBoot % 24)]++;
636 mLastOpsHourSinceBoot = numHoursSinceBoot;
637 }
638 }
639
640 /**
641 * Get the total operations added in the last 24 hours.
642 *
643 * @return The total number of operations added in the last 24 hours
644 */
645 int getOpsAdded() {
646 synchronized (mLock) {
647 int totalOperationsInLastDay = 0;
648 for (int i = 0; i < 24; i++) {
649 totalOperationsInLastDay += mNumOps[i];
650 }
651
652 return totalOperationsInLastDay;
653 }
654 }
655 }
656
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800657 private interface Operation {
658 void run(int opId, ISoundTriggerDetectionService service) throws RemoteException;
659 }
660
661 /**
662 * Local end for a {@link SoundTriggerDetectionService}. Operations are queued up and executed
663 * when the service connects.
664 *
665 * <p>If operations take too long they are forcefully aborted.
666 *
667 * <p>This also limits the amount of operations in 24 hours.
668 */
669 private class RemoteSoundTriggerDetectionService
670 extends IRecognitionStatusCallback.Stub implements ServiceConnection {
671 private static final int MSG_STOP_ALL_PENDING_OPERATIONS = 1;
672
673 private final Object mRemoteServiceLock = new Object();
674
675 /** UUID of the model the service is started for */
676 private final @NonNull ParcelUuid mPuuid;
677 /** Params passed into the start method for the service */
678 private final @Nullable Bundle mParams;
679 /** Component name passed when starting the service */
680 private final @NonNull ComponentName mServiceName;
681 /** User that started the service */
682 private final @NonNull UserHandle mUser;
683 /** Configuration of the recognition the service is handling */
684 private final @NonNull RecognitionConfig mRecognitionConfig;
685 /** Wake lock keeping the remote service alive */
686 private final @NonNull PowerManager.WakeLock mRemoteServiceWakeLock;
687
688 private final @NonNull Handler mHandler;
689
690 /** Callbacks that are called by the service */
691 private final @NonNull ISoundTriggerDetectionServiceClient mClient;
692
693 /** Operations that are pending because the service is not yet connected */
694 @GuardedBy("mRemoteServiceLock")
695 private final ArrayList<Operation> mPendingOps = new ArrayList<>();
696 /** Operations that have been send to the service but have no yet finished */
697 @GuardedBy("mRemoteServiceLock")
698 private final ArraySet<Integer> mRunningOpIds = new ArraySet<>();
Philip P. Moltmann7e25b3d2018-03-09 20:22:58 -0800699 /** The number of operations executed in each of the last 24 hours */
700 private final NumOps mNumOps;
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800701
702 /** The service binder if connected */
703 @GuardedBy("mRemoteServiceLock")
704 private @Nullable ISoundTriggerDetectionService mService;
705 /** Whether the service has been bound */
706 @GuardedBy("mRemoteServiceLock")
707 private boolean mIsBound;
708 /** Whether the service has been destroyed */
709 @GuardedBy("mRemoteServiceLock")
710 private boolean mIsDestroyed;
711 /**
712 * Set once a final op is scheduled. No further ops can be added and the service is
713 * destroyed once the op finishes.
714 */
715 @GuardedBy("mRemoteServiceLock")
716 private boolean mDestroyOnceRunningOpsDone;
717
718 /** Total number of operations performed by this service */
719 @GuardedBy("mRemoteServiceLock")
720 private int mNumTotalOpsPerformed;
721
722 /**
723 * Create a new remote sound trigger detection service. This only binds to the service when
724 * operations are in flight. Each operation has a certain time it can run. Once no
725 * operations are allowed to run anymore, {@link #stopAllPendingOperations() all operations
726 * are aborted and stopped} and the service is disconnected.
727 *
728 * @param modelUuid The UUID of the model the recognition is for
729 * @param params The params passed to each method of the service
730 * @param serviceName The component name of the service
731 * @param user The user of the service
732 * @param config The configuration of the recognition
733 */
734 public RemoteSoundTriggerDetectionService(@NonNull UUID modelUuid,
735 @Nullable Bundle params, @NonNull ComponentName serviceName, @NonNull UserHandle user,
736 @NonNull RecognitionConfig config) {
737 mPuuid = new ParcelUuid(modelUuid);
738 mParams = params;
739 mServiceName = serviceName;
740 mUser = user;
741 mRecognitionConfig = config;
742 mHandler = new Handler(Looper.getMainLooper());
743
744 PowerManager pm = ((PowerManager) mContext.getSystemService(Context.POWER_SERVICE));
745 mRemoteServiceWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
746 "RemoteSoundTriggerDetectionService " + mServiceName.getPackageName() + ":"
747 + mServiceName.getClassName());
748
Philip P. Moltmann7e25b3d2018-03-09 20:22:58 -0800749 synchronized (mLock) {
750 NumOps numOps = mNumOpsPerPackage.get(mServiceName.getPackageName());
751 if (numOps == null) {
752 numOps = new NumOps();
753 mNumOpsPerPackage.put(mServiceName.getPackageName(), numOps);
754 }
755 mNumOps = numOps;
756 }
757
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800758 mClient = new ISoundTriggerDetectionServiceClient.Stub() {
759 @Override
760 public void onOpFinished(int opId) {
761 long token = Binder.clearCallingIdentity();
762 try {
763 synchronized (mRemoteServiceLock) {
764 mRunningOpIds.remove(opId);
765
766 if (mRunningOpIds.isEmpty() && mPendingOps.isEmpty()) {
767 if (mDestroyOnceRunningOpsDone) {
768 destroy();
769 } else {
770 disconnectLocked();
771 }
772 }
773 }
774 } finally {
775 Binder.restoreCallingIdentity(token);
776 }
777 }
778 };
779 }
780
781 @Override
782 public boolean pingBinder() {
783 return !(mIsDestroyed || mDestroyOnceRunningOpsDone);
784 }
785
786 /**
787 * Disconnect from the service, but allow to re-connect when new operations are triggered.
788 */
789 private void disconnectLocked() {
790 if (mService != null) {
791 try {
792 mService.removeClient(mPuuid);
793 } catch (Exception e) {
794 Slog.e(TAG, mPuuid + ": Cannot remove client", e);
795 }
796
797 mService = null;
798 }
799
800 if (mIsBound) {
801 mContext.unbindService(RemoteSoundTriggerDetectionService.this);
802 mIsBound = false;
803
804 synchronized (mCallbacksLock) {
805 mRemoteServiceWakeLock.release();
806 }
807 }
808 }
809
810 /**
811 * Disconnect, do not allow to reconnect to the service. All further operations will be
812 * dropped.
813 */
814 private void destroy() {
815 if (DEBUG) Slog.v(TAG, mPuuid + ": destroy");
816
817 synchronized (mRemoteServiceLock) {
818 disconnectLocked();
819
820 mIsDestroyed = true;
821 }
822
823 // The callback is removed before the flag is set
824 if (!mDestroyOnceRunningOpsDone) {
825 synchronized (mCallbacksLock) {
826 mCallbacks.remove(mPuuid.getUuid());
827 }
828 }
829 }
830
831 /**
832 * Stop all pending operations and then disconnect for the service.
833 */
834 private void stopAllPendingOperations() {
835 synchronized (mRemoteServiceLock) {
836 if (mIsDestroyed) {
837 return;
838 }
839
840 if (mService != null) {
841 int numOps = mRunningOpIds.size();
842 for (int i = 0; i < numOps; i++) {
843 try {
844 mService.onStopOperation(mPuuid, mRunningOpIds.valueAt(i));
845 } catch (Exception e) {
846 Slog.e(TAG, mPuuid + ": Could not stop operation "
847 + mRunningOpIds.valueAt(i), e);
848 }
849 }
850
851 mRunningOpIds.clear();
852 }
853
854 disconnectLocked();
855 }
856 }
857
858 /**
859 * Verify that the service has the expected properties and then bind to the service
860 */
861 private void bind() {
862 long token = Binder.clearCallingIdentity();
863 try {
864 Intent i = new Intent();
865 i.setComponent(mServiceName);
866
867 ResolveInfo ri = mContext.getPackageManager().resolveServiceAsUser(i,
868 GET_SERVICES | GET_META_DATA | MATCH_DEBUG_TRIAGED_MISSING,
869 mUser.getIdentifier());
870
871 if (ri == null) {
872 Slog.w(TAG, mPuuid + ": " + mServiceName + " not found");
873 return;
874 }
875
876 if (!BIND_SOUND_TRIGGER_DETECTION_SERVICE
877 .equals(ri.serviceInfo.permission)) {
878 Slog.w(TAG, mPuuid + ": " + mServiceName + " does not require "
879 + BIND_SOUND_TRIGGER_DETECTION_SERVICE);
880 return;
881 }
882
883 mIsBound = mContext.bindServiceAsUser(i, this,
884 BIND_AUTO_CREATE | BIND_FOREGROUND_SERVICE, mUser);
885
886 if (mIsBound) {
887 mRemoteServiceWakeLock.acquire();
888 } else {
889 Slog.w(TAG, mPuuid + ": Could not bind to " + mServiceName);
890 }
891 } finally {
892 Binder.restoreCallingIdentity(token);
893 }
894 }
895
896 /**
897 * Run an operation (i.e. send it do the service). If the service is not connected, this
898 * binds the service and then runs the operation once connected.
899 *
900 * @param op The operation to run
901 */
902 private void runOrAddOperation(Operation op) {
903 synchronized (mRemoteServiceLock) {
904 if (mIsDestroyed || mDestroyOnceRunningOpsDone) {
905 return;
906 }
907
908 if (mService == null) {
909 mPendingOps.add(op);
910
911 if (!mIsBound) {
912 bind();
913 }
914 } else {
Philip P. Moltmann7e25b3d2018-03-09 20:22:58 -0800915 long currentTime = System.nanoTime();
916 mNumOps.clearOldOps(currentTime);
917
918 // Drop operation if too many were executed in the last 24 hours.
919 int opsAllowed = Settings.Global.getInt(mContext.getContentResolver(),
920 MAX_SOUND_TRIGGER_DETECTION_SERVICE_OPS_PER_DAY,
921 Integer.MAX_VALUE);
922
923 int opsAdded = mNumOps.getOpsAdded();
924 if (mNumOps.getOpsAdded() >= opsAllowed) {
925 if (DEBUG || opsAllowed + 10 > opsAdded) {
926 Slog.w(TAG, mPuuid + ": Dropped operation as too many operations were "
927 + "run in last 24 hours");
928 }
929 return;
930 }
931
932 mNumOps.addOp(currentTime);
933
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800934 // Find a free opID
935 int opId = mNumTotalOpsPerformed;
936 do {
937 mNumTotalOpsPerformed++;
938 } while (mRunningOpIds.contains(opId));
939
940 // Run OP
941 try {
942 if (DEBUG) Slog.v(TAG, mPuuid + ": runOp " + opId);
943
944 op.run(opId, mService);
945 mRunningOpIds.add(opId);
946 } catch (Exception e) {
947 Slog.e(TAG, mPuuid + ": Could not run operation " + opId, e);
948 }
949
950 // Unbind from service if no operations are left (i.e. if the operation failed)
951 if (mPendingOps.isEmpty() && mRunningOpIds.isEmpty()) {
952 if (mDestroyOnceRunningOpsDone) {
953 destroy();
954 } else {
955 disconnectLocked();
956 }
957 } else {
958 mHandler.removeMessages(MSG_STOP_ALL_PENDING_OPERATIONS);
959 mHandler.sendMessageDelayed(obtainMessage(
960 RemoteSoundTriggerDetectionService::stopAllPendingOperations, this)
961 .setWhat(MSG_STOP_ALL_PENDING_OPERATIONS),
962 Settings.Global.getLong(mContext.getContentResolver(),
963 SOUND_TRIGGER_DETECTION_SERVICE_OP_TIMEOUT,
964 Long.MAX_VALUE));
965 }
966 }
967 }
968 }
969
970 @Override
971 public void onKeyphraseDetected(SoundTrigger.KeyphraseRecognitionEvent event) {
972 Slog.w(TAG, mPuuid + "->" + mServiceName + ": IGNORED onKeyphraseDetected(" + event
973 + ")");
974 }
975
976 @Override
977 public void onGenericSoundTriggerDetected(SoundTrigger.GenericRecognitionEvent event) {
978 if (DEBUG) Slog.v(TAG, mPuuid + ": Generic sound trigger event: " + event);
979
980 runOrAddOperation((opId, service) -> {
981 if (!mRecognitionConfig.allowMultipleTriggers) {
982 synchronized (mCallbacksLock) {
983 mCallbacks.remove(mPuuid.getUuid());
984 }
985 mDestroyOnceRunningOpsDone = true;
986 }
987
988 service.onGenericRecognitionEvent(mPuuid, opId, event);
989 });
990 }
991
992 @Override
993 public void onError(int status) {
994 if (DEBUG) Slog.v(TAG, mPuuid + ": onError: " + status);
995
996 runOrAddOperation((opId, service) -> {
997 synchronized (mCallbacksLock) {
998 mCallbacks.remove(mPuuid.getUuid());
999 }
1000 mDestroyOnceRunningOpsDone = true;
1001
1002 service.onError(mPuuid, opId, status);
1003 });
1004 }
1005
1006 @Override
1007 public void onRecognitionPaused() {
1008 Slog.i(TAG, mPuuid + "->" + mServiceName + ": IGNORED onRecognitionPaused");
1009 }
1010
1011 @Override
1012 public void onRecognitionResumed() {
1013 Slog.i(TAG, mPuuid + "->" + mServiceName + ": IGNORED onRecognitionResumed");
1014 }
1015
1016 @Override
1017 public void onServiceConnected(ComponentName name, IBinder service) {
1018 if (DEBUG) Slog.v(TAG, mPuuid + ": onServiceConnected(" + service + ")");
1019
1020 synchronized (mRemoteServiceLock) {
1021 mService = ISoundTriggerDetectionService.Stub.asInterface(service);
1022
1023 try {
1024 mService.setClient(mPuuid, mParams, mClient);
1025 } catch (Exception e) {
1026 Slog.e(TAG, mPuuid + ": Could not init " + mServiceName, e);
1027 return;
1028 }
1029
1030 while (!mPendingOps.isEmpty()) {
1031 runOrAddOperation(mPendingOps.remove(0));
1032 }
1033 }
1034 }
1035
1036 @Override
1037 public void onServiceDisconnected(ComponentName name) {
1038 if (DEBUG) Slog.v(TAG, mPuuid + ": onServiceDisconnected");
1039
1040 synchronized (mRemoteServiceLock) {
1041 mService = null;
1042 }
1043 }
1044
1045 @Override
1046 public void onBindingDied(ComponentName name) {
1047 if (DEBUG) Slog.v(TAG, mPuuid + ": onBindingDied");
1048
1049 synchronized (mRemoteServiceLock) {
1050 destroy();
1051 }
1052 }
1053
1054 @Override
1055 public void onNullBinding(ComponentName name) {
1056 Slog.w(TAG, name + " for model " + mPuuid + " returned a null binding");
1057
1058 synchronized (mRemoteServiceLock) {
1059 disconnectLocked();
1060 }
1061 }
1062 }
1063
Chris Thorntonba08b792017-06-08 22:34:37 -07001064 private void grabWakeLock() {
Chris Thorntonae5fb992017-12-07 18:26:31 -08001065 synchronized (mCallbacksLock) {
Chris Thorntonba08b792017-06-08 22:34:37 -07001066 if (mWakelock == null) {
1067 PowerManager pm = ((PowerManager) mContext.getSystemService(Context.POWER_SERVICE));
1068 mWakelock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
1069 }
1070 mWakelock.acquire();
1071 }
1072 }
1073
1074 private PendingIntent.OnFinished mCallbackCompletedHandler = new PendingIntent.OnFinished() {
1075 @Override
1076 public void onSendFinished(PendingIntent pendingIntent, Intent intent, int resultCode,
1077 String resultData, Bundle resultExtras) {
1078 // We're only ever invoked when the callback is done, so release the lock.
Chris Thorntonae5fb992017-12-07 18:26:31 -08001079 synchronized (mCallbacksLock) {
Chris Thorntonba08b792017-06-08 22:34:37 -07001080 mWakelock.release();
1081 }
1082 }
1083 };
1084
Arunesh Mishraa772e5f2016-01-25 10:33:11 -08001085 public final class LocalSoundTriggerService extends SoundTriggerInternal {
1086 private final Context mContext;
1087 private SoundTriggerHelper mSoundTriggerHelper;
1088
1089 LocalSoundTriggerService(Context context) {
1090 mContext = context;
1091 }
1092
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001093 synchronized void setSoundTriggerHelper(SoundTriggerHelper helper) {
1094 mSoundTriggerHelper = helper;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -08001095 }
1096
1097 @Override
1098 public int startRecognition(int keyphraseId, KeyphraseSoundModel soundModel,
1099 IRecognitionStatusCallback listener, RecognitionConfig recognitionConfig) {
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001100 if (!isInitialized()) return STATUS_ERROR;
1101 return mSoundTriggerHelper.startKeyphraseRecognition(keyphraseId, soundModel, listener,
Arunesh Mishraa772e5f2016-01-25 10:33:11 -08001102 recognitionConfig);
1103 }
1104
1105 @Override
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001106 public synchronized int stopRecognition(int keyphraseId, IRecognitionStatusCallback listener) {
1107 if (!isInitialized()) return STATUS_ERROR;
1108 return mSoundTriggerHelper.stopKeyphraseRecognition(keyphraseId, listener);
Arunesh Mishraa772e5f2016-01-25 10:33:11 -08001109 }
1110
1111 @Override
Arunesh Mishra55a9b002016-02-01 14:06:37 -08001112 public ModuleProperties getModuleProperties() {
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001113 if (!isInitialized()) return null;
Arunesh Mishra55a9b002016-02-01 14:06:37 -08001114 return mSoundTriggerHelper.getModuleProperties();
1115 }
1116
1117 @Override
Arunesh Mishra2d1de782016-02-21 18:10:28 -08001118 public int unloadKeyphraseModel(int keyphraseId) {
1119 if (!isInitialized()) return STATUS_ERROR;
1120 return mSoundTriggerHelper.unloadKeyphraseSoundModel(keyphraseId);
1121 }
1122
1123 @Override
Arunesh Mishraa772e5f2016-01-25 10:33:11 -08001124 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001125 if (!isInitialized()) return;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -08001126 mSoundTriggerHelper.dump(fd, pw, args);
1127 }
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001128
1129 private synchronized boolean isInitialized() {
1130 if (mSoundTriggerHelper == null ) {
1131 Slog.e(TAG, "SoundTriggerHelper not initialized.");
1132 return false;
1133 }
1134 return true;
1135 }
Arunesh Mishraa772e5f2016-01-25 10:33:11 -08001136 }
1137
1138 private void enforceCallingPermission(String permission) {
1139 if (mContext.checkCallingOrSelfPermission(permission)
1140 != PackageManager.PERMISSION_GRANTED) {
1141 throw new SecurityException("Caller does not hold the permission " + permission);
1142 }
1143 }
1144}