Merge "Always derive native library paths at runtime."
diff --git a/api/current.txt b/api/current.txt
index d55cac6..558b7d1 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -26427,8 +26427,37 @@
package android.service.voice {
+ public class AlwaysOnHotwordDetector {
+ method public int getAvailability();
+ method public android.content.Intent getManageIntent(int);
+ method public int getRecognitionStatus();
+ method public int startRecognition();
+ method public int stopRecognition();
+ field public static final int KEYPHRASE_ENROLLED = 2; // 0x2
+ field public static final int KEYPHRASE_HARDWARE_UNAVAILABLE = -2; // 0xfffffffe
+ field public static final int KEYPHRASE_UNENROLLED = 1; // 0x1
+ field public static final int KEYPHRASE_UNSUPPORTED = -1; // 0xffffffff
+ field public static final int MANAGE_ACTION_ENROLL = 0; // 0x0
+ field public static final int MANAGE_ACTION_RE_ENROLL = 1; // 0x1
+ field public static final int MANAGE_ACTION_UN_ENROLL = 2; // 0x2
+ field public static final int RECOGNITION_ACTIVE = 2; // 0x2
+ field public static final int RECOGNITION_DISABLED_TEMPORARILY = -1; // 0xffffffff
+ field public static final int RECOGNITION_NOT_AVAILABLE = -3; // 0xfffffffd
+ field public static final int RECOGNITION_NOT_REQUESTED = -2; // 0xfffffffe
+ field public static final int RECOGNITION_REQUESTED = 1; // 0x1
+ field public static final int STATUS_ERROR = -2147483648; // 0x80000000
+ field public static final int STATUS_OK = 1; // 0x1
+ }
+
+ public static abstract interface AlwaysOnHotwordDetector.Callback {
+ method public abstract void onDetected();
+ method public abstract void onDetectionStarted();
+ method public abstract void onDetectionStopped();
+ }
+
public class VoiceInteractionService extends android.app.Service {
ctor public VoiceInteractionService();
+ method public final android.service.voice.AlwaysOnHotwordDetector getAlwaysOnHotwordDetector(java.lang.String, java.lang.String, android.service.voice.AlwaysOnHotwordDetector.Callback);
method public android.os.IBinder onBind(android.content.Intent);
method public void startSession(android.os.Bundle);
field public static final java.lang.String SERVICE_INTERFACE = "android.service.voice.VoiceInteractionService";
diff --git a/core/java/android/service/voice/DspInfo.java b/core/java/android/hardware/soundtrigger/DspInfo.java
similarity index 97%
rename from core/java/android/service/voice/DspInfo.java
rename to core/java/android/hardware/soundtrigger/DspInfo.java
index 0862309..517159d 100644
--- a/core/java/android/service/voice/DspInfo.java
+++ b/core/java/android/hardware/soundtrigger/DspInfo.java
@@ -1,4 +1,4 @@
-/*
+/**
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -14,12 +14,13 @@
* limitations under the License.
*/
-package android.service.voice;
+package android.hardware.soundtrigger;
import java.util.UUID;
/**
* Properties of the DSP hardware on the device.
+ *
* @hide
*/
public class DspInfo {
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/service/voice/KeyphraseEnrollmentInfo.java b/core/java/android/hardware/soundtrigger/KeyphraseEnrollmentInfo.java
similarity index 82%
rename from core/java/android/service/voice/KeyphraseEnrollmentInfo.java
rename to core/java/android/hardware/soundtrigger/KeyphraseEnrollmentInfo.java
index ebe41ce..2f5de6a 100644
--- a/core/java/android/service/voice/KeyphraseEnrollmentInfo.java
+++ b/core/java/android/hardware/soundtrigger/KeyphraseEnrollmentInfo.java
@@ -1,4 +1,4 @@
-/*
+/**
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package android.service.voice;
+package android.hardware.soundtrigger;
import android.Manifest;
import android.content.Intent;
@@ -24,6 +24,7 @@
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;
@@ -34,7 +35,11 @@
import java.io.IOException;
import java.util.List;
-/** @hide */
+/**
+ * Enrollment information about the different available keyphrases.
+ *
+ * @hide
+ */
public class KeyphraseEnrollmentInfo {
private static final String TAG = "KeyphraseEnrollmentInfo";
/**
@@ -53,10 +58,14 @@
public static final String ACTION_MANAGE_VOICE_KEYPHRASES =
"com.android.intent.action.MANAGE_VOICE_KEYPHRASES";
/**
- * Intent extra: The intent extra for un-enrolling a user for a particular keyphrase.
+ * 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_UNENROLL =
- "com.android.intent.extra.VOICE_KEYPHRASE_UNENROLL";
+ 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.
*/
@@ -68,7 +77,7 @@
public static final String EXTRA_VOICE_KEYPHRASE_LOCALE =
"com.android.intent.extra.VOICE_KEYPHRASE_LOCALE";
- private KeyphraseInfo[] mKeyphrases;
+ private KeyphraseMetadata[] mKeyphrases;
private String mEnrollmentPackage;
private String mParseError;
@@ -156,8 +165,8 @@
&& !searchKeyphraseSupportedLocales.isEmpty()) {
supportedLocales = searchKeyphraseSupportedLocales.split(",");
}
- mKeyphrases = new KeyphraseInfo[1];
- mKeyphrases[0] = new KeyphraseInfo(
+ mKeyphrases = new KeyphraseMetadata[1];
+ mKeyphrases[0] = new KeyphraseMetadata(
searchKeyphraseId, searchKeyphrase, supportedLocales);
} else {
mParseError = "searchKeyphraseId not specified in meta-data";
@@ -188,7 +197,7 @@
* @return An array of available keyphrases that can be enrolled on the system.
* It may be null if no keyphrases can be enrolled.
*/
- public KeyphraseInfo[] getKeyphrases() {
+ public KeyphraseMetadata[] listKeyphraseMetadata() {
return mKeyphrases;
}
@@ -196,51 +205,56 @@
* Returns an intent to launch an activity that manages the given keyphrase
* for the locale.
*
- * @param enroll Indicates if the intent should enroll the user or un-enroll them.
+ * @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(boolean enroll, String keyphrase, String locale) {
+ public Intent getManageKeyphraseIntent(int action, String keyphrase, String locale) {
if (mEnrollmentPackage == null || mEnrollmentPackage.isEmpty()) {
Slog.w(TAG, "No enrollment application exists");
return null;
}
- if (isKeyphraseEnrollmentSupported(keyphrase, locale)) {
+ 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);
- if (!enroll) intent.putExtra(EXTRA_VOICE_KEYPHRASE_UNENROLL, true);
+ .putExtra(EXTRA_VOICE_KEYPHRASE_LOCALE, locale)
+ .putExtra(EXTRA_VOICE_KEYPHRASE_ACTION, action);
return intent;
}
return null;
}
/**
- * Indicates if enrollment is supported for the given keyphrase & locale.
+ * 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 boolean isKeyphraseEnrollmentSupported(String keyphrase, String locale) {
+ public KeyphraseMetadata getKeyphraseMetadata(String keyphrase, String locale) {
if (mKeyphrases == null || mKeyphrases.length == 0) {
Slog.w(TAG, "Enrollment application doesn't support keyphrases");
- return false;
+ return null;
}
- for (KeyphraseInfo keyphraseInfo : mKeyphrases) {
+ for (KeyphraseMetadata keyphraseMetadata : mKeyphrases) {
// Check if the given keyphrase is supported in the locale provided by
// the enrollment application.
- String supportedKeyphrase = keyphraseInfo.keyphrase;
- if (supportedKeyphrase.equalsIgnoreCase(keyphrase)
- && keyphraseInfo.supportedLocales.contains(locale)) {
- return true;
+ if (keyphraseMetadata.supportsPhrase(keyphrase)
+ && keyphraseMetadata.supportsLocale(locale)) {
+ return keyphraseMetadata;
}
}
- Slog.w(TAG, "Enrollment application doesn't support the given keyphrase");
- return false;
+ 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.
+ }
+}
diff --git a/core/java/android/service/voice/AlwaysOnHotwordDetector.java b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
new file mode 100644
index 0000000..67ce31e
--- /dev/null
+++ b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
@@ -0,0 +1,270 @@
+/**
+ * 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.service.voice;
+
+import android.content.Intent;
+import android.hardware.soundtrigger.Keyphrase;
+import android.hardware.soundtrigger.KeyphraseEnrollmentInfo;
+import android.hardware.soundtrigger.KeyphraseMetadata;
+import android.hardware.soundtrigger.SoundTriggerHelper;
+import android.util.Slog;
+
+/**
+ * A class that lets a VoiceInteractionService implementation interact with
+ * always-on keyphrase detection APIs.
+ */
+public class AlwaysOnHotwordDetector {
+ //---- States of Keyphrase availability ----//
+ /**
+ * Indicates that the given keyphrase is not available on the system because of the
+ * hardware configuration.
+ */
+ public static final int KEYPHRASE_HARDWARE_UNAVAILABLE = -2;
+ /**
+ * Indicates that the given keyphrase is not supported.
+ */
+ public static final int KEYPHRASE_UNSUPPORTED = -1;
+ /**
+ * Indicates that the given keyphrase is not enrolled.
+ */
+ public static final int KEYPHRASE_UNENROLLED = 1;
+ /**
+ * Indicates that the given keyphrase is currently enrolled but not being actively listened for.
+ */
+ public static final int KEYPHRASE_ENROLLED = 2;
+
+ // Keyphrase management actions ----//
+ /** Indicates that we need to enroll. */
+ public static final int MANAGE_ACTION_ENROLL = 0;
+ /** Indicates that we need to re-enroll. */
+ public static final int MANAGE_ACTION_RE_ENROLL = 1;
+ /** Indicates that we need to un-enroll. */
+ public static final int MANAGE_ACTION_UN_ENROLL = 2;
+
+ /**
+ * Return codes for {@link #startRecognition()}, {@link #stopRecognition()}
+ */
+ public static final int STATUS_ERROR = Integer.MIN_VALUE;
+ public static final int STATUS_OK = 1;
+
+ //---- Keyphrase recognition status ----//
+ // TODO: Figure out if they are exclusive or should be flags instead?
+ public static final int RECOGNITION_NOT_AVAILABLE = -3;
+ public static final int RECOGNITION_NOT_REQUESTED = -2;
+ public static final int RECOGNITION_DISABLED_TEMPORARILY = -1;
+ public static final int RECOGNITION_REQUESTED = 1;
+ public static final int RECOGNITION_ACTIVE = 2;
+ static final String TAG = "AlwaysOnHotwordDetector";
+
+ private final String mText;
+ private final String mLocale;
+ private final Keyphrase mKeyphrase;
+ private final KeyphraseEnrollmentInfo mKeyphraseEnrollmentInfo;
+ private final SoundTriggerHelper mSoundTriggerHelper;
+ private final SoundTriggerHelper.Listener mListener;
+ private final int mAvailability;
+
+ private int mRecognitionState;
+
+ /**
+ * Callbacks for always-on hotword detection.
+ */
+ public interface Callback {
+ /**
+ * Called when the keyphrase is spoken.
+ * TODO: Add more data to the callback.
+ */
+ void onDetected();
+ /**
+ * Called when the detection for the associated keyphrase starts.
+ */
+ void onDetectionStarted();
+ /**
+ * Called when the detection for the associated keyphrase stops.
+ */
+ void onDetectionStopped();
+ }
+
+ /**
+ * @param text The keyphrase text to get the detector for.
+ * @param locale The java locale for the detector.
+ * @param callback A non-null Callback for receiving the recognition events.
+ *
+ * @hide
+ */
+ public AlwaysOnHotwordDetector(String text, String locale, Callback callback,
+ KeyphraseEnrollmentInfo keyphraseEnrollmentInfo,
+ SoundTriggerHelper soundTriggerHelper) {
+ mText = text;
+ mLocale = locale;
+ mKeyphraseEnrollmentInfo = keyphraseEnrollmentInfo;
+ KeyphraseMetadata keyphraseMetadata =
+ mKeyphraseEnrollmentInfo.getKeyphraseMetadata(text, locale);
+ if (keyphraseMetadata != null) {
+ mKeyphrase = new Keyphrase(keyphraseMetadata.id, text, locale);
+ } else {
+ mKeyphrase = null;
+ }
+ mListener = new SoundTriggerListener(callback);
+ mSoundTriggerHelper = soundTriggerHelper;
+ mAvailability = getAvailabilityInternal();
+ }
+
+ /**
+ * Gets the state of always-on hotword detection for the given keyphrase and locale
+ * on this system.
+ * Availability implies that the hardware on this system is capable of listening for
+ * the given keyphrase or not.
+ *
+ * @return Indicates if always-on hotword detection is available for the given keyphrase.
+ * The return code is one of {@link #KEYPHRASE_HARDWARE_UNAVAILABLE},
+ * {@link #KEYPHRASE_UNSUPPORTED}, {@link #KEYPHRASE_UNENROLLED} or
+ * {@link #KEYPHRASE_ENROLLED}.
+ */
+ public int getAvailability() {
+ return mAvailability;
+ }
+
+ /**
+ * Gets the status of the recognition.
+ * @return One of {@link #RECOGNITION_NOT_AVAILABLE}, {@link #RECOGNITION_NOT_REQUESTED},
+ * {@link #RECOGNITION_DISABLED_TEMPORARILY} or {@link #RECOGNITION_ACTIVE}.
+ * @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.
+ */
+ public int getRecognitionStatus() {
+ if (mAvailability != KEYPHRASE_ENROLLED) {
+ throw new UnsupportedOperationException(
+ "Recognition for the given keyphrase is not supported");
+ }
+
+ return mRecognitionState;
+ }
+
+ /**
+ * Starts recognition for the associated keyphrase.
+ *
+ * @return One of {@link #STATUS_ERROR} or {@link #STATUS_OK}.
+ * @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.
+ */
+ public int startRecognition() {
+ if (mAvailability != KEYPHRASE_ENROLLED) {
+ throw new UnsupportedOperationException(
+ "Recognition for the given keyphrase is not supported");
+ }
+
+ mRecognitionState = RECOGNITION_REQUESTED;
+ int code = mSoundTriggerHelper.startRecognition(mKeyphrase.id, mListener);
+ if (code != SoundTriggerHelper.STATUS_OK) {
+ Slog.w(TAG, "startRecognition() failed with error code " + code);
+ return STATUS_ERROR;
+ } else {
+ return STATUS_OK;
+ }
+ }
+
+ /**
+ * Stops recognition for the associated keyphrase.
+ *
+ * @return One of {@link #STATUS_ERROR} or {@link #STATUS_OK}.
+ * @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.
+ */
+ public int stopRecognition() {
+ if (mAvailability != KEYPHRASE_ENROLLED) {
+ throw new UnsupportedOperationException(
+ "Recognition for the given keyphrase is not supported");
+ }
+
+ mRecognitionState = RECOGNITION_NOT_REQUESTED;
+ int code = mSoundTriggerHelper.stopRecognition(mKeyphrase.id, mListener);
+ if (code != SoundTriggerHelper.STATUS_OK) {
+ Slog.w(TAG, "stopRecognition() failed with error code " + code);
+ return STATUS_ERROR;
+ } else {
+ return STATUS_OK;
+ }
+ }
+
+ /**
+ * Gets an intent to manage the associated keyphrase.
+ *
+ * @param action The manage action that needs to be performed.
+ * One of {@link #MANAGE_ACTION_ENROLL}, {@link #MANAGE_ACTION_RE_ENROLL} or
+ * {@link #MANAGE_ACTION_UN_ENROLL}.
+ * @return An {@link Intent} to manage the given keyphrase.
+ * @throws UnsupportedOperationException if managing they keyphrase isn't supported.
+ * Callers should check the availability by calling {@link #getAvailability()}
+ * before calling this method to avoid this exception.
+ */
+ public Intent getManageIntent(int action) {
+ if (mAvailability == KEYPHRASE_HARDWARE_UNAVAILABLE
+ || mAvailability == KEYPHRASE_UNSUPPORTED) {
+ throw new UnsupportedOperationException(
+ "Managing the given keyphrase is not supported");
+ }
+ if (action != MANAGE_ACTION_ENROLL
+ && action != MANAGE_ACTION_RE_ENROLL
+ && action != MANAGE_ACTION_UN_ENROLL) {
+ throw new IllegalArgumentException("Invalid action specified " + action);
+ }
+
+ return mKeyphraseEnrollmentInfo.getManageKeyphraseIntent(action, mText, mLocale);
+ }
+
+ private int getAvailabilityInternal() {
+ if (mSoundTriggerHelper.dspInfo == null) {
+ return KEYPHRASE_HARDWARE_UNAVAILABLE;
+ }
+ if (mKeyphrase == null || !mSoundTriggerHelper.isKeyphraseSupported(mKeyphrase)) {
+ return KEYPHRASE_UNSUPPORTED;
+ }
+ if (!mSoundTriggerHelper.isKeyphraseEnrolled(mKeyphrase)) {
+ return KEYPHRASE_UNENROLLED;
+ }
+ return KEYPHRASE_ENROLLED;
+ }
+
+ /** @hide */
+ static final class SoundTriggerListener implements SoundTriggerHelper.Listener {
+ private final Callback mCallback;
+
+ public SoundTriggerListener(Callback callback) {
+ this.mCallback = callback;
+ }
+
+ @Override
+ public void onKeyphraseSpoken() {
+ Slog.i(TAG, "onKeyphraseSpoken");
+ mCallback.onDetected();
+ }
+
+ @Override
+ public void onListeningStateChanged(int state) {
+ Slog.i(TAG, "onListeningStateChanged: state=" + state);
+ if (state == SoundTriggerHelper.STATE_STARTED) {
+ mCallback.onDetectionStarted();
+ } else if (state == SoundTriggerHelper.STATE_STOPPED) {
+ mCallback.onDetectionStopped();
+ }
+ }
+ }
+}
diff --git a/core/java/android/service/voice/KeyphraseInfo.java b/core/java/android/service/voice/KeyphraseInfo.java
deleted file mode 100644
index d266e1a..0000000
--- a/core/java/android/service/voice/KeyphraseInfo.java
+++ /dev/null
@@ -1,27 +0,0 @@
-package android.service.voice;
-
-import android.util.ArraySet;
-
-/**
- * A Voice Keyphrase.
- * @hide
- */
-public class KeyphraseInfo {
- public final int id;
- public final String keyphrase;
- public final ArraySet<String> supportedLocales;
-
- public KeyphraseInfo(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;
- }
-}
diff --git a/core/java/android/service/voice/SoundTriggerManager.java b/core/java/android/service/voice/SoundTriggerManager.java
deleted file mode 100644
index 2d049b9..0000000
--- a/core/java/android/service/voice/SoundTriggerManager.java
+++ /dev/null
@@ -1,73 +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.service.voice;
-
-import android.hardware.soundtrigger.SoundTrigger;
-import android.hardware.soundtrigger.SoundTrigger.ModuleProperties;
-
-import java.util.ArrayList;
-
-/**
- * Manager for {@link SoundTrigger} APIs.
- * Currently this just acts as an abstraction over all SoundTrigger API calls.
- * @hide
- */
-public class SoundTriggerManager {
- /** The {@link DspInfo} for the system, or null if none exists. */
- public DspInfo dspInfo;
-
- public SoundTriggerManager() {
- ArrayList <ModuleProperties> modules = new ArrayList<>();
- int status = SoundTrigger.listModules(modules);
- if (status != SoundTrigger.STATUS_OK || modules.size() == 0) {
- // TODO(sansid, elaurent): Figure out how to handle errors in listing the modules here.
- dspInfo = null;
- } else {
- // TODO(sansid, elaurent): Figure out how to determine which module corresponds to the
- // DSP hardware.
- ModuleProperties properties = modules.get(0);
- dspInfo = new DspInfo(properties.uuid, properties.implementor, properties.description,
- properties.version, properties.powerConsumptionMw);
- }
- }
-
- /**
- * @return True, if the keyphrase is supported on DSP for the given locale.
- */
- public boolean isKeyphraseSupported(String keyphrase, String locale) {
- // TODO(sansid): We also need to look into a SoundTrigger API that let's us
- // query this. For now just return supported if there's a DSP available.
- return dspInfo != null;
- }
-
- /**
- * @return True, if the keyphrase is has been enrolled for the given locale.
- */
- public boolean isKeyphraseEnrolled(String keyphrase, String locale) {
- // TODO(sansid, elaurent): Query SoundTrigger to list currently loaded sound models.
- // They have been enrolled.
- return false;
- }
-
- /**
- * @return True, if a recognition for the keyphrase is active for the given locale.
- */
- public boolean isKeyphraseActive(String keyphrase, String locale) {
- // TODO(sansid, elaurent): Check if the recognition for the keyphrase is currently active.
- return false;
- }
-}
diff --git a/core/java/android/service/voice/VoiceInteractionService.java b/core/java/android/service/voice/VoiceInteractionService.java
index e0329f8..cf8d502 100644
--- a/core/java/android/service/voice/VoiceInteractionService.java
+++ b/core/java/android/service/voice/VoiceInteractionService.java
@@ -20,6 +20,8 @@
import android.app.Service;
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.IBinder;
import android.os.RemoteException;
@@ -53,16 +55,6 @@
public static final String SERVICE_INTERFACE =
"android.service.voice.VoiceInteractionService";
- // TODO(sansid): Unhide these.
- /** @hide */
- public static final int KEYPHRASE_UNAVAILABLE = 0;
- /** @hide */
- public static final int KEYPHRASE_UNENROLLED = 1;
- /** @hide */
- public static final int KEYPHRASE_ENROLLED = 2;
- /** @hide */
- public static final int KEYPHRASE_ACTIVE = 3;
-
/**
* Name under which a VoiceInteractionService component publishes information about itself.
* This meta-data should reference an XML resource containing a
@@ -76,8 +68,8 @@
IVoiceInteractionManagerService mSystemService;
- private SoundTriggerManager mSoundTriggerManager;
private KeyphraseEnrollmentInfo mKeyphraseEnrollmentInfo;
+ private SoundTriggerHelper mSoundTriggerHelper;
public void startSession(Bundle args) {
try {
@@ -92,7 +84,7 @@
mSystemService = IVoiceInteractionManagerService.Stub.asInterface(
ServiceManager.getService(Context.VOICE_INTERACTION_MANAGER_SERVICE));
mKeyphraseEnrollmentInfo = new KeyphraseEnrollmentInfo(getPackageManager());
- mSoundTriggerManager = new SoundTriggerManager();
+ mSoundTriggerHelper = new SoundTriggerHelper();
}
@Override
@@ -104,34 +96,18 @@
}
/**
- * Gets the state of always-on hotword detection for the given keyphrase and locale
- * on this system.
- * Availability implies that the hardware on this system is capable of listening for
- * the given keyphrase or not.
- * The return code is one of {@link #KEYPHRASE_UNAVAILABLE}, {@link #KEYPHRASE_UNENROLLED}
- * {@link #KEYPHRASE_ENROLLED} or {@link #KEYPHRASE_ACTIVE}.
- *
- * @param keyphrase The keyphrase whose availability is being checked.
- * @param locale The locale for which the availability is being checked.
- * @return Indicates if always-on hotword detection is available for the given keyphrase.
- * TODO(sansid): Unhide this.
- * @hide
+ * @param keyphrase The keyphrase that's being used, for example "Hello Android".
+ * @param locale The locale for which the enrollment needs to be performed.
+ * This is a Java locale, for example "en_US".
+ * @param callback The callback to notify of detection events.
+ * @return An always-on hotword detector for the given keyphrase and locale.
*/
- public final int getAlwaysOnKeyphraseAvailability(String keyphrase, String locale) {
- // The available keyphrases is a combination of DSP availability and
- // the keyphrases that have an enrollment application for them.
- if (!mSoundTriggerManager.isKeyphraseSupported(keyphrase, locale)
- || !mKeyphraseEnrollmentInfo.isKeyphraseEnrollmentSupported(keyphrase, locale)) {
- return KEYPHRASE_UNAVAILABLE;
- }
- if (!mSoundTriggerManager.isKeyphraseEnrolled(keyphrase, locale)) {
- return KEYPHRASE_UNENROLLED;
- }
- if (!mSoundTriggerManager.isKeyphraseActive(keyphrase, locale)) {
- return KEYPHRASE_ENROLLED;
- } else {
- return KEYPHRASE_ACTIVE;
- }
+ public final AlwaysOnHotwordDetector getAlwaysOnHotwordDetector(
+ String keyphrase, String locale, AlwaysOnHotwordDetector.Callback callback) {
+ // 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);
}
/**
diff --git a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
index 98e35dd..c78f770 100644
--- a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
+++ b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
@@ -20,6 +20,7 @@
import android.os.Bundle;
import com.android.internal.app.IVoiceInteractor;
+import android.hardware.soundtrigger.KeyphraseSoundModel;
import android.service.voice.IVoiceInteractionService;
import android.service.voice.IVoiceInteractionSession;
@@ -29,4 +30,16 @@
IVoiceInteractor interactor);
int startVoiceActivity(IBinder token, in Intent intent, String resolvedType);
void finish(IBinder token);
+
+ /**
+ * Lists the registered Sound models for keyphrase detection.
+ * May be null if no matching sound models exist.
+ *
+ * @param service The current voice interaction service.
+ */
+ List<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);
}
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index 16afc8f..85042f7 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -24,6 +24,7 @@
import android.content.Intent;
import android.content.pm.PackageManager;
import android.database.ContentObserver;
+import android.hardware.soundtrigger.KeyphraseSoundModel;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
@@ -35,6 +36,7 @@
import android.service.voice.IVoiceInteractionService;
import android.service.voice.IVoiceInteractionSession;
import android.util.Slog;
+
import com.android.internal.app.IVoiceInteractionManagerService;
import com.android.internal.app.IVoiceInteractor;
import com.android.internal.content.PackageMonitor;
@@ -44,6 +46,7 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.util.List;
/**
@@ -53,6 +56,10 @@
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;
@@ -231,6 +238,59 @@
}
@Override
+ public List<KeyphraseSoundModel> listRegisteredKeyphraseSoundModels(
+ IVoiceInteractionService service) {
+ // Allow the call if this is the current voice interaction service
+ // or the caller holds the MANAGE_VOICE_KEYPHRASES permission.
+ synchronized (this) {
+ boolean permissionGranted =
+ mContext.checkCallingPermission(Manifest.permission.MANAGE_VOICE_KEYPHRASES)
+ == PackageManager.PERMISSION_GRANTED;
+ boolean currentVoiceInteractionService = service != null
+ && mImpl != null
+ && mImpl.mService != null
+ && service.asBinder() == mImpl.mService.asBinder();
+
+ if (!permissionGranted && !currentVoiceInteractionService) {
+ if (!currentVoiceInteractionService) {
+ throw new SecurityException(
+ "Caller is not the current voice interaction service");
+ }
+ if (!permissionGranted) {
+ throw new SecurityException("Caller does not hold the permission "
+ + Manifest.permission.MANAGE_VOICE_KEYPHRASES);
+ }
+ }
+
+ final long caller = Binder.clearCallingIdentity();
+ try {
+ // TODO: Add the implementation here.
+ return null;
+ } finally {
+ Binder.restoreCallingIdentity(caller);
+ }
+ }
+ }
+
+ @Override
+ public int updateKeyphraseSoundModel(KeyphraseSoundModel model) {
+ synchronized (this) {
+ if (mContext.checkCallingPermission(Manifest.permission.MANAGE_VOICE_KEYPHRASES)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Caller does not hold the permission "
+ + Manifest.permission.MANAGE_VOICE_KEYPHRASES);
+ }
+ final long caller = Binder.clearCallingIdentity();
+ try {
+ // TODO: Add the implementation here.
+ return VoiceInteractionManagerService.STATUS_ERROR;
+ } finally {
+ Binder.restoreCallingIdentity(caller);
+ }
+ }
+ }
+
+ @Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (mContext.checkCallingOrSelfPermission(Manifest.permission.DUMP)
!= PackageManager.PERMISSION_GRANTED) {
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
index 8d96fb3..1f40c26 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
@@ -26,7 +26,6 @@
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
-import android.content.pm.ServiceInfo;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
@@ -42,6 +41,7 @@
import android.util.Slog;
import android.view.IWindowManager;
import android.view.WindowManager;
+
import com.android.internal.app.IVoiceInteractor;
import java.io.FileDescriptor;
diff --git a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionService.java b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionService.java
index 00c2c64..db43be3 100644
--- a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionService.java
+++ b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionService.java
@@ -32,7 +32,7 @@
Log.i(TAG, "Creating " + this);
Log.i(TAG, "Keyphrase enrollment error? " + getKeyphraseEnrollmentInfo().getParseError());
Log.i(TAG, "Keyphrase enrollment meta-data: "
- + Arrays.toString(getKeyphraseEnrollmentInfo().getKeyphrases()));
+ + Arrays.toString(getKeyphraseEnrollmentInfo().listKeyphraseMetadata()));
}
@Override