16f6ceb7e1456698b1f33e04536bfb3227f9fcfcbAdam Lesinski/** 26f6ceb7e1456698b1f33e04536bfb3227f9fcfcbAdam Lesinski * Copyright (C) 2014 The Android Open Source Project 36f6ceb7e1456698b1f33e04536bfb3227f9fcfcbAdam Lesinski * 46f6ceb7e1456698b1f33e04536bfb3227f9fcfcbAdam Lesinski * Licensed under the Apache License, Version 2.0 (the "License"); 56f6ceb7e1456698b1f33e04536bfb3227f9fcfcbAdam Lesinski * you may not use this file except in compliance with the License. 66f6ceb7e1456698b1f33e04536bfb3227f9fcfcbAdam Lesinski * You may obtain a copy of the License at 76f6ceb7e1456698b1f33e04536bfb3227f9fcfcbAdam Lesinski * 86f6ceb7e1456698b1f33e04536bfb3227f9fcfcbAdam Lesinski * http://www.apache.org/licenses/LICENSE-2.0 96f6ceb7e1456698b1f33e04536bfb3227f9fcfcbAdam Lesinski * 106f6ceb7e1456698b1f33e04536bfb3227f9fcfcbAdam Lesinski * Unless required by applicable law or agreed to in writing, software 116f6ceb7e1456698b1f33e04536bfb3227f9fcfcbAdam Lesinski * distributed under the License is distributed on an "AS IS" BASIS, 126f6ceb7e1456698b1f33e04536bfb3227f9fcfcbAdam Lesinski * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 136f6ceb7e1456698b1f33e04536bfb3227f9fcfcbAdam Lesinski * See the License for the specific language governing permissions and 146f6ceb7e1456698b1f33e04536bfb3227f9fcfcbAdam Lesinski * limitations under the License. 156f6ceb7e1456698b1f33e04536bfb3227f9fcfcbAdam Lesinski */ 166f6ceb7e1456698b1f33e04536bfb3227f9fcfcbAdam Lesinski 176f6ceb7e1456698b1f33e04536bfb3227f9fcfcbAdam Lesinskipackage android.media.soundtrigger; 186f6ceb7e1456698b1f33e04536bfb3227f9fcfcbAdam Lesinskiimport static android.hardware.soundtrigger.SoundTrigger.STATUS_OK; 196f6ceb7e1456698b1f33e04536bfb3227f9fcfcbAdam Lesinski 201ab598f46c3ff520a67f9d80194847741f3467abAdam Lesinskiimport android.annotation.IntDef; 216f6ceb7e1456698b1f33e04536bfb3227f9fcfcbAdam Lesinskiimport android.annotation.NonNull; 226f6ceb7e1456698b1f33e04536bfb3227f9fcfcbAdam Lesinskiimport android.annotation.Nullable; 236f6ceb7e1456698b1f33e04536bfb3227f9fcfcbAdam Lesinskiimport android.annotation.RequiresPermission; 246f6ceb7e1456698b1f33e04536bfb3227f9fcfcbAdam Lesinskiimport android.annotation.SystemApi; 256f6ceb7e1456698b1f33e04536bfb3227f9fcfcbAdam Lesinskiimport android.hardware.soundtrigger.IRecognitionStatusCallback; 266f6ceb7e1456698b1f33e04536bfb3227f9fcfcbAdam Lesinskiimport android.hardware.soundtrigger.SoundTrigger; 276f6ceb7e1456698b1f33e04536bfb3227f9fcfcbAdam Lesinskiimport android.hardware.soundtrigger.SoundTrigger.RecognitionConfig; 286f6ceb7e1456698b1f33e04536bfb3227f9fcfcbAdam Lesinskiimport android.media.AudioFormat; 296f6ceb7e1456698b1f33e04536bfb3227f9fcfcbAdam Lesinskiimport android.os.Handler; 306f6ceb7e1456698b1f33e04536bfb3227f9fcfcbAdam Lesinskiimport android.os.Looper; 316f6ceb7e1456698b1f33e04536bfb3227f9fcfcbAdam Lesinskiimport android.os.Message; 32cacb28f2d60858106e2819cc7d95a65e8bda890bAdam Lesinskiimport android.os.ParcelUuid; 33cacb28f2d60858106e2819cc7d95a65e8bda890bAdam Lesinskiimport android.os.RemoteException; 34cacb28f2d60858106e2819cc7d95a65e8bda890bAdam Lesinskiimport android.util.Slog; 35cacb28f2d60858106e2819cc7d95a65e8bda890bAdam Lesinski 36cacb28f2d60858106e2819cc7d95a65e8bda890bAdam Lesinskiimport com.android.internal.app.ISoundTriggerService; 37cacb28f2d60858106e2819cc7d95a65e8bda890bAdam Lesinski 38cacb28f2d60858106e2819cc7d95a65e8bda890bAdam Lesinskiimport java.io.PrintWriter; 39cacb28f2d60858106e2819cc7d95a65e8bda890bAdam Lesinskiimport java.lang.annotation.Retention; 40cacb28f2d60858106e2819cc7d95a65e8bda890bAdam Lesinskiimport java.lang.annotation.RetentionPolicy; 41cacb28f2d60858106e2819cc7d95a65e8bda890bAdam Lesinskiimport java.util.UUID; 42cacb28f2d60858106e2819cc7d95a65e8bda890bAdam Lesinski 43cacb28f2d60858106e2819cc7d95a65e8bda890bAdam Lesinski/** 44cacb28f2d60858106e2819cc7d95a65e8bda890bAdam Lesinski * A class that allows interaction with the actual sound trigger detection on the system. 45cacb28f2d60858106e2819cc7d95a65e8bda890bAdam Lesinski * Sound trigger detection refers to a detectors that match generic sound patterns that are 46cacb28f2d60858106e2819cc7d95a65e8bda890bAdam Lesinski * not voice-based. The voice-based recognition models should utilize the {@link 47cacb28f2d60858106e2819cc7d95a65e8bda890bAdam Lesinski * VoiceInteractionService} instead. Access to this class is protected by a permission 48cacb28f2d60858106e2819cc7d95a65e8bda890bAdam Lesinski * granted only to system or privileged apps. 49cacb28f2d60858106e2819cc7d95a65e8bda890bAdam Lesinski * 50cacb28f2d60858106e2819cc7d95a65e8bda890bAdam Lesinski * @hide 51cacb28f2d60858106e2819cc7d95a65e8bda890bAdam Lesinski */ 52cacb28f2d60858106e2819cc7d95a65e8bda890bAdam Lesinski@SystemApi 53cacb28f2d60858106e2819cc7d95a65e8bda890bAdam Lesinskipublic final class SoundTriggerDetector { 54cacb28f2d60858106e2819cc7d95a65e8bda890bAdam Lesinski private static final boolean DBG = false; 55cacb28f2d60858106e2819cc7d95a65e8bda890bAdam Lesinski private static final String TAG = "SoundTriggerDetector"; 56cacb28f2d60858106e2819cc7d95a65e8bda890bAdam Lesinski 57cacb28f2d60858106e2819cc7d95a65e8bda890bAdam Lesinski private static final int MSG_AVAILABILITY_CHANGED = 1; 58cacb28f2d60858106e2819cc7d95a65e8bda890bAdam Lesinski private static final int MSG_SOUND_TRIGGER_DETECTED = 2; 59cacb28f2d60858106e2819cc7d95a65e8bda890bAdam Lesinski private static final int MSG_DETECTION_ERROR = 3; 60cacb28f2d60858106e2819cc7d95a65e8bda890bAdam Lesinski private static final int MSG_DETECTION_PAUSE = 4; 61cacb28f2d60858106e2819cc7d95a65e8bda890bAdam Lesinski private static final int MSG_DETECTION_RESUME = 5; 62cacb28f2d60858106e2819cc7d95a65e8bda890bAdam Lesinski 63cacb28f2d60858106e2819cc7d95a65e8bda890bAdam Lesinski private final Object mLock = new Object(); 64cacb28f2d60858106e2819cc7d95a65e8bda890bAdam Lesinski 65cacb28f2d60858106e2819cc7d95a65e8bda890bAdam Lesinski private final ISoundTriggerService mSoundTriggerService; 66cacb28f2d60858106e2819cc7d95a65e8bda890bAdam Lesinski private final UUID mSoundModelId; 67cacb28f2d60858106e2819cc7d95a65e8bda890bAdam Lesinski private final Callback mCallback; 68cacb28f2d60858106e2819cc7d95a65e8bda890bAdam Lesinski private final Handler mHandler; 69cacb28f2d60858106e2819cc7d95a65e8bda890bAdam Lesinski private final RecognitionCallback mRecognitionCallback; 70cacb28f2d60858106e2819cc7d95a65e8bda890bAdam Lesinski 71cacb28f2d60858106e2819cc7d95a65e8bda890bAdam Lesinski /** @hide */ 72cacb28f2d60858106e2819cc7d95a65e8bda890bAdam Lesinski @Retention(RetentionPolicy.SOURCE) 73cacb28f2d60858106e2819cc7d95a65e8bda890bAdam Lesinski @IntDef(flag = true, 74cacb28f2d60858106e2819cc7d95a65e8bda890bAdam Lesinski value = { 75cacb28f2d60858106e2819cc7d95a65e8bda890bAdam Lesinski RECOGNITION_FLAG_NONE, 76cacb28f2d60858106e2819cc7d95a65e8bda890bAdam Lesinski RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO, 77cacb28f2d60858106e2819cc7d95a65e8bda890bAdam Lesinski RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS 78cacb28f2d60858106e2819cc7d95a65e8bda890bAdam Lesinski }) 79cacb28f2d60858106e2819cc7d95a65e8bda890bAdam Lesinski public @interface RecognitionFlags {} 80cacb28f2d60858106e2819cc7d95a65e8bda890bAdam Lesinski 81cacb28f2d60858106e2819cc7d95a65e8bda890bAdam Lesinski /** 82cacb28f2d60858106e2819cc7d95a65e8bda890bAdam Lesinski * Empty flag for {@link #startRecognition(int)}. 83cacb28f2d60858106e2819cc7d95a65e8bda890bAdam Lesinski * 84cacb28f2d60858106e2819cc7d95a65e8bda890bAdam Lesinski * @hide 85cacb28f2d60858106e2819cc7d95a65e8bda890bAdam Lesinski */ 86cacb28f2d60858106e2819cc7d95a65e8bda890bAdam Lesinski public static final int RECOGNITION_FLAG_NONE = 0; 87cacb28f2d60858106e2819cc7d95a65e8bda890bAdam Lesinski 88cacb28f2d60858106e2819cc7d95a65e8bda890bAdam Lesinski /** 89cacb28f2d60858106e2819cc7d95a65e8bda890bAdam Lesinski * Recognition flag for {@link #startRecognition(int)} that indicates 90cacb28f2d60858106e2819cc7d95a65e8bda890bAdam Lesinski * whether the trigger audio for hotword needs to be captured. 91cacb28f2d60858106e2819cc7d95a65e8bda890bAdam Lesinski */ 92cacb28f2d60858106e2819cc7d95a65e8bda890bAdam Lesinski public static final int RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO = 0x1; 93cacb28f2d60858106e2819cc7d95a65e8bda890bAdam Lesinski 94cacb28f2d60858106e2819cc7d95a65e8bda890bAdam Lesinski /** 95cacb28f2d60858106e2819cc7d95a65e8bda890bAdam Lesinski * Recognition flag for {@link #startRecognition(int)} that indicates 96cacb28f2d60858106e2819cc7d95a65e8bda890bAdam Lesinski * whether the recognition should keep going on even after the 97cacb28f2d60858106e2819cc7d95a65e8bda890bAdam Lesinski * model triggers. 98cacb28f2d60858106e2819cc7d95a65e8bda890bAdam Lesinski * If this flag is specified, it's possible to get multiple 99cacb28f2d60858106e2819cc7d95a65e8bda890bAdam Lesinski * triggers after a call to {@link #startRecognition(int)}, if the model 100cacb28f2d60858106e2819cc7d95a65e8bda890bAdam Lesinski * triggers multiple times. 101cacb28f2d60858106e2819cc7d95a65e8bda890bAdam Lesinski * When this isn't specified, the default behavior is to stop recognition once the 102cacb28f2d60858106e2819cc7d95a65e8bda890bAdam Lesinski * trigger happenss, till the caller starts recognition again. 103cacb28f2d60858106e2819cc7d95a65e8bda890bAdam Lesinski */ 104cacb28f2d60858106e2819cc7d95a65e8bda890bAdam Lesinski public static final int RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS = 0x2; 105cacb28f2d60858106e2819cc7d95a65e8bda890bAdam Lesinski 1066f6ceb7e1456698b1f33e04536bfb3227f9fcfcbAdam Lesinski /** 1076f6ceb7e1456698b1f33e04536bfb3227f9fcfcbAdam Lesinski * Additional payload for {@link Callback#onDetected}. 1086f6ceb7e1456698b1f33e04536bfb3227f9fcfcbAdam Lesinski */ 109cacb28f2d60858106e2819cc7d95a65e8bda890bAdam Lesinski public static class EventPayload { 110cacb28f2d60858106e2819cc7d95a65e8bda890bAdam Lesinski private final boolean mTriggerAvailable; 1116f6ceb7e1456698b1f33e04536bfb3227f9fcfcbAdam Lesinski 1126f6ceb7e1456698b1f33e04536bfb3227f9fcfcbAdam Lesinski // Indicates if {@code captureSession} can be used to continue capturing more audio 1136f6ceb7e1456698b1f33e04536bfb3227f9fcfcbAdam Lesinski // from the DSP hardware. 114cacb28f2d60858106e2819cc7d95a65e8bda890bAdam Lesinski private final boolean mCaptureAvailable; 115cacb28f2d60858106e2819cc7d95a65e8bda890bAdam Lesinski // The session to use when attempting to capture more audio from the DSP hardware. 1166f6ceb7e1456698b1f33e04536bfb3227f9fcfcbAdam Lesinski private final int mCaptureSession; 1176f6ceb7e1456698b1f33e04536bfb3227f9fcfcbAdam Lesinski private final AudioFormat mAudioFormat; 1186f6ceb7e1456698b1f33e04536bfb3227f9fcfcbAdam Lesinski // Raw data associated with the event. 119cacb28f2d60858106e2819cc7d95a65e8bda890bAdam Lesinski // This is the audio that triggered the keyphrase if {@code isTriggerAudio} is true. 1206f6ceb7e1456698b1f33e04536bfb3227f9fcfcbAdam Lesinski private final byte[] mData; 1216f6ceb7e1456698b1f33e04536bfb3227f9fcfcbAdam Lesinski 1226f6ceb7e1456698b1f33e04536bfb3227f9fcfcbAdam Lesinski private EventPayload(boolean triggerAvailable, boolean captureAvailable, 123cacb28f2d60858106e2819cc7d95a65e8bda890bAdam Lesinski AudioFormat audioFormat, int captureSession, byte[] data) { 1246f6ceb7e1456698b1f33e04536bfb3227f9fcfcbAdam Lesinski mTriggerAvailable = triggerAvailable; 1256f6ceb7e1456698b1f33e04536bfb3227f9fcfcbAdam Lesinski mCaptureAvailable = captureAvailable; 126cacb28f2d60858106e2819cc7d95a65e8bda890bAdam Lesinski mCaptureSession = captureSession; 127cacb28f2d60858106e2819cc7d95a65e8bda890bAdam Lesinski mAudioFormat = audioFormat; 128cacb28f2d60858106e2819cc7d95a65e8bda890bAdam Lesinski mData = data; 129cacb28f2d60858106e2819cc7d95a65e8bda890bAdam Lesinski } 130cacb28f2d60858106e2819cc7d95a65e8bda890bAdam Lesinski 1316f6ceb7e1456698b1f33e04536bfb3227f9fcfcbAdam Lesinski /** 1326f6ceb7e1456698b1f33e04536bfb3227f9fcfcbAdam Lesinski * Gets the format of the audio obtained using {@link #getTriggerAudio()}. 133cacb28f2d60858106e2819cc7d95a65e8bda890bAdam Lesinski * May be null if there's no audio present. 134cacb28f2d60858106e2819cc7d95a65e8bda890bAdam Lesinski */ 135cacb28f2d60858106e2819cc7d95a65e8bda890bAdam Lesinski @Nullable 136cacb28f2d60858106e2819cc7d95a65e8bda890bAdam Lesinski public AudioFormat getCaptureAudioFormat() { 1376f6ceb7e1456698b1f33e04536bfb3227f9fcfcbAdam Lesinski return mAudioFormat; 1386f6ceb7e1456698b1f33e04536bfb3227f9fcfcbAdam Lesinski } 1396f6ceb7e1456698b1f33e04536bfb3227f9fcfcbAdam Lesinski 140cacb28f2d60858106e2819cc7d95a65e8bda890bAdam Lesinski /** 141cacb28f2d60858106e2819cc7d95a65e8bda890bAdam Lesinski * Gets the raw audio that triggered the detector. 1426f6ceb7e1456698b1f33e04536bfb3227f9fcfcbAdam Lesinski * This may be null if the trigger audio isn't available. 1436f6ceb7e1456698b1f33e04536bfb3227f9fcfcbAdam Lesinski * If non-null, the format of the audio can be obtained by calling 144cacb28f2d60858106e2819cc7d95a65e8bda890bAdam Lesinski * {@link #getCaptureAudioFormat()}. 145cacb28f2d60858106e2819cc7d95a65e8bda890bAdam Lesinski * 146cacb28f2d60858106e2819cc7d95a65e8bda890bAdam Lesinski * @see AlwaysOnHotwordDetector#RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO 14777788eb4cf0c5dba0f7370192e40364fe853050aAlexandria Cornwall */ 14877788eb4cf0c5dba0f7370192e40364fe853050aAlexandria Cornwall @Nullable 1496f6ceb7e1456698b1f33e04536bfb3227f9fcfcbAdam Lesinski public byte[] getTriggerAudio() { 150cacb28f2d60858106e2819cc7d95a65e8bda890bAdam Lesinski if (mTriggerAvailable) { 1516f6ceb7e1456698b1f33e04536bfb3227f9fcfcbAdam Lesinski return mData; 1526f6ceb7e1456698b1f33e04536bfb3227f9fcfcbAdam Lesinski } else { 1536f6ceb7e1456698b1f33e04536bfb3227f9fcfcbAdam Lesinski return null; 154cacb28f2d60858106e2819cc7d95a65e8bda890bAdam Lesinski } 1556f6ceb7e1456698b1f33e04536bfb3227f9fcfcbAdam Lesinski } 1566f6ceb7e1456698b1f33e04536bfb3227f9fcfcbAdam Lesinski 1576f6ceb7e1456698b1f33e04536bfb3227f9fcfcbAdam Lesinski /** 158cacb28f2d60858106e2819cc7d95a65e8bda890bAdam Lesinski * Gets the opaque data passed from the detection engine for the event. 1596f6ceb7e1456698b1f33e04536bfb3227f9fcfcbAdam Lesinski * This may be null if it was not populated by the engine, or if the data is known to 1606f6ceb7e1456698b1f33e04536bfb3227f9fcfcbAdam Lesinski * contain the trigger audio. 1616f6ceb7e1456698b1f33e04536bfb3227f9fcfcbAdam Lesinski * 162cacb28f2d60858106e2819cc7d95a65e8bda890bAdam Lesinski * @see #getTriggerAudio 1636f6ceb7e1456698b1f33e04536bfb3227f9fcfcbAdam Lesinski * 1646f6ceb7e1456698b1f33e04536bfb3227f9fcfcbAdam Lesinski * @hide 1656f6ceb7e1456698b1f33e04536bfb3227f9fcfcbAdam Lesinski */ 166cacb28f2d60858106e2819cc7d95a65e8bda890bAdam Lesinski @Nullable 1676f6ceb7e1456698b1f33e04536bfb3227f9fcfcbAdam Lesinski public byte[] getData() { 1686f6ceb7e1456698b1f33e04536bfb3227f9fcfcbAdam Lesinski if (!mTriggerAvailable) { 1696f6ceb7e1456698b1f33e04536bfb3227f9fcfcbAdam Lesinski return mData; 170cacb28f2d60858106e2819cc7d95a65e8bda890bAdam Lesinski } else { 1716f6ceb7e1456698b1f33e04536bfb3227f9fcfcbAdam Lesinski return null; 1726f6ceb7e1456698b1f33e04536bfb3227f9fcfcbAdam Lesinski } 173cacb28f2d60858106e2819cc7d95a65e8bda890bAdam Lesinski } 174cacb28f2d60858106e2819cc7d95a65e8bda890bAdam Lesinski 175cacb28f2d60858106e2819cc7d95a65e8bda890bAdam Lesinski /** 1766f6ceb7e1456698b1f33e04536bfb3227f9fcfcbAdam Lesinski * Gets the session ID to start a capture from the DSP. 1776f6ceb7e1456698b1f33e04536bfb3227f9fcfcbAdam Lesinski * This may be null if streaming capture isn't possible. 178cacb28f2d60858106e2819cc7d95a65e8bda890bAdam Lesinski * If non-null, the format of the audio that can be captured can be 1796f6ceb7e1456698b1f33e04536bfb3227f9fcfcbAdam Lesinski * obtained using {@link #getCaptureAudioFormat()}. 180cacb28f2d60858106e2819cc7d95a65e8bda890bAdam Lesinski * 181 * TODO: Candidate for Public API when the API to start capture with a session ID 182 * is made public. 183 * 184 * TODO: Add this to {@link #getCaptureAudioFormat()}: 185 * "Gets the format of the audio obtained using {@link #getTriggerAudio()} 186 * or {@link #getCaptureSession()}. May be null if no audio can be obtained 187 * for either the trigger or a streaming session." 188 * 189 * TODO: Should this return a known invalid value instead? 190 * 191 * @hide 192 */ 193 @Nullable 194 public Integer getCaptureSession() { 195 if (mCaptureAvailable) { 196 return mCaptureSession; 197 } else { 198 return null; 199 } 200 } 201 } 202 203 public static abstract class Callback { 204 /** 205 * Called when the availability of the sound model changes. 206 */ 207 public abstract void onAvailabilityChanged(int status); 208 209 /** 210 * Called when the sound model has triggered (such as when it matched a 211 * given sound pattern). 212 */ 213 public abstract void onDetected(@NonNull EventPayload eventPayload); 214 215 /** 216 * Called when the detection fails due to an error. 217 */ 218 public abstract void onError(); 219 220 /** 221 * Called when the recognition is paused temporarily for some reason. 222 * This is an informational callback, and the clients shouldn't be doing anything here 223 * except showing an indication on their UI if they have to. 224 */ 225 public abstract void onRecognitionPaused(); 226 227 /** 228 * Called when the recognition is resumed after it was temporarily paused. 229 * This is an informational callback, and the clients shouldn't be doing anything here 230 * except showing an indication on their UI if they have to. 231 */ 232 public abstract void onRecognitionResumed(); 233 } 234 235 /** 236 * This class should be constructed by the {@link SoundTriggerManager}. 237 * @hide 238 */ 239 SoundTriggerDetector(ISoundTriggerService soundTriggerService, UUID soundModelId, 240 @NonNull Callback callback, @Nullable Handler handler) { 241 mSoundTriggerService = soundTriggerService; 242 mSoundModelId = soundModelId; 243 mCallback = callback; 244 if (handler == null) { 245 mHandler = new MyHandler(); 246 } else { 247 mHandler = new MyHandler(handler.getLooper()); 248 } 249 mRecognitionCallback = new RecognitionCallback(); 250 } 251 252 /** 253 * Starts recognition on the associated sound model. Result is indicated via the 254 * {@link Callback}. 255 * @return Indicates whether the call succeeded or not. 256 */ 257 @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) 258 public boolean startRecognition(@RecognitionFlags int recognitionFlags) { 259 if (DBG) { 260 Slog.d(TAG, "startRecognition()"); 261 } 262 boolean captureTriggerAudio = 263 (recognitionFlags & RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO) != 0; 264 265 boolean allowMultipleTriggers = 266 (recognitionFlags & RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS) != 0; 267 int status = STATUS_OK; 268 try { 269 status = mSoundTriggerService.startRecognition(new ParcelUuid(mSoundModelId), 270 mRecognitionCallback, new RecognitionConfig(captureTriggerAudio, 271 allowMultipleTriggers, null, null)); 272 } catch (RemoteException e) { 273 return false; 274 } 275 return status == STATUS_OK; 276 } 277 278 /** 279 * Stops recognition for the associated model. 280 */ 281 @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) 282 public boolean stopRecognition() { 283 int status = STATUS_OK; 284 try { 285 status = mSoundTriggerService.stopRecognition(new ParcelUuid(mSoundModelId), 286 mRecognitionCallback); 287 } catch (RemoteException e) { 288 return false; 289 } 290 return status == STATUS_OK; 291 } 292 293 /** 294 * @hide 295 */ 296 public void dump(String prefix, PrintWriter pw) { 297 synchronized (mLock) { 298 // TODO: Dump useful debug information. 299 } 300 } 301 302 /** 303 * Callback that handles events from the lower sound trigger layer. 304 * 305 * Note that these callbacks will be called synchronously from the SoundTriggerService 306 * layer and thus should do minimal work (such as sending a message on a handler to do 307 * the real work). 308 * @hide 309 */ 310 private class RecognitionCallback extends IRecognitionStatusCallback.Stub { 311 312 /** 313 * @hide 314 */ 315 @Override 316 public void onGenericSoundTriggerDetected(SoundTrigger.GenericRecognitionEvent event) { 317 Slog.d(TAG, "onGenericSoundTriggerDetected()" + event); 318 Message.obtain(mHandler, 319 MSG_SOUND_TRIGGER_DETECTED, 320 new EventPayload(event.triggerInData, event.captureAvailable, 321 event.captureFormat, event.captureSession, event.data)) 322 .sendToTarget(); 323 } 324 325 @Override 326 public void onKeyphraseDetected(SoundTrigger.KeyphraseRecognitionEvent event) { 327 Slog.e(TAG, "Ignoring onKeyphraseDetected() called for " + event); 328 } 329 330 /** 331 * @hide 332 */ 333 @Override 334 public void onError(int status) { 335 Slog.d(TAG, "onError()" + status); 336 mHandler.sendEmptyMessage(MSG_DETECTION_ERROR); 337 } 338 339 /** 340 * @hide 341 */ 342 @Override 343 public void onRecognitionPaused() { 344 Slog.d(TAG, "onRecognitionPaused()"); 345 mHandler.sendEmptyMessage(MSG_DETECTION_PAUSE); 346 } 347 348 /** 349 * @hide 350 */ 351 @Override 352 public void onRecognitionResumed() { 353 Slog.d(TAG, "onRecognitionResumed()"); 354 mHandler.sendEmptyMessage(MSG_DETECTION_RESUME); 355 } 356 } 357 358 private class MyHandler extends Handler { 359 360 MyHandler() { 361 super(); 362 } 363 364 MyHandler(Looper looper) { 365 super(looper); 366 } 367 368 @Override 369 public void handleMessage(Message msg) { 370 if (mCallback == null) { 371 Slog.w(TAG, "Received message: " + msg.what + " for NULL callback."); 372 return; 373 } 374 switch (msg.what) { 375 case MSG_SOUND_TRIGGER_DETECTED: 376 mCallback.onDetected((EventPayload) msg.obj); 377 break; 378 case MSG_DETECTION_ERROR: 379 mCallback.onError(); 380 break; 381 case MSG_DETECTION_PAUSE: 382 mCallback.onRecognitionPaused(); 383 break; 384 case MSG_DETECTION_RESUME: 385 mCallback.onRecognitionResumed(); 386 break; 387 default: 388 super.handleMessage(msg); 389 390 } 391 } 392 } 393} 394