summaryrefslogtreecommitdiffstats
path: root/core/java/android/hardware/soundtrigger
diff options
context:
space:
mode:
authorSandeep <sansid@google.com>2014-07-10 15:15:39 -0700
committerSandeep <sansid@google.com>2014-07-10 18:32:37 -0700
commitd7018200312e4e4dc3f67cf33dc90bf7ce585844 (patch)
tree4479f56efb160bc525120e1ef19f724eccf47e8c /core/java/android/hardware/soundtrigger
parent3d4fe363b25293577faae3fddf85e6f73f76b0cd (diff)
downloadframeworks_base-d7018200312e4e4dc3f67cf33dc90bf7ce585844.zip
frameworks_base-d7018200312e4e4dc3f67cf33dc90bf7ce585844.tar.gz
frameworks_base-d7018200312e4e4dc3f67cf33dc90bf7ce585844.tar.bz2
Always on hotword changes
Add model management API skeleton to VoiceInteractionManagerService Add an "interactor" for all always-on APIs - The VoiceInteractionService will get an interactor for the given keyphrase and locale. - It can then check the availability and call methods to start and stop recognition on this interactor. - Add a common class to deal with SoundTrigger APIs - Cleanup the keyphrase representation: We now have separate representations for the keyphrase metadata and a keyphrase being used for recognition. This'll also help us to handle custom keyphrases in the future easily. This also ensures that for use within the framework, we rely on the ID of the KeyphraseInfo rather than comparing the text everytime. Add a callback for the AlwaysOnHotwordDetector This callback should be passed in by the VoiceInteractionService and is used to notify it of recognition events. Change-Id: I26252298773024f53a10cdd2af4404a4e6d74aae
Diffstat (limited to 'core/java/android/hardware/soundtrigger')
-rw-r--r--core/java/android/hardware/soundtrigger/DspInfo.java57
-rw-r--r--core/java/android/hardware/soundtrigger/Keyphrase.aidl4
-rw-r--r--core/java/android/hardware/soundtrigger/Keyphrase.java101
-rw-r--r--core/java/android/hardware/soundtrigger/KeyphraseEnrollmentInfo.java260
-rw-r--r--core/java/android/hardware/soundtrigger/KeyphraseMetadata.java60
-rw-r--r--core/java/android/hardware/soundtrigger/KeyphraseSoundModel.aidl4
-rw-r--r--core/java/android/hardware/soundtrigger/KeyphraseSoundModel.java68
-rw-r--r--core/java/android/hardware/soundtrigger/SoundTrigger.java2
-rw-r--r--core/java/android/hardware/soundtrigger/SoundTriggerHelper.java217
9 files changed, 772 insertions, 1 deletions
diff --git a/core/java/android/hardware/soundtrigger/DspInfo.java b/core/java/android/hardware/soundtrigger/DspInfo.java
new file mode 100644
index 0000000..517159d
--- /dev/null
+++ b/core/java/android/hardware/soundtrigger/DspInfo.java
@@ -0,0 +1,57 @@
+/**
+ * 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/Keyphrase.aidl b/core/java/android/hardware/soundtrigger/Keyphrase.aidl
new file mode 100644
index 0000000..d9853a7
--- /dev/null
+++ b/core/java/android/hardware/soundtrigger/Keyphrase.aidl
@@ -0,0 +1,4 @@
+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
new file mode 100644
index 0000000..42fd350
--- /dev/null
+++ b/core/java/android/hardware/soundtrigger/Keyphrase.java
@@ -0,0 +1,101 @@
+/**
+ * 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 String locale;
+
+ 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) {
+ return new Keyphrase(in.readInt(), in.readString(), in.readString());
+ }
+
+ public Keyphrase(int id, String hintText, String locale) {
+ this.id = id;
+ this.hintText = hintText;
+ this.locale = locale;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(id);
+ dest.writeString(hintText);
+ dest.writeString(locale);
+ }
+
+ @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;
+ }
+}
diff --git a/core/java/android/hardware/soundtrigger/KeyphraseEnrollmentInfo.java b/core/java/android/hardware/soundtrigger/KeyphraseEnrollmentInfo.java
new file mode 100644
index 0000000..2f5de6a
--- /dev/null
+++ b/core/java/android/hardware/soundtrigger/KeyphraseEnrollmentInfo.java
@@ -0,0 +1,260 @@
+/**
+ * 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.Manifest;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.service.voice.AlwaysOnHotwordDetector;
+import android.util.AttributeSet;
+import android.util.Slog;
+import android.util.Xml;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * Enrollment information about the different available keyphrases.
+ *
+ * @hide
+ */
+public class KeyphraseEnrollmentInfo {
+ private static final String TAG = "KeyphraseEnrollmentInfo";
+ /**
+ * Name under which a Hotword enrollment component publishes information about itself.
+ * This meta-data should reference an XML resource containing a
+ * <code>&lt;{@link
+ * android.R.styleable#VoiceEnrollmentApplication
+ * voice-enrollment-application}&gt;</code> tag.
+ */
+ private static final String VOICE_KEYPHRASE_META_DATA = "android.voice_enrollment";
+ /**
+ * Activity Action: Show activity for managing the keyphrases for hotword detection.
+ * This needs to be defined by an activity that supports enrolling users for hotword/keyphrase
+ * detection.
+ */
+ public static final String ACTION_MANAGE_VOICE_KEYPHRASES =
+ "com.android.intent.action.MANAGE_VOICE_KEYPHRASES";
+ /**
+ * Intent extra: The intent extra for the specific manage action that needs to be performed.
+ * Possible values are {@link AlwaysOnHotwordDetector#MANAGE_ACTION_ENROLL},
+ * {@link AlwaysOnHotwordDetector#MANAGE_ACTION_RE_ENROLL}
+ * or {@link AlwaysOnHotwordDetector#MANAGE_ACTION_UN_ENROLL}.
+ */
+ public static final String EXTRA_VOICE_KEYPHRASE_ACTION =
+ "com.android.intent.extra.VOICE_KEYPHRASE_ACTION";
+
+ /**
+ * Intent extra: The hint text to be shown on the voice keyphrase management UI.
+ */
+ public static final String EXTRA_VOICE_KEYPHRASE_HINT_TEXT =
+ "com.android.intent.extra.VOICE_KEYPHRASE_HINT_TEXT";
+ /**
+ * Intent extra: The voice locale to use while managing the keyphrase.
+ */
+ public static final String EXTRA_VOICE_KEYPHRASE_LOCALE =
+ "com.android.intent.extra.VOICE_KEYPHRASE_LOCALE";
+
+ private KeyphraseMetadata[] mKeyphrases;
+ private String mEnrollmentPackage;
+ private String mParseError;
+
+ public KeyphraseEnrollmentInfo(PackageManager pm) {
+ // Find the apps that supports enrollment for hotword keyhphrases,
+ // Pick a privileged app and obtain the information about the supported keyphrases
+ // from its metadata.
+ List<ResolveInfo> ris = pm.queryIntentActivities(
+ new Intent(ACTION_MANAGE_VOICE_KEYPHRASES), PackageManager.MATCH_DEFAULT_ONLY);
+ if (ris == null || ris.isEmpty()) {
+ // No application capable of enrolling for voice keyphrases is present.
+ mParseError = "No enrollment application found";
+ return;
+ }
+
+ boolean found = false;
+ ApplicationInfo ai = null;
+ for (ResolveInfo ri : ris) {
+ try {
+ ai = pm.getApplicationInfo(
+ ri.activityInfo.packageName, PackageManager.GET_META_DATA);
+ if ((ai.flags & ApplicationInfo.FLAG_PRIVILEGED) == 0) {
+ // The application isn't privileged (/system/priv-app).
+ // The enrollment application needs to be a privileged system app.
+ Slog.w(TAG, ai.packageName + "is not a privileged system app");
+ continue;
+ }
+ if (!Manifest.permission.MANAGE_VOICE_KEYPHRASES.equals(ai.permission)) {
+ // The application trying to manage keyphrases doesn't
+ // require the MANAGE_VOICE_KEYPHRASES permission.
+ Slog.w(TAG, ai.packageName + " does not require MANAGE_VOICE_KEYPHRASES");
+ continue;
+ }
+ mEnrollmentPackage = ai.packageName;
+ found = true;
+ break;
+ } catch (PackageManager.NameNotFoundException e) {
+ Slog.w(TAG, "error parsing voice enrollment meta-data", e);
+ }
+ }
+
+ if (!found) {
+ mKeyphrases = null;
+ mParseError = "No suitable enrollment application found";
+ return;
+ }
+
+ XmlResourceParser parser = null;
+ try {
+ parser = ai.loadXmlMetaData(pm, VOICE_KEYPHRASE_META_DATA);
+ if (parser == null) {
+ mParseError = "No " + VOICE_KEYPHRASE_META_DATA + " meta-data for "
+ + ai.packageName;
+ return;
+ }
+
+ Resources res = pm.getResourcesForApplication(ai);
+ AttributeSet attrs = Xml.asAttributeSet(parser);
+
+ int type;
+ while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
+ && type != XmlPullParser.START_TAG) {
+ }
+
+ String nodeName = parser.getName();
+ if (!"voice-enrollment-application".equals(nodeName)) {
+ mParseError = "Meta-data does not start with voice-enrollment-application tag";
+ return;
+ }
+
+ TypedArray array = res.obtainAttributes(attrs,
+ com.android.internal.R.styleable.VoiceEnrollmentApplication);
+ int searchKeyphraseId = array.getInt(
+ com.android.internal.R.styleable.VoiceEnrollmentApplication_searchKeyphraseId,
+ -1);
+ if (searchKeyphraseId != -1) {
+ String searchKeyphrase = array.getString(com.android.internal.R.styleable
+ .VoiceEnrollmentApplication_searchKeyphrase);
+ String searchKeyphraseSupportedLocales =
+ array.getString(com.android.internal.R.styleable
+ .VoiceEnrollmentApplication_searchKeyphraseSupportedLocales);
+ String[] supportedLocales = new String[0];
+ // Get all the supported locales from the comma-delimted string.
+ if (searchKeyphraseSupportedLocales != null
+ && !searchKeyphraseSupportedLocales.isEmpty()) {
+ supportedLocales = searchKeyphraseSupportedLocales.split(",");
+ }
+ mKeyphrases = new KeyphraseMetadata[1];
+ mKeyphrases[0] = new KeyphraseMetadata(
+ searchKeyphraseId, searchKeyphrase, supportedLocales);
+ } else {
+ mParseError = "searchKeyphraseId not specified in meta-data";
+ return;
+ }
+ } catch (XmlPullParserException e) {
+ mParseError = "Error parsing keyphrase enrollment meta-data: " + e;
+ Slog.w(TAG, "error parsing keyphrase enrollment meta-data", e);
+ return;
+ } catch (IOException e) {
+ mParseError = "Error parsing keyphrase enrollment meta-data: " + e;
+ Slog.w(TAG, "error parsing keyphrase enrollment meta-data", e);
+ return;
+ } catch (PackageManager.NameNotFoundException e) {
+ mParseError = "Error parsing keyphrase enrollment meta-data: " + e;
+ Slog.w(TAG, "error parsing keyphrase enrollment meta-data", e);
+ return;
+ } finally {
+ if (parser != null) parser.close();
+ }
+ }
+
+ public String getParseError() {
+ return mParseError;
+ }
+
+ /**
+ * @return An array of available keyphrases that can be enrolled on the system.
+ * It may be null if no keyphrases can be enrolled.
+ */
+ public KeyphraseMetadata[] listKeyphraseMetadata() {
+ return mKeyphrases;
+ }
+
+ /**
+ * Returns an intent to launch an activity that manages the given keyphrase
+ * for the locale.
+ *
+ * @param action The enrollment related action that this intent is supposed to perform.
+ * This can be one of {@link AlwaysOnHotwordDetector#MANAGE_ACTION_ENROLL},
+ * {@link AlwaysOnHotwordDetector#MANAGE_ACTION_RE_ENROLL}
+ * or {@link AlwaysOnHotwordDetector#MANAGE_ACTION_UN_ENROLL}
+ * @param keyphrase The keyphrase that the user needs to be enrolled to.
+ * @param locale The locale for which the enrollment needs to be performed.
+ * This is a Java locale, for example "en_US".
+ * @return An {@link Intent} to manage the keyphrase. This can be null if managing the
+ * given keyphrase/locale combination isn't possible.
+ */
+ public Intent getManageKeyphraseIntent(int action, String keyphrase, String locale) {
+ if (mEnrollmentPackage == null || mEnrollmentPackage.isEmpty()) {
+ Slog.w(TAG, "No enrollment application exists");
+ return null;
+ }
+
+ if (getKeyphraseMetadata(keyphrase, locale) != null) {
+ Intent intent = new Intent(ACTION_MANAGE_VOICE_KEYPHRASES)
+ .setPackage(mEnrollmentPackage)
+ .putExtra(EXTRA_VOICE_KEYPHRASE_HINT_TEXT, keyphrase)
+ .putExtra(EXTRA_VOICE_KEYPHRASE_LOCALE, locale)
+ .putExtra(EXTRA_VOICE_KEYPHRASE_ACTION, action);
+ return intent;
+ }
+ return null;
+ }
+
+ /**
+ * Gets the {@link KeyphraseMetadata} for the given keyphrase and locale, null if any metadata
+ * isn't available for the given combination.
+ *
+ * @param keyphrase The keyphrase that the user needs to be enrolled to.
+ * @param locale The locale for which the enrollment needs to be performed.
+ * This is a Java locale, for example "en_US".
+ * @return true, if an enrollment client supports the given keyphrase and the given locale.
+ */
+ public KeyphraseMetadata getKeyphraseMetadata(String keyphrase, String locale) {
+ if (mKeyphrases == null || mKeyphrases.length == 0) {
+ Slog.w(TAG, "Enrollment application doesn't support keyphrases");
+ return null;
+ }
+ for (KeyphraseMetadata keyphraseMetadata : mKeyphrases) {
+ // Check if the given keyphrase is supported in the locale provided by
+ // the enrollment application.
+ if (keyphraseMetadata.supportsPhrase(keyphrase)
+ && keyphraseMetadata.supportsLocale(locale)) {
+ return keyphraseMetadata;
+ }
+ }
+ Slog.w(TAG, "Enrollment application doesn't support the given keyphrase/locale");
+ return null;
+ }
+}
diff --git a/core/java/android/hardware/soundtrigger/KeyphraseMetadata.java b/core/java/android/hardware/soundtrigger/KeyphraseMetadata.java
new file mode 100644
index 0000000..03a4939
--- /dev/null
+++ b/core/java/android/hardware/soundtrigger/KeyphraseMetadata.java
@@ -0,0 +1,60 @@
+/**
+ * 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.util.ArraySet;
+
+/**
+ * A Voice Keyphrase metadata read from the enrollment application.
+ *
+ * @hide
+ */
+public class KeyphraseMetadata {
+ public final int id;
+ public final String keyphrase;
+ public final ArraySet<String> supportedLocales;
+
+ public KeyphraseMetadata(int id, String keyphrase, String[] supportedLocales) {
+ this.id = id;
+ this.keyphrase = keyphrase;
+ this.supportedLocales = new ArraySet<String>(supportedLocales.length);
+ for (String locale : supportedLocales) {
+ this.supportedLocales.add(locale);
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "id=" + id + ", keyphrase=" + keyphrase + ", supported-locales=" + supportedLocales;
+ }
+
+ /**
+ * @return Indicates if we support the given phrase.
+ */
+ public boolean supportsPhrase(String phrase) {
+ // TODO(sansid): Come up with a scheme for custom keyphrases that should always match.
+ return keyphrase.equalsIgnoreCase(phrase);
+ }
+
+ /**
+ * @return Indicates if we support the given locale.
+ */
+ public boolean supportsLocale(String locale) {
+ // TODO(sansid): Come up with a scheme for keyphrases that are available in all locales.
+ return supportedLocales.contains(locale);
+ }
+}
diff --git a/core/java/android/hardware/soundtrigger/KeyphraseSoundModel.aidl b/core/java/android/hardware/soundtrigger/KeyphraseSoundModel.aidl
new file mode 100644
index 0000000..39b33cc
--- /dev/null
+++ b/core/java/android/hardware/soundtrigger/KeyphraseSoundModel.aidl
@@ -0,0 +1,4 @@
+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
new file mode 100644
index 0000000..4ddba6a
--- /dev/null
+++ b/core/java/android/hardware/soundtrigger/KeyphraseSoundModel.java
@@ -0,0 +1,68 @@
+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);
+ }
+}
diff --git a/core/java/android/hardware/soundtrigger/SoundTrigger.java b/core/java/android/hardware/soundtrigger/SoundTrigger.java
index 7a4e5a5..1f48a92 100644
--- a/core/java/android/hardware/soundtrigger/SoundTrigger.java
+++ b/core/java/android/hardware/soundtrigger/SoundTrigger.java
@@ -1,4 +1,4 @@
-/*
+/**
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
diff --git a/core/java/android/hardware/soundtrigger/SoundTriggerHelper.java b/core/java/android/hardware/soundtrigger/SoundTriggerHelper.java
new file mode 100644
index 0000000..0be068d
--- /dev/null
+++ b/core/java/android/hardware/soundtrigger/SoundTriggerHelper.java
@@ -0,0 +1,217 @@
+/**
+ * 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.hardware.soundtrigger.SoundTrigger.ModuleProperties;
+import android.hardware.soundtrigger.SoundTrigger.RecognitionEvent;
+import android.util.Slog;
+import android.util.SparseArray;
+
+import java.util.ArrayList;
+
+/**
+ * Helper for {@link SoundTrigger} APIs.
+ * Currently this just acts as an abstraction over all SoundTrigger API calls.
+ *
+ * @hide
+ */
+public class SoundTriggerHelper implements SoundTrigger.StatusListener {
+ static final String TAG = "SoundTriggerHelper";
+ // TODO: Remove this.
+ static final int TEMP_KEYPHRASE_ID = 1;
+
+ /**
+ * Return codes for {@link #startRecognition(Keyphrase)}, {@link #stopRecognition(Keyphrase)}
+ * Note: Keep in sync with AlwaysOnKeyphraseInteractor.java
+ */
+ 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;
+
+ private static final int INVALID_SOUND_MODEL_HANDLE = -1;
+
+ /** The {@link DspInfo} for the system, or null if none exists. */
+ public final DspInfo dspInfo;
+
+ /** The properties for the DSP module */
+ private final ModuleProperties mModuleProperties;
+ private final SoundTriggerModule mModule;
+
+ private final SparseArray<Listener> mListeners;
+
+ private int mCurrentSoundModelHandle = INVALID_SOUND_MODEL_HANDLE;
+
+ /**
+ * The callback for sound trigger events.
+ */
+ public interface Listener {
+ /** Called when the given keyphrase is spoken. */
+ void onKeyphraseSpoken();
+
+ /**
+ * Called when the listening state for the given keyphrase changes.
+ * @param state Indicates the current state.
+ */
+ void onListeningStateChanged(int state);
+ }
+
+ public SoundTriggerHelper() {
+ ArrayList <ModuleProperties> modules = new ArrayList<>();
+ int status = SoundTrigger.listModules(modules);
+ mListeners = new SparseArray<>(1);
+ if (status != SoundTrigger.STATUS_OK || modules.size() == 0) {
+ // TODO: Figure out how to handle errors in listing the modules here.
+ dspInfo = null;
+ mModuleProperties = 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);
+ }
+ }
+
+ /**
+ * @return True, if the given {@link Keyphrase} is supported on DSP.
+ */
+ public boolean isKeyphraseSupported(Keyphrase keyphrase) {
+ // TODO: We also need to look into a SoundTrigger API that let's us
+ // query this. For now just return true.
+ return true;
+ }
+
+ /**
+ * @return True, if the given {@link Keyphrase} has been enrolled.
+ */
+ public boolean isKeyphraseEnrolled(Keyphrase keyphrase) {
+ // TODO: Query VoiceInteractionManagerService
+ // to list registered sound models.
+ return false;
+ }
+
+ /**
+ * @return True, if a recognition for the given {@link Keyphrase} is active.
+ */
+ public boolean isKeyphraseActive(Keyphrase keyphrase) {
+ // TODO: Check if the recognition for the keyphrase is currently active.
+ return false;
+ }
+
+ /**
+ * Starts recognition for the given {@link Keyphrase}.
+ *
+ * @param keyphraseId The identifier of the keyphrase for which
+ * the recognition is to be started.
+ * @param listener The listener for the recognition events related to the given keyphrase.
+ * @return One of {@link #STATUS_ERROR} or {@link #STATUS_OK}.
+ */
+ public int startRecognition(int keyphraseId, Listener listener) {
+ if (dspInfo == null || mModule == null) {
+ Slog.w(TAG, "Attempting startRecognition without the capability");
+ return STATUS_ERROR;
+ }
+
+ if (mListeners.get(keyphraseId) != listener) {
+ if (mCurrentSoundModelHandle != INVALID_SOUND_MODEL_HANDLE) {
+ Slog.w(TAG, "Canceling previous recognition");
+ // TODO: Inspect the return codes here.
+ mModule.unloadSoundModel(mCurrentSoundModelHandle);
+ }
+ mListeners.get(keyphraseId).onListeningStateChanged(STATE_STOPPED);
+ }
+
+ // Register the new listener. This replaces the old one.
+ // There can only be a maximum of one active listener for a keyphrase
+ // at any given time.
+ mListeners.put(keyphraseId, listener);
+ // TODO: Get the sound model for the given keyphrase here.
+ // mModule.loadSoundModel(model, soundModelHandle);
+ // mModule.startRecognition(soundModelHandle, data);
+ // mCurrentSoundModelHandle = soundModelHandle;
+ return STATUS_ERROR;
+ }
+
+ /**
+ * Stops recognition for the given {@link Keyphrase} if a recognition is currently active.
+ *
+ * @return One of {@link #STATUS_ERROR} or {@link #STATUS_OK}.
+ */
+ public int stopRecognition(int id, Listener listener) {
+ if (dspInfo == null || mModule == null) {
+ Slog.w(TAG, "Attempting stopRecognition without the capability");
+ return STATUS_ERROR;
+ }
+
+ if (mListeners.get(id) != listener) {
+ Slog.w(TAG, "Attempting stopRecognition for another recognition");
+ return STATUS_ERROR;
+ } else {
+ // Stop recognition if it's the current one, ignore otherwise.
+ // TODO: Inspect the return codes here.
+ mModule.stopRecognition(mCurrentSoundModelHandle);
+ mModule.unloadSoundModel(mCurrentSoundModelHandle);
+ mCurrentSoundModelHandle = INVALID_SOUND_MODEL_HANDLE;
+ return STATUS_OK;
+ }
+ }
+
+ //---- SoundTrigger.StatusListener methods
+ @Override
+ public void onRecognition(RecognitionEvent event) {
+ // Check which keyphrase triggered, and fire the appropriate event.
+ // 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.
+
+ switch (event.status) {
+ case SoundTrigger.RECOGNITION_STATUS_SUCCESS:
+ // TODO: The keyphrase should come from the recognition event
+ // as it may be for a different keyphrase than the current one.
+ if (mListeners.get(TEMP_KEYPHRASE_ID) != null) {
+ mListeners.get(TEMP_KEYPHRASE_ID).onKeyphraseSpoken();
+ }
+ break;
+ case SoundTrigger.RECOGNITION_STATUS_ABORT:
+ // TODO: The keyphrase should come from the recognition event
+ // as it may be for a different keyphrase than the current one.
+ if (mListeners.get(TEMP_KEYPHRASE_ID) != null) {
+ mListeners.get(TEMP_KEYPHRASE_ID).onListeningStateChanged(STATE_STOPPED);
+ }
+ break;
+ case SoundTrigger.RECOGNITION_STATUS_FAILURE:
+ // TODO: The keyphrase should come from the recognition event
+ // as it may be for a different keyphrase than the current one.
+ if (mListeners.get(TEMP_KEYPHRASE_ID) != null) {
+ mListeners.get(TEMP_KEYPHRASE_ID).onListeningStateChanged(STATE_STOPPED);
+ }
+ break;
+ }
+ }
+
+ @Override
+ public void onServiceDied() {
+ // TODO: Figure out how to restart the recognition here.
+ }
+}