blob: 51c805da2cac0c78d42a052835821e208da09f7b [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;
69 private final TreeMap<UUID, LocalSoundTriggerRecognitionStatusCallback> mIntentCallbacks;
70 private PowerManager.WakeLock mWakelock;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -080071
72 public SoundTriggerService(Context context) {
73 super(context);
74 mContext = context;
75 mServiceStub = new SoundTriggerServiceStub();
76 mLocalSoundTriggerService = new LocalSoundTriggerService(context);
Chris Thorntonba08b792017-06-08 22:34:37 -070077 mLoadedModels = new TreeMap<UUID, SoundModel>();
78 mIntentCallbacks = new TreeMap<UUID, LocalSoundTriggerRecognitionStatusCallback>();
79 mLock = new Object();
Arunesh Mishraa772e5f2016-01-25 10:33:11 -080080 }
81
82 @Override
83 public void onStart() {
84 publishBinderService(Context.SOUND_TRIGGER_SERVICE, mServiceStub);
85 publishLocalService(SoundTriggerInternal.class, mLocalSoundTriggerService);
86 }
87
88 @Override
89 public void onBootPhase(int phase) {
90 if (PHASE_SYSTEM_SERVICES_READY == phase) {
Arunesh Mishra3fff7f52016-02-09 12:15:19 -080091 initSoundTriggerHelper();
92 mLocalSoundTriggerService.setSoundTriggerHelper(mSoundTriggerHelper);
Arunesh Mishraa772e5f2016-01-25 10:33:11 -080093 } else if (PHASE_THIRD_PARTY_APPS_CAN_START == phase) {
94 mDbHelper = new SoundTriggerDbHelper(mContext);
95 }
96 }
97
98 @Override
99 public void onStartUser(int userHandle) {
100 }
101
102 @Override
103 public void onSwitchUser(int userHandle) {
104 }
105
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800106 private synchronized void initSoundTriggerHelper() {
107 if (mSoundTriggerHelper == null) {
108 mSoundTriggerHelper = new SoundTriggerHelper(mContext);
109 }
110 }
111
112 private synchronized boolean isInitialized() {
113 if (mSoundTriggerHelper == null ) {
114 Slog.e(TAG, "SoundTriggerHelper not initialized.");
115 return false;
116 }
117 return true;
118 }
119
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800120 class SoundTriggerServiceStub extends ISoundTriggerService.Stub {
121 @Override
122 public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
123 throws RemoteException {
124 try {
125 return super.onTransact(code, data, reply, flags);
126 } catch (RuntimeException e) {
127 // The activity manager only throws security exceptions, so let's
128 // log all others.
129 if (!(e instanceof SecurityException)) {
130 Slog.wtf(TAG, "SoundTriggerService Crash", e);
131 }
132 throw e;
133 }
134 }
135
136 @Override
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800137 public int startRecognition(ParcelUuid parcelUuid, IRecognitionStatusCallback callback,
138 RecognitionConfig config) {
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800139 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
Arunesh Mishra2d1de782016-02-21 18:10:28 -0800140 if (!isInitialized()) return STATUS_ERROR;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800141 if (DEBUG) {
142 Slog.i(TAG, "startRecognition(): Uuid : " + parcelUuid);
143 }
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800144
145 GenericSoundModel model = getSoundModel(parcelUuid);
146 if (model == null) {
147 Slog.e(TAG, "Null model in database for id: " + parcelUuid);
148 return STATUS_ERROR;
149 }
150
151 return mSoundTriggerHelper.startGenericRecognition(parcelUuid.getUuid(), model,
152 callback, config);
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800153 }
154
155 @Override
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800156 public int stopRecognition(ParcelUuid parcelUuid, IRecognitionStatusCallback callback) {
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800157 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
158 if (DEBUG) {
159 Slog.i(TAG, "stopRecognition(): Uuid : " + parcelUuid);
160 }
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800161 if (!isInitialized()) return STATUS_ERROR;
162 return mSoundTriggerHelper.stopGenericRecognition(parcelUuid.getUuid(), callback);
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800163 }
164
165 @Override
Arunesh Mishrac722ec412016-01-27 13:29:12 -0800166 public SoundTrigger.GenericSoundModel getSoundModel(ParcelUuid soundModelId) {
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800167 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
168 if (DEBUG) {
169 Slog.i(TAG, "getSoundModel(): id = " + soundModelId);
170 }
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800171 SoundTrigger.GenericSoundModel model = mDbHelper.getGenericSoundModel(
172 soundModelId.getUuid());
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800173 return model;
174 }
175
176 @Override
Arunesh Mishrac722ec412016-01-27 13:29:12 -0800177 public void updateSoundModel(SoundTrigger.GenericSoundModel soundModel) {
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800178 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
179 if (DEBUG) {
180 Slog.i(TAG, "updateSoundModel(): model = " + soundModel);
181 }
Arunesh Mishrac722ec412016-01-27 13:29:12 -0800182 mDbHelper.updateGenericSoundModel(soundModel);
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800183 }
184
185 @Override
186 public void deleteSoundModel(ParcelUuid soundModelId) {
187 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
188 if (DEBUG) {
189 Slog.i(TAG, "deleteSoundModel(): id = " + soundModelId);
190 }
Arunesh Mishra2d1de782016-02-21 18:10:28 -0800191 // Unload the model if it is loaded.
192 mSoundTriggerHelper.unloadGenericSoundModel(soundModelId.getUuid());
Arunesh Mishrac722ec412016-01-27 13:29:12 -0800193 mDbHelper.deleteGenericSoundModel(soundModelId.getUuid());
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800194 }
Chris Thorntonba08b792017-06-08 22:34:37 -0700195
196 @Override
197 public int loadGenericSoundModel(GenericSoundModel soundModel) {
198 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
199 if (!isInitialized()) return STATUS_ERROR;
200 if (soundModel == null || soundModel.uuid == null) {
201 Slog.e(TAG, "Invalid sound model");
202 return STATUS_ERROR;
203 }
204 if (DEBUG) {
205 Slog.i(TAG, "loadGenericSoundModel(): id = " + soundModel.uuid);
206 }
207 synchronized (mLock) {
208 SoundModel oldModel = mLoadedModels.get(soundModel.uuid);
209 // If the model we're loading is actually different than what we had loaded, we
210 // should unload that other model now. We don't care about return codes since we
211 // don't know if the other model is loaded.
212 if (oldModel != null && !oldModel.equals(soundModel)) {
213 mSoundTriggerHelper.unloadGenericSoundModel(soundModel.uuid);
214 mIntentCallbacks.remove(soundModel.uuid);
215 }
216 mLoadedModels.put(soundModel.uuid, soundModel);
217 }
218 return STATUS_OK;
219 }
220
221 @Override
222 public int loadKeyphraseSoundModel(KeyphraseSoundModel soundModel) {
223 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
224 if (!isInitialized()) return STATUS_ERROR;
225 if (soundModel == null || soundModel.uuid == null) {
226 Slog.e(TAG, "Invalid sound model");
227 return STATUS_ERROR;
228 }
229 if (soundModel.keyphrases == null || soundModel.keyphrases.length != 1) {
230 Slog.e(TAG, "Only one keyphrase per model is currently supported.");
231 return STATUS_ERROR;
232 }
233 if (DEBUG) {
234 Slog.i(TAG, "loadKeyphraseSoundModel(): id = " + soundModel.uuid);
235 }
236 synchronized (mLock) {
237 SoundModel oldModel = mLoadedModels.get(soundModel.uuid);
238 // If the model we're loading is actually different than what we had loaded, we
239 // should unload that other model now. We don't care about return codes since we
240 // don't know if the other model is loaded.
241 if (oldModel != null && !oldModel.equals(soundModel)) {
242 mSoundTriggerHelper.unloadKeyphraseSoundModel(soundModel.keyphrases[0].id);
243 mIntentCallbacks.remove(soundModel.uuid);
244 }
245 mLoadedModels.put(soundModel.uuid, soundModel);
246 }
247 return STATUS_OK;
248 }
249
250 @Override
251 public int startRecognitionForIntent(ParcelUuid soundModelId, PendingIntent callbackIntent,
252 SoundTrigger.RecognitionConfig config) {
253 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
254 if (!isInitialized()) return STATUS_ERROR;
255 if (DEBUG) {
256 Slog.i(TAG, "startRecognition(): id = " + soundModelId);
257 }
258
259 synchronized (mLock) {
260 SoundModel soundModel = mLoadedModels.get(soundModelId.getUuid());
261 if (soundModel == null) {
262 Slog.e(TAG, soundModelId + " is not loaded");
263 return STATUS_ERROR;
264 }
265 LocalSoundTriggerRecognitionStatusCallback callback = mIntentCallbacks.get(
266 soundModelId.getUuid());
267 if (callback != null) {
268 Slog.e(TAG, soundModelId + " is already running");
269 return STATUS_ERROR;
270 }
271 callback = new LocalSoundTriggerRecognitionStatusCallback(soundModelId.getUuid(),
272 callbackIntent, config);
273 int ret;
274 switch (soundModel.type) {
275 case SoundModel.TYPE_KEYPHRASE: {
276 KeyphraseSoundModel keyphraseSoundModel = (KeyphraseSoundModel) soundModel;
277 ret = mSoundTriggerHelper.startKeyphraseRecognition(
278 keyphraseSoundModel.keyphrases[0].id, keyphraseSoundModel, callback,
279 config);
280 } break;
281 case SoundModel.TYPE_GENERIC_SOUND:
282 ret = mSoundTriggerHelper.startGenericRecognition(soundModel.uuid,
283 (GenericSoundModel) soundModel, callback, config);
284 break;
285 default:
286 Slog.e(TAG, "Unknown model type");
287 return STATUS_ERROR;
288 }
289
290 if (ret != STATUS_OK) {
291 Slog.e(TAG, "Failed to start model: " + ret);
292 return ret;
293 }
294 mIntentCallbacks.put(soundModelId.getUuid(), callback);
295 }
296 return STATUS_OK;
297 }
298
299 @Override
300 public int stopRecognitionForIntent(ParcelUuid soundModelId) {
301 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
302 if (!isInitialized()) return STATUS_ERROR;
303 if (DEBUG) {
304 Slog.i(TAG, "stopRecognition(): id = " + soundModelId);
305 }
306
307 synchronized (mLock) {
308 SoundModel soundModel = mLoadedModels.get(soundModelId.getUuid());
309 if (soundModel == null) {
310 Slog.e(TAG, soundModelId + " is not loaded");
311 return STATUS_ERROR;
312 }
313 LocalSoundTriggerRecognitionStatusCallback callback = mIntentCallbacks.get(
314 soundModelId.getUuid());
315 if (callback == null) {
316 Slog.e(TAG, soundModelId + " is not running");
317 return STATUS_ERROR;
318 }
319 int ret;
320 switch (soundModel.type) {
321 case SoundModel.TYPE_KEYPHRASE:
322 ret = mSoundTriggerHelper.stopKeyphraseRecognition(
323 ((KeyphraseSoundModel)soundModel).keyphrases[0].id, callback);
324 break;
325 case SoundModel.TYPE_GENERIC_SOUND:
326 ret = mSoundTriggerHelper.stopGenericRecognition(soundModel.uuid, callback);
327 break;
328 default:
329 Slog.e(TAG, "Unknown model type");
330 return STATUS_ERROR;
331 }
332
333 if (ret != STATUS_OK) {
334 Slog.e(TAG, "Failed to stop model: " + ret);
335 return ret;
336 }
337 mIntentCallbacks.remove(soundModelId.getUuid());
338 }
339 return STATUS_OK;
340 }
341
342 @Override
343 public int unloadSoundModel(ParcelUuid soundModelId) {
344 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
345 if (!isInitialized()) return STATUS_ERROR;
346 if (DEBUG) {
347 Slog.i(TAG, "unloadSoundModel(): 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 }
356 int ret;
357 switch (soundModel.type) {
358 case SoundModel.TYPE_KEYPHRASE:
359 ret = mSoundTriggerHelper.unloadKeyphraseSoundModel(
360 ((KeyphraseSoundModel)soundModel).keyphrases[0].id);
361 break;
362 case SoundModel.TYPE_GENERIC_SOUND:
363 ret = mSoundTriggerHelper.unloadGenericSoundModel(soundModel.uuid);
364 break;
365 default:
366 Slog.e(TAG, "Unknown model type");
367 return STATUS_ERROR;
368 }
369 if (ret != STATUS_OK) {
370 Slog.e(TAG, "Failed to unload model");
371 return ret;
372 }
373 mLoadedModels.remove(soundModelId.getUuid());
374 return STATUS_OK;
375 }
376 }
377
378 @Override
379 public boolean isRecognitionActive(ParcelUuid parcelUuid) {
380 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
381 if (!isInitialized()) return false;
382 synchronized (mLock) {
383 LocalSoundTriggerRecognitionStatusCallback callback =
384 mIntentCallbacks.get(parcelUuid.getUuid());
385 if (callback == null) {
386 return false;
387 }
388 return mSoundTriggerHelper.isRecognitionRequested(parcelUuid.getUuid());
389 }
390 }
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800391 }
392
Chris Thorntonba08b792017-06-08 22:34:37 -0700393 private final class LocalSoundTriggerRecognitionStatusCallback
394 extends IRecognitionStatusCallback.Stub {
395 private UUID mUuid;
396 private PendingIntent mCallbackIntent;
397 private RecognitionConfig mRecognitionConfig;
398
399 public LocalSoundTriggerRecognitionStatusCallback(UUID modelUuid,
400 PendingIntent callbackIntent,
401 RecognitionConfig config) {
402 mUuid = modelUuid;
403 mCallbackIntent = callbackIntent;
404 mRecognitionConfig = config;
405 }
406
407 @Override
408 public boolean pingBinder() {
409 return mCallbackIntent != null;
410 }
411
412 @Override
413 public void onKeyphraseDetected(SoundTrigger.KeyphraseRecognitionEvent event) {
414 if (mCallbackIntent == null) {
415 return;
416 }
417 grabWakeLock();
418
419 Slog.w(TAG, "Keyphrase sound trigger event: " + event);
420 Intent extras = new Intent();
421 extras.putExtra(SoundTriggerManager.EXTRA_MESSAGE_TYPE,
422 SoundTriggerManager.FLAG_MESSAGE_TYPE_RECOGNITION_EVENT);
423 extras.putExtra(SoundTriggerManager.EXTRA_RECOGNITION_EVENT, event);
424 try {
425 mCallbackIntent.send(mContext, 0, extras, mCallbackCompletedHandler, null);
426 if (!mRecognitionConfig.allowMultipleTriggers) {
427 removeCallback(/*releaseWakeLock=*/false);
428 }
429 } catch (PendingIntent.CanceledException e) {
430 removeCallback(/*releaseWakeLock=*/true);
431 }
432 }
433
434 @Override
435 public void onGenericSoundTriggerDetected(SoundTrigger.GenericRecognitionEvent event) {
436 if (mCallbackIntent == null) {
437 return;
438 }
439 grabWakeLock();
440
441 Slog.w(TAG, "Generic sound trigger event: " + event);
442 Intent extras = new Intent();
443 extras.putExtra(SoundTriggerManager.EXTRA_MESSAGE_TYPE,
444 SoundTriggerManager.FLAG_MESSAGE_TYPE_RECOGNITION_EVENT);
445 extras.putExtra(SoundTriggerManager.EXTRA_RECOGNITION_EVENT, event);
446 try {
447 mCallbackIntent.send(mContext, 0, extras, mCallbackCompletedHandler, null);
448 if (!mRecognitionConfig.allowMultipleTriggers) {
449 removeCallback(/*releaseWakeLock=*/false);
450 }
451 } catch (PendingIntent.CanceledException e) {
452 removeCallback(/*releaseWakeLock=*/true);
453 }
454 }
455
456 @Override
457 public void onError(int status) {
458 if (mCallbackIntent == null) {
459 return;
460 }
461 grabWakeLock();
462
463 Slog.i(TAG, "onError: " + status);
464 Intent extras = new Intent();
465 extras.putExtra(SoundTriggerManager.EXTRA_MESSAGE_TYPE,
466 SoundTriggerManager.FLAG_MESSAGE_TYPE_RECOGNITION_ERROR);
467 extras.putExtra(SoundTriggerManager.EXTRA_STATUS, status);
468 try {
469 mCallbackIntent.send(mContext, 0, extras, mCallbackCompletedHandler, null);
470 // Remove the callback, but wait for the intent to finish before we let go of the
471 // wake lock
472 removeCallback(/*releaseWakeLock=*/false);
473 } catch (PendingIntent.CanceledException e) {
474 removeCallback(/*releaseWakeLock=*/true);
475 }
476 }
477
478 @Override
479 public void onRecognitionPaused() {
480 if (mCallbackIntent == null) {
481 return;
482 }
483 grabWakeLock();
484
485 Slog.i(TAG, "onRecognitionPaused");
486 Intent extras = new Intent();
487 extras.putExtra(SoundTriggerManager.EXTRA_MESSAGE_TYPE,
488 SoundTriggerManager.FLAG_MESSAGE_TYPE_RECOGNITION_PAUSED);
489 try {
490 mCallbackIntent.send(mContext, 0, extras, mCallbackCompletedHandler, null);
491 } catch (PendingIntent.CanceledException e) {
492 removeCallback(/*releaseWakeLock=*/true);
493 }
494 }
495
496 @Override
497 public void onRecognitionResumed() {
498 if (mCallbackIntent == null) {
499 return;
500 }
501 grabWakeLock();
502
503 Slog.i(TAG, "onRecognitionResumed");
504 Intent extras = new Intent();
505 extras.putExtra(SoundTriggerManager.EXTRA_MESSAGE_TYPE,
506 SoundTriggerManager.FLAG_MESSAGE_TYPE_RECOGNITION_RESUMED);
507 try {
508 mCallbackIntent.send(mContext, 0, extras, mCallbackCompletedHandler, null);
509 } catch (PendingIntent.CanceledException e) {
510 removeCallback(/*releaseWakeLock=*/true);
511 }
512 }
513
514 private void removeCallback(boolean releaseWakeLock) {
515 mCallbackIntent = null;
516 synchronized (mLock) {
517 mIntentCallbacks.remove(mUuid);
518 if (releaseWakeLock) {
519 mWakelock.release();
520 }
521 }
522 }
523 }
524
525 private void grabWakeLock() {
526 synchronized (mLock) {
527 if (mWakelock == null) {
528 PowerManager pm = ((PowerManager) mContext.getSystemService(Context.POWER_SERVICE));
529 mWakelock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
530 }
531 mWakelock.acquire();
532 }
533 }
534
535 private PendingIntent.OnFinished mCallbackCompletedHandler = new PendingIntent.OnFinished() {
536 @Override
537 public void onSendFinished(PendingIntent pendingIntent, Intent intent, int resultCode,
538 String resultData, Bundle resultExtras) {
539 // We're only ever invoked when the callback is done, so release the lock.
540 synchronized (mLock) {
541 mWakelock.release();
542 }
543 }
544 };
545
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800546 public final class LocalSoundTriggerService extends SoundTriggerInternal {
547 private final Context mContext;
548 private SoundTriggerHelper mSoundTriggerHelper;
549
550 LocalSoundTriggerService(Context context) {
551 mContext = context;
552 }
553
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800554 synchronized void setSoundTriggerHelper(SoundTriggerHelper helper) {
555 mSoundTriggerHelper = helper;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800556 }
557
558 @Override
559 public int startRecognition(int keyphraseId, KeyphraseSoundModel soundModel,
560 IRecognitionStatusCallback listener, RecognitionConfig recognitionConfig) {
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800561 if (!isInitialized()) return STATUS_ERROR;
562 return mSoundTriggerHelper.startKeyphraseRecognition(keyphraseId, soundModel, listener,
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800563 recognitionConfig);
564 }
565
566 @Override
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800567 public synchronized int stopRecognition(int keyphraseId, IRecognitionStatusCallback listener) {
568 if (!isInitialized()) return STATUS_ERROR;
569 return mSoundTriggerHelper.stopKeyphraseRecognition(keyphraseId, listener);
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800570 }
571
572 @Override
Arunesh Mishra55a9b002016-02-01 14:06:37 -0800573 public ModuleProperties getModuleProperties() {
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800574 if (!isInitialized()) return null;
Arunesh Mishra55a9b002016-02-01 14:06:37 -0800575 return mSoundTriggerHelper.getModuleProperties();
576 }
577
578 @Override
Arunesh Mishra2d1de782016-02-21 18:10:28 -0800579 public int unloadKeyphraseModel(int keyphraseId) {
580 if (!isInitialized()) return STATUS_ERROR;
581 return mSoundTriggerHelper.unloadKeyphraseSoundModel(keyphraseId);
582 }
583
584 @Override
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800585 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800586 if (!isInitialized()) return;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800587 mSoundTriggerHelper.dump(fd, pw, args);
588 }
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800589
590 private synchronized boolean isInitialized() {
591 if (mSoundTriggerHelper == null ) {
592 Slog.e(TAG, "SoundTriggerHelper not initialized.");
593 return false;
594 }
595 return true;
596 }
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800597 }
598
599 private void enforceCallingPermission(String permission) {
600 if (mContext.checkCallingOrSelfPermission(permission)
601 != PackageManager.PERMISSION_GRANTED) {
602 throw new SecurityException("Caller does not hold the permission " + permission);
603 }
604 }
605}