SoundTriggerHelper.java revision 2e14dd46e16432fe264025087b57ce6ec71622a3
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 17package com.android.server.voiceinteraction; 18 19import android.hardware.soundtrigger.IRecognitionStatusCallback; 20import android.hardware.soundtrigger.SoundTrigger; 21import android.hardware.soundtrigger.SoundTrigger.Keyphrase; 22import android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionEvent; 23import android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra; 24import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel; 25import android.hardware.soundtrigger.SoundTrigger.ModuleProperties; 26import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig; 27import android.hardware.soundtrigger.SoundTrigger.RecognitionEvent; 28import android.hardware.soundtrigger.SoundTriggerModule; 29import android.os.RemoteException; 30import android.util.Slog; 31import android.util.SparseArray; 32 33import java.util.ArrayList; 34 35/** 36 * Helper for {@link SoundTrigger} APIs. 37 * Currently this just acts as an abstraction over all SoundTrigger API calls. 38 * 39 * @hide 40 */ 41public class SoundTriggerHelper implements SoundTrigger.StatusListener { 42 static final String TAG = "SoundTriggerHelper"; 43 // TODO: Set to false. 44 static final boolean DBG = true; 45 46 /** 47 * Return codes for {@link #startRecognition(int, KeyphraseSoundModel, 48 * IRecognitionStatusCallback, RecognitionConfig)}, 49 * {@link #stopRecognition(int, IRecognitionStatusCallback)} 50 */ 51 public static final int STATUS_ERROR = SoundTrigger.STATUS_ERROR; 52 public static final int STATUS_OK = SoundTrigger.STATUS_OK; 53 54 private static final int INVALID_SOUND_MODEL_HANDLE = -1; 55 56 /** The {@link DspInfo} for the system, or null if none exists. */ 57 final ModuleProperties moduleProperties; 58 59 /** The properties for the DSP module */ 60 private final SoundTriggerModule mModule; 61 62 // Use a RemoteCallbackList here? 63 private final SparseArray<IRecognitionStatusCallback> mActiveListeners; 64 65 private int mCurrentSoundModelHandle = INVALID_SOUND_MODEL_HANDLE; 66 67 SoundTriggerHelper() { 68 ArrayList <ModuleProperties> modules = new ArrayList<>(); 69 int status = SoundTrigger.listModules(modules); 70 mActiveListeners = new SparseArray<>(1); 71 if (status != SoundTrigger.STATUS_OK || modules.size() == 0) { 72 Slog.w(TAG, "listModules status=" + status + ", # of modules=" + modules.size()); 73 moduleProperties = null; 74 mModule = null; 75 } else { 76 // TODO: Figure out how to determine which module corresponds to the DSP hardware. 77 moduleProperties = modules.get(0); 78 mModule = SoundTrigger.attachModule(moduleProperties.id, this, null); 79 } 80 } 81 82 /** 83 * @return True, if a recognition for the given {@link Keyphrase} is active. 84 */ 85 synchronized boolean isKeyphraseActive(Keyphrase keyphrase) { 86 if (keyphrase == null) { 87 Slog.w(TAG, "isKeyphraseActive requires a non-null keyphrase"); 88 return false; 89 } 90 return mActiveListeners.get(keyphrase.id) != null; 91 } 92 93 /** 94 * Starts recognition for the given keyphraseId. 95 * 96 * @param keyphraseId The identifier of the keyphrase for which 97 * the recognition is to be started. 98 * @param soundModel The sound model to use for recognition. 99 * @param listener The listener for the recognition events related to the given keyphrase. 100 * @return One of {@link #STATUS_ERROR} or {@link #STATUS_OK}. 101 */ 102 synchronized int startRecognition(int keyphraseId, 103 KeyphraseSoundModel soundModel, 104 IRecognitionStatusCallback listener, 105 RecognitionConfig recognitionConfig) { 106 if (DBG) { 107 Slog.d(TAG, "startRecognition for keyphraseId=" + keyphraseId 108 + " soundModel=" + soundModel + ", listener=" + listener 109 + ", recognitionConfig=" + recognitionConfig); 110 Slog.d(TAG, "moduleProperties=" + moduleProperties); 111 Slog.d(TAG, "# of current listeners=" + mActiveListeners.size()); 112 Slog.d(TAG, "mCurrentSoundModelHandle=" + mCurrentSoundModelHandle); 113 } 114 if (moduleProperties == null || mModule == null) { 115 Slog.w(TAG, "Attempting startRecognition without the capability"); 116 return STATUS_ERROR; 117 } 118 119 if (mCurrentSoundModelHandle != INVALID_SOUND_MODEL_HANDLE) { 120 Slog.w(TAG, "Unloading previous sound model"); 121 // TODO: Inspect the return codes here. 122 mModule.unloadSoundModel(mCurrentSoundModelHandle); 123 mCurrentSoundModelHandle = INVALID_SOUND_MODEL_HANDLE; 124 } 125 126 // If the previous recognition was by a different listener, 127 // Notify them that it was stopped. 128 IRecognitionStatusCallback oldListener = mActiveListeners.get(keyphraseId); 129 if (oldListener != null && oldListener.asBinder() != listener.asBinder()) { 130 Slog.w(TAG, "Canceling previous recognition"); 131 try { 132 oldListener.onDetectionStopped(); 133 } catch (RemoteException e) { 134 Slog.w(TAG, "RemoteException in onDetectionStopped"); 135 } 136 mActiveListeners.remove(keyphraseId); 137 } 138 139 int[] handle = new int[] { INVALID_SOUND_MODEL_HANDLE }; 140 int status = mModule.loadSoundModel(soundModel, handle); 141 if (status != SoundTrigger.STATUS_OK) { 142 Slog.w(TAG, "loadSoundModel call failed with " + status); 143 return STATUS_ERROR; 144 } 145 if (handle[0] == INVALID_SOUND_MODEL_HANDLE) { 146 Slog.w(TAG, "loadSoundModel call returned invalid sound model handle"); 147 return STATUS_ERROR; 148 } 149 150 // Start the recognition. 151 status = mModule.startRecognition(handle[0], recognitionConfig); 152 if (status != SoundTrigger.STATUS_OK) { 153 Slog.w(TAG, "startRecognition failed with " + status); 154 return STATUS_ERROR; 155 } 156 157 // Everything went well! 158 mCurrentSoundModelHandle = handle[0]; 159 // Register the new listener. This replaces the old one. 160 // There can only be a maximum of one active listener for a keyphrase 161 // at any given time. 162 mActiveListeners.put(keyphraseId, listener); 163 return STATUS_OK; 164 } 165 166 /** 167 * Stops recognition for the given {@link Keyphrase} if a recognition is 168 * currently active. 169 * 170 * @param keyphraseId The identifier of the keyphrase for which 171 * the recognition is to be stopped. 172 * @param listener The listener for the recognition events related to the given keyphrase. 173 * 174 * @return One of {@link #STATUS_ERROR} or {@link #STATUS_OK}. 175 */ 176 synchronized int stopRecognition(int keyphraseId, IRecognitionStatusCallback listener) { 177 if (DBG) { 178 Slog.d(TAG, "stopRecognition for keyphraseId=" + keyphraseId 179 + ", listener=" + listener); 180 Slog.d(TAG, "# of current listeners = " + mActiveListeners.size()); 181 } 182 183 if (moduleProperties == null || mModule == null) { 184 Slog.w(TAG, "Attempting stopRecognition without the capability"); 185 return STATUS_ERROR; 186 } 187 188 IRecognitionStatusCallback currentListener = mActiveListeners.get(keyphraseId); 189 if (listener == null) { 190 Slog.w(TAG, "Attempting stopRecognition without a valid listener"); 191 return STATUS_ERROR; 192 } if (currentListener == null) { 193 // startRecognition hasn't been called or it failed. 194 Slog.w(TAG, "Attempting stopRecognition without a successful startRecognition"); 195 return STATUS_ERROR; 196 } else if (currentListener.asBinder() != listener.asBinder()) { 197 // TODO: Figure out if this should match the listener that was passed in during 198 // startRecognition, or should we allow a different listener to stop the recognition, 199 // in which case we don't need to pass in a listener here. 200 Slog.w(TAG, "Attempting stopRecognition for another recognition"); 201 return STATUS_ERROR; 202 } else { 203 // Stop recognition if it's the current one, ignore otherwise. 204 // TODO: Inspect the return codes here. 205 int status = mModule.stopRecognition(mCurrentSoundModelHandle); 206 if (status != SoundTrigger.STATUS_OK) { 207 Slog.w(TAG, "stopRecognition call failed with " + status); 208 return STATUS_ERROR; 209 } 210 status = mModule.unloadSoundModel(mCurrentSoundModelHandle); 211 if (status != SoundTrigger.STATUS_OK) { 212 Slog.w(TAG, "unloadSoundModel call failed with " + status); 213 return STATUS_ERROR; 214 } 215 216 mCurrentSoundModelHandle = INVALID_SOUND_MODEL_HANDLE; 217 mActiveListeners.remove(keyphraseId); 218 return STATUS_OK; 219 } 220 } 221 222 //---- SoundTrigger.StatusListener methods 223 @Override 224 public void onRecognition(RecognitionEvent event) { 225 if (event == null) { 226 Slog.w(TAG, "Invalid recognition event!"); 227 return; 228 } 229 230 if (DBG) Slog.d(TAG, "onRecognition: " + event); 231 switch (event.status) { 232 // Fire aborts/failures to all listeners since it's not tied to a keyphrase. 233 case SoundTrigger.RECOGNITION_STATUS_ABORT: // fall-through 234 case SoundTrigger.RECOGNITION_STATUS_FAILURE: 235 try { 236 synchronized (this) { 237 for (int i = 0; i < mActiveListeners.size(); i++) { 238 mActiveListeners.valueAt(i).onDetectionStopped(); 239 } 240 } 241 } catch (RemoteException e) { 242 Slog.w(TAG, "RemoteException in onDetectionStopped"); 243 } 244 break; 245 case SoundTrigger.RECOGNITION_STATUS_SUCCESS: 246 if (!(event instanceof KeyphraseRecognitionEvent)) { 247 Slog.w(TAG, "Invalid recognition event!"); 248 return; 249 } 250 251 KeyphraseRecognitionExtra[] keyphraseExtras = 252 ((KeyphraseRecognitionEvent) event).keyphraseExtras; 253 if (keyphraseExtras == null || keyphraseExtras.length == 0) { 254 Slog.w(TAG, "Invalid keyphrase recognition event!"); 255 return; 256 } 257 // TODO: Handle more than one keyphrase extras. 258 int keyphraseId = keyphraseExtras[0].id; 259 try { 260 synchronized(this) { 261 // Check which keyphrase triggered, and fire the appropriate event. 262 IRecognitionStatusCallback listener = mActiveListeners.get(keyphraseId); 263 if (listener != null) { 264 listener.onDetected((KeyphraseRecognitionEvent) event); 265 } else { 266 Slog.w(TAG, "received onRecognition event without any listener for it"); 267 return; 268 } 269 } 270 } catch (RemoteException e) { 271 Slog.w(TAG, "RemoteException in onDetectionStopped"); 272 } 273 break; 274 } 275 } 276 277 @Override 278 public void onServiceDied() { 279 synchronized (this) { 280 try { 281 for (int i = 0; i < mActiveListeners.size(); i++) { 282 mActiveListeners.valueAt(i).onDetectionStopped(); 283 } 284 } catch (RemoteException e) { 285 Slog.w(TAG, "RemoteException in onDetectionStopped"); 286 } 287 mCurrentSoundModelHandle = INVALID_SOUND_MODEL_HANDLE; 288 // Remove all listeners. 289 mActiveListeners.clear(); 290 } 291 } 292} 293