1 /* 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 17 package com.android.server.soundtrigger; 18 import static android.hardware.soundtrigger.SoundTrigger.STATUS_ERROR; 19 import static android.hardware.soundtrigger.SoundTrigger.STATUS_OK; 20 21 import android.app.PendingIntent; 22 import android.content.Context; 23 import android.content.Intent; 24 import android.content.pm.PackageManager; 25 import android.Manifest; 26 import android.hardware.soundtrigger.IRecognitionStatusCallback; 27 import android.hardware.soundtrigger.SoundTrigger; 28 import android.hardware.soundtrigger.SoundTrigger.SoundModel; 29 import android.hardware.soundtrigger.SoundTrigger.GenericSoundModel; 30 import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel; 31 import android.hardware.soundtrigger.SoundTrigger.ModuleProperties; 32 import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig; 33 import android.media.soundtrigger.SoundTriggerManager; 34 import android.os.Bundle; 35 import android.os.Parcel; 36 import android.os.ParcelUuid; 37 import android.os.PowerManager; 38 import android.os.RemoteException; 39 import android.util.Slog; 40 41 import com.android.server.SystemService; 42 import com.android.internal.app.ISoundTriggerService; 43 44 import java.io.FileDescriptor; 45 import java.io.PrintWriter; 46 import java.util.TreeMap; 47 import 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 */ 58 public class SoundTriggerService extends SystemService { 59 private static final String TAG = "SoundTriggerService"; 60 private static final boolean DEBUG = true; 61 62 final Context mContext; 63 private Object mLock; 64 private final SoundTriggerServiceStub mServiceStub; 65 private final LocalSoundTriggerService mLocalSoundTriggerService; 66 private SoundTriggerDbHelper mDbHelper; 67 private SoundTriggerHelper mSoundTriggerHelper; 68 private final TreeMap<UUID, SoundModel> mLoadedModels; 69 private final TreeMap<UUID, LocalSoundTriggerRecognitionStatusCallback> mIntentCallbacks; 70 private PowerManager.WakeLock mWakelock; 71 72 public SoundTriggerService(Context context) { 73 super(context); 74 mContext = context; 75 mServiceStub = new SoundTriggerServiceStub(); 76 mLocalSoundTriggerService = new LocalSoundTriggerService(context); 77 mLoadedModels = new TreeMap<UUID, SoundModel>(); 78 mIntentCallbacks = new TreeMap<UUID, LocalSoundTriggerRecognitionStatusCallback>(); 79 mLock = new Object(); 80 } 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) { 91 initSoundTriggerHelper(); 92 mLocalSoundTriggerService.setSoundTriggerHelper(mSoundTriggerHelper); 93 } 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 106 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 120 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 137 public int startRecognition(ParcelUuid parcelUuid, IRecognitionStatusCallback callback, 138 RecognitionConfig config) { 139 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER); 140 if (!isInitialized()) return STATUS_ERROR; 141 if (DEBUG) { 142 Slog.i(TAG, "startRecognition(): Uuid : " + parcelUuid); 143 } 144 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); 153 } 154 155 @Override 156 public int stopRecognition(ParcelUuid parcelUuid, IRecognitionStatusCallback callback) { 157 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER); 158 if (DEBUG) { 159 Slog.i(TAG, "stopRecognition(): Uuid : " + parcelUuid); 160 } 161 if (!isInitialized()) return STATUS_ERROR; 162 return mSoundTriggerHelper.stopGenericRecognition(parcelUuid.getUuid(), callback); 163 } 164 165 @Override 166 public SoundTrigger.GenericSoundModel getSoundModel(ParcelUuid soundModelId) { 167 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER); 168 if (DEBUG) { 169 Slog.i(TAG, "getSoundModel(): id = " + soundModelId); 170 } 171 SoundTrigger.GenericSoundModel model = mDbHelper.getGenericSoundModel( 172 soundModelId.getUuid()); 173 return model; 174 } 175 176 @Override 177 public void updateSoundModel(SoundTrigger.GenericSoundModel soundModel) { 178 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER); 179 if (DEBUG) { 180 Slog.i(TAG, "updateSoundModel(): model = " + soundModel); 181 } 182 mDbHelper.updateGenericSoundModel(soundModel); 183 } 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 } 191 // Unload the model if it is loaded. 192 mSoundTriggerHelper.unloadGenericSoundModel(soundModelId.getUuid()); 193 mDbHelper.deleteGenericSoundModel(soundModelId.getUuid()); 194 } 195 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 } 391 } 392 393 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 546 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 554 synchronized void setSoundTriggerHelper(SoundTriggerHelper helper) { 555 mSoundTriggerHelper = helper; 556 } 557 558 @Override 559 public int startRecognition(int keyphraseId, KeyphraseSoundModel soundModel, 560 IRecognitionStatusCallback listener, RecognitionConfig recognitionConfig) { 561 if (!isInitialized()) return STATUS_ERROR; 562 return mSoundTriggerHelper.startKeyphraseRecognition(keyphraseId, soundModel, listener, 563 recognitionConfig); 564 } 565 566 @Override 567 public synchronized int stopRecognition(int keyphraseId, IRecognitionStatusCallback listener) { 568 if (!isInitialized()) return STATUS_ERROR; 569 return mSoundTriggerHelper.stopKeyphraseRecognition(keyphraseId, listener); 570 } 571 572 @Override 573 public ModuleProperties getModuleProperties() { 574 if (!isInitialized()) return null; 575 return mSoundTriggerHelper.getModuleProperties(); 576 } 577 578 @Override 579 public int unloadKeyphraseModel(int keyphraseId) { 580 if (!isInitialized()) return STATUS_ERROR; 581 return mSoundTriggerHelper.unloadKeyphraseSoundModel(keyphraseId); 582 } 583 584 @Override 585 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 586 if (!isInitialized()) return; 587 mSoundTriggerHelper.dump(fd, pw, args); 588 } 589 590 private synchronized boolean isInitialized() { 591 if (mSoundTriggerHelper == null ) { 592 Slog.e(TAG, "SoundTriggerHelper not initialized."); 593 return false; 594 } 595 return true; 596 } 597 } 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 } 606