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