diff options
author | Sandeep Siddhartha <sansid@google.com> | 2014-07-17 16:21:54 -0700 |
---|---|---|
committer | Sandeep Siddhartha <sansid@google.com> | 2014-07-20 11:22:55 -0700 |
commit | 055897208d659e9734a82def88be4a806ff55448 (patch) | |
tree | 4540186364f0a3fc3a3675119846448215696f68 | |
parent | 6eb262c3515c927df19340b3eee8c74bc9478d16 (diff) | |
download | frameworks_base-055897208d659e9734a82def88be4a806ff55448.zip frameworks_base-055897208d659e9734a82def88be4a806ff55448.tar.gz frameworks_base-055897208d659e9734a82def88be4a806ff55448.tar.bz2 |
Move sound trigger calls to VoiceInteractionManagerService
- This ensures that any data being loaded on the DSP comes from the framework
Change-Id: Ie15f0994850ba8f298ca07c49fe0b89e066d9e2b
16 files changed, 602 insertions, 403 deletions
@@ -167,6 +167,7 @@ LOCAL_SRC_FILES += \ core/java/android/hardware/location/IGeofenceHardware.aidl \ core/java/android/hardware/location/IGeofenceHardwareCallback.aidl \ core/java/android/hardware/location/IGeofenceHardwareMonitorCallback.aidl \ + core/java/android/hardware/soundtrigger/IRecognitionStatusCallback.aidl \ core/java/android/hardware/usb/IUsbManager.aidl \ core/java/android/net/IConnectivityManager.aidl \ core/java/android/net/IEthernetManager.aidl \ diff --git a/api/current.txt b/api/current.txt index dbdb1e5..c80e2b1 100644 --- a/api/current.txt +++ b/api/current.txt @@ -27242,7 +27242,7 @@ package android.service.voice { field public static final int RECOGNITION_STATUS_NOT_REQUESTED = 2; // 0x2 field public static final int RECOGNITION_STATUS_REQUESTED = 4; // 0x4 field public static final int STATUS_ERROR = -2147483648; // 0x80000000 - field public static final int STATUS_OK = 1; // 0x1 + field public static final int STATUS_OK = 0; // 0x0 } public static abstract interface AlwaysOnHotwordDetector.Callback { diff --git a/core/java/android/hardware/soundtrigger/DspInfo.java b/core/java/android/hardware/soundtrigger/DspInfo.java deleted file mode 100644 index 517159d..0000000 --- a/core/java/android/hardware/soundtrigger/DspInfo.java +++ /dev/null @@ -1,57 +0,0 @@ -/** - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.hardware.soundtrigger; - -import java.util.UUID; - -/** - * Properties of the DSP hardware on the device. - * - * @hide - */ -public class DspInfo { - /** - * Unique voice engine Id (changes with each version). - */ - public final UUID voiceEngineId; - - /** - * Human readable voice detection engine implementor. - */ - public final String voiceEngineImplementor; - /** - * Human readable voice detection engine description. - */ - public final String voiceEngineDescription; - /** - * Human readable voice detection engine version - */ - public final int voiceEngineVersion; - /** - * Rated power consumption when detection is active. - */ - public final int powerConsumptionMw; - - public DspInfo(UUID voiceEngineId, String voiceEngineImplementor, - String voiceEngineDescription, int version, int powerConsumptionMw) { - this.voiceEngineId = voiceEngineId; - this.voiceEngineImplementor = voiceEngineImplementor; - this.voiceEngineDescription = voiceEngineDescription; - this.voiceEngineVersion = version; - this.powerConsumptionMw = powerConsumptionMw; - } -} diff --git a/core/java/android/hardware/soundtrigger/IRecognitionStatusCallback.aidl b/core/java/android/hardware/soundtrigger/IRecognitionStatusCallback.aidl new file mode 100644 index 0000000..5738909 --- /dev/null +++ b/core/java/android/hardware/soundtrigger/IRecognitionStatusCallback.aidl @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.soundtrigger; + +/** + * @hide + */ +oneway interface IRecognitionStatusCallback { + /** + * Called when the keyphrase is spoken. + * + * @param data Optional trigger audio data, if it was requested and is available. + * TODO: See if the data being passed in works well, if not use shared memory. + * This *MUST* not exceed 100K. + */ + void onDetected(in byte[] data); + /** + * Called when the detection for the associated keyphrase starts. + */ + void onDetectionStarted(); + /** + * Called when the detection for the associated keyphrase stops. + */ + void onDetectionStopped(); +}
\ No newline at end of file diff --git a/core/java/android/hardware/soundtrigger/Keyphrase.aidl b/core/java/android/hardware/soundtrigger/Keyphrase.aidl deleted file mode 100644 index d9853a7..0000000 --- a/core/java/android/hardware/soundtrigger/Keyphrase.aidl +++ /dev/null @@ -1,4 +0,0 @@ -package android.hardware.soundtrigger; - -// @hide -parcelable Keyphrase;
\ No newline at end of file diff --git a/core/java/android/hardware/soundtrigger/Keyphrase.java b/core/java/android/hardware/soundtrigger/Keyphrase.java deleted file mode 100644 index 51311bb..0000000 --- a/core/java/android/hardware/soundtrigger/Keyphrase.java +++ /dev/null @@ -1,135 +0,0 @@ -/** - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.hardware.soundtrigger; - -import android.os.Parcel; -import android.os.Parcelable; - -/** - * A Voice Keyphrase. - * - * @hide - */ -public class Keyphrase implements Parcelable { - /** A unique identifier for this keyphrase */ - public final int id; - /** A hint text to display corresponding to this keyphrase, e.g. "Hello There". */ - public final String hintText; - /** The locale of interest when using this Keyphrase. */ - public final String locale; - /** The various recognition modes supported by this keyphrase */ - public final int recognitionModeFlags; - /** The users associated with this keyphrase */ - public final int[] users; - - public static final Parcelable.Creator<Keyphrase> CREATOR - = new Parcelable.Creator<Keyphrase>() { - public Keyphrase createFromParcel(Parcel in) { - return Keyphrase.fromParcel(in); - } - - public Keyphrase[] newArray(int size) { - return new Keyphrase[size]; - } - }; - - private static Keyphrase fromParcel(Parcel in) { - int id = in.readInt(); - String hintText = in.readString(); - String locale = in.readString(); - int recognitionModeFlags = in.readInt(); - int numUsers = in.readInt(); - int[] users = null; - if (numUsers > 0) { - users = new int[numUsers]; - in.readIntArray(users); - } - return new Keyphrase(id, hintText, locale, recognitionModeFlags, users); - } - - public Keyphrase(int id, String hintText, String locale, int recognitionModeFlags, - int[] users) { - this.id = id; - this.hintText = hintText; - this.locale = locale; - this.recognitionModeFlags = recognitionModeFlags; - this.users = users; - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeInt(id); - dest.writeString(hintText); - dest.writeString(locale); - dest.writeInt(recognitionModeFlags); - if (users != null) { - dest.writeInt(users.length); - dest.writeIntArray(users); - } else { - dest.writeInt(0); - } - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + ((hintText == null) ? 0 : hintText.hashCode()); - result = prime * result + id; - result = prime * result + ((locale == null) ? 0 : locale.hashCode()); - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - Keyphrase other = (Keyphrase) obj; - if (hintText == null) { - if (other.hintText != null) - return false; - } else if (!hintText.equals(other.hintText)) - return false; - if (id != other.id) - return false; - if (locale == null) { - if (other.locale != null) - return false; - } else if (!locale.equals(other.locale)) - return false; - return true; - } - - @Override - public String toString() { - return "Keyphrase[id=" + id + ", text=" + hintText + ", locale=" + locale - + ", recognitionModes=" + recognitionModeFlags + "]"; - } - - protected SoundTrigger.Keyphrase convertToSoundTriggerKeyphrase() { - return new SoundTrigger.Keyphrase(id, recognitionModeFlags, locale, hintText, users); - } -} diff --git a/core/java/android/hardware/soundtrigger/KeyphraseSoundModel.aidl b/core/java/android/hardware/soundtrigger/KeyphraseSoundModel.aidl deleted file mode 100644 index 39b33cc..0000000 --- a/core/java/android/hardware/soundtrigger/KeyphraseSoundModel.aidl +++ /dev/null @@ -1,4 +0,0 @@ -package android.hardware.soundtrigger; - -// @hide -parcelable KeyphraseSoundModel;
\ No newline at end of file diff --git a/core/java/android/hardware/soundtrigger/KeyphraseSoundModel.java b/core/java/android/hardware/soundtrigger/KeyphraseSoundModel.java deleted file mode 100644 index a5ab0d2..0000000 --- a/core/java/android/hardware/soundtrigger/KeyphraseSoundModel.java +++ /dev/null @@ -1,79 +0,0 @@ -package android.hardware.soundtrigger; - -import android.os.Parcel; -import android.os.Parcelable; - -import java.util.UUID; - -/** - * A KeyphraseSoundModel is a sound model capable of detecting voice keyphrases. - * It contains data needed by the hardware to detect a given number of key phrases - * and the list of corresponding {@link Keyphrase}s. - * - * @hide - */ -public class KeyphraseSoundModel implements Parcelable { - - /** Key phrases in this sound model */ - public final Keyphrase[] keyphrases; - public final byte[] data; - public final UUID uuid; - - public static final Parcelable.Creator<KeyphraseSoundModel> CREATOR - = new Parcelable.Creator<KeyphraseSoundModel>() { - public KeyphraseSoundModel createFromParcel(Parcel in) { - return KeyphraseSoundModel.fromParcel(in); - } - - public KeyphraseSoundModel[] newArray(int size) { - return new KeyphraseSoundModel[size]; - } - }; - - public KeyphraseSoundModel(UUID uuid, byte[] data,Keyphrase[] keyPhrases) { - this.uuid = uuid; - this.data = data; - this.keyphrases = keyPhrases; - } - - private static KeyphraseSoundModel fromParcel(Parcel in) { - UUID uuid = UUID.fromString(in.readString()); - int dataLength = in.readInt(); - byte[] data = null; - if (dataLength > 0) { - data = new byte[in.readInt()]; - in.readByteArray(data); - } - Keyphrase[] keyphrases = - (Keyphrase[]) in.readParcelableArray(Keyphrase.class.getClassLoader()); - return new KeyphraseSoundModel(uuid, data, keyphrases); - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeString(uuid.toString()); - if (data != null) { - dest.writeInt(data.length); - dest.writeByteArray(data); - } else { - dest.writeInt(0); - } - dest.writeParcelableArray(keyphrases, 0); - } - - public SoundTrigger.KeyphraseSoundModel convertToSoundTriggerKeyphraseSoundModel() { - SoundTrigger.Keyphrase[] stKeyphrases = null; - if (keyphrases != null) { - stKeyphrases = new SoundTrigger.Keyphrase[keyphrases.length]; - for (int i = 0; i < keyphrases.length; i++) { - stKeyphrases[i] = keyphrases[i].convertToSoundTriggerKeyphrase(); - } - } - return new SoundTrigger.KeyphraseSoundModel(uuid, data, stKeyphrases); - } -} diff --git a/core/java/android/hardware/soundtrigger/SoundTrigger.aidl b/core/java/android/hardware/soundtrigger/SoundTrigger.aidl new file mode 100644 index 0000000..837691a --- /dev/null +++ b/core/java/android/hardware/soundtrigger/SoundTrigger.aidl @@ -0,0 +1,24 @@ +/** + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.soundtrigger; + +parcelable SoundTrigger.ConfidenceLevel; +parcelable SoundTrigger.Keyphrase; +parcelable SoundTrigger.KeyphraseRecognitionExtra; +parcelable SoundTrigger.KeyphraseSoundModel; +parcelable SoundTrigger.ModuleProperties; +parcelable SoundTrigger.RecognitionConfig;
\ No newline at end of file diff --git a/core/java/android/hardware/soundtrigger/SoundTrigger.java b/core/java/android/hardware/soundtrigger/SoundTrigger.java index 1f48a92..7b0a678 100644 --- a/core/java/android/hardware/soundtrigger/SoundTrigger.java +++ b/core/java/android/hardware/soundtrigger/SoundTrigger.java @@ -17,8 +17,11 @@ package android.hardware.soundtrigger; import android.os.Handler; +import android.os.Parcel; +import android.os.Parcelable; import java.util.ArrayList; +import java.util.Arrays; import java.util.UUID; /** @@ -43,7 +46,7 @@ public class SoundTrigger { * ID used to target any API call to this paricular module. Module * properties are returned by listModules() method. ****************************************************************************/ - public static class ModuleProperties { + public static class ModuleProperties implements Parcelable { /** Unique module ID provided by the native service */ public final int id; @@ -102,6 +105,70 @@ public class SoundTrigger { this.supportsConcurrentCapture = supportsConcurrentCapture; this.powerConsumptionMw = powerConsumptionMw; } + + public static final Parcelable.Creator<ModuleProperties> CREATOR + = new Parcelable.Creator<ModuleProperties>() { + public ModuleProperties createFromParcel(Parcel in) { + return ModuleProperties.fromParcel(in); + } + + public ModuleProperties[] newArray(int size) { + return new ModuleProperties[size]; + } + }; + + private static ModuleProperties fromParcel(Parcel in) { + int id = in.readInt(); + String implementor = in.readString(); + String description = in.readString(); + String uuid = in.readString(); + int version = in.readInt(); + int maxSoundModels = in.readInt(); + int maxKeyphrases = in.readInt(); + int maxUsers = in.readInt(); + int recognitionModes = in.readInt(); + boolean supportsCaptureTransition = in.readByte() == 1; + int maxBufferMs = in.readInt(); + boolean supportsConcurrentCapture = in.readByte() == 1; + int powerConsumptionMw = in.readInt(); + return new ModuleProperties(id, implementor, description, uuid, version, + maxSoundModels, maxKeyphrases, maxUsers, recognitionModes, + supportsCaptureTransition, maxBufferMs, supportsConcurrentCapture, + powerConsumptionMw); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(id); + dest.writeString(implementor); + dest.writeString(description); + dest.writeString(uuid.toString()); + dest.writeInt(version); + dest.writeInt(maxSoundModels); + dest.writeInt(maxKeyphrases); + dest.writeInt(maxUsers); + dest.writeInt(recognitionModes); + dest.writeByte((byte) (supportsCaptureTransition ? 1 : 0)); + dest.writeInt(maxBufferMs); + dest.writeByte((byte) (supportsConcurrentCapture ? 1 : 0)); + dest.writeInt(powerConsumptionMw); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public String toString() { + return "ModuleProperties [id=" + id + ", implementor=" + implementor + ", description=" + + description + ", uuid=" + uuid + ", version=" + version + ", maxSoundModels=" + + maxSoundModels + ", maxKeyphrases=" + maxKeyphrases + ", maxUsers=" + + maxUsers + ", recognitionModes=" + recognitionModes + + ", supportsCaptureTransition=" + supportsCaptureTransition + ", maxBufferMs=" + + maxBufferMs + ", supportsConcurrentCapture=" + supportsConcurrentCapture + + ", powerConsumptionMw=" + powerConsumptionMw + "]"; + } } /***************************************************************************** @@ -137,7 +204,7 @@ public class SoundTrigger { * A Keyphrase describes a key phrase that can be detected by a * {@link KeyphraseSoundModel} ****************************************************************************/ - public static class Keyphrase { + public static class Keyphrase implements Parcelable { /** Unique identifier for this keyphrase */ public final int id; @@ -161,6 +228,96 @@ public class SoundTrigger { this.text = text; this.users = users; } + + public static final Parcelable.Creator<Keyphrase> CREATOR + = new Parcelable.Creator<Keyphrase>() { + public Keyphrase createFromParcel(Parcel in) { + return Keyphrase.fromParcel(in); + } + + public Keyphrase[] newArray(int size) { + return new Keyphrase[size]; + } + }; + + private static Keyphrase fromParcel(Parcel in) { + int id = in.readInt(); + int recognitionModes = in.readInt(); + String locale = in.readString(); + String text = in.readString(); + int[] users = null; + int numUsers = in.readInt(); + if (numUsers > 0) { + users = new int[numUsers]; + in.readIntArray(users); + } + return new Keyphrase(id, recognitionModes, locale, text, users); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(id); + dest.writeInt(recognitionModes); + dest.writeString(locale); + dest.writeString(text); + if (users != null) { + dest.writeInt(users.length); + dest.writeIntArray(users); + } else { + dest.writeInt(0); + } + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((text == null) ? 0 : text.hashCode()); + result = prime * result + id; + result = prime * result + ((locale == null) ? 0 : locale.hashCode()); + result = prime * result + recognitionModes; + result = prime * result + Arrays.hashCode(users); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Keyphrase other = (Keyphrase) obj; + if (text == null) { + if (other.text != null) + return false; + } else if (!text.equals(other.text)) + return false; + if (id != other.id) + return false; + if (locale == null) { + if (other.locale != null) + return false; + } else if (!locale.equals(other.locale)) + return false; + if (recognitionModes != other.recognitionModes) + return false; + if (!Arrays.equals(users, other.users)) + return false; + return true; + } + + @Override + public String toString() { + return "Keyphrase [id=" + id + ", recognitionModes=" + recognitionModes + ", locale=" + + locale + ", text=" + text + ", users=" + Arrays.toString(users) + "]"; + } } /***************************************************************************** @@ -168,7 +325,7 @@ public class SoundTrigger { * It contains data needed by the hardware to detect a certain number of key phrases * and the list of corresponding {@link Keyphrase} descriptors. ****************************************************************************/ - public static class KeyphraseSoundModel extends SoundModel { + public static class KeyphraseSoundModel extends SoundModel implements Parcelable { /** Key phrases in this sound model */ public final Keyphrase[] keyphrases; // keyword phrases in model @@ -176,6 +333,46 @@ public class SoundTrigger { super(id, TYPE_KEYPHRASE, data); this.keyphrases = keyphrases; } + + public static final Parcelable.Creator<KeyphraseSoundModel> CREATOR + = new Parcelable.Creator<KeyphraseSoundModel>() { + public KeyphraseSoundModel createFromParcel(Parcel in) { + return KeyphraseSoundModel.fromParcel(in); + } + + public KeyphraseSoundModel[] newArray(int size) { + return new KeyphraseSoundModel[size]; + } + }; + + private static KeyphraseSoundModel fromParcel(Parcel in) { + UUID uuid = UUID.fromString(in.readString()); + byte[] data = null; + int dataLength = in.readInt(); + if (dataLength > 0) { + data = new byte[dataLength]; + in.readByteArray(data); + } + Keyphrase[] keyphrases = in.createTypedArray(Keyphrase.CREATOR); + return new KeyphraseSoundModel(uuid, data, keyphrases); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(uuid.toString()); + if (data != null) { + dest.writeInt(data.length); + dest.writeByteArray(data); + } else { + dest.writeInt(0); + } + dest.writeTypedArray(keyphrases, 0); + } } /** @@ -239,7 +436,7 @@ public class SoundTrigger { * {@link SoundTriggerModule#startRecognition(int, RecognitionConfig)} to configure the * recognition request. */ - public static class RecognitionConfig { + public static class RecognitionConfig implements Parcelable { /** True if the DSP should capture the trigger sound and make it available for further * capture. */ public final boolean captureRequested; @@ -256,6 +453,47 @@ public class SoundTrigger { this.keyphrases = keyphrases; this.data = data; } + + public static final Parcelable.Creator<RecognitionConfig> CREATOR + = new Parcelable.Creator<RecognitionConfig>() { + public RecognitionConfig createFromParcel(Parcel in) { + return RecognitionConfig.fromParcel(in); + } + + public RecognitionConfig[] newArray(int size) { + return new RecognitionConfig[size]; + } + }; + + private static RecognitionConfig fromParcel(Parcel in) { + boolean captureRequested = in.readByte() == 1; + KeyphraseRecognitionExtra[] keyphrases = + in.createTypedArray(KeyphraseRecognitionExtra.CREATOR); + byte[] data = null; + int dataLength = in.readInt(); + if (dataLength > 0) { + data = new byte[dataLength]; + in.readByteArray(data); + } + return new RecognitionConfig(captureRequested, keyphrases, data); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeByte((byte) (captureRequested ? 1 : 0)); + dest.writeTypedArray(keyphrases, 0); + if (data != null) { + dest.writeInt(data.length); + dest.writeByteArray(data); + } else { + dest.writeInt(0); + } + } + + @Override + public int describeContents() { + return 0; + } } /** @@ -266,7 +504,7 @@ public class SoundTrigger { * should trigger a recognition. * - The user ID is derived from the system ID {@link android.os.UserHandle#getIdentifier()}. */ - public static class ConfidenceLevel { + public static class ConfidenceLevel implements Parcelable { public final int userId; public final int confidenceLevel; @@ -274,14 +512,42 @@ public class SoundTrigger { this.userId = userId; this.confidenceLevel = confidenceLevel; } + + public static final Parcelable.Creator<ConfidenceLevel> CREATOR + = new Parcelable.Creator<ConfidenceLevel>() { + public ConfidenceLevel createFromParcel(Parcel in) { + return ConfidenceLevel.fromParcel(in); + } + + public ConfidenceLevel[] newArray(int size) { + return new ConfidenceLevel[size]; + } + }; + + private static ConfidenceLevel fromParcel(Parcel in) { + int userId = in.readInt(); + int confidenceLevel = in.readInt(); + return new ConfidenceLevel(userId, confidenceLevel); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(userId); + dest.writeInt(confidenceLevel); + } + + @Override + public int describeContents() { + return 0; + } } /** * Additional data conveyed by a {@link KeyphraseRecognitionEvent} * for a key phrase detection. */ - public static class KeyphraseRecognitionExtra { - /** The keyphrse ID */ + public static class KeyphraseRecognitionExtra implements Parcelable { + /** The keyphrase ID */ public final int id; /** Recognition modes matched for this event */ @@ -297,6 +563,36 @@ public class SoundTrigger { this.recognitionModes = recognitionModes; this.confidenceLevels = confidenceLevels; } + + public static final Parcelable.Creator<KeyphraseRecognitionExtra> CREATOR + = new Parcelable.Creator<KeyphraseRecognitionExtra>() { + public KeyphraseRecognitionExtra createFromParcel(Parcel in) { + return KeyphraseRecognitionExtra.fromParcel(in); + } + + public KeyphraseRecognitionExtra[] newArray(int size) { + return new KeyphraseRecognitionExtra[size]; + } + }; + + private static KeyphraseRecognitionExtra fromParcel(Parcel in) { + int id = in.readInt(); + int recognitionModes = in.readInt(); + ConfidenceLevel[] confidenceLevels = in.createTypedArray(ConfidenceLevel.CREATOR); + return new KeyphraseRecognitionExtra(id, recognitionModes, confidenceLevels); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(id); + dest.writeInt(recognitionModes); + dest.writeTypedArray(confidenceLevels, 0); + } + + @Override + public int describeContents() { + return 0; + } } /** diff --git a/core/java/android/service/voice/AlwaysOnHotwordDetector.java b/core/java/android/service/voice/AlwaysOnHotwordDetector.java index 048fda1..c4ed8c5 100644 --- a/core/java/android/service/voice/AlwaysOnHotwordDetector.java +++ b/core/java/android/service/voice/AlwaysOnHotwordDetector.java @@ -17,14 +17,15 @@ package android.service.voice; import android.content.Intent; -import android.hardware.soundtrigger.Keyphrase; +import android.hardware.soundtrigger.IRecognitionStatusCallback; import android.hardware.soundtrigger.KeyphraseEnrollmentInfo; import android.hardware.soundtrigger.KeyphraseMetadata; -import android.hardware.soundtrigger.KeyphraseSoundModel; import android.hardware.soundtrigger.SoundTrigger; import android.hardware.soundtrigger.SoundTrigger.ConfidenceLevel; +import android.hardware.soundtrigger.SoundTrigger.Keyphrase; import android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra; -import android.hardware.soundtrigger.SoundTriggerHelper; +import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel; +import android.hardware.soundtrigger.SoundTrigger.ModuleProperties; import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig; import android.os.RemoteException; import android.util.Slog; @@ -68,8 +69,8 @@ public class AlwaysOnHotwordDetector { /** * Return codes for {@link #startRecognition(int)}, {@link #stopRecognition()} */ - public static final int STATUS_ERROR = Integer.MIN_VALUE; - public static final int STATUS_OK = 1; + public static final int STATUS_ERROR = SoundTrigger.STATUS_ERROR; + public static final int STATUS_OK = SoundTrigger.STATUS_OK; //---- Keyphrase recognition status ----// /** Indicates that recognition is not available. */ @@ -117,11 +118,10 @@ public class AlwaysOnHotwordDetector { */ private final KeyphraseSoundModel mEnrolledSoundModel; private final KeyphraseEnrollmentInfo mKeyphraseEnrollmentInfo; - private final SoundTriggerHelper mSoundTriggerHelper; - private final SoundTriggerHelper.Listener mListener; private final int mAvailability; private final IVoiceInteractionService mVoiceInteractionService; private final IVoiceInteractionManagerService mModelManagementService; + private final SoundTriggerListener mInternalCallback; private int mRecognitionState; @@ -157,15 +157,13 @@ public class AlwaysOnHotwordDetector { */ public AlwaysOnHotwordDetector(String text, String locale, Callback callback, KeyphraseEnrollmentInfo keyphraseEnrollmentInfo, - SoundTriggerHelper soundTriggerHelper, IVoiceInteractionService voiceInteractionService, IVoiceInteractionManagerService modelManagementService) { mText = text; mLocale = locale; mKeyphraseEnrollmentInfo = keyphraseEnrollmentInfo; mKeyphraseMetadata = mKeyphraseEnrollmentInfo.getKeyphraseMetadata(text, locale); - mListener = new SoundTriggerListener(callback); - mSoundTriggerHelper = soundTriggerHelper; + mInternalCallback = new SoundTriggerListener(callback); mVoiceInteractionService = voiceInteractionService; mModelManagementService = modelManagementService; if (mKeyphraseMetadata != null) { @@ -225,7 +223,7 @@ public class AlwaysOnHotwordDetector { * @param recognitionFlags The flags to control the recognition properties. * The allowed flags are {@link #RECOGNITION_FLAG_NONE} and * {@link #RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO}. - * @return One of {@link #STATUS_ERROR} or {@link #STATUS_OK}. + * @return {@link #STATUS_OK} if the call succeeds, an error code otherwise. * @throws UnsupportedOperationException if the recognition isn't supported. * Callers should check the availability by calling {@link #getAvailability()} * before calling this method to avoid this exception. @@ -245,22 +243,25 @@ public class AlwaysOnHotwordDetector { mKeyphraseMetadata.recognitionModeFlags, new ConfidenceLevel[0]); boolean captureTriggerAudio = (recognitionFlags & RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO) != 0; - int code = mSoundTriggerHelper.startRecognition(mKeyphraseMetadata.id, - mEnrolledSoundModel.convertToSoundTriggerKeyphraseSoundModel(), mListener, - new RecognitionConfig( - captureTriggerAudio, recognitionExtra,null /* additional data */)); - if (code != SoundTriggerHelper.STATUS_OK) { + int code = STATUS_ERROR; + try { + code = mModelManagementService.startRecognition(mVoiceInteractionService, + mKeyphraseMetadata.id, mEnrolledSoundModel, mInternalCallback, + new RecognitionConfig( + captureTriggerAudio, recognitionExtra, null /* additional data */)); + } catch (RemoteException e) { + Slog.w(TAG, "RemoteException in startRecognition!"); + } + if (code != STATUS_OK) { Slog.w(TAG, "startRecognition() failed with error code " + code); - return STATUS_ERROR; - } else { - return STATUS_OK; } + return code; } /** * Stops recognition for the associated keyphrase. * - * @return One of {@link #STATUS_ERROR} or {@link #STATUS_OK}. + * @return {@link #STATUS_OK} if the call succeeds, an error code otherwise. * @throws UnsupportedOperationException if the recognition isn't supported. * Callers should check the availability by calling {@link #getAvailability()} * before calling this method to avoid this exception. @@ -272,14 +273,18 @@ public class AlwaysOnHotwordDetector { } mRecognitionState &= ~RECOGNITION_STATUS_NOT_REQUESTED; - int code = mSoundTriggerHelper.stopRecognition(mKeyphraseMetadata.id, mListener); + int code = STATUS_ERROR; + try { + code = mModelManagementService.stopRecognition( + mVoiceInteractionService, mKeyphraseMetadata.id, mInternalCallback); + } catch (RemoteException e) { + Slog.w(TAG, "RemoteException in stopRecognition!"); + } - if (code != SoundTriggerHelper.STATUS_OK) { + if (code != STATUS_OK) { Slog.w(TAG, "stopRecognition() failed with error code " + code); - return STATUS_ERROR; - } else { - return STATUS_OK; } + return code; } /** @@ -309,8 +314,15 @@ public class AlwaysOnHotwordDetector { } private int internalGetAvailability() { + ModuleProperties dspModuleProperties = null; + try { + dspModuleProperties = + mModelManagementService.getDspModuleProperties(mVoiceInteractionService); + } catch (RemoteException e) { + Slog.w(TAG, "RemoteException in getDspProperties!"); + } // No DSP available - if (mSoundTriggerHelper.dspInfo == null) { + if (dspModuleProperties == null) { mRecognitionState = RECOGNITION_STATUS_NOT_AVAILABLE; return KEYPHRASE_HARDWARE_UNAVAILABLE; } @@ -359,7 +371,7 @@ public class AlwaysOnHotwordDetector { } /** @hide */ - static final class SoundTriggerListener implements SoundTriggerHelper.Listener { + static final class SoundTriggerListener extends IRecognitionStatusCallback.Stub { private final Callback mCallback; public SoundTriggerListener(Callback callback) { @@ -367,20 +379,21 @@ public class AlwaysOnHotwordDetector { } @Override - public void onKeyphraseSpoken(byte[] data) { + public void onDetected(byte[] data) { Slog.i(TAG, "onKeyphraseSpoken"); mCallback.onDetected(data); } @Override - public void onListeningStateChanged(int state) { - Slog.i(TAG, "onListeningStateChanged: state=" + state); - // TODO: Set/unset the RECOGNITION_STATUS_ACTIVE flag here. - if (state == SoundTriggerHelper.STATE_STARTED) { - mCallback.onDetectionStarted(); - } else if (state == SoundTriggerHelper.STATE_STOPPED) { - mCallback.onDetectionStopped(); - } + public void onDetectionStarted() { + // TODO: Set the RECOGNITION_STATUS_ACTIVE flag here. + mCallback.onDetectionStarted(); + } + + @Override + public void onDetectionStopped() { + // TODO: Unset the RECOGNITION_STATUS_ACTIVE flag here. + mCallback.onDetectionStopped(); } } } diff --git a/core/java/android/service/voice/VoiceInteractionService.java b/core/java/android/service/voice/VoiceInteractionService.java index 1f5d327..982f43d 100644 --- a/core/java/android/service/voice/VoiceInteractionService.java +++ b/core/java/android/service/voice/VoiceInteractionService.java @@ -22,7 +22,6 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.hardware.soundtrigger.KeyphraseEnrollmentInfo; -import android.hardware.soundtrigger.SoundTriggerHelper; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; @@ -78,7 +77,6 @@ public class VoiceInteractionService extends Service { IVoiceInteractionManagerService mSystemService; private KeyphraseEnrollmentInfo mKeyphraseEnrollmentInfo; - private SoundTriggerHelper mSoundTriggerHelper; static final int MSG_READY = 1; @@ -150,7 +148,6 @@ public class VoiceInteractionService extends Service { mSystemService = IVoiceInteractionManagerService.Stub.asInterface( ServiceManager.getService(Context.VOICE_INTERACTION_MANAGER_SERVICE)); mKeyphraseEnrollmentInfo = new KeyphraseEnrollmentInfo(getPackageManager()); - mSoundTriggerHelper = new SoundTriggerHelper(); } /** @@ -168,7 +165,7 @@ public class VoiceInteractionService extends Service { // TODO: Cache instances and return the same one instead of creating a new interactor // for the same keyphrase/locale combination. return new AlwaysOnHotwordDetector(keyphrase, locale, callback, - mKeyphraseEnrollmentInfo, mSoundTriggerHelper, mInterface, mSystemService); + mKeyphraseEnrollmentInfo, mInterface, mSystemService); } /** diff --git a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl index c78f770..7d5abd2 100644 --- a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl +++ b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl @@ -20,7 +20,8 @@ import android.content.Intent; import android.os.Bundle; import com.android.internal.app.IVoiceInteractor; -import android.hardware.soundtrigger.KeyphraseSoundModel; +import android.hardware.soundtrigger.IRecognitionStatusCallback; +import android.hardware.soundtrigger.SoundTrigger; import android.service.voice.IVoiceInteractionService; import android.service.voice.IVoiceInteractionSession; @@ -37,9 +38,26 @@ interface IVoiceInteractionManagerService { * * @param service The current voice interaction service. */ - List<KeyphraseSoundModel> listRegisteredKeyphraseSoundModels(in IVoiceInteractionService service); + List<SoundTrigger.KeyphraseSoundModel> listRegisteredKeyphraseSoundModels( + in IVoiceInteractionService service); /** * Updates the given keyphrase sound model. Adds the model if it doesn't exist currently. */ - int updateKeyphraseSoundModel(in KeyphraseSoundModel model); + int updateKeyphraseSoundModel(in SoundTrigger.KeyphraseSoundModel model); + + /** + * Gets the properties of the DSP hardware on this device, null if not present. + */ + SoundTrigger.ModuleProperties getDspModuleProperties(in IVoiceInteractionService service); + /** + * Starts a recognition for the given keyphrase. + */ + int startRecognition(in IVoiceInteractionService service, int keyphraseId, + in SoundTrigger.KeyphraseSoundModel soundModel, in IRecognitionStatusCallback callback, + in SoundTrigger.RecognitionConfig recognitionConfig); + /** + * Stops a recognition for the given keyphrase. + */ + int stopRecognition(in IVoiceInteractionService service, int keyphraseId, + in IRecognitionStatusCallback callback); } diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java index 27bec9f..eed2d44 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java @@ -22,8 +22,9 @@ import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import android.hardware.soundtrigger.SoundTrigger; -import android.hardware.soundtrigger.Keyphrase; -import android.hardware.soundtrigger.KeyphraseSoundModel; +import android.hardware.soundtrigger.SoundTrigger.Keyphrase; +import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel; +import android.text.TextUtils; import android.util.Slog; import java.util.ArrayList; @@ -39,7 +40,7 @@ public class DatabaseHelper extends SQLiteOpenHelper { static final String TAG = "SoundModelDBHelper"; private static final String NAME = "sound_model.db"; - private static final int VERSION = 1; + private static final int VERSION = 2; public static interface KeyphraseContract { public static final String TABLE = "keyphrase"; @@ -63,7 +64,7 @@ public class DatabaseHelper extends SQLiteOpenHelper { + KeyphraseContract.TABLE + "(" + KeyphraseContract.KEY_ID + " INTEGER PRIMARY KEY," + KeyphraseContract.KEY_RECOGNITION_MODES + " INTEGER," - + KeyphraseContract.KEY_USERS + " INTEGER," + + KeyphraseContract.KEY_USERS + " TEXT," + KeyphraseContract.KEY_SOUND_MODEL_ID + " TEXT," + KeyphraseContract.KEY_LOCALE + " TEXT," + KeyphraseContract.KEY_HINT_TEXT + " TEXT" + ")"; @@ -119,10 +120,11 @@ public class DatabaseHelper extends SQLiteOpenHelper { private boolean addOrUpdateKeyphrase(SQLiteDatabase db, UUID modelId, Keyphrase keyphrase) { ContentValues values = new ContentValues(); values.put(KeyphraseContract.KEY_ID, keyphrase.id); - values.put(KeyphraseContract.KEY_RECOGNITION_MODES, keyphrase.recognitionModeFlags); + values.put(KeyphraseContract.KEY_RECOGNITION_MODES, keyphrase.recognitionModes); values.put(KeyphraseContract.KEY_SOUND_MODEL_ID, modelId.toString()); - values.put(KeyphraseContract.KEY_HINT_TEXT, keyphrase.hintText); + values.put(KeyphraseContract.KEY_HINT_TEXT, keyphrase.text); values.put(KeyphraseContract.KEY_LOCALE, keyphrase.locale); + values.put(KeyphraseContract.KEY_USERS, getCommaSeparatedString(keyphrase.users)); if (db.insertWithOnConflict( KeyphraseContract.TABLE, null, values, SQLiteDatabase.CONFLICT_REPLACE) != -1) { return true; @@ -193,11 +195,12 @@ public class DatabaseHelper extends SQLiteOpenHelper { do { int id = c.getInt(c.getColumnIndex(KeyphraseContract.KEY_ID)); int modes = c.getInt(c.getColumnIndex(KeyphraseContract.KEY_RECOGNITION_MODES)); - int[] users = {c.getInt(c.getColumnIndex(KeyphraseContract.KEY_USERS))}; + int[] users = getArrayForCommaSeparatedString( + c.getString(c.getColumnIndex(KeyphraseContract.KEY_USERS))); String locale = c.getString(c.getColumnIndex(KeyphraseContract.KEY_LOCALE)); String hintText = c.getString(c.getColumnIndex(KeyphraseContract.KEY_HINT_TEXT)); - keyphrases.add(new Keyphrase(id, hintText, locale, modes, users)); + keyphrases.add(new Keyphrase(id, modes, locale, hintText, users)); } while (c.moveToNext()); } Keyphrase[] keyphraseArr = new Keyphrase[keyphrases.size()]; @@ -205,4 +208,29 @@ public class DatabaseHelper extends SQLiteOpenHelper { c.close(); return keyphraseArr; } + + + private String getCommaSeparatedString(int[] users) { + if (users == null || users.length == 0) { + return ""; + } + String csv = ""; + for (int user : users) { + csv += String.valueOf(user); + csv += ","; + } + return csv.substring(0, csv.length() - 1); + } + + private int[] getArrayForCommaSeparatedString(String text) { + if (TextUtils.isEmpty(text)) { + return null; + } + String[] usersStr = text.split(","); + int[] users = new int[usersStr.length]; + for (int i = 0; i < usersStr.length; i++) { + users[i] = Integer.valueOf(usersStr[i]); + } + return users; + } } diff --git a/core/java/android/hardware/soundtrigger/SoundTriggerHelper.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/SoundTriggerHelper.java index 431d550..6842f7d 100644 --- a/core/java/android/hardware/soundtrigger/SoundTriggerHelper.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/SoundTriggerHelper.java @@ -14,11 +14,17 @@ * limitations under the License. */ -package android.hardware.soundtrigger; +package com.android.server.voiceinteraction; +import android.hardware.soundtrigger.IRecognitionStatusCallback; +import android.hardware.soundtrigger.SoundTrigger; +import android.hardware.soundtrigger.SoundTrigger.Keyphrase; +import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel; import android.hardware.soundtrigger.SoundTrigger.ModuleProperties; import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig; import android.hardware.soundtrigger.SoundTrigger.RecognitionEvent; +import android.hardware.soundtrigger.SoundTriggerModule; +import android.os.RemoteException; import android.util.Slog; import android.util.SparseArray; @@ -36,72 +42,45 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener { static final int TEMP_KEYPHRASE_ID = 1; /** - * Return codes for {@link #startRecognition(Keyphrase)}, {@link #stopRecognition(Keyphrase)} - * Note: Keep in sync with AlwaysOnKeyphraseInteractor.java + * Return codes for {@link #startRecognition(int, KeyphraseSoundModel, + * IRecognitionStatusCallback, RecognitionConfig)}, + * {@link #stopRecognition(int, IRecognitionStatusCallback)} */ - public static final int STATUS_ERROR = Integer.MIN_VALUE; - public static final int STATUS_OK = 1; - - /** - * States for {@link Listener#onListeningStateChanged(int, int)}. - */ - public static final int STATE_STOPPED = 0; - public static final int STATE_STARTED = 1; + public static final int STATUS_ERROR = SoundTrigger.STATUS_ERROR; + public static final int STATUS_OK = SoundTrigger.STATUS_OK; private static final int INVALID_SOUND_MODEL_HANDLE = -1; /** The {@link DspInfo} for the system, or null if none exists. */ - public final DspInfo dspInfo; + final ModuleProperties moduleProperties; /** The properties for the DSP module */ - private final ModuleProperties mModuleProperties; private final SoundTriggerModule mModule; - private final SparseArray<Listener> mActiveListeners; + // Use a RemoteCallbackList here? + private final SparseArray<IRecognitionStatusCallback> mActiveListeners; private int mCurrentSoundModelHandle = INVALID_SOUND_MODEL_HANDLE; - /** - * The callback for sound trigger events. - */ - public interface Listener { - /** - * Called when the given keyphrase is spoken. - * - * @param data The captured audio, may be null. - */ - void onKeyphraseSpoken(byte[] data); - - /** - * Called when the listening state for the given keyphrase changes. - * @param state Indicates the current state. - */ - void onListeningStateChanged(int state); - } - - public SoundTriggerHelper() { + SoundTriggerHelper() { ArrayList <ModuleProperties> modules = new ArrayList<>(); int status = SoundTrigger.listModules(modules); mActiveListeners = new SparseArray<>(1); if (status != SoundTrigger.STATUS_OK || modules.size() == 0) { Slog.w(TAG, "listModules status=" + status + ", # of modules=" + modules.size()); - dspInfo = null; - mModuleProperties = null; + moduleProperties = null; mModule = null; } else { // TODO: Figure out how to determine which module corresponds to the DSP hardware. - mModuleProperties = modules.get(0); - dspInfo = new DspInfo(mModuleProperties.uuid, mModuleProperties.implementor, - mModuleProperties.description, mModuleProperties.version, - mModuleProperties.powerConsumptionMw); - mModule = SoundTrigger.attachModule(mModuleProperties.id, this, null); + moduleProperties = modules.get(0); + mModule = SoundTrigger.attachModule(moduleProperties.id, this, null); } } /** * @return True, if a recognition for the given {@link Keyphrase} is active. */ - public synchronized boolean isKeyphraseActive(Keyphrase keyphrase) { + synchronized boolean isKeyphraseActive(Keyphrase keyphrase) { if (keyphrase == null) { Slog.w(TAG, "isKeyphraseActive requires a non-null keyphrase"); return false; @@ -110,7 +89,7 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener { } /** - * Starts recognition for the given {@link Keyphrase}. + * Starts recognition for the given keyphraseId. * * @param keyphraseId The identifier of the keyphrase for which * the recognition is to be started. @@ -118,22 +97,27 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener { * @param listener The listener for the recognition events related to the given keyphrase. * @return One of {@link #STATUS_ERROR} or {@link #STATUS_OK}. */ - public synchronized int startRecognition(int keyphraseId, - SoundTrigger.KeyphraseSoundModel soundModel, - Listener listener, RecognitionConfig recognitionConfig) { - if (dspInfo == null || mModule == null) { + synchronized int startRecognition(int keyphraseId, + KeyphraseSoundModel soundModel, + IRecognitionStatusCallback listener, + RecognitionConfig recognitionConfig) { + if (moduleProperties == null || mModule == null) { Slog.w(TAG, "Attempting startRecognition without the capability"); return STATUS_ERROR; } - Listener oldListener = mActiveListeners.get(keyphraseId); + IRecognitionStatusCallback oldListener = mActiveListeners.get(keyphraseId); if (oldListener != null && oldListener != listener) { if (mCurrentSoundModelHandle != INVALID_SOUND_MODEL_HANDLE) { Slog.w(TAG, "Canceling previous recognition"); // TODO: Inspect the return codes here. mModule.unloadSoundModel(mCurrentSoundModelHandle); } - mActiveListeners.get(keyphraseId).onListeningStateChanged(STATE_STOPPED); + try { + mActiveListeners.get(keyphraseId).onDetectionStopped(); + } catch (RemoteException e) { + Slog.w(TAG, "RemoteException in onDetectionStopped"); + } mActiveListeners.remove(keyphraseId); } @@ -165,7 +149,8 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener { } /** - * Stops recognition for the given {@link Keyphrase} if a recognition is currently active. + * Stops recognition for the given {@link Keyphrase} if a recognition is + * currently active. * * @param keyphraseId The identifier of the keyphrase for which * the recognition is to be stopped. @@ -173,13 +158,13 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener { * * @return One of {@link #STATUS_ERROR} or {@link #STATUS_OK}. */ - public synchronized int stopRecognition(int keyphraseId, Listener listener) { - if (dspInfo == null || mModule == null) { + synchronized int stopRecognition(int keyphraseId, IRecognitionStatusCallback listener) { + if (moduleProperties == null || mModule == null) { Slog.w(TAG, "Attempting stopRecognition without the capability"); return STATUS_ERROR; } - Listener currentListener = mActiveListeners.get(keyphraseId); + IRecognitionStatusCallback currentListener = mActiveListeners.get(keyphraseId); if (currentListener == null) { // startRecognition hasn't been called or it failed. Slog.w(TAG, "Attempting stopRecognition without a successful startRecognition"); @@ -217,7 +202,7 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener { // TODO: Get the keyphrase out of the event and fire events on it. // For now, as a nasty workaround, we fire all events to the listener for // keyphrase with TEMP_KEYPHRASE_ID. - Listener listener = null; + IRecognitionStatusCallback listener = null; synchronized(this) { // TODO: The keyphrase should come from the recognition event // as it may be for a different keyphrase than the current one. @@ -231,13 +216,25 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener { switch (event.status) { case SoundTrigger.RECOGNITION_STATUS_SUCCESS: // TODO: Pass the captured audio back. - listener.onKeyphraseSpoken(null); + try { + listener.onDetected(null); + } catch (RemoteException e) { + Slog.w(TAG, "RemoteException in onDetected"); + } break; case SoundTrigger.RECOGNITION_STATUS_ABORT: - listener.onListeningStateChanged(STATE_STOPPED); + try { + listener.onDetectionStopped(); + } catch (RemoteException e) { + Slog.w(TAG, "RemoteException in onDetectionStopped"); + } break; case SoundTrigger.RECOGNITION_STATUS_FAILURE: - listener.onListeningStateChanged(STATE_STOPPED); + try { + listener.onDetectionStopped(); + } catch (RemoteException e) { + Slog.w(TAG, "RemoteException in onDetectionStopped"); + } break; } } diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java index 63702ba..2ce4971 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java @@ -24,7 +24,10 @@ import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.database.ContentObserver; -import android.hardware.soundtrigger.KeyphraseSoundModel; +import android.hardware.soundtrigger.IRecognitionStatusCallback; +import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel; +import android.hardware.soundtrigger.SoundTrigger.ModuleProperties; +import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig; import android.os.Binder; import android.os.Bundle; import android.os.Handler; @@ -56,19 +59,17 @@ public class VoiceInteractionManagerService extends SystemService { static final String TAG = "VoiceInteractionManagerService"; - // TODO: Add descriptive error codes. - public static final int STATUS_ERROR = -1; - public static final int STATUS_OK = 1; - final Context mContext; final ContentResolver mResolver; final DatabaseHelper mDbHelper; + final SoundTriggerHelper mSoundTriggerHelper; public VoiceInteractionManagerService(Context context) { super(context); mContext = context; mResolver = context.getContentResolver(); mDbHelper = new DatabaseHelper(context); + mSoundTriggerHelper = new SoundTriggerHelper(); } @Override @@ -239,6 +240,8 @@ public class VoiceInteractionManagerService extends SystemService { } } + //----------------- Model management APIs --------------------------------// + @Override public List<KeyphraseSoundModel> listRegisteredKeyphraseSoundModels( IVoiceInteractionService service) { @@ -290,15 +293,15 @@ public class VoiceInteractionManagerService extends SystemService { // If the keyphrases are not present in the model, delete the model. if (model.keyphrases == null) { if (mDbHelper.deleteKeyphraseSoundModel(model.uuid)) { - return STATUS_OK; + return SoundTriggerHelper.STATUS_OK; } else { - return STATUS_ERROR; + return SoundTriggerHelper.STATUS_ERROR; } } else { if (mDbHelper.addOrUpdateKeyphraseSoundModel(model)) { - return STATUS_OK; + return SoundTriggerHelper.STATUS_OK; } else { - return STATUS_ERROR; + return SoundTriggerHelper.STATUS_ERROR; } } } finally { @@ -307,6 +310,68 @@ public class VoiceInteractionManagerService extends SystemService { } } + //----------------- SoundTrigger APIs --------------------------------// + @Override + public ModuleProperties getDspModuleProperties(IVoiceInteractionService service) { + // Allow the call if this is the current voice interaction service. + synchronized (this) { + if (mImpl == null || mImpl.mService == null + || service == null || service.asBinder() != mImpl.mService.asBinder()) { + throw new SecurityException( + "Caller is not the current voice interaction service"); + } + + final long caller = Binder.clearCallingIdentity(); + try { + return mSoundTriggerHelper.moduleProperties; + } finally { + Binder.restoreCallingIdentity(caller); + } + } + } + + @Override + public int startRecognition(IVoiceInteractionService service, int keyphraseId, + KeyphraseSoundModel soundModel, IRecognitionStatusCallback callback, + RecognitionConfig recognitionConfig) { + // Allow the call if this is the current voice interaction service. + synchronized (this) { + if (mImpl == null || mImpl.mService == null + || service == null || service.asBinder() != mImpl.mService.asBinder()) { + throw new SecurityException( + "Caller is not the current voice interaction service"); + } + + final long caller = Binder.clearCallingIdentity(); + try { + return mSoundTriggerHelper.startRecognition( + keyphraseId, soundModel, callback, recognitionConfig); + } finally { + Binder.restoreCallingIdentity(caller); + } + } + } + + @Override + public int stopRecognition(IVoiceInteractionService service, int keyphraseId, + IRecognitionStatusCallback callback) { + // Allow the call if this is the current voice interaction service. + synchronized (this) { + if (mImpl == null || mImpl.mService == null + || service == null || service.asBinder() != mImpl.mService.asBinder()) { + throw new SecurityException( + "Caller is not the current voice interaction service"); + } + + final long caller = Binder.clearCallingIdentity(); + try { + return mSoundTriggerHelper.stopRecognition(keyphraseId, callback); + } finally { + Binder.restoreCallingIdentity(caller); + } + } + } + @Override public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { if (mContext.checkCallingOrSelfPermission(Manifest.permission.DUMP) |