blob: cd3fdeefee13e083a5f55c7b6c4ae56f6530e24d [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;
Arunesh Mishra3fff7f52016-02-09 12:15:19 -080018import static android.hardware.soundtrigger.SoundTrigger.STATUS_ERROR;
Chris Thorntonba08b792017-06-08 22:34:37 -070019import static android.hardware.soundtrigger.SoundTrigger.STATUS_OK;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -080020
Chris Thorntonba08b792017-06-08 22:34:37 -070021import android.app.PendingIntent;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -080022import android.content.Context;
Chris Thorntonba08b792017-06-08 22:34:37 -070023import android.content.Intent;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -080024import android.content.pm.PackageManager;
25import android.Manifest;
26import android.hardware.soundtrigger.IRecognitionStatusCallback;
27import android.hardware.soundtrigger.SoundTrigger;
Chris Thorntonba08b792017-06-08 22:34:37 -070028import android.hardware.soundtrigger.SoundTrigger.SoundModel;
Arunesh Mishrac722ec412016-01-27 13:29:12 -080029import android.hardware.soundtrigger.SoundTrigger.GenericSoundModel;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -080030import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel;
Arunesh Mishra55a9b002016-02-01 14:06:37 -080031import android.hardware.soundtrigger.SoundTrigger.ModuleProperties;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -080032import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig;
Chris Thorntonba08b792017-06-08 22:34:37 -070033import android.media.soundtrigger.SoundTriggerManager;
34import android.os.Bundle;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -080035import android.os.Parcel;
36import android.os.ParcelUuid;
Chris Thorntonba08b792017-06-08 22:34:37 -070037import android.os.PowerManager;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -080038import android.os.RemoteException;
39import android.util.Slog;
40
41import com.android.server.SystemService;
42import com.android.internal.app.ISoundTriggerService;
43
44import java.io.FileDescriptor;
45import java.io.PrintWriter;
Chris Thorntonba08b792017-06-08 22:34:37 -070046import java.util.TreeMap;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -080047import java.util.UUID;
48
49/**
50 * A single SystemService to manage all sound/voice-based sound models on the DSP.
51 * This services provides apis to manage sound trigger-based sound models via
52 * the ISoundTriggerService interface. This class also publishes a local interface encapsulating
53 * the functionality provided by {@link SoundTriggerHelper} for use by
54 * {@link VoiceInteractionManagerService}.
55 *
56 * @hide
57 */
58public class SoundTriggerService extends SystemService {
Arunesh Mishra3fff7f52016-02-09 12:15:19 -080059 private static final String TAG = "SoundTriggerService";
60 private static final boolean DEBUG = true;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -080061
62 final Context mContext;
Chris Thorntonba08b792017-06-08 22:34:37 -070063 private Object mLock;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -080064 private final SoundTriggerServiceStub mServiceStub;
65 private final LocalSoundTriggerService mLocalSoundTriggerService;
66 private SoundTriggerDbHelper mDbHelper;
Arunesh Mishra3fff7f52016-02-09 12:15:19 -080067 private SoundTriggerHelper mSoundTriggerHelper;
Chris Thorntonba08b792017-06-08 22:34:37 -070068 private final TreeMap<UUID, SoundModel> mLoadedModels;
Chris Thorntonae5fb992017-12-07 18:26:31 -080069 private Object mCallbacksLock;
Chris Thorntonba08b792017-06-08 22:34:37 -070070 private final TreeMap<UUID, LocalSoundTriggerRecognitionStatusCallback> mIntentCallbacks;
71 private PowerManager.WakeLock mWakelock;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -080072
73 public SoundTriggerService(Context context) {
74 super(context);
75 mContext = context;
76 mServiceStub = new SoundTriggerServiceStub();
77 mLocalSoundTriggerService = new LocalSoundTriggerService(context);
Chris Thorntonba08b792017-06-08 22:34:37 -070078 mLoadedModels = new TreeMap<UUID, SoundModel>();
Chris Thorntonae5fb992017-12-07 18:26:31 -080079 mCallbacksLock = new Object();
Chris Thorntonba08b792017-06-08 22:34:37 -070080 mIntentCallbacks = new TreeMap<UUID, LocalSoundTriggerRecognitionStatusCallback>();
81 mLock = new Object();
Arunesh Mishraa772e5f2016-01-25 10:33:11 -080082 }
83
84 @Override
85 public void onStart() {
86 publishBinderService(Context.SOUND_TRIGGER_SERVICE, mServiceStub);
87 publishLocalService(SoundTriggerInternal.class, mLocalSoundTriggerService);
88 }
89
90 @Override
91 public void onBootPhase(int phase) {
92 if (PHASE_SYSTEM_SERVICES_READY == phase) {
Arunesh Mishra3fff7f52016-02-09 12:15:19 -080093 initSoundTriggerHelper();
94 mLocalSoundTriggerService.setSoundTriggerHelper(mSoundTriggerHelper);
Arunesh Mishraa772e5f2016-01-25 10:33:11 -080095 } else if (PHASE_THIRD_PARTY_APPS_CAN_START == phase) {
96 mDbHelper = new SoundTriggerDbHelper(mContext);
97 }
98 }
99
100 @Override
101 public void onStartUser(int userHandle) {
102 }
103
104 @Override
105 public void onSwitchUser(int userHandle) {
106 }
107
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800108 private synchronized void initSoundTriggerHelper() {
109 if (mSoundTriggerHelper == null) {
110 mSoundTriggerHelper = new SoundTriggerHelper(mContext);
111 }
112 }
113
114 private synchronized boolean isInitialized() {
115 if (mSoundTriggerHelper == null ) {
116 Slog.e(TAG, "SoundTriggerHelper not initialized.");
117 return false;
118 }
119 return true;
120 }
121
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800122 class SoundTriggerServiceStub extends ISoundTriggerService.Stub {
123 @Override
124 public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
125 throws RemoteException {
126 try {
127 return super.onTransact(code, data, reply, flags);
128 } catch (RuntimeException e) {
129 // The activity manager only throws security exceptions, so let's
130 // log all others.
131 if (!(e instanceof SecurityException)) {
132 Slog.wtf(TAG, "SoundTriggerService Crash", e);
133 }
134 throw e;
135 }
136 }
137
138 @Override
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800139 public int startRecognition(ParcelUuid parcelUuid, IRecognitionStatusCallback callback,
140 RecognitionConfig config) {
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800141 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
Arunesh Mishra2d1de782016-02-21 18:10:28 -0800142 if (!isInitialized()) return STATUS_ERROR;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800143 if (DEBUG) {
144 Slog.i(TAG, "startRecognition(): Uuid : " + parcelUuid);
145 }
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800146
147 GenericSoundModel model = getSoundModel(parcelUuid);
148 if (model == null) {
149 Slog.e(TAG, "Null model in database for id: " + parcelUuid);
150 return STATUS_ERROR;
151 }
152
153 return mSoundTriggerHelper.startGenericRecognition(parcelUuid.getUuid(), model,
154 callback, config);
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800155 }
156
157 @Override
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800158 public int stopRecognition(ParcelUuid parcelUuid, IRecognitionStatusCallback callback) {
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800159 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
160 if (DEBUG) {
161 Slog.i(TAG, "stopRecognition(): Uuid : " + parcelUuid);
162 }
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800163 if (!isInitialized()) return STATUS_ERROR;
164 return mSoundTriggerHelper.stopGenericRecognition(parcelUuid.getUuid(), callback);
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800165 }
166
167 @Override
Arunesh Mishrac722ec412016-01-27 13:29:12 -0800168 public SoundTrigger.GenericSoundModel getSoundModel(ParcelUuid soundModelId) {
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800169 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
170 if (DEBUG) {
171 Slog.i(TAG, "getSoundModel(): id = " + soundModelId);
172 }
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800173 SoundTrigger.GenericSoundModel model = mDbHelper.getGenericSoundModel(
174 soundModelId.getUuid());
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800175 return model;
176 }
177
178 @Override
Arunesh Mishrac722ec412016-01-27 13:29:12 -0800179 public void updateSoundModel(SoundTrigger.GenericSoundModel soundModel) {
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800180 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
181 if (DEBUG) {
182 Slog.i(TAG, "updateSoundModel(): model = " + soundModel);
183 }
Arunesh Mishrac722ec412016-01-27 13:29:12 -0800184 mDbHelper.updateGenericSoundModel(soundModel);
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800185 }
186
187 @Override
188 public void deleteSoundModel(ParcelUuid soundModelId) {
189 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
190 if (DEBUG) {
191 Slog.i(TAG, "deleteSoundModel(): id = " + soundModelId);
192 }
Arunesh Mishra2d1de782016-02-21 18:10:28 -0800193 // Unload the model if it is loaded.
194 mSoundTriggerHelper.unloadGenericSoundModel(soundModelId.getUuid());
Arunesh Mishrac722ec412016-01-27 13:29:12 -0800195 mDbHelper.deleteGenericSoundModel(soundModelId.getUuid());
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800196 }
Chris Thorntonba08b792017-06-08 22:34:37 -0700197
198 @Override
199 public int loadGenericSoundModel(GenericSoundModel soundModel) {
200 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
201 if (!isInitialized()) return STATUS_ERROR;
202 if (soundModel == null || soundModel.uuid == null) {
203 Slog.e(TAG, "Invalid sound model");
204 return STATUS_ERROR;
205 }
206 if (DEBUG) {
207 Slog.i(TAG, "loadGenericSoundModel(): id = " + soundModel.uuid);
208 }
209 synchronized (mLock) {
210 SoundModel oldModel = mLoadedModels.get(soundModel.uuid);
211 // If the model we're loading is actually different than what we had loaded, we
212 // should unload that other model now. We don't care about return codes since we
213 // don't know if the other model is loaded.
214 if (oldModel != null && !oldModel.equals(soundModel)) {
215 mSoundTriggerHelper.unloadGenericSoundModel(soundModel.uuid);
Chris Thorntonae5fb992017-12-07 18:26:31 -0800216 synchronized (mCallbacksLock) {
217 mIntentCallbacks.remove(soundModel.uuid);
218 }
Chris Thorntonba08b792017-06-08 22:34:37 -0700219 }
220 mLoadedModels.put(soundModel.uuid, soundModel);
221 }
222 return STATUS_OK;
223 }
224
225 @Override
226 public int loadKeyphraseSoundModel(KeyphraseSoundModel soundModel) {
227 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
228 if (!isInitialized()) return STATUS_ERROR;
229 if (soundModel == null || soundModel.uuid == null) {
230 Slog.e(TAG, "Invalid sound model");
231 return STATUS_ERROR;
232 }
233 if (soundModel.keyphrases == null || soundModel.keyphrases.length != 1) {
234 Slog.e(TAG, "Only one keyphrase per model is currently supported.");
235 return STATUS_ERROR;
236 }
237 if (DEBUG) {
238 Slog.i(TAG, "loadKeyphraseSoundModel(): id = " + soundModel.uuid);
239 }
240 synchronized (mLock) {
241 SoundModel oldModel = mLoadedModels.get(soundModel.uuid);
242 // If the model we're loading is actually different than what we had loaded, we
243 // should unload that other model now. We don't care about return codes since we
244 // don't know if the other model is loaded.
245 if (oldModel != null && !oldModel.equals(soundModel)) {
246 mSoundTriggerHelper.unloadKeyphraseSoundModel(soundModel.keyphrases[0].id);
Chris Thorntonae5fb992017-12-07 18:26:31 -0800247 synchronized (mCallbacksLock) {
248 mIntentCallbacks.remove(soundModel.uuid);
249 }
Chris Thorntonba08b792017-06-08 22:34:37 -0700250 }
251 mLoadedModels.put(soundModel.uuid, soundModel);
252 }
253 return STATUS_OK;
254 }
255
256 @Override
257 public int startRecognitionForIntent(ParcelUuid soundModelId, PendingIntent callbackIntent,
258 SoundTrigger.RecognitionConfig config) {
259 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
260 if (!isInitialized()) return STATUS_ERROR;
261 if (DEBUG) {
262 Slog.i(TAG, "startRecognition(): id = " + soundModelId);
263 }
264
265 synchronized (mLock) {
266 SoundModel soundModel = mLoadedModels.get(soundModelId.getUuid());
267 if (soundModel == null) {
268 Slog.e(TAG, soundModelId + " is not loaded");
269 return STATUS_ERROR;
270 }
Chris Thorntonae5fb992017-12-07 18:26:31 -0800271 LocalSoundTriggerRecognitionStatusCallback callback = null;
272 synchronized (mCallbacksLock) {
273 callback = mIntentCallbacks.get(soundModelId.getUuid());
274 }
Chris Thorntonba08b792017-06-08 22:34:37 -0700275 if (callback != null) {
276 Slog.e(TAG, soundModelId + " is already running");
277 return STATUS_ERROR;
278 }
279 callback = new LocalSoundTriggerRecognitionStatusCallback(soundModelId.getUuid(),
280 callbackIntent, config);
281 int ret;
282 switch (soundModel.type) {
283 case SoundModel.TYPE_KEYPHRASE: {
284 KeyphraseSoundModel keyphraseSoundModel = (KeyphraseSoundModel) soundModel;
285 ret = mSoundTriggerHelper.startKeyphraseRecognition(
286 keyphraseSoundModel.keyphrases[0].id, keyphraseSoundModel, callback,
287 config);
288 } break;
289 case SoundModel.TYPE_GENERIC_SOUND:
290 ret = mSoundTriggerHelper.startGenericRecognition(soundModel.uuid,
291 (GenericSoundModel) soundModel, callback, config);
292 break;
293 default:
294 Slog.e(TAG, "Unknown model type");
295 return STATUS_ERROR;
296 }
297
298 if (ret != STATUS_OK) {
299 Slog.e(TAG, "Failed to start model: " + ret);
300 return ret;
301 }
Chris Thorntonae5fb992017-12-07 18:26:31 -0800302 synchronized (mCallbacksLock) {
303 mIntentCallbacks.put(soundModelId.getUuid(), callback);
304 }
Chris Thorntonba08b792017-06-08 22:34:37 -0700305 }
306 return STATUS_OK;
307 }
308
309 @Override
310 public int stopRecognitionForIntent(ParcelUuid soundModelId) {
311 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
312 if (!isInitialized()) return STATUS_ERROR;
313 if (DEBUG) {
314 Slog.i(TAG, "stopRecognition(): id = " + soundModelId);
315 }
316
317 synchronized (mLock) {
318 SoundModel soundModel = mLoadedModels.get(soundModelId.getUuid());
319 if (soundModel == null) {
320 Slog.e(TAG, soundModelId + " is not loaded");
321 return STATUS_ERROR;
322 }
Chris Thorntonae5fb992017-12-07 18:26:31 -0800323 LocalSoundTriggerRecognitionStatusCallback callback = null;
324 synchronized (mCallbacksLock) {
325 callback = mIntentCallbacks.get(soundModelId.getUuid());
326 }
Chris Thorntonba08b792017-06-08 22:34:37 -0700327 if (callback == null) {
328 Slog.e(TAG, soundModelId + " is not running");
329 return STATUS_ERROR;
330 }
331 int ret;
332 switch (soundModel.type) {
333 case SoundModel.TYPE_KEYPHRASE:
334 ret = mSoundTriggerHelper.stopKeyphraseRecognition(
335 ((KeyphraseSoundModel)soundModel).keyphrases[0].id, callback);
336 break;
337 case SoundModel.TYPE_GENERIC_SOUND:
338 ret = mSoundTriggerHelper.stopGenericRecognition(soundModel.uuid, callback);
339 break;
340 default:
341 Slog.e(TAG, "Unknown model type");
342 return STATUS_ERROR;
343 }
344
345 if (ret != STATUS_OK) {
346 Slog.e(TAG, "Failed to stop model: " + ret);
347 return ret;
348 }
Chris Thorntonae5fb992017-12-07 18:26:31 -0800349 synchronized (mCallbacksLock) {
350 mIntentCallbacks.remove(soundModelId.getUuid());
351 }
Chris Thorntonba08b792017-06-08 22:34:37 -0700352 }
353 return STATUS_OK;
354 }
355
356 @Override
357 public int unloadSoundModel(ParcelUuid soundModelId) {
358 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
359 if (!isInitialized()) return STATUS_ERROR;
360 if (DEBUG) {
361 Slog.i(TAG, "unloadSoundModel(): id = " + soundModelId);
362 }
363
364 synchronized (mLock) {
365 SoundModel soundModel = mLoadedModels.get(soundModelId.getUuid());
366 if (soundModel == null) {
367 Slog.e(TAG, soundModelId + " is not loaded");
368 return STATUS_ERROR;
369 }
370 int ret;
371 switch (soundModel.type) {
372 case SoundModel.TYPE_KEYPHRASE:
373 ret = mSoundTriggerHelper.unloadKeyphraseSoundModel(
374 ((KeyphraseSoundModel)soundModel).keyphrases[0].id);
375 break;
376 case SoundModel.TYPE_GENERIC_SOUND:
377 ret = mSoundTriggerHelper.unloadGenericSoundModel(soundModel.uuid);
378 break;
379 default:
380 Slog.e(TAG, "Unknown model type");
381 return STATUS_ERROR;
382 }
383 if (ret != STATUS_OK) {
384 Slog.e(TAG, "Failed to unload model");
385 return ret;
386 }
387 mLoadedModels.remove(soundModelId.getUuid());
388 return STATUS_OK;
389 }
390 }
391
392 @Override
393 public boolean isRecognitionActive(ParcelUuid parcelUuid) {
394 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
395 if (!isInitialized()) return false;
Chris Thorntonae5fb992017-12-07 18:26:31 -0800396 synchronized (mCallbacksLock) {
Chris Thorntonba08b792017-06-08 22:34:37 -0700397 LocalSoundTriggerRecognitionStatusCallback callback =
398 mIntentCallbacks.get(parcelUuid.getUuid());
399 if (callback == null) {
400 return false;
401 }
Chris Thorntonba08b792017-06-08 22:34:37 -0700402 }
Chris Thorntonae5fb992017-12-07 18:26:31 -0800403 return mSoundTriggerHelper.isRecognitionRequested(parcelUuid.getUuid());
Chris Thorntonba08b792017-06-08 22:34:37 -0700404 }
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800405 }
406
Chris Thorntonba08b792017-06-08 22:34:37 -0700407 private final class LocalSoundTriggerRecognitionStatusCallback
408 extends IRecognitionStatusCallback.Stub {
409 private UUID mUuid;
410 private PendingIntent mCallbackIntent;
411 private RecognitionConfig mRecognitionConfig;
412
413 public LocalSoundTriggerRecognitionStatusCallback(UUID modelUuid,
414 PendingIntent callbackIntent,
415 RecognitionConfig config) {
416 mUuid = modelUuid;
417 mCallbackIntent = callbackIntent;
418 mRecognitionConfig = config;
419 }
420
421 @Override
422 public boolean pingBinder() {
423 return mCallbackIntent != null;
424 }
425
426 @Override
427 public void onKeyphraseDetected(SoundTrigger.KeyphraseRecognitionEvent event) {
428 if (mCallbackIntent == null) {
429 return;
430 }
431 grabWakeLock();
432
433 Slog.w(TAG, "Keyphrase sound trigger event: " + event);
434 Intent extras = new Intent();
435 extras.putExtra(SoundTriggerManager.EXTRA_MESSAGE_TYPE,
436 SoundTriggerManager.FLAG_MESSAGE_TYPE_RECOGNITION_EVENT);
437 extras.putExtra(SoundTriggerManager.EXTRA_RECOGNITION_EVENT, event);
438 try {
439 mCallbackIntent.send(mContext, 0, extras, mCallbackCompletedHandler, null);
440 if (!mRecognitionConfig.allowMultipleTriggers) {
441 removeCallback(/*releaseWakeLock=*/false);
442 }
443 } catch (PendingIntent.CanceledException e) {
444 removeCallback(/*releaseWakeLock=*/true);
445 }
446 }
447
448 @Override
449 public void onGenericSoundTriggerDetected(SoundTrigger.GenericRecognitionEvent event) {
450 if (mCallbackIntent == null) {
451 return;
452 }
453 grabWakeLock();
454
455 Slog.w(TAG, "Generic sound trigger event: " + event);
456 Intent extras = new Intent();
457 extras.putExtra(SoundTriggerManager.EXTRA_MESSAGE_TYPE,
458 SoundTriggerManager.FLAG_MESSAGE_TYPE_RECOGNITION_EVENT);
459 extras.putExtra(SoundTriggerManager.EXTRA_RECOGNITION_EVENT, event);
460 try {
461 mCallbackIntent.send(mContext, 0, extras, mCallbackCompletedHandler, null);
462 if (!mRecognitionConfig.allowMultipleTriggers) {
463 removeCallback(/*releaseWakeLock=*/false);
464 }
465 } catch (PendingIntent.CanceledException e) {
466 removeCallback(/*releaseWakeLock=*/true);
467 }
468 }
469
470 @Override
471 public void onError(int status) {
472 if (mCallbackIntent == null) {
473 return;
474 }
475 grabWakeLock();
476
477 Slog.i(TAG, "onError: " + status);
478 Intent extras = new Intent();
479 extras.putExtra(SoundTriggerManager.EXTRA_MESSAGE_TYPE,
480 SoundTriggerManager.FLAG_MESSAGE_TYPE_RECOGNITION_ERROR);
481 extras.putExtra(SoundTriggerManager.EXTRA_STATUS, status);
482 try {
483 mCallbackIntent.send(mContext, 0, extras, mCallbackCompletedHandler, null);
484 // Remove the callback, but wait for the intent to finish before we let go of the
485 // wake lock
486 removeCallback(/*releaseWakeLock=*/false);
487 } catch (PendingIntent.CanceledException e) {
488 removeCallback(/*releaseWakeLock=*/true);
489 }
490 }
491
492 @Override
493 public void onRecognitionPaused() {
494 if (mCallbackIntent == null) {
495 return;
496 }
497 grabWakeLock();
498
499 Slog.i(TAG, "onRecognitionPaused");
500 Intent extras = new Intent();
501 extras.putExtra(SoundTriggerManager.EXTRA_MESSAGE_TYPE,
502 SoundTriggerManager.FLAG_MESSAGE_TYPE_RECOGNITION_PAUSED);
503 try {
504 mCallbackIntent.send(mContext, 0, extras, mCallbackCompletedHandler, null);
505 } catch (PendingIntent.CanceledException e) {
506 removeCallback(/*releaseWakeLock=*/true);
507 }
508 }
509
510 @Override
511 public void onRecognitionResumed() {
512 if (mCallbackIntent == null) {
513 return;
514 }
515 grabWakeLock();
516
517 Slog.i(TAG, "onRecognitionResumed");
518 Intent extras = new Intent();
519 extras.putExtra(SoundTriggerManager.EXTRA_MESSAGE_TYPE,
520 SoundTriggerManager.FLAG_MESSAGE_TYPE_RECOGNITION_RESUMED);
521 try {
522 mCallbackIntent.send(mContext, 0, extras, mCallbackCompletedHandler, null);
523 } catch (PendingIntent.CanceledException e) {
524 removeCallback(/*releaseWakeLock=*/true);
525 }
526 }
527
528 private void removeCallback(boolean releaseWakeLock) {
529 mCallbackIntent = null;
Chris Thorntonae5fb992017-12-07 18:26:31 -0800530 synchronized (mCallbacksLock) {
Chris Thorntonba08b792017-06-08 22:34:37 -0700531 mIntentCallbacks.remove(mUuid);
532 if (releaseWakeLock) {
533 mWakelock.release();
534 }
535 }
536 }
537 }
538
539 private void grabWakeLock() {
Chris Thorntonae5fb992017-12-07 18:26:31 -0800540 synchronized (mCallbacksLock) {
Chris Thorntonba08b792017-06-08 22:34:37 -0700541 if (mWakelock == null) {
542 PowerManager pm = ((PowerManager) mContext.getSystemService(Context.POWER_SERVICE));
543 mWakelock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
544 }
545 mWakelock.acquire();
546 }
547 }
548
549 private PendingIntent.OnFinished mCallbackCompletedHandler = new PendingIntent.OnFinished() {
550 @Override
551 public void onSendFinished(PendingIntent pendingIntent, Intent intent, int resultCode,
552 String resultData, Bundle resultExtras) {
553 // We're only ever invoked when the callback is done, so release the lock.
Chris Thorntonae5fb992017-12-07 18:26:31 -0800554 synchronized (mCallbacksLock) {
Chris Thorntonba08b792017-06-08 22:34:37 -0700555 mWakelock.release();
556 }
557 }
558 };
559
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800560 public final class LocalSoundTriggerService extends SoundTriggerInternal {
561 private final Context mContext;
562 private SoundTriggerHelper mSoundTriggerHelper;
563
564 LocalSoundTriggerService(Context context) {
565 mContext = context;
566 }
567
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800568 synchronized void setSoundTriggerHelper(SoundTriggerHelper helper) {
569 mSoundTriggerHelper = helper;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800570 }
571
572 @Override
573 public int startRecognition(int keyphraseId, KeyphraseSoundModel soundModel,
574 IRecognitionStatusCallback listener, RecognitionConfig recognitionConfig) {
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800575 if (!isInitialized()) return STATUS_ERROR;
576 return mSoundTriggerHelper.startKeyphraseRecognition(keyphraseId, soundModel, listener,
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800577 recognitionConfig);
578 }
579
580 @Override
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800581 public synchronized int stopRecognition(int keyphraseId, IRecognitionStatusCallback listener) {
582 if (!isInitialized()) return STATUS_ERROR;
583 return mSoundTriggerHelper.stopKeyphraseRecognition(keyphraseId, listener);
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800584 }
585
586 @Override
Arunesh Mishra55a9b002016-02-01 14:06:37 -0800587 public ModuleProperties getModuleProperties() {
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800588 if (!isInitialized()) return null;
Arunesh Mishra55a9b002016-02-01 14:06:37 -0800589 return mSoundTriggerHelper.getModuleProperties();
590 }
591
592 @Override
Arunesh Mishra2d1de782016-02-21 18:10:28 -0800593 public int unloadKeyphraseModel(int keyphraseId) {
594 if (!isInitialized()) return STATUS_ERROR;
595 return mSoundTriggerHelper.unloadKeyphraseSoundModel(keyphraseId);
596 }
597
598 @Override
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800599 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800600 if (!isInitialized()) return;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800601 mSoundTriggerHelper.dump(fd, pw, args);
602 }
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800603
604 private synchronized boolean isInitialized() {
605 if (mSoundTriggerHelper == null ) {
606 Slog.e(TAG, "SoundTriggerHelper not initialized.");
607 return false;
608 }
609 return true;
610 }
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800611 }
612
613 private void enforceCallingPermission(String permission) {
614 if (mContext.checkCallingOrSelfPermission(permission)
615 != PackageManager.PERMISSION_GRANTED) {
616 throw new SecurityException("Caller does not hold the permission " + permission);
617 }
618 }
619}