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