Merge "Changes to framework for ToT Skia."
diff --git a/api/current.txt b/api/current.txt
index 39eba8e..cee6604 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -1000,6 +1000,9 @@
     field public static final int scrollbars = 16842974; // 0x10100de
     field public static final int scrollingCache = 16843006; // 0x10100fe
     field public static final deprecated int searchButtonText = 16843269; // 0x1010205
+    field public static final int searchKeyphrase = 16843874; // 0x1010462
+    field public static final int searchKeyphraseId = 16843873; // 0x1010461
+    field public static final int searchKeyphraseSupportedLocales = 16843875; // 0x1010463
     field public static final int searchMode = 16843221; // 0x10101d5
     field public static final int searchSettingsDescription = 16843402; // 0x101028a
     field public static final int searchSuggestAuthority = 16843222; // 0x10101d6
diff --git a/core/java/android/service/voice/DspInfo.java b/core/java/android/service/voice/DspInfo.java
new file mode 100644
index 0000000..0862309
--- /dev/null
+++ b/core/java/android/service/voice/DspInfo.java
@@ -0,0 +1,56 @@
+/*
+ * 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 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/service/voice/KeyphraseEnrollmentInfo.java b/core/java/android/service/voice/KeyphraseEnrollmentInfo.java
new file mode 100644
index 0000000..ebe41ce
--- /dev/null
+++ b/core/java/android/service/voice/KeyphraseEnrollmentInfo.java
@@ -0,0 +1,246 @@
+/*
+ * 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.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.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;
+
+/** @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 un-enrolling a user for a particular keyphrase.
+     */
+    public static final String EXTRA_VOICE_KEYPHRASE_UNENROLL =
+            "com.android.intent.extra.VOICE_KEYPHRASE_UNENROLL";
+    /**
+     * 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 KeyphraseInfo[] 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 KeyphraseInfo[1];
+                mKeyphrases[0] = new KeyphraseInfo(
+                        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 KeyphraseInfo[] getKeyphrases() {
+        return mKeyphrases;
+    }
+
+    /**
+     * 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 keyphrase The keyphrase that the user needs to be enrolled to.
+     * @param locale The locale for which the enrollment needs to be performed.
+     * @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) {
+        if (mEnrollmentPackage == null || mEnrollmentPackage.isEmpty()) {
+            Slog.w(TAG, "No enrollment application exists");
+            return null;
+        }
+
+        if (isKeyphraseEnrollmentSupported(keyphrase, locale)) {
+            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);
+            return intent;
+        }
+        return null;
+    }
+
+    /**
+     * Indicates if enrollment is supported for the given keyphrase & locale.
+     *
+     * @param keyphrase The keyphrase that the user needs to be enrolled to.
+     * @param locale The locale for which the enrollment needs to be performed.
+     * @return true, if an enrollment client supports the given keyphrase and the given locale.
+     */
+    public boolean isKeyphraseEnrollmentSupported(String keyphrase, String locale) {
+        if (mKeyphrases == null || mKeyphrases.length == 0) {
+            Slog.w(TAG, "Enrollment application doesn't support keyphrases");
+            return false;
+        }
+        for (KeyphraseInfo keyphraseInfo : 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;
+            }
+        }
+        Slog.w(TAG, "Enrollment application doesn't support the given keyphrase");
+        return false;
+    }
+}
diff --git a/core/java/android/service/voice/KeyphraseInfo.java b/core/java/android/service/voice/KeyphraseInfo.java
new file mode 100644
index 0000000..d266e1a
--- /dev/null
+++ b/core/java/android/service/voice/KeyphraseInfo.java
@@ -0,0 +1,27 @@
+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
new file mode 100644
index 0000000..2d049b9
--- /dev/null
+++ b/core/java/android/service/voice/SoundTriggerManager.java
@@ -0,0 +1,73 @@
+/*
+ * 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 e15489b..e0329f8 100644
--- a/core/java/android/service/voice/VoiceInteractionService.java
+++ b/core/java/android/service/voice/VoiceInteractionService.java
@@ -17,7 +17,6 @@
 package android.service.voice;
 
 import android.annotation.SdkConstant;
-import android.app.Instrumentation;
 import android.app.Service;
 import android.content.Context;
 import android.content.Intent;
@@ -25,8 +24,11 @@
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.IVoiceInteractionManagerService;
 
+
 /**
  * Top-level service of the current global voice interactor, which is providing
  * support for hotwording, the back-end of a {@link android.app.VoiceInteractor}, etc.
@@ -51,6 +53,16 @@
     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
@@ -64,6 +76,9 @@
 
     IVoiceInteractionManagerService mSystemService;
 
+    private SoundTriggerManager mSoundTriggerManager;
+    private KeyphraseEnrollmentInfo mKeyphraseEnrollmentInfo;
+
     public void startSession(Bundle args) {
         try {
             mSystemService.startSession(mInterface, args);
@@ -76,6 +91,8 @@
         super.onCreate();
         mSystemService = IVoiceInteractionManagerService.Stub.asInterface(
                 ServiceManager.getService(Context.VOICE_INTERACTION_MANAGER_SERVICE));
+        mKeyphraseEnrollmentInfo = new KeyphraseEnrollmentInfo(getPackageManager());
+        mSoundTriggerManager = new SoundTriggerManager();
     }
 
     @Override
@@ -85,4 +102,44 @@
         }
         return null;
     }
+
+    /**
+     * 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
+     */
+    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;
+        }
+    }
+
+    /**
+     * @return Details of keyphrases available for enrollment.
+     * @hide
+     */
+    @VisibleForTesting
+    protected final KeyphraseEnrollmentInfo getKeyphraseEnrollmentInfo() {
+        return mKeyphraseEnrollmentInfo;
+    }
 }
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index f3b5ccf..75e226d 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -2071,6 +2071,14 @@
         android:description="@string/permdesc_bindVoiceInteraction"
         android:protectionLevel="signature" />
 
+    <!-- Must be required by hotword enrollment application,
+         to ensure that only the system can interact with it.
+         @hide <p>Not for use by third-party applications.</p> -->
+    <permission android:name="android.permission.MANAGE_VOICE_KEYPHRASES"
+        android:label="@string/permlab_manageVoiceKeyphrases"
+        android:description="@string/permdesc_manageVoiceKeyphrases"
+        android:protectionLevel="signature|system" />
+
     <!-- Must be required by a {@link com.android.media.remotedisplay.RemoteDisplayProvider},
          to ensure that only the system can bind to it.
          @hide -->
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 5b362fc..2fea91e 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -6390,6 +6390,16 @@
         <attr name="settingsActivity" />
     </declare-styleable>
 
+    <!-- Use <code>voice-enrollment-application</code>
+         as the root tag of the XML resource that escribes the supported keyphrases (hotwords)
+         by the enrollment application.
+         Described here are the attributes that can be included in that tag. -->
+    <declare-styleable name="VoiceEnrollmentApplication">
+        <attr name="searchKeyphraseId" format="integer" />
+        <attr name="searchKeyphrase" format="string" />
+        <attr name="searchKeyphraseSupportedLocales" format="string" />
+    </declare-styleable>
+
     <!-- Attributes used to style the Action Bar. -->
     <declare-styleable name="ActionBar">
         <!-- The type of navigation to use. -->
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 8f3ee60..2792c93 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2185,6 +2185,9 @@
   <public type="attr" name="translateY" />
   <public type="attr" name="selectableItemBackgroundBorderless" />
   <public type="attr" name="elegantTextHeight" />
+  <public type="attr" name="searchKeyphraseId" />
+  <public type="attr" name="searchKeyphrase" />
+  <public type="attr" name="searchKeyphraseSupportedLocales" />
 
   <public-padding type="dimen" name="l_resource_pad" end="0x01050010" />
 
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 2903ac2..f1d9dc3 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1122,6 +1122,12 @@
         interface of a voice interaction service. Should never be needed for normal apps.</string>
 
     <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_manageVoiceKeyphrases">manage voice keyphrases</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_manageVoiceKeyphrases">Allows the holder to manage the keyphrases for voice hotword detection.
+        Should never be needed for normal apps.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permlab_bindRemoteDisplay">bind to a remote display</string>
     <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permdesc_bindRemoteDisplay">Allows the holder to bind to the top-level
diff --git a/tests/VoiceEnrollment/Android.mk b/tests/VoiceEnrollment/Android.mk
new file mode 100644
index 0000000..2ab3d02
--- /dev/null
+++ b/tests/VoiceEnrollment/Android.mk
@@ -0,0 +1,12 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+
+LOCAL_PACKAGE_NAME := VoiceEnrollment
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_PRIVILEGED_MODULE := true
+
+include $(BUILD_PACKAGE)
diff --git a/tests/VoiceEnrollment/AndroidManifest.xml b/tests/VoiceEnrollment/AndroidManifest.xml
new file mode 100644
index 0000000..6321222
--- /dev/null
+++ b/tests/VoiceEnrollment/AndroidManifest.xml
@@ -0,0 +1,16 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="com.android.test.voiceenrollment">
+
+    <application
+        android:permission="android.permission.MANAGE_VOICE_KEYPHRASES">
+        <activity android:name="TestEnrollmentActivity" android:label="Voice Enrollment Application"
+                  android:theme="@android:style/Theme.Material.Light.Voice">
+            <intent-filter>
+                <action android:name="com.android.intent.action.MANAGE_VOICE_KEYPHRASES" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </activity>
+        <meta-data android:name="android.voice_enrollment"
+            android:resource="@xml/enrollment_application"/>
+    </application>
+</manifest>
diff --git a/tests/VoiceEnrollment/res/xml/enrollment_application.xml b/tests/VoiceEnrollment/res/xml/enrollment_application.xml
new file mode 100644
index 0000000..710a0ac
--- /dev/null
+++ b/tests/VoiceEnrollment/res/xml/enrollment_application.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * 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.
+ */
+-->
+
+<voice-enrollment-application xmlns:android="http://schemas.android.com/apk/res/android"
+    android:searchKeyphraseId="101"
+    android:searchKeyphrase="Hello There"
+    android:searchKeyphraseSupportedLocales="en-US,en-GB,fr-FR,de-DE" />
diff --git a/tests/VoiceEnrollment/src/com/android/test/voiceenrollment/TestEnrollmentActivity.java b/tests/VoiceEnrollment/src/com/android/test/voiceenrollment/TestEnrollmentActivity.java
new file mode 100644
index 0000000..7fbd965
--- /dev/null
+++ b/tests/VoiceEnrollment/src/com/android/test/voiceenrollment/TestEnrollmentActivity.java
@@ -0,0 +1,23 @@
+/*
+ * 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 com.android.test.voiceenrollment;
+
+import android.app.Activity;
+
+public class TestEnrollmentActivity extends Activity {
+    // TODO(sansid): Add a test enrollment flow here.
+}
diff --git a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionService.java b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionService.java
index d40b05f..00c2c64 100644
--- a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionService.java
+++ b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionService.java
@@ -21,6 +21,8 @@
 import android.service.voice.VoiceInteractionService;
 import android.util.Log;
 
+import java.util.Arrays;
+
 public class MainInteractionService extends VoiceInteractionService {
     static final String TAG = "MainInteractionService";
 
@@ -28,6 +30,9 @@
     public void onCreate() {
         super.onCreate();
         Log.i(TAG, "Creating " + this);
+        Log.i(TAG, "Keyphrase enrollment error? " + getKeyphraseEnrollmentInfo().getParseError());
+        Log.i(TAG, "Keyphrase enrollment meta-data: "
+                + Arrays.toString(getKeyphraseEnrollmentInfo().getKeyphrases()));
     }
 
     @Override