Home | History | Annotate | Download | only in soundtrigger
      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