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 android.hardware.soundtrigger;
18
19import android.media.AudioFormat;
20import android.os.Handler;
21import android.os.Parcel;
22import android.os.Parcelable;
23
24import java.util.ArrayList;
25import java.util.Arrays;
26import java.util.UUID;
27
28import static android.system.OsConstants.*;
29
30/**
31 * The SoundTrigger class provides access via JNI to the native service managing
32 * the sound trigger HAL.
33 *
34 * @hide
35 */
36public class SoundTrigger {
37
38    public static final int STATUS_OK = 0;
39    public static final int STATUS_ERROR = Integer.MIN_VALUE;
40    public static final int STATUS_PERMISSION_DENIED = -EPERM;
41    public static final int STATUS_NO_INIT = -ENODEV;
42    public static final int STATUS_BAD_VALUE = -EINVAL;
43    public static final int STATUS_DEAD_OBJECT = -EPIPE;
44    public static final int STATUS_INVALID_OPERATION = -ENOSYS;
45
46    /*****************************************************************************
47     * A ModuleProperties describes a given sound trigger hardware module
48     * managed by the native sound trigger service. Each module has a unique
49     * ID used to target any API call to this paricular module. Module
50     * properties are returned by listModules() method.
51     ****************************************************************************/
52    public static class ModuleProperties implements Parcelable {
53        /** Unique module ID provided by the native service */
54        public final int id;
55
56        /** human readable voice detection engine implementor */
57        public final String implementor;
58
59        /** human readable voice detection engine description */
60        public final String description;
61
62        /** Unique voice engine Id (changes with each version) */
63        public final UUID uuid;
64
65        /** Voice detection engine version */
66        public final int version;
67
68        /** Maximum number of active sound models */
69        public final int maxSoundModels;
70
71        /** Maximum number of key phrases */
72        public final int maxKeyphrases;
73
74        /** Maximum number of users per key phrase */
75        public final int maxUsers;
76
77        /** Supported recognition modes (bit field, RECOGNITION_MODE_VOICE_TRIGGER ...) */
78        public final int recognitionModes;
79
80        /** Supports seamless transition to capture mode after recognition */
81        public final boolean supportsCaptureTransition;
82
83        /** Maximum buffering capacity in ms if supportsCaptureTransition() is true */
84        public final int maxBufferMs;
85
86        /** Supports capture by other use cases while detection is active */
87        public final boolean supportsConcurrentCapture;
88
89        /** Rated power consumption when detection is active with TDB silence/sound/speech ratio */
90        public final int powerConsumptionMw;
91
92        /** Returns the trigger (key phrase) capture in the binary data of the
93         * recognition callback event */
94        public final boolean returnsTriggerInEvent;
95
96        ModuleProperties(int id, String implementor, String description,
97                String uuid, int version, int maxSoundModels, int maxKeyphrases,
98                int maxUsers, int recognitionModes, boolean supportsCaptureTransition,
99                int maxBufferMs, boolean supportsConcurrentCapture,
100                int powerConsumptionMw, boolean returnsTriggerInEvent) {
101            this.id = id;
102            this.implementor = implementor;
103            this.description = description;
104            this.uuid = UUID.fromString(uuid);
105            this.version = version;
106            this.maxSoundModels = maxSoundModels;
107            this.maxKeyphrases = maxKeyphrases;
108            this.maxUsers = maxUsers;
109            this.recognitionModes = recognitionModes;
110            this.supportsCaptureTransition = supportsCaptureTransition;
111            this.maxBufferMs = maxBufferMs;
112            this.supportsConcurrentCapture = supportsConcurrentCapture;
113            this.powerConsumptionMw = powerConsumptionMw;
114            this.returnsTriggerInEvent = returnsTriggerInEvent;
115        }
116
117        public static final Parcelable.Creator<ModuleProperties> CREATOR
118                = new Parcelable.Creator<ModuleProperties>() {
119            public ModuleProperties createFromParcel(Parcel in) {
120                return ModuleProperties.fromParcel(in);
121            }
122
123            public ModuleProperties[] newArray(int size) {
124                return new ModuleProperties[size];
125            }
126        };
127
128        private static ModuleProperties fromParcel(Parcel in) {
129            int id = in.readInt();
130            String implementor = in.readString();
131            String description = in.readString();
132            String uuid = in.readString();
133            int version = in.readInt();
134            int maxSoundModels = in.readInt();
135            int maxKeyphrases = in.readInt();
136            int maxUsers = in.readInt();
137            int recognitionModes = in.readInt();
138            boolean supportsCaptureTransition = in.readByte() == 1;
139            int maxBufferMs = in.readInt();
140            boolean supportsConcurrentCapture = in.readByte() == 1;
141            int powerConsumptionMw = in.readInt();
142            boolean returnsTriggerInEvent = in.readByte() == 1;
143            return new ModuleProperties(id, implementor, description, uuid, version,
144                    maxSoundModels, maxKeyphrases, maxUsers, recognitionModes,
145                    supportsCaptureTransition, maxBufferMs, supportsConcurrentCapture,
146                    powerConsumptionMw, returnsTriggerInEvent);
147        }
148
149        @Override
150        public void writeToParcel(Parcel dest, int flags) {
151            dest.writeInt(id);
152            dest.writeString(implementor);
153            dest.writeString(description);
154            dest.writeString(uuid.toString());
155            dest.writeInt(version);
156            dest.writeInt(maxSoundModels);
157            dest.writeInt(maxKeyphrases);
158            dest.writeInt(maxUsers);
159            dest.writeInt(recognitionModes);
160            dest.writeByte((byte) (supportsCaptureTransition ? 1 : 0));
161            dest.writeInt(maxBufferMs);
162            dest.writeByte((byte) (supportsConcurrentCapture ? 1 : 0));
163            dest.writeInt(powerConsumptionMw);
164            dest.writeByte((byte) (returnsTriggerInEvent ? 1 : 0));
165        }
166
167        @Override
168        public int describeContents() {
169            return 0;
170        }
171
172        @Override
173        public String toString() {
174            return "ModuleProperties [id=" + id + ", implementor=" + implementor + ", description="
175                    + description + ", uuid=" + uuid + ", version=" + version + ", maxSoundModels="
176                    + maxSoundModels + ", maxKeyphrases=" + maxKeyphrases + ", maxUsers="
177                    + maxUsers + ", recognitionModes=" + recognitionModes
178                    + ", supportsCaptureTransition=" + supportsCaptureTransition + ", maxBufferMs="
179                    + maxBufferMs + ", supportsConcurrentCapture=" + supportsConcurrentCapture
180                    + ", powerConsumptionMw=" + powerConsumptionMw
181                    + ", returnsTriggerInEvent=" + returnsTriggerInEvent + "]";
182        }
183    }
184
185    /*****************************************************************************
186     * A SoundModel describes the attributes and contains the binary data used by the hardware
187     * implementation to detect a particular sound pattern.
188     * A specialized version {@link KeyphraseSoundModel} is defined for key phrase
189     * sound models.
190     ****************************************************************************/
191    public static class SoundModel {
192        /** Undefined sound model type */
193        public static final int TYPE_UNKNOWN = -1;
194
195        /** Keyphrase sound model */
196        public static final int TYPE_KEYPHRASE = 0;
197
198        /**
199         * A generic sound model. Use this type only for non-keyphrase sound models such as
200         * ones that match a particular sound pattern.
201         */
202        public static final int TYPE_GENERIC_SOUND = 1;
203
204        /** Unique sound model identifier */
205        public final UUID uuid;
206
207        /** Sound model type (e.g. TYPE_KEYPHRASE); */
208        public final int type;
209
210        /** Unique sound model vendor identifier */
211        public final UUID vendorUuid;
212
213        /** Opaque data. For use by vendor implementation and enrollment application */
214        public final byte[] data;
215
216        public SoundModel(UUID uuid, UUID vendorUuid, int type, byte[] data) {
217            this.uuid = uuid;
218            this.vendorUuid = vendorUuid;
219            this.type = type;
220            this.data = data;
221        }
222
223        @Override
224        public int hashCode() {
225            final int prime = 31;
226            int result = 1;
227            result = prime * result + Arrays.hashCode(data);
228            result = prime * result + type;
229            result = prime * result + ((uuid == null) ? 0 : uuid.hashCode());
230            result = prime * result + ((vendorUuid == null) ? 0 : vendorUuid.hashCode());
231            return result;
232        }
233
234        @Override
235        public boolean equals(Object obj) {
236            if (this == obj)
237                return true;
238            if (obj == null)
239                return false;
240            if (!(obj instanceof SoundModel))
241                return false;
242            SoundModel other = (SoundModel) obj;
243            if (!Arrays.equals(data, other.data))
244                return false;
245            if (type != other.type)
246                return false;
247            if (uuid == null) {
248                if (other.uuid != null)
249                    return false;
250            } else if (!uuid.equals(other.uuid))
251                return false;
252            if (vendorUuid == null) {
253                if (other.vendorUuid != null)
254                    return false;
255            } else if (!vendorUuid.equals(other.vendorUuid))
256                return false;
257            return true;
258        }
259    }
260
261    /*****************************************************************************
262     * A Keyphrase describes a key phrase that can be detected by a
263     * {@link KeyphraseSoundModel}
264     ****************************************************************************/
265    public static class Keyphrase implements Parcelable {
266        /** Unique identifier for this keyphrase */
267        public final int id;
268
269        /** Recognition modes supported for this key phrase in the model */
270        public final int recognitionModes;
271
272        /** Locale of the keyphrase. JAVA Locale string e.g en_US */
273        public final String locale;
274
275        /** Key phrase text */
276        public final String text;
277
278        /** Users this key phrase has been trained for. countains sound trigger specific user IDs
279         * derived from system user IDs {@link android.os.UserHandle#getIdentifier()}. */
280        public final int[] users;
281
282        public Keyphrase(int id, int recognitionModes, String locale, String text, int[] users) {
283            this.id = id;
284            this.recognitionModes = recognitionModes;
285            this.locale = locale;
286            this.text = text;
287            this.users = users;
288        }
289
290        public static final Parcelable.Creator<Keyphrase> CREATOR
291                = new Parcelable.Creator<Keyphrase>() {
292            public Keyphrase createFromParcel(Parcel in) {
293                return Keyphrase.fromParcel(in);
294            }
295
296            public Keyphrase[] newArray(int size) {
297                return new Keyphrase[size];
298            }
299        };
300
301        private static Keyphrase fromParcel(Parcel in) {
302            int id = in.readInt();
303            int recognitionModes = in.readInt();
304            String locale = in.readString();
305            String text = in.readString();
306            int[] users = null;
307            int numUsers = in.readInt();
308            if (numUsers >= 0) {
309                users = new int[numUsers];
310                in.readIntArray(users);
311            }
312            return new Keyphrase(id, recognitionModes, locale, text, users);
313        }
314
315        @Override
316        public void writeToParcel(Parcel dest, int flags) {
317            dest.writeInt(id);
318            dest.writeInt(recognitionModes);
319            dest.writeString(locale);
320            dest.writeString(text);
321            if (users != null) {
322                dest.writeInt(users.length);
323                dest.writeIntArray(users);
324            } else {
325                dest.writeInt(-1);
326            }
327        }
328
329        @Override
330        public int describeContents() {
331            return 0;
332        }
333
334        @Override
335        public int hashCode() {
336            final int prime = 31;
337            int result = 1;
338            result = prime * result + ((text == null) ? 0 : text.hashCode());
339            result = prime * result + id;
340            result = prime * result + ((locale == null) ? 0 : locale.hashCode());
341            result = prime * result + recognitionModes;
342            result = prime * result + Arrays.hashCode(users);
343            return result;
344        }
345
346        @Override
347        public boolean equals(Object obj) {
348            if (this == obj)
349                return true;
350            if (obj == null)
351                return false;
352            if (getClass() != obj.getClass())
353                return false;
354            Keyphrase other = (Keyphrase) obj;
355            if (text == null) {
356                if (other.text != null)
357                    return false;
358            } else if (!text.equals(other.text))
359                return false;
360            if (id != other.id)
361                return false;
362            if (locale == null) {
363                if (other.locale != null)
364                    return false;
365            } else if (!locale.equals(other.locale))
366                return false;
367            if (recognitionModes != other.recognitionModes)
368                return false;
369            if (!Arrays.equals(users, other.users))
370                return false;
371            return true;
372        }
373
374        @Override
375        public String toString() {
376            return "Keyphrase [id=" + id + ", recognitionModes=" + recognitionModes + ", locale="
377                    + locale + ", text=" + text + ", users=" + Arrays.toString(users) + "]";
378        }
379    }
380
381    /*****************************************************************************
382     * A KeyphraseSoundModel is a specialized {@link SoundModel} for key phrases.
383     * It contains data needed by the hardware to detect a certain number of key phrases
384     * and the list of corresponding {@link Keyphrase} descriptors.
385     ****************************************************************************/
386    public static class KeyphraseSoundModel extends SoundModel implements Parcelable {
387        /** Key phrases in this sound model */
388        public final Keyphrase[] keyphrases; // keyword phrases in model
389
390        public KeyphraseSoundModel(
391                UUID uuid, UUID vendorUuid, byte[] data, Keyphrase[] keyphrases) {
392            super(uuid, vendorUuid, TYPE_KEYPHRASE, data);
393            this.keyphrases = keyphrases;
394        }
395
396        public static final Parcelable.Creator<KeyphraseSoundModel> CREATOR
397                = new Parcelable.Creator<KeyphraseSoundModel>() {
398            public KeyphraseSoundModel createFromParcel(Parcel in) {
399                return KeyphraseSoundModel.fromParcel(in);
400            }
401
402            public KeyphraseSoundModel[] newArray(int size) {
403                return new KeyphraseSoundModel[size];
404            }
405        };
406
407        private static KeyphraseSoundModel fromParcel(Parcel in) {
408            UUID uuid = UUID.fromString(in.readString());
409            UUID vendorUuid = null;
410            int length = in.readInt();
411            if (length >= 0) {
412                vendorUuid = UUID.fromString(in.readString());
413            }
414            byte[] data = in.readBlob();
415            Keyphrase[] keyphrases = in.createTypedArray(Keyphrase.CREATOR);
416            return new KeyphraseSoundModel(uuid, vendorUuid, data, keyphrases);
417        }
418
419        @Override
420        public int describeContents() {
421            return 0;
422        }
423
424        @Override
425        public void writeToParcel(Parcel dest, int flags) {
426            dest.writeString(uuid.toString());
427            if (vendorUuid == null) {
428                dest.writeInt(-1);
429            } else {
430                dest.writeInt(vendorUuid.toString().length());
431                dest.writeString(vendorUuid.toString());
432            }
433            dest.writeBlob(data);
434            dest.writeTypedArray(keyphrases, flags);
435        }
436
437        @Override
438        public String toString() {
439            return "KeyphraseSoundModel [keyphrases=" + Arrays.toString(keyphrases)
440                    + ", uuid=" + uuid + ", vendorUuid=" + vendorUuid
441                    + ", type=" + type + ", data=" + (data == null ? 0 : data.length) + "]";
442        }
443
444        @Override
445        public int hashCode() {
446            final int prime = 31;
447            int result = super.hashCode();
448            result = prime * result + Arrays.hashCode(keyphrases);
449            return result;
450        }
451
452        @Override
453        public boolean equals(Object obj) {
454            if (this == obj)
455                return true;
456            if (!super.equals(obj))
457                return false;
458            if (!(obj instanceof KeyphraseSoundModel))
459                return false;
460            KeyphraseSoundModel other = (KeyphraseSoundModel) obj;
461            if (!Arrays.equals(keyphrases, other.keyphrases))
462                return false;
463            return true;
464        }
465    }
466
467
468    /*****************************************************************************
469     * A GenericSoundModel is a specialized {@link SoundModel} for non-voice sound
470     * patterns.
471     ****************************************************************************/
472    public static class GenericSoundModel extends SoundModel implements Parcelable {
473
474        public static final Parcelable.Creator<GenericSoundModel> CREATOR
475                = new Parcelable.Creator<GenericSoundModel>() {
476            public GenericSoundModel createFromParcel(Parcel in) {
477                return GenericSoundModel.fromParcel(in);
478            }
479
480            public GenericSoundModel[] newArray(int size) {
481                return new GenericSoundModel[size];
482            }
483        };
484
485        public GenericSoundModel(UUID uuid, UUID vendorUuid, byte[] data) {
486            super(uuid, vendorUuid, TYPE_GENERIC_SOUND, data);
487        }
488
489        @Override
490        public int describeContents() {
491            return 0;
492        }
493
494        private static GenericSoundModel fromParcel(Parcel in) {
495            UUID uuid = UUID.fromString(in.readString());
496            UUID vendorUuid = null;
497            int length = in.readInt();
498            if (length >= 0) {
499                vendorUuid = UUID.fromString(in.readString());
500            }
501            byte[] data = in.readBlob();
502            return new GenericSoundModel(uuid, vendorUuid, data);
503        }
504
505        @Override
506        public void writeToParcel(Parcel dest, int flags) {
507            dest.writeString(uuid.toString());
508            if (vendorUuid == null) {
509                dest.writeInt(-1);
510            } else {
511                dest.writeInt(vendorUuid.toString().length());
512                dest.writeString(vendorUuid.toString());
513            }
514            dest.writeBlob(data);
515        }
516
517        @Override
518        public String toString() {
519            return "GenericSoundModel [uuid=" + uuid + ", vendorUuid=" + vendorUuid
520                    + ", type=" + type + ", data=" + (data == null ? 0 : data.length) + "]";
521        }
522    }
523
524    /**
525     *  Modes for key phrase recognition
526     */
527    /** Simple recognition of the key phrase */
528    public static final int RECOGNITION_MODE_VOICE_TRIGGER = 0x1;
529    /** Trigger only if one user is identified */
530    public static final int RECOGNITION_MODE_USER_IDENTIFICATION = 0x2;
531    /** Trigger only if one user is authenticated */
532    public static final int RECOGNITION_MODE_USER_AUTHENTICATION = 0x4;
533
534    /**
535     *  Status codes for {@link RecognitionEvent}
536     */
537    /** Recognition success */
538    public static final int RECOGNITION_STATUS_SUCCESS = 0;
539    /** Recognition aborted (e.g. capture preempted by anotehr use case */
540    public static final int RECOGNITION_STATUS_ABORT = 1;
541    /** Recognition failure */
542    public static final int RECOGNITION_STATUS_FAILURE = 2;
543
544    /**
545     *  A RecognitionEvent is provided by the
546     *  {@link StatusListener#onRecognition(RecognitionEvent)}
547     *  callback upon recognition success or failure.
548     */
549    public static class RecognitionEvent implements Parcelable {
550        /** Recognition status e.g {@link #RECOGNITION_STATUS_SUCCESS} */
551        public final int status;
552        /** Sound Model corresponding to this event callback */
553        public final int soundModelHandle;
554        /** True if it is possible to capture audio from this utterance buffered by the hardware */
555        public final boolean captureAvailable;
556        /** Audio session ID to be used when capturing the utterance with an AudioRecord
557         * if captureAvailable() is true. */
558        public final int captureSession;
559        /** Delay in ms between end of model detection and start of audio available for capture.
560         * A negative value is possible (e.g. if keyphrase is also available for capture) */
561        public final int captureDelayMs;
562        /** Duration in ms of audio captured before the start of the trigger. 0 if none. */
563        public final int capturePreambleMs;
564        /** True if  the trigger (key phrase capture is present in binary data */
565        public final boolean triggerInData;
566        /** Audio format of either the trigger in event data or to use for capture of the
567          * rest of the utterance */
568        public AudioFormat captureFormat;
569        /** Opaque data for use by system applications who know about voice engine internals,
570         * typically during enrollment. */
571        public final byte[] data;
572
573        public RecognitionEvent(int status, int soundModelHandle, boolean captureAvailable,
574                int captureSession, int captureDelayMs, int capturePreambleMs,
575                boolean triggerInData, AudioFormat captureFormat, byte[] data) {
576            this.status = status;
577            this.soundModelHandle = soundModelHandle;
578            this.captureAvailable = captureAvailable;
579            this.captureSession = captureSession;
580            this.captureDelayMs = captureDelayMs;
581            this.capturePreambleMs = capturePreambleMs;
582            this.triggerInData = triggerInData;
583            this.captureFormat = captureFormat;
584            this.data = data;
585        }
586
587        public static final Parcelable.Creator<RecognitionEvent> CREATOR
588                = new Parcelable.Creator<RecognitionEvent>() {
589            public RecognitionEvent createFromParcel(Parcel in) {
590                return RecognitionEvent.fromParcel(in);
591            }
592
593            public RecognitionEvent[] newArray(int size) {
594                return new RecognitionEvent[size];
595            }
596        };
597
598        protected static RecognitionEvent fromParcel(Parcel in) {
599            int status = in.readInt();
600            int soundModelHandle = in.readInt();
601            boolean captureAvailable = in.readByte() == 1;
602            int captureSession = in.readInt();
603            int captureDelayMs = in.readInt();
604            int capturePreambleMs = in.readInt();
605            boolean triggerInData = in.readByte() == 1;
606            AudioFormat captureFormat = null;
607            if (in.readByte() == 1) {
608                int sampleRate = in.readInt();
609                int encoding = in.readInt();
610                int channelMask = in.readInt();
611                captureFormat = (new AudioFormat.Builder())
612                        .setChannelMask(channelMask)
613                        .setEncoding(encoding)
614                        .setSampleRate(sampleRate)
615                        .build();
616            }
617            byte[] data = in.readBlob();
618            return new RecognitionEvent(status, soundModelHandle, captureAvailable, captureSession,
619                    captureDelayMs, capturePreambleMs, triggerInData, captureFormat, data);
620        }
621
622        @Override
623        public int describeContents() {
624            return 0;
625        }
626
627        @Override
628        public void writeToParcel(Parcel dest, int flags) {
629            dest.writeInt(status);
630            dest.writeInt(soundModelHandle);
631            dest.writeByte((byte) (captureAvailable ? 1 : 0));
632            dest.writeInt(captureSession);
633            dest.writeInt(captureDelayMs);
634            dest.writeInt(capturePreambleMs);
635            dest.writeByte((byte) (triggerInData ? 1 : 0));
636            if (captureFormat != null) {
637                dest.writeByte((byte)1);
638                dest.writeInt(captureFormat.getSampleRate());
639                dest.writeInt(captureFormat.getEncoding());
640                dest.writeInt(captureFormat.getChannelMask());
641            } else {
642                dest.writeByte((byte)0);
643            }
644            dest.writeBlob(data);
645        }
646
647        @Override
648        public int hashCode() {
649            final int prime = 31;
650            int result = 1;
651            result = prime * result + (captureAvailable ? 1231 : 1237);
652            result = prime * result + captureDelayMs;
653            result = prime * result + capturePreambleMs;
654            result = prime * result + captureSession;
655            result = prime * result + (triggerInData ? 1231 : 1237);
656            if (captureFormat != null) {
657                result = prime * result + captureFormat.getSampleRate();
658                result = prime * result + captureFormat.getEncoding();
659                result = prime * result + captureFormat.getChannelMask();
660            }
661            result = prime * result + Arrays.hashCode(data);
662            result = prime * result + soundModelHandle;
663            result = prime * result + status;
664            return result;
665        }
666
667        @Override
668        public boolean equals(Object obj) {
669            if (this == obj)
670                return true;
671            if (obj == null)
672                return false;
673            if (getClass() != obj.getClass())
674                return false;
675            RecognitionEvent other = (RecognitionEvent) obj;
676            if (captureAvailable != other.captureAvailable)
677                return false;
678            if (captureDelayMs != other.captureDelayMs)
679                return false;
680            if (capturePreambleMs != other.capturePreambleMs)
681                return false;
682            if (captureSession != other.captureSession)
683                return false;
684            if (!Arrays.equals(data, other.data))
685                return false;
686            if (soundModelHandle != other.soundModelHandle)
687                return false;
688            if (status != other.status)
689                return false;
690            if (triggerInData != other.triggerInData)
691                return false;
692            if (captureFormat == null) {
693                if (other.captureFormat != null)
694                    return false;
695            } else {
696                if (other.captureFormat == null)
697                    return false;
698                if (captureFormat.getSampleRate() != other.captureFormat.getSampleRate())
699                    return false;
700                if (captureFormat.getEncoding() != other.captureFormat.getEncoding())
701                    return false;
702                if (captureFormat.getChannelMask() != other.captureFormat.getChannelMask())
703                    return false;
704            }
705            return true;
706        }
707
708        @Override
709        public String toString() {
710            return "RecognitionEvent [status=" + status + ", soundModelHandle=" + soundModelHandle
711                    + ", captureAvailable=" + captureAvailable + ", captureSession="
712                    + captureSession + ", captureDelayMs=" + captureDelayMs
713                    + ", capturePreambleMs=" + capturePreambleMs
714                    + ", triggerInData=" + triggerInData
715                    + ((captureFormat == null) ? "" :
716                        (", sampleRate=" + captureFormat.getSampleRate()))
717                    + ((captureFormat == null) ? "" :
718                        (", encoding=" + captureFormat.getEncoding()))
719                    + ((captureFormat == null) ? "" :
720                        (", channelMask=" + captureFormat.getChannelMask()))
721                    + ", data=" + (data == null ? 0 : data.length) + "]";
722        }
723    }
724
725    /**
726     *  A RecognitionConfig is provided to
727     *  {@link SoundTriggerModule#startRecognition(int, RecognitionConfig)} to configure the
728     *  recognition request.
729     */
730    public static class RecognitionConfig implements Parcelable {
731        /** True if the DSP should capture the trigger sound and make it available for further
732         * capture. */
733        public final boolean captureRequested;
734        /**
735         * True if the service should restart listening after the DSP triggers.
736         * Note: This config flag is currently used at the service layer rather than by the DSP.
737         */
738        public final boolean allowMultipleTriggers;
739        /** List of all keyphrases in the sound model for which recognition should be performed with
740         * options for each keyphrase. */
741        public final KeyphraseRecognitionExtra keyphrases[];
742        /** Opaque data for use by system applications who know about voice engine internals,
743         * typically during enrollment. */
744        public final byte[] data;
745
746        public RecognitionConfig(boolean captureRequested, boolean allowMultipleTriggers,
747                KeyphraseRecognitionExtra keyphrases[], byte[] data) {
748            this.captureRequested = captureRequested;
749            this.allowMultipleTriggers = allowMultipleTriggers;
750            this.keyphrases = keyphrases;
751            this.data = data;
752        }
753
754        public static final Parcelable.Creator<RecognitionConfig> CREATOR
755                = new Parcelable.Creator<RecognitionConfig>() {
756            public RecognitionConfig createFromParcel(Parcel in) {
757                return RecognitionConfig.fromParcel(in);
758            }
759
760            public RecognitionConfig[] newArray(int size) {
761                return new RecognitionConfig[size];
762            }
763        };
764
765        private static RecognitionConfig fromParcel(Parcel in) {
766            boolean captureRequested = in.readByte() == 1;
767            boolean allowMultipleTriggers = in.readByte() == 1;
768            KeyphraseRecognitionExtra[] keyphrases =
769                    in.createTypedArray(KeyphraseRecognitionExtra.CREATOR);
770            byte[] data = in.readBlob();
771            return new RecognitionConfig(captureRequested, allowMultipleTriggers, keyphrases, data);
772        }
773
774        @Override
775        public void writeToParcel(Parcel dest, int flags) {
776            dest.writeByte((byte) (captureRequested ? 1 : 0));
777            dest.writeByte((byte) (allowMultipleTriggers ? 1 : 0));
778            dest.writeTypedArray(keyphrases, flags);
779            dest.writeBlob(data);
780        }
781
782        @Override
783        public int describeContents() {
784            return 0;
785        }
786
787        @Override
788        public String toString() {
789            return "RecognitionConfig [captureRequested=" + captureRequested
790                    + ", allowMultipleTriggers=" + allowMultipleTriggers + ", keyphrases="
791                    + Arrays.toString(keyphrases) + ", data=" + Arrays.toString(data) + "]";
792        }
793    }
794
795    /**
796     * Confidence level for users defined in a keyphrase.
797     * - The confidence level is expressed in percent (0% -100%).
798     * When used in a {@link KeyphraseRecognitionEvent} it indicates the detected confidence level
799     * When used in a {@link RecognitionConfig} it indicates the minimum confidence level that
800     * should trigger a recognition.
801     * - The user ID is derived from the system ID {@link android.os.UserHandle#getIdentifier()}.
802     */
803    public static class ConfidenceLevel implements Parcelable {
804        public final int userId;
805        public final int confidenceLevel;
806
807        public ConfidenceLevel(int userId, int confidenceLevel) {
808            this.userId = userId;
809            this.confidenceLevel = confidenceLevel;
810        }
811
812        public static final Parcelable.Creator<ConfidenceLevel> CREATOR
813                = new Parcelable.Creator<ConfidenceLevel>() {
814            public ConfidenceLevel createFromParcel(Parcel in) {
815                return ConfidenceLevel.fromParcel(in);
816            }
817
818            public ConfidenceLevel[] newArray(int size) {
819                return new ConfidenceLevel[size];
820            }
821        };
822
823        private static ConfidenceLevel fromParcel(Parcel in) {
824            int userId = in.readInt();
825            int confidenceLevel = in.readInt();
826            return new ConfidenceLevel(userId, confidenceLevel);
827        }
828
829        @Override
830        public void writeToParcel(Parcel dest, int flags) {
831            dest.writeInt(userId);
832            dest.writeInt(confidenceLevel);
833        }
834
835        @Override
836        public int describeContents() {
837            return 0;
838        }
839
840        @Override
841        public int hashCode() {
842            final int prime = 31;
843            int result = 1;
844            result = prime * result + confidenceLevel;
845            result = prime * result + userId;
846            return result;
847        }
848
849        @Override
850        public boolean equals(Object obj) {
851            if (this == obj)
852                return true;
853            if (obj == null)
854                return false;
855            if (getClass() != obj.getClass())
856                return false;
857            ConfidenceLevel other = (ConfidenceLevel) obj;
858            if (confidenceLevel != other.confidenceLevel)
859                return false;
860            if (userId != other.userId)
861                return false;
862            return true;
863        }
864
865        @Override
866        public String toString() {
867            return "ConfidenceLevel [userId=" + userId
868                    + ", confidenceLevel=" + confidenceLevel + "]";
869        }
870    }
871
872    /**
873     *  Additional data conveyed by a {@link KeyphraseRecognitionEvent}
874     *  for a key phrase detection.
875     */
876    public static class KeyphraseRecognitionExtra implements Parcelable {
877        /** The keyphrase ID */
878        public final int id;
879
880        /** Recognition modes matched for this event */
881        public final int recognitionModes;
882
883        /** Confidence level for mode RECOGNITION_MODE_VOICE_TRIGGER when user identification
884         * is not performed */
885        public final int coarseConfidenceLevel;
886
887        /** Confidence levels for all users recognized (KeyphraseRecognitionEvent) or to
888         * be recognized (RecognitionConfig) */
889        public final ConfidenceLevel[] confidenceLevels;
890
891        public KeyphraseRecognitionExtra(int id, int recognitionModes, int coarseConfidenceLevel,
892                ConfidenceLevel[] confidenceLevels) {
893            this.id = id;
894            this.recognitionModes = recognitionModes;
895            this.coarseConfidenceLevel = coarseConfidenceLevel;
896            this.confidenceLevels = confidenceLevels;
897        }
898
899        public static final Parcelable.Creator<KeyphraseRecognitionExtra> CREATOR
900                = new Parcelable.Creator<KeyphraseRecognitionExtra>() {
901            public KeyphraseRecognitionExtra createFromParcel(Parcel in) {
902                return KeyphraseRecognitionExtra.fromParcel(in);
903            }
904
905            public KeyphraseRecognitionExtra[] newArray(int size) {
906                return new KeyphraseRecognitionExtra[size];
907            }
908        };
909
910        private static KeyphraseRecognitionExtra fromParcel(Parcel in) {
911            int id = in.readInt();
912            int recognitionModes = in.readInt();
913            int coarseConfidenceLevel = in.readInt();
914            ConfidenceLevel[] confidenceLevels = in.createTypedArray(ConfidenceLevel.CREATOR);
915            return new KeyphraseRecognitionExtra(id, recognitionModes, coarseConfidenceLevel,
916                    confidenceLevels);
917        }
918
919        @Override
920        public void writeToParcel(Parcel dest, int flags) {
921            dest.writeInt(id);
922            dest.writeInt(recognitionModes);
923            dest.writeInt(coarseConfidenceLevel);
924            dest.writeTypedArray(confidenceLevels, flags);
925        }
926
927        @Override
928        public int describeContents() {
929            return 0;
930        }
931
932        @Override
933        public int hashCode() {
934            final int prime = 31;
935            int result = 1;
936            result = prime * result + Arrays.hashCode(confidenceLevels);
937            result = prime * result + id;
938            result = prime * result + recognitionModes;
939            result = prime * result + coarseConfidenceLevel;
940            return result;
941        }
942
943        @Override
944        public boolean equals(Object obj) {
945            if (this == obj)
946                return true;
947            if (obj == null)
948                return false;
949            if (getClass() != obj.getClass())
950                return false;
951            KeyphraseRecognitionExtra other = (KeyphraseRecognitionExtra) obj;
952            if (!Arrays.equals(confidenceLevels, other.confidenceLevels))
953                return false;
954            if (id != other.id)
955                return false;
956            if (recognitionModes != other.recognitionModes)
957                return false;
958            if (coarseConfidenceLevel != other.coarseConfidenceLevel)
959                return false;
960            return true;
961        }
962
963        @Override
964        public String toString() {
965            return "KeyphraseRecognitionExtra [id=" + id + ", recognitionModes=" + recognitionModes
966                    + ", coarseConfidenceLevel=" + coarseConfidenceLevel
967                    + ", confidenceLevels=" + Arrays.toString(confidenceLevels) + "]";
968        }
969    }
970
971    /**
972     *  Specialized {@link RecognitionEvent} for a key phrase detection.
973     */
974    public static class KeyphraseRecognitionEvent extends RecognitionEvent {
975        /** Indicates if the key phrase is present in the buffered audio available for capture */
976        public final KeyphraseRecognitionExtra[] keyphraseExtras;
977
978        public KeyphraseRecognitionEvent(int status, int soundModelHandle, boolean captureAvailable,
979               int captureSession, int captureDelayMs, int capturePreambleMs,
980               boolean triggerInData, AudioFormat captureFormat, byte[] data,
981               KeyphraseRecognitionExtra[] keyphraseExtras) {
982            super(status, soundModelHandle, captureAvailable, captureSession, captureDelayMs,
983                  capturePreambleMs, triggerInData, captureFormat, data);
984            this.keyphraseExtras = keyphraseExtras;
985        }
986
987        public static final Parcelable.Creator<KeyphraseRecognitionEvent> CREATOR
988                = new Parcelable.Creator<KeyphraseRecognitionEvent>() {
989            public KeyphraseRecognitionEvent createFromParcel(Parcel in) {
990                return KeyphraseRecognitionEvent.fromParcelForKeyphrase(in);
991            }
992
993            public KeyphraseRecognitionEvent[] newArray(int size) {
994                return new KeyphraseRecognitionEvent[size];
995            }
996        };
997
998        private static KeyphraseRecognitionEvent fromParcelForKeyphrase(Parcel in) {
999            int status = in.readInt();
1000            int soundModelHandle = in.readInt();
1001            boolean captureAvailable = in.readByte() == 1;
1002            int captureSession = in.readInt();
1003            int captureDelayMs = in.readInt();
1004            int capturePreambleMs = in.readInt();
1005            boolean triggerInData = in.readByte() == 1;
1006            AudioFormat captureFormat = null;
1007            if (in.readByte() == 1) {
1008                int sampleRate = in.readInt();
1009                int encoding = in.readInt();
1010                int channelMask = in.readInt();
1011                captureFormat = (new AudioFormat.Builder())
1012                    .setChannelMask(channelMask)
1013                    .setEncoding(encoding)
1014                    .setSampleRate(sampleRate)
1015                    .build();
1016            }
1017            byte[] data = in.readBlob();
1018            KeyphraseRecognitionExtra[] keyphraseExtras =
1019                    in.createTypedArray(KeyphraseRecognitionExtra.CREATOR);
1020            return new KeyphraseRecognitionEvent(status, soundModelHandle, captureAvailable,
1021                    captureSession, captureDelayMs, capturePreambleMs, triggerInData,
1022                    captureFormat, data, keyphraseExtras);
1023        }
1024
1025        @Override
1026        public void writeToParcel(Parcel dest, int flags) {
1027            dest.writeInt(status);
1028            dest.writeInt(soundModelHandle);
1029            dest.writeByte((byte) (captureAvailable ? 1 : 0));
1030            dest.writeInt(captureSession);
1031            dest.writeInt(captureDelayMs);
1032            dest.writeInt(capturePreambleMs);
1033            dest.writeByte((byte) (triggerInData ? 1 : 0));
1034            if (captureFormat != null) {
1035                dest.writeByte((byte)1);
1036                dest.writeInt(captureFormat.getSampleRate());
1037                dest.writeInt(captureFormat.getEncoding());
1038                dest.writeInt(captureFormat.getChannelMask());
1039            } else {
1040                dest.writeByte((byte)0);
1041            }
1042            dest.writeBlob(data);
1043            dest.writeTypedArray(keyphraseExtras, flags);
1044        }
1045
1046        @Override
1047        public int describeContents() {
1048            return 0;
1049        }
1050
1051        @Override
1052        public int hashCode() {
1053            final int prime = 31;
1054            int result = super.hashCode();
1055            result = prime * result + Arrays.hashCode(keyphraseExtras);
1056            return result;
1057        }
1058
1059        @Override
1060        public boolean equals(Object obj) {
1061            if (this == obj)
1062                return true;
1063            if (!super.equals(obj))
1064                return false;
1065            if (getClass() != obj.getClass())
1066                return false;
1067            KeyphraseRecognitionEvent other = (KeyphraseRecognitionEvent) obj;
1068            if (!Arrays.equals(keyphraseExtras, other.keyphraseExtras))
1069                return false;
1070            return true;
1071        }
1072
1073        @Override
1074        public String toString() {
1075            return "KeyphraseRecognitionEvent [keyphraseExtras=" + Arrays.toString(keyphraseExtras)
1076                    + ", status=" + status
1077                    + ", soundModelHandle=" + soundModelHandle + ", captureAvailable="
1078                    + captureAvailable + ", captureSession=" + captureSession + ", captureDelayMs="
1079                    + captureDelayMs + ", capturePreambleMs=" + capturePreambleMs
1080                    + ", triggerInData=" + triggerInData
1081                    + ((captureFormat == null) ? "" :
1082                        (", sampleRate=" + captureFormat.getSampleRate()))
1083                    + ((captureFormat == null) ? "" :
1084                        (", encoding=" + captureFormat.getEncoding()))
1085                    + ((captureFormat == null) ? "" :
1086                        (", channelMask=" + captureFormat.getChannelMask()))
1087                    + ", data=" + (data == null ? 0 : data.length) + "]";
1088        }
1089    }
1090
1091    /**
1092     * Sub-class of RecognitionEvent specifically for sound-trigger based sound
1093     * models(non-keyphrase). Currently does not contain any additional fields.
1094     */
1095    public static class GenericRecognitionEvent extends RecognitionEvent {
1096        public GenericRecognitionEvent(int status, int soundModelHandle,
1097                boolean captureAvailable, int captureSession, int captureDelayMs,
1098                int capturePreambleMs, boolean triggerInData, AudioFormat captureFormat,
1099                byte[] data) {
1100            super(status, soundModelHandle, captureAvailable, captureSession,
1101                    captureDelayMs, capturePreambleMs, triggerInData, captureFormat,
1102                    data);
1103        }
1104
1105        public static final Parcelable.Creator<GenericRecognitionEvent> CREATOR
1106                = new Parcelable.Creator<GenericRecognitionEvent>() {
1107            public GenericRecognitionEvent createFromParcel(Parcel in) {
1108                return GenericRecognitionEvent.fromParcelForGeneric(in);
1109            }
1110
1111            public GenericRecognitionEvent[] newArray(int size) {
1112                return new GenericRecognitionEvent[size];
1113            }
1114        };
1115
1116        private static GenericRecognitionEvent fromParcelForGeneric(Parcel in) {
1117            RecognitionEvent event = RecognitionEvent.fromParcel(in);
1118            return new GenericRecognitionEvent(event.status, event.soundModelHandle,
1119                    event.captureAvailable, event.captureSession, event.captureDelayMs,
1120                    event.capturePreambleMs, event.triggerInData, event.captureFormat, event.data);
1121        }
1122
1123        @Override
1124        public boolean equals(Object obj) {
1125            if (this == obj)
1126                return true;
1127            if (obj == null)
1128                return false;
1129            if (getClass() != obj.getClass()) return false;
1130            RecognitionEvent other = (RecognitionEvent) obj;
1131            return super.equals(obj);
1132        }
1133
1134        @Override
1135        public String toString() {
1136            return "GenericRecognitionEvent ::" + super.toString();
1137        }
1138    }
1139
1140    /**
1141     *  Status codes for {@link SoundModelEvent}
1142     */
1143    /** Sound Model was updated */
1144    public static final int SOUNDMODEL_STATUS_UPDATED = 0;
1145
1146    /**
1147     *  A SoundModelEvent is provided by the
1148     *  {@link StatusListener#onSoundModelUpdate(SoundModelEvent)}
1149     *  callback when a sound model has been updated by the implementation
1150     */
1151    public static class SoundModelEvent implements Parcelable {
1152        /** Status e.g {@link #SOUNDMODEL_STATUS_UPDATED} */
1153        public final int status;
1154        /** The updated sound model handle */
1155        public final int soundModelHandle;
1156        /** New sound model data */
1157        public final byte[] data;
1158
1159        SoundModelEvent(int status, int soundModelHandle, byte[] data) {
1160            this.status = status;
1161            this.soundModelHandle = soundModelHandle;
1162            this.data = data;
1163        }
1164
1165        public static final Parcelable.Creator<SoundModelEvent> CREATOR
1166                = new Parcelable.Creator<SoundModelEvent>() {
1167            public SoundModelEvent createFromParcel(Parcel in) {
1168                return SoundModelEvent.fromParcel(in);
1169            }
1170
1171            public SoundModelEvent[] newArray(int size) {
1172                return new SoundModelEvent[size];
1173            }
1174        };
1175
1176        private static SoundModelEvent fromParcel(Parcel in) {
1177            int status = in.readInt();
1178            int soundModelHandle = in.readInt();
1179            byte[] data = in.readBlob();
1180            return new SoundModelEvent(status, soundModelHandle, data);
1181        }
1182
1183        @Override
1184        public int describeContents() {
1185            return 0;
1186        }
1187
1188        @Override
1189        public void writeToParcel(Parcel dest, int flags) {
1190            dest.writeInt(status);
1191            dest.writeInt(soundModelHandle);
1192            dest.writeBlob(data);
1193        }
1194
1195        @Override
1196        public int hashCode() {
1197            final int prime = 31;
1198            int result = 1;
1199            result = prime * result + Arrays.hashCode(data);
1200            result = prime * result + soundModelHandle;
1201            result = prime * result + status;
1202            return result;
1203        }
1204
1205        @Override
1206        public boolean equals(Object obj) {
1207            if (this == obj)
1208                return true;
1209            if (obj == null)
1210                return false;
1211            if (getClass() != obj.getClass())
1212                return false;
1213            SoundModelEvent other = (SoundModelEvent) obj;
1214            if (!Arrays.equals(data, other.data))
1215                return false;
1216            if (soundModelHandle != other.soundModelHandle)
1217                return false;
1218            if (status != other.status)
1219                return false;
1220            return true;
1221        }
1222
1223        @Override
1224        public String toString() {
1225            return "SoundModelEvent [status=" + status + ", soundModelHandle=" + soundModelHandle
1226                    + ", data=" + (data == null ? 0 : data.length) + "]";
1227        }
1228    }
1229
1230    /**
1231     *  Native service state. {@link StatusListener#onServiceStateChange(int)}
1232     */
1233    // Keep in sync with system/core/include/system/sound_trigger.h
1234    /** Sound trigger service is enabled */
1235    public static final int SERVICE_STATE_ENABLED = 0;
1236    /** Sound trigger service is disabled */
1237    public static final int SERVICE_STATE_DISABLED = 1;
1238
1239    /**
1240     * Returns a list of descriptors for all hardware modules loaded.
1241     * @param modules A ModuleProperties array where the list will be returned.
1242     * @return - {@link #STATUS_OK} in case of success
1243     *         - {@link #STATUS_ERROR} in case of unspecified error
1244     *         - {@link #STATUS_PERMISSION_DENIED} if the caller does not have system permission
1245     *         - {@link #STATUS_NO_INIT} if the native service cannot be reached
1246     *         - {@link #STATUS_BAD_VALUE} if modules is null
1247     *         - {@link #STATUS_DEAD_OBJECT} if the binder transaction to the native service fails
1248     */
1249    public static native int listModules(ArrayList <ModuleProperties> modules);
1250
1251    /**
1252     * Get an interface on a hardware module to control sound models and recognition on
1253     * this module.
1254     * @param moduleId Sound module system identifier {@link ModuleProperties#id}. mandatory.
1255     * @param listener {@link StatusListener} interface. Mandatory.
1256     * @param handler the Handler that will receive the callabcks. Can be null if default handler
1257     *                is OK.
1258     * @return a valid sound module in case of success or null in case of error.
1259     */
1260    public static SoundTriggerModule attachModule(int moduleId,
1261                                                  StatusListener listener,
1262                                                  Handler handler) {
1263        if (listener == null) {
1264            return null;
1265        }
1266        SoundTriggerModule module = new SoundTriggerModule(moduleId, listener, handler);
1267        return module;
1268    }
1269
1270    /**
1271     * Interface provided by the client application when attaching to a {@link SoundTriggerModule}
1272     * to received recognition and error notifications.
1273     */
1274    public static interface StatusListener {
1275        /**
1276         * Called when recognition succeeds of fails
1277         */
1278        public abstract void onRecognition(RecognitionEvent event);
1279
1280        /**
1281         * Called when a sound model has been updated
1282         */
1283        public abstract void onSoundModelUpdate(SoundModelEvent event);
1284
1285        /**
1286         * Called when the sound trigger native service state changes.
1287         * @param state Native service state. One of {@link SoundTrigger#SERVICE_STATE_ENABLED},
1288         * {@link SoundTrigger#SERVICE_STATE_DISABLED}
1289         */
1290        public abstract void onServiceStateChange(int state);
1291
1292        /**
1293         * Called when the sound trigger native service dies
1294         */
1295        public abstract void onServiceDied();
1296    }
1297}
1298