Merge "Remove MR2 BLE Advertising hidden API from L codebase (1/2)." into lmp-dev
diff --git a/Android.mk b/Android.mk
index a823ba0..8a50ae84 100644
--- a/Android.mk
+++ b/Android.mk
@@ -336,6 +336,7 @@
 	media/java/android/media/tv/ITvInputHardware.aidl \
 	media/java/android/media/tv/ITvInputHardwareCallback.aidl \
 	media/java/android/media/tv/ITvInputManager.aidl \
+	media/java/android/media/tv/ITvInputManagerCallback.aidl \
 	media/java/android/media/tv/ITvInputService.aidl \
 	media/java/android/media/tv/ITvInputServiceCallback.aidl \
 	media/java/android/media/tv/ITvInputSession.aidl \
diff --git a/api/current.txt b/api/current.txt
index fcc02d0..8076d47 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -8358,6 +8358,36 @@
     field public int reqGlEsVersion;
   }
 
+  public class InstallSessionInfo implements android.os.Parcelable {
+    method public int describeContents();
+    method public android.graphics.Bitmap getIcon();
+    method public java.lang.String getInstallerPackageName();
+    method public java.lang.String getPackageName();
+    method public int getProgress();
+    method public int getSessionId();
+    method public java.lang.CharSequence getTitle();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator CREATOR;
+  }
+
+  public class InstallSessionParams implements android.os.Parcelable {
+    ctor public InstallSessionParams();
+    method public int describeContents();
+    method public void setDeltaSize(long);
+    method public void setIcon(android.graphics.Bitmap);
+    method public void setInstallLocation(int);
+    method public void setModeFullInstall();
+    method public void setModeInheritExisting();
+    method public void setOriginatingUri(android.net.Uri);
+    method public void setPackageName(java.lang.String);
+    method public void setProgressMax(int);
+    method public void setReferrerUri(android.net.Uri);
+    method public void setSignatures(android.content.pm.Signature[]);
+    method public void setTitle(java.lang.CharSequence);
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator CREATOR;
+  }
+
   public class InstrumentationInfo extends android.content.pm.PackageItemInfo implements android.os.Parcelable {
     ctor public InstrumentationInfo();
     ctor public InstrumentationInfo(android.content.pm.InstrumentationInfo);
@@ -8425,6 +8455,9 @@
     method public int describeContents();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator CREATOR;
+    field public static final int INSTALL_LOCATION_AUTO = 0; // 0x0
+    field public static final int INSTALL_LOCATION_INTERNAL_ONLY = 1; // 0x1
+    field public static final int INSTALL_LOCATION_PREFER_EXTERNAL = 2; // 0x2
     field public static final int REQUESTED_PERMISSION_GRANTED = 2; // 0x2
     field public static final int REQUESTED_PERMISSION_REQUIRED = 1; // 0x1
     field public android.content.pm.ActivityInfo[] activities;
@@ -8432,6 +8465,7 @@
     field public android.content.pm.ConfigurationInfo[] configPreferences;
     field public long firstInstallTime;
     field public int[] gids;
+    field public int installLocation;
     field public android.content.pm.InstrumentationInfo[] instrumentation;
     field public long lastUpdateTime;
     field public java.lang.String packageName;
@@ -8445,10 +8479,52 @@
     field public java.lang.String sharedUserId;
     field public int sharedUserLabel;
     field public android.content.pm.Signature[] signatures;
+    field public java.lang.String[] splitNames;
     field public int versionCode;
     field public java.lang.String versionName;
   }
 
+  public class PackageInstaller {
+    method public int createSession(android.content.pm.InstallSessionParams) throws java.io.IOException;
+    method public java.util.List<android.content.pm.InstallSessionInfo> getActiveSessions();
+    method public android.content.pm.PackageInstaller.Session openSession(int);
+    method public void registerSessionObserver(android.content.pm.PackageInstaller.SessionObserver);
+    method public void uninstall(java.lang.String, android.content.pm.PackageInstaller.UninstallResultCallback);
+    method public void unregisterSessionObserver(android.content.pm.PackageInstaller.SessionObserver);
+  }
+
+  public static abstract class PackageInstaller.CommitResultCallback {
+    ctor public PackageInstaller.CommitResultCallback();
+    method public abstract void onFailure(java.lang.String);
+    method public void onFailureConflict(java.lang.String, java.lang.String);
+    method public void onFailureIncompatible(java.lang.String);
+    method public void onFailureInvalid(java.lang.String);
+    method public void onFailureStorage(java.lang.String);
+    method public abstract void onSuccess();
+  }
+
+  public static class PackageInstaller.Session implements java.io.Closeable {
+    method public void close();
+    method public void commit(android.content.pm.PackageInstaller.CommitResultCallback);
+    method public void destroy();
+    method public void fsync(java.io.OutputStream) throws java.io.IOException;
+    method public java.io.OutputStream openWrite(java.lang.String, long, long) throws java.io.IOException;
+    method public void setProgress(int);
+  }
+
+  public static abstract class PackageInstaller.SessionObserver {
+    ctor public PackageInstaller.SessionObserver();
+    method public abstract void onCreated(android.content.pm.InstallSessionInfo);
+    method public abstract void onFinalized(int, boolean);
+    method public abstract void onProgress(int, int);
+  }
+
+  public static abstract class PackageInstaller.UninstallResultCallback {
+    ctor public PackageInstaller.UninstallResultCallback();
+    method public abstract void onFailure(java.lang.String);
+    method public abstract void onSuccess();
+  }
+
   public class PackageItemInfo {
     ctor public PackageItemInfo();
     ctor public PackageItemInfo(android.content.pm.PackageItemInfo);
@@ -8511,6 +8587,7 @@
     method public abstract android.graphics.drawable.Drawable getDrawable(java.lang.String, int, android.content.pm.ApplicationInfo);
     method public abstract java.util.List<android.content.pm.ApplicationInfo> getInstalledApplications(int);
     method public abstract java.util.List<android.content.pm.PackageInfo> getInstalledPackages(int);
+    method public abstract android.content.pm.PackageInstaller getInstaller();
     method public abstract java.lang.String getInstallerPackageName(java.lang.String);
     method public abstract android.content.pm.InstrumentationInfo getInstrumentationInfo(android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException;
     method public abstract android.content.pm.KeySet getKeySetByAlias(java.lang.String, java.lang.String);
@@ -16425,8 +16502,13 @@
   }
 
   public final class TvInputManager {
-    method public boolean getAvailability(java.lang.String);
+    method public int getInputState(java.lang.String);
     method public java.util.List<android.media.tv.TvInputInfo> getTvInputList();
+    method public void registerListener(android.media.tv.TvInputManager.TvInputListener, android.os.Handler);
+    method public void unregisterListener(android.media.tv.TvInputManager.TvInputListener);
+    field public static final int INPUT_STATE_CONNECTED = 0; // 0x0
+    field public static final int INPUT_STATE_CONNECTED_STANDBY = 1; // 0x1
+    field public static final int INPUT_STATE_DISCONNECTED = 2; // 0x2
     field public static final int VIDEO_UNAVAILABLE_REASON_BUFFERING = 3; // 0x3
     field public static final int VIDEO_UNAVAILABLE_REASON_TUNE = 1; // 0x1
     field public static final int VIDEO_UNAVAILABLE_REASON_UNKNOWN = 0; // 0x0
@@ -16435,7 +16517,7 @@
 
   public static abstract class TvInputManager.TvInputListener {
     ctor public TvInputManager.TvInputListener();
-    method public void onAvailabilityChanged(java.lang.String, boolean);
+    method public void onInputStateChanged(java.lang.String, int);
   }
 
   public abstract class TvInputService extends android.app.Service {
@@ -27956,10 +28038,10 @@
 
   public class CallPropertyPresentation {
     ctor public CallPropertyPresentation();
-    field public static final int ALLOWED = 0; // 0x0
-    field public static final int PAYPHONE = 3; // 0x3
-    field public static final int RESTRICTED = 1; // 0x1
-    field public static final int UNKNOWN = 2; // 0x2
+    field public static final int ALLOWED = 1; // 0x1
+    field public static final int PAYPHONE = 4; // 0x4
+    field public static final int RESTRICTED = 2; // 0x2
+    field public static final int UNKNOWN = 3; // 0x3
   }
 
   public final class CallState extends java.lang.Enum {
@@ -28079,7 +28161,8 @@
 
   public abstract class ConnectionService extends android.app.Service {
     ctor public ConnectionService();
-    method public final void createRemoteOutgoingConnection(android.telecomm.ConnectionRequest, android.telecomm.ConnectionService.OutgoingCallResponse<android.telecomm.RemoteConnection>);
+    method public final void createRemoteIncomingConnection(android.telecomm.ConnectionRequest, android.telecomm.ConnectionService.CreateConnectionResponse<android.telecomm.RemoteConnection>);
+    method public final void createRemoteOutgoingConnection(android.telecomm.ConnectionRequest, android.telecomm.ConnectionService.CreateConnectionResponse<android.telecomm.RemoteConnection>);
     method public final java.util.Collection<android.telecomm.Connection> getAllConnections();
     method public final void lookupRemoteAccounts(android.net.Uri, android.telecomm.SimpleResponse<android.net.Uri, java.util.List<android.telecomm.PhoneAccount>>);
     method public final void maybeRespondToAccountLookup();
@@ -28087,11 +28170,11 @@
     method protected void onConnectionAdded(android.telecomm.Connection);
     method protected void onConnectionRemoved(android.telecomm.Connection);
     method protected void onCreateConferenceConnection(java.lang.String, android.telecomm.Connection, android.telecomm.Response<java.lang.String, android.telecomm.Connection>);
-    method protected void onCreateConnections(android.telecomm.ConnectionRequest, android.telecomm.ConnectionService.OutgoingCallResponse<android.telecomm.Connection>);
-    method protected void onCreateIncomingConnection(android.telecomm.ConnectionRequest, android.telecomm.Response<android.telecomm.ConnectionRequest, android.telecomm.Connection>);
+    method protected void onCreateIncomingConnection(android.telecomm.ConnectionRequest, android.telecomm.ConnectionService.CreateConnectionResponse<android.telecomm.Connection>);
+    method protected void onCreateOutgoingConnection(android.telecomm.ConnectionRequest, android.telecomm.ConnectionService.CreateConnectionResponse<android.telecomm.Connection>);
   }
 
-  public static abstract interface ConnectionService.OutgoingCallResponse {
+  public static abstract interface ConnectionService.CreateConnectionResponse {
     method public abstract void onCancel(android.telecomm.ConnectionRequest);
     method public abstract void onFailure(android.telecomm.ConnectionRequest, int, java.lang.String);
     method public abstract void onSuccess(android.telecomm.ConnectionRequest, CONNECTION);
@@ -29428,8 +29511,10 @@
     method public android.graphics.drawable.Drawable getDrawable(java.lang.String, int, android.content.pm.ApplicationInfo);
     method public java.util.List<android.content.pm.ApplicationInfo> getInstalledApplications(int);
     method public java.util.List<android.content.pm.PackageInfo> getInstalledPackages(int);
+    method public android.content.pm.PackageInstaller getInstaller();
     method public java.lang.String getInstallerPackageName(java.lang.String);
     method public android.content.pm.InstrumentationInfo getInstrumentationInfo(android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException;
+    method public android.content.pm.KeySet getKeySetByAlias(java.lang.String, java.lang.String);
     method public android.content.Intent getLaunchIntentForPackage(java.lang.String);
     method public android.content.Intent getLeanbackLaunchIntentForPackage(java.lang.String);
     method public java.lang.String getNameForUid(int);
@@ -29447,12 +29532,15 @@
     method public android.content.res.Resources getResourcesForApplication(android.content.pm.ApplicationInfo);
     method public android.content.res.Resources getResourcesForApplication(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
     method public android.content.pm.ServiceInfo getServiceInfo(android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException;
+    method public android.content.pm.KeySet getSigningKeySet(java.lang.String);
     method public android.content.pm.FeatureInfo[] getSystemAvailableFeatures();
     method public java.lang.String[] getSystemSharedLibraryNames();
     method public java.lang.CharSequence getText(java.lang.String, int, android.content.pm.ApplicationInfo);
     method public android.content.res.XmlResourceParser getXml(java.lang.String, int, android.content.pm.ApplicationInfo);
     method public boolean hasSystemFeature(java.lang.String);
     method public boolean isSafeMode();
+    method public boolean isSignedBy(java.lang.String, android.content.pm.KeySet);
+    method public boolean isSignedByExactly(java.lang.String, android.content.pm.KeySet);
     method public java.util.List<android.content.pm.ResolveInfo> queryBroadcastReceivers(android.content.Intent, int);
     method public java.util.List<android.content.pm.ProviderInfo> queryContentProviders(java.lang.String, int, int);
     method public java.util.List<android.content.pm.InstrumentationInfo> queryInstrumentation(java.lang.String, int);
diff --git a/cmds/pm/src/com/android/commands/pm/Pm.java b/cmds/pm/src/com/android/commands/pm/Pm.java
index 3a2ca30..b7f1ff9 100644
--- a/cmds/pm/src/com/android/commands/pm/Pm.java
+++ b/cmds/pm/src/com/android/commands/pm/Pm.java
@@ -998,7 +998,7 @@
 
         final InstallSessionParams params = new InstallSessionParams();
         params.installFlags = PackageManager.INSTALL_ALL_USERS;
-        params.fullInstall = true;
+        params.mode = InstallSessionParams.MODE_FULL_INSTALL;
         params.progressMax = -1;
 
         String opt;
@@ -1021,7 +1021,7 @@
             } else if (opt.equals("-d")) {
                 params.installFlags |= PackageManager.INSTALL_ALLOW_DOWNGRADE;
             } else if (opt.equals("-p")) {
-                params.fullInstall = false;
+                params.mode = InstallSessionParams.MODE_INHERIT_EXISTING;
             } else if (opt.equals("-S")) {
                 params.deltaSize = Long.parseLong(nextOptionData());
                 params.progressMax = (int) params.deltaSize;
diff --git a/core/java/android/animation/PropertyValuesHolder.java b/core/java/android/animation/PropertyValuesHolder.java
index bf2924c..bdfbde1 100644
--- a/core/java/android/animation/PropertyValuesHolder.java
+++ b/core/java/android/animation/PropertyValuesHolder.java
@@ -631,6 +631,9 @@
     public void setObjectValues(Object... values) {
         mValueType = values[0].getClass();
         mKeyframeSet = KeyframeSet.ofObject(values);
+        if (mEvaluator != null) {
+            mKeyframeSet.setEvaluator(mEvaluator);
+        }
     }
 
     /**
diff --git a/core/java/android/app/ActivityTransitionState.java b/core/java/android/app/ActivityTransitionState.java
index 0d2af8c..4f556a8 100644
--- a/core/java/android/app/ActivityTransitionState.java
+++ b/core/java/android/app/ActivityTransitionState.java
@@ -95,6 +95,8 @@
      */
     private int mExitTransitionCoordinatorsKey = 1;
 
+    private boolean mIsEnterTriggered;
+
     public ActivityTransitionState() {
     }
 
@@ -142,8 +144,10 @@
     public void setEnterActivityOptions(Activity activity, ActivityOptions options) {
         if (activity.getWindow().hasFeature(Window.FEATURE_CONTENT_TRANSITIONS)
                 && options != null && mEnterActivityOptions == null
+                && mEnterTransitionCoordinator == null
                 && options.getAnimationType() == ActivityOptions.ANIM_SCENE_TRANSITION) {
             mEnterActivityOptions = options;
+            mIsEnterTriggered = false;
             if (mEnterActivityOptions.isReturning()) {
                 int result = mEnterActivityOptions.getResultCode();
                 if (result != 0) {
@@ -154,9 +158,10 @@
     }
 
     public void enterReady(Activity activity) {
-        if (mEnterActivityOptions == null) {
+        if (mEnterActivityOptions == null || mIsEnterTriggered) {
             return;
         }
+        mIsEnterTriggered = true;
         mHasExited = false;
         ArrayList<String> sharedElementNames = mEnterActivityOptions.getSharedElementNames();
         ResultReceiver resultReceiver = mEnterActivityOptions.getResultReceiver();
@@ -259,6 +264,7 @@
             return;
         }
         ActivityOptions activityOptions = new ActivityOptions(options);
+        mEnterTransitionCoordinator = null;
         if (activityOptions.getAnimationType() == ActivityOptions.ANIM_SCENE_TRANSITION) {
             int key = activityOptions.getExitCoordinatorKey();
             int index = mExitTransitionCoordinators.indexOfKey(key);
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 4730559..264553b 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -1520,7 +1520,7 @@
     }
 
     @Override
-    public PackageInstaller getPackageInstaller() {
+    public PackageInstaller getInstaller() {
         try {
             return new PackageInstaller(this, mPM.getPackageInstaller(), mContext.getPackageName(),
                     mContext.getUserId());
@@ -1529,6 +1529,15 @@
         }
     }
 
+    @Override
+    public boolean isPackageAvailable(String packageName) {
+        try {
+            return mPM.isPackageAvailable(packageName, mContext.getUserId());
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
+    }
+
     /**
      * @hide
      */
diff --git a/core/java/android/app/ExitTransitionCoordinator.java b/core/java/android/app/ExitTransitionCoordinator.java
index 3f3e00c..4f5a098 100644
--- a/core/java/android/app/ExitTransitionCoordinator.java
+++ b/core/java/android/app/ExitTransitionCoordinator.java
@@ -329,7 +329,7 @@
     private Bundle captureExitSharedElementsState() {
         Bundle bundle = new Bundle();
         Rect bounds = new Rect();
-        for (int i = 0; i < mSharedElementNames.size(); i++) {
+        for (int i = 0; i < mSharedElements.size(); i++) {
             String name = mSharedElementNames.get(i);
             Bundle sharedElementState = mExitSharedElementBundle.getBundle(name);
             if (sharedElementState != null) {
diff --git a/core/java/android/content/pm/InstallSessionInfo.java b/core/java/android/content/pm/InstallSessionInfo.java
index 45606d1..3336727 100644
--- a/core/java/android/content/pm/InstallSessionInfo.java
+++ b/core/java/android/content/pm/InstallSessionInfo.java
@@ -20,15 +20,25 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 
-/** {@hide} */
+/**
+ * Details for an active install session.
+ */
 public class InstallSessionInfo implements Parcelable {
+
+    /** {@hide} */
     public int sessionId;
+    /** {@hide} */
     public String installerPackageName;
+    /** {@hide} */
     public int progress;
 
-    public boolean fullInstall;
+    /** {@hide} */
+    public int mode;
+    /** {@hide} */
     public String packageName;
+    /** {@hide} */
     public Bitmap icon;
+    /** {@hide} */
     public CharSequence title;
 
     /** {@hide} */
@@ -41,12 +51,62 @@
         installerPackageName = source.readString();
         progress = source.readInt();
 
-        fullInstall = source.readInt() != 0;
+        mode = source.readInt();
         packageName = source.readString();
         icon = source.readParcelable(null);
         title = source.readString();
     }
 
+    /**
+     * Return the ID for this session.
+     */
+    public int getSessionId() {
+        return sessionId;
+    }
+
+    /**
+     * Return the package name of the app that owns this session.
+     */
+    public String getInstallerPackageName() {
+        return installerPackageName;
+    }
+
+    /**
+     * Return current overall progress of this session, between 0 and 100.
+     * <p>
+     * Note that this progress may not directly correspond to the value reported
+     * by {@link PackageInstaller.Session#setProgress(int)}, as the system may
+     * carve out a portion of the overall progress to represent its own internal
+     * installation work.
+     */
+    public int getProgress() {
+        return progress;
+    }
+
+    /**
+     * Return the package name this session is working with. May be {@code null}
+     * if unknown.
+     */
+    public String getPackageName() {
+        return packageName;
+    }
+
+    /**
+     * Return an icon representing the app being installed. May be {@code null}
+     * if unavailable.
+     */
+    public Bitmap getIcon() {
+        return icon;
+    }
+
+    /**
+     * Return a title representing the app being installed. May be {@code null}
+     * if unavailable.
+     */
+    public CharSequence getTitle() {
+        return title;
+    }
+
     @Override
     public int describeContents() {
         return 0;
@@ -58,7 +118,7 @@
         dest.writeString(installerPackageName);
         dest.writeInt(progress);
 
-        dest.writeInt(fullInstall ? 1 : 0);
+        dest.writeInt(mode);
         dest.writeString(packageName);
         dest.writeParcelable(icon, flags);
         dest.writeString(title != null ? title.toString() : null);
diff --git a/core/java/android/content/pm/InstallSessionParams.java b/core/java/android/content/pm/InstallSessionParams.java
index 43e3314..e039699 100644
--- a/core/java/android/content/pm/InstallSessionParams.java
+++ b/core/java/android/content/pm/InstallSessionParams.java
@@ -16,6 +16,7 @@
 
 package android.content.pm;
 
+import android.content.Intent;
 import android.graphics.Bitmap;
 import android.net.Uri;
 import android.os.Parcel;
@@ -23,13 +24,22 @@
 
 import com.android.internal.util.IndentingPrintWriter;
 
-/** {@hide} */
+/**
+ * Parameters for creating a new {@link PackageInstaller.Session}.
+ */
 public class InstallSessionParams implements Parcelable {
 
     // TODO: extend to support remaining VerificationParams
 
     /** {@hide} */
-    public boolean fullInstall;
+    public static final int MODE_INVALID = 0;
+    /** {@hide} */
+    public static final int MODE_FULL_INSTALL = 1;
+    /** {@hide} */
+    public static final int MODE_INHERIT_EXISTING = 2;
+
+    /** {@hide} */
+    public int mode = MODE_INVALID;
     /** {@hide} */
     public int installFlags;
     /** {@hide} */
@@ -58,7 +68,7 @@
 
     /** {@hide} */
     public InstallSessionParams(Parcel source) {
-        fullInstall = source.readInt() != 0;
+        mode = source.readInt();
         installFlags = source.readInt();
         installLocation = source.readInt();
         signatures = (Signature[]) source.readParcelableArray(null);
@@ -72,53 +82,108 @@
         abiOverride = source.readString();
     }
 
-    public void setFullInstall(boolean fullInstall) {
-        this.fullInstall = fullInstall;
+    /**
+     * Set session mode indicating that it should fully replace any existing
+     * APKs for this application.
+     */
+    public void setModeFullInstall() {
+        this.mode = MODE_FULL_INSTALL;
     }
 
-    public void setInstallFlags(int installFlags) {
-        this.installFlags = installFlags;
+    /**
+     * Set session mode indicating that it should inherit any existing APKs for
+     * this application, unless they are explicitly overridden (by split name)
+     * in the session.
+     */
+    public void setModeInheritExisting() {
+        this.mode = MODE_INHERIT_EXISTING;
     }
 
+    /**
+     * Provide value of {@link PackageInfo#installLocation}, which may be used
+     * to determine where the app will be staged. Defaults to
+     * {@link PackageInfo#INSTALL_LOCATION_INTERNAL_ONLY}.
+     */
     public void setInstallLocation(int installLocation) {
         this.installLocation = installLocation;
     }
 
+    /**
+     * Optionally provide the required value of {@link PackageInfo#signatures}.
+     * This can be used to assert that all staged APKs have been signed with
+     * this set of specific certificates. Regardless of this value, all APKs in
+     * the application must have the same signing certificates.
+     */
     public void setSignatures(Signature[] signatures) {
         this.signatures = signatures;
     }
 
+    /**
+     * Indicate the expected growth in disk usage resulting from this session.
+     * This may be used to ensure enough disk space exists before proceeding, or
+     * to estimate container size for installations living on external storage.
+     * <p>
+     * This value should only reflect APK sizes.
+     */
     public void setDeltaSize(long deltaSize) {
         this.deltaSize = deltaSize;
     }
 
+    /**
+     * Set the maximum progress for this session, used for normalization
+     * purposes.
+     *
+     * @see PackageInstaller.Session#setProgress(int)
+     */
     public void setProgressMax(int progressMax) {
         this.progressMax = progressMax;
     }
 
+    /**
+     * Optionally set the package name this session will be working with. It's
+     * strongly recommended that you provide this value when known.
+     */
     public void setPackageName(String packageName) {
         this.packageName = packageName;
     }
 
+    /**
+     * Optionally set an icon representing the app being installed.
+     */
     public void setIcon(Bitmap icon) {
         this.icon = icon;
     }
 
+    /**
+     * Optionally set a title representing the app being installed.
+     */
     public void setTitle(CharSequence title) {
         this.title = title;
     }
 
+    /**
+     * Optionally set the URI where this package was downloaded from. Used for
+     * verification purposes.
+     *
+     * @see Intent#EXTRA_ORIGINATING_URI
+     */
     public void setOriginatingUri(Uri originatingUri) {
         this.originatingUri = originatingUri;
     }
 
+    /**
+     * Optionally set the URI that referred you to install this package. Used
+     * for verification purposes.
+     *
+     * @see Intent#EXTRA_REFERRER
+     */
     public void setReferrerUri(Uri referrerUri) {
         this.referrerUri = referrerUri;
     }
 
     /** {@hide} */
     public void dump(IndentingPrintWriter pw) {
-        pw.printPair("fullInstall", fullInstall);
+        pw.printPair("mode", mode);
         pw.printHexPair("installFlags", installFlags);
         pw.printPair("installLocation", installLocation);
         pw.printPair("signatures", (signatures != null));
@@ -140,7 +205,7 @@
 
     @Override
     public void writeToParcel(Parcel dest, int flags) {
-        dest.writeInt(fullInstall ? 1 : 0);
+        dest.writeInt(mode);
         dest.writeInt(installFlags);
         dest.writeInt(installLocation);
         dest.writeParcelableArray(signatures, flags);
diff --git a/core/java/android/content/pm/PackageInfo.java b/core/java/android/content/pm/PackageInfo.java
index ef0c4d5..8f0c249 100644
--- a/core/java/android/content/pm/PackageInfo.java
+++ b/core/java/android/content/pm/PackageInfo.java
@@ -31,6 +31,11 @@
     public String packageName;
 
     /**
+     * The names of any installed split APKs for this package.
+     */
+    public String[] splitNames;
+
+    /**
      * The version number of this package, as specified by the &lt;manifest&gt;
      * tag's {@link android.R.styleable#AndroidManifest_versionCode versionCode}
      * attribute.
@@ -190,24 +195,25 @@
      * @hide
      */
     public static final int INSTALL_LOCATION_UNSPECIFIED = -1;
+
     /**
-     * Constant corresponding to <code>auto</code> in
-     * the {@link android.R.attr#installLocation} attribute.
-     * @hide
+     * Constant corresponding to <code>auto</code> in the
+     * {@link android.R.attr#installLocation} attribute.
      */
     public static final int INSTALL_LOCATION_AUTO = 0;
+
     /**
-     * Constant corresponding to <code>internalOnly</code> in
-     * the {@link android.R.attr#installLocation} attribute.
-     * @hide
+     * Constant corresponding to <code>internalOnly</code> in the
+     * {@link android.R.attr#installLocation} attribute.
      */
     public static final int INSTALL_LOCATION_INTERNAL_ONLY = 1;
+
     /**
-     * Constant corresponding to <code>preferExternal</code> in
-     * the {@link android.R.attr#installLocation} attribute.
-     * @hide
+     * Constant corresponding to <code>preferExternal</code> in the
+     * {@link android.R.attr#installLocation} attribute.
      */
     public static final int INSTALL_LOCATION_PREFER_EXTERNAL = 2;
+
     /**
      * Flag for {@link #requiredForProfile}
      * The application will always be installed for a restricted profile.
@@ -222,12 +228,10 @@
     public static final int MANAGED_PROFILE = 2;
 
     /**
-     * The install location requested by the activity.  From the
+     * The install location requested by the package. From the
      * {@link android.R.attr#installLocation} attribute, one of
-     * {@link #INSTALL_LOCATION_AUTO},
-     * {@link #INSTALL_LOCATION_INTERNAL_ONLY},
+     * {@link #INSTALL_LOCATION_AUTO}, {@link #INSTALL_LOCATION_INTERNAL_ONLY},
      * {@link #INSTALL_LOCATION_PREFER_EXTERNAL}
-     * @hide
      */
     public int installLocation = INSTALL_LOCATION_INTERNAL_ONLY;
 
@@ -269,6 +273,7 @@
 
     public void writeToParcel(Parcel dest, int parcelableFlags) {
         dest.writeString(packageName);
+        dest.writeStringArray(splitNames);
         dest.writeInt(versionCode);
         dest.writeString(versionName);
         dest.writeString(sharedUserId);
@@ -314,6 +319,7 @@
 
     private PackageInfo(Parcel source) {
         packageName = source.readString();
+        splitNames = source.readStringArray();
         versionCode = source.readInt();
         versionName = source.readString();
         sharedUserId = source.readString();
diff --git a/core/java/android/content/pm/PackageInfoLite.java b/core/java/android/content/pm/PackageInfoLite.java
index 50a0483..e336c5f 100644
--- a/core/java/android/content/pm/PackageInfoLite.java
+++ b/core/java/android/content/pm/PackageInfoLite.java
@@ -73,6 +73,7 @@
         dest.writeInt(versionCode);
         dest.writeInt(recommendedInstallLocation);
         dest.writeInt(installLocation);
+        dest.writeInt(multiArch ? 1 : 0);
 
         if (verifiers == null || verifiers.length == 0) {
             dest.writeInt(0);
@@ -98,6 +99,7 @@
         versionCode = source.readInt();
         recommendedInstallLocation = source.readInt();
         installLocation = source.readInt();
+        multiArch = (source.readInt() != 0);
 
         final int verifiersLength = source.readInt();
         if (verifiersLength == 0) {
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index 401be06..348a7ad 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -30,7 +30,31 @@
 import java.io.OutputStream;
 import java.util.List;
 
-/** {@hide} */
+/**
+ * Offers the ability to install, upgrade, and remove applications on the
+ * device. This includes support for apps packaged either as a single
+ * "monolithic" APK, or apps packaged as multiple "split" APKs.
+ * <p>
+ * An app is delivered for installation through a
+ * {@link PackageInstaller.Session}, which any app can create. Once the session
+ * is created, the installer can stream one or more APKs into place until it
+ * decides to either commit or destroy the session. Committing may require user
+ * intervention to complete the installation.
+ * <p>
+ * Sessions can install brand new apps, upgrade existing apps, or add new splits
+ * onto an existing app.
+ * <p>
+ * Apps packaged into multiple split APKs always consist of a single "base" APK
+ * (with a {@code null} split name) and zero or more "split" APKs (with unique
+ * split names). Any subset of these APKs can be installed together, as long as
+ * the following constraints are met:
+ * <ul>
+ * <li>All APKs must have the exact same package name, version code, and signing
+ * certificates.
+ * <li>All installations must contain a single base APK.
+ * <li>All APKs must have unique split names.
+ * </ul>
+ */
 public class PackageInstaller {
     private final PackageManager mPm;
     private final IPackageInstaller mInstaller;
@@ -46,16 +70,18 @@
         mUserId = userId;
     }
 
+    /**
+     * Quickly test if the given package is already available on the device.
+     * This is typically used in multi-user scenarios where another user on the
+     * device has already installed the package.
+     *
+     * @hide
+     */
     public boolean isPackageAvailable(String packageName) {
-        try {
-            final ApplicationInfo info = mPm.getApplicationInfo(packageName,
-                    PackageManager.GET_UNINSTALLED_PACKAGES);
-            return ((info.flags & ApplicationInfo.FLAG_INSTALLED) != 0);
-        } catch (NameNotFoundException e) {
-            return false;
-        }
+        return mPm.isPackageAvailable(packageName);
     }
 
+    /** {@hide} */
     public void installAvailablePackage(String packageName, PackageInstallObserver observer) {
         int returnCode;
         try {
@@ -66,6 +92,18 @@
         observer.packageInstalled(packageName, null, returnCode);
     }
 
+    /**
+     * Create a new session using the given parameters, returning a unique ID
+     * that represents the session. Once created, the session can be opened
+     * multiple times across multiple device boots.
+     * <p>
+     * The system may automatically destroy sessions that have not been
+     * finalized (either committed or abandoned) within a reasonable period of
+     * time, typically on the order of a day.
+     *
+     * @throws IOException if parameters were unsatisfiable, such as lack of
+     *             disk space or unavailable media.
+     */
     public int createSession(InstallSessionParams params) throws IOException {
         try {
             return mInstaller.createSession(mInstallerPackageName, params, mUserId);
@@ -77,6 +115,9 @@
         }
     }
 
+    /**
+     * Open an existing session to actively perform work.
+     */
     public Session openSession(int sessionId) {
         try {
             return new Session(mInstaller.openSession(sessionId));
@@ -85,7 +126,10 @@
         }
     }
 
-    public List<InstallSessionInfo> getSessions() {
+    /**
+     * Return list of all active install sessions on the device.
+     */
+    public List<InstallSessionInfo> getActiveSessions() {
         // TODO: filter based on caller
         // TODO: let launcher app see all active sessions
         try {
@@ -95,6 +139,11 @@
         }
     }
 
+    /**
+     * Uninstall the given package, removing it completely from the device. This
+     * method is only available to the current "installer of record" for the
+     * package.
+     */
     public void uninstall(String packageName, UninstallResultCallback callback) {
         try {
             mInstaller.uninstall(packageName, 0,
@@ -104,6 +153,11 @@
         }
     }
 
+    /**
+     * Uninstall only a specific split from the given package.
+     *
+     * @hide
+     */
     public void uninstall(String packageName, String splitName, UninstallResultCallback callback) {
         try {
             mInstaller.uninstallSplit(packageName, splitName, 0,
@@ -113,6 +167,9 @@
         }
     }
 
+    /**
+     * Events for observing session lifecycle.
+     */
     public static abstract class SessionObserver {
         private final IPackageInstallerObserver.Stub mBinder = new IPackageInstallerObserver.Stub() {
             @Override
@@ -127,7 +184,7 @@
 
             @Override
             public void onSessionFinished(int sessionId, boolean success) {
-                SessionObserver.this.onFinished(sessionId, success);
+                SessionObserver.this.onFinalized(sessionId, success);
             }
         };
 
@@ -136,11 +193,30 @@
             return mBinder;
         }
 
+        /**
+         * New session has been created.
+         */
         public abstract void onCreated(InstallSessionInfo info);
+
+        /**
+         * Progress for given session has been updated.
+         * <p>
+         * Note that this progress may not directly correspond to the value
+         * reported by {@link PackageInstaller.Session#setProgress(int)}, as the
+         * system may carve out a portion of the overall progress to represent
+         * its own internal installation work.
+         */
         public abstract void onProgress(int sessionId, int progress);
-        public abstract void onFinished(int sessionId, boolean success);
+
+        /**
+         * Session has been finalized, either with success or failure.
+         */
+        public abstract void onFinalized(int sessionId, boolean success);
     }
 
+    /**
+     * Register to watch for session lifecycle events.
+     */
     public void registerSessionObserver(SessionObserver observer) {
         try {
             mInstaller.registerObserver(observer.getBinder(), mUserId);
@@ -149,6 +225,9 @@
         }
     }
 
+    /**
+     * Unregister an existing observer.
+     */
     public void unregisterSessionObserver(SessionObserver observer) {
         try {
             mInstaller.unregisterObserver(observer.getBinder(), mUserId);
@@ -177,6 +256,10 @@
             mSession = session;
         }
 
+        /**
+         * Set current progress. Valid values are anywhere between 0 and
+         * {@link InstallSessionParams#setProgressMax(int)}.
+         */
         public void setProgress(int progress) {
             try {
                 mSession.setClientProgress(progress);
@@ -197,7 +280,7 @@
         /**
          * Open an APK file for writing, starting at the given offset. You can
          * then stream data into the file, periodically calling
-         * {@link OutputStream#flush()} to ensure bytes have been written to
+         * {@link #fsync(OutputStream)} to ensure bytes have been written to
          * disk.
          */
         public OutputStream openWrite(String splitName, long offsetBytes, long lengthBytes)
@@ -214,6 +297,11 @@
             }
         }
 
+        /**
+         * Ensure that any outstanding data for given stream has been committed
+         * to disk. This is only valid for streams returned from
+         * {@link #openWrite(String, long, long)}.
+         */
         public void fsync(OutputStream out) throws IOException {
             if (out instanceof FileBridge.FileBridgeOutputStream) {
                 ((FileBridge.FileBridgeOutputStream) out).fsync();
@@ -222,6 +310,15 @@
             }
         }
 
+        /**
+         * Attempt to commit everything staged in this session. This may require
+         * user intervention, and so it may not happen immediately. The final
+         * result of the commit will be reported through the given callback.
+         * <p>
+         * Once this method is called, no additional mutations may be performed
+         * on the session. If the device reboots before the session has been
+         * finalized, you may commit the session again.
+         */
         public void commit(CommitResultCallback callback) {
             try {
                 mSession.install(new CommitResultCallbackDelegate(callback).getBinder());
@@ -230,11 +327,18 @@
             }
         }
 
+        /**
+         * Release this session object. You can open the session again if it
+         * hasn't been finalized.
+         */
         @Override
         public void close() {
             // No resources to release at the moment
         }
 
+        /**
+         * Completely destroy this session, rendering it invalid.
+         */
         public void destroy() {
             try {
                 mSession.destroy();
@@ -244,11 +348,15 @@
         }
     }
 
+    /**
+     * Final result of an uninstall request.
+     */
     public static abstract class UninstallResultCallback {
         public abstract void onSuccess();
         public abstract void onFailure(String msg);
     }
 
+    /** {@hide} */
     private static class UninstallResultCallbackDelegate extends PackageUninstallObserver {
         private final UninstallResultCallback target;
 
@@ -271,8 +379,18 @@
         }
     }
 
+    /**
+     * Final result of a session commit request.
+     */
     public static abstract class CommitResultCallback {
         public abstract void onSuccess();
+
+        /**
+         * Generic failure occurred. You can override methods (such as
+         * {@link #onFailureInvalid(String)}) to handle more specific categories
+         * of failure. By default, those specific categories all flow into this
+         * generic failure.
+         */
         public abstract void onFailure(String msg);
 
         /**
@@ -286,12 +404,16 @@
         }
 
         /**
-         * This install session conflicts (or is inconsistent with) with
-         * another package already installed on the device. For example, an
-         * existing permission, incompatible certificates, etc. The user may
-         * be able to uninstall another app to fix the issue.
+         * This install session conflicts (or is inconsistent with) with another
+         * package already installed on the device. For example, an existing
+         * permission, incompatible certificates, etc. The user may be able to
+         * uninstall another app to fix the issue.
+         *
+         * @param otherPackageName if one specific package was identified as the
+         *            cause of the conflict, it's named here. If unknown, or
+         *            multiple packages, this may be {@code null}.
          */
-        public void onFailureConflict(String msg, String packageName) {
+        public void onFailureConflict(String msg, String otherPackageName) {
             onFailure(msg);
         }
 
@@ -317,6 +439,7 @@
         }
     }
 
+    /** {@hide} */
     private static class CommitResultCallbackDelegate extends PackageInstallObserver {
         private final CommitResultCallback target;
 
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 376369a..052c2ca 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -3672,8 +3672,11 @@
      */
     public abstract VerifierDeviceIdentity getVerifierDeviceIdentity();
 
-    /** {@hide} */
-    public abstract PackageInstaller getPackageInstaller();
+    /**
+     * Return interface that offers the ability to install, upgrade, and remove
+     * applications on the device.
+     */
+    public abstract PackageInstaller getInstaller();
 
     /**
      * Returns the data directory for a particular user and package, given the uid of the package.
@@ -3719,4 +3722,7 @@
      * @hide
      */
     public abstract Drawable loadItemIcon(PackageItemInfo itemInfo, ApplicationInfo appInfo);
+
+    /** {@hide} */
+    public abstract boolean isPackageAvailable(String packageName);
 }
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index d92a90d..7bfe55d 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -380,6 +380,7 @@
         }
         PackageInfo pi = new PackageInfo();
         pi.packageName = p.packageName;
+        pi.splitNames = p.splitNames;
         pi.versionCode = p.mVersionCode;
         pi.versionName = p.mVersionName;
         pi.sharedUserId = p.mSharedUserId;
@@ -1128,11 +1129,8 @@
             } else if (attr.equals("versionCode")) {
                 versionCode = attrs.getAttributeIntValue(i, 0);
                 numFound++;
-            } else if (attr.equals("multiArch")) {
-                multiArch = attrs.getAttributeBooleanValue(i, false);
-                numFound++;
             }
-            if (numFound >= 3) {
+            if (numFound >= 2) {
                 break;
             }
         }
@@ -1154,6 +1152,16 @@
                     verifiers.add(verifier);
                 }
             }
+
+            if (parser.getDepth() == searchDepth && "application".equals(parser.getName())) {
+                for (int i = 0; i < attrs.getAttributeCount(); ++i) {
+                    final String attr = attrs.getAttributeName(i);
+                    if ("multiArch".equals(attr)) {
+                        multiArch = attrs.getAttributeBooleanValue(i, false);
+                        break;
+                    }
+                }
+            }
         }
 
         return new ApkLite(codePath, packageSplit.first, packageSplit.second, versionCode,
diff --git a/core/java/com/android/internal/backup/LocalTransport.java b/core/java/com/android/internal/backup/LocalTransport.java
index 8b56ceb..7e58351 100644
--- a/core/java/com/android/internal/backup/LocalTransport.java
+++ b/core/java/com/android/internal/backup/LocalTransport.java
@@ -56,7 +56,7 @@
 
 public class LocalTransport extends BackupTransport {
     private static final String TAG = "LocalTransport";
-    private static final boolean DEBUG = true;
+    private static final boolean DEBUG = false;
 
     private static final String TRANSPORT_DIR_NAME
             = "com.android.internal.backup.LocalTransport";
diff --git a/media/java/android/media/AudioDevice.java b/media/java/android/media/AudioDevice.java
index 1fd27fe..96d6196 100644
--- a/media/java/android/media/AudioDevice.java
+++ b/media/java/android/media/AudioDevice.java
@@ -66,8 +66,20 @@
         return mConfig.port().address();
     }
 
+    /** @hide */
+    public static int convertDeviceTypeToInternalDevice(int deviceType) {
+        return EXT_TO_INT_DEVICE_MAPPING.get(deviceType, AudioSystem.DEVICE_NONE);
+    }
+
+    /** @hide */
+    public static int convertInternalDeviceToDeviceType(int intDevice) {
+        return INT_TO_EXT_DEVICE_MAPPING.get(intDevice, DEVICE_TYPE_UNKNOWN);
+    }
+
     private static final SparseIntArray INT_TO_EXT_DEVICE_MAPPING;
 
+    private static final SparseIntArray EXT_TO_INT_DEVICE_MAPPING;
+
     static {
         INT_TO_EXT_DEVICE_MAPPING = new SparseIntArray();
         INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_OUT_EARPIECE, DEVICE_TYPE_BUILTIN_EARPIECE);
@@ -110,6 +122,27 @@
         // not covered here, legacy
         //AudioSystem.DEVICE_OUT_REMOTE_SUBMIX
         //AudioSystem.DEVICE_IN_REMOTE_SUBMIX
+
+        // privileges mapping to output device
+        EXT_TO_INT_DEVICE_MAPPING = new SparseIntArray();
+        EXT_TO_INT_DEVICE_MAPPING.put(DEVICE_TYPE_BUILTIN_EARPIECE, AudioSystem.DEVICE_OUT_EARPIECE);
+        EXT_TO_INT_DEVICE_MAPPING.put(DEVICE_TYPE_BUILTIN_SPEAKER, AudioSystem.DEVICE_OUT_SPEAKER);
+        EXT_TO_INT_DEVICE_MAPPING.put(DEVICE_TYPE_WIRED_HEADSET, AudioSystem.DEVICE_OUT_WIRED_HEADSET);
+        EXT_TO_INT_DEVICE_MAPPING.put(DEVICE_TYPE_WIRED_HEADPHONES, AudioSystem.DEVICE_OUT_WIRED_HEADPHONE);
+        EXT_TO_INT_DEVICE_MAPPING.put(DEVICE_TYPE_LINE_ANALOG, AudioSystem.DEVICE_OUT_LINE);
+        EXT_TO_INT_DEVICE_MAPPING.put(DEVICE_TYPE_LINE_DIGITAL, AudioSystem.DEVICE_OUT_SPDIF);
+        EXT_TO_INT_DEVICE_MAPPING.put(DEVICE_TYPE_BLUETOOTH_SCO, AudioSystem.DEVICE_OUT_BLUETOOTH_SCO);
+        EXT_TO_INT_DEVICE_MAPPING.put(DEVICE_TYPE_BLUETOOTH_A2DP, AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP);
+        EXT_TO_INT_DEVICE_MAPPING.put(DEVICE_TYPE_HDMI, AudioSystem.DEVICE_OUT_HDMI);
+        EXT_TO_INT_DEVICE_MAPPING.put(DEVICE_TYPE_HDMI_ARC, AudioSystem.DEVICE_OUT_HDMI_ARC);
+        EXT_TO_INT_DEVICE_MAPPING.put(DEVICE_TYPE_USB_DEVICE, AudioSystem.DEVICE_OUT_USB_DEVICE);
+        EXT_TO_INT_DEVICE_MAPPING.put(DEVICE_TYPE_USB_ACCESSORY, AudioSystem.DEVICE_OUT_USB_ACCESSORY);
+        EXT_TO_INT_DEVICE_MAPPING.put(DEVICE_TYPE_DOCK, AudioSystem.DEVICE_OUT_ANLG_DOCK_HEADSET);
+        EXT_TO_INT_DEVICE_MAPPING.put(DEVICE_TYPE_FM, AudioSystem.DEVICE_OUT_FM);
+        EXT_TO_INT_DEVICE_MAPPING.put(DEVICE_TYPE_BUILTIN_MIC, AudioSystem.DEVICE_IN_BUILTIN_MIC);
+        EXT_TO_INT_DEVICE_MAPPING.put(DEVICE_TYPE_FM_TUNER, AudioSystem.DEVICE_IN_FM_TUNER);
+        EXT_TO_INT_DEVICE_MAPPING.put(DEVICE_TYPE_TV_TUNER, AudioSystem.DEVICE_IN_TV_TUNER);
+        EXT_TO_INT_DEVICE_MAPPING.put(DEVICE_TYPE_TELEPHONY, AudioSystem.DEVICE_OUT_TELEPHONY_TX);
     }
 }
 
diff --git a/media/java/android/media/AudioFormat.java b/media/java/android/media/AudioFormat.java
index 025d354..79be108 100644
--- a/media/java/android/media/AudioFormat.java
+++ b/media/java/android/media/AudioFormat.java
@@ -78,9 +78,9 @@
     public static final int CHANNEL_OUT_FRONT_LEFT_OF_CENTER = 0x100;
     public static final int CHANNEL_OUT_FRONT_RIGHT_OF_CENTER = 0x200;
     public static final int CHANNEL_OUT_BACK_CENTER = 0x400;
-    /** @hide */
+    /** @hide  CANDIDATE FOR PUBLIC API */
     public static final int CHANNEL_OUT_SIDE_LEFT =         0x800;
-    /** @hide */
+    /** @hide  CANDIDATE FOR PUBLIC API */
     public static final int CHANNEL_OUT_SIDE_RIGHT =       0x1000;
     /** @hide */
     public static final int CHANNEL_OUT_TOP_CENTER =       0x2000;
@@ -128,6 +128,35 @@
             CHANNEL_OUT_LOW_FREQUENCY);
     // CHANNEL_OUT_ALL is not yet defined; if added then it should match AUDIO_CHANNEL_OUT_ALL
 
+    /**
+     * @hide
+     * Return the number of channels from an output channel mask
+     * @param mask a combination of the CHANNEL_OUT_* definitions, but not CHANNEL_OUT_DEFAULT
+     * @return number of channels for the mask
+     */
+    public static int channelCountFromOutChannelMask(int mask) {
+        return Integer.bitCount(mask);
+    }
+    /**
+     * @hide
+     * Return a channel mask ready to be used by native code
+     * @param mask a combination of the CHANNEL_OUT_* definitions, but not CHANNEL_OUT_DEFAULT
+     * @return a native channel mask
+     */
+    public static int convertChannelOutMaskToNativeMask(int javaMask) {
+        return (javaMask >> 2);
+    }
+
+    /**
+     * @hide
+     * Return a java output channel mask
+     * @param mask a native channel mask
+     * @return a combination of the CHANNEL_OUT_* definitions
+     */
+    public static int convertNativeChannelMaskToOutMask(int nativeMask) {
+        return (nativeMask << 2);
+    }
+
     public static final int CHANNEL_IN_DEFAULT = 1;
     // These directly match native
     public static final int CHANNEL_IN_LEFT = 0x4;
diff --git a/media/java/android/media/audiofx/AudioEffect.java b/media/java/android/media/audiofx/AudioEffect.java
index 9b381cc..9fa3f50 100644
--- a/media/java/android/media/audiofx/AudioEffect.java
+++ b/media/java/android/media/audiofx/AudioEffect.java
@@ -1282,7 +1282,7 @@
     /**
      * @hide
      */
-    public int byteArrayToInt(byte[] valueBuf) {
+    public static int byteArrayToInt(byte[] valueBuf) {
         return byteArrayToInt(valueBuf, 0);
 
     }
@@ -1290,7 +1290,7 @@
     /**
      * @hide
      */
-    public int byteArrayToInt(byte[] valueBuf, int offset) {
+    public static int byteArrayToInt(byte[] valueBuf, int offset) {
         ByteBuffer converter = ByteBuffer.wrap(valueBuf);
         converter.order(ByteOrder.nativeOrder());
         return converter.getInt(offset);
@@ -1300,7 +1300,7 @@
     /**
      * @hide
      */
-    public byte[] intToByteArray(int value) {
+    public static byte[] intToByteArray(int value) {
         ByteBuffer converter = ByteBuffer.allocate(4);
         converter.order(ByteOrder.nativeOrder());
         converter.putInt(value);
@@ -1310,14 +1310,14 @@
     /**
      * @hide
      */
-    public short byteArrayToShort(byte[] valueBuf) {
+    public static short byteArrayToShort(byte[] valueBuf) {
         return byteArrayToShort(valueBuf, 0);
     }
 
     /**
      * @hide
      */
-    public short byteArrayToShort(byte[] valueBuf, int offset) {
+    public static short byteArrayToShort(byte[] valueBuf, int offset) {
         ByteBuffer converter = ByteBuffer.wrap(valueBuf);
         converter.order(ByteOrder.nativeOrder());
         return converter.getShort(offset);
@@ -1327,7 +1327,7 @@
     /**
      * @hide
      */
-    public byte[] shortToByteArray(short value) {
+    public static byte[] shortToByteArray(short value) {
         ByteBuffer converter = ByteBuffer.allocate(2);
         converter.order(ByteOrder.nativeOrder());
         short sValue = (short) value;
@@ -1338,7 +1338,7 @@
     /**
      * @hide
      */
-    public byte[] concatArrays(byte[]... arrays) {
+    public static byte[] concatArrays(byte[]... arrays) {
         int len = 0;
         for (byte[] a : arrays) {
             len += a.length;
diff --git a/media/java/android/media/audiofx/Virtualizer.java b/media/java/android/media/audiofx/Virtualizer.java
index 6b20006..136761b 100644
--- a/media/java/android/media/audiofx/Virtualizer.java
+++ b/media/java/android/media/audiofx/Virtualizer.java
@@ -16,9 +16,13 @@
 
 package android.media.audiofx;
 
+import android.media.AudioDevice;
+import android.media.AudioFormat;
 import android.media.audiofx.AudioEffect;
 import android.util.Log;
 
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
 import java.util.StringTokenizer;
 
 
@@ -44,8 +48,10 @@
 public class Virtualizer extends AudioEffect {
 
     private final static String TAG = "Virtualizer";
+    private final static boolean DEBUG = false;
 
-    // These constants must be synchronized with those in frameworks/base/include/media/EffectVirtualizerApi.h
+    // These constants must be synchronized with those in
+    //        system/media/audio_effects/include/audio_effects/effect_virtualizer.h
     /**
      * Is strength parameter supported by virtualizer engine. Parameter ID for getParameter().
      */
@@ -55,6 +61,21 @@
      * {@link android.media.audiofx.Virtualizer.OnParameterChangeListener}
      */
     public static final int PARAM_STRENGTH = 1;
+    /**
+     * @hide
+     * Parameter ID to query the virtual speaker angles for a channel mask / device configuration.
+     */
+    public static final int PARAM_VIRTUAL_SPEAKER_ANGLES = 2;
+    /**
+     * @hide
+     * Parameter ID to force the virtualization mode to be that of a specific device
+     */
+    public static final int PARAM_FORCE_VIRTUALIZATION_MODE = 3;
+    /**
+     * @hide
+     * Parameter ID to query the current virtualization mode.
+     */
+    public static final int PARAM_VIRTUALIZATION_MODE = 4;
 
     /**
      * Indicates if strength parameter is supported by the virtualizer engine
@@ -145,6 +166,223 @@
     }
 
     /**
+     * Checks if a configuration is supported, and query the virtual speaker angles.
+     * @param inputChannelMask
+     * @param deviceType
+     * @param angles if non-null: array in which the angles will be written. If null, no angles
+     *    are returned
+     * @return true if the combination of channel mask and output device type is supported, false
+     *    otherwise
+     * @throws IllegalStateException
+     * @throws IllegalArgumentException
+     * @throws UnsupportedOperationException
+     */
+    private boolean getAnglesInt(int inputChannelMask, int deviceType, int[] angles)
+            throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException {
+        // parameter check
+        if (inputChannelMask == AudioFormat.CHANNEL_INVALID) {
+            throw (new IllegalArgumentException(
+                    "Virtualizer: illegal CHANNEL_INVALID channel mask"));
+        }
+        int channelMask = inputChannelMask == AudioFormat.CHANNEL_OUT_DEFAULT ?
+                AudioFormat.CHANNEL_OUT_STEREO : inputChannelMask;
+        int nbChannels = AudioFormat.channelCountFromOutChannelMask(channelMask);
+        if ((angles != null) && (angles.length < (nbChannels * 3))) {
+            Log.e(TAG, "Size of array for angles cannot accomodate number of channels in mask ("
+                    + nbChannels + ")");
+            throw (new IllegalArgumentException(
+                    "Virtualizer: array for channel / angle pairs is too small: is " + angles.length
+                    + ", should be " + (nbChannels * 3)));
+        }
+
+        ByteBuffer paramsConverter = ByteBuffer.allocate(3 /* param + mask + device*/ * 4);
+        paramsConverter.order(ByteOrder.nativeOrder());
+        paramsConverter.putInt(PARAM_VIRTUAL_SPEAKER_ANGLES);
+        // convert channel mask to internal native representation
+        paramsConverter.putInt(AudioFormat.convertChannelOutMaskToNativeMask(channelMask));
+        // convert Java device type to internal representation
+        paramsConverter.putInt(AudioDevice.convertDeviceTypeToInternalDevice(deviceType));
+        // allocate an array to store the results
+        byte[] result = new byte[nbChannels * 4/*int to byte*/ * 3/*for mask, azimuth, elevation*/];
+
+        // call into the effect framework
+        int status = getParameter(paramsConverter.array(), result);
+        if (DEBUG) {
+            Log.v(TAG, "getAngles(0x" + Integer.toHexString(inputChannelMask) + ", 0x"
+                    + Integer.toHexString(deviceType) + ") returns " + status);
+        }
+
+        if (status >= 0) {
+            if (angles != null) {
+                // convert and copy the results
+                ByteBuffer resultConverter = ByteBuffer.wrap(result);
+                resultConverter.order(ByteOrder.nativeOrder());
+                for (int i = 0 ; i < nbChannels ; i++) {
+                    // write the channel mask
+                    angles[3 * i] = AudioFormat.convertNativeChannelMaskToOutMask(
+                            resultConverter.getInt((i * 4 * 3)));
+                    // write the azimuth
+                    angles[3 * i + 1] = resultConverter.getInt(i * 4 * 3 + 4);
+                    // write the elevation
+                    angles[3 * i + 2] = resultConverter.getInt(i * 4 * 3 + 8);
+                    if (DEBUG) {
+                        Log.v(TAG, "channel 0x" + Integer.toHexString(angles[3*i]).toUpperCase()
+                                + " at az=" + angles[3*i+1] + "deg"
+                                + " elev="  + angles[3*i+2] + "deg");
+                    }
+                }
+            }
+            return true;
+        } else if (status == AudioEffect.ERROR_BAD_VALUE) {
+            // a BAD_VALUE return from getParameter indicates the configuration is not supported
+            // don't throw an exception, just return false
+            return false;
+        } else {
+            // something wrong may have happened
+            checkStatus(status);
+        }
+        // unexpected virtualizer behavior
+        Log.e(TAG, "unexpected status code " + status
+                + " after getParameter(PARAM_VIRTUAL_SPEAKER_ANGLES)");
+        return false;
+    }
+
+    /**
+     * @hide
+     * CANDIDATE FOR PUBLIC API
+     * Checks if the combination of a channel mask and device type is supported by this virtualizer.
+     * Some virtualizer implementations may only support binaural processing (i.e. only support
+     * headphone output), some may support transaural processing (i.e. for speaker output) for the
+     * built-in speakers. Use this method to query the virtualizer implementation capabilities.
+     * @param inputChannelMask the channel mask of the content to virtualize.
+     * @param deviceType the device type for which virtualization processing is to be performed.
+     *    Valid values are the device types defined in {@link AudioDevice}.
+     * @return true if the combination of channel mask and output device type is supported, false
+     *    otherwise.
+     *    <br>An indication that a certain channel mask is not supported doesn't necessarily mean
+     *    you cannot play content with that channel mask, it more likely implies the content will
+     *    be downmixed before being virtualized. For instance a virtualizer that only supports a
+     *    mask such as {@link AudioFormat#CHANNEL_OUT_STEREO}
+     *    will still be able to process content with a mask of
+     *    {@link AudioFormat#CHANNEL_OUT_5POINT1}, but will downmix the content to stereo first, and
+     *    then will virtualize, as opposed to virtualizing each channel individually.
+     * @throws IllegalStateException
+     * @throws IllegalArgumentException
+     * @throws UnsupportedOperationException
+     */
+    public boolean canVirtualize(int inputChannelMask, int deviceType)
+            throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException {
+        return getAnglesInt(inputChannelMask, deviceType, null);
+    }
+
+    /**
+     * @hide
+     * CANDIDATE FOR PUBLIC API
+     * Queries the virtual speaker angles (azimuth and elevation) for a combination of a channel
+     * mask and device type.
+     * If the virtualization configuration (mask and device) is supported (see
+     * {@link #canVirtualize(int, int)}, the array angles will contain upon return the
+     * definition of each virtual speaker and its azimuth and elevation angles relative to the
+     * listener.
+     * <br>Note that in some virtualizer implementations, the angles may be strength-dependent.
+     * @param inputChannelMask the channel mask of the content to virtualize.
+     * @param deviceType the device type for which virtualization processing is to be performed.
+     *    Valid values are the device types defined in {@link AudioDevice}.
+     * @param angles a non-null array whose length is 3 times the number of channels in the channel
+     *    mask.
+     *    If the method indicates the configuration is supported, the array will contain upon return
+     *    triplets of values: for each channel <code>i</code> among the channels of the mask:
+     *    <ul>
+     *      <li>the element at index <code>3*i</code> in the array contains the speaker
+     *          identification (e.g. {@link AudioFormat#CHANNEL_OUT_FRONT_LEFT}),</li>
+     *      <li>the element at index <code>3*i+1</code> contains its corresponding azimuth angle
+     *          expressed in degrees, where 0 is the direction the listener faces, 180 is behind
+     *          the listener, and -90 is to her/his left,</li>
+     *      <li>the element at index <code>3*i+2</code> contains its corresponding elevation angle
+     *          where +90 is directly above the listener, 0 is the horizontal plane, and -90 is
+     *          directly below the listener.</li>
+     * @return true if the combination of channel mask and output device type is supported, false
+     *    otherwise.
+     * @throws IllegalStateException
+     * @throws IllegalArgumentException
+     * @throws UnsupportedOperationException
+     */
+    public boolean getSpeakerAngles(int inputChannelMask, int deviceType, int[] angles)
+            throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException {
+        if (angles == null) {
+            throw (new IllegalArgumentException(
+                    "Virtualizer: illegal null channel / angle array"));
+        }
+
+        return getAnglesInt(inputChannelMask, deviceType, angles);
+    }
+
+    /**
+     * @hide
+     * CANDIDATE FOR PUBLIC API
+     * Forces the virtualizer effect to use the processing mode used for the given device type.
+     * The effect must be enabled for the forced mode to be applied.
+     * @param deviceType one of the device types defined in {@link AudioDevice}.
+     *     Use {@link AudioDevice#DEVICE_TYPE_UNKNOWN} to return to the non-forced mode.
+     * @return true if the processing mode for the device type is supported, and it is successfully
+     *     set, or forcing was successfully disabled with {@link AudioDevice#DEVICE_TYPE_UNKNOWN},
+     *     false otherwise.
+     * @throws IllegalStateException
+     * @throws IllegalArgumentException
+     * @throws UnsupportedOperationException
+     */
+    public boolean forceVirtualizationMode(int deviceType)
+            throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException {
+        // convert Java device type to internal representation
+        int internalDevice = AudioDevice.convertDeviceTypeToInternalDevice(deviceType);
+
+        int status = setParameter(PARAM_FORCE_VIRTUALIZATION_MODE, internalDevice);
+
+        if (status >= 0) {
+            return true;
+        } else if (status == AudioEffect.ERROR_BAD_VALUE) {
+            // a BAD_VALUE return from setParameter indicates the mode can't be forced to that
+            // of this device, don't throw an exception, just return false
+            return false;
+        } else {
+            // something wrong may have happened
+            checkStatus(status);
+        }
+        // unexpected virtualizer behavior
+        Log.e(TAG, "unexpected status code " + status
+                + " after setParameter(PARAM_FORCE_VIRTUALIZATION_MODE)");
+        return false;
+    }
+
+    /**
+     * @hide
+     * CANDIDATE FOR PUBLIC API
+     * Return the device type which reflects the virtualization mode being used, if any.
+     * @return a device type (as defined in {@link AudioDevice}) which reflects the virtualization
+     *     mode being used.
+     *     If virtualization is not active, the device type will be
+     *     {@link AudioDevice#DEVICE_TYPE_UNKNOWN}. Virtualization may not be active either because
+     *     the effect is not enabled or because the current output device is not compatible with
+     *     this virtualization implementation.
+     */
+    public int getVirtualizationMode() {
+        int[] value = new int[1];
+        int status = getParameter(PARAM_VIRTUALIZATION_MODE, value);
+        if (status >= 0) {
+            return AudioDevice.convertInternalDeviceToDeviceType(value[0]);
+        } else if (status == AudioEffect.ERROR_BAD_VALUE) {
+            return AudioDevice.DEVICE_TYPE_UNKNOWN;
+        } else {
+            // something wrong may have happened
+            checkStatus(status);
+        }
+        // unexpected virtualizer behavior
+        Log.e(TAG, "unexpected status code " + status
+                + " after getParameter(PARAM_VIRTUALIZATION_MODE)");
+        return AudioDevice.DEVICE_TYPE_UNKNOWN;
+    }
+
+    /**
      * The OnParameterChangeListener interface defines a method called by the Virtualizer when a
      * parameter value has changed.
      */
diff --git a/media/java/android/media/tv/ITvInputClient.aidl b/media/java/android/media/tv/ITvInputClient.aidl
index cac8a14..423e317 100644
--- a/media/java/android/media/tv/ITvInputClient.aidl
+++ b/media/java/android/media/tv/ITvInputClient.aidl
@@ -30,7 +30,6 @@
  */
 oneway interface ITvInputClient {
     void onSessionCreated(in String inputId, IBinder token, in InputChannel channel, int seq);
-    void onAvailabilityChanged(in String inputId, boolean isAvailable);
     void onSessionReleased(int seq);
     void onSessionEvent(in String name, in Bundle args, int seq);
     void onChannelRetuned(in Uri channelUri, int seq);
diff --git a/media/java/android/media/tv/ITvInputManager.aidl b/media/java/android/media/tv/ITvInputManager.aidl
index 9a6a648..6a0c592 100644
--- a/media/java/android/media/tv/ITvInputManager.aidl
+++ b/media/java/android/media/tv/ITvInputManager.aidl
@@ -18,9 +18,10 @@
 
 import android.content.ComponentName;
 import android.graphics.Rect;
+import android.media.tv.ITvInputClient;
 import android.media.tv.ITvInputHardware;
 import android.media.tv.ITvInputHardwareCallback;
-import android.media.tv.ITvInputClient;
+import android.media.tv.ITvInputManagerCallback;
 import android.media.tv.TvInputHardwareInfo;
 import android.media.tv.TvInputInfo;
 import android.media.tv.TvTrackInfo;
@@ -34,10 +35,8 @@
 interface ITvInputManager {
     List<TvInputInfo> getTvInputList(int userId);
 
-    boolean getAvailability(in ITvInputClient client, in String inputId, int userId);
-
-    void registerCallback(in ITvInputClient client, in String inputId, int userId);
-    void unregisterCallback(in ITvInputClient client, in String inputId, int userId);
+    void registerCallback(in ITvInputManagerCallback callback, int userId);
+    void unregisterCallback(in ITvInputManagerCallback callback, int userId);
 
     void createSession(in ITvInputClient client, in String inputId, int seq, int userId);
     void releaseSession(in IBinder sessionToken, int userId);
@@ -56,7 +55,12 @@
 
     // For TV input hardware binding
     List<TvInputHardwareInfo> getHardwareList();
+    /*
+     * All TvInputServices which want to use hardware must call this method on
+     * BOOT_COMPLETE.
+     */
+    void registerTvInputInfo(in TvInputInfo info, int deviceId);
     ITvInputHardware acquireTvInputHardware(int deviceId, in ITvInputHardwareCallback callback,
-            int userId);
+            in TvInputInfo info, int userId);
     void releaseTvInputHardware(int deviceId, in ITvInputHardware hardware, int userId);
 }
diff --git a/media/java/android/media/tv/ITvInputManagerCallback.aidl b/media/java/android/media/tv/ITvInputManagerCallback.aidl
new file mode 100644
index 0000000..5c8a0a3
--- /dev/null
+++ b/media/java/android/media/tv/ITvInputManagerCallback.aidl
@@ -0,0 +1,25 @@
+/*
+ * 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.media.tv;
+
+/**
+ * Interface to receive callbacks from ITvInputManager regardless of sessions.
+ * @hide
+ */
+oneway interface ITvInputManagerCallback {
+    void onInputStateChanged(in String inputId, int state);
+}
diff --git a/media/java/android/media/tv/ITvInputServiceCallback.aidl b/media/java/android/media/tv/ITvInputServiceCallback.aidl
index c9484dd..1fdb8c5 100644
--- a/media/java/android/media/tv/ITvInputServiceCallback.aidl
+++ b/media/java/android/media/tv/ITvInputServiceCallback.aidl
@@ -24,5 +24,5 @@
  * @hide
  */
 oneway interface ITvInputServiceCallback {
-    void onAvailabilityChanged(in String inputId, boolean isAvailable);
+    void onInputStateChanged(int state);
 }
diff --git a/media/java/android/media/tv/TvInputInfo.java b/media/java/android/media/tv/TvInputInfo.java
index 7b8f2ec..5624f3e 100644
--- a/media/java/android/media/tv/TvInputInfo.java
+++ b/media/java/android/media/tv/TvInputInfo.java
@@ -80,7 +80,7 @@
     // Attributes from XML meta data.
     private String mSetupActivity;
     private String mSettingsActivity;
-    private int mType;
+    private int mType = TYPE_VIRTUAL;
 
     /**
      * Create a new instance of the TvInputInfo class,
@@ -128,10 +128,13 @@
                 Log.d(TAG, "Settings activity loaded. [" + input.mSettingsActivity + "] for "
                         + si.name);
             }
-            input.mType = sa.getInt(
-                    com.android.internal.R.styleable.TvInputService_tvInputType, TYPE_VIRTUAL);
-            if (DEBUG) {
-                Log.d(TAG, "Type loaded. [" + input.mType + "] for " + si.name);
+            if (pm.checkPermission(android.Manifest.permission.TV_INPUT_HARDWARE, si.packageName)
+                    == PackageManager.PERMISSION_GRANTED) {
+                input.mType = sa.getInt(
+                        com.android.internal.R.styleable.TvInputService_tvInputType, TYPE_VIRTUAL);
+                if (DEBUG) {
+                    Log.d(TAG, "Type loaded. [" + input.mType + "] for " + si.name);
+                }
             }
             sa.recycle();
 
diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java
index 867b0db..79a83b0 100644
--- a/media/java/android/media/tv/TvInputManager.java
+++ b/media/java/android/media/tv/TvInputManager.java
@@ -24,6 +24,7 @@
 import android.os.Looper;
 import android.os.Message;
 import android.os.RemoteException;
+import android.util.ArrayMap;
 import android.util.Log;
 import android.util.Pools.Pool;
 import android.util.Pools.SimplePool;
@@ -37,6 +38,7 @@
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.Iterator;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 
@@ -65,11 +67,43 @@
      */
     public static final int VIDEO_UNAVAILABLE_REASON_BUFFERING = 3;
 
+    /**
+     * The TV input is connected.
+     * <p>
+     * State for {@link #getInputState} and {@link
+     * TvInputManager.TvInputListener#onInputStateChanged}.
+     * </p>
+     */
+    public static final int INPUT_STATE_CONNECTED = 0;
+    /**
+     * The TV input is connected but in standby mode. It would take a while until it becomes
+     * fully ready.
+     * <p>
+     * State for {@link #getInputState} and {@link
+     * TvInputManager.TvInputListener#onInputStateChanged}.
+     * </p>
+     */
+    public static final int INPUT_STATE_CONNECTED_STANDBY = 1;
+    /**
+     * The TV input is disconnected.
+     * <p>
+     * State for {@link #getInputState} and {@link
+     * TvInputManager.TvInputListener#onInputStateChanged}.
+     * </p>
+     */
+    public static final int INPUT_STATE_DISCONNECTED = 2;
+
     private final ITvInputManager mService;
 
-    // A mapping from an input to the list of its TvInputListenerRecords.
-    private final Map<String, List<TvInputListenerRecord>> mTvInputListenerRecordsMap =
-            new HashMap<String, List<TvInputListenerRecord>>();
+    private final Object mLock = new Object();
+
+    // @GuardedBy(mLock)
+    private final List<TvInputListenerRecord> mTvInputListenerRecordsList =
+            new LinkedList<TvInputListenerRecord>();
+
+    // A mapping from TV input ID to the state of corresponding input.
+    // @GuardedBy(mLock)
+    private final Map<String, Integer> mStateMap = new ArrayMap<String, Integer>();
 
     // A mapping from the sequence number of a session to its SessionCallbackRecord.
     private final SparseArray<SessionCallbackRecord> mSessionCallbackRecordMap =
@@ -81,6 +115,8 @@
 
     private final ITvInputClient mClient;
 
+    private final ITvInputManagerCallback mCallback;
+
     private final int mUserId;
 
     /**
@@ -242,13 +278,17 @@
      */
     public abstract static class TvInputListener {
         /**
-         * This is called when the availability status of a given TV input is changed.
+         * This is called when the state of a given TV input is changed.
          *
          * @param inputId the id of the TV input.
-         * @param isAvailable {@code true} if the given TV input is available to show TV programs.
-         *            {@code false} otherwise.
+         * @param state state of the TV input. The value is one of the following:
+         * <ul>
+         * <li>{@link TvInputManager#INPUT_STATE_CONNECTED}
+         * <li>{@link TvInputManager#INPUT_STATE_CONNECTED_STANDBY}
+         * <li>{@link TvInputManager#INPUT_STATE_DISCONNECTED}
+         * </ul>
          */
-        public void onAvailabilityChanged(String inputId, boolean isAvailable) {
+        public void onInputStateChanged(String inputId, int state) {
         }
     }
 
@@ -265,11 +305,11 @@
             return mListener;
         }
 
-        public void postAvailabilityChanged(final String inputId, final boolean isAvailable) {
+        public void postStateChanged(final String inputId, final int state) {
             mHandler.post(new Runnable() {
                 @Override
                 public void run() {
-                    mListener.onAvailabilityChanged(inputId, isAvailable);
+                    mListener.onInputStateChanged(inputId, state);
                 }
             });
         }
@@ -373,22 +413,23 @@
                     record.postSessionEvent(eventType, eventArgs);
                 }
             }
-
+        };
+        mCallback = new ITvInputManagerCallback.Stub() {
             @Override
-            public void onAvailabilityChanged(String inputId, boolean isAvailable) {
-                synchronized (mTvInputListenerRecordsMap) {
-                    List<TvInputListenerRecord> records = mTvInputListenerRecordsMap.get(inputId);
-                    if (records == null) {
-                        // Silently ignore - no listener is registered yet.
-                        return;
-                    }
-                    int recordsCount = records.size();
-                    for (int i = 0; i < recordsCount; i++) {
-                        records.get(i).postAvailabilityChanged(inputId, isAvailable);
+            public void onInputStateChanged(String inputId, int state) {
+                synchronized (mLock) {
+                    mStateMap.put(inputId, state);
+                    for (TvInputListenerRecord record : mTvInputListenerRecordsList) {
+                        record.postStateChanged(inputId, state);
                     }
                 }
             }
         };
+        try {
+            mService.registerCallback(mCallback, mUserId);
+        } catch (RemoteException e) {
+            Log.e(TAG, "mService.registerCallback failed: " + e);
+        }
     }
 
     /**
@@ -405,98 +446,66 @@
     }
 
     /**
-     * Returns the availability of a given TV input.
+     * Returns the state of a given TV input. It retuns one of the following:
+     * <ul>
+     * <li>{@link #INPUT_STATE_CONNECTED}
+     * <li>{@link #INPUT_STATE_CONNECTED_STANDBY}
+     * <li>{@link #INPUT_STATE_DISCONNECTED}
+     * </ul>
      *
      * @param inputId the id of the TV input.
-     * @throws IllegalArgumentException if the argument is {@code null}.
-     * @throws IllegalStateException If there is no {@link TvInputListener} registered on the given
-     *             TV input.
+     * @throws IllegalArgumentException if the argument is {@code null} or if there is no
+     *        {@link TvInputInfo} corresponding to {@code inputId}.
      */
-    public boolean getAvailability(String inputId) {
+    public int getInputState(String inputId) {
         if (inputId == null) {
             throw new IllegalArgumentException("id cannot be null");
         }
-        synchronized (mTvInputListenerRecordsMap) {
-            List<TvInputListenerRecord> records = mTvInputListenerRecordsMap.get(inputId);
-            if (records == null || records.size() == 0) {
-                throw new IllegalStateException("At least one listener should be registered.");
+        synchronized (mLock) {
+            Integer state = mStateMap.get(inputId);
+            if (state == null) {
+                throw new IllegalArgumentException("Unrecognized input ID: " + inputId);
             }
-        }
-        try {
-            return mService.getAvailability(mClient, inputId, mUserId);
-        } catch (RemoteException e) {
-            throw new RuntimeException(e);
+            return state.intValue();
         }
     }
 
     /**
-     * Registers a {@link TvInputListener} for a given TV input.
+     * Registers a {@link TvInputListener}.
      *
-     * @param inputId the id of the TV input.
-     * @param listener a listener used to monitor status of the given TV input.
+     * @param listener a listener used to monitor status of the TV inputs.
      * @param handler a {@link Handler} that the status change will be delivered to.
      * @throws IllegalArgumentException if any of the arguments is {@code null}.
-     * @hide
      */
-    public void registerListener(String inputId, TvInputListener listener, Handler handler) {
-        if (inputId == null) {
-            throw new IllegalArgumentException("id cannot be null");
-        }
+    public void registerListener(TvInputListener listener, Handler handler) {
         if (listener == null) {
             throw new IllegalArgumentException("listener cannot be null");
         }
         if (handler == null) {
             throw new IllegalArgumentException("handler cannot be null");
         }
-        synchronized (mTvInputListenerRecordsMap) {
-            List<TvInputListenerRecord> records = mTvInputListenerRecordsMap.get(inputId);
-            if (records == null) {
-                records = new ArrayList<TvInputListenerRecord>();
-                mTvInputListenerRecordsMap.put(inputId, records);
-                try {
-                    mService.registerCallback(mClient, inputId, mUserId);
-                } catch (RemoteException e) {
-                    throw new RuntimeException(e);
-                }
-            }
-            records.add(new TvInputListenerRecord(listener, handler));
+        synchronized (mLock) {
+            mTvInputListenerRecordsList.add(new TvInputListenerRecord(listener, handler));
         }
     }
 
     /**
-     * Unregisters the existing {@link TvInputListener} for a given TV input.
+     * Unregisters the existing {@link TvInputListener}.
      *
-     * @param inputId the id of the TV input.
-     * @param listener the existing listener to remove for the given TV input.
+     * @param listener the existing listener to remove.
      * @throws IllegalArgumentException if any of the arguments is {@code null}.
-     * @hide
      */
-    public void unregisterListener(String inputId, final TvInputListener listener) {
-        if (inputId == null) {
-            throw new IllegalArgumentException("id cannot be null");
-        }
+    public void unregisterListener(final TvInputListener listener) {
         if (listener == null) {
             throw new IllegalArgumentException("listener cannot be null");
         }
-        synchronized (mTvInputListenerRecordsMap) {
-            List<TvInputListenerRecord> records = mTvInputListenerRecordsMap.get(inputId);
-            if (records == null) {
-                Log.e(TAG, "No listener found for " + inputId);
-                return;
-            }
-            for (Iterator<TvInputListenerRecord> it = records.iterator(); it.hasNext();) {
+        synchronized (mLock) {
+            for (Iterator<TvInputListenerRecord> it = mTvInputListenerRecordsList.iterator();
+                    it.hasNext(); ) {
                 TvInputListenerRecord record = it.next();
                 if (record.getListener() == listener) {
                     it.remove();
-                }
-            }
-            if (records.isEmpty()) {
-                try {
-                    mService.unregisterCallback(mClient, inputId, mUserId);
-                } catch (RemoteException e) {
-                    throw new RuntimeException(e);
-                } finally {
-                    mTvInputListenerRecordsMap.remove(inputId);
+                    break;
                 }
             }
         }
diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java
index a994f54..3206320 100644
--- a/media/java/android/media/tv/TvInputService.java
+++ b/media/java/android/media/tv/TvInputService.java
@@ -82,20 +82,9 @@
      */
     public static final String SERVICE_META_DATA = "android.media.tv.input";
 
-    private String mId;
     private final Handler mHandler = new ServiceHandler();
     private final RemoteCallbackList<ITvInputServiceCallback> mCallbacks =
             new RemoteCallbackList<ITvInputServiceCallback>();
-    // STOPSHIP: Redesign the API around the availability change. For now, the service will be
-    // always available.
-    private final boolean mAvailable = true;
-
-    @Override
-    public void onCreate() {
-        super.onCreate();
-        mId = TvInputInfo.generateInputIdForComponentName(
-                new ComponentName(getPackageName(), getClass().getName()));
-    }
 
     @Override
     public final IBinder onBind(Intent intent) {
@@ -104,13 +93,6 @@
             public void registerCallback(ITvInputServiceCallback cb) {
                 if (cb != null) {
                     mCallbacks.register(cb);
-                    // The first time a callback is registered, the service needs to report its
-                    // availability status so that the system can know its initial value.
-                    try {
-                        cb.onAvailabilityChanged(mId, mAvailable);
-                    } catch (RemoteException e) {
-                        Log.e(TAG, "error in onAvailabilityChanged", e);
-                    }
                 }
             }
 
@@ -733,7 +715,6 @@
     @SuppressLint("HandlerLeak")
     private final class ServiceHandler extends Handler {
         private static final int DO_CREATE_SESSION = 1;
-        private static final int DO_BROADCAST_AVAILABILITY_CHANGE = 2;
 
         @Override
         public final void handleMessage(Message msg) {
@@ -759,20 +740,6 @@
                     args.recycle();
                     return;
                 }
-                case DO_BROADCAST_AVAILABILITY_CHANGE: {
-                    boolean isAvailable = (Boolean) msg.obj;
-                    int n = mCallbacks.beginBroadcast();
-                    try {
-                        for (int i = 0; i < n; i++) {
-                            mCallbacks.getBroadcastItem(i).onAvailabilityChanged(mId, isAvailable);
-                        }
-                    } catch (RemoteException e) {
-                        Log.e(TAG, "Unexpected exception", e);
-                    } finally {
-                        mCallbacks.finishBroadcast();
-                    }
-                    return;
-                }
                 default: {
                     Log.w(TAG, "Unhandled message code: " + msg.what);
                     return;
diff --git a/packages/SystemUI/res/layout/qs_user_detail.xml b/packages/SystemUI/res/layout/qs_user_detail.xml
new file mode 100644
index 0000000..eedae9f
--- /dev/null
+++ b/packages/SystemUI/res/layout/qs_user_detail.xml
@@ -0,0 +1,24 @@
+<?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
+  -->
+
+<com.android.systemui.qs.tiles.UserDetail
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+    <include layout="@layout/user_switcher_host" />
+</com.android.systemui.qs.tiles.UserDetail>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/recents_task_view.xml b/packages/SystemUI/res/layout/recents_task_view.xml
index 7e8bfd32..3e4c1f6 100644
--- a/packages/SystemUI/res/layout/recents_task_view.xml
+++ b/packages/SystemUI/res/layout/recents_task_view.xml
@@ -28,7 +28,7 @@
         android:layout_height="@dimen/recents_task_bar_height"
         android:layout_gravity="top|center_horizontal"
         android:background="@color/recents_task_bar_default_background_color">
-        <ImageView
+        <com.android.systemui.recents.views.FixedSizeImageView
             android:id="@+id/application_icon"
             android:layout_width="@dimen/recents_task_view_application_icon_size"
             android:layout_height="@dimen/recents_task_view_application_icon_size"
@@ -51,7 +51,7 @@
             android:maxLines="2"
             android:ellipsize="marquee"
             android:fadingEdge="horizontal" />
-        <ImageView
+        <com.android.systemui.recents.views.FixedSizeImageView
             android:id="@+id/dismiss_task"
             android:layout_width="48dp"
             android:layout_height="48dp"
diff --git a/packages/SystemUI/res/layout/user_switcher_host.xml b/packages/SystemUI/res/layout/user_switcher_host.xml
index 816af57..c1626c6 100644
--- a/packages/SystemUI/res/layout/user_switcher_host.xml
+++ b/packages/SystemUI/res/layout/user_switcher_host.xml
@@ -21,17 +21,11 @@
         xmlns:android="http://schemas.android.com/apk/res/android"
         xmlns:tools="http://schemas.android.com/tools"
         android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:background="#dd000000"
-        android:elevation="12dp">
-    <FrameLayout
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_marginTop="@dimen/volume_panel_top"
-            android:background="@*android:drawable/dialog_full_holo_dark">
+        android:layout_height="match_parent">
+
         <ListView android:id="@android:id/list"
                 android:layout_width="match_parent"
                 android:layout_height="match_parent"
                 tools:listitem="@layout/user_switcher_item"/>
-    </FrameLayout>
+
 </com.android.systemui.settings.UserSwitcherHostView>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 3d53f9c..adab243 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -45,7 +45,7 @@
     <color name="data_usage_secondary">#99FFFFFF</color><!-- 60% white -->
     <color name="data_usage_graph_track">#33FFFFFF</color><!-- 20% white -->
     <color name="data_usage_graph_warning">#FFFFFFFF</color>
-    <color name="status_bar_clock_color">#33FFFFFF</color>
+    <color name="status_bar_clock_color">#FFFFFFFF</color>
 
     <!-- Tint color for the content on the notification overflow card. -->
     <color name="keyguard_overflow_content_color">#ff686868</color>
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index 72474b8..5f09cbd 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -34,6 +34,7 @@
 import com.android.systemui.qs.QSTile.DetailAdapter;
 import com.android.systemui.settings.BrightnessController;
 import com.android.systemui.settings.ToggleSlider;
+import com.android.systemui.statusbar.phone.QSTileHost;
 
 import java.util.ArrayList;
 
@@ -61,9 +62,10 @@
     private boolean mExpanded;
     private boolean mListening;
 
-    private TileRecord mDetailRecord;
+    private Record mDetailRecord;
     private Callback mCallback;
     private BrightnessController mBrightnessController;
+    private QSTileHost mHost;
 
     public QSPanel(Context context) {
         this(context, null);
@@ -89,12 +91,24 @@
         mBrightnessController = new BrightnessController(getContext(),
                 (ImageView) findViewById(R.id.brightness_icon),
                 (ToggleSlider) findViewById(R.id.brightness_slider));
+
+        mDetailDoneButton.setOnClickListener(new OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                showDetail(false, mDetailRecord);
+            }
+        });
     }
 
     public void setCallback(Callback callback) {
         mCallback = callback;
     }
 
+    public void setHost(QSTileHost host) {
+        mHost = host;
+    }
+
+
     public void updateResources() {
         final Resources res = mContext.getResources();
         final int columns = Math.max(1, res.getInteger(R.integer.quick_settings_num_columns));
@@ -143,7 +157,13 @@
         }
     }
 
-    private void showDetail(boolean show, TileRecord r) {
+    public void showDetailAdapter(boolean show, DetailAdapter adapter) {
+        Record r = new Record();
+        r.detailAdapter = adapter;
+        showDetail(show, r);
+    }
+
+    private void showDetail(boolean show, Record r) {
         mHandler.obtainMessage(H.SHOW_DETAIL, show ? 1 : 0, 0, r).sendToTarget();
     }
 
@@ -203,40 +223,52 @@
         addView(r.tileView);
     }
 
-    private void handleShowDetail(TileRecord r, boolean show) {
-        if (r == null) return;
-        AnimatorListener listener = null;
+    private void handleShowDetail(Record r, boolean show) {
+        if (r instanceof TileRecord) {
+            handleShowDetailTile((TileRecord) r, show);
+        } else {
+            handleShowDetailImpl(r, show, getWidth() /* x */, 0/* y */);
+        }
+    }
+
+    private void handleShowDetailTile(TileRecord r, boolean show) {
+        if ((mDetailRecord != null) == show) return;
+
         if (show) {
-            if (mDetailRecord != null) return;  // already showing something in detail
             r.detailAdapter = r.tile.getDetailAdapter();
             if (r.detailAdapter == null) return;
-            mDetailRecord = r;
-            r.detailView = r.detailAdapter.createDetailView(mContext, r.detailView, mDetailContent);
+        }
+        int x = r.tileView.getLeft() + r.tileView.getWidth() / 2;
+        int y = r.tileView.getTop() + r.tileView.getHeight() / 2;
+        handleShowDetailImpl(r, show, x, y);
+    }
+
+    private void handleShowDetailImpl(Record r, boolean show, int x, int y) {
+        if ((mDetailRecord != null) == show) return;  // already in right state
+        DetailAdapter detailAdapter = null;
+        AnimatorListener listener = null;
+        if (show) {
+            detailAdapter = r.detailAdapter;
+            r.detailView = detailAdapter.createDetailView(mContext, r.detailView, mDetailContent);
             if (r.detailView == null) throw new IllegalStateException("Must return detail view");
-            mDetailDoneButton.setOnClickListener(new OnClickListener() {
-                @Override
-                public void onClick(View v) {
-                    showDetail(false, mDetailRecord);
-                }
-            });
-            final Intent settingsIntent = r.detailAdapter.getSettingsIntent();
+
+            final Intent settingsIntent = detailAdapter.getSettingsIntent();
             mDetailSettingsButton.setVisibility(settingsIntent != null ? VISIBLE : GONE);
             mDetailSettingsButton.setOnClickListener(new OnClickListener() {
                 @Override
                 public void onClick(View v) {
-                    mDetailRecord.tile.mHost.startSettingsActivity(settingsIntent);
+                    mHost.startSettingsActivity(settingsIntent);
                 }
             });
+
             mDetailContent.removeAllViews();
             mDetail.bringToFront();
             mDetailContent.addView(r.detailView);
+            mDetailRecord = r;
         } else {
-            if (mDetailRecord == null) return;
             listener = mTeardownDetailWhenDone;
         }
-        fireShowingDetail(show ? r.detailAdapter : null);
-        int x = r.tileView.getLeft() + r.tileView.getWidth() / 2;
-        int y = r.tileView.getTop() + r.tileView.getHeight() / 2;
+        fireShowingDetail(show ? detailAdapter : null);
         mClipper.animateCircularClip(x, y, show, listener);
     }
 
@@ -339,18 +371,21 @@
         @Override
         public void handleMessage(Message msg) {
             if (msg.what == SHOW_DETAIL) {
-                handleShowDetail((TileRecord)msg.obj, msg.arg1 != 0);
+                handleShowDetail((Record)msg.obj, msg.arg1 != 0);
             } else if (msg.what == SET_TILE_VISIBILITY) {
                 handleSetTileVisibility((View)msg.obj, msg.arg1 != 0);
             }
         }
     }
 
-    private static final class TileRecord {
-        QSTile<?> tile;
-        QSTileView tileView;
+    private static class Record {
         View detailView;
         DetailAdapter detailAdapter;
+    }
+
+    private static final class TileRecord extends Record {
+        QSTile<?> tile;
+        QSTileView tileView;
         int row;
         int col;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetail.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetail.java
new file mode 100644
index 0000000..a9a2724
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetail.java
@@ -0,0 +1,78 @@
+/*
+ * 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.systemui.qs.tiles;
+
+import com.android.systemui.R;
+import com.android.systemui.qs.QSTile;
+
+import android.content.Context;
+import android.content.Intent;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+
+/**
+ * Quick settings detail view for user switching.
+ */
+public class UserDetail extends FrameLayout {
+
+    static final Intent USER_SETTINGS_INTENT = new Intent("android.settings.USER_SETTINGS");
+
+    public UserDetail(Context context) {
+        this(context, null);
+    }
+
+    public UserDetail(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public UserDetail(Context context, AttributeSet attrs, int defStyleAttr) {
+        this(context, attrs, defStyleAttr, 0);
+    }
+
+    public UserDetail(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+    }
+
+    public static QSTile.DetailAdapter USER_DETAIL_ADAPTER = new QSTile.DetailAdapter() {
+        @Override
+        public int getTitle() {
+            return R.string.quick_settings_user_title;
+        }
+
+        @Override
+        public Boolean getToggleState() {
+            return null;
+        }
+
+        @Override
+        public View createDetailView(Context context, View convertView, ViewGroup parent) {
+            return LayoutInflater.from(context).inflate(R.layout.qs_user_detail, parent, false);
+        }
+
+        @Override
+        public Intent getSettingsIntent() {
+            return USER_SETTINGS_INTENT;
+        }
+
+        @Override
+        public void setToggleState(boolean state) {
+        }
+    };
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsPackageMonitor.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsPackageMonitor.java
index 2f1c1c4..31011ae 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsPackageMonitor.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsPackageMonitor.java
@@ -25,7 +25,6 @@
 
 import java.util.HashSet;
 import java.util.List;
-import java.util.Set;
 
 /**
  * The package monitor listens for changes from PackageManager to update the contents of the Recents
@@ -33,7 +32,7 @@
  */
 public class RecentsPackageMonitor extends PackageMonitor {
     public interface PackageCallbacks {
-        public void onComponentRemoved(Set<ComponentName> cns);
+        public void onComponentRemoved(HashSet<ComponentName> cns);
     }
 
     PackageCallbacks mCb;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
index e3bcff0..13fbe64 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
@@ -204,8 +204,8 @@
                 removeGroup(group);
             }
             // Update the lock-to-app state
-            Task newFrontMostTask = getFrontMostTask();
             t.canLockToTask = false;
+            Task newFrontMostTask = getFrontMostTask();
             if (newFrontMostTask != null) {
                 newFrontMostTask.canLockToTask = true;
             }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/FixedSizeImageView.java b/packages/SystemUI/src/com/android/systemui/recents/views/FixedSizeImageView.java
new file mode 100644
index 0000000..3adee0ea
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/FixedSizeImageView.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 com.android.systemui.recents.views;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.widget.ImageView;
+
+/**
+ * This is an optimized ImageView that does not trigger a requestLayout() or invalidate() when
+ * setting the image to Null.
+ */
+public class FixedSizeImageView extends ImageView {
+
+    int mFixedWidth;
+    int mFixedHeight;
+    boolean mAllowRelayout = true;
+    boolean mAllowInvalidate = true;
+
+    public FixedSizeImageView(Context context) {
+        this(context, null);
+    }
+
+    public FixedSizeImageView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public FixedSizeImageView(Context context, AttributeSet attrs, int defStyleAttr) {
+        this(context, attrs, defStyleAttr, 0);
+    }
+
+    public FixedSizeImageView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+        mFixedWidth = getMeasuredWidth();
+        mFixedHeight = getMeasuredHeight();
+    }
+
+    @Override
+    public void requestLayout() {
+        if (mAllowRelayout) {
+            super.requestLayout();
+        }
+    }
+
+    @Override
+    public void invalidate() {
+        if (mAllowInvalidate) {
+            super.invalidate();
+        }
+    }
+
+    @Override
+    public void setImageDrawable(Drawable drawable) {
+        if (drawable == null || (mFixedWidth > 0 && mFixedHeight > 0)) {
+            mAllowRelayout = false;
+            mAllowInvalidate = false;
+        }
+        super.setImageDrawable(drawable);
+        mAllowRelayout = true;
+        mAllowInvalidate = true;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
index 7bb6144..b32d3dd 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
@@ -45,7 +45,7 @@
 import com.android.systemui.recents.model.TaskStack;
 
 import java.util.ArrayList;
-import java.util.Set;
+import java.util.HashSet;
 
 /**
  * This view is the the top level layout that contains TaskStacks (which are laid out according
@@ -551,7 +551,7 @@
     /**** RecentsPackageMonitor.PackageCallbacks Implementation ****/
 
     @Override
-    public void onComponentRemoved(Set<ComponentName> cns) {
+    public void onComponentRemoved(HashSet<ComponentName> cns) {
         // Propagate this event down to each task stack view
         int childCount = getChildCount();
         for (int i = 0; i < childCount; i++) {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
index 7b52163..0b35f59 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
@@ -23,9 +23,8 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.graphics.Canvas;
+import android.graphics.Paint;
 import android.graphics.Rect;
-import android.graphics.Region;
-import android.os.SystemClock;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.View;
@@ -45,7 +44,7 @@
 
 import java.util.ArrayList;
 import java.util.HashMap;
-import java.util.Set;
+import java.util.HashSet;
 
 
 /* The visual representation of a task stack view */
@@ -83,6 +82,7 @@
     int mFocusedTaskIndex = -1;
     OverScroller mScroller;
     ObjectAnimator mScrollAnimator;
+    boolean mEnableStackClipping = true;
 
     // Optimizations
     ReferenceCountedTrigger mHwLayersTrigger;
@@ -169,6 +169,7 @@
     void requestSynchronizeStackViewsWithModel(int duration) {
         if (!mStackViewsDirty) {
             invalidate(mStackAlgorithm.mStackRect);
+            mStackViewsDirty = true;
         }
         if (mAwaitingFirstLayout) {
             // Skip the animation if we are awaiting first layout
@@ -176,7 +177,6 @@
         } else {
             mStackViewsAnimationDuration = Math.max(mStackViewsAnimationDuration, duration);
         }
-        mStackViewsDirty = true;
     }
 
     /** Returns a mapping of child view to Task. */
@@ -338,7 +338,7 @@
     /** Updates the clip for each of the task views. */
     void clipTaskViews() {
         // Update the clip on each task child
-        if (Constants.DebugFlags.App.EnableTaskStackClipping) {
+        if (Constants.DebugFlags.App.EnableTaskStackClipping && mEnableStackClipping) {
             int childCount = getChildCount();
             for (int i = 0; i < childCount - 1; i++) {
                 TaskView tv = (TaskView) getChildAt(i);
@@ -360,10 +360,12 @@
                     // stacked and we can make assumptions about the visibility of the this
                     // task relative to the ones in front of it.
                     if (nextTv != null) {
-                        // XXX: Can hash the visible rects for this run
+                        // We calculate the bottom clip independent of the footer (since we animate
+                        // that)
+                        int scaledMaxFooterHeight = (int) (tv.getMaxFooterHeight() * tv.getScaleX());
                         tv.getHitRect(mTmpRect);
                         nextTv.getHitRect(mTmpRect2);
-                        clipBottom = (mTmpRect.bottom - mTmpRect2.top);
+                        clipBottom = (mTmpRect.bottom - scaledMaxFooterHeight - mTmpRect2.top);
                     }
                 }
                 tv.setClipFromBottom(clipBottom);
@@ -376,6 +378,18 @@
         }
     }
 
+    /** Enables/Disables clipping of the tasks in the stack. */
+    void setStackClippingEnabled(boolean stackClippingEnabled) {
+        if (!stackClippingEnabled) {
+            int childCount = getChildCount();
+            for (int i = 0; i < childCount; i++) {
+                TaskView tv = (TaskView) getChildAt(i);
+                tv.setClipFromBottom(0);
+            }
+        }
+        mEnableStackClipping = stackClippingEnabled;
+    }
+
     /** Sets the current stack scroll */
     public void setStackScroll(int value) {
         mStackScroll = value;
@@ -614,7 +628,6 @@
     public void computeScroll() {
         if (mScroller.computeScrollOffset()) {
             setStackScroll(mScroller.getCurrY());
-            invalidate(mStackAlgorithm.mStackRect);
 
             // If we just finished scrolling, then disable the hw layers
             if (mScroller.isFinished()) {
@@ -668,7 +681,7 @@
             TaskView t = (TaskView) getChildAt(i);
             t.measure(MeasureSpec.makeMeasureSpec(mStackAlgorithm.mTaskRect.width(), MeasureSpec.EXACTLY),
                     MeasureSpec.makeMeasureSpec(mStackAlgorithm.mTaskRect.height() +
-                            mConfig.taskViewLockToAppButtonHeight, MeasureSpec.EXACTLY));
+                            t.getMaxFooterHeight(), MeasureSpec.EXACTLY));
         }
 
         setMeasuredDimension(width, height);
@@ -687,7 +700,7 @@
             TaskView t = (TaskView) getChildAt(i);
             t.layout(mStackAlgorithm.mTaskRect.left, mStackAlgorithm.mStackRectSansPeek.top,
                     mStackAlgorithm.mTaskRect.right, mStackAlgorithm.mStackRectSansPeek.top +
-                    mStackAlgorithm.mTaskRect.height() + mConfig.taskViewLockToAppButtonHeight);
+                    mStackAlgorithm.mTaskRect.height() + t.getMaxFooterHeight());
         }
 
         if (mAwaitingFirstLayout) {
@@ -1056,7 +1069,7 @@
     /**** RecentsPackageMonitor.PackageCallbacks Implementation ****/
 
     @Override
-    public void onComponentRemoved(Set<ComponentName> cns) {
+    public void onComponentRemoved(HashSet<ComponentName> cns) {
         // For other tasks, just remove them directly if they no longer exist
         ArrayList<Task> tasks = mStack.getTasks();
         for (int i = tasks.size() - 1; i >= 0; i--) {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewLayoutAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewLayoutAlgorithm.java
index 9c48896..65407a6 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewLayoutAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewLayoutAlgorithm.java
@@ -146,7 +146,8 @@
         transformOut.translationZ = (int) Math.max(minZ, minZ + ((boundedT + numPeekCards) * incZ));
 
         // Set the alphas
-        transformOut.dismissAlpha = Math.max(-1f, Math.min(0f, t + 1)) + 1f;
+        // transformOut.dismissAlpha = Math.max(-1f, Math.min(0f, t + 1)) + 1f;
+        transformOut.dismissAlpha = 1f;
 
         // Update the rect and visibility
         transformOut.rect.set(mTaskRect);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskThumbnailView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskThumbnailView.java
index 636746d..08a25f1 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskThumbnailView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskThumbnailView.java
@@ -21,12 +21,11 @@
 import android.graphics.Rect;
 import android.util.AttributeSet;
 import android.view.View;
-import android.widget.ImageView;
 import com.android.systemui.recents.model.Task;
 
 
 /** The task thumbnail view */
-public class TaskThumbnailView extends ImageView {
+public class TaskThumbnailView extends FixedSizeImageView {
 
     Task mTask;
 
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
index 7e30047..199d3f3 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
@@ -495,7 +495,7 @@
         mLockToAppButtonView.setLayerType(View.LAYER_TYPE_NONE, mLayerPaint);
     }
 
-    /** Sets the stubbed state of this task view.
+    /** Sets the stubbed state of this task view. */
     void setStubState(boolean isStub) {
         if (!mIsStub && isStub) {
             // This is now a stub task view, so clip to the bar height, hide the thumbnail
@@ -508,7 +508,7 @@
             mThumbnailView.setVisibility(View.VISIBLE);
         }
         mIsStub = isStub;
-    } */
+    }
 
     /**
      * Returns whether this view should be clipped, or any views below should clip against this
@@ -549,9 +549,19 @@
         return mFooterHeight;
     }
 
+    /** Gets the max footer height. */
+    public int getMaxFooterHeight() {
+        return mMaxFooterHeight;
+    }
+
     /** Animates the footer into and out of view. */
     public void animateFooterVisibility(boolean visible, int duration, int delay) {
-        if (!mTask.canLockToTask) return;
+        if (!mTask.canLockToTask) {
+            if (mLockToAppButtonView.getVisibility() == View.VISIBLE) {
+                mLockToAppButtonView.setVisibility(View.INVISIBLE);
+            }
+            return;
+        }
         if (mMaxFooterHeight <= 0) return;
 
         if (mFooterAnimator != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserSwitcherHostView.java b/packages/SystemUI/src/com/android/systemui/settings/UserSwitcherHostView.java
index a3b10f2..a5c5862 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/UserSwitcherHostView.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/UserSwitcherHostView.java
@@ -90,6 +90,7 @@
         mListView = (ListView) findViewById(android.R.id.list);
         mListView.setAdapter(mAdapter);
         mListView.setOnItemClickListener(this);
+        refreshUsers();
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitch.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitch.java
index 0f12274..d32ad50 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitch.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitch.java
@@ -18,29 +18,22 @@
 
 import android.content.Context;
 import android.content.Intent;
-import android.graphics.Canvas;
-import android.graphics.Path;
-import android.graphics.drawable.Drawable;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.ContactsContract;
 import android.util.AttributeSet;
-import android.view.LayoutInflater;
 import android.view.View;
-import android.view.ViewGroup;
 import android.widget.FrameLayout;
-import android.widget.ImageButton;
 
-import com.android.systemui.R;
-import com.android.systemui.settings.UserSwitcherHostView;
-import com.android.systemui.statusbar.policy.UserInfoController;
+import com.android.systemui.qs.QSPanel;
+import com.android.systemui.qs.tiles.UserDetail;
 
 /**
  * Container for image of the multi user switcher (tappable).
  */
 public class MultiUserSwitch extends FrameLayout implements View.OnClickListener {
 
-    private ViewGroup mOverlayParent;
+    private QSPanel mQsPanel;
 
     public MultiUserSwitch(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -52,25 +45,15 @@
         setOnClickListener(this);
     }
 
-    public void setOverlayParent(ViewGroup parent) {
-        mOverlayParent = parent;
+    public void setQsPanel(QSPanel qsPanel) {
+        mQsPanel = qsPanel;
     }
 
     @Override
     public void onClick(View v) {
         final UserManager um = UserManager.get(getContext());
         if (um.isUserSwitcherEnabled()) {
-            final UserSwitcherHostView switcher =
-                    (UserSwitcherHostView) LayoutInflater.from(getContext()).inflate(
-                            R.layout.user_switcher_host, mOverlayParent, false);
-            switcher.setFinishRunnable(new Runnable() {
-                @Override
-                public void run() {
-                    mOverlayParent.removeView(switcher);
-                }
-            });
-            switcher.refreshUsers();
-            mOverlayParent.addView(switcher);
+            mQsPanel.showDetailAdapter(true, UserDetail.USER_DETAIL_ADAPTER);
         } else {
             Intent intent = ContactsContract.QuickContact.composeQuickContactsIntent(
                     getContext(), v, ContactsContract.Profile.CONTENT_URI,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
index 55b3088..fc0f2d5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -150,7 +150,6 @@
         super.onFinishInflate();
         mHeader = (StatusBarHeaderView) findViewById(R.id.header);
         mHeader.setOnClickListener(this);
-        mHeader.setOverlayParent(this);
         mKeyguardStatusView = findViewById(R.id.keyguard_status_view);
         mQsContainer = findViewById(R.id.quick_settings_container);
         mQsPanel = (QSPanel) findViewById(R.id.quick_settings_panel);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
index 505af44..2c43161 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -699,6 +699,7 @@
                     mBluetoothController, mLocationController, mRotationLockController,
                     mNetworkController, mZenModeController, null /*tethering*/,
                     mCastController, mVolumeComponent, mFlashlightController);
+            mQSPanel.setHost(qsh);
             for (QSTile<?> tile : qsh.getTiles()) {
                 mQSPanel.addTile(tile);
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java
index 33d1b15..dc06ec2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java
@@ -475,10 +475,6 @@
         });
     }
 
-    public void setOverlayParent(ViewGroup parent) {
-        mMultiUserSwitch.setOverlayParent(parent);
-    }
-
     @Override
     public void onClick(View v) {
         if (v == mSettingsButton) {
@@ -501,6 +497,7 @@
         if (mQSPanel != null) {
             mQSPanel.setCallback(mQsPanelCallback);
         }
+        mMultiUserSwitch.setQsPanel(qsp);
     }
 
     @Override
@@ -589,7 +586,7 @@
                     mQsDetailHeader.setOnClickListener(new OnClickListener() {
                         @Override
                         public void onClick(View v) {
-                            detail.setToggleState(!toggleState);
+                            detail.setToggleState(!mQsDetailHeaderSwitch.isChecked());
                         }
                     });
                 }
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index 260a5b5..7e5cac7 100755
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -1081,6 +1081,7 @@
         if (next == mLastScreenshotActivity) {
             invalidateLastScreenshot();
         }
+        mReturningActivityOptions = null;
     }
 
     private void setVisibile(ActivityRecord r, boolean visible) {
@@ -1217,13 +1218,10 @@
                         if (DEBUG_VISBILITY) Slog.v(TAG, "Skipping: already visible at " + r);
                         r.stopFreezingScreenLocked(false);
                         try {
-                            if (mReturningActivityOptions != null) {
-                                if (activityNdx > 0) {
-                                    ActivityRecord under = activities.get(activityNdx - 1);
-                                    under.app.thread.scheduleOnNewActivityOptions(under.appToken,
-                                            mReturningActivityOptions);
-                                }
-                                mReturningActivityOptions = null;
+                            if (mReturningActivityOptions != null && r == top && activityNdx > 0) {
+                                ActivityRecord under = activities.get(activityNdx - 1);
+                                under.app.thread.scheduleOnNewActivityOptions(under.appToken,
+                                        mReturningActivityOptions);
                             }
                         } catch(RemoteException e) {
                         }
@@ -1842,8 +1840,11 @@
             ActivityStack lastStack = mStackSupervisor.getLastStack();
             final boolean fromHome = lastStack.isHomeStack();
             if (!isHomeStack() && (fromHome || topTask() != task)) {
-                task.setTaskToReturnTo(fromHome ?
-                        lastStack.topTask().taskType : APPLICATION_ACTIVITY_TYPE);
+                task.setTaskToReturnTo(fromHome
+                        ? lastStack.topTask() == null
+                                ? HOME_ACTIVITY_TYPE
+                                : lastStack.topTask().taskType
+                        : APPLICATION_ACTIVITY_TYPE);
             }
         } else {
             task.setTaskToReturnTo(APPLICATION_ACTIVITY_TYPE);
diff --git a/services/core/java/com/android/server/connectivity/NetworkMonitor.java b/services/core/java/com/android/server/connectivity/NetworkMonitor.java
index 6fb8570..8289f5a 100644
--- a/services/core/java/com/android/server/connectivity/NetworkMonitor.java
+++ b/services/core/java/com/android/server/connectivity/NetworkMonitor.java
@@ -648,6 +648,7 @@
                         new InputStreamReader(socket.getInputStream()));
                 OutputStreamWriter writer = new OutputStreamWriter(socket.getOutputStream());
                 writer.write("GET " + url.getFile() + " HTTP/1.1\r\nHost: " + url.getHost() +
+                        "\r\nUser-Agent: " + System.getProperty("http.agent") +
                         "\r\nConnection: close\r\n\r\n");
                 writer.flush();
                 String response = reader.readLine();
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 0eb922d..190e87c 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -60,6 +60,7 @@
 
     // TODO: destroy sessions with old timestamps
     // TODO: remove outstanding sessions when installer package goes away
+    // TODO: notify listeners in other users when package has been installed there
 
     private final Context mContext;
     private final PackageManagerService mPm;
@@ -167,6 +168,10 @@
             params.installFlags |= INSTALL_REPLACE_EXISTING;
         }
 
+        if (params.mode == InstallSessionParams.MODE_INVALID) {
+            throw new IllegalArgumentException("Params must have valid mode set");
+        }
+
         // Sanity check that install could fit
         if (params.deltaSize > 0) {
             try {
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 41ab66a..31d9704 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -166,7 +166,7 @@
         info.installerPackageName = installerPackageName;
         info.progress = mProgress;
 
-        info.fullInstall = params.fullInstall;
+        info.mode = params.mode;
         info.packageName = params.packageName;
         info.icon = params.icon;
         info.title = params.title;
@@ -289,7 +289,7 @@
 
         // Inherit any packages and native libraries from existing install that
         // haven't been overridden.
-        if (!params.fullInstall) {
+        if (params.mode == InstallSessionParams.MODE_INHERIT_EXISTING) {
             spliceExistingFilesIntoStage();
         }
 
@@ -383,7 +383,7 @@
         // currently relying on PMS to do this.
         // TODO: teach about compatible upgrade keysets.
 
-        if (params.fullInstall) {
+        if (params.mode == InstallSessionParams.MODE_FULL_INSTALL) {
             // Full installs must include a base package
             if (!seenSplits.contains(null)) {
                 throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index cfba19c..74a1945 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -1713,7 +1713,7 @@
                 // NOTE: We ignore potential failures here during a system scan (like
                 // the rest of the commands above) because there's precious little we
                 // can do about it. A settings error is reported, though.
-                adjustCpuAbisForSharedUserLPw(setting.packages, null,
+                adjustCpuAbisForSharedUserLPw(setting.packages, null /* scanned package */,
                         false /* force dexopt */, false /* defer dexopt */);
             }
 
@@ -5428,11 +5428,8 @@
                         }
                     }
 
-                    if (abi32 < 0 && abi32 != PackageManager.NO_NATIVE_LIBRARIES) {
-                        throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
-                                "Error unpackaging 32 bit native libs for multiarch app, errorCode="
-                                + abi32);
-                    }
+                    maybeThrowExceptionForMultiArchCopy(
+                            "Error unpackaging 32 bit native libs for multiarch app.", abi32);
 
                     if (Build.SUPPORTED_64_BIT_ABIS.length > 0) {
                         if (isAsec) {
@@ -5443,11 +5440,8 @@
                         }
                     }
 
-                    if (abi64 < 0 && abi64 != PackageManager.NO_NATIVE_LIBRARIES) {
-                        throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
-                                "Error unpackaging 64 bit native libs for multiarch app, errorCode="
-                                + abi32);
-                    }
+                    maybeThrowExceptionForMultiArchCopy(
+                            "Error unpackaging 64 bit native libs for multiarch app.", abi64);
 
                     if (abi64 >= 0) {
                         pkg.applicationInfo.primaryCpuAbi = Build.SUPPORTED_64_BIT_ABIS[abi64];
@@ -5544,11 +5538,8 @@
             // We also do this *before* we perform dexopt on this package, so that
             // we can avoid redundant dexopts, and also to make sure we've got the
             // code and package path correct.
-            if (!adjustCpuAbisForSharedUserLPw(pkgSetting.sharedUser.packages,
-                    pkg, forceDex, (scanMode & SCAN_DEFER_DEX) != 0)) {
-                throw new PackageManagerException(INSTALL_FAILED_CPU_ABI_INCOMPATIBLE,
-                        "scanPackageLI");
-            }
+            adjustCpuAbisForSharedUserLPw(pkgSetting.sharedUser.packages,
+                    pkg, forceDex, (scanMode & SCAN_DEFER_DEX) != 0);
         }
 
         if ((scanMode&SCAN_NO_DEX) == 0) {
@@ -6047,7 +6038,7 @@
      * NOTE: We currently only match for the primary CPU abi string. Matching the secondary
      * adds unnecessary complexity.
      */
-    private boolean adjustCpuAbisForSharedUserLPw(Set<PackageSetting> packagesForUser,
+    private void adjustCpuAbisForSharedUserLPw(Set<PackageSetting> packagesForUser,
             PackageParser.Package scannedPackage, boolean forceDexOpt, boolean deferDexOpt) {
         String requiredInstructionSet = null;
         if (scannedPackage != null && scannedPackage.applicationInfo.primaryCpuAbi != null) {
@@ -6061,27 +6052,23 @@
             // when scannedPackage is an update of an existing package. Without this check,
             // we will never be able to change the ABI of any package belonging to a shared
             // user, even if it's compatible with other packages.
-            if (scannedPackage == null || ! scannedPackage.packageName.equals(ps.name)) {
+            if (scannedPackage == null || !scannedPackage.packageName.equals(ps.name)) {
                 if (ps.primaryCpuAbiString == null) {
                     continue;
                 }
 
                 final String instructionSet = VMRuntime.getInstructionSet(ps.primaryCpuAbiString);
-                if (requiredInstructionSet != null) {
-                    if (!instructionSet.equals(requiredInstructionSet)) {
-                        // We have a mismatch between instruction sets (say arm vs arm64).
-                        // bail out.
-                        String errorMessage = "Instruction set mismatch, "
-                                + ((requirer == null) ? "[caller]" : requirer)
-                                + " requires " + requiredInstructionSet + " whereas " + ps
-                                + " requires " + instructionSet;
-                        Slog.e(TAG, errorMessage);
+                if (requiredInstructionSet != null && !instructionSet.equals(requiredInstructionSet)) {
+                    // We have a mismatch between instruction sets (say arm vs arm64) warn about
+                    // this but there's not much we can do.
+                    String errorMessage = "Instruction set mismatch, "
+                            + ((requirer == null) ? "[caller]" : requirer)
+                            + " requires " + requiredInstructionSet + " whereas " + ps
+                            + " requires " + instructionSet;
+                    Slog.w(TAG, errorMessage);
+                }
 
-                        reportSettingsProblem(Log.WARN, errorMessage);
-                        // Give up, don't bother making any other changes to the package settings.
-                        return false;
-                    }
-                } else {
+                if (requiredInstructionSet == null) {
                     requiredInstructionSet = instructionSet;
                     requirer = ps;
                 }
@@ -6118,7 +6105,7 @@
                         if (performDexOptLI(ps.pkg, forceDexOpt, deferDexOpt, true) == DEX_OPT_FAILED) {
                             ps.primaryCpuAbiString = null;
                             ps.pkg.applicationInfo.primaryCpuAbi = null;
-                            return false;
+                            return;
                         } else {
                             mInstaller.rmdex(ps.codePathString, getPreferredInstructionSet());
                         }
@@ -6126,8 +6113,6 @@
                 }
             }
         }
-
-        return true;
     }
 
     private void setUpCustomResolverActivity(PackageParser.Package pkg) {
@@ -6240,9 +6225,6 @@
             if (info.primaryCpuAbi != null) {
                 info.nativeLibraryDir = new File(info.nativeLibraryRootDir,
                         VMRuntime.getInstructionSet(info.primaryCpuAbi)).getAbsolutePath();
-            } else {
-                Slog.w(TAG, "Package " + info.packageName
-                        + " missing ABI; unable to derive nativeLibraryDir");
             }
         } else {
             info.nativeLibraryDir = info.nativeLibraryRootDir;
@@ -9264,29 +9246,13 @@
                     if (Build.SUPPORTED_32_BIT_ABIS.length > 0) {
                         copyRet = copyNativeLibrariesForInternalApp(handle, libraryRoot,
                                 Build.SUPPORTED_32_BIT_ABIS, true /* use isa specific subdirs */);
-                        if (copyRet < 0 && copyRet != PackageManager.NO_NATIVE_LIBRARIES) {
-                            Slog.w(TAG, "Failure copying 32 bit native libraries [errorCode=" + copyRet + "]");
-                            return copyRet;
-                        }
-                    }
-
-                    if (DEBUG_ABI_SELECTION && copyRet >= 0) {
-                        Log.d(TAG, "Installed 32 bit libraries under: " + codeFile + " abi=" +
-                                Build.SUPPORTED_32_BIT_ABIS[copyRet]);
+                        maybeThrowExceptionForMultiArchCopy("Failure copying 32 bit native libraries", copyRet);
                     }
 
                     if (Build.SUPPORTED_64_BIT_ABIS.length > 0) {
                         copyRet = copyNativeLibrariesForInternalApp(handle, libraryRoot,
                                 Build.SUPPORTED_64_BIT_ABIS, true /* use isa specific subdirs */);
-                        if (copyRet < 0 && copyRet != PackageManager.NO_NATIVE_LIBRARIES) {
-                            Slog.w(TAG, "Failure copying 64 bit native libraries [errorCode=" + copyRet + "]");
-                            return copyRet;
-                        }
-                    }
-
-                    if (DEBUG_ABI_SELECTION && copyRet >= 0) {
-                        Log.d(TAG, "Installed 64 bit libraries under: " + codeFile + " abi=" +
-                                Build.SUPPORTED_64_BIT_ABIS[copyRet]);
+                        maybeThrowExceptionForMultiArchCopy("Failure copying 64 bit native libraries", copyRet);
                     }
                 } else {
                     String[] abiList = (abiOverride != null) ?
@@ -9303,14 +9269,13 @@
                         Slog.w(TAG, "Failure copying native libraries [errorCode=" + copyRet + "]");
                         return copyRet;
                     }
-
-                    if (DEBUG_ABI_SELECTION && copyRet >= 0) {
-                        Log.d(TAG, "Installed libraries under: " + codeFile + " abi=" + abiList[copyRet]);
-                    }
                 }
             } catch (IOException e) {
                 Slog.e(TAG, "Copying native libraries failed", e);
                 ret = PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
+            } catch (PackageManagerException pme) {
+                Slog.e(TAG, "Copying native libraries failed", pme);
+                ret = pme.error;
             } finally {
                 IoUtils.closeQuietly(handle);
             }
@@ -9459,6 +9424,16 @@
         return !asecPath.startsWith(mAsecInternalPath);
     }
 
+    private static void maybeThrowExceptionForMultiArchCopy(String message, int copyRet) throws
+            PackageManagerException {
+        if (copyRet < 0) {
+            if (copyRet != PackageManager.NO_NATIVE_LIBRARIES &&
+                    copyRet != PackageManager.INSTALL_FAILED_NO_MATCHING_ABIS) {
+                throw new PackageManagerException(copyRet, message);
+            }
+        }
+    }
+
     /**
      * Extract the MountService "container ID" from the full code path of an
      * .apk.
diff --git a/services/core/java/com/android/server/tv/TvInputHardwareManager.java b/services/core/java/com/android/server/tv/TvInputHardwareManager.java
index efe543b..8f237db 100644
--- a/services/core/java/com/android/server/tv/TvInputHardwareManager.java
+++ b/services/core/java/com/android/server/tv/TvInputHardwareManager.java
@@ -16,7 +16,12 @@
 
 package com.android.server.tv;
 
+import static android.media.tv.TvInputManager.INPUT_STATE_CONNECTED;
+import static android.media.tv.TvInputManager.INPUT_STATE_DISCONNECTED;
+
 import android.content.Context;
+import android.hardware.hdmi.HdmiControlManager;
+import android.hardware.hdmi.HdmiHotplugEvent;
 import android.media.AudioDevicePort;
 import android.media.AudioManager;
 import android.media.AudioPatch;
@@ -25,14 +30,22 @@
 import android.media.tv.ITvInputHardware;
 import android.media.tv.ITvInputHardwareCallback;
 import android.media.tv.TvInputHardwareInfo;
+import android.media.tv.TvInputInfo;
 import android.media.tv.TvStreamConfig;
+import android.os.Handler;
+import android.os.HandlerThread;
 import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
 import android.os.RemoteException;
 import android.util.Slog;
 import android.util.SparseArray;
+import android.util.SparseBooleanArray;
 import android.view.KeyEvent;
 import android.view.Surface;
 
+import com.android.server.SystemService;
+
 import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.List;
@@ -46,23 +59,42 @@
  *
  * @hide
  */
-class TvInputHardwareManager implements TvInputHal.Callback {
+class TvInputHardwareManager
+        implements TvInputHal.Callback, HdmiControlManager.HotplugEventListener {
     private static final String TAG = TvInputHardwareManager.class.getSimpleName();
     private final TvInputHal mHal = new TvInputHal(this);
     private final SparseArray<Connection> mConnections = new SparseArray<Connection>();
     private final List<TvInputHardwareInfo> mInfoList = new ArrayList<TvInputHardwareInfo>();
     private final Context mContext;
+    private final TvInputManagerService.Client mClient;
     private final Set<Integer> mActiveHdmiSources = new HashSet<Integer>();
     private final AudioManager mAudioManager;
+    private final SparseBooleanArray mHdmiStateMap = new SparseBooleanArray();
+    // TODO: Should handle INACTIVE case.
+    private final SparseArray<TvInputInfo> mTvInputInfoMap = new SparseArray<TvInputInfo>();
+
+    // Calls to mClient should happen here.
+    private final HandlerThread mHandlerThread = new HandlerThread(TAG);
+    private final Handler mHandler;
 
     private final Object mLock = new Object();
 
-    public TvInputHardwareManager(Context context) {
+    public TvInputHardwareManager(Context context, TvInputManagerService.Client client) {
         mContext = context;
+        mClient = client;
         mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
-        // TODO(hdmi): mHdmiManager = mContext.getSystemService(...);
-        // TODO(hdmi): mHdmiClient = mHdmiManager.getTvClient();
         mHal.init();
+
+        mHandlerThread.start();
+        mHandler = new ClientHandler(mHandlerThread.getLooper());
+    }
+
+    public void onBootPhase(int phase) {
+        if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
+            HdmiControlManager hdmiControlManager =
+                    (HdmiControlManager) mContext.getSystemService(Context.HDMI_CONTROL_SERVICE);
+            hdmiControlManager.addHotplugEventListener(this);
+        }
     }
 
     @Override
@@ -80,7 +112,7 @@
     private void buildInfoListLocked() {
         mInfoList.clear();
         for (int i = 0; i < mConnections.size(); ++i) {
-            mInfoList.add(mConnections.valueAt(i).getInfoLocked());
+            mInfoList.add(mConnections.valueAt(i).getHardwareInfoLocked());
         }
     }
 
@@ -92,7 +124,7 @@
                 Slog.e(TAG, "onDeviceUnavailable: Cannot find a connection with " + deviceId);
                 return;
             }
-            connection.resetLocked(null, null, null, null);
+            connection.resetLocked(null, null, null, null, null);
             mConnections.remove(deviceId);
             buildInfoListLocked();
             // TODO: notify if necessary
@@ -136,6 +168,37 @@
         return false;
     }
 
+    private int convertConnectedToState(boolean connected) {
+        if (connected) {
+            return INPUT_STATE_CONNECTED;
+        } else {
+            return INPUT_STATE_DISCONNECTED;
+        }
+    }
+
+    public void registerTvInputInfo(TvInputInfo info, int deviceId) {
+        if (info.getType() == TvInputInfo.TYPE_VIRTUAL) {
+            throw new IllegalArgumentException("info (" + info + ") has virtual type.");
+        }
+        synchronized (mLock) {
+            if (mTvInputInfoMap.indexOfKey(deviceId) >= 0) {
+                Slog.w(TAG, "Trying to override previous registration: old = "
+                        + mTvInputInfoMap.get(deviceId) + ":" + deviceId + ", new = "
+                        + info + ":" + deviceId);
+            }
+            mTvInputInfoMap.put(deviceId, info);
+
+            for (int i = 0; i < mHdmiStateMap.size(); ++i) {
+                String inputId = findInputIdForHdmiPortLocked(mHdmiStateMap.keyAt(i));
+                if (inputId != null && inputId.equals(info.getId())) {
+                    mHandler.obtainMessage(ClientHandler.DO_SET_AVAILABLE,
+                            convertConnectedToState(mHdmiStateMap.valueAt(i)), 0,
+                            inputId).sendToTarget();
+                }
+            }
+        }
+    }
+
     /**
      * Create a TvInputHardware object with a specific deviceId. One service at a time can access
      * the object, and if more than one process attempts to create hardware with the same deviceId,
@@ -143,7 +206,7 @@
      * release is notified via ITvInputHardwareCallback.onReleased().
      */
     public ITvInputHardware acquireHardware(int deviceId, ITvInputHardwareCallback callback,
-            int callingUid, int resolvedUserId) {
+            TvInputInfo info, int callingUid, int resolvedUserId) {
         if (callback == null) {
             throw new NullPointerException();
         }
@@ -154,14 +217,15 @@
                 return null;
             }
             if (checkUidChangedLocked(connection, callingUid, resolvedUserId)) {
-                TvInputHardwareImpl hardware = new TvInputHardwareImpl(connection.getInfoLocked());
+                TvInputHardwareImpl hardware =
+                        new TvInputHardwareImpl(connection.getHardwareInfoLocked());
                 try {
                     callback.asBinder().linkToDeath(connection, 0);
                 } catch (RemoteException e) {
                     hardware.release();
                     return null;
                 }
-                connection.resetLocked(hardware, callback, callingUid, resolvedUserId);
+                connection.resetLocked(hardware, callback, info, callingUid, resolvedUserId);
             }
             return connection.getHardwareLocked();
         }
@@ -182,26 +246,55 @@
                     || checkUidChangedLocked(connection, callingUid, resolvedUserId)) {
                 return;
             }
-            connection.resetLocked(null, null, null, null);
+            connection.resetLocked(null, null, null, null, null);
+        }
+    }
+
+    private String findInputIdForHdmiPortLocked(int port) {
+        for (TvInputHardwareInfo hardwareInfo : mInfoList) {
+            if (hardwareInfo.getType() == TvInputHardwareInfo.TV_INPUT_TYPE_HDMI
+                    && hardwareInfo.getHdmiPortId() == port) {
+                TvInputInfo info = mTvInputInfoMap.get(hardwareInfo.getDeviceId());
+                return (info == null) ? null : info.getId();
+            }
+        }
+        return null;
+    }
+
+    // HdmiControlManager.HotplugEventListener implementation.
+
+    @Override
+    public void onReceived(HdmiHotplugEvent event) {
+        String inputId = null;
+
+        synchronized (mLock) {
+            mHdmiStateMap.put(event.getPort(), event.isConnected());
+            inputId = findInputIdForHdmiPortLocked(event.getPort());
+            if (inputId == null) {
+                return;
+            }
+            mHandler.obtainMessage(ClientHandler.DO_SET_AVAILABLE,
+                    convertConnectedToState(event.isConnected()), 0, inputId).sendToTarget();
         }
     }
 
     private class Connection implements IBinder.DeathRecipient {
-        private final TvInputHardwareInfo mInfo;
+        private final TvInputHardwareInfo mHardwareInfo;
+        private TvInputInfo mInfo;
         private TvInputHardwareImpl mHardware = null;
         private ITvInputHardwareCallback mCallback;
         private TvStreamConfig[] mConfigs = null;
         private Integer mCallingUid = null;
         private Integer mResolvedUserId = null;
 
-        public Connection(TvInputHardwareInfo info) {
-            mInfo = info;
+        public Connection(TvInputHardwareInfo hardwareInfo) {
+            mHardwareInfo = hardwareInfo;
         }
 
         // *Locked methods assume TvInputHardwareManager.mLock is held.
 
-        public void resetLocked(TvInputHardwareImpl hardware,
-                ITvInputHardwareCallback callback, Integer callingUid, Integer resolvedUserId) {
+        public void resetLocked(TvInputHardwareImpl hardware, ITvInputHardwareCallback callback,
+                TvInputInfo info, Integer callingUid, Integer resolvedUserId) {
             if (mHardware != null) {
                 try {
                     mCallback.onReleased();
@@ -212,6 +305,7 @@
             }
             mHardware = hardware;
             mCallback = callback;
+            mInfo = info;
             mCallingUid = callingUid;
             mResolvedUserId = resolvedUserId;
 
@@ -228,7 +322,11 @@
             mConfigs = configs;
         }
 
-        public TvInputHardwareInfo getInfoLocked() {
+        public TvInputHardwareInfo getHardwareInfoLocked() {
+            return mHardwareInfo;
+        }
+
+        public TvInputInfo getInfoLocked() {
             return mInfo;
         }
 
@@ -255,7 +353,7 @@
         @Override
         public void binderDied() {
             synchronized (mLock) {
-                resetLocked(null, null, null, null);
+                resetLocked(null, null, null, null, null);
             }
         }
     }
@@ -403,4 +501,28 @@
             return false;
         }
     }
+
+    private class ClientHandler extends Handler {
+        private static final int DO_SET_AVAILABLE = 1;
+
+        ClientHandler(Looper looper) {
+            super(looper);
+        }
+
+        @Override
+        public final void handleMessage(Message msg) {
+            switch (msg.what) {
+                case DO_SET_AVAILABLE: {
+                    String inputId = (String) msg.obj;
+                    int state = msg.arg1;
+                    mClient.setState(inputId, state);
+                    break;
+                }
+                default: {
+                    Slog.w(TAG, "Unhandled message: " + msg);
+                    break;
+                }
+            }
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java
index 5e95af4..20fdefa 100644
--- a/services/core/java/com/android/server/tv/TvInputManagerService.java
+++ b/services/core/java/com/android/server/tv/TvInputManagerService.java
@@ -16,6 +16,9 @@
 
 package com.android.server.tv;
 
+import static android.media.tv.TvInputManager.INPUT_STATE_CONNECTED;
+import static android.media.tv.TvInputManager.INPUT_STATE_DISCONNECTED;
+
 import android.app.ActivityManager;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
@@ -38,6 +41,7 @@
 import android.media.tv.ITvInputHardware;
 import android.media.tv.ITvInputHardwareCallback;
 import android.media.tv.ITvInputManager;
+import android.media.tv.ITvInputManagerCallback;
 import android.media.tv.ITvInputService;
 import android.media.tv.ITvInputServiceCallback;
 import android.media.tv.ITvInputSession;
@@ -109,7 +113,7 @@
         mContentResolver = context.getContentResolver();
         mLogHandler = new LogHandler(IoThread.get().getLooper());
 
-        mTvInputHardwareManager = new TvInputHardwareManager(context);
+        mTvInputHardwareManager = new TvInputHardwareManager(context, new Client());
 
         synchronized (mLock) {
             mUserStates.put(mCurrentUserId, new UserState());
@@ -129,6 +133,7 @@
                 buildTvInputListLocked(mCurrentUserId);
             }
         }
+        mTvInputHardwareManager.onBootPhase(phase);
     }
 
     private void registerBroadcastReceivers() {
@@ -144,7 +149,7 @@
             public void onPackageRemoved(String packageName, int uid) {
                 synchronized (mLock) {
                     UserState userState = getUserStateLocked(mCurrentUserId);
-                    if (!userState.packageList.contains(packageName)) {
+                    if (!userState.packageSet.contains(packageName)) {
                         // Not a TV input package.
                         return;
                     }
@@ -198,8 +203,11 @@
 
     private void buildTvInputListLocked(int userId) {
         UserState userState = getUserStateLocked(userId);
-        userState.inputMap.clear();
-        userState.packageList.clear();
+
+        Map<String, TvInputState> oldInputMap = userState.inputMap;
+        userState.inputMap = new HashMap<String, TvInputState>();
+
+        userState.packageSet.clear();
 
         if (DEBUG) Slog.d(TAG, "buildTvInputList");
         PackageManager pm = mContext.getPackageManager();
@@ -216,8 +224,13 @@
             try {
                 TvInputInfo info = TvInputInfo.createTvInputInfo(mContext, ri);
                 if (DEBUG) Slog.d(TAG, "add " + info.getId());
-                userState.inputMap.put(info.getId(), info);
-                userState.packageList.add(si.packageName);
+                TvInputState state = oldInputMap.get(info.getId());
+                if (state == null) {
+                    state = new TvInputState();
+                }
+                userState.inputMap.put(info.getId(), state);
+                state.mInfo = info;
+                userState.packageSet.add(si.packageName);
 
                 // Reconnect the service if existing input is updated.
                 updateServiceConnectionLocked(info.getId(), userId);
@@ -225,6 +238,7 @@
                 Slog.e(TAG, "Can't load TV input " + si.name, e);
             }
         }
+        oldInputMap.clear();
     }
 
     private void switchUser(int userId) {
@@ -359,7 +373,7 @@
             }
 
             Intent i = new Intent(TvInputService.SERVICE_INTERFACE).setComponent(
-                    userState.inputMap.get(inputId).getComponent());
+                    userState.inputMap.get(inputId).mInfo.getComponent());
             // Binding service may fail if the service is updating.
             // In that case, the connection will be revived in buildTvInputListLocked called by
             // onSomePackagesChanged.
@@ -588,7 +602,7 @@
         updateServiceConnectionLocked(sessionState.mInputId, userId);
     }
 
-    private void unregisterCallbackInternalLocked(IBinder clientToken, String inputId,
+    private void unregisterClientInternalLocked(IBinder clientToken, String inputId,
             int userId) {
         UserState userState = getUserStateLocked(userId);
         ClientState clientState = userState.clientStateMap.get(clientToken);
@@ -623,14 +637,43 @@
         }
     }
 
-    private void broadcastServiceAvailabilityChangedLocked(ServiceState serviceState) {
-        for (IBinder clientToken : serviceState.mClientTokens) {
-            try {
-                ITvInputClient.Stub.asInterface(clientToken).onAvailabilityChanged(
-                        serviceState.mTvInputInfo.getId(), serviceState.mAvailable);
-            } catch (RemoteException e) {
-                Slog.e(TAG, "error in onAvailabilityChanged", e);
+    private void notifyStateChangedLocked(UserState userState, String inputId,
+            int state, ITvInputManagerCallback targetCallback) {
+        if (DEBUG) {
+            Slog.d(TAG, "notifyStateChangedLocked: inputId = " + inputId
+                    + "; state = " + state);
+        }
+        if (targetCallback == null) {
+            for (ITvInputManagerCallback callback : userState.callbackSet) {
+                try {
+                    callback.onInputStateChanged(inputId, state);
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "Failed to report state change to callback.");
+                }
             }
+        } else {
+            try {
+                targetCallback.onInputStateChanged(inputId, state);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Failed to report state change to callback.");
+            }
+        }
+    }
+
+    private void setStateLocked(String inputId, int state, int userId) {
+        UserState userState = getUserStateLocked(userId);
+        TvInputState inputState = userState.inputMap.get(inputId);
+        ServiceState serviceState = userState.serviceStateMap.get(inputId);
+        int oldState = inputState.mState;
+        inputState.mState = state;
+        boolean isStateEmpty = serviceState.mClientTokens.isEmpty()
+                && serviceState.mSessionTokens.isEmpty();
+        if (serviceState != null && serviceState.mService == null && !isStateEmpty) {
+            // We don't notify state change while reconnecting. It should remain disconnected.
+            return;
+        }
+        if (oldState != state) {
+            notifyStateChangedLocked(userState, inputId, state, null);
         }
     }
 
@@ -643,80 +686,29 @@
             try {
                 synchronized (mLock) {
                     UserState userState = getUserStateLocked(resolvedUserId);
-                    return new ArrayList<TvInputInfo>(userState.inputMap.values());
-                }
-            } finally {
-                Binder.restoreCallingIdentity(identity);
-            }
-        }
-
-        @Override
-        public boolean getAvailability(final ITvInputClient client, final String inputId,
-                int userId) {
-            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
-                    Binder.getCallingUid(), userId, "getAvailability");
-            final long identity = Binder.clearCallingIdentity();
-            try {
-                synchronized (mLock) {
-                    UserState userState = getUserStateLocked(resolvedUserId);
-                    ServiceState serviceState = userState.serviceStateMap.get(inputId);
-                    if (serviceState != null) {
-                        // We already know the status of this input service. Return the cached
-                        // status.
-                        return serviceState.mAvailable;
+                    List<TvInputInfo> inputList = new ArrayList<TvInputInfo>();
+                    for (TvInputState state : userState.inputMap.values()) {
+                        inputList.add(state.mInfo);
                     }
+                    return inputList;
                 }
             } finally {
                 Binder.restoreCallingIdentity(identity);
             }
-            // STOPSHIP: Redesign the API around the availability change. For now, the service
-            // will be always available.
-            return true;
         }
 
         @Override
-        public void registerCallback(final ITvInputClient client, final String inputId,
-                int userId) {
+        public void registerCallback(final ITvInputManagerCallback callback, int userId) {
             final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
                     Binder.getCallingUid(), userId, "registerCallback");
             final long identity = Binder.clearCallingIdentity();
             try {
                 synchronized (mLock) {
-                    // Create a new service callback and add it to the callback map of the current
-                    // service.
                     UserState userState = getUserStateLocked(resolvedUserId);
-                    ServiceState serviceState = userState.serviceStateMap.get(inputId);
-                    if (serviceState == null) {
-                        serviceState = new ServiceState(
-                                userState.inputMap.get(inputId), resolvedUserId);
-                        userState.serviceStateMap.put(inputId, serviceState);
-                    }
-                    IBinder clientToken = client.asBinder();
-                    if (!serviceState.mClientTokens.contains(clientToken)) {
-                        serviceState.mClientTokens.add(clientToken);
-                    }
-
-                    ClientState clientState = userState.clientStateMap.get(clientToken);
-                    if (clientState == null) {
-                        clientState = createClientStateLocked(clientToken, resolvedUserId);
-                    }
-                    if (!clientState.mInputIds.contains(inputId)) {
-                        clientState.mInputIds.add(inputId);
-                    }
-
-                    if (serviceState.mService != null) {
-                        if (serviceState.mCallback != null) {
-                            // We already handled.
-                            return;
-                        }
-                        serviceState.mCallback = new ServiceCallback(resolvedUserId);
-                        try {
-                            serviceState.mService.registerCallback(serviceState.mCallback);
-                        } catch (RemoteException e) {
-                            Slog.e(TAG, "error in registerCallback", e);
-                        }
-                    } else {
-                        updateServiceConnectionLocked(inputId, resolvedUserId);
+                    userState.callbackSet.add(callback);
+                    for (TvInputState state : userState.inputMap.values()) {
+                        notifyStateChangedLocked(userState, state.mInfo.getId(),
+                                state.mState, callback);
                     }
                 }
             } finally {
@@ -725,13 +717,14 @@
         }
 
         @Override
-        public void unregisterCallback(ITvInputClient client, String inputId, int userId) {
+        public void unregisterCallback(ITvInputManagerCallback callback, int userId) {
             final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
                     Binder.getCallingUid(), userId, "unregisterCallback");
             final long identity = Binder.clearCallingIdentity();
             try {
                 synchronized (mLock) {
-                    unregisterCallbackInternalLocked(client.asBinder(), inputId, resolvedUserId);
+                    UserState userState = getUserStateLocked(resolvedUserId);
+                    userState.callbackSet.remove(callback);
                 }
             } finally {
                 Binder.restoreCallingIdentity(identity);
@@ -751,7 +744,7 @@
                     ServiceState serviceState = userState.serviceStateMap.get(inputId);
                     if (serviceState == null) {
                         serviceState = new ServiceState(
-                                userState.inputMap.get(inputId), resolvedUserId);
+                                userState.inputMap.get(inputId).mInfo, resolvedUserId);
                         userState.serviceStateMap.put(inputId, serviceState);
                     }
                     // Send a null token immediately while reconnecting.
@@ -868,7 +861,7 @@
                         }
 
                         // Create a log entry and fill it later.
-                        String packageName = userState.inputMap.get(sessionState.mInputId)
+                        String packageName = userState.inputMap.get(sessionState.mInputId).mInfo
                                 .getServiceInfo().packageName;
                         ContentValues values = new ContentValues();
                         values.put(TvContract.WatchedPrograms.COLUMN_PACKAGE_NAME, packageName);
@@ -1017,8 +1010,7 @@
 
         @Override
         public List<TvInputHardwareInfo> getHardwareList() throws RemoteException {
-            if (mContext.checkCallingPermission(
-                    android.Manifest.permission.TV_INPUT_HARDWARE)
+            if (mContext.checkCallingPermission(android.Manifest.permission.TV_INPUT_HARDWARE)
                     != PackageManager.PERMISSION_GRANTED) {
                 return null;
             }
@@ -1032,10 +1024,25 @@
         }
 
         @Override
+        public void registerTvInputInfo(TvInputInfo info, int deviceId) {
+            if (mContext.checkCallingPermission(android.Manifest.permission.TV_INPUT_HARDWARE)
+                    != PackageManager.PERMISSION_GRANTED) {
+                return;
+            }
+
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                mTvInputHardwareManager.registerTvInputInfo(info, deviceId);
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        @Override
         public ITvInputHardware acquireTvInputHardware(int deviceId,
-                ITvInputHardwareCallback callback, int userId) throws RemoteException {
-            if (mContext.checkCallingPermission(
-                    android.Manifest.permission.TV_INPUT_HARDWARE)
+                ITvInputHardwareCallback callback, TvInputInfo info, int userId)
+                throws RemoteException {
+            if (mContext.checkCallingPermission(android.Manifest.permission.TV_INPUT_HARDWARE)
                     != PackageManager.PERMISSION_GRANTED) {
                 return null;
             }
@@ -1046,7 +1053,7 @@
                     userId, "acquireTvInputHardware");
             try {
                 return mTvInputHardwareManager.acquireHardware(
-                        deviceId, callback, callingUid, resolvedUserId);
+                        deviceId, callback, info, callingUid, resolvedUserId);
             } finally {
                 Binder.restoreCallingIdentity(identity);
             }
@@ -1055,8 +1062,7 @@
         @Override
         public void releaseTvInputHardware(int deviceId, ITvInputHardware hardware, int userId)
                 throws RemoteException {
-            if (mContext.checkCallingPermission(
-                    android.Manifest.permission.TV_INPUT_HARDWARE)
+            if (mContext.checkCallingPermission(android.Manifest.permission.TV_INPUT_HARDWARE)
                     != PackageManager.PERMISSION_GRANTED) {
                 return;
             }
@@ -1099,16 +1105,16 @@
                     pw.println("UserState (" + userId + "):");
                     pw.increaseIndent();
 
-                    pw.println("inputMap: inputId -> TvInputInfo");
+                    pw.println("inputMap: inputId -> TvInputState");
                     pw.increaseIndent();
-                    for (TvInputInfo info : userState.inputMap.values()) {
-                        pw.println(info.toString());
+                    for (TvInputState state : userState.inputMap.values()) {
+                        pw.println(state.toString());
                     }
                     pw.decreaseIndent();
 
-                    pw.println("packageList:");
+                    pw.println("packageSet:");
                     pw.increaseIndent();
-                    for (String packageName : userState.packageList) {
+                    for (String packageName : userState.packageSet) {
                         pw.println(packageName);
                     }
                     pw.decreaseIndent();
@@ -1169,7 +1175,6 @@
                         pw.println("mService: " + service.mService);
                         pw.println("mCallback: " + service.mCallback);
                         pw.println("mBound: " + service.mBound);
-                        pw.println("mAvailable: " + service.mAvailable);
                         pw.println("mReconnecting: " + service.mReconnecting);
 
                         pw.decreaseIndent();
@@ -1196,18 +1201,38 @@
                     }
                     pw.decreaseIndent();
 
+                    pw.println("callbackSet:");
+                    pw.increaseIndent();
+                    for (ITvInputManagerCallback callback : userState.callbackSet) {
+                        pw.println(callback.toString());
+                    }
+                    pw.decreaseIndent();
+
                     pw.decreaseIndent();
                 }
             }
         }
     }
 
-    private static final class UserState {
-        // A mapping from the TV input id to its TvInputInfo.
-        private final Map<String, TvInputInfo> inputMap = new HashMap<String,TvInputInfo>();
+    private static final class TvInputState {
+        // A TvInputInfo object which represents the TV input.
+        private TvInputInfo mInfo;
 
-        // A list of all TV input packages.
-        private final Set<String> packageList = new HashSet<String>();
+        // The state of TV input. Connected by default.
+        private int mState = INPUT_STATE_CONNECTED;
+
+        @Override
+        public String toString() {
+            return "mInfo: " + mInfo + "; mState: " + mState;
+        }
+    }
+
+    private static final class UserState {
+        // A mapping from the TV input id to its TvInputState.
+        private Map<String, TvInputState> inputMap = new HashMap<String, TvInputState>();
+
+        // A set of all TV input packages.
+        private final Set<String> packageSet = new HashSet<String>();
 
         // A mapping from the token of a client to its state.
         private final Map<IBinder, ClientState> clientStateMap =
@@ -1220,6 +1245,10 @@
         // A mapping from the token of a TV input session to its state.
         private final Map<IBinder, SessionState> sessionStateMap =
                 new HashMap<IBinder, SessionState>();
+
+        // A set of callbacks.
+        private final Set<ITvInputManagerCallback> callbackSet =
+                new HashSet<ITvInputManagerCallback>();
     }
 
     private final class ClientState implements IBinder.DeathRecipient {
@@ -1243,7 +1272,7 @@
             synchronized (mLock) {
                 UserState userState = getUserStateLocked(mUserId);
                 // DO NOT remove the client state of clientStateMap in this method. It will be
-                // removed in releaseSessionLocked() or unregisterCallbackInternalLocked().
+                // removed in releaseSessionLocked() or unregisterClientInternalLocked().
                 ClientState clientState = userState.clientStateMap.get(mClientToken);
                 if (clientState != null) {
                     while (clientState.mSessionTokens.size() > 0) {
@@ -1251,7 +1280,7 @@
                                 clientState.mSessionTokens.get(0), Process.SYSTEM_UID, mUserId);
                     }
                     while (clientState.mInputIds.size() > 0) {
-                        unregisterCallbackInternalLocked(
+                        unregisterClientInternalLocked(
                                 mClientToken, clientState.mInputIds.get(0), mUserId);
                     }
                 }
@@ -1269,7 +1298,6 @@
         private ITvInputService mService;
         private ServiceCallback mCallback;
         private boolean mBound;
-        private boolean mAvailable;
         private boolean mReconnecting;
 
         private ServiceState(TvInputInfo inputInfo, int userId) {
@@ -1325,16 +1353,18 @@
 
         @Override
         public void onServiceConnected(ComponentName name, IBinder service) {
+            String inputId = mTvInputInfo.getId();
             if (DEBUG) {
-                Slog.d(TAG, "onServiceConnected(inputId=" + mTvInputInfo.getId() + ")");
+                Slog.d(TAG, "onServiceConnected(inputId=" + inputId + ")");
             }
             synchronized (mLock) {
-                ServiceState serviceState = getServiceStateLocked(mTvInputInfo.getId(), mUserId);
+                UserState userState = getUserStateLocked(mUserId);
+                ServiceState serviceState = userState.serviceStateMap.get(inputId);
                 serviceState.mService = ITvInputService.Stub.asInterface(service);
 
                 // Register a callback, if we need to.
                 if (!serviceState.mClientTokens.isEmpty() && serviceState.mCallback == null) {
-                    serviceState.mCallback = new ServiceCallback(mUserId);
+                    serviceState.mCallback = new ServiceCallback(mTvInputInfo.getId(), mUserId);
                     try {
                         serviceState.mService.registerCallback(serviceState.mCallback);
                     } catch (RemoteException e) {
@@ -1346,6 +1376,12 @@
                 for (IBinder sessionToken : serviceState.mSessionTokens) {
                     createSessionInternalLocked(serviceState.mService, sessionToken, mUserId);
                 }
+
+                TvInputState inputState = userState.inputMap.get(inputId);
+                if (inputState != null && inputState.mState != INPUT_STATE_DISCONNECTED) {
+                    notifyStateChangedLocked(userState, mTvInputInfo.getId(),
+                            inputState.mState, null);
+                }
             }
         }
 
@@ -1377,10 +1413,8 @@
                         }
                     }
 
-                    if (serviceState.mAvailable) {
-                        serviceState.mAvailable = false;
-                        broadcastServiceAvailabilityChangedLocked(serviceState);
-                    }
+                    notifyStateChangedLocked(userState, mTvInputInfo.getId(),
+                            INPUT_STATE_DISCONNECTED, null);
                     updateServiceConnectionLocked(mTvInputInfo.getId(), mUserId);
                 }
             }
@@ -1388,24 +1422,22 @@
     }
 
     private final class ServiceCallback extends ITvInputServiceCallback.Stub {
+        private final String mInputId;
         private final int mUserId;
 
-        ServiceCallback(int userId) {
+        ServiceCallback(String inputId, int userId) {
+            mInputId = inputId;
             mUserId = userId;
         }
 
         @Override
-        public void onAvailabilityChanged(String inputId, boolean isAvailable) {
+        public void onInputStateChanged(int state) {
             if (DEBUG) {
-                Slog.d(TAG, "onAvailabilityChanged(inputId=" + inputId + ", isAvailable="
-                        + isAvailable + ")");
+                Slog.d(TAG, "onInputStateChanged(inputId=" + mInputId + ", state="
+                        + state + ")");
             }
             synchronized (mLock) {
-                ServiceState serviceState = getServiceStateLocked(inputId, mUserId);
-                if (serviceState.mAvailable != isAvailable) {
-                    serviceState.mAvailable = isAvailable;
-                    broadcastServiceAvailabilityChangedLocked(serviceState);
-                }
+                setStateLocked(mInputId, state, mUserId);
             }
         }
     }
@@ -1553,4 +1585,12 @@
             mContentResolver.update(uri, values, null, null);
         }
     }
+
+    final class Client {
+        public void setState(String inputId, int state) {
+            synchronized (mLock) {
+                setStateLocked(inputId, state, mCurrentUserId);
+            }
+        }
+    }
 }
diff --git a/telecomm/java/android/telecomm/CallPropertyPresentation.java b/telecomm/java/android/telecomm/CallPropertyPresentation.java
index 350980c..319e565 100644
--- a/telecomm/java/android/telecomm/CallPropertyPresentation.java
+++ b/telecomm/java/android/telecomm/CallPropertyPresentation.java
@@ -19,14 +19,14 @@
 /** Defines how numbers and names are displayed in caller id. */
 public class CallPropertyPresentation {
     /** Property is displayed normally. */
-    public static final int ALLOWED = 0;
+    public static final int ALLOWED = 1;
 
     /** Property was blocked. */
-    public static final int RESTRICTED = 1;
+    public static final int RESTRICTED = 2;
 
     /** Presentation was not specified or is unknown. */
-    public static final int UNKNOWN = 2;
+    public static final int UNKNOWN = 3;
 
     /** Property should be displayed as a pay phone. */
-    public static final int PAYPHONE = 3;
+    public static final int PAYPHONE = 4;
 }
diff --git a/telecomm/java/android/telecomm/ConnectionRequest.java b/telecomm/java/android/telecomm/ConnectionRequest.java
index 0db9e29..5888d6a 100644
--- a/telecomm/java/android/telecomm/ConnectionRequest.java
+++ b/telecomm/java/android/telecomm/ConnectionRequest.java
@@ -30,11 +30,11 @@
     // TODO: Token to limit recursive invocations
     // TODO: Consider upgrading "mHandle" to ordered list of handles, indicating a set of phone
     //         numbers that would satisfy the client's needs, in order of preference
+    private final PhoneAccount mAccount;
     private final String mCallId;
     private final Uri mHandle;
     private final int mHandlePresentation;
     private final Bundle mExtras;
-    private final PhoneAccount mAccount;
     private final int mVideoState;
 
     /**
@@ -61,6 +61,15 @@
         mVideoState = videoState;
     }
 
+    private ConnectionRequest(Parcel in) {
+        mAccount = in.readParcelable(getClass().getClassLoader());
+        mCallId = in.readString();
+        mHandle = in.readParcelable(getClass().getClassLoader());
+        mHandlePresentation = in.readInt();
+        mExtras = in.readParcelable(getClass().getClassLoader());
+        mVideoState = in.readInt();
+    }
+
     /**
      * The account which should be used to place the call.
      */
@@ -109,26 +118,17 @@
                 mExtras == null ? "" : mExtras);
     }
 
-    public static final Parcelable.Creator<ConnectionRequest> CREATOR =
-            new Parcelable.Creator<ConnectionRequest> () {
-                @Override
-                public ConnectionRequest createFromParcel(Parcel source) {
-                    PhoneAccount account = (PhoneAccount) source.readParcelable(
-                            getClass().getClassLoader());
-                    String callId = source.readString();
-                    Uri handle = (Uri) source.readParcelable(getClass().getClassLoader());
-                    int presentation = source.readInt();
-                    Bundle extras = (Bundle) source.readParcelable(getClass().getClassLoader());
-                    int videoState = source.readInt();
-                    return new ConnectionRequest(
-                            account, callId, handle, presentation, extras, videoState);
-                }
+    public static final Creator<ConnectionRequest> CREATOR = new Creator<ConnectionRequest> () {
+        @Override
+        public ConnectionRequest createFromParcel(Parcel source) {
+            return new ConnectionRequest(source);
+        }
 
-                @Override
-                public ConnectionRequest[] newArray(int size) {
-                    return new ConnectionRequest[size];
-                }
-            };
+        @Override
+        public ConnectionRequest[] newArray(int size) {
+            return new ConnectionRequest[size];
+        }
+    };
 
     /**
      * {@inheritDoc}
diff --git a/telecomm/java/android/telecomm/ConnectionService.java b/telecomm/java/android/telecomm/ConnectionService.java
index 1966081..178cee8 100644
--- a/telecomm/java/android/telecomm/ConnectionService.java
+++ b/telecomm/java/android/telecomm/ConnectionService.java
@@ -47,22 +47,21 @@
     private static final Connection NULL_CONNECTION = new Connection() {};
 
     private static final int MSG_ADD_CALL_SERVICE_ADAPTER = 1;
-    private static final int MSG_CALL = 2;
+    private static final int MSG_CREATE_CONNECTION = 2;
     private static final int MSG_ABORT = 3;
-    private static final int MSG_CREATE_INCOMING_CALL = 4;
-    private static final int MSG_ANSWER = 5;
-    private static final int MSG_REJECT = 6;
-    private static final int MSG_DISCONNECT = 7;
-    private static final int MSG_HOLD = 8;
-    private static final int MSG_UNHOLD = 9;
-    private static final int MSG_ON_AUDIO_STATE_CHANGED = 10;
-    private static final int MSG_PLAY_DTMF_TONE = 11;
-    private static final int MSG_STOP_DTMF_TONE = 12;
-    private static final int MSG_CONFERENCE = 13;
-    private static final int MSG_SPLIT_FROM_CONFERENCE = 14;
-    private static final int MSG_SWAP_WITH_BACKGROUND_CALL = 15;
-    private static final int MSG_ON_POST_DIAL_CONTINUE = 16;
-    private static final int MSG_ON_PHONE_ACCOUNT_CLICKED = 17;
+    private static final int MSG_ANSWER = 4;
+    private static final int MSG_REJECT = 5;
+    private static final int MSG_DISCONNECT = 6;
+    private static final int MSG_HOLD = 7;
+    private static final int MSG_UNHOLD = 8;
+    private static final int MSG_ON_AUDIO_STATE_CHANGED = 9;
+    private static final int MSG_PLAY_DTMF_TONE = 10;
+    private static final int MSG_STOP_DTMF_TONE = 11;
+    private static final int MSG_CONFERENCE = 12;
+    private static final int MSG_SPLIT_FROM_CONFERENCE = 13;
+    private static final int MSG_SWAP_WITH_BACKGROUND_CALL = 14;
+    private static final int MSG_ON_POST_DIAL_CONTINUE = 15;
+    private static final int MSG_ON_PHONE_ACCOUNT_CLICKED = 16;
 
     private final Map<String, Connection> mConnectionById = new HashMap<>();
     private final Map<Connection, String> mIdByConnection = new HashMap<>();
@@ -74,11 +73,11 @@
     private final ConnectionServiceAdapter mAdapter = new ConnectionServiceAdapter();
 
     /**
-     * A callback for providing the resuilt of creating a connection.
+     * A callback for providing the result of creating a connection.
      */
-    public interface OutgoingCallResponse<CONNECTION> {
+    public interface CreateConnectionResponse<CONNECTION> {
         /**
-         * Tells Telecomm that an attempt to place the specified outgoing call succeeded.
+         * Tells Telecomm that an attempt to create the connection succeeded.
          *
          * @param request The original request.
          * @param connection The connection.
@@ -86,7 +85,8 @@
         void onSuccess(ConnectionRequest request, CONNECTION connection);
 
         /**
-         * Tells Telecomm that an attempt to place the specified outgoing call failed.
+         * Tells Telecomm that an attempt to create the connection failed. Telecomm will try a
+         * different service until a service cancels the process or completes it successfully.
          *
          * @param request The original request.
          * @param code An integer code indicating the reason for failure.
@@ -95,7 +95,8 @@
         void onFailure(ConnectionRequest request, int code, String msg);
 
         /**
-         * Tells Telecomm to cancel the call.
+         * Tells Telecomm to cancel creating the connection. Telecomm will stop trying to create
+         * the connection an no more services will be tried.
          *
          * @param request The original request.
          */
@@ -109,8 +110,9 @@
         }
 
         @Override
-        public void call(ConnectionRequest request) {
-            mHandler.obtainMessage(MSG_CALL, request).sendToTarget();
+        public void createConnection(ConnectionRequest request, boolean isIncoming) {
+            mHandler.obtainMessage(
+                    MSG_CREATE_CONNECTION, isIncoming ? 1 : 0, 0, request).sendToTarget();
         }
 
         @Override
@@ -119,11 +121,6 @@
         }
 
         @Override
-        public void createIncomingCall(ConnectionRequest request) {
-            mHandler.obtainMessage(MSG_CREATE_INCOMING_CALL, request).sendToTarget();
-        }
-
-        @Override
         public void answer(String callId) {
             mHandler.obtainMessage(MSG_ANSWER, callId).sendToTarget();
         }
@@ -206,15 +203,12 @@
                     mAdapter.addAdapter((IConnectionServiceAdapter) msg.obj);
                     onAdapterAttached();
                     break;
-                case MSG_CALL:
-                    call((ConnectionRequest) msg.obj);
+                case MSG_CREATE_CONNECTION:
+                    createConnection((ConnectionRequest) msg.obj, msg.arg1 == 1);
                     break;
                 case MSG_ABORT:
                     abort((String) msg.obj);
                     break;
-                case MSG_CREATE_INCOMING_CALL:
-                    createIncomingCall((ConnectionRequest) msg.obj);
-                    break;
                 case MSG_ANSWER:
                     answer((String) msg.obj);
                     break;
@@ -394,29 +388,39 @@
         return mBinder;
     }
 
-    private void call(final ConnectionRequest originalRequest) {
+    /**
+     * This can be used by telecomm to either create a new outgoing call or attach to an existing
+     * incoming call. In either case, telecomm will cycle through a set of services and call
+     * createConnection util a connection service cancels the process or completes it successfully.
+     */
+    private void createConnection(ConnectionRequest originalRequest, boolean isIncoming) {
         Log.d(this, "call %s", originalRequest);
-        onCreateConnections(
-                originalRequest,
-                new OutgoingCallResponse<Connection>() {
-                    @Override
-                    public void onSuccess(ConnectionRequest request, Connection connection) {
-                        Log.d(this, "adapter handleSuccessfulOutgoingCall %s", request.getCallId());
-                        mAdapter.handleSuccessfulOutgoingCall(request);
-                        addConnection(request.getCallId(), connection);
-                    }
+        CreateConnectionResponse response = new CreateConnectionResponse<Connection>() {
+            @Override
+            public void onSuccess(ConnectionRequest request, Connection connection) {
+                Log.d(this, "adapter handleCreateConnectionSuccessful %s",
+                        request.getCallId());
+                mAdapter.handleCreateConnectionSuccessful(request);
+                addConnection(request.getCallId(), connection);
+            }
 
-                    @Override
-                    public void onFailure(ConnectionRequest request, int code, String msg) {
-                        mAdapter.handleFailedOutgoingCall(request, code, msg);
-                    }
+            @Override
+            public void onFailure(ConnectionRequest request, int code, String msg) {
+                // Tell telecomm to try a different service.
+                mAdapter.handleCreateConnectionFailed(request, code, msg);
+            }
 
-                    @Override
-                    public void onCancel(ConnectionRequest request) {
-                        mAdapter.cancelOutgoingCall(request);
-                    }
-                }
-        );
+            @Override
+            public void onCancel(ConnectionRequest request) {
+                // Tell telecomm not to attempt any more services.
+                mAdapter.handleCreateConnectionCancelled(request);
+            }
+        };
+        if (isIncoming) {
+            onCreateIncomingConnection(originalRequest, response);
+        } else {
+            onCreateOutgoingConnection(originalRequest, response);
+        }
     }
 
     private void abort(String callId) {
@@ -424,33 +428,6 @@
         findConnectionForAction(callId, "abort").onAbort();
     }
 
-    private void createIncomingCall(ConnectionRequest originalRequest) {
-        Log.d(this, "createIncomingCall %s", originalRequest);
-        onCreateIncomingConnection(
-                originalRequest,
-                new Response<ConnectionRequest, Connection>() {
-                    @Override
-                    public void onResult(ConnectionRequest request, Connection... result) {
-                        if (result != null && result.length != 1) {
-                            for (Connection c : result) {
-                                c.onAbort();
-                            }
-                        } else {
-                            addConnection(request.getCallId(), result[0]);
-                            Log.d(this, "adapter notifyIncomingCall %s", request);
-                            mAdapter.notifyIncomingCall(request);
-                        }
-                    }
-
-                    @Override
-                    public void onError(ConnectionRequest request, int code, String msg) {
-                        Log.d(this, "adapter failed createIncomingCall %s %d %s",
-                                request, code, msg);
-                    }
-                }
-        );
-    }
-
     private void answer(String callId) {
         Log.d(this, "answer %s", callId);
         findConnectionForAction(callId, "answer").onAnswer();
@@ -570,7 +547,7 @@
                                     IConnectionService.Stub.asInterface(services.get(i)));
                         }
                         mAreAccountsInitialized = true;
-                        Log.d(this, "remote call services found: " + services);
+                        Log.d(this, "remote connection services found: " + services);
                         maybeRespondToAccountLookup();
                     }
                 });
@@ -606,10 +583,16 @@
         }
     }
 
+    public final void createRemoteIncomingConnection(
+            ConnectionRequest request,
+            CreateConnectionResponse<RemoteConnection> response) {
+        mRemoteConnectionManager.createRemoteConnection(request, response, true);
+    }
+
     public final void createRemoteOutgoingConnection(
             ConnectionRequest request,
-            OutgoingCallResponse<RemoteConnection> response) {
-        mRemoteConnectionManager.createOutgoingConnection(request, response);
+            CreateConnectionResponse<RemoteConnection> response) {
+        mRemoteConnectionManager.createRemoteConnection(request, response, false);
     }
 
     /**
@@ -620,14 +603,25 @@
     }
 
     /**
-     * Create a Connection given a request.
+     * Create a Connection given an incoming request. This is used to attach to existing incoming
+     * calls.
      *
-     * @param request Data encapsulating details of the desired Connection.
+     * @param request Details about the incoming call.
      * @param callback A callback for providing the result.
      */
-    protected void onCreateConnections(
+    protected void onCreateIncomingConnection(
             ConnectionRequest request,
-            OutgoingCallResponse<Connection> callback) {}
+            CreateConnectionResponse<Connection> callback) {}
+
+    /**
+     * Create a Connection given an outgoing request. This is used to initiate new outgoing calls.
+     *
+     * @param request Details about the outgoing call.
+     * @param callback A callback for providing the result.
+     */
+    protected void onCreateOutgoingConnection(
+            ConnectionRequest request,
+            CreateConnectionResponse<Connection> callback) {}
 
     /**
      * Returns a new or existing conference connection when the the user elects to convert the
@@ -645,21 +639,6 @@
             Response<String, Connection> callback) {}
 
     /**
-     * Create a Connection to match an incoming connection notification.
-     *
-     * IMPORTANT: If the incoming connection has a phone number (or other handle) that the user
-     * is not supposed to be able to see (e.g. it is PRESENTATION_RESTRICTED), then a compliant
-     * ConnectionService implementation MUST NOT reveal this phone number as part of the Intent
-     * it sends to notify Telecomm of an incoming connection.
-     *
-     * @param request Data encapsulating details of the desired Connection.
-     * @param callback A callback for providing the result.
-     */
-    protected void onCreateIncomingConnection(
-            ConnectionRequest request,
-            Response<ConnectionRequest, Connection> callback) {}
-
-    /**
      * Notifies that a connection has been added to this connection service and sent to Telecomm.
      *
      * @param connection The connection which was added.
diff --git a/telecomm/java/android/telecomm/ConnectionServiceAdapter.java b/telecomm/java/android/telecomm/ConnectionServiceAdapter.java
index b90dec3..a812fa4 100644
--- a/telecomm/java/android/telecomm/ConnectionServiceAdapter.java
+++ b/telecomm/java/android/telecomm/ConnectionServiceAdapter.java
@@ -73,65 +73,28 @@
         }
     }
 
-    /**
-     * Provides Telecomm with the details of an incoming call. An invocation of this method must
-     * follow {@link ConnectionService#setIncomingCallId} and use the call ID specified therein.
-     * Upon the invocation of this method, Telecomm will bring up the incoming-call interface where
-     * the user can elect to answer or reject a call.
-     *
-     * @param request The connection request.
-     */
-    void notifyIncomingCall(ConnectionRequest request) {
+    void handleCreateConnectionSuccessful(ConnectionRequest request) {
         for (IConnectionServiceAdapter adapter : mAdapters) {
             try {
-                adapter.notifyIncomingCall(request);
+                adapter.handleCreateConnectionSuccessful(request);
             } catch (RemoteException e) {
             }
         }
     }
 
-    /**
-     * Tells Telecomm that an attempt to place the specified outgoing call succeeded.
-     *
-     * @param request The originating request for a connection.
-     */
-    void handleSuccessfulOutgoingCall(ConnectionRequest request) {
+    void handleCreateConnectionFailed(ConnectionRequest request, int errorCode, String errorMsg) {
         for (IConnectionServiceAdapter adapter : mAdapters) {
             try {
-                adapter.handleSuccessfulOutgoingCall(request);
+                adapter.handleCreateConnectionFailed(request, errorCode, errorMsg);
             } catch (RemoteException e) {
             }
         }
     }
 
-    /**
-     * Tells Telecomm that an attempt to place the specified outgoing call failed.
-     *
-     * @param request The originating request for a connection.
-     * @param errorCode The error code associated with the failed call attempt.
-     * @param errorMsg The error message associated with the failed call attempt.
-     */
-    void handleFailedOutgoingCall(
-            ConnectionRequest request,
-            int errorCode,
-            String errorMsg) {
+    void handleCreateConnectionCancelled(ConnectionRequest request) {
         for (IConnectionServiceAdapter adapter : mAdapters) {
             try {
-                adapter.handleFailedOutgoingCall(request, errorCode, errorMsg);
-            } catch (RemoteException e) {
-            }
-        }
-    }
-
-    /**
-     * Tells Telecomm to cancel the call.
-     *
-     * @param request The originating request for a connection.
-     */
-    void cancelOutgoingCall(ConnectionRequest request) {
-        for (IConnectionServiceAdapter adapter : mAdapters) {
-            try {
-                adapter.cancelOutgoingCall(request);
+                adapter.handleCreateConnectionCancelled(request);
             } catch (RemoteException e) {
             }
         }
diff --git a/telecomm/java/android/telecomm/RemoteConnectionManager.java b/telecomm/java/android/telecomm/RemoteConnectionManager.java
index 9cffdcc..0a0b245 100644
--- a/telecomm/java/android/telecomm/RemoteConnectionManager.java
+++ b/telecomm/java/android/telecomm/RemoteConnectionManager.java
@@ -54,9 +54,10 @@
         return accounts;
     }
 
-    public void createOutgoingConnection(
+    public void createRemoteConnection(
             ConnectionRequest request,
-            final ConnectionService.OutgoingCallResponse response) {
+            ConnectionService.CreateConnectionResponse response,
+            boolean isIncoming) {
         PhoneAccount account = request.getAccount();
         if (account == null) {
             throw new IllegalArgumentException("account must be specified.");
@@ -67,7 +68,7 @@
             throw new UnsupportedOperationException("account not supported: " + componentName);
         } else {
             RemoteConnectionService remoteService = mRemoteConnectionServices.get(componentName);
-            remoteService.createOutgoingConnection(request, response);
+            remoteService.createRemoteConnection(request, response, isIncoming);
         }
     }
 }
diff --git a/telecomm/java/android/telecomm/RemoteConnectionService.java b/telecomm/java/android/telecomm/RemoteConnectionService.java
index 430133c..7fd8f93 100644
--- a/telecomm/java/android/telecomm/RemoteConnectionService.java
+++ b/telecomm/java/android/telecomm/RemoteConnectionService.java
@@ -44,40 +44,34 @@
 
     private String mConnectionId;
     private ConnectionRequest mPendingRequest;
-    private ConnectionService.OutgoingCallResponse<RemoteConnection> mPendingOutgoingCallResponse;
+    private ConnectionService.CreateConnectionResponse<RemoteConnection> mPendingResponse;
     // Remote connection services only support a single connection.
     private RemoteConnection mConnection;
 
     private final IConnectionServiceAdapter mAdapter = new IConnectionServiceAdapter.Stub() {
-
         @Override
-        public void notifyIncomingCall(ConnectionRequest request) {
-            Log.w(this, "notifyIncomingCall not implemented in Remote connection");
-        }
-
-        @Override
-        public void handleSuccessfulOutgoingCall(ConnectionRequest request) {
+        public void handleCreateConnectionSuccessful(ConnectionRequest request) {
             if (isPendingConnection(request.getCallId())) {
                 mConnection = new RemoteConnection(mConnectionService, request.getCallId());
-                mPendingOutgoingCallResponse.onSuccess(request, mConnection);
+                mPendingResponse.onSuccess(request, mConnection);
                 clearPendingInformation();
             }
         }
 
         @Override
-        public void handleFailedOutgoingCall(
+        public void handleCreateConnectionFailed(
                 ConnectionRequest request, int errorCode, String errorMessage) {
             if (isPendingConnection(request.getCallId())) {
-                mPendingOutgoingCallResponse.onFailure(request, errorCode, errorMessage);
+                mPendingResponse.onFailure(request, errorCode, errorMessage);
                 mConnectionId = null;
                 clearPendingInformation();
             }
         }
 
         @Override
-        public void cancelOutgoingCall(ConnectionRequest request) {
+        public void handleCreateConnectionCancelled(ConnectionRequest request) {
             if (isPendingConnection(request.getCallId())) {
-                mPendingOutgoingCallResponse.onCancel(request);
+                mPendingResponse.onCancel(request);
                 mConnectionId = null;
                 clearPendingInformation();
             }
@@ -226,12 +220,10 @@
         release();
     }
 
-    /**
-     * Places an outgoing call.
-     */
-    final void createOutgoingConnection(
+    final void createRemoteConnection(
             ConnectionRequest request,
-            ConnectionService.OutgoingCallResponse<RemoteConnection> response) {
+            ConnectionService.CreateConnectionResponse<RemoteConnection> response,
+            boolean isIncoming) {
 
         if (mConnectionId == null) {
             String id = UUID.randomUUID().toString();
@@ -243,9 +235,9 @@
                     request.getExtras(),
                     request.getVideoState());
             try {
-                mConnectionService.call(newRequest);
+                mConnectionService.createConnection(newRequest, isIncoming);
                 mConnectionId = id;
-                mPendingOutgoingCallResponse = response;
+                mPendingResponse = response;
                 mPendingRequest = request;
             } catch (RemoteException e) {
                 response.onFailure(request, DisconnectCause.ERROR_UNSPECIFIED, e.toString());
@@ -255,9 +247,6 @@
         }
     }
 
-    // TODO(santoscordon): Handle incoming connections
-    // public final void handleIncomingConnection() {}
-
     final List<PhoneAccount> lookupAccounts(Uri handle) {
         // TODO(santoscordon): Update this so that is actually calls into the RemoteConnection
         // each time.
@@ -279,7 +268,7 @@
     }
 
     private boolean isPendingConnection(String id) {
-        return TextUtils.equals(mConnectionId, id) && mPendingOutgoingCallResponse != null;
+        return TextUtils.equals(mConnectionId, id) && mPendingResponse != null;
     }
 
     private boolean isCurrentConnection(String id) {
@@ -288,7 +277,7 @@
 
     private void clearPendingInformation() {
         mPendingRequest = null;
-        mPendingOutgoingCallResponse = null;
+        mPendingResponse = null;
     }
 
     private void destroyConnection() {
diff --git a/telecomm/java/com/android/internal/telecomm/IConnectionService.aidl b/telecomm/java/com/android/internal/telecomm/IConnectionService.aidl
index 16d2edf..9360219 100644
--- a/telecomm/java/com/android/internal/telecomm/IConnectionService.aidl
+++ b/telecomm/java/com/android/internal/telecomm/IConnectionService.aidl
@@ -32,12 +32,10 @@
 oneway interface IConnectionService {
     void addConnectionServiceAdapter(in IConnectionServiceAdapter adapter);
 
-    void call(in ConnectionRequest request);
+    void createConnection(in ConnectionRequest request, boolean isIncoming);
 
     void abort(String callId);
 
-    void createIncomingCall(in ConnectionRequest request);
-
     void answer(String callId);
 
     void reject(String callId);
diff --git a/telecomm/java/com/android/internal/telecomm/IConnectionServiceAdapter.aidl b/telecomm/java/com/android/internal/telecomm/IConnectionServiceAdapter.aidl
index bc67eab..b36f72c 100644
--- a/telecomm/java/com/android/internal/telecomm/IConnectionServiceAdapter.aidl
+++ b/telecomm/java/com/android/internal/telecomm/IConnectionServiceAdapter.aidl
@@ -31,13 +31,12 @@
  * {@hide}
  */
 oneway interface IConnectionServiceAdapter {
-    void notifyIncomingCall(in ConnectionRequest request);
+    void handleCreateConnectionSuccessful(in ConnectionRequest request);
 
-    void handleSuccessfulOutgoingCall(in ConnectionRequest request);
+    void handleCreateConnectionFailed(
+            in ConnectionRequest request, int errorCode, String errorMessage);
 
-    void handleFailedOutgoingCall(in ConnectionRequest request, int errorCode, String errorMessage);
-
-    void cancelOutgoingCall(in ConnectionRequest request);
+    void handleCreateConnectionCancelled(in ConnectionRequest request);
 
     void setActive(String callId);
 
diff --git a/test-runner/src/android/test/mock/MockPackageManager.java b/test-runner/src/android/test/mock/MockPackageManager.java
index 648c418..e388480 100644
--- a/test-runner/src/android/test/mock/MockPackageManager.java
+++ b/test-runner/src/android/test/mock/MockPackageManager.java
@@ -750,7 +750,13 @@
     }
 
     /** {@hide} */
-    public PackageInstaller getPackageInstaller() {
+    public PackageInstaller getInstaller() {
+        throw new UnsupportedOperationException();
+    }
+
+    /** {@hide} */
+    @Override
+    public boolean isPackageAvailable(String packageName) {
         throw new UnsupportedOperationException();
     }