Initial implementation of new voice interaction API.

This gives a basic working implementation of a persist
running service that can start a voice interaction when
it wants, with the target activity(s) able to go through
the protocol to interact with it.  It may even work when
the screen is off by putting the activity manager in the
correct state to act like the screen is on.

Includes a sample app that is a voice interation service
and also has an activity it can launch.

Now that I have this initial implementation, I think I
want to rework some aspects of the API.

Change-Id: I7646d0af8fb4ac768c63a18fe3de43f8091f60e9
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 599a608..197eaf8 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -22,6 +22,7 @@
 import android.util.ArrayMap;
 import android.util.SuperNotCalledException;
 import android.widget.Toolbar;
+import com.android.internal.app.IVoiceInteractor;
 import com.android.internal.app.WindowDecorActionBar;
 import com.android.internal.app.ToolbarActionBar;
 import com.android.internal.policy.PolicyManager;
@@ -726,6 +727,8 @@
     /*package*/ ActionBar mActionBar = null;
     private boolean mEnableDefaultActionBarUp;
 
+    private VoiceInteractor mVoiceInteractor;
+
     private CharSequence mTitle;
     private int mTitleColor = 0;
 
@@ -1134,6 +1137,23 @@
     }
 
     /**
+     * Check whether this activity is running as part of a voice interaction with the user.
+     * If true, it should perform its interaction with the user through the
+     * {@link VoiceInteractor} returned by {@link #getVoiceInteractor}.
+     */
+    public boolean isVoiceInteraction() {
+        return mVoiceInteractor != null;
+    }
+
+    /**
+     * Retrieve the active {@link VoiceInteractor} that the user is going through to
+     * interact with this activity.
+     */
+    public VoiceInteractor getVoiceInteractor() {
+        return mVoiceInteractor;
+    }
+
+    /**
      * This is called for activities that set launchMode to "singleTop" in
      * their package, or if a client used the {@link Intent#FLAG_ACTIVITY_SINGLE_TOP}
      * flag when calling {@link #startActivity}.  In either case, when the
@@ -5397,7 +5417,7 @@
             NonConfigurationInstances lastNonConfigurationInstances,
             Configuration config) {
         attach(context, aThread, instr, token, ident, application, intent, info, title, parent, id,
-                lastNonConfigurationInstances, config, null);
+                lastNonConfigurationInstances, config, null, null);
     }
 
     final void attach(Context context, ActivityThread aThread,
@@ -5405,7 +5425,7 @@
             Application application, Intent intent, ActivityInfo info,
             CharSequence title, Activity parent, String id,
             NonConfigurationInstances lastNonConfigurationInstances,
-            Configuration config, Bundle options) {
+            Configuration config, Bundle options, IVoiceInteractor voiceInteractor) {
         attachBaseContext(context);
 
         mFragments.attachActivity(this, mContainer, null);
@@ -5433,6 +5453,8 @@
         mParent = parent;
         mEmbeddedID = id;
         mLastNonConfigurationInstances = lastNonConfigurationInstances;
+        mVoiceInteractor = voiceInteractor != null
+                ? new VoiceInteractor(this, voiceInteractor, Looper.myLooper()) : null;
 
         mWindow.setWindowManager(
                 (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index c027e99..018e949 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -76,6 +76,13 @@
     public static final String META_HOME_ALTERNATE = "android.app.home.alternate";
 
     /**
+     * Result for IActivityManager.startActivity: trying to start an activity under voice
+     * control when that activity does not support the VOICE category.
+     * @hide
+     */
+    public static final int START_NOT_VOICE_COMPATIBLE = -7;
+
+    /**
      * Result for IActivityManager.startActivity: an error where the
      * start had to be canceled.
      * @hide
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index 10831f2..b1c37de 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -43,9 +43,11 @@
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.StrictMode;
+import android.service.voice.IVoiceInteractionSession;
 import android.text.TextUtils;
 import android.util.Log;
 import android.util.Singleton;
+import com.android.internal.app.IVoiceInteractor;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -242,6 +244,33 @@
             return true;
         }
 
+        case START_VOICE_ACTIVITY_TRANSACTION:
+        {
+            data.enforceInterface(IActivityManager.descriptor);
+            String callingPackage = data.readString();
+            int callingPid = data.readInt();
+            int callingUid = data.readInt();
+            Intent intent = Intent.CREATOR.createFromParcel(data);
+            String resolvedType = data.readString();
+            IVoiceInteractionSession session = IVoiceInteractionSession.Stub.asInterface(
+                    data.readStrongBinder());
+            IVoiceInteractor interactor = IVoiceInteractor.Stub.asInterface(
+                    data.readStrongBinder());
+            int startFlags = data.readInt();
+            String profileFile = data.readString();
+            ParcelFileDescriptor profileFd = data.readInt() != 0
+                    ? ParcelFileDescriptor.CREATOR.createFromParcel(data) : null;
+            Bundle options = data.readInt() != 0
+                    ? Bundle.CREATOR.createFromParcel(data) : null;
+            int userId = data.readInt();
+            int result = startVoiceActivity(callingPackage, callingPid, callingUid,
+                    intent, resolvedType, session, interactor, startFlags,
+                    profileFile, profileFd, options, userId);
+            reply.writeNoException();
+            reply.writeInt(result);
+            return true;
+        }
+
         case START_NEXT_MATCHING_ACTIVITY_TRANSACTION:
         {
             data.enforceInterface(IActivityManager.descriptor);
@@ -2323,6 +2352,42 @@
         data.recycle();
         return result;
     }
+    public int startVoiceActivity(String callingPackage, int callingPid, int callingUid,
+            Intent intent, String resolvedType, IVoiceInteractionSession session,
+            IVoiceInteractor interactor, int startFlags, String profileFile,
+            ParcelFileDescriptor profileFd, Bundle options, int userId) throws RemoteException {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        data.writeString(callingPackage);
+        data.writeInt(callingPid);
+        data.writeInt(callingUid);
+        intent.writeToParcel(data, 0);
+        data.writeString(resolvedType);
+        data.writeStrongBinder(session.asBinder());
+        data.writeStrongBinder(interactor.asBinder());
+        data.writeInt(startFlags);
+        data.writeString(profileFile);
+        if (profileFd != null) {
+            data.writeInt(1);
+            profileFd.writeToParcel(data, Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
+        } else {
+            data.writeInt(0);
+        }
+        if (options != null) {
+            data.writeInt(1);
+            options.writeToParcel(data, 0);
+        } else {
+            data.writeInt(0);
+        }
+        data.writeInt(userId);
+        mRemote.transact(START_VOICE_ACTIVITY_TRANSACTION, data, reply, 0);
+        reply.readException();
+        int result = reply.readInt();
+        reply.recycle();
+        data.recycle();
+        return result;
+    }
     public boolean startNextMatchingActivity(IBinder callingActivity,
             Intent intent, Bundle options) throws RemoteException {
         Parcel data = Parcel.obtain();
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 4562d6e..7dc21b4 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -94,6 +94,7 @@
 import android.renderscript.RenderScript;
 import android.security.AndroidKeyStoreProvider;
 
+import com.android.internal.app.IVoiceInteractor;
 import com.android.internal.os.BinderInternal;
 import com.android.internal.os.RuntimeInit;
 import com.android.internal.os.SamplingProfilerIntegration;
@@ -265,6 +266,7 @@
         IBinder token;
         int ident;
         Intent intent;
+        IVoiceInteractor voiceInteractor;
         Bundle state;
         Activity activity;
         Window window;
@@ -603,6 +605,7 @@
         // activity itself back to the activity manager. (matters more with ipc)
         public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident,
                 ActivityInfo info, Configuration curConfig, CompatibilityInfo compatInfo,
+                IVoiceInteractor voiceInteractor,
                 int procState, Bundle state, List<ResultInfo> pendingResults,
                 List<Intent> pendingNewIntents, boolean notResumed, boolean isForward,
                 String profileName, ParcelFileDescriptor profileFd, boolean autoStopProfiler,
@@ -615,6 +618,7 @@
             r.token = token;
             r.ident = ident;
             r.intent = intent;
+            r.voiceInteractor = voiceInteractor;
             r.activityInfo = info;
             r.compatInfo = compatInfo;
             r.state = state;
@@ -2197,7 +2201,8 @@
                         + r.activityInfo.name + " with config " + config);
                 activity.attach(appContext, this, getInstrumentation(), r.token,
                         r.ident, app, r.intent, r.activityInfo, title, r.parent,
-                        r.embeddedID, r.lastNonConfigurationInstances, config, options);
+                        r.embeddedID, r.lastNonConfigurationInstances, config, options,
+                        r.voiceInteractor);
 
                 if (customIntent != null) {
                     activity.mIntent = customIntent;
diff --git a/core/java/android/app/ApplicationThreadNative.java b/core/java/android/app/ApplicationThreadNative.java
index f1c632e..fcc7f8e 100644
--- a/core/java/android/app/ApplicationThreadNative.java
+++ b/core/java/android/app/ApplicationThreadNative.java
@@ -33,6 +33,7 @@
 import android.os.IBinder;
 import android.os.Parcel;
 import android.os.ParcelFileDescriptor;
+import com.android.internal.app.IVoiceInteractor;
 
 import java.io.FileDescriptor;
 import java.io.IOException;
@@ -136,6 +137,8 @@
             ActivityInfo info = ActivityInfo.CREATOR.createFromParcel(data);
             Configuration curConfig = Configuration.CREATOR.createFromParcel(data);
             CompatibilityInfo compatInfo = CompatibilityInfo.CREATOR.createFromParcel(data);
+            IVoiceInteractor voiceInteractor = IVoiceInteractor.Stub.asInterface(
+                    data.readStrongBinder());
             int procState = data.readInt();
             Bundle state = data.readBundle();
             List<ResultInfo> ri = data.createTypedArrayList(ResultInfo.CREATOR);
@@ -147,7 +150,8 @@
                     ? ParcelFileDescriptor.CREATOR.createFromParcel(data) : null;
             boolean autoStopProfiler = data.readInt() != 0;
             Bundle resumeArgs = data.readBundle();
-            scheduleLaunchActivity(intent, b, ident, info, curConfig, compatInfo, procState, state,
+            scheduleLaunchActivity(intent, b, ident, info, curConfig, compatInfo,
+                    voiceInteractor, procState, state,
                     ri, pi, notResumed, isForward, profileName, profileFd, autoStopProfiler,
                     resumeArgs);
             return true;
@@ -735,6 +739,7 @@
 
     public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident,
             ActivityInfo info, Configuration curConfig, CompatibilityInfo compatInfo,
+            IVoiceInteractor voiceInteractor,
             int procState, Bundle state, List<ResultInfo> pendingResults,
             List<Intent> pendingNewIntents, boolean notResumed, boolean isForward,
             String profileName, ParcelFileDescriptor profileFd, boolean autoStopProfiler,
@@ -748,6 +753,7 @@
         info.writeToParcel(data, 0);
         curConfig.writeToParcel(data, 0);
         compatInfo.writeToParcel(data, 0);
+        data.writeStrongBinder(voiceInteractor != null ? voiceInteractor.asBinder() : null);
         data.writeInt(procState);
         data.writeBundle(state);
         data.writeTypedList(pendingResults);
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index 52003f1..6b94c4e 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -47,6 +47,8 @@
 import android.os.Parcelable;
 import android.os.RemoteException;
 import android.os.StrictMode;
+import android.service.voice.IVoiceInteractionSession;
+import com.android.internal.app.IVoiceInteractor;
 
 import java.util.List;
 
@@ -77,6 +79,10 @@
             IntentSender intent, Intent fillInIntent, String resolvedType,
             IBinder resultTo, String resultWho, int requestCode,
             int flagsMask, int flagsValues, Bundle options) throws RemoteException;
+    public int startVoiceActivity(String callingPackage, int callingPid, int callingUid,
+            Intent intent, String resolvedType, IVoiceInteractionSession session,
+            IVoiceInteractor interactor, int flags, String profileFile,
+            ParcelFileDescriptor profileFd, Bundle options, int userId) throws RemoteException;
     public boolean startNextMatchingActivity(IBinder callingActivity,
             Intent intent, Bundle options) throws RemoteException;
     public boolean finishActivity(IBinder token, int code, Intent data, boolean finishTask)
@@ -733,4 +739,5 @@
     int STOP_LOCK_TASK_MODE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+215;
     int IS_IN_LOCK_TASK_MODE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+216;
     int SET_ACTIVITY_LABEL_ICON_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+217;
+    int START_VOICE_ACTIVITY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+218;
 }
diff --git a/core/java/android/app/IApplicationThread.java b/core/java/android/app/IApplicationThread.java
index ac8ac8f..f290e94 100644
--- a/core/java/android/app/IApplicationThread.java
+++ b/core/java/android/app/IApplicationThread.java
@@ -31,6 +31,8 @@
 import android.os.RemoteException;
 import android.os.IBinder;
 import android.os.IInterface;
+import android.service.voice.IVoiceInteractionSession;
+import com.android.internal.app.IVoiceInteractor;
 
 import java.io.FileDescriptor;
 import java.util.List;
@@ -55,8 +57,9 @@
     void scheduleSendResult(IBinder token, List<ResultInfo> results) throws RemoteException;
     void scheduleLaunchActivity(Intent intent, IBinder token, int ident,
             ActivityInfo info, Configuration curConfig, CompatibilityInfo compatInfo,
-            int procState, Bundle state, List<ResultInfo> pendingResults,
-            List<Intent> pendingNewIntents, boolean notResumed, boolean isForward,
+            IVoiceInteractor voiceInteractor, int procState, Bundle state,
+            List<ResultInfo> pendingResults, List<Intent> pendingNewIntents, boolean notResumed,
+            boolean isForward,
             String profileName, ParcelFileDescriptor profileFd, boolean autoStopProfiler,
             Bundle resumeArgs)
             throws RemoteException;
diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java
index 028fa68..e58ccb8 100644
--- a/core/java/android/app/Instrumentation.java
+++ b/core/java/android/app/Instrumentation.java
@@ -1428,7 +1428,7 @@
     }
 
     /**
-     * Like {@link #execStartActivity(Context, IBinder, IBinder, Activity, Intent, int)},
+     * Like {@link #execStartActivity},
      * but accepts an array of activities to be started.  Note that active
      * {@link ActivityMonitor} objects only match against the first activity in
      * the array.
@@ -1442,7 +1442,7 @@
     }
 
     /**
-     * Like {@link #execStartActivity(Context, IBinder, IBinder, Activity, Intent, int)},
+     * Like {@link #execStartActivity},
      * but accepts an array of activities to be started.  Note that active
      * {@link ActivityMonitor} objects only match against the first activity in
      * the array.
@@ -1545,8 +1545,7 @@
     }
 
     /**
-     * Like {@link #execStartActivity(Context, IBinder, IBinder, Activity, Intent, int)},
-     * but for starting as a particular user.
+     * Like {@link #execStartActivity}, but for starting as a particular user.
      *
      * @param who The Context from which the activity is being started.
      * @param contextThread The main thread of the Context from which the activity
@@ -1616,7 +1615,8 @@
         mUiAutomationConnection = uiAutomationConnection;
     }
 
-    /*package*/ static void checkStartActivityResult(int res, Object intent) {
+    /** @hide */
+    public static void checkStartActivityResult(int res, Object intent) {
         if (res >= ActivityManager.START_SUCCESS) {
             return;
         }
@@ -1640,6 +1640,9 @@
             case ActivityManager.START_NOT_ACTIVITY:
                 throw new IllegalArgumentException(
                         "PendingIntent is not an activity");
+            case ActivityManager.START_NOT_VOICE_COMPATIBLE:
+                throw new SecurityException(
+                        "Starting under voice control not allowed for: " + intent);
             default:
                 throw new AndroidRuntimeException("Unknown error code "
                         + res + " when starting " + intent);
diff --git a/core/java/android/app/VoiceInteractor.java b/core/java/android/app/VoiceInteractor.java
new file mode 100644
index 0000000..6820dfd
--- /dev/null
+++ b/core/java/android/app/VoiceInteractor.java
@@ -0,0 +1,235 @@
+/*
+ * 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.app;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+import android.util.Log;
+import com.android.internal.app.IVoiceInteractor;
+import com.android.internal.app.IVoiceInteractorCallback;
+import com.android.internal.app.IVoiceInteractorRequest;
+import com.android.internal.os.HandlerCaller;
+import com.android.internal.os.SomeArgs;
+
+import java.util.WeakHashMap;
+
+/**
+ * Interface for an {@link Activity} to interact with the user through voice.
+ */
+public class VoiceInteractor {
+    static final String TAG = "VoiceInteractor";
+    static final boolean DEBUG = true;
+
+    final Context mContext;
+    final IVoiceInteractor mInteractor;
+    final 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_CONFIRMATION_RESULT:
+                    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);
+                    break;
+                case MSG_COMMAND_RESULT:
+                    if (DEBUG) Log.d(TAG, "onCommandResult: req="
+                            + ((IVoiceInteractorRequest)args.arg2).asBinder()
+                            + " result=" + args.arg2);
+                    ((Callback)args.arg1).onCommandResult(
+                            findRequest((IVoiceInteractorRequest) args.arg2),
+                            (Bundle) args.arg3);
+                    break;
+                case MSG_CANCEL_RESULT:
+                    if (DEBUG) Log.d(TAG, "onCancelResult: req="
+                            + ((IVoiceInteractorRequest)args.arg2).asBinder());
+                    ((Callback)args.arg1).onCancel(
+                            findRequest((IVoiceInteractorRequest) args.arg2));
+                    break;
+            }
+        }
+    };
+
+    final WeakHashMap<IBinder, Request> mActiveRequests = new WeakHashMap<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;
+
+        Request(IVoiceInteractorRequest requestInterface) {
+            mRequestInterface = requestInterface;
+        }
+
+        public void cancel() {
+            try {
+                mRequestInterface.cancel();
+            } catch (RemoteException e) {
+                Log.w(TAG, "Voice interactor has died", e);
+            }
+        }
+    }
+
+    public static class Callback {
+        VoiceInteractor mInteractor;
+
+        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) {
+        }
+
+        public void onCommandResult(Request request, Bundle result) {
+        }
+
+        public void onCancel(Request request) {
+        }
+    }
+
+    VoiceInteractor(Context context, IVoiceInteractor interactor, Looper looper) {
+        mContext = context;
+        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) {
+        synchronized (mActiveRequests) {
+            Request req = mActiveRequests.get(request.asBinder());
+            if (req == null) {
+                throw new IllegalStateException("Received callback without active request: "
+                        + request);
+            }
+            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) {
+        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;
+        } 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);
+        }
+    }
+
+    /**
+     * Queries the supported commands available from the VoiceinteractionService.
+     * The command is a string that describes the generic operation to be performed.
+     * 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 commands
+     */
+    public boolean[] supportsCommands(String[] commands) {
+        try {
+            boolean[] res = mInteractor.supportsCommands(mContext.getOpPackageName(), commands);
+            if (DEBUG) Log.d(TAG, "supportsCommands: cmds=" + commands + " res=" + res);
+            return res;
+        } catch (RemoteException e) {
+            throw new RuntimeException("Voice interactor has died", e);
+        }
+    }
+}
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index cbb6cf5..de223a3 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -2442,6 +2442,14 @@
     public static final String APPWIDGET_SERVICE = "appwidget";
 
     /**
+     * Official published name of the (internal) voice interaction manager service.
+     *
+     * @hide
+     * @see #getSystemService
+     */
+    public static final String VOICE_INTERACTION_MANAGER_SERVICE = "voiceinteraction";
+
+    /**
      * Use with {@link #getSystemService} to retrieve an
      * {@link android.app.backup.IBackupManager IBackupManager} for communicating
      * with the backup mechanism.
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index c0f04af..ae5437b 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -2804,6 +2804,14 @@
     @SdkConstant(SdkConstantType.INTENT_CATEGORY)
     public static final String CATEGORY_BROWSABLE = "android.intent.category.BROWSABLE";
     /**
+     * Categories for activities that can participate in voice interaction.
+     * An activity that supports this category must be prepared to run with
+     * no UI shown at all (though in some case it may have a UI shown), and
+     * rely on {@link android.app.VoiceInteractor} to interact with the user.
+     */
+    @SdkConstant(SdkConstantType.INTENT_CATEGORY)
+    public static final String CATEGORY_VOICE = "android.intent.category.VOICE";
+    /**
      * Set if the activity should be considered as an alternative action to
      * the data the user is currently viewing.  See also
      * {@link #CATEGORY_SELECTED_ALTERNATIVE} for an alternative action that
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index ae0899f..488e25f 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -74,6 +74,9 @@
 
     ActivityInfo getActivityInfo(in ComponentName className, int flags, int userId);
 
+    boolean activitySupportsIntent(in ComponentName className, in Intent intent,
+            String resolvedType);
+
     ActivityInfo getReceiverInfo(in ComponentName className, int flags, int userId);
 
     ServiceInfo getServiceInfo(in ComponentName className, int flags, int userId);
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index d981cc1..484a2a1 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -1235,7 +1235,6 @@
      */
     @SdkConstant(SdkConstantType.FEATURE)
     public static final String FEATURE_LIVE_WALLPAPER = "android.software.live_wallpaper";
-
     /**
      * Feature for {@link #getSystemAvailableFeatures} and
      * {@link #hasSystemFeature}: The device supports app widgets.
@@ -1244,6 +1243,17 @@
     public static final String FEATURE_APP_WIDGETS = "android.software.app_widgets";
 
     /**
+     * @hide
+     * Feature for {@link #getSystemAvailableFeatures} and
+     * {@link #hasSystemFeature}: The device supports
+     * {@link android.service.voice.VoiceInteractionService} and
+     * {@link android.app.VoiceInteractor}.
+     */
+    @SdkConstant(SdkConstantType.FEATURE)
+    public static final String FEATURE_VOICE_RECOGNIZERS = "android.software.voice_recognizers";
+
+
+    /**
      * Feature for {@link #getSystemAvailableFeatures} and
      * {@link #hasSystemFeature}: The device supports a home screen that is replaceable
      * by third party applications.
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 7f9f862..691317b 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -3311,6 +3311,12 @@
                 "input_method_selector_visibility";
 
         /**
+         * The currently selected voice interaction service flattened ComponentName.
+         * @hide
+         */
+        public static final String VOICE_INTERACTION_SERVICE = "voice_interaction_service";
+
+        /**
          * bluetooth HCI snoop log configuration
          * @hide
          */
diff --git a/core/java/android/service/voice/IVoiceInteractionService.aidl b/core/java/android/service/voice/IVoiceInteractionService.aidl
new file mode 100644
index 0000000..e9e2f4c
--- /dev/null
+++ b/core/java/android/service/voice/IVoiceInteractionService.aidl
@@ -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 android.service.voice;
+
+/**
+ * @hide
+ */
+oneway interface IVoiceInteractionService {
+}
diff --git a/core/java/android/service/voice/IVoiceInteractionSession.aidl b/core/java/android/service/voice/IVoiceInteractionSession.aidl
new file mode 100644
index 0000000..7dbf66b
--- /dev/null
+++ b/core/java/android/service/voice/IVoiceInteractionSession.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 com.android.internal.app.IVoiceInteractorCallback;
+import com.android.internal.app.IVoiceInteractorRequest;
+
+/**
+ * @hide
+ */
+interface IVoiceInteractionSession {
+}
diff --git a/core/java/android/service/voice/VoiceInteractionService.java b/core/java/android/service/voice/VoiceInteractionService.java
new file mode 100644
index 0000000..ed93b74
--- /dev/null
+++ b/core/java/android/service/voice/VoiceInteractionService.java
@@ -0,0 +1,77 @@
+/**
+ * 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.annotation.SdkConstant;
+import android.app.Instrumentation;
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import com.android.internal.app.IVoiceInteractionManagerService;
+
+public class VoiceInteractionService extends Service {
+    /**
+     * The {@link Intent} that must be declared as handled by the service.
+     * To be supported, the service must also require the
+     * {@link android.Manifest.permission#BIND_VOICE_INTERACTION} permission so
+     * that other applications can not abuse it.
+     */
+    @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
+    public static final String SERVICE_INTERFACE =
+            "android.service.voice.VoiceInteractionService";
+
+    /**
+     * Name under which a VoiceInteractionService component publishes information about itself.
+     * This meta-data should reference an XML resource containing a
+     * <code>&lt;{@link
+     * android.R.styleable#VoiceInteractionService voice-interaction-service}&gt;</code> tag.
+     */
+    public static final String SERVICE_META_DATA = "android.voice_interaction";
+
+    IVoiceInteractionService mInterface = new IVoiceInteractionService.Stub() {
+    };
+
+    IVoiceInteractionManagerService mSystemService;
+
+    public void startVoiceActivity(Intent intent, VoiceInteractionSession session) {
+        try {
+            int res = mSystemService.startVoiceActivity(intent,
+                    intent.resolveType(getContentResolver()),
+                    mInterface, session.mSession, session.mInteractor);
+            Instrumentation.checkStartActivityResult(res, intent);
+        } catch (RemoteException e) {
+        }
+    }
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        mSystemService = IVoiceInteractionManagerService.Stub.asInterface(
+                ServiceManager.getService(Context.VOICE_INTERACTION_MANAGER_SERVICE));
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        if (SERVICE_INTERFACE.equals(intent.getAction())) {
+            return mInterface.asBinder();
+        }
+        return null;
+    }
+}
diff --git a/core/java/android/service/voice/VoiceInteractionSession.java b/core/java/android/service/voice/VoiceInteractionSession.java
new file mode 100644
index 0000000..59544be
--- /dev/null
+++ b/core/java/android/service/voice/VoiceInteractionSession.java
@@ -0,0 +1,195 @@
+/**
+ * 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.Context;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+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;
+import com.android.internal.app.IVoiceInteractorRequest;
+import com.android.internal.os.HandlerCaller;
+import com.android.internal.os.SomeArgs;
+
+public abstract class VoiceInteractionSession {
+    static final String TAG = "VoiceInteractionSession";
+    static final boolean DEBUG = true;
+
+    final IVoiceInteractor mInteractor = new IVoiceInteractor.Stub() {
+        @Override
+        public IVoiceInteractorRequest startConfirmation(String callingPackage,
+                IVoiceInteractorCallback callback, String prompt, Bundle extras) {
+            Request request = findRequest(callback, true);
+            mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageOOOO(MSG_START_CONFIRMATION,
+                    new Caller(callingPackage, Binder.getCallingUid()), request,
+                    prompt, extras));
+            return request.mInterface;
+        }
+
+        @Override
+        public IVoiceInteractorRequest startCommand(String callingPackage,
+                IVoiceInteractorCallback callback, String command, Bundle extras) {
+            Request request = findRequest(callback, true);
+            mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageOOOO(MSG_START_COMMAND,
+                    new Caller(callingPackage, Binder.getCallingUid()), request,
+                    command, extras));
+            return request.mInterface;
+        }
+
+        @Override
+        public boolean[] supportsCommands(String callingPackage, String[] commands) {
+            Message msg = mHandlerCaller.obtainMessageIOO(MSG_SUPPORTS_COMMANDS,
+                    0, new Caller(callingPackage, Binder.getCallingUid()), commands);
+            SomeArgs args = mHandlerCaller.sendMessageAndWait(msg);
+            if (args != null) {
+                boolean[] res = (boolean[])args.arg1;
+                args.recycle();
+                return res;
+            }
+            return new boolean[commands.length];
+        }
+    };
+
+    final IVoiceInteractionSession mSession = new IVoiceInteractionSession.Stub() {
+    };
+
+    public static class Request {
+        final IVoiceInteractorRequest mInterface = new IVoiceInteractorRequest.Stub() {
+            @Override
+            public void cancel() throws RemoteException {
+                mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageO(MSG_CANCEL, Request.this));
+            }
+        };
+        final IVoiceInteractorCallback mCallback;
+        final HandlerCaller mHandlerCaller;
+        Request(IVoiceInteractorCallback callback, HandlerCaller handlerCaller) {
+            mCallback = callback;
+            mHandlerCaller = handlerCaller;
+        }
+
+        public void sendConfirmResult(boolean confirmed, Bundle result) {
+            try {
+                if (DEBUG) Log.d(TAG, "sendConfirmResult: req=" + mInterface
+                        + " confirmed=" + confirmed + " result=" + result);
+                mCallback.deliverConfirmationResult(mInterface, confirmed, result);
+            } catch (RemoteException e) {
+            }
+        }
+
+        public void sendCommandResult(Bundle result) {
+            try {
+                if (DEBUG) Log.d(TAG, "sendCommandResult: req=" + mInterface
+                        + " result=" + result);
+                mCallback.deliverCommandResult(mInterface, result);
+            } catch (RemoteException e) {
+            }
+        }
+
+        public void sendCancelResult() {
+            try {
+                if (DEBUG) Log.d(TAG, "sendCancelResult: req=" + mInterface);
+                mCallback.deliverCancel(mInterface);
+            } catch (RemoteException e) {
+            }
+        }
+    }
+
+    public static class Caller {
+        final String packageName;
+        final int uid;
+
+        Caller(String _packageName, int _uid) {
+            packageName = _packageName;
+            uid = _uid;
+        }
+    }
+
+    static final int MSG_START_CONFIRMATION = 1;
+    static final int MSG_START_COMMAND = 2;
+    static final int MSG_SUPPORTS_COMMANDS = 3;
+    static final int MSG_CANCEL = 4;
+
+    final Context mContext;
+    final 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_START_CONFIRMATION:
+                    if (DEBUG) Log.d(TAG, "onConfirm: req=" + ((Request) args.arg2).mInterface
+                            + " prompt=" + args.arg3 + " extras=" + args.arg4);
+                    onConfirm((Caller)args.arg1, (Request)args.arg2, (String)args.arg3,
+                            (Bundle)args.arg4);
+                    break;
+                case MSG_START_COMMAND:
+                    if (DEBUG) Log.d(TAG, "onCommand: req=" + ((Request) args.arg2).mInterface
+                            + " command=" + args.arg3 + " extras=" + args.arg4);
+                    onCommand((Caller) args.arg1, (Request) args.arg2, (String) args.arg3,
+                            (Bundle) args.arg4);
+                    break;
+                case MSG_SUPPORTS_COMMANDS:
+                    if (DEBUG) Log.d(TAG, "onGetSupportedCommands: cmds=" + args.arg2);
+                    args.arg1 = onGetSupportedCommands((Caller) args.arg1, (String[]) args.arg2);
+                    break;
+                case MSG_CANCEL:
+                    if (DEBUG) Log.d(TAG, "onCancel: req=" + ((Request) args.arg1).mInterface);
+                    onCancel((Request)args.arg1);
+                    break;
+            }
+        }
+    };
+
+    final ArrayMap<IBinder, Request> mActiveRequests = new ArrayMap<IBinder, Request>();
+
+    public VoiceInteractionSession(Context context) {
+        this(context, new Handler());
+    }
+
+    public VoiceInteractionSession(Context context, Handler handler) {
+        mContext = context;
+        mHandlerCaller = new HandlerCaller(context, handler.getLooper(),
+                mHandlerCallerCallback, true);
+    }
+
+    Request findRequest(IVoiceInteractorCallback callback, boolean newRequest) {
+        synchronized (this) {
+            Request req = mActiveRequests.get(callback.asBinder());
+            if (req != null) {
+                if (newRequest) {
+                    throw new IllegalArgumentException("Given request callback " + callback
+                            + " is already active");
+                }
+                return req;
+            }
+            req = new Request(callback, mHandlerCaller);
+            mActiveRequests.put(callback.asBinder(), req);
+            return req;
+        }
+    }
+
+    public abstract boolean[] onGetSupportedCommands(Caller caller, String[] commands);
+    public abstract void onConfirm(Caller caller, Request request, String prompt, Bundle extras);
+    public abstract void onCommand(Caller caller, Request request, String command, Bundle extras);
+    public abstract void onCancel(Request request);
+}
diff --git a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
new file mode 100644
index 0000000..e3c0728
--- /dev/null
+++ b/core/java/com/android/internal/app/IVoiceInteractionManagerService.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 com.android.internal.app;
+
+import android.content.Intent;
+
+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);
+}
diff --git a/core/java/com/android/internal/app/IVoiceInteractor.aidl b/core/java/com/android/internal/app/IVoiceInteractor.aidl
new file mode 100644
index 0000000..737906a
--- /dev/null
+++ b/core/java/com/android/internal/app/IVoiceInteractor.aidl
@@ -0,0 +1,33 @@
+/*
+ * 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.internal.app;
+
+import android.os.Bundle;
+
+import com.android.internal.app.IVoiceInteractorCallback;
+import com.android.internal.app.IVoiceInteractorRequest;
+
+/**
+ * IPC interface for an application to perform calls through a VoiceInteractor.
+ */
+interface IVoiceInteractor {
+    IVoiceInteractorRequest startConfirmation(String callingPackage,
+            IVoiceInteractorCallback callback, String prompt, in Bundle extras);
+    IVoiceInteractorRequest startCommand(String callingPackage,
+            IVoiceInteractorCallback callback, String command, in Bundle extras);
+    boolean[] supportsCommands(String callingPackage, in String[] commands);
+}
diff --git a/core/java/com/android/internal/app/IVoiceInteractorCallback.aidl b/core/java/com/android/internal/app/IVoiceInteractorCallback.aidl
new file mode 100644
index 0000000..f5392e9
--- /dev/null
+++ b/core/java/com/android/internal/app/IVoiceInteractorCallback.aidl
@@ -0,0 +1,31 @@
+/*
+ * 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.internal.app;
+
+import android.os.Bundle;
+
+import com.android.internal.app.IVoiceInteractorRequest;
+
+/**
+ * IPC interface for an application to receive callbacks from the voice system.
+ */
+oneway interface IVoiceInteractorCallback {
+    void deliverConfirmationResult(IVoiceInteractorRequest request, boolean confirmed,
+            in Bundle result);
+    void deliverCommandResult(IVoiceInteractorRequest request, in Bundle result);
+    void deliverCancel(IVoiceInteractorRequest request);
+}
diff --git a/core/java/com/android/internal/app/IVoiceInteractorRequest.aidl b/core/java/com/android/internal/app/IVoiceInteractorRequest.aidl
new file mode 100644
index 0000000..ce2902d
--- /dev/null
+++ b/core/java/com/android/internal/app/IVoiceInteractorRequest.aidl
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.app;
+
+/**
+ * IPC interface identifying a request from an application calling through an IVoiceInteractor.
+ */
+interface IVoiceInteractorRequest {
+    void cancel();
+}
diff --git a/core/java/com/android/internal/os/HandlerCaller.java b/core/java/com/android/internal/os/HandlerCaller.java
index d9e3ef6..40834ba 100644
--- a/core/java/com/android/internal/os/HandlerCaller.java
+++ b/core/java/com/android/internal/os/HandlerCaller.java
@@ -85,7 +85,27 @@
     public void sendMessage(Message msg) {
         mH.sendMessage(msg);
     }
-    
+
+    public SomeArgs sendMessageAndWait(Message msg) {
+        if (Looper.myLooper() == mH.getLooper()) {
+            throw new IllegalStateException("Can't wait on same thread as looper");
+        }
+        SomeArgs args = (SomeArgs)msg.obj;
+        args.mWaitState = SomeArgs.WAIT_WAITING;
+        mH.sendMessage(msg);
+        synchronized (args) {
+            while (args.mWaitState == SomeArgs.WAIT_WAITING) {
+                try {
+                    args.wait();
+                } catch (InterruptedException e) {
+                    return null;
+                }
+            }
+        }
+        args.mWaitState = SomeArgs.WAIT_NONE;
+        return args;
+    }
+
     public Message obtainMessage(int what) {
         return mH.obtainMessage(what);
     }
@@ -136,6 +156,14 @@
         return mH.obtainMessage(what, arg1, 0, args);
     }
     
+    public Message obtainMessageIOOO(int what, int arg1, Object arg2, Object arg3, Object arg4) {
+        SomeArgs args = SomeArgs.obtain();
+        args.arg1 = arg2;
+        args.arg2 = arg3;
+        args.arg3 = arg4;
+        return mH.obtainMessage(what, arg1, 0, args);
+    }
+
     public Message obtainMessageOO(int what, Object arg1, Object arg2) {
         SomeArgs args = SomeArgs.obtain();
         args.arg1 = arg1;
@@ -161,6 +189,17 @@
         return mH.obtainMessage(what, 0, 0, args);
     }
     
+    public Message obtainMessageOOOOO(int what, Object arg1, Object arg2,
+            Object arg3, Object arg4, Object arg5) {
+        SomeArgs args = SomeArgs.obtain();
+        args.arg1 = arg1;
+        args.arg2 = arg2;
+        args.arg3 = arg3;
+        args.arg4 = arg4;
+        args.arg5 = arg5;
+        return mH.obtainMessage(what, 0, 0, args);
+    }
+
     public Message obtainMessageIIII(int what, int arg1, int arg2,
             int arg3, int arg4) {
         SomeArgs args = SomeArgs.obtain();
diff --git a/core/java/com/android/internal/os/SomeArgs.java b/core/java/com/android/internal/os/SomeArgs.java
index 6fb72f1..7edf4cc 100644
--- a/core/java/com/android/internal/os/SomeArgs.java
+++ b/core/java/com/android/internal/os/SomeArgs.java
@@ -35,6 +35,11 @@
 
     private boolean mInPool;
 
+    static final int WAIT_NONE = 0;
+    static final int WAIT_WAITING = 1;
+    static final int WAIT_FINISHED = 2;
+    int mWaitState = WAIT_NONE;
+
     public Object arg1;
     public Object arg2;
     public Object arg3;
@@ -70,6 +75,9 @@
         if (mInPool) {
             throw new IllegalStateException("Already recycled.");
         }
+        if (mWaitState != WAIT_NONE) {
+            return;
+        }
         synchronized (sPoolLock) {
             clear();
             if (sPoolSize < MAX_POOL_SIZE) {
diff --git a/core/java/com/android/server/SystemService.java b/core/java/com/android/server/SystemService.java
index e374563..bf36bb1 100644
--- a/core/java/com/android/server/SystemService.java
+++ b/core/java/com/android/server/SystemService.java
@@ -123,6 +123,38 @@
     public void onBootPhase(int phase) {}
 
     /**
+     * Called when a new user is starting, for system services to initialize any per-user
+     * state they maintain for running users.
+     * @param userHandle The identifier of the user.
+     */
+    public void onStartUser(int userHandle) {}
+
+    /**
+     * Called when switching to a different foreground user, for system services that have
+     * special behavior for whichever user is currently in the foreground.  This is called
+     * before any application processes are aware of the new user.
+     * @param userHandle The identifier of the user.
+     */
+    public void onSwitchUser(int userHandle) {}
+
+    /**
+     * Called when an existing user is stopping, for system services to finalize any per-user
+     * state they maintain for running users.  This is called prior to sending the SHUTDOWN
+     * broadcast to the user; it is a good place to stop making use of any resources of that
+     * user (such as binding to a service running in the user).
+     * @param userHandle The identifier of the user.
+     */
+    public void onStopUser(int userHandle) {}
+
+    /**
+     * Called when an existing user is stopping, for system services to finalize any per-user
+     * state they maintain for running users.  This is called after all application process
+     * teardown of the user is complete.
+     * @param userHandle The identifier of the user.
+     */
+    public void onCleanupUser(int userHandle) {}
+
+    /**
      * Publish the service so it is accessible to other services and apps.
      */
     protected final void publishBinderService(String name, IBinder service) {
diff --git a/core/java/com/android/server/SystemServiceManager.java b/core/java/com/android/server/SystemServiceManager.java
index eb8df0e..87a50a9 100644
--- a/core/java/com/android/server/SystemServiceManager.java
+++ b/core/java/com/android/server/SystemServiceManager.java
@@ -131,6 +131,58 @@
         }
     }
 
+    public void startUser(final int userHandle) {
+        final int serviceLen = mServices.size();
+        for (int i = 0; i < serviceLen; i++) {
+            final SystemService service = mServices.get(i);
+            try {
+                service.onStartUser(userHandle);
+            } catch (Exception ex) {
+                Slog.wtf(TAG, "Failure reporting start of user " + userHandle
+                        + " to service " + service.getClass().getName(), ex);
+            }
+        }
+    }
+
+    public void switchUser(final int userHandle) {
+        final int serviceLen = mServices.size();
+        for (int i = 0; i < serviceLen; i++) {
+            final SystemService service = mServices.get(i);
+            try {
+                service.onSwitchUser(userHandle);
+            } catch (Exception ex) {
+                Slog.wtf(TAG, "Failure reporting switch of user " + userHandle
+                        + " to service " + service.getClass().getName(), ex);
+            }
+        }
+    }
+
+    public void stopUser(final int userHandle) {
+        final int serviceLen = mServices.size();
+        for (int i = 0; i < serviceLen; i++) {
+            final SystemService service = mServices.get(i);
+            try {
+                service.onStopUser(userHandle);
+            } catch (Exception ex) {
+                Slog.wtf(TAG, "Failure reporting stop of user " + userHandle
+                        + " to service " + service.getClass().getName(), ex);
+            }
+        }
+    }
+
+    public void cleanupUser(final int userHandle) {
+        final int serviceLen = mServices.size();
+        for (int i = 0; i < serviceLen; i++) {
+            final SystemService service = mServices.get(i);
+            try {
+                service.onCleanupUser(userHandle);
+            } catch (Exception ex) {
+                Slog.wtf(TAG, "Failure reporting cleanup of user " + userHandle
+                        + " to service " + service.getClass().getName(), ex);
+            }
+        }
+    }
+
     /** Sets the safe mode flag for services to query. */
     public void setSafeMode(boolean safeMode) {
         mSafeMode = safeMode;