Merge "Rework some of the voice interaction APIs."
diff --git a/Android.mk b/Android.mk
index 852247c..232f5bf 100644
--- a/Android.mk
+++ b/Android.mk
@@ -201,6 +201,7 @@
 	core/java/android/service/trust/ITrustAgentServiceCallback.aidl \
 	core/java/android/service/voice/IVoiceInteractionService.aidl \
 	core/java/android/service/voice/IVoiceInteractionSession.aidl \
+	core/java/android/service/voice/IVoiceInteractionSessionService.aidl \
 	core/java/android/service/wallpaper/IWallpaperConnection.aidl \
 	core/java/android/service/wallpaper/IWallpaperEngine.aidl \
 	core/java/android/service/wallpaper/IWallpaperService.aidl \
diff --git a/api/current.txt b/api/current.txt
index 4fef0d0..34b1ac8 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -1009,6 +1009,7 @@
     field public static final int selectedDateVerticalBar = 16843591; // 0x1010347
     field public static final int selectedWeekBackgroundColor = 16843586; // 0x1010342
     field public static final int sequence = 16843815; // 0x1010427
+    field public static final int sessionService = 16843850; // 0x101044a
     field public static final int settingsActivity = 16843301; // 0x1010225
     field public static final int shadowColor = 16843105; // 0x1010161
     field public static final int shadowDx = 16843106; // 0x1010162
@@ -1663,10 +1664,14 @@
     field public static final int decelerate_cubic = 17563651; // 0x10c0003
     field public static final int decelerate_quad = 17563649; // 0x10c0001
     field public static final int decelerate_quint = 17563653; // 0x10c0005
-    field public static final int fast_out_linear_in = 17563663; // 0x10c000f
-    field public static final int fast_out_slow_in = 17563661; // 0x10c000d
+    field public static final int fast_out_linear_in = 17563667; // 0x10c0013
+    field public static final int fast_out_slow_in = 17563665; // 0x10c0011
+    field public static final int l_resource_pad1 = 17563664; // 0x10c0010
+    field public static final int l_resource_pad2 = 17563663; // 0x10c000f
+    field public static final int l_resource_pad3 = 17563662; // 0x10c000e
+    field public static final int l_resource_pad4 = 17563661; // 0x10c000d
     field public static final int linear = 17563659; // 0x10c000b
-    field public static final int linear_out_slow_in = 17563662; // 0x10c000e
+    field public static final int linear_out_slow_in = 17563666; // 0x10c0012
     field public static final int overshoot = 17563656; // 0x10c0008
   }
 
@@ -4842,20 +4847,26 @@
   }
 
   public class VoiceInteractor {
-    method public android.app.VoiceInteractor.Request startCommand(android.app.VoiceInteractor.Callback, java.lang.String, android.os.Bundle);
-    method public android.app.VoiceInteractor.Request startConfirmation(android.app.VoiceInteractor.Callback, java.lang.String, android.os.Bundle);
+    method public boolean submitRequest(android.app.VoiceInteractor.Request);
     method public boolean[] supportsCommands(java.lang.String[]);
   }
 
-  public static class VoiceInteractor.Callback {
-    ctor public VoiceInteractor.Callback();
-    method public void onCancel(android.app.VoiceInteractor.Request);
-    method public void onCommandResult(android.app.VoiceInteractor.Request, android.os.Bundle);
-    method public void onConfirmationResult(android.app.VoiceInteractor.Request, boolean, android.os.Bundle);
+  public static class VoiceInteractor.CommandRequest extends android.app.VoiceInteractor.Request {
+    ctor public VoiceInteractor.CommandRequest(java.lang.String, android.os.Bundle);
+    method public void onCommandResult(android.os.Bundle);
   }
 
-  public static class VoiceInteractor.Request {
+  public static class VoiceInteractor.ConfirmationRequest extends android.app.VoiceInteractor.Request {
+    ctor public VoiceInteractor.ConfirmationRequest(java.lang.CharSequence, android.os.Bundle);
+    method public void onConfirmationResult(boolean, android.os.Bundle);
+  }
+
+  public static abstract class VoiceInteractor.Request {
+    ctor public VoiceInteractor.Request();
     method public void cancel();
+    method public android.app.Activity getActivity();
+    method public android.content.Context getContext();
+    method public void onCancel();
   }
 
   public final class WallpaperInfo implements android.os.Parcelable {
@@ -24919,7 +24930,7 @@
   public class VoiceInteractionService extends android.app.Service {
     ctor public VoiceInteractionService();
     method public android.os.IBinder onBind(android.content.Intent);
-    method public void startVoiceActivity(android.content.Intent, android.service.voice.VoiceInteractionSession);
+    method public void startVoiceActivity(android.content.Intent, android.os.Bundle);
     field public static final java.lang.String SERVICE_INTERFACE = "android.service.voice.VoiceInteractionService";
     field public static final java.lang.String SERVICE_META_DATA = "android.voice_interaction";
   }
@@ -24938,10 +24949,16 @@
 
   public static class VoiceInteractionSession.Request {
     method public void sendCancelResult();
-    method public void sendCommandResult(android.os.Bundle);
+    method public void sendCommandResult(boolean, android.os.Bundle);
     method public void sendConfirmResult(boolean, android.os.Bundle);
   }
 
+  public abstract class VoiceInteractionSessionService extends android.app.Service {
+    ctor public VoiceInteractionSessionService();
+    method public android.os.IBinder onBind(android.content.Intent);
+    method public abstract android.service.voice.VoiceInteractionSession onNewSession(android.os.Bundle);
+  }
+
 }
 
 package android.service.wallpaper {
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 197eaf8..8981c88 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -5454,7 +5454,7 @@
         mEmbeddedID = id;
         mLastNonConfigurationInstances = lastNonConfigurationInstances;
         mVoiceInteractor = voiceInteractor != null
-                ? new VoiceInteractor(this, voiceInteractor, Looper.myLooper()) : null;
+                ? new VoiceInteractor(this, this, voiceInteractor, Looper.myLooper()) : null;
 
         mWindow.setWindowManager(
                 (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
diff --git a/core/java/android/app/VoiceInteractor.java b/core/java/android/app/VoiceInteractor.java
index 6820dfd..6dc48b0 100644
--- a/core/java/android/app/VoiceInteractor.java
+++ b/core/java/android/app/VoiceInteractor.java
@@ -22,6 +22,7 @@
 import android.os.Looper;
 import android.os.Message;
 import android.os.RemoteException;
+import android.util.ArrayMap;
 import android.util.Log;
 import com.android.internal.app.IVoiceInteractor;
 import com.android.internal.app.IVoiceInteractorCallback;
@@ -39,50 +40,85 @@
     static final boolean DEBUG = true;
 
     final Context mContext;
+    final Activity mActivity;
     final IVoiceInteractor mInteractor;
     final HandlerCaller mHandlerCaller;
     final HandlerCaller.Callback mHandlerCallerCallback = new HandlerCaller.Callback() {
         @Override
         public void executeMessage(Message msg) {
             SomeArgs args = (SomeArgs)msg.obj;
+            Request request;
             switch (msg.what) {
                 case MSG_CONFIRMATION_RESULT:
+                    request = pullRequest((IVoiceInteractorRequest)args.arg1, true);
                     if (DEBUG) Log.d(TAG, "onConfirmResult: req="
-                            + ((IVoiceInteractorRequest)args.arg2).asBinder()
-                            + " confirmed=" + msg.arg1 + " result=" + args.arg3);
-                    ((Callback)args.arg1).onConfirmationResult(
-                            findRequest((IVoiceInteractorRequest)args.arg2),
-                            msg.arg1 != 0, (Bundle)args.arg3);
+                            + ((IVoiceInteractorRequest)args.arg1).asBinder() + "/" + request
+                            + " confirmed=" + msg.arg1 + " result=" + args.arg2);
+                    if (request != null) {
+                        ((ConfirmationRequest)request).onConfirmationResult(msg.arg1 != 0,
+                                (Bundle) args.arg2);
+                        request.clear();
+                    }
                     break;
                 case MSG_COMMAND_RESULT:
+                    request = pullRequest((IVoiceInteractorRequest)args.arg1, msg.arg1 != 0);
                     if (DEBUG) Log.d(TAG, "onCommandResult: req="
-                            + ((IVoiceInteractorRequest)args.arg2).asBinder()
+                            + ((IVoiceInteractorRequest)args.arg1).asBinder() + "/" + request
                             + " result=" + args.arg2);
-                    ((Callback)args.arg1).onCommandResult(
-                            findRequest((IVoiceInteractorRequest) args.arg2),
-                            (Bundle) args.arg3);
+                    if (request != null) {
+                        ((CommandRequest)request).onCommandResult((Bundle) args.arg2);
+                        if (msg.arg1 != 0) {
+                            request.clear();
+                        }
+                    }
                     break;
                 case MSG_CANCEL_RESULT:
+                    request = pullRequest((IVoiceInteractorRequest)args.arg1, true);
                     if (DEBUG) Log.d(TAG, "onCancelResult: req="
-                            + ((IVoiceInteractorRequest)args.arg2).asBinder());
-                    ((Callback)args.arg1).onCancel(
-                            findRequest((IVoiceInteractorRequest) args.arg2));
+                            + ((IVoiceInteractorRequest)args.arg1).asBinder() + "/" + request);
+                    if (request != null) {
+                        request.onCancel();
+                        request.clear();
+                    }
                     break;
             }
         }
     };
 
-    final WeakHashMap<IBinder, Request> mActiveRequests = new WeakHashMap<IBinder, Request>();
+    final IVoiceInteractorCallback.Stub mCallback = new IVoiceInteractorCallback.Stub() {
+        @Override
+        public void deliverConfirmationResult(IVoiceInteractorRequest request, boolean confirmed,
+                Bundle result) {
+            mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageIOO(
+                    MSG_CONFIRMATION_RESULT, confirmed ? 1 : 0, request, result));
+        }
+
+        @Override
+        public void deliverCommandResult(IVoiceInteractorRequest request, boolean complete,
+                Bundle result) {
+            mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageIOO(
+                    MSG_COMMAND_RESULT, complete ? 1 : 0, request, result));
+        }
+
+        @Override
+        public void deliverCancel(IVoiceInteractorRequest request) throws RemoteException {
+            mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageO(
+                    MSG_CANCEL_RESULT, request));
+        }
+    };
+
+    final ArrayMap<IBinder, Request> mActiveRequests = new ArrayMap<IBinder, Request>();
 
     static final int MSG_CONFIRMATION_RESULT = 1;
     static final int MSG_COMMAND_RESULT = 2;
     static final int MSG_CANCEL_RESULT = 3;
 
-    public static class Request {
-        final IVoiceInteractorRequest mRequestInterface;
+    public static abstract class Request {
+        IVoiceInteractorRequest mRequestInterface;
+        Context mContext;
+        Activity mActivity;
 
-        Request(IVoiceInteractorRequest requestInterface) {
-            mRequestInterface = requestInterface;
+        public Request() {
         }
 
         public void cancel() {
@@ -92,126 +128,130 @@
                 Log.w(TAG, "Voice interactor has died", e);
             }
         }
+
+        public Context getContext() {
+            return mContext;
+        }
+
+        public Activity getActivity() {
+            return mActivity;
+        }
+
+        public void onCancel() {
+        }
+
+        void clear() {
+            mRequestInterface = null;
+            mContext = null;
+            mActivity = null;
+        }
+
+        abstract IVoiceInteractorRequest submit(IVoiceInteractor interactor,
+                String packageName, IVoiceInteractorCallback callback) throws RemoteException;
     }
 
-    public static class Callback {
-        VoiceInteractor mInteractor;
+    public static class ConfirmationRequest extends Request {
+        final CharSequence mPrompt;
+        final Bundle mExtras;
 
-        final IVoiceInteractorCallback.Stub mWrapper = new IVoiceInteractorCallback.Stub() {
-            @Override
-            public void deliverConfirmationResult(IVoiceInteractorRequest request, boolean confirmed,
-                    Bundle result) {
-                mInteractor.mHandlerCaller.sendMessage(mInteractor.mHandlerCaller.obtainMessageIOOO(
-                        MSG_CONFIRMATION_RESULT, confirmed ? 1 : 0, Callback.this, request,
-                        result));
-            }
-
-            @Override
-            public void deliverCommandResult(IVoiceInteractorRequest request, Bundle result) {
-                mInteractor.mHandlerCaller.sendMessage(mInteractor.mHandlerCaller.obtainMessageOOO(
-                        MSG_COMMAND_RESULT, Callback.this, request, result));
-            }
-
-            @Override
-            public void deliverCancel(IVoiceInteractorRequest request) throws RemoteException {
-                mInteractor.mHandlerCaller.sendMessage(mInteractor.mHandlerCaller.obtainMessageOO(
-                        MSG_CANCEL_RESULT, Callback.this, request));
-            }
-        };
-
-        public void onConfirmationResult(Request request, boolean confirmed, Bundle result) {
+        /**
+         * Confirms an operation with the user via the trusted system
+         * VoiceInteractionService.  This allows an Activity to complete an unsafe operation that
+         * would require the user to touch the screen when voice interaction mode is not enabled.
+         * The result of the confirmation will be returned through an asynchronous call to
+         * either {@link #onConfirmationResult(boolean, android.os.Bundle)} or
+         * {@link #onCancel()}.
+         *
+         * <p>In some cases this may be a simple yes / no confirmation or the confirmation could
+         * include context information about how the action will be completed
+         * (e.g. booking a cab might include details about how long until the cab arrives)
+         * so the user can give a confirmation.
+         * @param prompt Optional confirmation text to read to the user as the action being
+         * confirmed.
+         * @param extras Additional optional information.
+         */
+        public ConfirmationRequest(CharSequence prompt, Bundle extras) {
+            mPrompt = prompt;
+            mExtras = extras;
         }
 
-        public void onCommandResult(Request request, Bundle result) {
+        public void onConfirmationResult(boolean confirmed, Bundle result) {
         }
 
-        public void onCancel(Request request) {
+        IVoiceInteractorRequest submit(IVoiceInteractor interactor, String packageName,
+                IVoiceInteractorCallback callback) throws RemoteException {
+            return interactor.startConfirmation(packageName, callback, mPrompt.toString(), mExtras);
         }
-    }
+   }
 
-    VoiceInteractor(Context context, IVoiceInteractor interactor, Looper looper) {
+    public static class CommandRequest extends Request {
+        final String mCommand;
+        final Bundle mArgs;
+
+        /**
+         * Execute a command using the trusted system VoiceInteractionService.
+         * This allows an Activity to request additional information from the user needed to
+         * complete an action (e.g. booking a table might have several possible times that the
+         * user could select from or an app might need the user to agree to a terms of service).
+         * The result of the confirmation will be returned through an asynchronous call to
+         * either {@link #onCommandResult(android.os.Bundle)} or
+         * {@link #onCancel()}.
+         *
+         * <p>The command is a string that describes the generic operation to be performed.
+         * The command will determine how the properties in extras are interpreted and the set of
+         * available commands is expected to grow over time.  An example might be
+         * "com.google.voice.commands.REQUEST_NUMBER_BAGS" to request the number of bags as part of
+         * airline check-in.  (This is not an actual working example.)
+         *
+         * @param command The desired command to perform.
+         * @param args Additional arguments to control execution of the command.
+         */
+        public CommandRequest(String command, Bundle args) {
+            mCommand = command;
+            mArgs = args;
+        }
+
+        public void onCommandResult(Bundle result) {
+        }
+
+        IVoiceInteractorRequest submit(IVoiceInteractor interactor, String packageName,
+                IVoiceInteractorCallback callback) throws RemoteException {
+            return interactor.startConfirmation(packageName, callback, mCommand, mArgs);
+        }
+   }
+
+    VoiceInteractor(Context context, Activity activity, IVoiceInteractor interactor,
+            Looper looper) {
         mContext = context;
+        mActivity = activity;
         mInteractor = interactor;
         mHandlerCaller = new HandlerCaller(context, looper, mHandlerCallerCallback, true);
     }
 
-    Request storeRequest(IVoiceInteractorRequest request) {
-        synchronized (mActiveRequests) {
-            Request req = new Request(request);
-            mActiveRequests.put(request.asBinder(), req);
-            return req;
-        }
-    }
-
-    Request findRequest(IVoiceInteractorRequest request) {
+    Request pullRequest(IVoiceInteractorRequest request, boolean complete) {
         synchronized (mActiveRequests) {
             Request req = mActiveRequests.get(request.asBinder());
-            if (req == null) {
-                throw new IllegalStateException("Received callback without active request: "
-                        + request);
+            if (req != null && complete) {
+                mActiveRequests.remove(request.asBinder());
             }
             return req;
         }
     }
 
-    /**
-     * Asynchronously confirms an operation with the user via the trusted system
-     * VoiceinteractionService.  This allows an Activity to complete an unsafe operation that
-     * would require the user to touch the screen when voice interaction mode is not enabled.
-     * The result of the confirmation will be returned by calling the
-     * {@link Callback#onConfirmationResult Callback.onConfirmationResult} method.
-     *
-     * In some cases this may be a simple yes / no confirmation or the confirmation could
-     * include context information about how the action will be completed
-     * (e.g. booking a cab might include details about how long until the cab arrives) so the user
-     * can give informed consent.
-     * @param callback Required callback target for interaction results.
-     * @param prompt Optional confirmation text to read to the user as the action being confirmed.
-     * @param extras Additional optional information.
-     * @return Returns a new {@link Request} object representing this operation.
-     */
-    public Request startConfirmation(Callback callback, String prompt, Bundle extras) {
+    public boolean submitRequest(Request request) {
         try {
-            callback.mInteractor = this;
-            Request req = storeRequest(mInteractor.startConfirmation(
-                    mContext.getOpPackageName(), callback.mWrapper, prompt, extras));
-            if (DEBUG) Log.d(TAG, "startConfirmation: req=" + req.mRequestInterface.asBinder()
-                    + " prompt=" + prompt + " extras=" + extras);
-            return req;
+            IVoiceInteractorRequest ireq = request.submit(mInteractor,
+                    mContext.getOpPackageName(), mCallback);
+            request.mRequestInterface = ireq;
+            request.mContext = mContext;
+            request.mActivity = mActivity;
+            synchronized (mActiveRequests) {
+                mActiveRequests.put(ireq.asBinder(), request);
+            }
+            return true;
         } catch (RemoteException e) {
-            throw new RuntimeException("Voice interactor has died", e);
-        }
-    }
-
-    /**
-     * Asynchronously executes a command using the trusted system VoiceinteractionService.
-     * This allows an Activity to request additional information from the user needed to
-     * complete an action (e.g. booking a table might have several possible times that the
-     * user could select from or an app might need the user to agree to a terms of service).
-     *
-     * The command is a string that describes the generic operation to be performed.
-     * The command will determine how the properties in extras are interpreted and the set of
-     * available commands is expected to grow over time.  An example might be
-     * "com.google.voice.commands.REQUEST_NUMBER_BAGS" to request the number of bags as part of
-     * airline check-in.  (This is not an actual working example.)
-     * The result of the command will be returned by calling the
-     * {@link Callback#onCommandResult Callback.onCommandResult} method.
-     *
-     * @param callback Required callback target for interaction results.
-     * @param command
-     * @param extras
-     * @return Returns a new {@link Request} object representing this operation.
-     */
-    public Request startCommand(Callback callback, String command, Bundle extras) {
-        try {
-            callback.mInteractor = this;
-            Request req = storeRequest(mInteractor.startCommand(
-                    mContext.getOpPackageName(), callback.mWrapper, command, extras));
-            if (DEBUG) Log.d(TAG, "startCommand: req=" + req.mRequestInterface.asBinder()
-                    + " command=" + command + " extras=" + extras);
-            return req;
-        } catch (RemoteException e) {
-            throw new RuntimeException("Voice interactor has died", e);
+            Log.w(TAG, "Remove voice interactor service died", e);
+            return false;
         }
     }
 
diff --git a/core/java/android/service/voice/IVoiceInteractionSessionService.aidl b/core/java/android/service/voice/IVoiceInteractionSessionService.aidl
new file mode 100644
index 0000000..2519442
--- /dev/null
+++ b/core/java/android/service/voice/IVoiceInteractionSessionService.aidl
@@ -0,0 +1,28 @@
+/*
+ * 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.os.Bundle;
+
+import android.service.voice.IVoiceInteractionSession;
+
+/**
+ * @hide
+ */
+oneway interface IVoiceInteractionSessionService {
+    void newSession(IBinder token, in Bundle args);
+}
diff --git a/core/java/android/service/voice/VoiceInteractionService.java b/core/java/android/service/voice/VoiceInteractionService.java
index ed93b74..d005890 100644
--- a/core/java/android/service/voice/VoiceInteractionService.java
+++ b/core/java/android/service/voice/VoiceInteractionService.java
@@ -21,6 +21,7 @@
 import android.app.Service;
 import android.content.Context;
 import android.content.Intent;
+import android.os.Bundle;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.ServiceManager;
@@ -50,12 +51,11 @@
 
     IVoiceInteractionManagerService mSystemService;
 
-    public void startVoiceActivity(Intent intent, VoiceInteractionSession session) {
+    public void startVoiceActivity(Intent intent, Bundle sessionArgs) {
         try {
-            int res = mSystemService.startVoiceActivity(intent,
+            mSystemService.startVoiceActivity(intent,
                     intent.resolveType(getContentResolver()),
-                    mInterface, session.mSession, session.mInteractor);
-            Instrumentation.checkStartActivityResult(res, intent);
+                    mInterface, sessionArgs);
         } catch (RemoteException e) {
         }
     }
diff --git a/core/java/android/service/voice/VoiceInteractionServiceInfo.java b/core/java/android/service/voice/VoiceInteractionServiceInfo.java
new file mode 100644
index 0000000..a909ead
--- /dev/null
+++ b/core/java/android/service/voice/VoiceInteractionServiceInfo.java
@@ -0,0 +1,125 @@
+/*
+ * 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.ComponentName;
+import android.content.pm.PackageManager;
+import android.content.pm.ServiceInfo;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.speech.RecognitionService;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.Xml;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+
+/** @hide */
+public class VoiceInteractionServiceInfo {
+    static final String TAG = "VoiceInteractionServiceInfo";
+
+    private String mParseError;
+
+    private ServiceInfo mServiceInfo;
+    private String mSessionService;
+    private String mSettingsActivity;
+
+    public VoiceInteractionServiceInfo(PackageManager pm, ComponentName comp)
+            throws PackageManager.NameNotFoundException {
+        this(pm, pm.getServiceInfo(comp, PackageManager.GET_META_DATA));
+    }
+
+    public VoiceInteractionServiceInfo(PackageManager pm, ServiceInfo si) {
+        if (!Manifest.permission.BIND_VOICE_INTERACTION.equals(si.permission)) {
+            mParseError = "Service does not require permission "
+                    + Manifest.permission.BIND_VOICE_INTERACTION;
+            return;
+        }
+
+        XmlResourceParser parser = null;
+        try {
+            parser = si.loadXmlMetaData(pm, VoiceInteractionService.SERVICE_META_DATA);
+            if (parser == null) {
+                mParseError = "No " + VoiceInteractionService.SERVICE_META_DATA
+                        + " meta-data for " + si.packageName;
+                return;
+            }
+
+            Resources res = pm.getResourcesForApplication(si.applicationInfo);
+
+            AttributeSet attrs = Xml.asAttributeSet(parser);
+
+            int type;
+            while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
+                    && type != XmlPullParser.START_TAG) {
+            }
+
+            String nodeName = parser.getName();
+            if (!"voice-interaction-service".equals(nodeName)) {
+                mParseError = "Meta-data does not start with voice-interaction-service tag";
+                return;
+            }
+
+            TypedArray array = res.obtainAttributes(attrs,
+                    com.android.internal.R.styleable.VoiceInteractionService);
+            mSessionService = array.getString(
+                    com.android.internal.R.styleable.VoiceInteractionService_sessionService);
+            mSettingsActivity = array.getString(
+                    com.android.internal.R.styleable.VoiceInteractionService_settingsActivity);
+            array.recycle();
+            if (mSessionService == null) {
+                mParseError = "No sessionService specified";
+                return;
+            }
+        } catch (XmlPullParserException e) {
+            mParseError = "Error parsing voice interation service meta-data: " + e;
+            Log.w(TAG, "error parsing voice interaction service meta-data", e);
+            return;
+        } catch (IOException e) {
+            mParseError = "Error parsing voice interation service meta-data: " + e;
+            Log.w(TAG, "error parsing voice interaction service meta-data", e);
+            return;
+        } catch (PackageManager.NameNotFoundException e) {
+            mParseError = "Error parsing voice interation service meta-data: " + e;
+            Log.w(TAG, "error parsing voice interaction service meta-data", e);
+            return;
+        } finally {
+            if (parser != null) parser.close();
+        }
+        mServiceInfo = si;
+    }
+
+    public String getParseError() {
+        return mParseError;
+    }
+
+    public ServiceInfo getServiceInfo() {
+        return mServiceInfo;
+    }
+
+    public String getSessionService() {
+        return mSessionService;
+    }
+
+    public String getSettingsActivity() {
+        return mSettingsActivity;
+    }
+}
diff --git a/core/java/android/service/voice/VoiceInteractionSession.java b/core/java/android/service/voice/VoiceInteractionSession.java
index 59544be..963b6b4 100644
--- a/core/java/android/service/voice/VoiceInteractionSession.java
+++ b/core/java/android/service/voice/VoiceInteractionSession.java
@@ -96,11 +96,11 @@
             }
         }
 
-        public void sendCommandResult(Bundle result) {
+        public void sendCommandResult(boolean complete, Bundle result) {
             try {
                 if (DEBUG) Log.d(TAG, "sendCommandResult: req=" + mInterface
                         + " result=" + result);
-                mCallback.deliverCommandResult(mInterface, result);
+                mCallback.deliverCommandResult(mInterface, complete, result);
             } catch (RemoteException e) {
             }
         }
diff --git a/core/java/android/service/voice/VoiceInteractionSessionService.java b/core/java/android/service/voice/VoiceInteractionSessionService.java
new file mode 100644
index 0000000..40e5bba
--- /dev/null
+++ b/core/java/android/service/voice/VoiceInteractionSessionService.java
@@ -0,0 +1,82 @@
+/**
+ * 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.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import com.android.internal.app.IVoiceInteractionManagerService;
+import com.android.internal.os.HandlerCaller;
+import com.android.internal.os.SomeArgs;
+
+public abstract class VoiceInteractionSessionService extends Service {
+
+    static final int MSG_NEW_SESSION = 1;
+
+    IVoiceInteractionManagerService mSystemService;
+
+    IVoiceInteractionSessionService mInterface = new IVoiceInteractionSessionService.Stub() {
+        public void newSession(IBinder token, Bundle args) {
+            mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageOO(MSG_NEW_SESSION,
+                    token, args));
+
+        }
+    };
+
+    HandlerCaller mHandlerCaller;
+    final HandlerCaller.Callback mHandlerCallerCallback = new HandlerCaller.Callback() {
+        @Override
+        public void executeMessage(Message msg) {
+            SomeArgs args = (SomeArgs)msg.obj;
+            switch (msg.what) {
+                case MSG_NEW_SESSION:
+                    doNewSession((IBinder)args.arg1, (Bundle)args.arg2);
+                    break;
+            }
+        }
+    };
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        mSystemService = IVoiceInteractionManagerService.Stub.asInterface(
+                ServiceManager.getService(Context.VOICE_INTERACTION_MANAGER_SERVICE));
+        mHandlerCaller = new HandlerCaller(this, Looper.myLooper(),
+                mHandlerCallerCallback, true);
+    }
+
+    public abstract VoiceInteractionSession onNewSession(Bundle args);
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return mInterface.asBinder();
+    }
+
+    void doNewSession(IBinder token, Bundle args) {
+        VoiceInteractionSession session = onNewSession(args);
+        try {
+            mSystemService.deliverNewSession(token, session.mSession, session.mInteractor);
+        } catch (RemoteException e) {
+        }
+    }
+}
diff --git a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
index e3c0728..3219ddd 100644
--- a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
+++ b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
@@ -17,12 +17,15 @@
 package com.android.internal.app;
 
 import android.content.Intent;
+import android.os.Bundle;
 
 import com.android.internal.app.IVoiceInteractor;
 import android.service.voice.IVoiceInteractionService;
 import android.service.voice.IVoiceInteractionSession;
 
 interface IVoiceInteractionManagerService {
-    int startVoiceActivity(in Intent intent, String resolvedType, IVoiceInteractionService service,
-            IVoiceInteractionSession session, IVoiceInteractor interactor);
+    void startVoiceActivity(in Intent intent, String resolvedType, IVoiceInteractionService service,
+            in Bundle sessionArgs);
+    int deliverNewSession(IBinder token, IVoiceInteractionSession session,
+            IVoiceInteractor interactor);
 }
diff --git a/core/java/com/android/internal/app/IVoiceInteractorCallback.aidl b/core/java/com/android/internal/app/IVoiceInteractorCallback.aidl
index f5392e9..c6f93e1 100644
--- a/core/java/com/android/internal/app/IVoiceInteractorCallback.aidl
+++ b/core/java/com/android/internal/app/IVoiceInteractorCallback.aidl
@@ -26,6 +26,6 @@
 oneway interface IVoiceInteractorCallback {
     void deliverConfirmationResult(IVoiceInteractorRequest request, boolean confirmed,
             in Bundle result);
-    void deliverCommandResult(IVoiceInteractorRequest request, in Bundle result);
+    void deliverCommandResult(IVoiceInteractorRequest request, boolean complete, in Bundle result);
     void deliverCancel(IVoiceInteractorRequest request);
 }
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 326485d..69440be 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -6279,6 +6279,7 @@
          its {@link android.service.voice.VoiceInteractionService#SERVICE_META_DATA} meta-data entry.
          Described here are the attributes that can be included in that tag. -->
     <declare-styleable name="VoiceInteractionService">
+        <attr name="sessionService" format="string" />
         <attr name="settingsActivity" />
     </declare-styleable>
 
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 6491b33..085b9c3 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2155,6 +2155,13 @@
   <public type="attr" name="colorPrimaryDark" />
   <public type="attr" name="colorAccent" />
   <public type="attr" name="nestedScrollingEnabled" />
+  <public type="attr" name="windowEnterTransition" />
+  <public type="attr" name="windowExitTransition" />
+  <public type="attr" name="windowSharedElementEnterTransition" />
+  <public type="attr" name="windowSharedElementExitTransition" />
+  <public type="attr" name="windowAllowExitTransitionOverlap" />
+  <public type="attr" name="windowAllowEnterTransitionOverlap" />
+  <public type="attr" name="sessionService" />
 
   <public-padding type="dimen" name="l_resource_pad" end="0x01050010" />
 
@@ -2385,17 +2392,14 @@
 
   <public type="style" name="Widget.Holo.Light.Button.Borderless" />
 
+  <public-padding type="interpolator" name="l_resource_pad" end="0x010c0010" />
+
   <!-- An interpolator which accelerates fast but decelerates slowly. -->
   <public type="interpolator" name="fast_out_slow_in" />
   <!-- An interpolator which starts with a peak non-zero velocity and decelerates slowly. -->
   <public type="interpolator" name="linear_out_slow_in" />
   <!-- An interpolator which accelerates fast and keeps accelerating until the end. -->
   <public type="interpolator" name="fast_out_linear_in" />
-  <public type="attr" name="windowEnterTransition" />
-  <public type="attr" name="windowExitTransition" />
-  <public type="attr" name="windowSharedElementEnterTransition" />
-  <public type="attr" name="windowSharedElementExitTransition" />
-  <public type="attr" name="windowAllowExitTransitionOverlap" />
-  <public type="attr" name="windowAllowEnterTransitionOverlap" />
+
   <public type="transition" name="no_transition" id="0x010f0000"/>
 </resources>
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index 9e2bcab..045c0f6 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -16,14 +16,18 @@
 
 package com.android.server.voiceinteraction;
 
+import android.Manifest;
 import android.app.ActivityManager;
 import android.content.ComponentName;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.PackageManager;
 import android.database.ContentObserver;
 import android.os.Binder;
+import android.os.Bundle;
 import android.os.Handler;
+import android.os.IBinder;
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.service.voice.IVoiceInteractionService;
@@ -134,9 +138,8 @@
         }
 
         @Override
-        public int startVoiceActivity(Intent intent, String resolvedType,
-                IVoiceInteractionService service,
-                IVoiceInteractionSession session, IVoiceInteractor interactor) {
+        public void startVoiceActivity(Intent intent, String resolvedType,
+                IVoiceInteractionService service, Bundle args) {
             synchronized (this) {
                 if (mImpl == null || service.asBinder() != mImpl.mService.asBinder()) {
                     throw new SecurityException(
@@ -146,8 +149,8 @@
                 final int callingUid = Binder.getCallingUid();
                 final long caller = Binder.clearCallingIdentity();
                 try {
-                    return mImpl.startVoiceActivityLocked(callingPid, callingUid,
-                            intent, resolvedType, session, interactor);
+                    mImpl.startVoiceActivityLocked(callingPid, callingUid,
+                            intent, resolvedType, args);
                 } finally {
                     Binder.restoreCallingIdentity(caller);
                 }
@@ -155,8 +158,43 @@
         }
 
         @Override
+        public int deliverNewSession(IBinder token, IVoiceInteractionSession session,
+                IVoiceInteractor interactor) {
+            synchronized (this) {
+                if (mImpl == null) {
+                    Slog.w(TAG, "deliverNewSession without running voice interaction service");
+                    return ActivityManager.START_CANCELED;
+                }
+                final int callingPid = Binder.getCallingPid();
+                final int callingUid = Binder.getCallingUid();
+                final long caller = Binder.clearCallingIdentity();
+                try {
+                    return mImpl.deliverNewSessionLocked(callingPid, callingUid, token, session,
+                            interactor);
+                } finally {
+                    Binder.restoreCallingIdentity(caller);
+                }
+            }
+
+        }
+
+        @Override
         public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-            mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG);
+            if (mContext.checkCallingOrSelfPermission(Manifest.permission.DUMP)
+                    != PackageManager.PERMISSION_GRANTED) {
+                pw.println("Permission Denial: can't dump PowerManager from from pid="
+                        + Binder.getCallingPid()
+                        + ", uid=" + Binder.getCallingUid());
+                return;
+            }
+            synchronized (this) {
+                pw.println("VOICE INTERACTION MANAGER (dumpsys voiceinteraction)\n");
+                if (mImpl == null) {
+                    pw.println("  (No active implementation)");
+                    return;
+                }
+                mImpl.dumpLocked(fd, pw, args);
+            }
         }
 
         class SettingsObserver extends ContentObserver {
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
index af8ae1e..6bbd1c1 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
@@ -16,6 +16,7 @@
 
 package com.android.server.voiceinteraction;
 
+import android.app.ActivityManager;
 import android.app.ActivityManagerNative;
 import android.app.IActivityManager;
 import android.content.ComponentName;
@@ -24,29 +25,40 @@
 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;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.service.voice.IVoiceInteractionService;
 import android.service.voice.IVoiceInteractionSession;
+import android.service.voice.IVoiceInteractionSessionService;
 import android.service.voice.VoiceInteractionService;
+import android.service.voice.VoiceInteractionServiceInfo;
 import android.util.Slog;
 import com.android.internal.app.IVoiceInteractor;
 
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
 class VoiceInteractionManagerServiceImpl {
     final static String TAG = "VoiceInteractionServiceManager";
 
+    final boolean mValid;
+
     final Context mContext;
     final Handler mHandler;
     final Object mLock;
     final int mUser;
     final ComponentName mComponent;
     final IActivityManager mAm;
+    final VoiceInteractionServiceInfo mInfo;
+    final ComponentName mSessionComponentName;
     boolean mBound = false;
     IVoiceInteractionService mService;
-    IVoiceInteractionSession mActiveSession;
-    IVoiceInteractor mActiveInteractor;
+
+    SessionConnection mActiveSession;
 
     final ServiceConnection mConnection = new ServiceConnection() {
         @Override
@@ -62,6 +74,72 @@
         }
     };
 
+    final class SessionConnection implements ServiceConnection {
+        final IBinder mToken = new Binder();
+        final Intent mIntent;
+        final String mResolvedType;
+        final Bundle mArgs;
+        boolean mBound;
+        IVoiceInteractionSessionService mService;
+        IVoiceInteractionSession mSession;
+        IVoiceInteractor mInteractor;
+
+        SessionConnection(Intent intent, String resolvedType, Bundle args) {
+            mIntent = intent;
+            mResolvedType = resolvedType;
+            mArgs = args;
+            Intent serviceIntent = new Intent(VoiceInteractionService.SERVICE_INTERFACE);
+            serviceIntent.setComponent(mSessionComponentName);
+            mBound = mContext.bindServiceAsUser(serviceIntent, this,
+                    Context.BIND_AUTO_CREATE, new UserHandle(mUser));
+            if (!mBound) {
+                Slog.w(TAG, "Failed binding to voice interaction session service " + mComponent);
+            }
+        }
+
+        @Override
+        public void onServiceConnected(ComponentName name, IBinder service) {
+            synchronized (mLock) {
+                mService = IVoiceInteractionSessionService.Stub.asInterface(service);
+                if (mActiveSession == this) {
+                    try {
+                        mService.newSession(mToken, mArgs);
+                    } catch (RemoteException e) {
+                        Slog.w(TAG, "Failed making new session", e);
+                    }
+                }
+            }
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName name) {
+            mService = null;
+        }
+
+        public void cancel() {
+            if (mBound) {
+                mContext.unbindService(this);
+                mBound = false;
+                mService = null;
+                mSession = null;
+                mInteractor = null;
+            }
+        }
+
+        public void dump(String prefix, PrintWriter pw) {
+            pw.print(prefix); pw.print("mToken="); pw.println(mToken);
+            pw.print(prefix); pw.print("mIntent="); pw.println(mIntent);
+                    pw.print(" mResolvedType="); pw.println(mResolvedType);
+            pw.print(prefix); pw.print("mArgs="); pw.println(mArgs);
+            pw.print(prefix); pw.print("mBound="); pw.println(mBound);
+            if (mBound) {
+                pw.print(prefix); pw.print("mService="); pw.println(mService);
+                pw.print(prefix); pw.print("mSession="); pw.println(mSession);
+                pw.print(prefix); pw.print("mInteractor="); pw.println(mInteractor);
+            }
+        }
+    };
+
     VoiceInteractionManagerServiceImpl(Context context, Handler handler, Object lock,
             int userHandle, ComponentName service) {
         mContext = context;
@@ -70,50 +148,85 @@
         mUser = userHandle;
         mComponent = service;
         mAm = ActivityManagerNative.getDefault();
+        VoiceInteractionServiceInfo info;
+        try {
+            info = new VoiceInteractionServiceInfo(context.getPackageManager(), service);
+        } catch (PackageManager.NameNotFoundException e) {
+            Slog.w(TAG, "Voice interaction service not found: " + service);
+            mInfo = null;
+            mSessionComponentName = null;
+            mValid = false;
+            return;
+        }
+        mInfo = info;
+        if (mInfo.getParseError() != null) {
+            Slog.w(TAG, "Bad voice interaction service: " + mInfo.getParseError());
+            mSessionComponentName = null;
+            mValid = false;
+            return;
+        }
+        mValid = true;
+        mSessionComponentName = new ComponentName(service.getPackageName(),
+                mInfo.getSessionService());
     }
 
-    public int startVoiceActivityLocked(int callingPid, int callingUid, Intent intent,
-            String resolvedType, IVoiceInteractionSession session, IVoiceInteractor interactor) {
-        if (session == null) {
-            throw new NullPointerException("session is null");
-        }
-        if (interactor == null) {
-            throw new NullPointerException("interactor is null");
-        }
+    public void startVoiceActivityLocked(int callingPid, int callingUid, Intent intent,
+            String resolvedType, Bundle args) {
         if (mActiveSession != null) {
-            // XXX cancel current session.
+            mActiveSession.cancel();
+            mActiveSession = null;
         }
+        mActiveSession = new SessionConnection(intent, resolvedType, args);
         intent.addCategory(Intent.CATEGORY_VOICE);
         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
-        mActiveSession = session;
-        mActiveInteractor = interactor;
+    }
+
+    public int deliverNewSessionLocked(int callingPid, int callingUid, IBinder token,
+            IVoiceInteractionSession session, IVoiceInteractor interactor) {
         try {
+            if (mActiveSession == null || token != mActiveSession.mToken) {
+                Slog.w(TAG, "deliverNewSession does not match active session");
+                return ActivityManager.START_CANCELED;
+            }
+            mActiveSession.mSession = session;
+            mActiveSession.mInteractor = interactor;
             return mAm.startVoiceActivity(mComponent.getPackageName(), callingPid, callingUid,
-                    intent, resolvedType, mActiveSession, mActiveInteractor,
+                    mActiveSession.mIntent, mActiveSession.mResolvedType,
+                    mActiveSession.mSession, mActiveSession.mInteractor,
                     0, null, null, null, mUser);
         } catch (RemoteException e) {
             throw new IllegalStateException("Unexpected remote error", e);
         }
     }
 
+    public void dumpLocked(FileDescriptor fd, PrintWriter pw, String[] args) {
+        if (!mValid) {
+            pw.print("  NOT VALID: ");
+            if (mInfo == null) {
+                pw.println("no info");
+            } else {
+                pw.println(mInfo.getParseError());
+            }
+            return;
+        }
+        pw.print("  mComponent="); pw.println(mComponent.flattenToShortString());
+        pw.print("  Session service="); pw.println(mInfo.getSessionService());
+        pw.print("  Settings activity="); pw.println(mInfo.getSettingsActivity());
+        pw.print("  mBound="); pw.print(mBound);  pw.print(" mService="); pw.println(mService);
+        if (mActiveSession != null) {
+            pw.println("  Active session:");
+            mActiveSession.dump("    ", pw);
+        }
+    }
+
     void startLocked() {
         Intent intent = new Intent(VoiceInteractionService.SERVICE_INTERFACE);
         intent.setComponent(mComponent);
-        try {
-            ServiceInfo si = mContext.getPackageManager().getServiceInfo(mComponent, 0);
-            if (!android.Manifest.permission.BIND_VOICE_INTERACTION.equals(si.permission)) {
-                Slog.w(TAG, "Not using voice interaction service " + mComponent
-                        + ": does not require permission "
-                        + android.Manifest.permission.BIND_VOICE_INTERACTION);
-                return;
-            }
-        } catch (PackageManager.NameNotFoundException e) {
-            Slog.w(TAG, "Unable to find voice interaction service: " + mComponent, e);
-            return;
-        }
-        mContext.bindServiceAsUser(intent, mConnection,
+        mBound = mContext.bindServiceAsUser(intent, mConnection,
                 Context.BIND_AUTO_CREATE, new UserHandle(mUser));
-        mBound = true;
+        if (!mBound) {
+            Slog.w(TAG, "Failed binding to voice interaction service " + mComponent);
+        }
     }
 
     void shutdownLocked() {
diff --git a/tests/VoiceInteraction/AndroidManifest.xml b/tests/VoiceInteraction/AndroidManifest.xml
index 9c5acf9..ac0f701 100644
--- a/tests/VoiceInteraction/AndroidManifest.xml
+++ b/tests/VoiceInteraction/AndroidManifest.xml
@@ -12,10 +12,16 @@
         <service android:name="MainInteractionService"
                 android:permission="android.permission.BIND_VOICE_INTERACTION"
                 android:process=":interactor">
+            <meta-data android:name="android.voice_interaction"
+                       android:resource="@xml/interaction_service" />
             <intent-filter>
                 <action android:name="android.service.voice.VoiceInteractionService" />
             </intent-filter>
         </service>
+        <service android:name="MainInteractionSessionService"
+                android:permission="android.permission.BIND_VOICE_INTERACTION"
+                android:process=":session">
+        </service>
         <activity android:name="TestInteractionActivity" android:label="Voice Interaction Target">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
diff --git a/tests/VoiceInteraction/res/xml/interaction_service.xml b/tests/VoiceInteraction/res/xml/interaction_service.xml
new file mode 100644
index 0000000..45bd994d
--- /dev/null
+++ b/tests/VoiceInteraction/res/xml/interaction_service.xml
@@ -0,0 +1,21 @@
+<?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-interaction-service xmlns:android="http://schemas.android.com/apk/res/android"
+    android:sessionService="com.android.test.voiceinteraction.MainInteractionSessionService" />
diff --git a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionService.java b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionService.java
index 35702f1..008d97b 100644
--- a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionService.java
+++ b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionService.java
@@ -31,8 +31,7 @@
 
     @Override
     public int onStartCommand(Intent intent, int flags, int startId) {
-        startVoiceActivity(new Intent(this, TestInteractionActivity.class),
-                new MainInteractionSession(this));
+        startVoiceActivity(new Intent(this, TestInteractionActivity.class), null);
         stopSelf(startId);
         return START_NOT_STICKY;
     }
diff --git a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionSession.java b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionSession.java
index adc0df4..0fc563b 100644
--- a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionSession.java
+++ b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionSession.java
@@ -24,8 +24,11 @@
 public class MainInteractionSession extends VoiceInteractionSession {
     static final String TAG = "MainInteractionSession";
 
-    MainInteractionSession(Context context) {
+    final Bundle mArgs;
+
+    MainInteractionSession(Context context, Bundle args) {
         super(context);
+        mArgs = args;
     }
 
     @Override
@@ -42,7 +45,7 @@
     @Override
     public void onCommand(Caller caller, Request request, String command, Bundle extras) {
         Log.i(TAG, "onCommand: command=" + command + " extras=" + extras);
-        request.sendCommandResult(null);
+        request.sendCommandResult(true, null);
     }
 
     @Override
diff --git a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionSessionService.java b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionSessionService.java
new file mode 100644
index 0000000..8864d71
--- /dev/null
+++ b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionSessionService.java
@@ -0,0 +1,28 @@
+/*
+ * 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.voiceinteraction;
+
+import android.os.Bundle;
+import android.service.voice.VoiceInteractionSession;
+import android.service.voice.VoiceInteractionSessionService;
+
+public class MainInteractionSessionService extends VoiceInteractionSessionService {
+    @Override
+    public VoiceInteractionSession onNewSession(Bundle args) {
+        return new MainInteractionSession(this, args);
+    }
+}
diff --git a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/TestInteractionActivity.java b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/TestInteractionActivity.java
index 016a80e..9c772ff 100644
--- a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/TestInteractionActivity.java
+++ b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/TestInteractionActivity.java
@@ -30,28 +30,30 @@
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
 
-        setContentView(R.layout.test_interaction);
         if (!isVoiceInteraction()) {
             Log.w(TAG, "Not running as a voice interaction!");
             finish();
             return;
         }
 
+        setContentView(R.layout.test_interaction);
+
         mInteractor = getVoiceInteractor();
-        mInteractor.startConfirmation(new VoiceInteractor.Callback() {
+        VoiceInteractor.ConfirmationRequest req = new VoiceInteractor.ConfirmationRequest(
+                "This is a confirmation", null) {
             @Override
-            public void onConfirmationResult(VoiceInteractor.Request request, boolean confirmed,
-                    Bundle result) {
-                Log.i(TAG, "Confirmation result: confirmed=" + confirmed + " result=" + result);
-                finish();
+            public void onCancel() {
+                Log.i(TAG, "Canceled!");
+                getActivity().finish();
             }
 
             @Override
-            public void onCancel(VoiceInteractor.Request request) {
-                Log.i(TAG, "Canceled!");
-                finish();
+            public void onConfirmationResult(boolean confirmed, Bundle result) {
+                Log.i(TAG, "Confirmation result: confirmed=" + confirmed + " result=" + result);
+                getActivity().finish();
             }
-        }, "This is a confirmation", null);
+        };
+        mInteractor.submitRequest(req);
     }
 
     @Override