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