Merge "Remove content description from SearchView text field" into lmp-dev
diff --git a/Android.mk b/Android.mk
index e3d3433..30cb9c6 100644
--- a/Android.mk
+++ b/Android.mk
@@ -131,7 +131,7 @@
 	core/java/android/content/pm/IPackageInstallObserver.aidl \
 	core/java/android/content/pm/IPackageInstallObserver2.aidl \
 	core/java/android/content/pm/IPackageInstaller.aidl \
-	core/java/android/content/pm/IPackageInstallerObserver.aidl \
+	core/java/android/content/pm/IPackageInstallerCallback.aidl \
 	core/java/android/content/pm/IPackageInstallerSession.aidl \
 	core/java/android/content/pm/IPackageManager.aidl \
 	core/java/android/content/pm/IPackageMoveObserver.aidl \
diff --git a/CleanSpec.mk b/CleanSpec.mk
index 3014e0f..abec4c8 100644
--- a/CleanSpec.mk
+++ b/CleanSpec.mk
@@ -212,6 +212,7 @@
 $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework-base_intermediates/src/telecomm/java/com/android/internal/telecomm)
 $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework_intermediates)
 $(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/framework.* $(PRODUCT_OUT)/system/framework2.*)
+$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework-base_intermediates)
 
 # ******************************************************************
 # NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST ABOVE THIS BANNER
diff --git a/api/current.txt b/api/current.txt
index d482260..a94d10b 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -8454,32 +8454,31 @@
 
   public class InstallSessionInfo implements android.os.Parcelable {
     method public int describeContents();
-    method public android.graphics.Bitmap getIcon();
+    method public android.graphics.Bitmap getAppIcon();
+    method public java.lang.CharSequence getAppLabel();
+    method public java.lang.String getAppPackageName();
     method public java.lang.String getInstallerPackageName();
-    method public java.lang.String getPackageName();
-    method public int getProgress();
+    method public float 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();
+    ctor public InstallSessionParams(int);
     method public int describeContents();
-    method public void setDeltaSize(long);
-    method public void setIcon(android.graphics.Bitmap);
+    method public void setAppIcon(android.graphics.Bitmap);
+    method public void setAppLabel(java.lang.CharSequence);
+    method public void setAppPackageName(java.lang.String);
     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 setSize(long);
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator CREATOR;
+    field public static final int MODE_FULL_INSTALL = 1; // 0x1
+    field public static final int MODE_INHERIT_EXISTING = 2; // 0x2
   }
 
   public class InstrumentationInfo extends android.content.pm.PackageItemInfo implements android.os.Parcelable {
@@ -8580,42 +8579,47 @@
   }
 
   public class PackageInstaller {
+    method public void addSessionCallback(android.content.pm.PackageInstaller.SessionCallback);
+    method public void addSessionCallback(android.content.pm.PackageInstaller.SessionCallback, android.os.Handler);
     method public int createSession(android.content.pm.InstallSessionParams) throws java.io.IOException;
-    method public java.util.List<android.content.pm.InstallSessionInfo> getActiveSessions();
+    method public java.util.List<android.content.pm.InstallSessionInfo> getAllSessions();
+    method public java.util.List<android.content.pm.InstallSessionInfo> getMySessions();
+    method public android.content.pm.InstallSessionInfo getSessionInfo(int);
     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);
+    method public void removeSessionCallback(android.content.pm.PackageInstaller.SessionCallback);
+    method public void uninstall(java.lang.String, android.content.pm.PackageInstaller.UninstallCallback);
   }
 
-  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);
+  public static abstract class PackageInstaller.CommitCallback {
+    ctor public PackageInstaller.CommitCallback();
+    method public abstract void onFailure(int, java.lang.String, android.os.Bundle);
     method public abstract void onSuccess();
+    field public static final java.lang.String EXTRA_PACKAGE_NAME = "android.content.pm.extra.PACKAGE_NAME";
+    field public static final int FAILURE_CONFLICT = 2; // 0x2
+    field public static final int FAILURE_INCOMPATIBLE = 4; // 0x4
+    field public static final int FAILURE_INVALID = 1; // 0x1
+    field public static final int FAILURE_STORAGE = 3; // 0x3
+    field public static final int FAILURE_UNKNOWN = 0; // 0x0
   }
 
   public static class PackageInstaller.Session implements java.io.Closeable {
+    method public void abandon();
     method public void close();
-    method public void commit(android.content.pm.PackageInstaller.CommitResultCallback);
-    method public void destroy();
+    method public void commit(android.content.pm.PackageInstaller.CommitCallback);
     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);
+    method public void setProgress(float);
   }
 
-  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.SessionCallback {
+    ctor public PackageInstaller.SessionCallback();
+    method public abstract void onCreated(int);
+    method public abstract void onFinished(int, boolean);
+    method public abstract void onProgressChanged(int, float);
   }
 
-  public static abstract class PackageInstaller.UninstallResultCallback {
-    ctor public PackageInstaller.UninstallResultCallback();
+  public static abstract class PackageInstaller.UninstallCallback {
+    ctor public PackageInstaller.UninstallCallback();
     method public abstract void onFailure(java.lang.String);
     method public abstract void onSuccess();
   }
@@ -8682,7 +8686,6 @@
     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);
@@ -8692,6 +8695,7 @@
     method public android.content.pm.PackageInfo getPackageArchiveInfo(java.lang.String, int);
     method public abstract int[] getPackageGids(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
     method public abstract android.content.pm.PackageInfo getPackageInfo(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException;
+    method public abstract android.content.pm.PackageInstaller getPackageInstaller();
     method public abstract java.lang.String[] getPackagesForUid(int);
     method public abstract java.util.List<android.content.pm.PackageInfo> getPackagesHoldingPermissions(java.lang.String[], int);
     method public abstract android.content.pm.PermissionGroupInfo getPermissionGroupInfo(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException;
@@ -16230,8 +16234,8 @@
     method public void notifyChildrenChanged(android.net.Uri);
     method public android.os.IBinder onBind(android.content.Intent);
     method public abstract android.media.browse.MediaBrowserService.BrowserRoot onGetRoot(java.lang.String, int, android.os.Bundle);
-    method protected abstract void onLoadChildren(android.net.Uri, android.media.browse.MediaBrowserService.Result<java.util.List<android.media.browse.MediaBrowserItem>>);
-    method protected abstract void onLoadThumbnail(android.net.Uri, int, int, android.media.browse.MediaBrowserService.Result<android.graphics.Bitmap>);
+    method public abstract void onLoadChildren(android.net.Uri, android.media.browse.MediaBrowserService.Result<java.util.List<android.media.browse.MediaBrowserItem>>);
+    method public abstract void onLoadThumbnail(android.net.Uri, int, int, android.media.browse.MediaBrowserService.Result<android.graphics.Bitmap>);
     method public void setSessionToken(android.media.session.MediaSession.Token);
     field public static final java.lang.String SERVICE_ACTION = "android.media.browse.MediaBrowserService";
   }
@@ -24343,6 +24347,7 @@
   }
 
   public static final class ContactsContract.CommonDataKinds.Event implements android.provider.ContactsContract.CommonDataKinds.CommonColumns android.provider.ContactsContract.DataColumnsWithJoins {
+    method public static final java.lang.CharSequence getTypeLabel(android.content.res.Resources, int, java.lang.CharSequence);
     method public static int getTypeResource(java.lang.Integer);
     field public static final java.lang.String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/contact_event";
     field public static final java.lang.String START_DATE = "data1";
@@ -30129,7 +30134,6 @@
     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);
@@ -30138,6 +30142,7 @@
     method public java.lang.String getNameForUid(int);
     method public int[] getPackageGids(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
     method public android.content.pm.PackageInfo getPackageInfo(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException;
+    method public android.content.pm.PackageInstaller getPackageInstaller();
     method public java.lang.String[] getPackagesForUid(int);
     method public java.util.List<android.content.pm.PackageInfo> getPackagesHoldingPermissions(java.lang.String[], int);
     method public android.content.pm.PermissionGroupInfo getPermissionGroupInfo(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException;
@@ -39749,8 +39754,6 @@
     method public void setLogoDescription(java.lang.CharSequence);
     method public void setNavigationContentDescription(java.lang.CharSequence);
     method public void setNavigationContentDescription(int);
-    method public void setNavigationDescription(int);
-    method public void setNavigationDescription(java.lang.CharSequence);
     method public void setNavigationIcon(int);
     method public void setNavigationIcon(android.graphics.drawable.Drawable);
     method public void setNavigationOnClickListener(android.view.View.OnClickListener);
diff --git a/cmds/pm/src/com/android/commands/pm/Pm.java b/cmds/pm/src/com/android/commands/pm/Pm.java
index faf5622..96019b3 100644
--- a/cmds/pm/src/com/android/commands/pm/Pm.java
+++ b/cmds/pm/src/com/android/commands/pm/Pm.java
@@ -27,11 +27,12 @@
 import android.content.pm.IPackageDeleteObserver;
 import android.content.pm.IPackageInstaller;
 import android.content.pm.IPackageManager;
+import android.content.pm.InstallSessionInfo;
 import android.content.pm.InstallSessionParams;
 import android.content.pm.InstrumentationInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageInstaller;
-import android.content.pm.PackageInstaller.CommitResultCallback;
+import android.content.pm.PackageInstaller.CommitCallback;
 import android.content.pm.PackageItemInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.ParceledListSlice;
@@ -157,8 +158,8 @@
             return;
         }
 
-        if ("install-destroy".equals(op)) {
-            runInstallDestroy();
+        if ("install-abandon".equals(op) || "install-destroy".equals(op)) {
+            runInstallAbandon();
             return;
         }
 
@@ -770,7 +771,7 @@
         }
     }
 
-    class LocalCommitResultCallback extends CommitResultCallback {
+    class LocalCommitCallback extends CommitCallback {
         boolean finished;
         boolean success;
         String msg;
@@ -790,7 +791,7 @@
         }
 
         @Override
-        public void onFailure(String msg) {
+        public void onFailure(int failureReason, String msg, Bundle extras) {
             setResult(false, msg);
         }
     }
@@ -996,10 +997,9 @@
     private void runInstallCreate() throws RemoteException {
         String installerPackageName = null;
 
-        final InstallSessionParams params = new InstallSessionParams();
+        final InstallSessionParams params = new InstallSessionParams(
+                InstallSessionParams.MODE_FULL_INSTALL);
         params.installFlags = PackageManager.INSTALL_ALL_USERS;
-        params.setModeFullInstall();
-        params.setProgressMax(-1);
 
         String opt;
         while ((opt = nextOption()) != null) {
@@ -1021,11 +1021,9 @@
             } else if (opt.equals("-d")) {
                 params.installFlags |= PackageManager.INSTALL_ALLOW_DOWNGRADE;
             } else if (opt.equals("-p")) {
-                params.setModeInheritExisting();
+                params.mode = InstallSessionParams.MODE_INHERIT_EXISTING;
             } else if (opt.equals("-S")) {
-                final long deltaSize = Long.parseLong(nextOptionData());
-                params.setDeltaSize(deltaSize);
-                params.setProgressMax((int) params.deltaSize);
+                params.setSize(Long.parseLong(nextOptionData()));
             } else if (opt.equals("--abi")) {
                 params.abiOverride = checkAbiArgument(nextOptionData());
             } else {
@@ -1033,7 +1031,7 @@
             }
         }
 
-        final int sessionId = mInstaller.createSession(installerPackageName, params,
+        final int sessionId = mInstaller.createSession(params, installerPackageName,
                 UserHandle.USER_OWNER);
 
         // NOTE: adb depends on parsing this string
@@ -1080,7 +1078,12 @@
 
             final int n = Streams.copy(in, out);
             session.fsync(out);
-            session.addProgress(n);
+
+            final InstallSessionInfo info = mInstaller.getSessionInfo(sessionId);
+            if (info.sizeBytes > 0) {
+                final float fraction = ((float) n / (float) info.sizeBytes);
+                session.addProgress(fraction);
+            }
 
             System.out.println("Success: streamed " + n + " bytes");
         } finally {
@@ -1097,7 +1100,7 @@
         try {
             session = new PackageInstaller.Session(mInstaller.openSession(sessionId));
 
-            final LocalCommitResultCallback callback = new LocalCommitResultCallback();
+            final LocalCommitCallback callback = new LocalCommitCallback();
             session.commit(callback);
 
             synchronized (callback) {
@@ -1118,13 +1121,13 @@
         }
     }
 
-    private void runInstallDestroy() throws RemoteException {
+    private void runInstallAbandon() throws RemoteException {
         final int sessionId = Integer.parseInt(nextArg());
 
         PackageInstaller.Session session = null;
         try {
             session = new PackageInstaller.Session(mInstaller.openSession(sessionId));
-            session.destroy();
+            session.abandon();
             System.out.println("Success");
         } finally {
             IoUtils.closeQuietly(session);
@@ -1743,7 +1746,7 @@
         System.err.println("       pm install-create [-lrtsfdp] [-i PACKAGE] [-S BYTES]");
         System.err.println("       pm install-write [-S BYTES] SESSION_ID SPLIT_NAME [PATH]");
         System.err.println("       pm install-commit SESSION_ID");
-        System.err.println("       pm install-destroy SESSION_ID");
+        System.err.println("       pm install-abandon SESSION_ID");
         System.err.println("       pm uninstall [-k] [--user USER_ID] PACKAGE");
         System.err.println("       pm set-installer PACKAGE INSTALLER");
         System.err.println("       pm clear [--user USER_ID] PACKAGE");
@@ -1813,7 +1816,7 @@
         System.err.println("    -S: size in bytes of package, required for stdin");
         System.err.println("");
         System.err.println("pm install-commit: perform install of fully staged session");
-        System.err.println("pm install-destroy: destroy session");
+        System.err.println("pm install-abandon: abandon session");
         System.err.println("");
         System.err.println("pm set-installer: set installer package name");
         System.err.println("");
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 1cb0fd4..68d4cf1 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -61,7 +61,10 @@
 import android.util.ArrayMap;
 import android.util.Log;
 import android.view.Display;
+
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.Preconditions;
+
 import dalvik.system.VMRuntime;
 
 import java.lang.ref.WeakReference;
@@ -74,13 +77,20 @@
     private final static boolean DEBUG = false;
     private final static boolean DEBUG_ICONS = false;
 
-    UserManager mUserManager;
+    private final Object mLock = new Object();
+
+    @GuardedBy("mLock")
+    private UserManager mUserManager;
+    @GuardedBy("mLock")
+    private PackageInstaller mInstaller;
 
     UserManager getUserManager() {
-        if (mUserManager == null) {
-            mUserManager = UserManager.get(mContext);
+        synchronized (mLock) {
+            if (mUserManager == null) {
+                mUserManager = UserManager.get(mContext);
+            }
+            return mUserManager;
         }
-        return mUserManager;
     }
 
     @Override
@@ -1543,12 +1553,17 @@
     }
 
     @Override
-    public PackageInstaller getInstaller() {
-        try {
-            return new PackageInstaller(this, mPM.getPackageInstaller(), mContext.getPackageName(),
-                    mContext.getUserId());
-        } catch (RemoteException e) {
-            throw e.rethrowAsRuntimeException();
+    public PackageInstaller getPackageInstaller() {
+        synchronized (mLock) {
+            if (mInstaller == null) {
+                try {
+                    mInstaller = new PackageInstaller(this, mPM.getPackageInstaller(),
+                            mContext.getPackageName(), mContext.getUserId());
+                } catch (RemoteException e) {
+                    throw e.rethrowAsRuntimeException();
+                }
+            }
+            return mInstaller;
         }
     }
 
diff --git a/core/java/android/app/usage/UsageStatsManagerInternal.java b/core/java/android/app/usage/UsageStatsManagerInternal.java
index d6345f3..0d41be2 100644
--- a/core/java/android/app/usage/UsageStatsManagerInternal.java
+++ b/core/java/android/app/usage/UsageStatsManagerInternal.java
@@ -29,11 +29,13 @@
      * Reports an event to the UsageStatsManager.
      *
      * @param component The component for which this event ocurred.
+     * @param userId The user id to which the component belongs to.
      * @param timeStamp The time at which this event ocurred.
      * @param eventType The event that occured. Valid values can be found at
      * {@link android.app.usage.UsageStats.Event}
      */
-    public abstract void reportEvent(ComponentName component, long timeStamp, int eventType);
+    public abstract void reportEvent(ComponentName component, int userId,
+            long timeStamp, int eventType);
 
     /**
      * Prepares the UsageStatsService for shutdown.
diff --git a/core/java/android/bluetooth/le/ScanSettings.java b/core/java/android/bluetooth/le/ScanSettings.java
index 2f86d09..b2ee6a8 100644
--- a/core/java/android/bluetooth/le/ScanSettings.java
+++ b/core/java/android/bluetooth/le/ScanSettings.java
@@ -21,38 +21,37 @@
 import android.os.Parcelable;
 
 /**
- * Bluetooth LE scan settings are passed to {@link BluetoothLeScanner#startScan}
- * to define the parameters for the scan.
+ * Bluetooth LE scan settings are passed to {@link BluetoothLeScanner#startScan} to define the
+ * parameters for the scan.
  */
 public final class ScanSettings implements Parcelable {
     /**
-     * Perform Bluetooth LE scan in low power mode.
-     * This is the default scan mode as it consumes the least power.
+     * Perform Bluetooth LE scan in low power mode. This is the default scan mode as it consumes the
+     * least power.
      */
     public static final int SCAN_MODE_LOW_POWER = 0;
 
     /**
-     * Perform Bluetooth LE scan in balanced power mode.
-     * Scan results are returned at a rate that provides a good trade-off between scan
-     * frequency and power consumption.
+     * Perform Bluetooth LE scan in balanced power mode. Scan results are returned at a rate that
+     * provides a good trade-off between scan frequency and power consumption.
      */
     public static final int SCAN_MODE_BALANCED = 1;
 
     /**
-     * Scan using highest duty cycle.
-     * It's recommended to only use this mode when the application is running in the foreground.
+     * Scan using highest duty cycle. It's recommended to only use this mode when the application is
+     * running in the foreground.
      */
     public static final int SCAN_MODE_LOW_LATENCY = 2;
 
     /**
-     * Trigger a callback for every Bluetooth advertisement found that matches the
-     * filter criteria. If no filter is active, all advertisement packets are reported.
+     * Trigger a callback for every Bluetooth advertisement found that matches the filter criteria.
+     * If no filter is active, all advertisement packets are reported.
      */
     public static final int CALLBACK_TYPE_ALL_MATCHES = 1;
 
     /**
-     * A result callback is only triggered for the first advertisement packet received that
-     * matches the filter criteria.
+     * A result callback is only triggered for the first advertisement packet received that matches
+     * the filter criteria.
      */
     public static final int CALLBACK_TYPE_FIRST_MATCH = 2;
 
@@ -63,15 +62,17 @@
     public static final int CALLBACK_TYPE_MATCH_LOST = 4;
 
     /**
-     * Request full scan results which contain the device, rssi, advertising data, scan response
-     * as well as the scan timestamp.
+     * Request full scan results which contain the device, rssi, advertising data, scan response as
+     * well as the scan timestamp.
      */
     public static final int SCAN_RESULT_TYPE_FULL = 0;
 
     /**
      * Request abbreviated scan results which contain the device, rssi and scan timestamp.
-     * <p><b>Note:</b> It is possible for an application to get more scan results than
-     * it asked for, if there are multiple apps using this type.
+     * <p>
+     * <b>Note:</b> It is possible for an application to get more scan results than it asked for, if
+     * there are multiple apps using this type.
+     *
      * @hide
      */
     @SystemApi
@@ -109,11 +110,11 @@
     }
 
     private ScanSettings(int scanMode, int callbackType, int scanResultType,
-            long reportDelaySeconds) {
+            long reportDelayMillis) {
         mScanMode = scanMode;
         mCallbackType = callbackType;
         mScanResultType = scanResultType;
-        mReportDelayMillis = reportDelaySeconds;
+        mReportDelayMillis = reportDelayMillis;
     }
 
     private ScanSettings(Parcel in) {
@@ -184,15 +185,24 @@
          * @throws IllegalArgumentException If the {@code callbackType} is invalid.
          */
         public Builder setCallbackType(int callbackType) {
-            if (callbackType < CALLBACK_TYPE_ALL_MATCHES
-             || callbackType > (CALLBACK_TYPE_FIRST_MATCH | CALLBACK_TYPE_MATCH_LOST)
-             || (callbackType & CALLBACK_TYPE_ALL_MATCHES) != CALLBACK_TYPE_ALL_MATCHES) {
+
+            if (!isValidCallbackType(callbackType)) {
                 throw new IllegalArgumentException("invalid callback type - " + callbackType);
             }
             mCallbackType = callbackType;
             return this;
         }
 
+        // Returns true if the callbackType is valid.
+        private boolean isValidCallbackType(int callbackType) {
+            if (callbackType == CALLBACK_TYPE_ALL_MATCHES ||
+                    callbackType == CALLBACK_TYPE_FIRST_MATCH ||
+                    callbackType == CALLBACK_TYPE_MATCH_LOST) {
+                return true;
+            }
+            return callbackType == (CALLBACK_TYPE_FIRST_MATCH | CALLBACK_TYPE_MATCH_LOST);
+        }
+
         /**
          * Set scan result type for Bluetooth LE scan.
          *
@@ -215,16 +225,15 @@
 
         /**
          * Set report delay timestamp for Bluetooth LE scan.
-         * @param reportDelayMillis Set to 0 to be notified of results immediately.
-         *                           Values &gt; 0 causes the scan results to be queued
-         *                           up and delivered after the requested delay or when
-         *                           the internal buffers fill up.
-         * @throws IllegalArgumentException If {@code reportDelaySeconds} &lt; 0.
          *
+         * @param reportDelayMillis Set to 0 to be notified of results immediately. Values &gt; 0
+         *            causes the scan results to be queued up and delivered after the requested
+         *            delay or when the internal buffers fill up.
+         * @throws IllegalArgumentException If {@code reportDelayMillis} &lt; 0.
          */
         public Builder setReportDelayMillis(long reportDelayMillis) {
             if (reportDelayMillis < 0) {
-                throw new IllegalArgumentException("reportDelaySeconds must be > 0");
+                throw new IllegalArgumentException("reportDelayMillis must be > 0");
             }
             mReportDelayMillis = reportDelayMillis;
             return this;
diff --git a/core/java/android/content/pm/IPackageInstaller.aidl b/core/java/android/content/pm/IPackageInstaller.aidl
index 32460c9..0c65034 100644
--- a/core/java/android/content/pm/IPackageInstaller.aidl
+++ b/core/java/android/content/pm/IPackageInstaller.aidl
@@ -17,7 +17,7 @@
 package android.content.pm;
 
 import android.content.pm.IPackageDeleteObserver;
-import android.content.pm.IPackageInstallerObserver;
+import android.content.pm.IPackageInstallerCallback;
 import android.content.pm.IPackageInstallerSession;
 import android.content.pm.InstallSessionInfo;
 import android.content.pm.InstallSessionParams;
@@ -25,13 +25,15 @@
 
 /** {@hide} */
 interface IPackageInstaller {
-    int createSession(String installerPackageName, in InstallSessionParams params, int userId);
+    int createSession(in InstallSessionParams params, String installerPackageName, int userId);
     IPackageInstallerSession openSession(int sessionId);
 
-    List<InstallSessionInfo> getSessions(int userId);
+    InstallSessionInfo getSessionInfo(int sessionId);
+    List<InstallSessionInfo> getAllSessions(int userId);
+    List<InstallSessionInfo> getMySessions(String installerPackageName, int userId);
 
-    void registerObserver(IPackageInstallerObserver observer, int userId);
-    void unregisterObserver(IPackageInstallerObserver observer, int userId);
+    void registerCallback(IPackageInstallerCallback callback, int userId);
+    void unregisterCallback(IPackageInstallerCallback callback);
 
     void uninstall(String packageName, int flags, in IPackageDeleteObserver observer, int userId);
     void uninstallSplit(String packageName, String splitName, int flags, in IPackageDeleteObserver observer, int userId);
diff --git a/core/java/android/content/pm/IPackageInstallerObserver.aidl b/core/java/android/content/pm/IPackageInstallerCallback.aidl
similarity index 77%
rename from core/java/android/content/pm/IPackageInstallerObserver.aidl
rename to core/java/android/content/pm/IPackageInstallerCallback.aidl
index 85660e4..a31ae54 100644
--- a/core/java/android/content/pm/IPackageInstallerObserver.aidl
+++ b/core/java/android/content/pm/IPackageInstallerCallback.aidl
@@ -16,11 +16,9 @@
 
 package android.content.pm;
 
-import android.content.pm.InstallSessionInfo;
-
 /** {@hide} */
-oneway interface IPackageInstallerObserver {
-    void onSessionCreated(in InstallSessionInfo info);
-    void onSessionProgress(int sessionId, int progress);
+oneway interface IPackageInstallerCallback {
+    void onSessionCreated(int sessionId);
+    void onSessionProgressChanged(int sessionId, float progress);
     void onSessionFinished(int sessionId, boolean success);
 }
diff --git a/core/java/android/content/pm/IPackageInstallerSession.aidl b/core/java/android/content/pm/IPackageInstallerSession.aidl
index d6775d4..2fd7ddb 100644
--- a/core/java/android/content/pm/IPackageInstallerSession.aidl
+++ b/core/java/android/content/pm/IPackageInstallerSession.aidl
@@ -21,11 +21,12 @@
 
 /** {@hide} */
 interface IPackageInstallerSession {
-    void setClientProgress(int progress);
-    void addClientProgress(int progress);
+    void setClientProgress(float progress);
+    void addClientProgress(float progress);
 
     ParcelFileDescriptor openWrite(String name, long offsetBytes, long lengthBytes);
 
-    void install(in IPackageInstallObserver2 observer);
-    void destroy();
+    void close();
+    void commit(in IPackageInstallObserver2 observer);
+    void abandon();
 }
diff --git a/core/java/android/content/pm/InstallSessionInfo.java b/core/java/android/content/pm/InstallSessionInfo.java
index 3336727..a9c574a 100644
--- a/core/java/android/content/pm/InstallSessionInfo.java
+++ b/core/java/android/content/pm/InstallSessionInfo.java
@@ -16,6 +16,7 @@
 
 package android.content.pm;
 
+import android.annotation.Nullable;
 import android.graphics.Bitmap;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -30,16 +31,18 @@
     /** {@hide} */
     public String installerPackageName;
     /** {@hide} */
-    public int progress;
+    public float progress;
 
     /** {@hide} */
     public int mode;
     /** {@hide} */
-    public String packageName;
+    public long sizeBytes;
     /** {@hide} */
-    public Bitmap icon;
+    public String appPackageName;
     /** {@hide} */
-    public CharSequence title;
+    public Bitmap appIcon;
+    /** {@hide} */
+    public CharSequence appLabel;
 
     /** {@hide} */
     public InstallSessionInfo() {
@@ -49,12 +52,13 @@
     public InstallSessionInfo(Parcel source) {
         sessionId = source.readInt();
         installerPackageName = source.readString();
-        progress = source.readInt();
+        progress = source.readFloat();
 
         mode = source.readInt();
-        packageName = source.readString();
-        icon = source.readParcelable(null);
-        title = source.readString();
+        sizeBytes = source.readLong();
+        appPackageName = source.readString();
+        appIcon = source.readParcelable(null);
+        appLabel = source.readString();
     }
 
     /**
@@ -67,19 +71,19 @@
     /**
      * Return the package name of the app that owns this session.
      */
-    public String getInstallerPackageName() {
+    public @Nullable String getInstallerPackageName() {
         return installerPackageName;
     }
 
     /**
-     * Return current overall progress of this session, between 0 and 100.
+     * Return current overall progress of this session, between 0 and 1.
      * <p>
      * Note that this progress may not directly correspond to the value reported
-     * by {@link PackageInstaller.Session#setProgress(int)}, as the system may
+     * by {@link PackageInstaller.Session#setProgress(float)}, as the system may
      * carve out a portion of the overall progress to represent its own internal
      * installation work.
      */
-    public int getProgress() {
+    public float getProgress() {
         return progress;
     }
 
@@ -87,24 +91,24 @@
      * Return the package name this session is working with. May be {@code null}
      * if unknown.
      */
-    public String getPackageName() {
-        return packageName;
+    public @Nullable String getAppPackageName() {
+        return appPackageName;
     }
 
     /**
      * Return an icon representing the app being installed. May be {@code null}
      * if unavailable.
      */
-    public Bitmap getIcon() {
-        return icon;
+    public @Nullable Bitmap getAppIcon() {
+        return appIcon;
     }
 
     /**
-     * Return a title representing the app being installed. May be {@code null}
+     * Return a label representing the app being installed. May be {@code null}
      * if unavailable.
      */
-    public CharSequence getTitle() {
-        return title;
+    public @Nullable CharSequence getAppLabel() {
+        return appLabel;
     }
 
     @Override
@@ -116,12 +120,13 @@
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeInt(sessionId);
         dest.writeString(installerPackageName);
-        dest.writeInt(progress);
+        dest.writeFloat(progress);
 
         dest.writeInt(mode);
-        dest.writeString(packageName);
-        dest.writeParcelable(icon, flags);
-        dest.writeString(title != null ? title.toString() : null);
+        dest.writeLong(sizeBytes);
+        dest.writeString(appPackageName);
+        dest.writeParcelable(appIcon, flags);
+        dest.writeString(appLabel != null ? appLabel.toString() : null);
     }
 
     public static final Parcelable.Creator<InstallSessionInfo>
diff --git a/core/java/android/content/pm/InstallSessionParams.java b/core/java/android/content/pm/InstallSessionParams.java
index e039699..3de9863 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.annotation.Nullable;
 import android.content.Intent;
 import android.graphics.Bitmap;
 import android.net.Uri;
@@ -29,17 +30,25 @@
  */
 public class InstallSessionParams implements Parcelable {
 
-    // TODO: extend to support remaining VerificationParams
-
-    /** {@hide} */
-    public static final int MODE_INVALID = 0;
-    /** {@hide} */
+    /**
+     * Mode for an install session whose staged APKs should fully replace any
+     * existing APKs for the target app.
+     */
     public static final int MODE_FULL_INSTALL = 1;
-    /** {@hide} */
+
+    /**
+     * Mode for an install session that should inherit any existing APKs for the
+     * target app, unless they have been explicitly overridden (based on split
+     * name) by the session. For example, this can be used to add one or more
+     * split APKs to an existing installation.
+     * <p>
+     * If there are no existing APKs for the target app, this behaves like
+     * {@link #MODE_FULL_INSTALL}.
+     */
     public static final int MODE_INHERIT_EXISTING = 2;
 
     /** {@hide} */
-    public int mode = MODE_INVALID;
+    public int mode;
     /** {@hide} */
     public int installFlags;
     /** {@hide} */
@@ -47,15 +56,13 @@
     /** {@hide} */
     public Signature[] signatures;
     /** {@hide} */
-    public long deltaSize = -1;
+    public long sizeBytes = -1;
     /** {@hide} */
-    public int progressMax = 100;
+    public String appPackageName;
     /** {@hide} */
-    public String packageName;
+    public Bitmap appIcon;
     /** {@hide} */
-    public Bitmap icon;
-    /** {@hide} */
-    public CharSequence title;
+    public CharSequence appLabel;
     /** {@hide} */
     public Uri originatingUri;
     /** {@hide} */
@@ -63,7 +70,15 @@
     /** {@hide} */
     public String abiOverride;
 
-    public InstallSessionParams() {
+    /**
+     * Construct parameters for a new package install session.
+     *
+     * @param mode one of {@link #MODE_FULL_INSTALL} or
+     *            {@link #MODE_INHERIT_EXISTING} describing how the session
+     *            should interact with an existing app.
+     */
+    public InstallSessionParams(int mode) {
+        this.mode = mode;
     }
 
     /** {@hide} */
@@ -72,34 +87,16 @@
         installFlags = source.readInt();
         installLocation = source.readInt();
         signatures = (Signature[]) source.readParcelableArray(null);
-        deltaSize = source.readLong();
-        progressMax = source.readInt();
-        packageName = source.readString();
-        icon = source.readParcelable(null);
-        title = source.readString();
+        sizeBytes = source.readLong();
+        appPackageName = source.readString();
+        appIcon = source.readParcelable(null);
+        appLabel = source.readString();
         originatingUri = source.readParcelable(null);
         referrerUri = source.readParcelable(null);
         abiOverride = source.readString();
     }
 
     /**
-     * Set session mode indicating that it should fully replace any existing
-     * APKs for this application.
-     */
-    public void setModeFullInstall() {
-        this.mode = MODE_FULL_INSTALL;
-    }
-
-    /**
-     * 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}.
@@ -109,56 +106,57 @@
     }
 
     /**
-     * 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.
+     * Optionally provide a set of certificates for the app being installed.
+     * <p>
+     * If the APKs staged in the session aren't consistent with these
+     * signatures, the install will fail. Regardless of this value, all APKs in
+     * the app must have the same signing certificates.
+     *
+     * @see PackageInfo#signatures
      */
-    public void setSignatures(Signature[] signatures) {
+    public void setSignatures(@Nullable 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.
+     * Optionally indicate the total size (in bytes) of all APKs that will be
+     * delivered in this session. The system may use this to ensure enough disk
+     * space exists before proceeding, or to estimate container size for
+     * installations living on external storage.
      *
-     * @see PackageInstaller.Session#setProgress(int)
+     * @see PackageInfo#INSTALL_LOCATION_AUTO
+     * @see PackageInfo#INSTALL_LOCATION_PREFER_EXTERNAL
      */
-    public void setProgressMax(int progressMax) {
-        this.progressMax = progressMax;
+    public void setSize(long sizeBytes) {
+        this.sizeBytes = sizeBytes;
     }
 
     /**
-     * Optionally set the package name this session will be working with. It's
-     * strongly recommended that you provide this value when known.
+     * Optionally set the package name of the app being installed. It's strongly
+     * recommended that you provide this value when known, so that observers can
+     * communicate installing apps to users.
+     * <p>
+     * If the APKs staged in the session aren't consistent with this package
+     * name, the install will fail. Regardless of this value, all APKs in the
+     * app must have the same package name.
      */
-    public void setPackageName(String packageName) {
-        this.packageName = packageName;
+    public void setAppPackageName(@Nullable String appPackageName) {
+        this.appPackageName = appPackageName;
     }
 
     /**
-     * Optionally set an icon representing the app being installed.
+     * Optionally set an icon representing the app being installed. This should
+     * be at least {@link android.R.dimen#app_icon_size} in both dimensions.
      */
-    public void setIcon(Bitmap icon) {
-        this.icon = icon;
+    public void setAppIcon(@Nullable Bitmap appIcon) {
+        this.appIcon = appIcon;
     }
 
     /**
-     * Optionally set a title representing the app being installed.
+     * Optionally set a label representing the app being installed.
      */
-    public void setTitle(CharSequence title) {
-        this.title = title;
+    public void setAppLabel(@Nullable CharSequence appLabel) {
+        this.appLabel = appLabel;
     }
 
     /**
@@ -167,7 +165,7 @@
      *
      * @see Intent#EXTRA_ORIGINATING_URI
      */
-    public void setOriginatingUri(Uri originatingUri) {
+    public void setOriginatingUri(@Nullable Uri originatingUri) {
         this.originatingUri = originatingUri;
     }
 
@@ -177,7 +175,7 @@
      *
      * @see Intent#EXTRA_REFERRER
      */
-    public void setReferrerUri(Uri referrerUri) {
+    public void setReferrerUri(@Nullable Uri referrerUri) {
         this.referrerUri = referrerUri;
     }
 
@@ -187,11 +185,10 @@
         pw.printHexPair("installFlags", installFlags);
         pw.printPair("installLocation", installLocation);
         pw.printPair("signatures", (signatures != null));
-        pw.printPair("deltaSize", deltaSize);
-        pw.printPair("progressMax", progressMax);
-        pw.printPair("packageName", packageName);
-        pw.printPair("icon", (icon != null));
-        pw.printPair("title", title);
+        pw.printPair("sizeBytes", sizeBytes);
+        pw.printPair("appPackageName", appPackageName);
+        pw.printPair("appIcon", (appIcon != null));
+        pw.printPair("appLabel", appLabel);
         pw.printPair("originatingUri", originatingUri);
         pw.printPair("referrerUri", referrerUri);
         pw.printPair("abiOverride", abiOverride);
@@ -209,11 +206,10 @@
         dest.writeInt(installFlags);
         dest.writeInt(installLocation);
         dest.writeParcelableArray(signatures, flags);
-        dest.writeLong(deltaSize);
-        dest.writeInt(progressMax);
-        dest.writeString(packageName);
-        dest.writeParcelable(icon, flags);
-        dest.writeString(title != null ? title.toString() : null);
+        dest.writeLong(sizeBytes);
+        dest.writeString(appPackageName);
+        dest.writeParcelable(appIcon, flags);
+        dest.writeString(appLabel != null ? appLabel.toString() : null);
         dest.writeParcelable(originatingUri, flags);
         dest.writeParcelable(referrerUri, flags);
         dest.writeString(abiOverride);
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index df82d26..a114bb8 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -16,11 +16,16 @@
 
 package android.content.pm;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.PackageInstallObserver;
 import android.app.PackageUninstallObserver;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.os.Bundle;
 import android.os.FileBridge;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
 import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
 import android.util.ExceptionUtils;
@@ -28,6 +33,8 @@
 import java.io.Closeable;
 import java.io.IOException;
 import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Iterator;
 import java.util.List;
 
 /**
@@ -61,6 +68,8 @@
     private final int mUserId;
     private final String mInstallerPackageName;
 
+    private final ArrayList<SessionCallbackDelegate> mDelegates = new ArrayList<>();
+
     /** {@hide} */
     public PackageInstaller(PackageManager pm, IPackageInstaller installer,
             String installerPackageName, int userId) {
@@ -71,28 +80,6 @@
     }
 
     /**
-     * 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) {
-        return mPm.isPackageAvailable(packageName);
-    }
-
-    /** {@hide} */
-    public void installAvailablePackage(String packageName, PackageInstallObserver observer) {
-        int returnCode;
-        try {
-            returnCode = mPm.installExistingPackage(packageName);
-        } catch (NameNotFoundException e) {
-            returnCode = PackageManager.INSTALL_FAILED_PACKAGE_CHANGED;
-        }
-        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.
@@ -104,9 +91,9 @@
      * @throws IOException if parameters were unsatisfiable, such as lack of
      *             disk space or unavailable media.
      */
-    public int createSession(InstallSessionParams params) throws IOException {
+    public int createSession(@NonNull InstallSessionParams params) throws IOException {
         try {
-            return mInstaller.createSession(mInstallerPackageName, params, mUserId);
+            return mInstaller.createSession(params, mInstallerPackageName, mUserId);
         } catch (RuntimeException e) {
             ExceptionUtils.maybeUnwrapIOException(e);
             throw e;
@@ -116,9 +103,10 @@
     }
 
     /**
-     * Open an existing session to actively perform work.
+     * Open an existing session to actively perform work. To succeed, the caller
+     * must be the owner of the install session.
      */
-    public Session openSession(int sessionId) {
+    public @NonNull Session openSession(int sessionId) {
         try {
             return new Session(mInstaller.openSession(sessionId));
         } catch (RemoteException e) {
@@ -127,13 +115,35 @@
     }
 
     /**
-     * Return list of all active install sessions on the device.
+     * Return details for a specific session. To succeed, the caller must either
+     * own this session, or be the current home app.
      */
-    public List<InstallSessionInfo> getActiveSessions() {
-        // TODO: filter based on caller
-        // TODO: let launcher app see all active sessions
+    public @Nullable InstallSessionInfo getSessionInfo(int sessionId) {
         try {
-            return mInstaller.getSessions(mUserId);
+            return mInstaller.getSessionInfo(sessionId);
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
+    }
+
+    /**
+     * Return list of all active install sessions, regardless of the installer.
+     * To succeed, the caller must be the current home app.
+     */
+    public @NonNull List<InstallSessionInfo> getAllSessions() {
+        try {
+            return mInstaller.getAllSessions(mUserId);
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
+    }
+
+    /**
+     * Return list of all install sessions owned by the calling app.
+     */
+    public @NonNull List<InstallSessionInfo> getMySessions() {
+        try {
+            return mInstaller.getMySessions(mInstallerPackageName, mUserId);
         } catch (RemoteException e) {
             throw e.rethrowAsRuntimeException();
         }
@@ -144,10 +154,10 @@
      * method is only available to the current "installer of record" for the
      * package.
      */
-    public void uninstall(String packageName, UninstallResultCallback callback) {
+    public void uninstall(@NonNull String packageName, @NonNull UninstallCallback callback) {
         try {
             mInstaller.uninstall(packageName, 0,
-                    new UninstallResultCallbackDelegate(callback).getBinder(), mUserId);
+                    new UninstallCallbackDelegate(callback).getBinder(), mUserId);
         } catch (RemoteException e) {
             throw e.rethrowAsRuntimeException();
         }
@@ -158,10 +168,11 @@
      *
      * @hide
      */
-    public void uninstall(String packageName, String splitName, UninstallResultCallback callback) {
+    public void uninstall(@NonNull String packageName, @NonNull String splitName,
+            @NonNull UninstallCallback callback) {
         try {
             mInstaller.uninstallSplit(packageName, splitName, 0,
-                    new UninstallResultCallbackDelegate(callback).getBinder(), mUserId);
+                    new UninstallCallbackDelegate(callback).getBinder(), mUserId);
         } catch (RemoteException e) {
             throw e.rethrowAsRuntimeException();
         }
@@ -170,69 +181,121 @@
     /**
      * Events for observing session lifecycle.
      */
-    public static abstract class SessionObserver {
-        private final IPackageInstallerObserver.Stub mBinder = new IPackageInstallerObserver.Stub() {
-            @Override
-            public void onSessionCreated(InstallSessionInfo info) {
-                SessionObserver.this.onCreated(info);
-            }
-
-            @Override
-            public void onSessionProgress(int sessionId, int progress) {
-                SessionObserver.this.onProgress(sessionId, progress);
-            }
-
-            @Override
-            public void onSessionFinished(int sessionId, boolean success) {
-                SessionObserver.this.onFinalized(sessionId, success);
-            }
-        };
-
-        /** {@hide} */
-        public IPackageInstallerObserver getBinder() {
-            return mBinder;
-        }
-
+    public static abstract class SessionCallback {
         /**
          * New session has been created.
          */
-        public abstract void onCreated(InstallSessionInfo info);
+        public abstract void onCreated(int sessionId);
 
         /**
          * 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.
+         * reported by {@link PackageInstaller.Session#setProgress(float)}, 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 onProgressChanged(int sessionId, float progress);
 
         /**
-         * Session has been finalized, either with success or failure.
+         * Session has completely finished, either with success or failure.
          */
-        public abstract void onFinalized(int sessionId, boolean success);
+        public abstract void onFinished(int sessionId, boolean success);
     }
 
-    /**
-     * Register to watch for session lifecycle events.
-     */
-    public void registerSessionObserver(SessionObserver observer) {
-        try {
-            mInstaller.registerObserver(observer.getBinder(), mUserId);
-        } catch (RemoteException e) {
-            throw e.rethrowAsRuntimeException();
+    /** {@hide} */
+    private static class SessionCallbackDelegate extends IPackageInstallerCallback.Stub implements
+            Handler.Callback {
+        private static final int MSG_SESSION_CREATED = 1;
+        private static final int MSG_SESSION_PROGRESS_CHANGED = 2;
+        private static final int MSG_SESSION_FINISHED = 3;
+
+        final SessionCallback mCallback;
+        final Handler mHandler;
+
+        public SessionCallbackDelegate(SessionCallback callback, Looper looper) {
+            mCallback = callback;
+            mHandler = new Handler(looper, this);
+        }
+
+        @Override
+        public boolean handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_SESSION_CREATED:
+                    mCallback.onCreated(msg.arg1);
+                    return true;
+                case MSG_SESSION_PROGRESS_CHANGED:
+                    mCallback.onProgressChanged(msg.arg1, (float) msg.obj);
+                    return true;
+                case MSG_SESSION_FINISHED:
+                    mCallback.onFinished(msg.arg1, msg.arg2 != 0);
+                    return true;
+            }
+            return false;
+        }
+
+        @Override
+        public void onSessionCreated(int sessionId) {
+            mHandler.obtainMessage(MSG_SESSION_CREATED, sessionId, 0).sendToTarget();
+        }
+
+        @Override
+        public void onSessionProgressChanged(int sessionId, float progress) {
+            mHandler.obtainMessage(MSG_SESSION_PROGRESS_CHANGED, sessionId, 0, progress)
+                    .sendToTarget();
+        }
+
+        @Override
+        public void onSessionFinished(int sessionId, boolean success) {
+            mHandler.obtainMessage(MSG_SESSION_FINISHED, sessionId, success ? 1 : 0)
+                    .sendToTarget();
         }
     }
 
     /**
-     * Unregister an existing observer.
+     * Register to watch for session lifecycle events. To succeed, the caller
+     * must be the current home app.
      */
-    public void unregisterSessionObserver(SessionObserver observer) {
-        try {
-            mInstaller.unregisterObserver(observer.getBinder(), mUserId);
-        } catch (RemoteException e) {
-            throw e.rethrowAsRuntimeException();
+    public void addSessionCallback(@NonNull SessionCallback callback) {
+        addSessionCallback(callback, new Handler());
+    }
+
+    /**
+     * Register to watch for session lifecycle events. To succeed, the caller
+     * must be the current home app.
+     *
+     * @param handler to dispatch callback events through, otherwise uses
+     *            calling thread.
+     */
+    public void addSessionCallback(@NonNull SessionCallback callback, @NonNull Handler handler) {
+        synchronized (mDelegates) {
+            final SessionCallbackDelegate delegate = new SessionCallbackDelegate(callback,
+                    handler.getLooper());
+            try {
+                mInstaller.registerCallback(delegate, mUserId);
+            } catch (RemoteException e) {
+                throw e.rethrowAsRuntimeException();
+            }
+            mDelegates.add(delegate);
+        }
+    }
+
+    /**
+     * Unregister an existing callback.
+     */
+    public void removeSessionCallback(@NonNull SessionCallback callback) {
+        synchronized (mDelegates) {
+            for (Iterator<SessionCallbackDelegate> i = mDelegates.iterator(); i.hasNext();) {
+                final SessionCallbackDelegate delegate = i.next();
+                if (delegate.mCallback == callback) {
+                    try {
+                        mInstaller.unregisterCallback(delegate);
+                    } catch (RemoteException e) {
+                        throw e.rethrowAsRuntimeException();
+                    }
+                    i.remove();
+                }
+            }
         }
     }
 
@@ -244,9 +307,9 @@
      * A session may contain any number of split packages. If the application
      * does not yet exist, this session must include a base package.
      * <p>
-     * If a package included in this session is already defined by the existing
-     * installation (for example, the same split name), the package in this
-     * session will replace the existing package.
+     * If an APK included in this session is already defined by the existing
+     * installation (for example, the same split name), the APK in this session
+     * will replace the existing APK.
      */
     public static class Session implements Closeable {
         private IPackageInstallerSession mSession;
@@ -257,10 +320,9 @@
         }
 
         /**
-         * Set current progress. Valid values are anywhere between 0 and
-         * {@link InstallSessionParams#setProgressMax(int)}.
+         * Set current progress. Valid values are anywhere between 0 and 1.
          */
-        public void setProgress(int progress) {
+        public void setProgress(float progress) {
             try {
                 mSession.setClientProgress(progress);
             } catch (RemoteException e) {
@@ -269,7 +331,7 @@
         }
 
         /** {@hide} */
-        public void addProgress(int progress) {
+        public void addProgress(float progress) {
             try {
                 mSession.addClientProgress(progress);
             } catch (RemoteException e) {
@@ -278,15 +340,33 @@
         }
 
         /**
-         * Open an APK file for writing, starting at the given offset. You can
-         * then stream data into the file, periodically calling
-         * {@link #fsync(OutputStream)} to ensure bytes have been written to
-         * disk.
+         * Open a stream to write an APK file into the session.
+         * <p>
+         * The returned stream will start writing data at the requested offset
+         * in the underlying file, which can be used to resume a partially
+         * written file. If a valid file length is specified, the system will
+         * preallocate the underlying disk space to optimize placement on disk.
+         * It's strongly recommended to provide a valid file length when known.
+         * <p>
+         * You can write data into the returned stream, optionally call
+         * {@link #fsync(OutputStream)} as needed to ensure bytes have been
+         * persisted to disk, and then close when finished. All streams must be
+         * closed before calling {@link #commit(CommitCallback)}.
+         *
+         * @param name arbitrary, unique name of your choosing to identify the
+         *            APK being written. You can open a file again for
+         *            additional writes (such as after a reboot) by using the
+         *            same name. This name is only meaningful within the context
+         *            of a single install session.
+         * @param offsetBytes offset into the file to begin writing at, or 0 to
+         *            start at the beginning of the file.
+         * @param lengthBytes total size of the file being written, used to
+         *            preallocate the underlying disk space, or -1 if unknown.
          */
-        public OutputStream openWrite(String splitName, long offsetBytes, long lengthBytes)
-                throws IOException {
+        public @NonNull OutputStream openWrite(@NonNull String name, long offsetBytes,
+                long lengthBytes) throws IOException {
             try {
-                final ParcelFileDescriptor clientSocket = mSession.openWrite(splitName,
+                final ParcelFileDescriptor clientSocket = mSession.openWrite(name,
                         offsetBytes, lengthBytes);
                 return new FileBridge.FileBridgeOutputStream(clientSocket.getFileDescriptor());
             } catch (RuntimeException e) {
@@ -302,7 +382,7 @@
          * to disk. This is only valid for streams returned from
          * {@link #openWrite(String, long, long)}.
          */
-        public void fsync(OutputStream out) throws IOException {
+        public void fsync(@NonNull OutputStream out) throws IOException {
             if (out instanceof FileBridge.FileBridgeOutputStream) {
                 ((FileBridge.FileBridgeOutputStream) out).fsync();
             } else {
@@ -319,9 +399,9 @@
          * on the session. If the device reboots before the session has been
          * finalized, you may commit the session again.
          */
-        public void commit(CommitResultCallback callback) {
+        public void commit(@NonNull CommitCallback callback) {
             try {
-                mSession.install(new CommitResultCallbackDelegate(callback).getBinder());
+                mSession.commit(new CommitCallbackDelegate(callback).getBinder());
             } catch (RemoteException e) {
                 throw e.rethrowAsRuntimeException();
             }
@@ -333,15 +413,20 @@
          */
         @Override
         public void close() {
-            // No resources to release at the moment
+            try {
+                mSession.close();
+            } catch (RemoteException e) {
+                throw e.rethrowAsRuntimeException();
+            }
         }
 
         /**
-         * Completely destroy this session, rendering it invalid.
+         * Completely abandon this session, destroying all staged data and
+         * rendering it invalid.
          */
-        public void destroy() {
+        public void abandon() {
             try {
-                mSession.destroy();
+                mSession.abandon();
             } catch (RemoteException e) {
                 throw e.rethrowAsRuntimeException();
             }
@@ -351,30 +436,26 @@
     /**
      * Final result of an uninstall request.
      */
-    public static abstract class UninstallResultCallback {
+    public static abstract class UninstallCallback {
         public abstract void onSuccess();
         public abstract void onFailure(String msg);
     }
 
     /** {@hide} */
-    private static class UninstallResultCallbackDelegate extends PackageUninstallObserver {
-        private final UninstallResultCallback target;
+    private static class UninstallCallbackDelegate extends PackageUninstallObserver {
+        private final UninstallCallback target;
 
-        public UninstallResultCallbackDelegate(UninstallResultCallback target) {
+        public UninstallCallbackDelegate(UninstallCallback target) {
             this.target = target;
         }
 
         @Override
         public void onUninstallFinished(String basePackageName, int returnCode) {
-            final String msg = null;
-
-            switch (returnCode) {
-                case PackageManager.DELETE_SUCCEEDED: target.onSuccess(); break;
-                case PackageManager.DELETE_FAILED_INTERNAL_ERROR: target.onFailure("DELETE_FAILED_INTERNAL_ERROR: " + msg); break;
-                case PackageManager.DELETE_FAILED_DEVICE_POLICY_MANAGER: target.onFailure("DELETE_FAILED_DEVICE_POLICY_MANAGER: " + msg); break;
-                case PackageManager.DELETE_FAILED_USER_RESTRICTED: target.onFailure("DELETE_FAILED_USER_RESTRICTED: " + msg); break;
-                case PackageManager.DELETE_FAILED_OWNER_BLOCKED: target.onFailure("DELETE_FAILED_OWNER_BLOCKED: " + msg); break;
-                default: target.onFailure(msg); break;
+            if (returnCode == PackageManager.DELETE_SUCCEEDED) {
+                target.onSuccess();
+            } else {
+                final String msg = PackageManager.deleteStatusToString(returnCode);
+                target.onFailure(msg);
             }
         }
     }
@@ -382,16 +463,13 @@
     /**
      * Final result of a session commit request.
      */
-    public static abstract class CommitResultCallback {
-        public abstract void onSuccess();
-
+    public static abstract class CommitCallback {
         /**
-         * 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.
+         * Generic unknown failure. The system will always try to provide a more
+         * specific failure reason, but in some rare cases this may be
+         * delivered.
          */
-        public abstract void onFailure(String msg);
+        public static final int FAILURE_UNKNOWN = 0;
 
         /**
          * One or more of the APKs included in the session was invalid. For
@@ -399,23 +477,19 @@
          * mismatched, etc. The installer may want to try downloading and
          * installing again.
          */
-        public void onFailureInvalid(String msg) {
-            onFailure(msg);
-        }
+        public static final int FAILURE_INVALID = 1;
 
         /**
          * 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}.
+         * <p>
+         * The extras bundle may contain {@link #EXTRA_PACKAGE_NAME} if one
+         * specific package was identified as the cause of the conflict. If
+         * unknown, or multiple packages, the extra may be {@code null}.
          */
-        public void onFailureConflict(String msg, String otherPackageName) {
-            onFailure(msg);
-        }
+        public static final int FAILURE_CONFLICT = 2;
 
         /**
          * This install session failed due to storage issues. For example,
@@ -423,9 +497,7 @@
          * media may be unavailable. The user may be able to help free space
          * or insert the correct media.
          */
-        public void onFailureStorage(String msg) {
-            onFailure(msg);
-        }
+        public static final int FAILURE_STORAGE = 3;
 
         /**
          * This install session is fundamentally incompatible with this
@@ -434,66 +506,37 @@
          * ABI, or it requires a newer SDK version, etc. This install would
          * never succeed.
          */
-        public void onFailureIncompatible(String msg) {
-            onFailure(msg);
-        }
+        public static final int FAILURE_INCOMPATIBLE = 4;
+
+        public static final String EXTRA_PACKAGE_NAME = "android.content.pm.extra.PACKAGE_NAME";
+
+        public abstract void onSuccess();
+        public abstract void onFailure(int failureReason, String msg, Bundle extras);
     }
 
     /** {@hide} */
-    private static class CommitResultCallbackDelegate extends PackageInstallObserver {
-        private final CommitResultCallback target;
+    private static class CommitCallbackDelegate extends PackageInstallObserver {
+        private final CommitCallback target;
 
-        public CommitResultCallbackDelegate(CommitResultCallback target) {
+        public CommitCallbackDelegate(CommitCallback target) {
             this.target = target;
         }
 
         @Override
         public void packageInstalled(String basePackageName, Bundle extras, int returnCode,
                 String msg) {
-            final String otherPackage = null;
+            if (returnCode == PackageManager.INSTALL_SUCCEEDED) {
+                target.onSuccess();
+            } else {
+                final int failureReason = PackageManager.installStatusToFailureReason(returnCode);
+                msg = PackageManager.installStatusToString(returnCode) + ": " + msg;
 
-            switch (returnCode) {
-                case PackageManager.INSTALL_SUCCEEDED: target.onSuccess(); break;
-                case PackageManager.INSTALL_FAILED_ALREADY_EXISTS: target.onFailureConflict("INSTALL_FAILED_ALREADY_EXISTS: " + msg, otherPackage); break;
-                case PackageManager.INSTALL_FAILED_INVALID_APK: target.onFailureInvalid("INSTALL_FAILED_INVALID_APK: " + msg); break;
-                case PackageManager.INSTALL_FAILED_INVALID_URI: target.onFailureInvalid("INSTALL_FAILED_INVALID_URI: " + msg); break;
-                case PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE: target.onFailureStorage("INSTALL_FAILED_INSUFFICIENT_STORAGE: " + msg); break;
-                case PackageManager.INSTALL_FAILED_DUPLICATE_PACKAGE: target.onFailureConflict("INSTALL_FAILED_DUPLICATE_PACKAGE: " + msg, otherPackage); break;
-                case PackageManager.INSTALL_FAILED_NO_SHARED_USER: target.onFailureConflict("INSTALL_FAILED_NO_SHARED_USER: " + msg, otherPackage); break;
-                case PackageManager.INSTALL_FAILED_UPDATE_INCOMPATIBLE: target.onFailureConflict("INSTALL_FAILED_UPDATE_INCOMPATIBLE: " + msg, otherPackage); break;
-                case PackageManager.INSTALL_FAILED_SHARED_USER_INCOMPATIBLE: target.onFailureConflict("INSTALL_FAILED_SHARED_USER_INCOMPATIBLE: " + msg, otherPackage); break;
-                case PackageManager.INSTALL_FAILED_MISSING_SHARED_LIBRARY: target.onFailureIncompatible("INSTALL_FAILED_MISSING_SHARED_LIBRARY: " + msg); break;
-                case PackageManager.INSTALL_FAILED_REPLACE_COULDNT_DELETE: target.onFailureConflict("INSTALL_FAILED_REPLACE_COULDNT_DELETE: " + msg, otherPackage); break;
-                case PackageManager.INSTALL_FAILED_DEXOPT: target.onFailureInvalid("INSTALL_FAILED_DEXOPT: " + msg); break;
-                case PackageManager.INSTALL_FAILED_OLDER_SDK: target.onFailureIncompatible("INSTALL_FAILED_OLDER_SDK: " + msg); break;
-                case PackageManager.INSTALL_FAILED_CONFLICTING_PROVIDER: target.onFailureConflict("INSTALL_FAILED_CONFLICTING_PROVIDER: " + msg, otherPackage); break;
-                case PackageManager.INSTALL_FAILED_NEWER_SDK: target.onFailureIncompatible("INSTALL_FAILED_NEWER_SDK: " + msg); break;
-                case PackageManager.INSTALL_FAILED_TEST_ONLY: target.onFailureInvalid("INSTALL_FAILED_TEST_ONLY: " + msg); break;
-                case PackageManager.INSTALL_FAILED_CPU_ABI_INCOMPATIBLE: target.onFailureIncompatible("INSTALL_FAILED_CPU_ABI_INCOMPATIBLE: " + msg); break;
-                case PackageManager.INSTALL_FAILED_MISSING_FEATURE: target.onFailureIncompatible("INSTALL_FAILED_MISSING_FEATURE: " + msg); break;
-                case PackageManager.INSTALL_FAILED_CONTAINER_ERROR: target.onFailureStorage("INSTALL_FAILED_CONTAINER_ERROR: " + msg); break;
-                case PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION: target.onFailureStorage("INSTALL_FAILED_INVALID_INSTALL_LOCATION: " + msg); break;
-                case PackageManager.INSTALL_FAILED_MEDIA_UNAVAILABLE: target.onFailureStorage("INSTALL_FAILED_MEDIA_UNAVAILABLE: " + msg); break;
-                case PackageManager.INSTALL_FAILED_VERIFICATION_TIMEOUT: target.onFailure("INSTALL_FAILED_VERIFICATION_TIMEOUT: " + msg); break;
-                case PackageManager.INSTALL_FAILED_VERIFICATION_FAILURE: target.onFailure("INSTALL_FAILED_VERIFICATION_FAILURE: " + msg); break;
-                case PackageManager.INSTALL_FAILED_PACKAGE_CHANGED: target.onFailureInvalid("INSTALL_FAILED_PACKAGE_CHANGED: " + msg); break;
-                case PackageManager.INSTALL_FAILED_UID_CHANGED: target.onFailureInvalid("INSTALL_FAILED_UID_CHANGED: " + msg); break;
-                case PackageManager.INSTALL_FAILED_VERSION_DOWNGRADE: target.onFailureInvalid("INSTALL_FAILED_VERSION_DOWNGRADE: " + msg); break;
-                case PackageManager.INSTALL_PARSE_FAILED_NOT_APK: target.onFailureInvalid("INSTALL_PARSE_FAILED_NOT_APK: " + msg); break;
-                case PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST: target.onFailureInvalid("INSTALL_PARSE_FAILED_BAD_MANIFEST: " + msg); break;
-                case PackageManager.INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION: target.onFailureInvalid("INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION: " + msg); break;
-                case PackageManager.INSTALL_PARSE_FAILED_NO_CERTIFICATES: target.onFailureInvalid("INSTALL_PARSE_FAILED_NO_CERTIFICATES: " + msg); break;
-                case PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES: target.onFailureInvalid("INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES: " + msg); break;
-                case PackageManager.INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING: target.onFailureInvalid("INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING: " + msg); break;
-                case PackageManager.INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME: target.onFailureInvalid("INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME: " + msg); break;
-                case PackageManager.INSTALL_PARSE_FAILED_BAD_SHARED_USER_ID: target.onFailureInvalid("INSTALL_PARSE_FAILED_BAD_SHARED_USER_ID: " + msg); break;
-                case PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED: target.onFailureInvalid("INSTALL_PARSE_FAILED_MANIFEST_MALFORMED: " + msg); break;
-                case PackageManager.INSTALL_PARSE_FAILED_MANIFEST_EMPTY: target.onFailureInvalid("INSTALL_PARSE_FAILED_MANIFEST_EMPTY: " + msg); break;
-                case PackageManager.INSTALL_FAILED_INTERNAL_ERROR: target.onFailure("INSTALL_FAILED_INTERNAL_ERROR: " + msg); break;
-                case PackageManager.INSTALL_FAILED_USER_RESTRICTED: target.onFailureIncompatible("INSTALL_FAILED_USER_RESTRICTED: " + msg); break;
-                case PackageManager.INSTALL_FAILED_DUPLICATE_PERMISSION: target.onFailureConflict("INSTALL_FAILED_DUPLICATE_PERMISSION: " + msg, otherPackage); break;
-                case PackageManager.INSTALL_FAILED_NO_MATCHING_ABIS: target.onFailureInvalid("INSTALL_FAILED_NO_MATCHING_ABIS: " + msg); break;
-                default: target.onFailure(msg); break;
+                if (extras != null) {
+                    extras.putString(CommitCallback.EXTRA_PACKAGE_NAME,
+                            extras.getString(PackageManager.EXTRA_FAILURE_EXISTING_PACKAGE));
+                }
+
+                target.onFailure(failureReason, msg, extras);
             }
         }
     }
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 8b6ae41..62611efa 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -17,6 +17,7 @@
 package android.content.pm;
 
 import android.annotation.IntDef;
+import android.annotation.NonNull;
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
 import android.annotation.SystemApi;
@@ -26,6 +27,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.IntentSender;
+import android.content.pm.PackageInstaller.CommitCallback;
 import android.content.pm.PackageParser.PackageParserException;
 import android.content.res.Resources;
 import android.content.res.XmlResourceParser;
@@ -3723,7 +3725,7 @@
      * Return interface that offers the ability to install, upgrade, and remove
      * applications on the device.
      */
-    public abstract PackageInstaller getInstaller();
+    public abstract @NonNull PackageInstaller getPackageInstaller();
 
     /**
      * Returns the data directory for a particular user and package, given the uid of the package.
@@ -3772,4 +3774,109 @@
 
     /** {@hide} */
     public abstract boolean isPackageAvailable(String packageName);
+
+    /** {@hide} */
+    public static String installStatusToString(int status) {
+        switch (status) {
+            case INSTALL_SUCCEEDED: return "INSTALL_SUCCEEDED";
+            case INSTALL_FAILED_ALREADY_EXISTS: return "INSTALL_FAILED_ALREADY_EXISTS";
+            case INSTALL_FAILED_INVALID_APK: return "INSTALL_FAILED_INVALID_APK";
+            case INSTALL_FAILED_INVALID_URI: return "INSTALL_FAILED_INVALID_URI";
+            case INSTALL_FAILED_INSUFFICIENT_STORAGE: return "INSTALL_FAILED_INSUFFICIENT_STORAGE";
+            case INSTALL_FAILED_DUPLICATE_PACKAGE: return "INSTALL_FAILED_DUPLICATE_PACKAGE";
+            case INSTALL_FAILED_NO_SHARED_USER: return "INSTALL_FAILED_NO_SHARED_USER";
+            case INSTALL_FAILED_UPDATE_INCOMPATIBLE: return "INSTALL_FAILED_UPDATE_INCOMPATIBLE";
+            case INSTALL_FAILED_SHARED_USER_INCOMPATIBLE: return "INSTALL_FAILED_SHARED_USER_INCOMPATIBLE";
+            case INSTALL_FAILED_MISSING_SHARED_LIBRARY: return "INSTALL_FAILED_MISSING_SHARED_LIBRARY";
+            case INSTALL_FAILED_REPLACE_COULDNT_DELETE: return "INSTALL_FAILED_REPLACE_COULDNT_DELETE";
+            case INSTALL_FAILED_DEXOPT: return "INSTALL_FAILED_DEXOPT";
+            case INSTALL_FAILED_OLDER_SDK: return "INSTALL_FAILED_OLDER_SDK";
+            case INSTALL_FAILED_CONFLICTING_PROVIDER: return "INSTALL_FAILED_CONFLICTING_PROVIDER";
+            case INSTALL_FAILED_NEWER_SDK: return "INSTALL_FAILED_NEWER_SDK";
+            case INSTALL_FAILED_TEST_ONLY: return "INSTALL_FAILED_TEST_ONLY";
+            case INSTALL_FAILED_CPU_ABI_INCOMPATIBLE: return "INSTALL_FAILED_CPU_ABI_INCOMPATIBLE";
+            case INSTALL_FAILED_MISSING_FEATURE: return "INSTALL_FAILED_MISSING_FEATURE";
+            case INSTALL_FAILED_CONTAINER_ERROR: return "INSTALL_FAILED_CONTAINER_ERROR";
+            case INSTALL_FAILED_INVALID_INSTALL_LOCATION: return "INSTALL_FAILED_INVALID_INSTALL_LOCATION";
+            case INSTALL_FAILED_MEDIA_UNAVAILABLE: return "INSTALL_FAILED_MEDIA_UNAVAILABLE";
+            case INSTALL_FAILED_VERIFICATION_TIMEOUT: return "INSTALL_FAILED_VERIFICATION_TIMEOUT";
+            case INSTALL_FAILED_VERIFICATION_FAILURE: return "INSTALL_FAILED_VERIFICATION_FAILURE";
+            case INSTALL_FAILED_PACKAGE_CHANGED: return "INSTALL_FAILED_PACKAGE_CHANGED";
+            case INSTALL_FAILED_UID_CHANGED: return "INSTALL_FAILED_UID_CHANGED";
+            case INSTALL_FAILED_VERSION_DOWNGRADE: return "INSTALL_FAILED_VERSION_DOWNGRADE";
+            case INSTALL_PARSE_FAILED_NOT_APK: return "INSTALL_PARSE_FAILED_NOT_APK";
+            case INSTALL_PARSE_FAILED_BAD_MANIFEST: return "INSTALL_PARSE_FAILED_BAD_MANIFEST";
+            case INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION: return "INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION";
+            case INSTALL_PARSE_FAILED_NO_CERTIFICATES: return "INSTALL_PARSE_FAILED_NO_CERTIFICATES";
+            case INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES: return "INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES";
+            case INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING: return "INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING";
+            case INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME: return "INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME";
+            case INSTALL_PARSE_FAILED_BAD_SHARED_USER_ID: return "INSTALL_PARSE_FAILED_BAD_SHARED_USER_ID";
+            case INSTALL_PARSE_FAILED_MANIFEST_MALFORMED: return "INSTALL_PARSE_FAILED_MANIFEST_MALFORMED";
+            case INSTALL_PARSE_FAILED_MANIFEST_EMPTY: return "INSTALL_PARSE_FAILED_MANIFEST_EMPTY";
+            case INSTALL_FAILED_INTERNAL_ERROR: return "INSTALL_FAILED_INTERNAL_ERROR";
+            case INSTALL_FAILED_USER_RESTRICTED: return "INSTALL_FAILED_USER_RESTRICTED";
+            case INSTALL_FAILED_DUPLICATE_PERMISSION: return "INSTALL_FAILED_DUPLICATE_PERMISSION";
+            case INSTALL_FAILED_NO_MATCHING_ABIS: return "INSTALL_FAILED_NO_MATCHING_ABIS";
+            default: return Integer.toString(status);
+        }
+    }
+
+    /** {@hide} */
+    public static int installStatusToFailureReason(int status) {
+        switch (status) {
+            case INSTALL_FAILED_ALREADY_EXISTS: return CommitCallback.FAILURE_CONFLICT;
+            case INSTALL_FAILED_INVALID_APK: return CommitCallback.FAILURE_INVALID;
+            case INSTALL_FAILED_INVALID_URI: return CommitCallback.FAILURE_INVALID;
+            case INSTALL_FAILED_INSUFFICIENT_STORAGE: return CommitCallback.FAILURE_STORAGE;
+            case INSTALL_FAILED_DUPLICATE_PACKAGE: return CommitCallback.FAILURE_CONFLICT;
+            case INSTALL_FAILED_NO_SHARED_USER: return CommitCallback.FAILURE_CONFLICT;
+            case INSTALL_FAILED_UPDATE_INCOMPATIBLE: return CommitCallback.FAILURE_CONFLICT;
+            case INSTALL_FAILED_SHARED_USER_INCOMPATIBLE: return CommitCallback.FAILURE_CONFLICT;
+            case INSTALL_FAILED_MISSING_SHARED_LIBRARY: return CommitCallback.FAILURE_INCOMPATIBLE;
+            case INSTALL_FAILED_REPLACE_COULDNT_DELETE: return CommitCallback.FAILURE_CONFLICT;
+            case INSTALL_FAILED_DEXOPT: return CommitCallback.FAILURE_INVALID;
+            case INSTALL_FAILED_OLDER_SDK: return CommitCallback.FAILURE_INCOMPATIBLE;
+            case INSTALL_FAILED_CONFLICTING_PROVIDER: return CommitCallback.FAILURE_CONFLICT;
+            case INSTALL_FAILED_NEWER_SDK: return CommitCallback.FAILURE_INCOMPATIBLE;
+            case INSTALL_FAILED_TEST_ONLY: return CommitCallback.FAILURE_INVALID;
+            case INSTALL_FAILED_CPU_ABI_INCOMPATIBLE: return CommitCallback.FAILURE_INCOMPATIBLE;
+            case INSTALL_FAILED_MISSING_FEATURE: return CommitCallback.FAILURE_INCOMPATIBLE;
+            case INSTALL_FAILED_CONTAINER_ERROR: return CommitCallback.FAILURE_STORAGE;
+            case INSTALL_FAILED_INVALID_INSTALL_LOCATION: return CommitCallback.FAILURE_STORAGE;
+            case INSTALL_FAILED_MEDIA_UNAVAILABLE: return CommitCallback.FAILURE_STORAGE;
+            case INSTALL_FAILED_VERIFICATION_TIMEOUT: return CommitCallback.FAILURE_UNKNOWN;
+            case INSTALL_FAILED_VERIFICATION_FAILURE: return CommitCallback.FAILURE_UNKNOWN;
+            case INSTALL_FAILED_PACKAGE_CHANGED: return CommitCallback.FAILURE_INVALID;
+            case INSTALL_FAILED_UID_CHANGED: return CommitCallback.FAILURE_INVALID;
+            case INSTALL_FAILED_VERSION_DOWNGRADE: return CommitCallback.FAILURE_INVALID;
+            case INSTALL_PARSE_FAILED_NOT_APK: return CommitCallback.FAILURE_INVALID;
+            case INSTALL_PARSE_FAILED_BAD_MANIFEST: return CommitCallback.FAILURE_INVALID;
+            case INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION: return CommitCallback.FAILURE_INVALID;
+            case INSTALL_PARSE_FAILED_NO_CERTIFICATES: return CommitCallback.FAILURE_INVALID;
+            case INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES: return CommitCallback.FAILURE_INVALID;
+            case INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING: return CommitCallback.FAILURE_INVALID;
+            case INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME: return CommitCallback.FAILURE_INVALID;
+            case INSTALL_PARSE_FAILED_BAD_SHARED_USER_ID: return CommitCallback.FAILURE_INVALID;
+            case INSTALL_PARSE_FAILED_MANIFEST_MALFORMED: return CommitCallback.FAILURE_INVALID;
+            case INSTALL_PARSE_FAILED_MANIFEST_EMPTY: return CommitCallback.FAILURE_INVALID;
+            case INSTALL_FAILED_INTERNAL_ERROR: return CommitCallback.FAILURE_UNKNOWN;
+            case INSTALL_FAILED_USER_RESTRICTED: return CommitCallback.FAILURE_INCOMPATIBLE;
+            case INSTALL_FAILED_DUPLICATE_PERMISSION: return CommitCallback.FAILURE_CONFLICT;
+            case INSTALL_FAILED_NO_MATCHING_ABIS: return CommitCallback.FAILURE_INCOMPATIBLE;
+            default: return CommitCallback.FAILURE_UNKNOWN;
+        }
+    }
+
+    /** {@hide} */
+    public static String deleteStatusToString(int status) {
+        switch (status) {
+            case DELETE_SUCCEEDED: return "DELETE_SUCCEEDED";
+            case DELETE_FAILED_INTERNAL_ERROR: return "DELETE_FAILED_INTERNAL_ERROR";
+            case DELETE_FAILED_DEVICE_POLICY_MANAGER: return "DELETE_FAILED_DEVICE_POLICY_MANAGER";
+            case DELETE_FAILED_USER_RESTRICTED: return "DELETE_FAILED_USER_RESTRICTED";
+            case DELETE_FAILED_OWNER_BLOCKED: return "DELETE_FAILED_OWNER_BLOCKED";
+            default: return Integer.toString(status);
+        }
+    }
 }
diff --git a/core/java/android/hardware/camera2/legacy/CameraDeviceState.java b/core/java/android/hardware/camera2/legacy/CameraDeviceState.java
index ab7e844..2fa9d85 100644
--- a/core/java/android/hardware/camera2/legacy/CameraDeviceState.java
+++ b/core/java/android/hardware/camera2/legacy/CameraDeviceState.java
@@ -65,7 +65,7 @@
         void onError(int errorCode, RequestHolder holder);
         void onConfiguring();
         void onIdle();
-        void onCaptureStarted(RequestHolder holder);
+        void onCaptureStarted(RequestHolder holder, long timestamp);
         void onCaptureResult(CameraMetadataNative result, RequestHolder holder);
     }
 
@@ -125,11 +125,12 @@
      * </p>
      *
      * @param request A {@link RequestHolder} containing the request for the current capture.
+     * @param timestamp The timestamp of the capture start in nanoseconds.
      * @return {@link CameraBinderDecorator#NO_ERROR}, or an error if one has occurred.
      */
-    public synchronized int setCaptureStart(final RequestHolder request) {
+    public synchronized int setCaptureStart(final RequestHolder request, long timestamp) {
         mCurrentRequest = request;
-        doStateTransition(STATE_CAPTURING);
+        doStateTransition(STATE_CAPTURING, timestamp);
         return mCurrentError;
     }
 
@@ -180,6 +181,10 @@
     }
 
     private void doStateTransition(int newState) {
+        doStateTransition(newState, /*timestamp*/0);
+    }
+
+    private void doStateTransition(int newState, final long timestamp) {
         if (DEBUG) {
             if (newState != mCurrentState) {
                 Log.d(TAG, "Transitioning to state " + newState);
@@ -250,7 +255,7 @@
                     mCurrentHandler.post(new Runnable() {
                         @Override
                         public void run() {
-                            mCurrentListener.onCaptureStarted(mCurrentRequest);
+                            mCurrentListener.onCaptureStarted(mCurrentRequest, timestamp);
                         }
                     });
                 }
diff --git a/core/java/android/hardware/camera2/legacy/CaptureCollector.java b/core/java/android/hardware/camera2/legacy/CaptureCollector.java
new file mode 100644
index 0000000..a9834d0
--- /dev/null
+++ b/core/java/android/hardware/camera2/legacy/CaptureCollector.java
@@ -0,0 +1,453 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.hardware.camera2.legacy;
+
+import android.hardware.camera2.impl.CameraMetadataNative;
+import android.util.Log;
+import android.util.Pair;
+
+import java.util.ArrayDeque;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * Collect timestamps and state for each {@link CaptureRequest} as it passes through
+ * the Legacy camera pipeline.
+ */
+public class CaptureCollector {
+    private static final String TAG = "CaptureCollector";
+
+    private static final boolean DEBUG = Log.isLoggable(LegacyCameraDevice.DEBUG_PROP, Log.DEBUG);
+
+    private static final int FLAG_RECEIVED_JPEG = 1;
+    private static final int FLAG_RECEIVED_JPEG_TS = 2;
+    private static final int FLAG_RECEIVED_PREVIEW = 4;
+    private static final int FLAG_RECEIVED_PREVIEW_TS = 8;
+    private static final int FLAG_RECEIVED_ALL_JPEG = FLAG_RECEIVED_JPEG | FLAG_RECEIVED_JPEG_TS;
+    private static final int FLAG_RECEIVED_ALL_PREVIEW = FLAG_RECEIVED_PREVIEW |
+            FLAG_RECEIVED_PREVIEW_TS;
+
+    private static final int MAX_JPEGS_IN_FLIGHT = 1;
+
+    private class CaptureHolder {
+        private final RequestHolder mRequest;
+        private final LegacyRequest mLegacy;
+        public final boolean needsJpeg;
+        public final boolean needsPreview;
+
+        private long mTimestamp = 0;
+        private int mReceivedFlags = 0;
+        private boolean mHasStarted = false;
+
+        public CaptureHolder(RequestHolder request, LegacyRequest legacyHolder) {
+            mRequest = request;
+            mLegacy = legacyHolder;
+            needsJpeg = request.hasJpegTargets();
+            needsPreview = request.hasPreviewTargets();
+        }
+
+        public boolean isPreviewCompleted() {
+            return (mReceivedFlags & FLAG_RECEIVED_ALL_PREVIEW) == FLAG_RECEIVED_ALL_PREVIEW;
+        }
+
+        public  boolean isJpegCompleted() {
+            return (mReceivedFlags & FLAG_RECEIVED_ALL_JPEG) == FLAG_RECEIVED_ALL_JPEG;
+        }
+
+        public boolean isCompleted() {
+            return (needsJpeg == isJpegCompleted()) && (needsPreview == isPreviewCompleted());
+        }
+
+        public void tryComplete() {
+            if (needsPreview && isPreviewCompleted()) {
+                CaptureCollector.this.onPreviewCompleted();
+            }
+            if (isCompleted()) {
+                CaptureCollector.this.onRequestCompleted(mRequest, mLegacy, mTimestamp);
+            }
+        }
+
+        public void setJpegTimestamp(long timestamp) {
+            if (DEBUG) {
+                Log.d(TAG, "setJpegTimestamp - called for request " + mRequest.getRequestId());
+            }
+            if (!needsJpeg) {
+                throw new IllegalStateException(
+                        "setJpegTimestamp called for capture with no jpeg targets.");
+            }
+            if (isCompleted()) {
+                throw new IllegalStateException(
+                        "setJpegTimestamp called on already completed request.");
+            }
+
+            mReceivedFlags |= FLAG_RECEIVED_JPEG_TS;
+
+            if (mTimestamp == 0) {
+                mTimestamp = timestamp;
+            }
+
+            if (!mHasStarted) {
+                mHasStarted = true;
+                CaptureCollector.this.mDeviceState.setCaptureStart(mRequest, mTimestamp);
+            }
+
+            tryComplete();
+        }
+
+        public void setJpegProduced() {
+            if (DEBUG) {
+                Log.d(TAG, "setJpegProduced - called for request " + mRequest.getRequestId());
+            }
+            if (!needsJpeg) {
+                throw new IllegalStateException(
+                        "setJpegProduced called for capture with no jpeg targets.");
+            }
+            if (isCompleted()) {
+                throw new IllegalStateException(
+                        "setJpegProduced called on already completed request.");
+            }
+
+            mReceivedFlags |= FLAG_RECEIVED_JPEG;
+            tryComplete();
+        }
+
+        public void setPreviewTimestamp(long timestamp) {
+            if (DEBUG) {
+                Log.d(TAG, "setPreviewTimestamp - called for request " + mRequest.getRequestId());
+            }
+            if (!needsPreview) {
+                throw new IllegalStateException(
+                        "setPreviewTimestamp called for capture with no preview targets.");
+            }
+            if (isCompleted()) {
+                throw new IllegalStateException(
+                        "setPreviewTimestamp called on already completed request.");
+            }
+
+            mReceivedFlags |= FLAG_RECEIVED_PREVIEW_TS;
+
+            if (mTimestamp == 0) {
+                mTimestamp = timestamp;
+            }
+
+            if (!needsJpeg) {
+                if (!mHasStarted) {
+                    mHasStarted = true;
+                    CaptureCollector.this.mDeviceState.setCaptureStart(mRequest, mTimestamp);
+                }
+            }
+
+            tryComplete();
+        }
+
+        public void setPreviewProduced() {
+            if (DEBUG) {
+                Log.d(TAG, "setPreviewProduced - called for request " + mRequest.getRequestId());
+            }
+            if (!needsPreview) {
+                throw new IllegalStateException(
+                        "setPreviewProduced called for capture with no preview targets.");
+            }
+            if (isCompleted()) {
+                throw new IllegalStateException(
+                        "setPreviewProduced called on already completed request.");
+            }
+
+            mReceivedFlags |= FLAG_RECEIVED_PREVIEW;
+            tryComplete();
+        }
+    }
+
+    private final ArrayDeque<CaptureHolder> mJpegCaptureQueue;
+    private final ArrayDeque<CaptureHolder> mJpegProduceQueue;
+    private final ArrayDeque<CaptureHolder> mPreviewCaptureQueue;
+    private final ArrayDeque<CaptureHolder> mPreviewProduceQueue;
+
+    private final ReentrantLock mLock = new ReentrantLock();
+    private final Condition mIsEmpty;
+    private final Condition mPreviewsEmpty;
+    private final Condition mNotFull;
+    private final CameraDeviceState mDeviceState;
+    private final LegacyResultMapper mMapper = new LegacyResultMapper();
+    private int mInFlight = 0;
+    private int mInFlightPreviews = 0;
+    private final int mMaxInFlight;
+
+    /**
+     * Create a new {@link CaptureCollector} that can modify the given {@link CameraDeviceState}.
+     *
+     * @param maxInFlight max allowed in-flight requests.
+     * @param deviceState the {@link CameraDeviceState} to update as requests are processed.
+     */
+    public CaptureCollector(int maxInFlight, CameraDeviceState deviceState) {
+        mMaxInFlight = maxInFlight;
+        mJpegCaptureQueue = new ArrayDeque<>(MAX_JPEGS_IN_FLIGHT);
+        mJpegProduceQueue = new ArrayDeque<>(MAX_JPEGS_IN_FLIGHT);
+        mPreviewCaptureQueue = new ArrayDeque<>(mMaxInFlight);
+        mPreviewProduceQueue = new ArrayDeque<>(mMaxInFlight);
+        mIsEmpty = mLock.newCondition();
+        mNotFull = mLock.newCondition();
+        mPreviewsEmpty = mLock.newCondition();
+        mDeviceState = deviceState;
+    }
+
+    /**
+     * Queue a new request.
+     *
+     * <p>
+     * For requests that use the Camera1 API preview output stream, this will block if there are
+     * already {@code maxInFlight} requests in progress (until at least one prior request has
+     * completed). For requests that use the Camera1 API jpeg callbacks, this will block until
+     * all prior requests have been completed to avoid stopping preview for
+     * {@link android.hardware.Camera#takePicture} before prior preview requests have been
+     * completed.
+     * </p>
+     * @param holder the {@link RequestHolder} for this request.
+     * @param legacy the {@link LegacyRequest} for this request; this will not be mutated.
+     * @param timeout a timeout to use for this call.
+     * @param unit the units to use for the timeout.
+     * @return {@code false} if this method timed out.
+     * @throws InterruptedException if this thread is interrupted.
+     */
+    public boolean queueRequest(RequestHolder holder, LegacyRequest legacy, long timeout,
+                                TimeUnit unit)
+            throws InterruptedException {
+        CaptureHolder h = new CaptureHolder(holder, legacy);
+        long nanos = unit.toNanos(timeout);
+        final ReentrantLock lock = this.mLock;
+        lock.lock();
+        try {
+            if (DEBUG) {
+                Log.d(TAG, "queueRequest  for request " + holder.getRequestId() +
+                        " - " + mInFlight + " requests remain in flight.");
+            }
+            if (h.needsJpeg) {
+                // Wait for all current requests to finish before queueing jpeg.
+                while (mInFlight > 0) {
+                    if (nanos <= 0) {
+                        return false;
+                    }
+                    nanos = mIsEmpty.awaitNanos(nanos);
+                }
+                mJpegCaptureQueue.add(h);
+                mJpegProduceQueue.add(h);
+            }
+            if (h.needsPreview) {
+                while (mInFlight >= mMaxInFlight) {
+                    if (nanos <= 0) {
+                        return false;
+                    }
+                    nanos = mNotFull.awaitNanos(nanos);
+                }
+                mPreviewCaptureQueue.add(h);
+                mPreviewProduceQueue.add(h);
+                mInFlightPreviews++;
+            }
+
+            if (!(h.needsJpeg || h.needsPreview)) {
+                throw new IllegalStateException("Request must target at least one output surface!");
+            }
+
+            mInFlight++;
+            return true;
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    /**
+     * Wait all queued requests to complete.
+     *
+     * @param timeout a timeout to use for this call.
+     * @param unit the units to use for the timeout.
+     * @return {@code false} if this method timed out.
+     * @throws InterruptedException if this thread is interrupted.
+     */
+    public boolean waitForEmpty(long timeout, TimeUnit unit) throws InterruptedException {
+        long nanos = unit.toNanos(timeout);
+        final ReentrantLock lock = this.mLock;
+        lock.lock();
+        try {
+            while (mInFlight > 0) {
+                if (nanos <= 0) {
+                    return false;
+                }
+                nanos = mIsEmpty.awaitNanos(nanos);
+            }
+            return true;
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    /**
+     * Wait all queued requests that use the Camera1 API preview output to complete.
+     *
+     * @param timeout a timeout to use for this call.
+     * @param unit the units to use for the timeout.
+     * @return {@code false} if this method timed out.
+     * @throws InterruptedException if this thread is interrupted.
+     */
+    public boolean waitForPreviewsEmpty(long timeout, TimeUnit unit) throws InterruptedException {
+        long nanos = unit.toNanos(timeout);
+        final ReentrantLock lock = this.mLock;
+        lock.lock();
+        try {
+            while (mInFlightPreviews > 0) {
+                if (nanos <= 0) {
+                    return false;
+                }
+                nanos = mPreviewsEmpty.awaitNanos(nanos);
+            }
+            return true;
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    /**
+     * Called to alert the {@link CaptureCollector} that the jpeg capture has begun.
+     *
+     * @param timestamp the time of the jpeg capture.
+     * @return the {@link RequestHolder} for the request associated with this capture.
+     */
+    public RequestHolder jpegCaptured(long timestamp) {
+        final ReentrantLock lock = this.mLock;
+        lock.lock();
+        try {
+            CaptureHolder h = mJpegCaptureQueue.poll();
+            if (h == null) {
+                Log.w(TAG, "jpegCaptured called with no jpeg request on queue!");
+                return null;
+            }
+            h.setJpegTimestamp(timestamp);
+            return h.mRequest;
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    /**
+     * Called to alert the {@link CaptureCollector} that the jpeg capture has completed.
+     *
+     * @return a pair containing the {@link RequestHolder} and the timestamp of the capture.
+     */
+    public Pair<RequestHolder, Long> jpegProduced() {
+        final ReentrantLock lock = this.mLock;
+        lock.lock();
+        try {
+            CaptureHolder h = mJpegProduceQueue.poll();
+            if (h == null) {
+                Log.w(TAG, "jpegProduced called with no jpeg request on queue!");
+                return null;
+            }
+            h.setJpegProduced();
+            return new Pair<>(h.mRequest, h.mTimestamp);
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    /**
+     * Check if there are any pending capture requests that use the Camera1 API preview output.
+     *
+     * @return {@code true} if there are pending preview requests.
+     */
+    public boolean hasPendingPreviewCaptures() {
+        final ReentrantLock lock = this.mLock;
+        lock.lock();
+        try {
+            return !mPreviewCaptureQueue.isEmpty();
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    /**
+     * Called to alert the {@link CaptureCollector} that the preview capture has begun.
+     *
+     * @param timestamp the time of the preview capture.
+     * @return a pair containing the {@link RequestHolder} and the timestamp of the capture.
+     */
+    public Pair<RequestHolder, Long> previewCaptured(long timestamp) {
+        final ReentrantLock lock = this.mLock;
+        lock.lock();
+        try {
+            CaptureHolder h = mPreviewCaptureQueue.poll();
+            if (h == null) {
+                Log.w(TAG, "previewCaptured called with no preview request on queue!");
+                return null;
+            }
+            h.setPreviewTimestamp(timestamp);
+            return new Pair<>(h.mRequest, h.mTimestamp);
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    /**
+     * Called to alert the {@link CaptureCollector} that the preview capture has completed.
+     *
+     * @return the {@link RequestHolder} for the request associated with this capture.
+     */
+    public RequestHolder previewProduced() {
+        final ReentrantLock lock = this.mLock;
+        lock.lock();
+        try {
+            CaptureHolder h = mPreviewProduceQueue.poll();
+            if (h == null) {
+                Log.w(TAG, "previewProduced called with no preview request on queue!");
+                return null;
+            }
+            h.setPreviewProduced();
+            return h.mRequest;
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    private void onPreviewCompleted() {
+        mInFlightPreviews--;
+        if (mInFlightPreviews < 0) {
+            throw new IllegalStateException(
+                    "More preview captures completed than requests queued.");
+        }
+        if (mInFlightPreviews == 0) {
+            mPreviewsEmpty.signalAll();
+        }
+    }
+
+    private void onRequestCompleted(RequestHolder request, LegacyRequest legacyHolder,
+                                    long timestamp) {
+        mInFlight--;
+        if (DEBUG) {
+            Log.d(TAG, "Completed request " + request.getRequestId() +
+                    ", " + mInFlight + " requests remain in flight.");
+        }
+        if (mInFlight < 0) {
+            throw new IllegalStateException(
+                    "More captures completed than requests queued.");
+        }
+        mNotFull.signalAll();
+        if (mInFlight == 0) {
+            mIsEmpty.signalAll();
+        }
+        CameraMetadataNative result = mMapper.cachedConvertResultMetadata(
+                legacyHolder, timestamp);
+        mDeviceState.setCaptureResult(request, result);
+    }
+}
diff --git a/core/java/android/hardware/camera2/legacy/GLThreadManager.java b/core/java/android/hardware/camera2/legacy/GLThreadManager.java
index 5d44fd2..06521cf 100644
--- a/core/java/android/hardware/camera2/legacy/GLThreadManager.java
+++ b/core/java/android/hardware/camera2/legacy/GLThreadManager.java
@@ -25,6 +25,8 @@
 
 import java.util.Collection;
 
+import static com.android.internal.util.Preconditions.*;
+
 /**
  * GLThreadManager handles the thread used for rendering into the configured output surfaces.
  */
@@ -38,6 +40,8 @@
     private static final int MSG_DROP_FRAMES = 4;
     private static final int MSG_ALLOW_FRAMES = 5;
 
+    private CaptureCollector mCaptureCollector;
+
     private final SurfaceTextureRenderer mTextureRenderer;
 
     private final RequestHandlerThread mGLHandlerThread;
@@ -51,10 +55,13 @@
     private static class ConfigureHolder {
         public final ConditionVariable condition;
         public final Collection<Surface> surfaces;
+        public final CaptureCollector collector;
 
-        public ConfigureHolder(ConditionVariable condition, Collection<Surface> surfaces) {
+        public ConfigureHolder(ConditionVariable condition, Collection<Surface> surfaces,
+                               CaptureCollector collector) {
             this.condition = condition;
             this.surfaces = surfaces;
+            this.collector = collector;
         }
     }
 
@@ -74,6 +81,7 @@
                     ConfigureHolder configure = (ConfigureHolder) msg.obj;
                     mTextureRenderer.cleanupEGLContext();
                     mTextureRenderer.configureSurfaces(configure.surfaces);
+                    mCaptureCollector = checkNotNull(configure.collector);
                     configure.condition.open();
                     mConfigured = true;
                     break;
@@ -88,7 +96,7 @@
                     if (!mConfigured) {
                         Log.e(TAG, "Dropping frame, EGL context not configured!");
                     }
-                    mTextureRenderer.drawIntoSurfaces((Collection<Surface>) msg.obj);
+                    mTextureRenderer.drawIntoSurfaces(mCaptureCollector);
                     break;
                 case MSG_CLEANUP:
                     mTextureRenderer.cleanupEGLContext();
@@ -158,16 +166,11 @@
     }
 
     /**
-     * Queue a new call to draw into a given set of surfaces.
-     *
-     * <p>
-     * The set of surfaces passed here must be a subset of the set of surfaces passed in
-     * the last call to {@link #setConfigurationAndWait}.
-     * </p>
-     *
-     * @param targets a collection of {@link android.view.Surface}s to draw into.
+     * Queue a new call to draw into the surfaces specified in the next available preview
+     * request from the {@link CaptureCollector} passed to
+     * {@link #setConfigurationAndWait(java.util.Collection, CaptureCollector)};
      */
-    public void queueNewFrame(Collection<Surface> targets) {
+    public void queueNewFrame() {
         Handler handler = mGLHandlerThread.getHandler();
 
         /**
@@ -175,7 +178,7 @@
          * are produced, drop frames rather than allowing the queue to back up.
          */
         if (!handler.hasMessages(MSG_NEW_FRAME)) {
-            handler.sendMessage(handler.obtainMessage(MSG_NEW_FRAME, targets));
+            handler.sendMessage(handler.obtainMessage(MSG_NEW_FRAME));
         } else {
             Log.e(TAG, "GLThread dropping frame.  Not consuming frames quickly enough!");
         }
@@ -186,12 +189,14 @@
      * this configuration has been applied.
      *
      * @param surfaces a collection of {@link android.view.Surface}s to configure.
+     * @param collector a {@link CaptureCollector} to retrieve requests from.
      */
-    public void setConfigurationAndWait(Collection<Surface> surfaces) {
+    public void setConfigurationAndWait(Collection<Surface> surfaces, CaptureCollector collector) {
+        checkNotNull(collector, "collector must not be null");
         Handler handler = mGLHandlerThread.getHandler();
 
         final ConditionVariable condition = new ConditionVariable(/*closed*/false);
-        ConfigureHolder configure = new ConfigureHolder(condition, surfaces);
+        ConfigureHolder configure = new ConfigureHolder(condition, surfaces, collector);
 
         Message m = handler.obtainMessage(MSG_NEW_CONFIGURATION, /*arg1*/0, /*arg2*/0, configure);
         handler.sendMessage(m);
diff --git a/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java b/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java
index 71d3d4b..cbf4a3d 100644
--- a/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java
+++ b/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java
@@ -135,10 +135,9 @@
         }
 
         @Override
-        public void onCaptureStarted(RequestHolder holder) {
+        public void onCaptureStarted(RequestHolder holder, final long timestamp) {
             final CaptureResultExtras extras = getExtrasFromRequest(holder);
 
-            final long timestamp = System.nanoTime();
             mResultHandler.post(new Runnable() {
                 @Override
                 public void run() {
@@ -146,7 +145,6 @@
                         Log.d(TAG, "doing onCaptureStarted callback.");
                     }
                     try {
-                        // TODO: Don't fake timestamp
                         mDeviceCallbacks.onCaptureStarted(extras, timestamp);
                     } catch (RemoteException e) {
                         throw new IllegalStateException(
@@ -167,7 +165,6 @@
                         Log.d(TAG, "doing onCaptureResult callback.");
                     }
                     try {
-                        // TODO: Don't fake metadata
                         mDeviceCallbacks.onResultReceived(result, extras);
                     } catch (RemoteException e) {
                         throw new IllegalStateException(
@@ -483,6 +480,12 @@
         return new Size(dimens[0], dimens[1]);
     }
 
+    static void setNextTimestamp(Surface surface, long timestamp)
+            throws BufferQueueAbandonedException {
+        checkNotNull(surface);
+        LegacyExceptionUtils.throwOnError(nativeSetNextTimestamp(surface, timestamp));
+    }
+
     private static native int nativeDetectSurfaceType(Surface surface);
 
     private static native int nativeDetectSurfaceDimens(Surface surface,
@@ -506,4 +509,5 @@
     private static native int nativeDetectTextureDimens(SurfaceTexture surfaceTexture,
             /*out*/int[/*2*/] dimens);
 
+    private static native int nativeSetNextTimestamp(Surface surface, long timestamp);
 }
diff --git a/core/java/android/hardware/camera2/legacy/RequestThreadManager.java b/core/java/android/hardware/camera2/legacy/RequestThreadManager.java
index cc7a90eb..066b416 100644
--- a/core/java/android/hardware/camera2/legacy/RequestThreadManager.java
+++ b/core/java/android/hardware/camera2/legacy/RequestThreadManager.java
@@ -38,6 +38,8 @@
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.TimeUnit;
 
 import static com.android.internal.util.Preconditions.*;
 
@@ -62,22 +64,20 @@
     private final CameraCharacteristics mCharacteristics;
 
     private final CameraDeviceState mDeviceState;
+    private final CaptureCollector mCaptureCollector;
 
     private static final int MSG_CONFIGURE_OUTPUTS = 1;
     private static final int MSG_SUBMIT_CAPTURE_REQUEST = 2;
     private static final int MSG_CLEANUP = 3;
 
+    private static final int MAX_IN_FLIGHT_REQUESTS = 2;
+
     private static final int PREVIEW_FRAME_TIMEOUT = 300; // ms
     private static final int JPEG_FRAME_TIMEOUT = 3000; // ms (same as CTS for API2)
 
     private static final float ASPECT_RATIO_TOLERANCE = 0.01f;
     private boolean mPreviewRunning = false;
 
-    private volatile long mLastJpegTimestamp;
-    private volatile long mLastPreviewTimestamp;
-    private volatile RequestHolder mInFlightPreview;
-    private volatile RequestHolder mInFlightJpeg;
-
     private final List<Surface> mPreviewOutputs = new ArrayList<>();
     private final List<Surface> mCallbackOutputs = new ArrayList<>();
     private GLThreadManager mGLThreadManager;
@@ -167,16 +167,16 @@
     }
 
     private final ConditionVariable mReceivedJpeg = new ConditionVariable(false);
-    private final ConditionVariable mReceivedPreview = new ConditionVariable(false);
 
     private final Camera.PictureCallback mJpegCallback = new Camera.PictureCallback() {
         @Override
         public void onPictureTaken(byte[] data, Camera camera) {
             Log.i(TAG, "Received jpeg.");
-            RequestHolder holder = mInFlightJpeg;
+            Pair<RequestHolder, Long> captureInfo = mCaptureCollector.jpegProduced();
+            RequestHolder holder = captureInfo.first;
+            long timestamp = captureInfo.second;
             if (holder == null) {
-                Log.w(TAG, "Dropping jpeg frame.");
-                mInFlightJpeg = null;
+                Log.e(TAG, "Dropping jpeg frame.");
                 return;
             }
             for (Surface s : holder.getHolderTargets()) {
@@ -184,6 +184,7 @@
                     if (RequestHolder.jpegType(s)) {
                         Log.i(TAG, "Producing jpeg buffer...");
                         LegacyCameraDevice.setSurfaceDimens(s, data.length, /*height*/1);
+                        LegacyCameraDevice.setNextTimestamp(s, timestamp);
                         LegacyCameraDevice.produceFrame(s, data, data.length, /*height*/1,
                                 CameraMetadataNative.NATIVE_JPEG_FORMAT);
                     }
@@ -191,6 +192,7 @@
                     Log.w(TAG, "Surface abandoned, dropping frame. ", e);
                 }
             }
+
             mReceivedJpeg.open();
         }
     };
@@ -198,7 +200,7 @@
     private final Camera.ShutterCallback mJpegShutterCallback = new Camera.ShutterCallback() {
         @Override
         public void onShutter() {
-            mLastJpegTimestamp = SystemClock.elapsedRealtimeNanos();
+            mCaptureCollector.jpegCaptured(SystemClock.elapsedRealtimeNanos());
         }
     };
 
@@ -206,29 +208,10 @@
             new SurfaceTexture.OnFrameAvailableListener() {
                 @Override
                 public void onFrameAvailable(SurfaceTexture surfaceTexture) {
-                    RequestHolder holder = mInFlightPreview;
-
                     if (DEBUG) {
                         mPrevCounter.countAndLog();
                     }
-
-                    if (holder == null) {
-                        mGLThreadManager.queueNewFrame(null);
-                        Log.w(TAG, "Dropping preview frame.");
-                        return;
-                    }
-
-                    mInFlightPreview = null;
-
-                    if (holder.hasPreviewTargets()) {
-                        mGLThreadManager.queueNewFrame(holder.getHolderTargets());
-                    }
-
-                    /**
-                     * TODO: Get timestamp from GL thread after buffer update.
-                     */
-                    mLastPreviewTimestamp = surfaceTexture.getTimestamp();
-                    mReceivedPreview.open();
+                    mGLThreadManager.queueNewFrame();
                 }
             };
 
@@ -256,14 +239,11 @@
             mCamera.setPreviewTexture(mDummyTexture);
             startPreview();
         }
-        mInFlightJpeg = request;
-        // TODO: Hook up shutter callback to CameraDeviceStateListener#onCaptureStarted
         mCamera.takePicture(mJpegShutterCallback, /*raw*/null, mJpegCallback);
         mPreviewRunning = false;
     }
 
     private void doPreviewCapture(RequestHolder request) throws IOException {
-        mInFlightPreview = request;
         if (mPreviewRunning) {
             return; // Already running
         }
@@ -290,8 +270,6 @@
         mPreviewOutputs.clear();
         mCallbackOutputs.clear();
         mPreviewTexture = null;
-        mInFlightPreview = null;
-        mInFlightJpeg = null;
 
         int facing = mCharacteristics.get(CameraCharacteristics.LENS_FACING);
         int orientation = mCharacteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);
@@ -389,7 +367,7 @@
             mGLThreadManager.start();
         }
         mGLThreadManager.waitUntilStarted();
-        mGLThreadManager.setConfigurationAndWait(mPreviewOutputs);
+        mGLThreadManager.setConfigurationAndWait(mPreviewOutputs, mCaptureCollector);
         mGLThreadManager.allowNewFrames();
         mPreviewTexture = mGLThreadManager.getCurrentSurfaceTexture();
         if (mPreviewTexture != null) {
@@ -553,6 +531,18 @@
                     int sizes = config.surfaces != null ? config.surfaces.size() : 0;
                     Log.i(TAG, "Configure outputs: " + sizes +
                             " surfaces configured.");
+
+                    try {
+                        boolean success = mCaptureCollector.waitForEmpty(JPEG_FRAME_TIMEOUT,
+                                TimeUnit.MILLISECONDS);
+                        if (!success) {
+                            Log.e(TAG, "Timed out while queueing configure request.");
+                        }
+                    } catch (InterruptedException e) {
+                        // TODO: report error to CameraDevice
+                        Log.e(TAG, "Interrupted while waiting for requests to complete.");
+                    }
+
                     try {
                         configureOutputs(config.surfaces);
                     } catch (IOException e) {
@@ -571,6 +561,16 @@
                     // Get the next burst from the request queue.
                     Pair<BurstHolder, Long> nextBurst = mRequestQueue.getNext();
                     if (nextBurst == null) {
+                        try {
+                            boolean success = mCaptureCollector.waitForEmpty(JPEG_FRAME_TIMEOUT,
+                                    TimeUnit.MILLISECONDS);
+                            if (!success) {
+                                Log.e(TAG, "Timed out while waiting for empty.");
+                            }
+                        } catch (InterruptedException e) {
+                            // TODO: report error to CameraDevice
+                            Log.e(TAG, "Interrupted while waiting for requests to complete.");
+                        }
                         mDeviceState.setIdle();
                         stopPreview();
                         break;
@@ -603,39 +603,41 @@
                             if (!mParams.same(legacyRequest.parameters)) {
                                 mParams = legacyRequest.parameters;
                                 mCamera.setParameters(mParams);
+
                                 paramsChanged = true;
                             }
                         }
 
-                        mDeviceState.setCaptureStart(holder);
-                        long timestamp = 0;
                         try {
+                            boolean success = mCaptureCollector.queueRequest(holder,
+                                    mLastRequest, JPEG_FRAME_TIMEOUT, TimeUnit.MILLISECONDS);
+
+                            if (!success) {
+                                Log.e(TAG, "Timed out while queueing capture request.");
+                            }
                             if (holder.hasPreviewTargets()) {
-                                mReceivedPreview.close();
                                 doPreviewCapture(holder);
-                                if (!mReceivedPreview.block(PREVIEW_FRAME_TIMEOUT)) {
-                                    // TODO: report error to CameraDevice
-                                    Log.e(TAG, "Hit timeout for preview callback!");
-                                }
-                                timestamp = mLastPreviewTimestamp;
                             }
                             if (holder.hasJpegTargets()) {
+                                success = mCaptureCollector.
+                                        waitForPreviewsEmpty(PREVIEW_FRAME_TIMEOUT *
+                                                MAX_IN_FLIGHT_REQUESTS, TimeUnit.MILLISECONDS);
+                                if (!success) {
+                                    Log.e(TAG, "Timed out waiting for prior requests to complete.");
+                                }
                                 mReceivedJpeg.close();
                                 doJpegCapture(holder);
                                 if (!mReceivedJpeg.block(JPEG_FRAME_TIMEOUT)) {
                                     // TODO: report error to CameraDevice
                                     Log.e(TAG, "Hit timeout for jpeg callback!");
                                 }
-                                mInFlightJpeg = null;
-                                timestamp = mLastJpegTimestamp;
                             }
                         } catch (IOException e) {
-                            // TODO: err handling
+                            // TODO: report error to CameraDevice
                             throw new IOError(e);
-                        }
-
-                        if (timestamp == 0) {
-                            timestamp = SystemClock.elapsedRealtimeNanos();
+                        } catch (InterruptedException e) {
+                            // TODO: report error to CameraDevice
+                            Log.e(TAG, "Interrupted during capture.", e);
                         }
 
                         if (paramsChanged) {
@@ -647,11 +649,6 @@
                             // Update parameters to the latest that we think the camera is using
                             mLastRequest.setParameters(mParams);
                         }
-
-
-                        CameraMetadataNative result = mMapper.cachedConvertResultMetadata(
-                                mLastRequest, timestamp);
-                        mDeviceState.setCaptureResult(holder, result);
                     }
                     if (DEBUG) {
                         long totalTime = SystemClock.elapsedRealtimeNanos() - startTime;
@@ -661,6 +658,16 @@
                     break;
                 case MSG_CLEANUP:
                     mCleanup = true;
+                    try {
+                        boolean success = mCaptureCollector.waitForEmpty(JPEG_FRAME_TIMEOUT,
+                                TimeUnit.MILLISECONDS);
+                        if (!success) {
+                            Log.e(TAG, "Timed out while queueing cleanup request.");
+                        }
+                    } catch (InterruptedException e) {
+                        // TODO: report error to CameraDevice
+                        Log.e(TAG, "Interrupted while waiting for requests to complete.");
+                    }
                     if (mGLThreadManager != null) {
                         mGLThreadManager.quit();
                     }
@@ -693,6 +700,7 @@
         String name = String.format("RequestThread-%d", cameraId);
         TAG = name;
         mDeviceState = checkNotNull(deviceState, "deviceState must not be null");
+        mCaptureCollector = new CaptureCollector(MAX_IN_FLIGHT_REQUESTS, mDeviceState);
         mRequestThread = new RequestHandlerThread(name, mRequestHandlerCb);
     }
 
diff --git a/core/java/android/hardware/camera2/legacy/SurfaceTextureRenderer.java b/core/java/android/hardware/camera2/legacy/SurfaceTextureRenderer.java
index fdf9ba0..0687264 100644
--- a/core/java/android/hardware/camera2/legacy/SurfaceTextureRenderer.java
+++ b/core/java/android/hardware/camera2/legacy/SurfaceTextureRenderer.java
@@ -28,6 +28,7 @@
 import android.opengl.Matrix;
 import android.text.format.Time;
 import android.util.Log;
+import android.util.Pair;
 import android.util.Size;
 import android.view.Surface;
 import android.os.SystemProperties;
@@ -599,41 +600,63 @@
 
     /**
      * Draw the current buffer in the {@link SurfaceTexture} returned from
-     * {@link #getSurfaceTexture()} into the given set of target surfaces.
+     * {@link #getSurfaceTexture()} into the set of target {@link Surface}s
+     * in the next request from the given {@link CaptureCollector}, or drop
+     * the frame if none is available.
      *
      * <p>
-     * The given surfaces must be a subset of the surfaces set in the last
-     * {@link #configureSurfaces(java.util.Collection)} call.
+     * Any {@link Surface}s targeted must be a subset of the {@link Surface}s
+     * set in the last {@link #configureSurfaces(java.util.Collection)} call.
      * </p>
      *
-     * @param targetSurfaces the surfaces to draw to.
+     * @param targetCollector the surfaces to draw to.
      */
-    public void drawIntoSurfaces(Collection<Surface> targetSurfaces) {
+    public void drawIntoSurfaces(CaptureCollector targetCollector) {
         if ((mSurfaces == null || mSurfaces.size() == 0)
                 && (mConversionSurfaces == null || mConversionSurfaces.size() == 0)) {
             return;
         }
 
+        boolean doTiming = targetCollector.hasPendingPreviewCaptures();
         checkGlError("before updateTexImage");
 
-        if (targetSurfaces == null) {
-            mSurfaceTexture.updateTexImage();
-            return;
+        if (doTiming) {
+            beginGlTiming();
         }
 
-        beginGlTiming();
-
         mSurfaceTexture.updateTexImage();
 
         long timestamp = mSurfaceTexture.getTimestamp();
-        addGlTimestamp(timestamp);
+
+        Pair<RequestHolder, Long> captureHolder = targetCollector.previewCaptured(timestamp);
+
+        // No preview request queued, drop frame.
+        if (captureHolder == null) {
+            Log.w(TAG, "Dropping preview frame.");
+            if (doTiming) {
+                endGlTiming();
+            }
+            return;
+        }
+
+        RequestHolder request = captureHolder.first;
+
+        Collection<Surface> targetSurfaces = request.getHolderTargets();
+        if (doTiming) {
+            addGlTimestamp(timestamp);
+        }
 
         List<Long> targetSurfaceIds = LegacyCameraDevice.getSurfaceIds(targetSurfaces);
         for (EGLSurfaceHolder holder : mSurfaces) {
             if (LegacyCameraDevice.containsSurfaceId(holder.surface, targetSurfaceIds)) {
                 makeCurrent(holder.eglSurface);
-                drawFrame(mSurfaceTexture, holder.width, holder.height);
-                swapBuffers(holder.eglSurface);
+                try {
+                    LegacyCameraDevice.setNextTimestamp(holder.surface, captureHolder.second);
+                    drawFrame(mSurfaceTexture, holder.width, holder.height);
+                    swapBuffers(holder.eglSurface);
+                } catch (LegacyExceptionUtils.BufferQueueAbandonedException e) {
+                    Log.w(TAG, "Surface abandoned, dropping frame. ", e);
+                }
             }
         }
         for (EGLSurfaceHolder holder : mConversionSurfaces) {
@@ -647,6 +670,7 @@
 
                 try {
                     int format = LegacyCameraDevice.detectSurfaceType(holder.surface);
+                    LegacyCameraDevice.setNextTimestamp(holder.surface, captureHolder.second);
                     LegacyCameraDevice.produceFrame(holder.surface, mPBufferPixels.array(),
                             holder.width, holder.height, format);
                     swapBuffers(holder.eglSurface);
@@ -655,8 +679,11 @@
                 }
             }
         }
+        targetCollector.previewProduced();
 
-        endGlTiming();
+        if (doTiming) {
+            endGlTiming();
+        }
     }
 
     /**
diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java
index d0dbd96..762e1ee 100644
--- a/core/java/android/provider/ContactsContract.java
+++ b/core/java/android/provider/ContactsContract.java
@@ -6802,6 +6802,21 @@
                     default: return com.android.internal.R.string.eventTypeCustom;
                 }
             }
+
+            /**
+             * Return a {@link CharSequence} that best describes the given type,
+             * possibly substituting the given {@link #LABEL} value
+             * for {@link #TYPE_CUSTOM}.
+             */
+            public static final CharSequence getTypeLabel(Resources res, int type,
+                    CharSequence label) {
+                if (type == TYPE_CUSTOM && !TextUtils.isEmpty(label)) {
+                    return label;
+                } else {
+                    final int labelRes = getTypeResource(type);
+                    return res.getText(labelRes);
+                }
+            }
         }
 
         /**
diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java
index 4bfcaff..a36f06c7 100644
--- a/core/java/android/text/Layout.java
+++ b/core/java/android/text/Layout.java
@@ -926,7 +926,7 @@
     public float getLineMax(int line) {
         float margin = getParagraphLeadingMargin(line);
         float signedExtent = getLineExtent(line, false);
-        return margin + signedExtent >= 0 ? signedExtent : -signedExtent;
+        return margin + (signedExtent >= 0 ? signedExtent : -signedExtent);
     }
 
     /**
@@ -936,7 +936,7 @@
     public float getLineWidth(int line) {
         float margin = getParagraphLeadingMargin(line);
         float signedExtent = getLineExtent(line, true);
-        return margin + signedExtent >= 0 ? signedExtent : -signedExtent;
+        return margin + (signedExtent >= 0 ? signedExtent : -signedExtent);
     }
 
     /**
@@ -1571,6 +1571,16 @@
             int len = mt.mLen;
             boolean hasTabs = false;
             TabStops tabStops = null;
+            // leading margins should be taken into account when measuring a paragraph
+            int margin = 0;
+            if (text instanceof Spanned) {
+                Spanned spanned = (Spanned) text;
+                LeadingMarginSpan[] spans = getParagraphSpans(spanned, start, end,
+                        LeadingMarginSpan.class);
+                for (LeadingMarginSpan lms : spans) {
+                    margin += lms.getLeadingMargin(true);
+                }
+            }
             for (int i = 0; i < len; ++i) {
                 if (chars[i] == '\t') {
                     hasTabs = true;
@@ -1588,7 +1598,7 @@
                 }
             }
             tl.set(paint, text, start, end, dir, directions, hasTabs, tabStops);
-            return tl.metrics(null);
+            return margin + tl.metrics(null);
         } finally {
             TextLine.recycle(tl);
             MeasuredText.recycle(mt);
diff --git a/core/java/android/transition/TransitionSet.java b/core/java/android/transition/TransitionSet.java
index 363f97f..83c60af 100644
--- a/core/java/android/transition/TransitionSet.java
+++ b/core/java/android/transition/TransitionSet.java
@@ -371,6 +371,8 @@
 
     private TransitionValuesMaps removeExcludes(TransitionValuesMaps values) {
         if (mTargetIds.isEmpty() && mTargetIdExcludes == null && mTargetTypeExcludes == null
+                && mTargetNames == null && mTargetTypes == null
+                && mTargetExcludes == null && mTargetNameExcludes == null
                 && mTargets.isEmpty()) {
             return values;
         }
diff --git a/core/java/android/widget/AbsSeekBar.java b/core/java/android/widget/AbsSeekBar.java
index be7e0bc..2708525 100644
--- a/core/java/android/widget/AbsSeekBar.java
+++ b/core/java/android/widget/AbsSeekBar.java
@@ -16,6 +16,7 @@
 
 package android.widget;
 
+import android.animation.ObjectAnimator;
 import android.annotation.Nullable;
 import android.content.Context;
 import android.content.res.ColorStateList;
@@ -63,6 +64,9 @@
      * progress.
      */
     private int mKeyProgressIncrement = 1;
+    private ObjectAnimator mPositionAnimator;
+    private static final int PROGRESS_ANIMATION_DURATION = 250;
+
 
     private static final int NO_ALPHA = 0xFF;
     private float mDisabledAlpha;
@@ -361,18 +365,17 @@
     void onProgressRefresh(float scale, boolean fromUser) {
         super.onProgressRefresh(scale, fromUser);
 
-        final Drawable thumb = mThumb;
-        if (thumb != null) {
-            setThumbPos(getWidth(), thumb, scale, Integer.MIN_VALUE);
-
-            // Since we draw translated, the drawable's bounds that it signals
-            // for invalidation won't be the actual bounds we want invalidated,
-            // so just invalidate this whole view.
-            invalidate();
+        if (!isAnimationRunning()) {
+            setThumbPos(scale);
         }
     }
 
     @Override
+    void onAnimatePosition(float scale, boolean fromUser) {
+        setThumbPos(scale);
+    }
+
+    @Override
     protected void onSizeChanged(int w, int h, int oldw, int oldh) {
         super.onSizeChanged(w, h, oldw, oldh);
 
@@ -414,6 +417,18 @@
         return max > 0 ? getProgress() / (float) max : 0;
     }
 
+    private void setThumbPos(float scale) {
+        final Drawable thumb = mThumb;
+        if (thumb != null) {
+            setThumbPos(getWidth(), thumb, scale, Integer.MIN_VALUE);
+            // Since we draw translated, the drawable's bounds that it signals
+            // for invalidation won't be the actual bounds we want invalidated,
+            // so just invalidate this whole view.
+            invalidate();
+
+        }
+    }
+
     /**
      * Updates the thumb drawable bounds.
      *
@@ -676,13 +691,13 @@
             switch (keyCode) {
                 case KeyEvent.KEYCODE_DPAD_LEFT:
                     if (progress <= 0) break;
-                    setProgress(progress - mKeyProgressIncrement, true);
+                    animateSetProgress(progress - mKeyProgressIncrement);
                     onKeyChange();
                     return true;
 
                 case KeyEvent.KEYCODE_DPAD_RIGHT:
                     if (progress >= getMax()) break;
-                    setProgress(progress + mKeyProgressIncrement, true);
+                    animateSetProgress(progress + mKeyProgressIncrement);
                     onKeyChange();
                     return true;
             }
@@ -691,6 +706,38 @@
         return super.onKeyDown(keyCode, event);
     }
 
+    boolean isAnimationRunning() {
+        return mPositionAnimator != null && mPositionAnimator.isRunning();
+    }
+
+    /**
+     * @hide
+     */
+    @Override
+    public void setProgress(int progress, boolean fromUser) {
+        if (isAnimationRunning()) {
+            mPositionAnimator.cancel();
+        }
+        super.setProgress(progress, fromUser);
+    }
+
+    void animateSetProgress(int progress) {
+        float curProgress = isAnimationRunning() ? getAnimationPosition() : getProgress();
+
+        if (progress < 0) {
+            progress = 0;
+        } else if (progress > getMax()) {
+            progress = getMax();
+        }
+        setProgressValueOnly(progress);
+
+        mPositionAnimator = ObjectAnimator.ofFloat(this, "animationPosition", curProgress,
+                progress);
+        mPositionAnimator.setDuration(PROGRESS_ANIMATION_DURATION);
+        mPositionAnimator.setAutoCancel(true);
+        mPositionAnimator.start();
+    }
+
     @Override
     public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
         super.onInitializeAccessibilityEvent(event);
diff --git a/core/java/android/widget/ProgressBar.java b/core/java/android/widget/ProgressBar.java
index 20c1aa4..4a30809 100644
--- a/core/java/android/widget/ProgressBar.java
+++ b/core/java/android/widget/ProgressBar.java
@@ -242,6 +242,8 @@
     private long mUiThreadId;
     private boolean mShouldStartAnimationDrawable;
 
+    private float mAnimationPosition;
+
     private boolean mInDrawing;
     private boolean mAttached;
     private boolean mRefreshIsPosted;
@@ -259,7 +261,7 @@
     public ProgressBar(Context context) {
         this(context, null);
     }
-    
+
     public ProgressBar(Context context, AttributeSet attrs) {
         this(context, attrs, com.android.internal.R.attr.progressBarStyle);
     }
@@ -276,9 +278,9 @@
 
         final TypedArray a = context.obtainStyledAttributes(
                 attrs, R.styleable.ProgressBar, defStyleAttr, defStyleRes);
-        
+
         mNoInvalidate = true;
-        
+
         final Drawable progressDrawable = a.getDrawable(R.styleable.ProgressBar_progressDrawable);
         if (progressDrawable != null) {
             // Calling this method can set mMaxHeight, make sure the corresponding
@@ -297,11 +299,11 @@
         mBehavior = a.getInt(R.styleable.ProgressBar_indeterminateBehavior, mBehavior);
 
         final int resID = a.getResourceId(
-                com.android.internal.R.styleable.ProgressBar_interpolator, 
+                com.android.internal.R.styleable.ProgressBar_interpolator,
                 android.R.anim.linear_interpolator); // default to linear interpolator
         if (resID > 0) {
             setInterpolator(context, resID);
-        } 
+        }
 
         setMax(a.getInt(R.styleable.ProgressBar_max, mMax));
 
@@ -386,12 +388,12 @@
      * traverse layer and state list drawables.
      */
     private Drawable tileify(Drawable drawable, boolean clip) {
-        
+
         if (drawable instanceof LayerDrawable) {
             LayerDrawable background = (LayerDrawable) drawable;
             final int N = background.getNumberOfLayers();
             Drawable[] outDrawables = new Drawable[N];
-            
+
             for (int i = 0; i < N; i++) {
                 int id = background.getId(i);
                 outDrawables[i] = tileify(background.getDrawable(i),
@@ -399,13 +401,13 @@
             }
 
             LayerDrawable newBg = new LayerDrawable(outDrawables);
-            
+
             for (int i = 0; i < N; i++) {
                 newBg.setId(i, background.getId(i));
             }
-            
+
             return newBg;
-            
+
         } else if (drawable instanceof StateListDrawable) {
             StateListDrawable in = (StateListDrawable) drawable;
             StateListDrawable out = new StateListDrawable();
@@ -414,7 +416,7 @@
                 out.addState(in.getStateSet(i), tileify(in.getStateDrawable(i), clip));
             }
             return out;
-            
+
         } else if (drawable instanceof BitmapDrawable) {
             final BitmapDrawable bitmap = (BitmapDrawable) drawable;
             final Bitmap tileBitmap = bitmap.getBitmap();
@@ -434,7 +436,7 @@
             return clip ? new ClipDrawable(
                     shapeDrawable, Gravity.LEFT, ClipDrawable.HORIZONTAL) : shapeDrawable;
         }
-        
+
         return drawable;
     }
 
@@ -442,7 +444,7 @@
         final float[] roundedCorners = new float[] { 5, 5, 5, 5, 5, 5, 5, 5 };
         return new RoundRectShape(roundedCorners, null, null);
     }
-    
+
     /**
      * Convert a AnimationDrawable for use as a barberpole animation.
      * Each frame of the animation is wrapped in a ClipDrawable and
@@ -454,7 +456,7 @@
             final int N = background.getNumberOfFrames();
             AnimationDrawable newBg = new AnimationDrawable();
             newBg.setOneShot(background.isOneShot());
-            
+
             for (int i = 0; i < N; i++) {
                 Drawable frame = tileify(background.getFrame(i), true);
                 frame.setLevel(10000);
@@ -465,7 +467,7 @@
         }
         return drawable;
     }
-    
+
     /**
      * <p>
      * Initialize the progress bar's default values:
@@ -506,7 +508,7 @@
      * <p>Change the indeterminate mode for this progress bar. In indeterminate
      * mode, the progress is ignored and the progress bar shows an infinite
      * animation instead.</p>
-     * 
+     *
      * If this progress bar's style only supports indeterminate mode (such as the circular
      * progress bars), then this will be ignored.
      *
@@ -657,7 +659,7 @@
 
         setIndeterminateDrawable(d);
     }
-    
+
     /**
      * <p>Get the drawable used to draw the progress bar in
      * progress mode.</p>
@@ -963,7 +965,7 @@
 
         setProgressDrawable(d);
     }
-    
+
     /**
      * @return The drawable currently used to draw the progress bar
      */
@@ -1014,7 +1016,7 @@
                 final int count = mRefreshData.size();
                 for (int i = 0; i < count; i++) {
                     final RefreshData rd = mRefreshData.get(i);
-                    doRefreshProgress(rd.id, rd.progress, rd.fromUser, true);
+                    doRefreshProgress(rd.id, rd.progress, rd.fromUser, true, rd.animate);
                     rd.recycle();
                 }
                 mRefreshData.clear();
@@ -1029,10 +1031,12 @@
                 new SynchronizedPool<RefreshData>(POOL_MAX);
 
         public int id;
-        public int progress;
+        public float progress;
         public boolean fromUser;
+        public boolean animate;
 
-        public static RefreshData obtain(int id, int progress, boolean fromUser) {
+        public static RefreshData obtain(int id, float progress, boolean fromUser,
+                boolean animate) {
             RefreshData rd = sPool.acquire();
             if (rd == null) {
                 rd = new RefreshData();
@@ -1040,9 +1044,10 @@
             rd.id = id;
             rd.progress = progress;
             rd.fromUser = fromUser;
+            rd.animate = animate;
             return rd;
         }
-        
+
         public void recycle() {
             sPool.release(this);
         }
@@ -1064,9 +1069,19 @@
         layer.mutate().setTint(tint, tintMode);
     }
 
-    private synchronized void doRefreshProgress(int id, int progress, boolean fromUser,
+    private float getScale(float progress) {
+        return mMax > 0 ? progress / (float) mMax : 0;
+    }
+
+    private synchronized void doRefreshProgress(int id, float progress, boolean fromUser,
             boolean callBackToApp) {
-        float scale = mMax > 0 ? (float) progress / (float) mMax : 0;
+        doRefreshProgress(id, progress, fromUser, callBackToApp, false);
+    }
+
+    private synchronized void doRefreshProgress(int id, float progress, boolean fromUser,
+            boolean callBackToApp, boolean animate) {
+        float scale = getScale(progress);
+
         final Drawable d = mCurrentDrawable;
         if (d != null) {
             Drawable progressDrawable = null;
@@ -1083,27 +1098,65 @@
         } else {
             invalidate();
         }
-        
-        if (callBackToApp && id == R.id.progress) {
-            onProgressRefresh(scale, fromUser);
+
+        if (id == R.id.progress) {
+            if (animate) {
+                onAnimatePosition(scale, fromUser);
+            } else if (callBackToApp) {
+                onProgressRefresh(scale, fromUser);
+            }
         }
     }
 
+    /**
+     * Called when a ProgressBar is animating its position.
+     *
+     * @param scale Current position/progress between 0 and 1.
+     * @param fromUser True if the progress change was initiated by the user.
+     */
+    void onAnimatePosition(float scale, boolean fromUser) {
+    }
+
+    /**
+     * Sets the progress value without going through the entire refresh process.
+     *
+     * @see #setProgress(int, boolean)
+     * @param progress The new progress, between 0 and {@link #getMax()}
+     */
+    void setProgressValueOnly(int progress) {
+        mProgress = progress;
+        onProgressRefresh(getScale(progress), true);
+    }
+
+    void setAnimationPosition(float position) {
+        mAnimationPosition = position;
+        refreshProgress(R.id.progress, position, true, true);
+    }
+
+    float getAnimationPosition() {
+        return mAnimationPosition;
+    }
+
     void onProgressRefresh(float scale, boolean fromUser) {
         if (AccessibilityManager.getInstance(mContext).isEnabled()) {
             scheduleAccessibilityEventSender();
         }
     }
 
-    private synchronized void refreshProgress(int id, int progress, boolean fromUser) {
+    private synchronized void refreshProgress(int id, float progress, boolean fromUser) {
+        refreshProgress(id, progress, fromUser, false);
+    }
+
+    private synchronized void refreshProgress(int id, float progress, boolean fromUser,
+            boolean animate) {
         if (mUiThreadId == Thread.currentThread().getId()) {
-            doRefreshProgress(id, progress, fromUser, true);
+            doRefreshProgress(id, progress, fromUser, true, animate);
         } else {
             if (mRefreshProgressRunnable == null) {
                 mRefreshProgressRunnable = new RefreshProgressRunnable();
             }
 
-            final RefreshData rd = RefreshData.obtain(id, progress, fromUser);
+            final RefreshData rd = RefreshData.obtain(id, progress, fromUser, animate);
             mRefreshData.add(rd);
             if (mAttached && !mRefreshIsPosted) {
                 post(mRefreshProgressRunnable);
@@ -1111,7 +1164,7 @@
             }
         }
     }
-    
+
     /**
      * <p>Set the current progress to the specified value. Does not do anything
      * if the progress bar is in indeterminate mode.</p>
@@ -1121,13 +1174,13 @@
      * @see #setIndeterminate(boolean)
      * @see #isIndeterminate()
      * @see #getProgress()
-     * @see #incrementProgressBy(int) 
+     * @see #incrementProgressBy(int)
      */
     @android.view.RemotableViewMethod
     public synchronized void setProgress(int progress) {
         setProgress(progress, false);
     }
-    
+
     @android.view.RemotableViewMethod
     synchronized void setProgress(int progress, boolean fromUser) {
         if (mIndeterminate) {
@@ -1153,7 +1206,7 @@
      * Set the current secondary progress to the specified value. Does not do
      * anything if the progress bar is in indeterminate mode.
      * </p>
-     * 
+     *
      * @param secondaryProgress the new secondary progress, between 0 and {@link #getMax()}
      * @see #setIndeterminate(boolean)
      * @see #isIndeterminate()
@@ -1234,8 +1287,8 @@
      * @param max the upper range of this progress bar
      *
      * @see #getMax()
-     * @see #setProgress(int) 
-     * @see #setSecondaryProgress(int) 
+     * @see #setProgress(int)
+     * @see #setSecondaryProgress(int)
      */
     @android.view.RemotableViewMethod
     public synchronized void setMax(int max) {
@@ -1252,13 +1305,13 @@
             refreshProgress(R.id.progress, mProgress, false);
         }
     }
-    
+
     /**
      * <p>Increase the progress bar's progress by the specified amount.</p>
      *
      * @param diff the amount by which the progress must be increased
      *
-     * @see #setProgress(int) 
+     * @see #setProgress(int)
      */
     public synchronized final void incrementProgressBy(int diff) {
         setProgress(mProgress + diff);
@@ -1269,7 +1322,7 @@
      *
      * @param diff the amount by which the secondary progress must be increased
      *
-     * @see #setSecondaryProgress(int) 
+     * @see #setSecondaryProgress(int)
      */
     public synchronized final void incrementSecondaryProgressBy(int diff) {
         setSecondaryProgress(mSecondaryProgress + diff);
@@ -1292,13 +1345,13 @@
             if (mInterpolator == null) {
                 mInterpolator = new LinearInterpolator();
             }
-    
+
             if (mTransformation == null) {
                 mTransformation = new Transformation();
             } else {
                 mTransformation.clear();
             }
-            
+
             if (mAnimation == null) {
                 mAnimation = new AlphaAnimation(0.0f, 1.0f);
             } else {
@@ -1449,7 +1502,7 @@
             }
             mIndeterminateDrawable.setBounds(left, top, right, bottom);
         }
-        
+
         if (mProgressDrawable != null) {
             mProgressDrawable.setBounds(0, 0, right, bottom);
         }
@@ -1519,20 +1572,20 @@
         setMeasuredDimension(resolveSizeAndState(dw, widthMeasureSpec, 0),
                 resolveSizeAndState(dh, heightMeasureSpec, 0));
     }
-    
+
     @Override
     protected void drawableStateChanged() {
         super.drawableStateChanged();
         updateDrawableState();
     }
-        
+
     private void updateDrawableState() {
         int[] state = getDrawableState();
-        
+
         if (mProgressDrawable != null && mProgressDrawable.isStateful()) {
             mProgressDrawable.setState(state);
         }
-        
+
         if (mIndeterminateDrawable != null && mIndeterminateDrawable.isStateful()) {
             mIndeterminateDrawable.setState(state);
         }
@@ -1554,14 +1607,14 @@
     static class SavedState extends BaseSavedState {
         int progress;
         int secondaryProgress;
-        
+
         /**
          * Constructor called from {@link ProgressBar#onSaveInstanceState()}
          */
         SavedState(Parcelable superState) {
             super(superState);
         }
-        
+
         /**
          * Constructor called from {@link #CREATOR}
          */
@@ -1595,10 +1648,10 @@
         // Force our ancestor class to save its state
         Parcelable superState = super.onSaveInstanceState();
         SavedState ss = new SavedState(superState);
-        
+
         ss.progress = mProgress;
         ss.secondaryProgress = mSecondaryProgress;
-        
+
         return ss;
     }
 
@@ -1606,7 +1659,7 @@
     public void onRestoreInstanceState(Parcelable state) {
         SavedState ss = (SavedState) state;
         super.onRestoreInstanceState(ss.getSuperState());
-        
+
         setProgress(ss.progress);
         setSecondaryProgress(ss.secondaryProgress);
     }
@@ -1622,7 +1675,7 @@
                 final int count = mRefreshData.size();
                 for (int i = 0; i < count; i++) {
                     final RefreshData rd = mRefreshData.get(i);
-                    doRefreshProgress(rd.id, rd.progress, rd.fromUser, true);
+                    doRefreshProgress(rd.id, rd.progress, rd.fromUser, rd.animate);
                     rd.recycle();
                 }
                 mRefreshData.clear();
diff --git a/core/java/android/widget/RadialTimePickerView.java b/core/java/android/widget/RadialTimePickerView.java
index 9b763c1..1e30bfa 100644
--- a/core/java/android/widget/RadialTimePickerView.java
+++ b/core/java/android/widget/RadialTimePickerView.java
@@ -518,6 +518,9 @@
         if (mIs24HourMode) {
             if (mIsOnInnerCircle) {
                 hours = hours % 12;
+                if (hours == 0) {
+                    hours = 12;
+                }
             } else {
                 if (hours != 0) {
                     hours += 12;
diff --git a/core/java/android/widget/RatingBar.java b/core/java/android/widget/RatingBar.java
index 82b490e..c4a7c0a 100644
--- a/core/java/android/widget/RatingBar.java
+++ b/core/java/android/widget/RatingBar.java
@@ -314,6 +314,10 @@
         dispatchRatingChange(true);
     }
 
+    @Override
+    void animateSetProgress(int progress) {
+    }
+
     void dispatchRatingChange(boolean fromUser) {
         if (mOnRatingBarChangeListener != null) {
             mOnRatingBarChangeListener.onRatingChanged(this, getRating(),
diff --git a/core/java/android/widget/Toolbar.java b/core/java/android/widget/Toolbar.java
index 71102e8..0b15eb6 100644
--- a/core/java/android/widget/Toolbar.java
+++ b/core/java/android/widget/Toolbar.java
@@ -17,6 +17,7 @@
 package android.widget;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.ActionBar;
 import android.content.Context;
 import android.content.res.TypedArray;
@@ -654,27 +655,13 @@
     }
 
     /**
-     * Set the icon to use for the toolbar's navigation button.
-     *
-     * <p>The navigation button appears at the start of the toolbar if present. Setting an icon
-     * will make the navigation button visible.</p>
-     *
-     * <p>If you use a navigation icon you should also set a description for its action using
-     * {@link #setNavigationDescription(int)}. This is used for accessibility and tooltips.</p>
-     *
-     * @param resId Resource ID of a drawable to set
-     */
-    public void setNavigationIcon(int resId) {
-        setNavigationIcon(getContext().getDrawable(resId));
-    }
-
-    /**
      * Retrieve the currently configured content description for the navigation button view.
      * This will be used to describe the navigation action to users through mechanisms such
      * as screen readers or tooltips.
      *
      * @return The navigation button's content description
      */
+    @Nullable
     public CharSequence getNavigationContentDescription() {
         return mNavButtonView != null ? mNavButtonView.getContentDescription() : null;
     }
@@ -684,11 +671,11 @@
      * description will be read via screen readers or other accessibility systems to explain
      * the action of the navigation button.
      *
-     * @param description Content description to set
+     * @param resId Resource ID of a content description string to set, or 0 to
+     *              clear the description
      */
-    public void setNavigationContentDescription(CharSequence description) {
-        ensureNavButtonView();
-        mNavButtonView.setContentDescription(description);
+    public void setNavigationContentDescription(int resId) {
+        setNavigationContentDescription(resId != 0 ? getContext().getText(resId) : null);
     }
 
     /**
@@ -696,11 +683,16 @@
      * description will be read via screen readers or other accessibility systems to explain
      * the action of the navigation button.
      *
-     * @param resId Resource ID of a content description string to set
+     * @param description Content description to set, or <code>null</code> to
+     *                    clear the content description
      */
-    public void setNavigationContentDescription(int resId) {
-        ensureNavButtonView();
-        mNavButtonView.setContentDescription(resId != 0 ? getContext().getText(resId) : null);
+    public void setNavigationContentDescription(@Nullable CharSequence description) {
+        if (!TextUtils.isEmpty(description)) {
+            ensureNavButtonView();
+        }
+        if (mNavButtonView != null) {
+            mNavButtonView.setContentDescription(description);
+        }
     }
 
     /**
@@ -710,11 +702,28 @@
      * will make the navigation button visible.</p>
      *
      * <p>If you use a navigation icon you should also set a description for its action using
-     * {@link #setNavigationDescription(int)}. This is used for accessibility and tooltips.</p>
+     * {@link #setNavigationContentDescription(int)}. This is used for accessibility and
+     * tooltips.</p>
      *
-     * @param icon Drawable to set
+     * @param resId Resource ID of a drawable to set
      */
-    public void setNavigationIcon(Drawable icon) {
+    public void setNavigationIcon(int resId) {
+        setNavigationIcon(getContext().getDrawable(resId));
+    }
+
+    /**
+     * Set the icon to use for the toolbar's navigation button.
+     *
+     * <p>The navigation button appears at the start of the toolbar if present. Setting an icon
+     * will make the navigation button visible.</p>
+     *
+     * <p>If you use a navigation icon you should also set a description for its action using
+     * {@link #setNavigationContentDescription(int)}. This is used for accessibility and
+     * tooltips.</p>
+     *
+     * @param icon Drawable to set, may be null to clear the icon
+     */
+    public void setNavigationIcon(@Nullable Drawable icon) {
         if (icon != null) {
             ensureNavButtonView();
             if (mNavButtonView.getParent() == null) {
@@ -733,40 +742,12 @@
      *
      * @return The navigation icon drawable
      */
+    @Nullable
     public Drawable getNavigationIcon() {
         return mNavButtonView != null ? mNavButtonView.getDrawable() : null;
     }
 
     /**
-     * Set a description for the navigation button.
-     *
-     * <p>This description string is used for accessibility, tooltips and other facilities
-     * to improve discoverability.</p>
-     *
-     * @param resId Resource ID of a string to set
-     */
-    public void setNavigationDescription(int resId) {
-        setNavigationDescription(getContext().getText(resId));
-    }
-
-    /**
-     * Set a description for the navigation button.
-     *
-     * <p>This description string is used for accessibility, tooltips and other facilities
-     * to improve discoverability.</p>
-     *
-     * @param description String to set as the description
-     */
-    public void setNavigationDescription(CharSequence description) {
-        if (!TextUtils.isEmpty(description)) {
-            ensureNavButtonView();
-        }
-        if (mNavButtonView != null) {
-            mNavButtonView.setContentDescription(description);
-        }
-    }
-
-    /**
      * Set a listener to respond to navigation events.
      *
      * <p>This listener will be called whenever the user clicks the navigation button
diff --git a/core/java/com/android/internal/app/ToolbarActionBar.java b/core/java/com/android/internal/app/ToolbarActionBar.java
index 1bcb684..298dd44 100644
--- a/core/java/com/android/internal/app/ToolbarActionBar.java
+++ b/core/java/com/android/internal/app/ToolbarActionBar.java
@@ -154,7 +154,7 @@
 
     @Override
     public void setHomeActionContentDescription(CharSequence description) {
-        mToolbar.setNavigationDescription(description);
+        mToolbar.setNavigationContentDescription(description);
     }
 
     @Override
@@ -164,7 +164,7 @@
 
     @Override
     public void setHomeActionContentDescription(int resId) {
-        mToolbar.setNavigationDescription(resId);
+        mToolbar.setNavigationContentDescription(resId);
     }
 
     @Override
diff --git a/core/java/com/android/internal/inputmethod/InputMethodSubtypeSwitchingController.java b/core/java/com/android/internal/inputmethod/InputMethodSubtypeSwitchingController.java
index fdd24a6..52485dd 100644
--- a/core/java/com/android/internal/inputmethod/InputMethodSubtypeSwitchingController.java
+++ b/core/java/com/android/internal/inputmethod/InputMethodSubtypeSwitchingController.java
@@ -54,8 +54,8 @@
         public final CharSequence mSubtypeName;
         public final InputMethodInfo mImi;
         public final int mSubtypeId;
-        private final boolean mIsSystemLocale;
-        private final boolean mIsSystemLanguage;
+        public final boolean mIsSystemLocale;
+        public final boolean mIsSystemLanguage;
 
         public ImeSubtypeListItem(CharSequence imeName, CharSequence subtypeName,
                 InputMethodInfo imi, int subtypeId, String subtypeLocale, String systemLocale) {
@@ -68,8 +68,28 @@
                 mIsSystemLanguage = false;
             } else {
                 mIsSystemLocale = subtypeLocale.equals(systemLocale);
-                mIsSystemLanguage = mIsSystemLocale
-                        || subtypeLocale.startsWith(systemLocale.substring(0, 2));
+                if (mIsSystemLocale) {
+                    mIsSystemLanguage = true;
+                } else {
+                    // TODO: Use Locale#getLanguage or Locale#toLanguageTag
+                    final String systemLanguage = parseLanguageFromLocaleString(systemLocale);
+                    final String subtypeLanguage = parseLanguageFromLocaleString(subtypeLocale);
+                    mIsSystemLanguage = systemLanguage.length() >= 2 &&
+                            systemLanguage.equals(subtypeLanguage);
+                }
+            }
+        }
+
+        /**
+         * Returns the language component of a given locale string.
+         * TODO: Use {@link Locale#getLanguage()} instead.
+         */
+        private static String parseLanguageFromLocaleString(final String locale) {
+            final int idx = locale.indexOf('_');
+            if (idx < 0) {
+                return locale;
+            } else {
+                return locale.substring(0, idx);
             }
         }
 
diff --git a/core/java/com/android/internal/util/Preconditions.java b/core/java/com/android/internal/util/Preconditions.java
index c0d1e88..414b7bc 100644
--- a/core/java/com/android/internal/util/Preconditions.java
+++ b/core/java/com/android/internal/util/Preconditions.java
@@ -16,6 +16,8 @@
 
 package com.android.internal.util;
 
+import java.util.Collection;
+
 /**
  * Simple static methods to be called at the start of your own methods to verify
  * correct arguments and state.
@@ -210,7 +212,7 @@
     }
 
     /**
-     * Ensures that the array is not {@code null}, and none if its elements are {@code null}.
+     * Ensures that the array is not {@code null}, and none of its elements are {@code null}.
      *
      * @param value an array of boxed objects
      * @param valueName the name of the argument to use if the check fails
@@ -235,6 +237,57 @@
     }
 
     /**
+     * Ensures that the {@link Collection} is not {@code null}, and none of its elements are
+     * {@code null}.
+     *
+     * @param value a {@link Collection} of boxed objects
+     * @param valueName the name of the argument to use if the check fails
+     *
+     * @return the validated {@link Collection}
+     *
+     * @throws NullPointerException if the {@code value} or any of its elements were {@code null}
+     */
+    public static <T> Collection<T> checkCollectionElementsNotNull(final Collection<T> value,
+            final String valueName) {
+        if (value == null) {
+            throw new NullPointerException(valueName + " must not be null");
+        }
+
+        long ctr = 0;
+        for (T elem : value) {
+            if (elem == null) {
+                throw new NullPointerException(
+                        String.format("%s[%d] must not be null", valueName, ctr));
+            }
+            ++ctr;
+        }
+
+        return value;
+    }
+
+    /**
+     * Ensures that the {@link Collection} is not {@code null}, and contains at least one element.
+     *
+     * @param value a {@link Collection} of boxed elements.
+     * @param valueName the name of the argument to use if the check fails.
+
+     * @return the validated {@link Collection}
+     *
+     * @throws NullPointerException if the {@code value} was {@code null}
+     * @throws IllegalArgumentException if the {@code value} was empty
+     */
+    public static <T> Collection<T> checkCollectionNotEmpty(final Collection<T> value,
+            final String valueName) {
+        if (value == null) {
+            throw new NullPointerException(valueName + " must not be null");
+        }
+        if (value.isEmpty()) {
+            throw new IllegalArgumentException(valueName + " is empty");
+        }
+        return value;
+    }
+
+    /**
      * Ensures that all elements in the argument floating point array are within the inclusive range
      *
      * <p>While this can be used to range check against +/- infinity, note that all NaN numbers
diff --git a/core/jni/android/graphics/MinikinUtils.cpp b/core/jni/android/graphics/MinikinUtils.cpp
index 802f2abe..2ad8330 100644
--- a/core/jni/android/graphics/MinikinUtils.cpp
+++ b/core/jni/android/graphics/MinikinUtils.cpp
@@ -40,8 +40,8 @@
     return off + n;
 }
 
-std::string MinikinUtils::setLayoutProperties(Layout* layout, const Paint* paint, int bidiFlags,
-        TypefaceImpl* typeface) {
+void MinikinUtils::doLayout(Layout* layout, const Paint* paint, int bidiFlags, TypefaceImpl* typeface,
+        const uint16_t* buf, size_t start, size_t count, size_t bufSize) {
     TypefaceImpl* resolvedFace = TypefaceImpl_resolveDefault(typeface);
     layout->setFontCollection(resolvedFace->fFontCollection);
     FontStyle style = resolvedFace->fStyle;
@@ -62,7 +62,7 @@
     SkPaintOptionsAndroid::FontVariant var = paint->getPaintOptionsAndroid().getFontVariant();
     const char* varstr = var == SkPaintOptionsAndroid::kElegant_Variant ? "elegant" : "compact";
     off = snprintfcat(css, off, sizeof(css), " -minikin-variant: %s;", varstr);
-    return std::string(css);
+    layout->doLayout(buf, start, count, bufSize, std::string(css));
 }
 
 float MinikinUtils::xOffsetForTextAlign(Paint* paint, const Layout& layout) {
diff --git a/core/jni/android/graphics/MinikinUtils.h b/core/jni/android/graphics/MinikinUtils.h
index 0562c3b1..647cbd8 100644
--- a/core/jni/android/graphics/MinikinUtils.h
+++ b/core/jni/android/graphics/MinikinUtils.h
@@ -45,8 +45,8 @@
 
 class MinikinUtils {
 public:
-    static std::string setLayoutProperties(Layout* layout, const Paint* paint, int bidiFlags,
-            TypefaceImpl* typeface);
+    static void doLayout(Layout* layout, const Paint* paint, int bidiFlags, TypefaceImpl* typeface,
+            const uint16_t* buf, size_t start, size_t count, size_t bufSize);
 
     static float xOffsetForTextAlign(Paint* paint, const Layout& layout);
 
diff --git a/core/jni/android/graphics/Paint.cpp b/core/jni/android/graphics/Paint.cpp
index e2b3684..a1f09bd 100644
--- a/core/jni/android/graphics/Paint.cpp
+++ b/core/jni/android/graphics/Paint.cpp
@@ -535,8 +535,7 @@
 
         Layout layout;
         TypefaceImpl* typeface = GraphicsJNI::getNativeTypeface(env, jpaint);
-        std::string css = MinikinUtils::setLayoutProperties(&layout, paint, bidiFlags, typeface);
-        layout.doLayout(textArray, index, count, textLength, css);
+        MinikinUtils::doLayout(&layout, paint, bidiFlags, typeface, textArray, index, count, textLength);
         result = layout.getAdvance();
         env->ReleaseCharArrayElements(text, const_cast<jchar*>(textArray), JNI_ABORT);
         return result;
@@ -563,8 +562,7 @@
 
         Layout layout;
         TypefaceImpl* typeface = GraphicsJNI::getNativeTypeface(env, jpaint);
-        std::string css = MinikinUtils::setLayoutProperties(&layout, paint, bidiFlags, typeface);
-        layout.doLayout(textArray, start, count, textLength, css);
+        MinikinUtils::doLayout(&layout, paint, bidiFlags, typeface, textArray, start, count, textLength);
         width = layout.getAdvance();
 
         env->ReleaseStringChars(text, textArray);
@@ -586,8 +584,7 @@
 
         Layout layout;
         TypefaceImpl* typeface = GraphicsJNI::getNativeTypeface(env, jpaint);
-        std::string css = MinikinUtils::setLayoutProperties(&layout, paint, bidiFlags, typeface);
-        layout.doLayout(textArray, 0, textLength, textLength, css);
+        MinikinUtils::doLayout(&layout, paint, bidiFlags, typeface, textArray, 0, textLength, textLength);
         width = layout.getAdvance();
 
         env->ReleaseStringChars(text, textArray);
@@ -616,8 +613,7 @@
         jfloat* widthsArray = autoWidths.ptr();
 
         Layout layout;
-        std::string css = MinikinUtils::setLayoutProperties(&layout, paint, bidiFlags, typeface);
-        layout.doLayout(text, 0, count, count, css);
+        MinikinUtils::doLayout(&layout, paint, bidiFlags, typeface, text, 0, count, count);
         layout.getAdvances(widthsArray);
 
         return count;
@@ -670,8 +666,7 @@
         int bidiFlags = isRtl ? kBidi_Force_RTL : kBidi_Force_LTR;
 
         Layout layout;
-        std::string css = MinikinUtils::setLayoutProperties(&layout, paint, bidiFlags, typeface);
-        layout.doLayout(text, start, count, contextCount, css);
+        MinikinUtils::doLayout(&layout, paint, bidiFlags, typeface, text, start, count, contextCount);
         layout.getAdvances(advancesArray);
         totalAdvance = layout.getAdvance();
 
@@ -770,8 +765,7 @@
     static void getTextPath(JNIEnv* env, Paint* paint, TypefaceImpl* typeface, const jchar* text,
             jint count, jint bidiFlags, jfloat x, jfloat y, SkPath* path) {
         Layout layout;
-        std::string css = MinikinUtils::setLayoutProperties(&layout, paint, bidiFlags, typeface);
-        layout.doLayout(text, 0, count, count, css);
+        MinikinUtils::doLayout(&layout, paint, bidiFlags, typeface, text, 0, count, count);
         size_t nGlyphs = layout.nGlyphs();
         uint16_t* glyphs = new uint16_t[nGlyphs];
         SkPoint* pos = new SkPoint[nGlyphs];
@@ -833,8 +827,7 @@
         float measured = 0;
 
         Layout layout;
-        std::string css = MinikinUtils::setLayoutProperties(&layout, &paint, bidiFlags, typeface);
-        layout.doLayout(text, 0, count, count, css);
+        MinikinUtils::doLayout(&layout, &paint, bidiFlags, typeface, text, 0, count, count);
         float* advances = new float[count];
         layout.getAdvances(advances);
         const bool forwardScan = (textBufferDirection == Paint::kForward_TextBufferDirection);
@@ -914,8 +907,7 @@
         SkIRect ir;
 
         Layout layout;
-        std::string css = MinikinUtils::setLayoutProperties(&layout, &paint, bidiFlags, typeface);
-        layout.doLayout(text, 0, count, count, css);
+        MinikinUtils::doLayout(&layout, &paint, bidiFlags, typeface, text, 0, count, count);
         MinikinRect rect;
         layout.getBounds(&rect);
         r.fLeft = rect.mLeft;
diff --git a/core/jni/android/graphics/TextLayout.h b/core/jni/android/graphics/TextLayout.h
deleted file mode 100644
index d58c692..0000000
--- a/core/jni/android/graphics/TextLayout.h
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * Copyright (C) 2010 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.
- */
-
-#include "jni.h"
-
-#include "SkCanvas.h"
-#include "SkPaint.h"
-#include "unicode/utypes.h"
-
-#include "TextLayoutCache.h"
-
-namespace android {
-
-#define UNICODE_NOT_A_CHAR              0xffff
-#define UNICODE_ZWSP                    0x200b
-#define UNICODE_FIRST_LOW_SURROGATE     0xdc00
-#define UNICODE_FIRST_HIGH_SURROGATE    0xd800
-#define UNICODE_FIRST_PRIVATE_USE       0xe000
-#define UNICODE_FIRST_RTL_CHAR          0x0590
-
-/*
- * Temporary buffer size
- */
-#define CHAR_BUFFER_SIZE 80
-
-/**
- * Turn on for using the Cache
- */
-#define USE_TEXT_LAYOUT_CACHE 1
-
-enum {
-    kBidi_LTR = 0,
-    kBidi_RTL = 1,
-    kBidi_Default_LTR = 2,
-    kBidi_Default_RTL = 3,
-    kBidi_Force_LTR = 4,
-    kBidi_Force_RTL = 5,
-
-    kBidi_Mask = 0x7
-};
-
-enum {
-    kDirection_LTR = 0,
-    kDirection_RTL = 1,
-
-    kDirection_Mask = 0x1
-};
-
-class TextLayout {
-public:
-
-    static void getTextRunAdvances(SkPaint* paint, const jchar* chars, jint start,
-                                   jint count, jint contextCount, jint dirFlags,
-                                   jfloat* resultAdvances, jfloat* resultTotalAdvance);
-
-    static void getTextPath(SkPaint* paint, const jchar* text, jsize len,
-                            jint bidiFlags, jfloat x, jfloat y, SkPath* path);
-
-    static void drawTextOnPath(SkPaint* paint, const jchar* text, jsize len,
-                               int bidiFlags, jfloat hOffset, jfloat vOffset,
-                               SkPath* path, SkCanvas* canvas);
-
-private:
-    static bool needsLayout(const jchar* text, jint len, jint bidiFlags);
-
-    static void handleText(SkPaint* paint, const jchar* text, jsize len,
-                           int bidiFlags, jfloat x, jfloat y, SkPath* path);
-};
-} // namespace android
diff --git a/core/jni/android_graphics_Canvas.cpp b/core/jni/android_graphics_Canvas.cpp
index 0007912..a9b01d0 100644
--- a/core/jni/android_graphics_Canvas.cpp
+++ b/core/jni/android_graphics_Canvas.cpp
@@ -486,8 +486,7 @@
     Paint paint(origPaint);
 
     Layout layout;
-    std::string css = MinikinUtils::setLayoutProperties(&layout, &paint, bidiFlags, typeface);
-    layout.doLayout(text, start, count, contextCount, css);
+    MinikinUtils::doLayout(&layout, &paint, bidiFlags, typeface, text, start, count, contextCount);
 
     size_t nGlyphs = layout.nGlyphs();
     uint16_t* glyphs = new uint16_t[nGlyphs];
@@ -625,8 +624,7 @@
                            const Paint& paint, TypefaceImpl* typeface) {
     Paint paintCopy(paint);
     Layout layout;
-    std::string css = MinikinUtils::setLayoutProperties(&layout, &paintCopy, bidiFlags, typeface);
-    layout.doLayout(text, 0, count, count, css);
+    MinikinUtils::doLayout(&layout, &paintCopy, bidiFlags, typeface, text, 0, count, count);
     hOffset += MinikinUtils::hOffsetForTextAlign(&paintCopy, layout, path);
 
     // Set align to left for drawing, as we don't want individual
diff --git a/core/jni/android_hardware_camera2_legacy_LegacyCameraDevice.cpp b/core/jni/android_hardware_camera2_legacy_LegacyCameraDevice.cpp
index d2f5b5d..697cdc6 100644
--- a/core/jni/android_hardware_camera2_legacy_LegacyCameraDevice.cpp
+++ b/core/jni/android_hardware_camera2_legacy_LegacyCameraDevice.cpp
@@ -587,7 +587,7 @@
 
     int32_t transform = 0;
 
-    if ((err = CameraUtils::getRotationTransform(staticMetadata, /*out*/&transform)) != OK) {
+    if ((err = CameraUtils::getRotationTransform(staticMetadata, /*out*/&transform)) != NO_ERROR) {
         ALOGE("%s: Invalid rotation transform %s (%d)", __FUNCTION__, strerror(-err),
                 err);
         return err;
@@ -595,7 +595,7 @@
 
     ALOGV("%s: Setting buffer sticky transform to %d", __FUNCTION__, transform);
 
-    if ((err = native_window_set_buffers_sticky_transform(anw.get(), transform)) != OK) {
+    if ((err = native_window_set_buffers_sticky_transform(anw.get(), transform)) != NO_ERROR) {
         ALOGE("%s: Unable to configure surface transform, error %s (%d)", __FUNCTION__,
                 strerror(-err), err);
         return err;
@@ -604,6 +604,26 @@
     return NO_ERROR;
 }
 
+static jint LegacyCameraDevice_nativeSetNextTimestamp(JNIEnv* env, jobject thiz, jobject surface,
+        jlong timestamp) {
+    ALOGV("nativeSetNextTimestamp");
+    sp<ANativeWindow> anw;
+    if ((anw = getNativeWindow(env, surface)) == NULL) {
+        ALOGE("%s: Could not retrieve native window from surface.", __FUNCTION__);
+        return BAD_VALUE;
+    }
+
+    status_t err = NO_ERROR;
+
+    if ((err = native_window_set_buffers_timestamp(anw.get(), static_cast<int64_t>(timestamp))) !=
+            NO_ERROR) {
+        ALOGE("%s: Unable to set surface timestamp, error %s (%d)", __FUNCTION__, strerror(-err),
+                err);
+        return err;
+    }
+    return NO_ERROR;
+}
+
 } // extern "C"
 
 static JNINativeMethod gCameraDeviceMethods[] = {
@@ -634,6 +654,9 @@
     { "nativeSetSurfaceOrientation",
     "(Landroid/view/Surface;II)I",
     (void *)LegacyCameraDevice_nativeSetSurfaceOrientation },
+    { "nativeSetNextTimestamp",
+    "(Landroid/view/Surface;J)I",
+    (void *)LegacyCameraDevice_nativeSetNextTimestamp },
 };
 
 // Get all the required offsets in java class and register native functions
diff --git a/core/jni/android_view_GLES20Canvas.cpp b/core/jni/android_view_GLES20Canvas.cpp
index 0a259aa..3cd031e 100644
--- a/core/jni/android_view_GLES20Canvas.cpp
+++ b/core/jni/android_view_GLES20Canvas.cpp
@@ -620,8 +620,7 @@
 static void renderText(DisplayListRenderer* renderer, const jchar* text, int count,
         jfloat x, jfloat y, int bidiFlags, Paint* paint, TypefaceImpl* typeface) {
     Layout layout;
-    std::string css = MinikinUtils::setLayoutProperties(&layout, paint, bidiFlags, typeface);
-    layout.doLayout(text, 0, count, count, css);
+    MinikinUtils::doLayout(&layout, paint, bidiFlags, typeface, text, 0, count, count);
     x += MinikinUtils::xOffsetForTextAlign(paint, layout);
     renderTextLayout(renderer, &layout, x, y, paint);
 }
@@ -655,8 +654,7 @@
         SkPath* path, jfloat hOffset, jfloat vOffset, int bidiFlags, Paint* paint,
         TypefaceImpl* typeface) {
     Layout layout;
-    std::string css = MinikinUtils::setLayoutProperties(&layout, paint, bidiFlags, typeface);
-    layout.doLayout(text, 0, count, count, css);
+    MinikinUtils::doLayout(&layout, paint, bidiFlags, typeface, text, 0, count, count);
     hOffset += MinikinUtils::hOffsetForTextAlign(paint, layout, *path);
     Paint::Align align = paint->getTextAlign();
     paint->setTextAlign(Paint::kLeft_Align);
@@ -670,8 +668,7 @@
         jint start, jint count, jint contextCount, jfloat x, jfloat y,
         int bidiFlags, Paint* paint, TypefaceImpl* typeface) {
     Layout layout;
-    std::string css = MinikinUtils::setLayoutProperties(&layout, paint, bidiFlags, typeface);
-    layout.doLayout(text, start, count, contextCount, css);
+    MinikinUtils::doLayout(&layout, paint, bidiFlags, typeface, text, start, count, contextCount);
     x += MinikinUtils::xOffsetForTextAlign(paint, layout);
     renderTextLayout(renderer, &layout, x, y, paint);
 }
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 056d470..2043214 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -2798,6 +2798,13 @@
         android:description="@string/permdesc_createMediaProjection"
         android:protectionLevel="signature" />
 
+    <!-- @SystemApi Allows an application to read install sessions
+         @hide This is not a third-party API (intended for system apps). -->
+    <permission android:name="android.permission.READ_INSTALL_SESSIONS"
+        android:label="@string/permlab_readInstallSessions"
+        android:description="@string/permdesc_readInstallSessions"
+        android:protectionLevel="signature|system" />
+
     <!-- The system process is explicitly the only one allowed to launch the
          confirmation UI for full backup/restore -->
     <uses-permission android:name="android.permission.CONFIRM_FULL_BACKUP"/>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 351acf0..c7b5580 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -3872,6 +3872,11 @@
     <!-- Description of an application permission that lets it create media projection sessions. -->
     <string name="permdesc_createMediaProjection">Allows an application to create media projection sessions. These sessions can provide applications the ability to capture display and audio contents. Should never be needed by normal apps.</string>
 
+    <!-- Title of an application permission that lets it read install sessions. -->
+    <string name="permlab_readInstallSessions">Read install sessions</string>
+    <!-- Description of an application permission that lets it read install sessions. -->
+    <string name="permdesc_readInstallSessions">Allows an application to read install sessions. This allows it to see details about active package installations.</string>
+
     <!-- Shown in the tutorial for tap twice for zoom control. -->
     <string name="tutorial_double_tap_to_zoom_message_short">Touch twice for zoom control</string>
 
diff --git a/core/tests/bluetoothtests/src/android/bluetooth/le/ScanSettingsTest.java b/core/tests/bluetoothtests/src/android/bluetooth/le/ScanSettingsTest.java
new file mode 100644
index 0000000..7c42c3b
--- /dev/null
+++ b/core/tests/bluetoothtests/src/android/bluetooth/le/ScanSettingsTest.java
@@ -0,0 +1,64 @@
+/*
+ * 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.bluetooth.le;
+
+import android.test.suitebuilder.annotation.SmallTest;
+
+import junit.framework.TestCase;
+
+/**
+ * Test for Bluetooth LE {@link ScanSettings}.
+ */
+public class ScanSettingsTest extends TestCase {
+
+    @SmallTest
+    public void testCallbackType() {
+        ScanSettings.Builder builder = new ScanSettings.Builder();
+        builder.setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES);
+        builder.setCallbackType(ScanSettings.CALLBACK_TYPE_FIRST_MATCH);
+        builder.setCallbackType(ScanSettings.CALLBACK_TYPE_MATCH_LOST);
+        builder.setCallbackType(
+                ScanSettings.CALLBACK_TYPE_FIRST_MATCH | ScanSettings.CALLBACK_TYPE_MATCH_LOST);
+        try {
+            builder.setCallbackType(
+                    ScanSettings.CALLBACK_TYPE_ALL_MATCHES | ScanSettings.CALLBACK_TYPE_MATCH_LOST);
+            fail("should have thrown IllegalArgumentException!");
+        } catch (IllegalArgumentException e) {
+            // nothing to do
+        }
+
+        try {
+            builder.setCallbackType(
+                    ScanSettings.CALLBACK_TYPE_ALL_MATCHES |
+                    ScanSettings.CALLBACK_TYPE_FIRST_MATCH);
+            fail("should have thrown IllegalArgumentException!");
+        } catch (IllegalArgumentException e) {
+            // nothing to do
+        }
+
+        try {
+            builder.setCallbackType(
+                    ScanSettings.CALLBACK_TYPE_ALL_MATCHES |
+                    ScanSettings.CALLBACK_TYPE_FIRST_MATCH |
+                    ScanSettings.CALLBACK_TYPE_MATCH_LOST);
+            fail("should have thrown IllegalArgumentException!");
+        } catch (IllegalArgumentException e) {
+            // nothing to do
+        }
+
+    }
+}
diff --git a/core/tests/inputmethodtests/src/android/os/InputMethodSubtypeSwitchingControllerTest.java b/core/tests/inputmethodtests/src/android/os/InputMethodSubtypeSwitchingControllerTest.java
index ca68e93..3a598f2 100644
--- a/core/tests/inputmethodtests/src/android/os/InputMethodSubtypeSwitchingControllerTest.java
+++ b/core/tests/inputmethodtests/src/android/os/InputMethodSubtypeSwitchingControllerTest.java
@@ -295,4 +295,33 @@
         assertRotationOrder(anotherController, false /* onlyCurrentIme */,
                 switchingUnawarelatinIme_en_UK, switchUnawareJapaneseIme_ja_JP);
     }
+
+    @SmallTest
+    public void testImeSubtypeListItem() throws Exception {
+        final List<ImeSubtypeListItem> items = new ArrayList<ImeSubtypeListItem>();
+        addDummyImeSubtypeListItems(items, "LatinIme", "LatinIme",
+                Arrays.asList("en_US", "fr", "en", "en_uk", "enn", "e", "EN_US"),
+                true /* supportsSwitchingToNextInputMethod*/);
+        final ImeSubtypeListItem item_en_US = items.get(0);
+        final ImeSubtypeListItem item_fr = items.get(1);
+        final ImeSubtypeListItem item_en = items.get(2);
+        final ImeSubtypeListItem item_enn = items.get(3);
+        final ImeSubtypeListItem item_e = items.get(4);
+        final ImeSubtypeListItem item_EN_US = items.get(5);
+
+        assertTrue(item_en_US.mIsSystemLocale);
+        assertFalse(item_fr.mIsSystemLocale);
+        assertFalse(item_en.mIsSystemLocale);
+        assertFalse(item_en.mIsSystemLocale);
+        assertFalse(item_enn.mIsSystemLocale);
+        assertFalse(item_e.mIsSystemLocale);
+        assertFalse(item_EN_US.mIsSystemLocale);
+
+        assertTrue(item_en_US.mIsSystemLanguage);
+        assertFalse(item_fr.mIsSystemLanguage);
+        assertTrue(item_en.mIsSystemLanguage);
+        assertFalse(item_enn.mIsSystemLocale);
+        assertFalse(item_e.mIsSystemLocale);
+        assertFalse(item_EN_US.mIsSystemLocale);
+    }
 }
diff --git a/graphics/java/android/graphics/drawable/VectorDrawable.java b/graphics/java/android/graphics/drawable/VectorDrawable.java
index 8ed6776d..f32fa1f 100644
--- a/graphics/java/android/graphics/drawable/VectorDrawable.java
+++ b/graphics/java/android/graphics/drawable/VectorDrawable.java
@@ -188,6 +188,12 @@
     public void draw(Canvas canvas) {
         final int saveCount = canvas.save();
         final Rect bounds = getBounds();
+
+        if (bounds.width() == 0 || bounds.height() == 0) {
+            // too small to draw
+            return;
+        }
+
         final boolean needMirroring = needMirroring();
 
         canvas.translate(bounds.left, bounds.top);
diff --git a/libs/hwui/renderthread/DrawFrameTask.cpp b/libs/hwui/renderthread/DrawFrameTask.cpp
index 763e727..dd34e09 100644
--- a/libs/hwui/renderthread/DrawFrameTask.cpp
+++ b/libs/hwui/renderthread/DrawFrameTask.cpp
@@ -85,7 +85,8 @@
 
 void DrawFrameTask::postAndWait() {
     AutoMutex _lock(mLock);
-    mRenderThread->queueAndWait(this, mSignal, mLock);
+    mRenderThread->queue(this);
+    mSignal.wait(mLock);
 }
 
 void DrawFrameTask::run() {
diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp
index 1e91eb5..3f03093 100644
--- a/libs/hwui/renderthread/RenderProxy.cpp
+++ b/libs/hwui/renderthread/RenderProxy.cpp
@@ -412,7 +412,8 @@
     task->setReturnPtr(&retval);
     SignalingRenderTask syncTask(task, &mSyncMutex, &mSyncCondition);
     AutoMutex _lock(mSyncMutex);
-    mRenderThread.queueAndWait(&syncTask, mSyncCondition, mSyncMutex);
+    mRenderThread.queue(&syncTask);
+    mSyncCondition.wait(mSyncMutex);
     return retval;
 }
 
diff --git a/libs/hwui/renderthread/RenderThread.cpp b/libs/hwui/renderthread/RenderThread.cpp
index 32dc46e..03e98d5 100644
--- a/libs/hwui/renderthread/RenderThread.cpp
+++ b/libs/hwui/renderthread/RenderThread.cpp
@@ -20,7 +20,6 @@
 
 #include <gui/DisplayEventReceiver.h>
 #include <utils/Log.h>
-#include <pthread.h>
 
 #include "../RenderState.h"
 #include "CanvasContext.h"
@@ -137,7 +136,6 @@
 };
 
 RenderThread::RenderThread() : Thread(true), Singleton<RenderThread>()
-        , mThreadId(0)
         , mNextWakeup(LLONG_MAX)
         , mDisplayEventReceiver(0)
         , mVsyncRequested(false)
@@ -246,7 +244,6 @@
 }
 
 bool RenderThread::threadLoop() {
-    mThreadId = pthread_self();
     initThreadLocals();
 
     int timeoutMillis = -1;
@@ -292,16 +289,6 @@
     }
 }
 
-void RenderThread::queueAndWait(RenderTask* task, Condition& signal, Mutex& lock) {
-    static nsecs_t sTimeout = milliseconds(500);
-    queue(task);
-    status_t err = signal.waitRelative(lock, sTimeout);
-    if (CC_UNLIKELY(err != NO_ERROR)) {
-        ALOGE("Timeout waiting for RenderTherad! err=%d", err);
-        nukeFromOrbit();
-    }
-}
-
 void RenderThread::queueAtFront(RenderTask* task) {
     AutoMutex _lock(mLock);
     mQueue.queueAtFront(task);
@@ -354,10 +341,6 @@
     return next;
 }
 
-void RenderThread::nukeFromOrbit() {
-    pthread_kill(mThreadId, SIGABRT);
-}
-
 } /* namespace renderthread */
 } /* namespace uirenderer */
 } /* namespace android */
diff --git a/libs/hwui/renderthread/RenderThread.h b/libs/hwui/renderthread/RenderThread.h
index 5984373..0b91e9d 100644
--- a/libs/hwui/renderthread/RenderThread.h
+++ b/libs/hwui/renderthread/RenderThread.h
@@ -23,7 +23,6 @@
 #include <set>
 
 #include <cutils/compiler.h>
-#include <utils/Condition.h>
 #include <utils/Looper.h>
 #include <utils/Mutex.h>
 #include <utils/Singleton.h>
@@ -74,7 +73,6 @@
     // RenderThread takes complete ownership of tasks that are queued
     // and will delete them after they are run
     ANDROID_API void queue(RenderTask* task);
-    void queueAndWait(RenderTask* task, Condition& signal, Mutex& lock);
     ANDROID_API void queueAtFront(RenderTask* task);
     void queueDelayed(RenderTask* task, int delayMs);
     void remove(RenderTask* task);
@@ -108,15 +106,11 @@
     void dispatchFrameCallbacks();
     void requestVsync();
 
-    // VERY DANGEROUS HANDLE WITH EXTREME CARE
-    void nukeFromOrbit();
-
     // Returns the next task to be run. If this returns NULL nextWakeup is set
     // to the time to requery for the nextTask to run. mNextWakeup is also
     // set to this time
     RenderTask* nextTask(nsecs_t* nextWakeup);
 
-    pthread_t mThreadId;
     sp<Looper> mLooper;
     Mutex mLock;
 
diff --git a/media/java/android/media/browse/MediaBrowserService.java b/media/java/android/media/browse/MediaBrowserService.java
index ffbaedd..c940259 100644
--- a/media/java/android/media/browse/MediaBrowserService.java
+++ b/media/java/android/media/browse/MediaBrowserService.java
@@ -371,7 +371,7 @@
      * children are to be queried.
      * @return The list of children, or null if the uri is invalid.
      */
-    protected abstract void onLoadChildren(@NonNull Uri parentUri,
+    public abstract void onLoadChildren(@NonNull Uri parentUri,
             @NonNull Result<List<MediaBrowserItem>> result);
 
     /**
@@ -390,7 +390,7 @@
      * @return The file descriptor of the thumbnail, which may then be loaded
      *          using a bitmap factory, or null if the item does not have a thumbnail.
      */
-    protected abstract void onLoadThumbnail(@NonNull Uri uri, int width, int height,
+    public abstract void onLoadThumbnail(@NonNull Uri uri, int width, int height,
             @NonNull Result<Bitmap> result);
 
     /**
diff --git a/packages/SystemUI/res/drawable/ic_clear_all.xml b/packages/SystemUI/res/drawable/ic_clear_all.xml
new file mode 100644
index 0000000..b0f3a5a
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_clear_all.xml
@@ -0,0 +1,28 @@
+<!--
+  ~ Copyright (C) 2014 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android" >
+    <size
+        android:width="32dp"
+        android:height="32dp"/>
+
+    <viewport
+        android:viewportWidth="48.0"
+        android:viewportHeight="48.0"/>
+
+    <path
+        android:fill="#FFFFFFFF"
+        android:pathData="M10.0,26.0l28.0,0.0l0.0,-4.0L10.0,22.0L10.0,26.0zM6.0,34.0l28.0,0.0l0.0,-4.0L6.0,30.0L6.0,34.0zM14.0,14.0l0.0,4.0l28.0,0.0l0.0,-4.0L14.0,14.0z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/notification_guts_bg.xml b/packages/SystemUI/res/drawable/notification_guts_bg.xml
new file mode 100644
index 0000000..07932d1
--- /dev/null
+++ b/packages/SystemUI/res/drawable/notification_guts_bg.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2014 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+    <solid android:color="@color/notification_guts_bg_color" />
+    <corners android:radius="@dimen/notification_material_rounded_rect_radius" />
+</shape>
diff --git a/packages/SystemUI/res/layout/notification_guts.xml b/packages/SystemUI/res/layout/notification_guts.xml
new file mode 100644
index 0000000..0e78d66
--- /dev/null
+++ b/packages/SystemUI/res/layout/notification_guts.xml
@@ -0,0 +1,106 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 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.
+-->
+
+<FrameLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:background="@drawable/notification_guts_bg"
+    android:id="@+id/notification_guts"
+    android:visibility="gone"
+    android:clickable="true"
+    android:gravity="top|start"
+    >
+    <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="@android:dimen/notification_large_icon_height"
+            android:orientation="horizontal"
+            >
+
+        <ImageView android:id="@android:id/icon"
+               android:layout_width="@android:dimen/notification_large_icon_width"
+               android:layout_height="@android:dimen/notification_large_icon_height"
+               android:layout_weight="0"
+               android:padding="8dp"
+               android:scaleType="centerInside"
+               />
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_gravity="start|center_vertical"
+            android:orientation="vertical"
+            android:paddingStart="8dp"
+            android:paddingEnd="8dp"
+            android:layout_weight="1"
+            >
+            <TextView
+                    android:id="@+id/pkgname"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_gravity="center_vertical|start"
+                    android:layout_weight="1"
+                    android:textAppearance="@*android:style/TextAppearance.StatusBar.Material.EventContent.Title"
+                    android:textColor="@color/notification_guts_title_color"
+                    />
+            <DateTimeView
+                    android:id="@+id/timestamp"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_weight="0"
+                    android:layout_gravity="center_vertical|start"
+                    android:textAppearance="@*android:style/TextAppearance.StatusBar.Material.EventContent.Time"
+                    android:textColor="@color/notification_guts_text_color"
+                    />
+            <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:id="@+id/debug_info"
+                    android:layout_weight="0"
+                    android:textAppearance="@*android:style/TextAppearance.StatusBar.Material.EventContent.Time"
+                    android:layout_gravity="bottom|start"
+                    android:visibility="gone"
+                    android:textColor="@color/notification_guts_text_color"
+                    />
+        </LinearLayout>
+
+        <LinearLayout
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:padding="8dp"
+                android:layout_weight="0"
+                android:orientation="horizontal"
+                android:showDividers="beginning|middle"
+                android:divider="@*android:drawable/list_divider_holo_dark"
+                android:dividerPadding="8dp"
+                >
+            <Button style="@android:style/Widget.Material.Light.Button.Borderless.Small"
+                    android:id="@+id/notification_inspect_item"
+                    android:layout_width="0dp"
+                    android:layout_height="match_parent"
+                    android:layout_weight="1"
+                    android:gravity="start|center_vertical"
+                    android:drawablePadding="8dp"
+                    android:paddingStart="8dp"
+                    android:textColor="@color/notification_guts_btn_color"
+                    android:textSize="14dp"
+                    android:singleLine="true"
+                    android:ellipsize="end"
+                    android:text="@string/status_bar_notification_inspect_item_title"
+                    />
+        </LinearLayout>
+    </LinearLayout>
+</FrameLayout>
diff --git a/packages/SystemUI/res/layout/status_bar_expanded.xml b/packages/SystemUI/res/layout/status_bar_expanded.xml
index 1f68cd8..9af2879 100644
--- a/packages/SystemUI/res/layout/status_bar_expanded.xml
+++ b/packages/SystemUI/res/layout/status_bar_expanded.xml
@@ -56,14 +56,6 @@
         android:clipToPadding="false"
         android:clipChildren="false">
 
-        <ViewStub
-                android:id="@+id/keyguard_user_switcher"
-                android:layout_height="wrap_content"
-                android:layout_width="wrap_content"
-                android:layout_marginTop="@dimen/status_bar_header_height_keyguard"
-                android:layout_gravity="end"
-                android:layout="@layout/keyguard_user_switcher" />
-
         <com.android.systemui.statusbar.phone.ObservableScrollView
             android:id="@+id/scroll_view"
             android:layout_width="match_parent"
@@ -103,6 +95,14 @@
             android:layout_height="match_parent"
             android:layout_marginBottom="@dimen/close_handle_underlap"/>
 
+        <ViewStub
+            android:id="@+id/keyguard_user_switcher"
+            android:layout_height="wrap_content"
+            android:layout_width="wrap_content"
+            android:layout_marginTop="@dimen/status_bar_header_height_keyguard"
+            android:layout_gravity="end"
+            android:layout="@layout/keyguard_user_switcher" />
+
     </com.android.systemui.statusbar.phone.NotificationsQuickSettingsContainer>
 
     <include layout="@layout/status_bar_expanded_header" />
diff --git a/packages/SystemUI/res/layout/status_bar_notification_dismiss_all.xml b/packages/SystemUI/res/layout/status_bar_notification_dismiss_all.xml
new file mode 100644
index 0000000..515270a
--- /dev/null
+++ b/packages/SystemUI/res/layout/status_bar_notification_dismiss_all.xml
@@ -0,0 +1,40 @@
+<!--
+  ~ 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
+  -->
+
+<!-- Extends Framelayout -->
+<com.android.systemui.statusbar.DismissView
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:visibility="gone"
+        >
+    <Button
+            android:id="@+id/dismiss_text"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:minHeight="0dp"
+            android:textColor="#ffffffff"
+            android:text="@string/clear_all_notifications_text"
+            android:textSize="18sp"
+            android:textAllCaps="true"
+            android:paddingTop="@dimen/clear_all_padding_top"
+            android:paddingEnd="8dp"
+            android:layout_gravity="end|center_vertical"
+            android:drawableEnd="@drawable/ic_clear_all"
+            android:drawablePadding="4dp"
+            android:fontFamily="sans-serif-light"
+            android:background="@drawable/ripple_drawable" />
+</com.android.systemui.statusbar.DismissView>
diff --git a/packages/SystemUI/res/layout/status_bar_notification_row.xml b/packages/SystemUI/res/layout/status_bar_notification_row.xml
index 7663d54..ef4e27c 100644
--- a/packages/SystemUI/res/layout/status_bar_notification_row.xml
+++ b/packages/SystemUI/res/layout/status_bar_notification_row.xml
@@ -1,3 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 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.statusbar.ExpandableNotificationRow
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
@@ -35,17 +52,11 @@
         android:paddingStart="8dp"
         />
 
-    <TextView
-        android:id="@+id/debug_info"
-        android:visibility="invisible"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_gravity="bottom|end"
-        android:fontFamily="sans-serif-condensed"
-        android:textSize="9dp"
-        android:textStyle="bold"
-        android:textColor="#00A040"
-        android:padding="2dp"
+    <include
+        layout="@layout/notification_guts"
+        android:id="@+id/notification_guts"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
         />
 
     <com.android.systemui.statusbar.NotificationScrimView
diff --git a/packages/SystemUI/res/layout/super_status_bar.xml b/packages/SystemUI/res/layout/super_status_bar.xml
index 6f64e17..ac998f6 100644
--- a/packages/SystemUI/res/layout/super_status_bar.xml
+++ b/packages/SystemUI/res/layout/super_status_bar.xml
@@ -54,7 +54,8 @@
     <com.android.systemui.statusbar.phone.PanelHolder
         android:id="@+id/panel_holder"
         android:layout_width="match_parent"
-        android:layout_height="match_parent" >
+        android:layout_height="match_parent"
+        android:background="@color/transparent" >
         <include layout="@layout/status_bar_expanded"
             android:layout_width="match_parent"
             android:layout_height="match_parent"
diff --git a/packages/SystemUI/res/menu/notification_popup_menu.xml b/packages/SystemUI/res/menu/notification_popup_menu.xml
deleted file mode 100644
index 8923fb6..0000000
--- a/packages/SystemUI/res/menu/notification_popup_menu.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/* apps/common/assets/default/default/skins/StatusBar.xml
-**
-** Copyright 2012, 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.
-*/
--->
-<menu xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:id="@+id/notification_inspect_item" android:title="@string/status_bar_notification_inspect_item_title" />
-</menu>
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index e9fe09e..a718f4f 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -62,7 +62,7 @@
     <!-- The recents task bar light dismiss icon color to be drawn on top of dark backgrounds. -->
     <color name="recents_task_bar_light_dismiss_color">#ffeeeeee</color>
     <!-- The recents task bar dark dismiss icon color to be drawn on top of light backgrounds. -->
-    <color name="recents_task_bar_dark_dismiss_color">#cc000000</color>
+    <color name="recents_task_bar_dark_dismiss_color">#99000000</color>
     <!-- The recents task bar highlight color. -->
     <color name="recents_task_bar_highlight_color">#28ffffff</color>
     <!-- The lock to task button background color. -->
@@ -94,4 +94,10 @@
     <color name="current_user_border_color">@color/system_accent_color</color>
 
     <color name="segmented_button_text_inactive">#99afbdc4</color><!-- 60% -->
+
+    <!-- The "inside" of a notification, reached via longpress -->
+    <color name="notification_guts_bg_color">#ff424242</color><!-- grey 800 -->
+    <color name="notification_guts_title_color">#FFFFFFFF</color>
+    <color name="notification_guts_text_color">#99FFFFFF</color>
+    <color name="notification_guts_btn_color">#FFFFFFFF</color>
 </resources>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 4e536e4..fbd3eb5 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -371,6 +371,9 @@
     <!-- Battery level padding end when in expanded QS (but not on Keyguard) -->
     <dimen name="battery_level_padding_end">4dp</dimen>
 
+    <!-- The top padding of the clear all button -->
+    <dimen name="clear_all_padding_top">4dp</dimen>
+
     <!-- Largest size an avatar might need to be drawn in the user picker, status bar, or
          quick settings header -->
     <dimen name="max_avatar_size">48dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 416cd54..ddbddd1 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -380,8 +380,12 @@
     <!-- Content description of the ringer silent icon in the notification panel for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
     <string name="accessibility_ringer_silent">Ringer silent.</string>
 
+    <!-- Content description to tell the user that this button will remove an application from recents -->
+    <string name="accessibility_recents_item_will_be_dismissed">Dismiss <xliff:g id="app" example="Calendar">%s</xliff:g>.</string>
     <!-- Content description to tell the user an application has been removed from recents -->
     <string name="accessibility_recents_item_dismissed"><xliff:g id="app" example="Calendar">%s</xliff:g> dismissed.</string>
+    <!-- Content description to tell the user an application has been launched from recents -->
+    <string name="accessibility_recents_item_launched">Starting <xliff:g id="app" example="Calendar">%s</xliff:g>.</string>
     <!-- Content description to tell the user a notification has been removed from the notification shade -->
     <string name="accessibility_notification_dismissed">Notification dismissed.</string>
 
@@ -732,6 +736,9 @@
     <!-- Media projection permission dialog permanent grant check box. [CHAR LIMIT=NONE] -->
     <string name="media_projection_remember_text">Don\'t show again</string>
 
+    <!-- The text to clear all notifications. [CHAR LIMIT=60] -->
+    <string name="clear_all_notifications_text">Clear all</string>
+
     <!-- Media projection permission dialog action text. [CHAR LIMIT=60] -->
     <string name="media_projection_action_text">Start now</string>
 </resources>
diff --git a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
index d153d09..6c30c89 100644
--- a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
@@ -21,6 +21,7 @@
 import android.animation.ObjectAnimator;
 import android.animation.ValueAnimator;
 import android.animation.ValueAnimator.AnimatorUpdateListener;
+import android.content.Context;
 import android.graphics.RectF;
 import android.os.Handler;
 import android.util.Log;
@@ -29,6 +30,8 @@
 import android.view.View;
 import android.view.ViewConfiguration;
 import android.view.accessibility.AccessibilityEvent;
+import android.view.animation.AnimationUtils;
+import android.view.animation.Interpolator;
 import android.view.animation.LinearInterpolator;
 
 public class SwipeHelper implements Gefingerpoken {
@@ -44,6 +47,7 @@
     public static final int Y = 1;
 
     private static LinearInterpolator sLinearInterpolator = new LinearInterpolator();
+    private final Interpolator mFastOutLinearInInterpolator;
 
     private float SWIPE_ESCAPE_VELOCITY = 100f; // dp/sec
     private int DEFAULT_ESCAPE_ANIMATION_DURATION = 200; // ms
@@ -72,23 +76,26 @@
     private float mDensityScale;
 
     private boolean mLongPressSent;
-    private View.OnLongClickListener mLongPressListener;
+    private LongPressListener mLongPressListener;
     private Runnable mWatchLongPress;
     private long mLongPressTimeout;
 
-    public SwipeHelper(int swipeDirection, Callback callback, float densityScale,
-            float pagingTouchSlop) {
+    final private int[] mTmpPos = new int[2];
+
+    public SwipeHelper(int swipeDirection, Callback callback, Context context) {
         mCallback = callback;
         mHandler = new Handler();
         mSwipeDirection = swipeDirection;
         mVelocityTracker = VelocityTracker.obtain();
-        mDensityScale = densityScale;
-        mPagingTouchSlop = pagingTouchSlop;
+        mDensityScale =  context.getResources().getDisplayMetrics().density;
+        mPagingTouchSlop = ViewConfiguration.get(context).getScaledPagingTouchSlop();
 
         mLongPressTimeout = (long) (ViewConfiguration.getLongPressTimeout() * 1.5f); // extra long-press!
+        mFastOutLinearInInterpolator = AnimationUtils.loadInterpolator(context,
+                android.R.interpolator.fast_out_linear_in);
     }
 
-    public void setLongPressListener(View.OnLongClickListener listener) {
+    public void setLongPressListener(LongPressListener listener) {
         mLongPressListener = listener;
     }
 
@@ -210,7 +217,7 @@
         }
     }
 
-    public boolean onInterceptTouchEvent(MotionEvent ev) {
+    public boolean onInterceptTouchEvent(final MotionEvent ev) {
         final int action = ev.getAction();
 
         switch (action) {
@@ -232,8 +239,12 @@
                                 public void run() {
                                     if (mCurrView != null && !mLongPressSent) {
                                         mLongPressSent = true;
-                                        mCurrView.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);
-                                        mLongPressListener.onLongClick(mCurrView);
+                                        mCurrView.sendAccessibilityEvent(
+                                                AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);
+                                        mCurrView.getLocationOnScreen(mTmpPos);
+                                        final int x = (int) ev.getRawX() - mTmpPos[0];
+                                        final int y = (int) ev.getRawY() - mTmpPos[1];
+                                        mLongPressListener.onLongPress(mCurrView, x, y);
                                     }
                                 }
                             };
@@ -262,14 +273,16 @@
 
             case MotionEvent.ACTION_UP:
             case MotionEvent.ACTION_CANCEL:
+                final boolean captured = (mDragging || mLongPressSent);
                 mDragging = false;
                 mCurrView = null;
                 mCurrAnimView = null;
                 mLongPressSent = false;
                 removeLongPressCallback();
+                if (captured) return true;
                 break;
         }
-        return mDragging;
+        return mDragging || mLongPressSent;
     }
 
     /**
@@ -277,6 +290,19 @@
      * @param velocity The desired pixels/second speed at which the view should move
      */
     public void dismissChild(final View view, float velocity) {
+        dismissChild(view, velocity, null, 0, false, 0);
+    }
+
+    /**
+     * @param view The view to be dismissed
+     * @param velocity The desired pixels/second speed at which the view should move
+     * @param endAction The action to perform at the end
+     * @param delay The delay after which we should start
+     * @param useAccelerateInterpolator Should an accelerating Interpolator be used
+     * @param fixedDuration If not 0, this exact duration will be taken
+     */
+    public void dismissChild(final View view, float velocity, final Runnable endAction,
+            long delay, boolean useAccelerateInterpolator, long fixedDuration) {
         final View animView = mCallback.getChildContentView(view);
         final boolean canAnimViewBeDismissed = mCallback.canChildBeDismissed(view);
         float newPos;
@@ -289,22 +315,38 @@
         } else {
             newPos = getSize(animView);
         }
-        int duration = MAX_ESCAPE_ANIMATION_DURATION;
-        if (velocity != 0) {
-            duration = Math.min(duration,
-                                (int) (Math.abs(newPos - getTranslation(animView)) * 1000f / Math
-                                        .abs(velocity)));
+        long duration;
+        if (fixedDuration == 0) {
+            duration = MAX_ESCAPE_ANIMATION_DURATION;
+            if (velocity != 0) {
+                duration = Math.min(duration,
+                        (int) (Math.abs(newPos - getTranslation(animView)) * 1000f / Math
+                                .abs(velocity))
+                );
+            } else {
+                duration = DEFAULT_ESCAPE_ANIMATION_DURATION;
+            }
         } else {
-            duration = DEFAULT_ESCAPE_ANIMATION_DURATION;
+            duration = fixedDuration;
         }
 
         animView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
         ObjectAnimator anim = createTranslationAnimation(animView, newPos);
-        anim.setInterpolator(sLinearInterpolator);
+        if (useAccelerateInterpolator) {
+            anim.setInterpolator(mFastOutLinearInInterpolator);
+        } else {
+            anim.setInterpolator(sLinearInterpolator);
+        }
         anim.setDuration(duration);
+        if (delay > 0) {
+            anim.setStartDelay(delay);
+        }
         anim.addListener(new AnimatorListenerAdapter() {
             public void onAnimationEnd(Animator animation) {
                 mCallback.onChildDismissed(view);
+                if (endAction != null) {
+                    endAction.run();
+                }
                 animView.setLayerType(View.LAYER_TYPE_NONE, null);
             }
         });
@@ -426,4 +468,15 @@
          */
         boolean updateSwipeProgress(View animView, boolean dismissable, float swipeProgress);
     }
+
+    /**
+     * Equivalent to View.OnLongClickListener with coordinates
+     */
+    public interface LongPressListener {
+        /**
+         * Equivalent to {@link View.OnLongClickListener#onLongClick(View)} with coordinates
+         * @return whether the longpress was handled
+         */
+        boolean onLongPress(View v, int x, int y);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/recent/RecentsHorizontalScrollView.java b/packages/SystemUI/src/com/android/systemui/recent/RecentsHorizontalScrollView.java
index 72a3341..25a62ae 100644
--- a/packages/SystemUI/src/com/android/systemui/recent/RecentsHorizontalScrollView.java
+++ b/packages/SystemUI/src/com/android/systemui/recent/RecentsHorizontalScrollView.java
@@ -56,9 +56,7 @@
 
     public RecentsHorizontalScrollView(Context context, AttributeSet attrs) {
         super(context, attrs, 0);
-        float densityScale = getResources().getDisplayMetrics().density;
-        float pagingTouchSlop = ViewConfiguration.get(getContext()).getScaledPagingTouchSlop();
-        mSwipeHelper = new SwipeHelper(SwipeHelper.Y, this, densityScale, pagingTouchSlop);
+        mSwipeHelper = new SwipeHelper(SwipeHelper.Y, this, context);
         mFadedEdgeDrawHelper = FadedEdgeDrawHelper.create(context, attrs, this, false);
         mRecycledViews = new HashSet<View>();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/recent/RecentsVerticalScrollView.java b/packages/SystemUI/src/com/android/systemui/recent/RecentsVerticalScrollView.java
index 1213375..e8e9d52 100644
--- a/packages/SystemUI/src/com/android/systemui/recent/RecentsVerticalScrollView.java
+++ b/packages/SystemUI/src/com/android/systemui/recent/RecentsVerticalScrollView.java
@@ -56,9 +56,7 @@
 
     public RecentsVerticalScrollView(Context context, AttributeSet attrs) {
         super(context, attrs, 0);
-        float densityScale = getResources().getDisplayMetrics().density;
-        float pagingTouchSlop = ViewConfiguration.get(getContext()).getScaledPagingTouchSlop();
-        mSwipeHelper = new SwipeHelper(SwipeHelper.X, this, densityScale, pagingTouchSlop);
+        mSwipeHelper = new SwipeHelper(SwipeHelper.X, this, context);
 
         mFadedEdgeDrawHelper = FadedEdgeDrawHelper.create(context, attrs, this, true);
         mRecycledViews = new HashSet<View>();
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskBarView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskBarView.java
index fd636ed..dc8f0db 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskBarView.java
@@ -163,6 +163,7 @@
         } else if (t.applicationIcon != null) {
             mApplicationIcon.setImageDrawable(t.applicationIcon);
         }
+        mApplicationIcon.setContentDescription(t.activityLabel);
         if (!mActivityDescription.getText().toString().equals(t.activityLabel)) {
             mActivityDescription.setText(t.activityLabel);
         }
@@ -176,6 +177,9 @@
                 mConfig.taskBarViewLightTextColor : mConfig.taskBarViewDarkTextColor);
         mDismissButton.setImageDrawable(t.useLightOnPrimaryColor ?
                 mLightDismissDrawable : mDarkDismissDrawable);
+        mDismissButton.setContentDescription(
+                getContext().getString(R.string.accessibility_recents_item_will_be_dismissed,
+                        t.activityLabel));
     }
 
     /** Unbinds the bar view from the task */
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 32b9d8a..f7f96da 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
@@ -27,12 +27,12 @@
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.View;
+import android.view.accessibility.AccessibilityEvent;
 import android.widget.FrameLayout;
 import android.widget.OverScroller;
 import com.android.systemui.R;
 import com.android.systemui.recents.Constants;
 import com.android.systemui.recents.RecentsConfiguration;
-import com.android.systemui.recents.misc.Console;
 import com.android.systemui.recents.misc.DozeTrigger;
 import com.android.systemui.recents.misc.ReferenceCountedTrigger;
 import com.android.systemui.recents.misc.Utilities;
@@ -1045,4 +1045,4 @@
             }
         }
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
index 04fc02c..48fc4ec 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
@@ -16,6 +16,9 @@
 
 package com.android.systemui.statusbar;
 
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.TimeInterpolator;
 import android.app.ActivityManager;
 import android.app.ActivityManagerNative;
 import android.app.Notification;
@@ -33,6 +36,7 @@
 import android.content.res.Configuration;
 import android.database.ContentObserver;
 import android.graphics.Rect;
+import android.graphics.Typeface;
 import android.graphics.drawable.Drawable;
 import android.net.Uri;
 import android.os.Build;
@@ -57,18 +61,17 @@
 import android.view.Display;
 import android.view.IWindowManager;
 import android.view.LayoutInflater;
-import android.view.MenuItem;
 import android.view.MotionEvent;
 import android.view.View;
+import android.view.ViewAnimationUtils;
 import android.view.ViewGroup;
 import android.view.ViewGroup.LayoutParams;
-import android.view.ViewStub;
 import android.view.WindowManager;
 import android.view.WindowManagerGlobal;
+import android.view.animation.AnimationUtils;
 import android.widget.DateTimeView;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
-import android.widget.PopupMenu;
 import android.widget.RemoteViews;
 import android.widget.TextView;
 
@@ -79,6 +82,7 @@
 import com.android.systemui.R;
 import com.android.systemui.RecentsComponent;
 import com.android.systemui.SearchPanelView;
+import com.android.systemui.SwipeHelper;
 import com.android.systemui.SystemUI;
 import com.android.systemui.statusbar.NotificationData.Entry;
 import com.android.systemui.statusbar.phone.KeyguardTouchDelegate;
@@ -141,8 +145,6 @@
     // Search panel
     protected SearchPanelView mSearchPanelView;
 
-    protected PopupMenu mNotificationBlamePopup;
-
     protected int mCurrentUserId = 0;
     final protected SparseArray<UserInfo> mCurrentProfiles = new SparseArray<UserInfo>();
 
@@ -184,6 +186,11 @@
 
     protected int mZenMode;
 
+    // which notification is currently being longpress-examined by the user
+    private View mNotificationGutsExposed;
+
+    private TimeInterpolator mLinearOutSlowIn, mFastOutLinearIn;
+
     /**
      * The {@link StatusBarState} of the status bar.
      */
@@ -192,6 +199,7 @@
     protected boolean mShowLockscreenNotifications;
 
     protected NotificationOverflowContainer mKeyguardIconOverflowContainer;
+    protected DismissView mDismissView;
 
     public boolean isDeviceProvisioned() {
         return mDeviceProvisioned;
@@ -415,6 +423,11 @@
 
         mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
 
+        mLinearOutSlowIn = AnimationUtils.loadInterpolator(mContext,
+                android.R.interpolator.linear_out_slow_in);
+        mFastOutLinearIn = AnimationUtils.loadInterpolator(mContext,
+                android.R.interpolator.fast_out_linear_in);
+
         // Connect in to the status bar manager service
         StatusBarIconList iconList = new StatusBarIconList();
         mCommandQueue = new CommandQueue(this, iconList);
@@ -592,29 +605,61 @@
                 null, UserHandle.CURRENT);
     }
 
-    protected View.OnLongClickListener getNotificationLongClicker() {
-        return new View.OnLongClickListener() {
+    private static final int max(int...args) {
+        switch (args.length) {
+            case 0:
+                return 0;
+            case 1:
+                return args[0];
+            case 2:
+                return args[1] > args[0] ? args[1] : args[0];
+            default:
+                int m = args[0];
+                for (int i = 0; i < args.length; i++) {
+                    if (args[i] > m) {
+                        m = args[i];
+                    }
+                }
+                return m;
+        }
+    }
+
+    protected SwipeHelper.LongPressListener getNotificationLongClicker() {
+        return new SwipeHelper.LongPressListener() {
             @Override
-            public boolean onLongClick(View v) {
+            public boolean onLongPress(View v, int x, int y) {
+                dismissPopups();
+
                 final String packageNameF = (String) v.getTag();
                 if (packageNameF == null) return false;
                 if (v.getWindowToken() == null) return false;
-                mNotificationBlamePopup = new PopupMenu(mContext, v);
-                mNotificationBlamePopup.getMenuInflater().inflate(
-                        R.menu.notification_popup_menu,
-                        mNotificationBlamePopup.getMenu());
-                mNotificationBlamePopup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
-                    public boolean onMenuItemClick(MenuItem item) {
-                        if (item.getItemId() == R.id.notification_inspect_item) {
-                            startApplicationDetailsActivity(packageNameF);
-                            animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE);
-                        } else {
-                            return false;
-                        }
-                        return true;
+
+                // Assume we are a status_bar_notification_row
+                final View guts = v.findViewById(R.id.notification_guts);
+                if (guts == null) return false;
+
+                // Already showing?
+                if (guts.getVisibility() == View.VISIBLE) return false;
+
+                final View button = guts.findViewById(R.id.notification_inspect_item);
+                button.setOnClickListener(new View.OnClickListener() {
+                    public void onClick(View v) {
+                        startApplicationDetailsActivity(packageNameF);
+                        animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE);
                     }
                 });
-                mNotificationBlamePopup.show();
+
+                guts.setVisibility(View.VISIBLE);
+                final double horz = Math.max(v.getWidth() - x, x);
+                final double vert = Math.max(v.getHeight() - y, y);
+                final float r = (float) Math.hypot(horz, vert);
+                final Animator a
+                        = ViewAnimationUtils.createCircularReveal(guts, x, y, 0, r);
+                a.setDuration(400);
+                a.setInterpolator(mLinearOutSlowIn);
+                a.start();
+
+                mNotificationGutsExposed = guts;
 
                 return true;
             }
@@ -622,9 +667,24 @@
     }
 
     public void dismissPopups() {
-        if (mNotificationBlamePopup != null) {
-            mNotificationBlamePopup.dismiss();
-            mNotificationBlamePopup = null;
+        if (mNotificationGutsExposed != null) {
+            final View v = mNotificationGutsExposed;
+            mNotificationGutsExposed = null;
+
+            final int x = (v.getLeft() + v.getRight()) / 2;
+            final int y = (v.getTop() + v.getBottom()) / 2;
+            final Animator a = ViewAnimationUtils.createCircularReveal(v,
+                    x, y, x, 0);
+            a.setDuration(200);
+            a.setInterpolator(mFastOutLinearIn);
+            a.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    super.onAnimationEnd(animation);
+                    v.setVisibility(View.GONE);
+                }
+            });
+            a.start();
         }
     }
 
@@ -909,6 +969,27 @@
             return inflateViews(entry, parent, true);
     }
 
+    private Drawable loadPackageIconDrawable(String pkg, int userId) {
+        Drawable icon = null;
+        try {
+            icon = mContext.getPackageManager().getApplicationIcon(pkg);
+        } catch (PackageManager.NameNotFoundException e) {
+        }
+
+        return icon;
+    }
+
+    private CharSequence loadPackageName(String pkg) {
+        final PackageManager pm = mContext.getPackageManager();
+        try {
+            ApplicationInfo info = pm.getApplicationInfo(pkg,
+                    PackageManager.GET_UNINSTALLED_PACKAGES);
+            if (info != null) return pm.getApplicationLabel(info);
+        } catch (PackageManager.NameNotFoundException e) {
+        }
+        return pkg;
+    }
+
     private boolean inflateViews(NotificationData.Entry entry, ViewGroup parent, boolean isHeadsUp) {
         int maxHeight = mRowMaxHeight;
         StatusBarNotification sbn = entry.notification;
@@ -956,8 +1037,15 @@
                     parent, false);
         }
 
-        // for blaming (see SwipeHelper.setLongPressListener)
+        // the notification inspector (see SwipeHelper.setLongPressListener)
         row.setTag(sbn.getPackageName());
+        final View guts = row.findViewById(R.id.notification_guts);
+        final Drawable pkgicon = loadPackageIconDrawable(entry.notification.getPackageName(),
+                entry.notification.getUserId());
+        final String pkgname = loadPackageName(entry.notification.getPackageName()).toString();
+        ((ImageView) row.findViewById(android.R.id.icon)).setImageDrawable(pkgicon);
+        ((DateTimeView) row.findViewById(R.id.timestamp)).setTime(entry.notification.getPostTime());
+        ((TextView) row.findViewById(R.id.pkgname)).setText(pkgname);
 
         workAroundBadLayerDrawableOpacity(row);
         View vetoButton = updateNotificationVetoButton(row, sbn);
@@ -1201,6 +1289,9 @@
     protected void visibilityChanged(boolean visible) {
         if (mPanelSlightlyVisible != visible) {
             mPanelSlightlyVisible = visible;
+            if (!visible) {
+                dismissPopups();
+            }
             try {
                 if (visible) {
                     mBarService.onPanelRevealed();
@@ -1330,9 +1421,12 @@
         } else {
             mKeyguardIconOverflowContainer.setVisibility(View.GONE);
         }
-        // Move overflow container to last position.
+        // Move overflow container to second last position.
         mStackScroller.changeViewPosition(mKeyguardIconOverflowContainer,
-                mStackScroller.getChildCount() - 1);
+                mStackScroller.getChildCount() - 2);
+
+        // Now move dismissView to the last position.
+        mStackScroller.changeViewPosition(mDismissView, mStackScroller.getChildCount() - 1);
     }
 
     private boolean shouldShowOnKeyguard(StatusBarNotification sbn) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/DismissView.java b/packages/SystemUI/src/com/android/systemui/statusbar/DismissView.java
new file mode 100644
index 0000000..6cc1a57
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/DismissView.java
@@ -0,0 +1,129 @@
+/*
+ * 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.statusbar;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.animation.AnimationUtils;
+import android.view.animation.Interpolator;
+import android.view.animation.PathInterpolator;
+import android.widget.Button;
+import android.widget.TextView;
+import com.android.systemui.R;
+
+public class DismissView extends ExpandableView {
+
+    private Button mClearAllText;
+    private boolean mIsVisible;
+    private final Interpolator mAppearInterpolator = new PathInterpolator(0f, 0.2f, 1f, 1f);
+    private final Interpolator mDisappearInterpolator = new PathInterpolator(0f, 0f, 0.8f, 1f);
+
+    public DismissView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        mClearAllText = (Button) findViewById(R.id.dismiss_text);
+        setInvisible();
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        super.onLayout(changed, left, top, right, bottom);
+        setOutlineProvider(null);
+    }
+
+    @Override
+    public boolean isTransparent() {
+        return true;
+    }
+
+    public void performVisibilityAnimation(boolean nowVisible) {
+        animateText(nowVisible, null /* onFinishedRunnable */);
+    }
+
+    public void performVisibilityAnimation(boolean nowVisible, Runnable onFinishedRunnable) {
+        animateText(nowVisible, onFinishedRunnable);
+    }
+
+    /**
+     * Animate the text to a new visibility.
+     *
+     * @param nowVisible should it now be visible
+     * @param onFinishedRunnable A runnable which should be run when the animation is
+     *        finished.
+     */
+    public void animateText(boolean nowVisible, Runnable onFinishedRunnable) {
+        if (nowVisible != mIsVisible) {
+            // Animate text
+            float endValue = nowVisible ? 1.0f : 0.0f;
+            Interpolator interpolator;
+            if (nowVisible) {
+                interpolator = mAppearInterpolator;
+            } else {
+                interpolator = mDisappearInterpolator;
+            }
+            mClearAllText.animate()
+                    .alpha(endValue)
+                    .setInterpolator(interpolator)
+                    .withEndAction(onFinishedRunnable)
+                    .setDuration(260)
+                    .withLayer();
+            mIsVisible = nowVisible;
+        } else {
+            if (onFinishedRunnable != null) {
+                onFinishedRunnable.run();
+            }
+        }
+    }
+
+    public void setInvisible() {
+        mClearAllText.setAlpha(0.0f);
+        mIsVisible = false;
+    }
+
+    @Override
+    public void performRemoveAnimation(float translationDirection, Runnable onFinishedRunnable) {
+        performVisibilityAnimation(false);
+    }
+
+    @Override
+    public void performAddAnimation(long delay) {
+        performVisibilityAnimation(true);
+    }
+
+    @Override
+    public void setScrimAmount(float scrimAmount) {
+        // We don't need to scrim the dismissView
+    }
+
+    public void setOnButtonClickListener(OnClickListener onClickListener) {
+        mClearAllText.setOnClickListener(onClickListener);
+    }
+
+    @Override
+    public boolean hasOverlappingRendering() {
+        return false;
+    }
+
+    public void cancelAnimation() {
+        mClearAllText.animate().cancel();
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java
index f41e78d..7c6e47c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java
@@ -17,23 +17,71 @@
 package com.android.systemui.statusbar.phone;
 
 import android.content.Context;
+import android.graphics.Canvas;
 import android.graphics.Rect;
 import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewStub;
 import android.widget.FrameLayout;
 
+import com.android.systemui.R;
+
 /**
  * The container with notification stack scroller and quick settings inside.
  */
-public class NotificationsQuickSettingsContainer extends FrameLayout {
+public class NotificationsQuickSettingsContainer extends FrameLayout
+        implements ViewStub.OnInflateListener {
+
+    private View mScrollView;
+    private View mUserSwitcher;
+    private View mStackScroller;
+    private boolean mInflated;
 
     public NotificationsQuickSettingsContainer(Context context, AttributeSet attrs) {
         super(context, attrs);
     }
 
     @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        mScrollView = findViewById(R.id.scroll_view);
+        mStackScroller = findViewById(R.id.notification_stack_scroller);
+        ViewStub userSwitcher = (ViewStub) findViewById(R.id.keyguard_user_switcher);
+        userSwitcher.setOnInflateListener(this);
+        mUserSwitcher = userSwitcher;
+    }
+
+    @Override
     protected boolean fitSystemWindows(Rect insets) {
         setPadding(0, 0, 0, insets.bottom);
         insets.bottom = 0;
         return true;
     }
+
+    @Override
+    protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
+        boolean userSwitcherVisible = mInflated && mUserSwitcher.getVisibility() == View.VISIBLE;
+
+        // Invert the order of the scroll view and user switcher such that the notifications receive
+        // touches first but the panel gets drawn above.
+        if (child == mScrollView) {
+            return super.drawChild(canvas, mStackScroller, drawingTime);
+        } else if (child == mStackScroller) {
+            return super.drawChild(canvas, userSwitcherVisible ? mUserSwitcher : mScrollView,
+                    drawingTime);
+        } else if (child == mUserSwitcher) {
+            return super.drawChild(canvas, userSwitcherVisible ? mScrollView : mUserSwitcher,
+                    drawingTime);
+        } else {
+            return super.drawChild(canvas, child, drawingTime);
+        }
+    }
+
+    @Override
+    public void onInflate(ViewStub stub, View inflated) {
+        if (stub == mUserSwitcher) {
+            mUserSwitcher = inflated;
+            mInflated = true;
+        }
+    }
 }
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 acfdb06..13fad7b3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -118,6 +118,7 @@
 import com.android.systemui.statusbar.ActivatableNotificationView;
 import com.android.systemui.statusbar.BaseStatusBar;
 import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.DismissView;
 import com.android.systemui.statusbar.DragDownHelper;
 import com.android.systemui.statusbar.ExpandableNotificationRow;
 import com.android.systemui.statusbar.GestureRecorder;
@@ -676,6 +677,15 @@
         SpeedBumpView speedBump = (SpeedBumpView) LayoutInflater.from(mContext).inflate(
                         R.layout.status_bar_notification_speed_bump, mStackScroller, false);
         mStackScroller.setSpeedBumpView(speedBump);
+        mDismissView = (DismissView) LayoutInflater.from(mContext).inflate(
+                R.layout.status_bar_notification_dismiss_all, mStackScroller, false);
+        mDismissView.setOnButtonClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                clearAllNotifications();
+            }
+        });
+        mStackScroller.setDismissView(mDismissView);
         mExpandedContents = mStackScroller;
 
         mScrimController = new ScrimController(mStatusBarWindow.findViewById(R.id.scrim_behind),
@@ -828,6 +838,73 @@
         return mStatusBarView;
     }
 
+    private void clearAllNotifications() {
+
+        // animate-swipe all dismissable notifications, then animate the shade closed
+        int numChildren = mStackScroller.getChildCount();
+
+        final ArrayList<View> viewsToHide = new ArrayList<View>(numChildren);
+        for (int i = 0; i < numChildren; i++) {
+            final View child = mStackScroller.getChildAt(i);
+            if (mStackScroller.canChildBeDismissed(child)) {
+                if (child.getVisibility() == View.VISIBLE) {
+                    viewsToHide.add(child);
+                }
+            }
+        }
+        if (viewsToHide.isEmpty()) {
+            animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE);
+            return;
+        }
+
+        mPostCollapseCleanup = new Runnable() {
+            @Override
+            public void run() {
+                try {
+                    mBarService.onClearAllNotifications(mCurrentUserId);
+                } catch (Exception ex) { }
+            }
+        };
+
+        performDismissAllAnimations(viewsToHide);
+
+    }
+
+    private void performDismissAllAnimations(ArrayList<View> hideAnimatedList) {
+        Runnable animationFinishAction = new Runnable() {
+            @Override
+            public void run() {
+                mStackScroller.post(new Runnable() {
+                    @Override
+                    public void run() {
+                        mStackScroller.setDismissAllInProgress(false);
+                    }
+                });
+                animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE);
+            }
+        };
+
+        // let's disable our normal animations
+        mStackScroller.setDismissAllInProgress(true);
+
+        // Decrease the delay for every row we animate to give the sense of
+        // accelerating the swipes
+        int rowDelayDecrement = 10;
+        int currentDelay = 140;
+        int totalDelay = 0;
+        int numItems = hideAnimatedList.size();
+        for (int i = 0; i < numItems; i++) {
+            View view = hideAnimatedList.get(i);
+            Runnable endRunnable = null;
+            if (i == numItems - 1) {
+                endRunnable = animationFinishAction;
+            }
+            mStackScroller.dismissViewAnimated(view, endRunnable, totalDelay, 260);
+            currentDelay = Math.max(50, currentDelay - rowDelayDecrement);
+            totalDelay += currentDelay;
+        }
+    }
+
     /**
      * Hack to improve glyph rasterization for scaled text views.
      */
@@ -1338,10 +1415,32 @@
         }
         updateRowStates();
         updateSpeedbump();
+        updateClearAll();
+
         mNotificationPanel.setQsExpansionEnabled(provisioned && mUserSetup);
         mShadeUpdates.check();
     }
 
+    private void updateClearAll() {
+        boolean showDismissView = false;
+        if (mState != StatusBarState.KEYGUARD) {
+            for (int i = 0; i < mNotificationData.size(); i++) {
+                Entry entry = mNotificationData.get(i);
+                if (entry.row.getParent() == null) {
+                    // This view isn't even added, so the stack scroller doesn't
+                    // know about it. Ignore completely.
+                    continue;
+                }
+                if (entry.row.getVisibility() != View.GONE && entry.expanded != null
+                        && entry.notification.isClearable()) {
+                    showDismissView = true;
+                    break;
+                }
+            }
+        }
+        mStackScroller.updateDismissView(showDismissView);
+    }
+
     private void updateSpeedbump() {
         int speedbumpIndex = -1;
         int currentIndex = 0;
@@ -2021,6 +2120,10 @@
     public void animateCollapsePanels(int flags, boolean force) {
         if (!force &&
                 (mState == StatusBarState.KEYGUARD || mState == StatusBarState.SHADE_LOCKED)) {
+            if (mPostCollapseCleanup != null) {
+                mPostCollapseCleanup.run();
+                mPostCollapseCleanup = null;
+            }
             return;
         }
         if (SPEW) {
@@ -3446,6 +3549,10 @@
     }
 
     public void onTrackingStarted() {
+        if (mPostCollapseCleanup != null) {
+            mPostCollapseCleanup.run();
+            mPostCollapseCleanup = null;
+        }
     }
 
     public void onUnlockHintStarted() {
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 bac1d5b..65359ee 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java
@@ -98,7 +98,7 @@
     private ActivityStarter mActivityStarter;
     private BatteryController mBatteryController;
     private QSPanel mQSPanel;
-    private boolean mHasKeyguardUserSwitcher;
+    private KeyguardUserSwitcher mKeyguardUserSwitcher;
 
     private final Rect mClipBounds = new Rect();
     private final StatusIconClipper mStatusIconClipper = new StatusIconClipper();
@@ -300,6 +300,9 @@
                 ? VISIBLE : GONE);
         mBatteryLevel.setVisibility(mKeyguardShowing && mCharging || mExpanded && !mOverscrolled
                 ? View.VISIBLE : View.GONE);
+        if (mExpanded && !mOverscrolled && mKeyguardUserSwitcherShowing) {
+            mKeyguardUserSwitcher.hide();
+        }
     }
 
     private void updateSystemIconsLayoutParams() {
@@ -377,7 +380,7 @@
         mDateTime.setClickable(mExpanded);
 
         boolean keyguardSwitcherAvailable =
-                mHasKeyguardUserSwitcher && mKeyguardShowing && !mExpanded;
+                mKeyguardUserSwitcher != null && mKeyguardShowing && !mExpanded;
         mMultiUserSwitch.setClickable(mExpanded || keyguardSwitcherAvailable);
         mMultiUserSwitch.setKeyguardMode(keyguardSwitcherAvailable);
         mSystemIconsSuperContainer.setClickable(mExpanded);
@@ -516,7 +519,7 @@
     }
 
     public void setKeyguarUserSwitcher(KeyguardUserSwitcher keyguardUserSwitcher) {
-        mHasKeyguardUserSwitcher = true;
+        mKeyguardUserSwitcher = keyguardUserSwitcher;
         mMultiUserSwitch.setKeyguardUserSwitcher(keyguardUserSwitcher);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpNotificationView.java
index f3ff8ad..ac260db 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpNotificationView.java
@@ -176,11 +176,9 @@
 
     @Override
     public void onAttachedToWindow() {
-        float densityScale = getResources().getDisplayMetrics().density;
         final ViewConfiguration viewConfiguration = ViewConfiguration.get(getContext());
-        float pagingTouchSlop = viewConfiguration.getScaledPagingTouchSlop();
         float touchSlop = viewConfiguration.getScaledTouchSlop();
-        mSwipeHelper = new SwipeHelper(SwipeHelper.X, this, densityScale, pagingTouchSlop);
+        mSwipeHelper = new SwipeHelper(SwipeHelper.X, this, getContext());
         mSwipeHelper.setMaxSwipeProgress(mMaxAlpha);
         mEdgeSwipeHelper = new EdgeSwipeHelper(touchSlop);
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcher.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcher.java
index b0bab48..2be566c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcher.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcher.java
@@ -88,7 +88,7 @@
         }
     }
 
-    private void hide() {
+    public void hide() {
         if (mUserSwitcher != null) {
             // TODO: animate
             mUserSwitcher.setVisibility(View.GONE);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
index 9bcffd1..8b4e79f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -35,6 +35,7 @@
 import com.android.systemui.R;
 import com.android.systemui.SwipeHelper;
 import com.android.systemui.statusbar.ActivatableNotificationView;
+import com.android.systemui.statusbar.DismissView;
 import com.android.systemui.statusbar.ExpandableNotificationRow;
 import com.android.systemui.statusbar.ExpandableView;
 import com.android.systemui.statusbar.SpeedBumpView;
@@ -139,6 +140,8 @@
     private boolean mExpandingNotification;
     private boolean mExpandedInThisMotion;
     private boolean mScrollingEnabled;
+    private DismissView mDismissView;
+    private boolean mDismissAllInProgress;
 
     /**
      * Was the scroller scrolled to the top when the down motion was observed?
@@ -161,7 +164,7 @@
      * motion.
      */
     private int mMaxScrollAfterExpand;
-    private OnLongClickListener mLongClickListener;
+    private SwipeHelper.LongPressListener mLongPressListener;
 
     /**
      * Should in this touch motion only be scrolling allowed? It's true when the scroller was
@@ -172,6 +175,7 @@
     private boolean mInterceptDelegateEnabled;
     private boolean mDelegateToScrollView;
     private boolean mDisallowScrollingInThisMotion;
+    private boolean mDismissWillBeGone;
 
     private ViewTreeObserver.OnPreDrawListener mChildrenUpdater
             = new ViewTreeObserver.OnPreDrawListener() {
@@ -238,8 +242,8 @@
         mOverflingDistance = configuration.getScaledOverflingDistance();
         float densityScale = getResources().getDisplayMetrics().density;
         float pagingTouchSlop = ViewConfiguration.get(getContext()).getScaledPagingTouchSlop();
-        mSwipeHelper = new SwipeHelper(SwipeHelper.X, this, densityScale, pagingTouchSlop);
-        mSwipeHelper.setLongPressListener(mLongClickListener);
+        mSwipeHelper = new SwipeHelper(SwipeHelper.X, this, getContext());
+        mSwipeHelper.setLongPressListener(mLongPressListener);
 
         mSidePaddings = context.getResources()
                 .getDimensionPixelSize(R.dimen.notification_side_padding);
@@ -467,9 +471,9 @@
         return mBottomStackPeekSize;
     }
 
-    public void setLongPressListener(View.OnLongClickListener listener) {
+    public void setLongPressListener(SwipeHelper.LongPressListener listener) {
         mSwipeHelper.setLongPressListener(listener);
-        mLongClickListener = listener;
+        mLongPressListener = listener;
     }
 
     public void setScrollView(ViewGroup scrollView) {
@@ -481,6 +485,9 @@
     }
 
     public void onChildDismissed(View v) {
+        if (mDismissAllInProgress) {
+            return;
+        }
         if (DEBUG) Log.v(TAG, "onChildDismissed: " + v);
         final View veto = v.findViewById(R.id.veto);
         if (veto != null && veto.getVisibility() != View.GONE) {
@@ -627,8 +634,9 @@
         initView(getContext());
     }
 
-    public void dismissRowAnimated(View child, int vel) {
-        mSwipeHelper.dismissChild(child, vel);
+    public void dismissViewAnimated(View child, Runnable endRunnable, int delay, long duration) {
+        child.setClipBounds(null);
+        mSwipeHelper.dismissChild(child, 0, endRunnable, delay, true, duration);
     }
 
     @Override
@@ -1526,12 +1534,13 @@
      * @param newIndex the new index
      */
     public void changeViewPosition(View child, int newIndex) {
-        if (child != null && child.getParent() == this) {
+        int currentIndex = indexOfChild(child);
+        if (child != null && child.getParent() == this && currentIndex != newIndex) {
             mChangePositionInProgress = true;
             removeView(child);
             addView(child, newIndex);
             mChangePositionInProgress = false;
-            if (mIsExpanded && mAnimationsEnabled) {
+            if (mIsExpanded && mAnimationsEnabled && child.getVisibility() != View.GONE) {
                 mChildrenChangingPositions.add(child);
                 mNeedsAnimation = true;
             }
@@ -1918,7 +1927,8 @@
     }
 
     public void goToFullShade() {
-        updateSpeedBump(true);
+        updateSpeedBump(true /* visibility */);
+        mDismissView.setInvisible();
     }
 
     public void cancelExpandHelper() {
@@ -1962,6 +1972,40 @@
         requestChildrenUpdate();
     }
 
+    public void setDismissView(DismissView dismissView) {
+        mDismissView = dismissView;
+        addView(mDismissView);
+    }
+
+    public void updateDismissView(boolean visible) {
+        int oldVisibility = mDismissWillBeGone ? GONE : mDismissView.getVisibility();
+        int newVisibility = visible ? VISIBLE : GONE;
+        if (oldVisibility != newVisibility) {
+            if (oldVisibility == GONE) {
+                if (mDismissWillBeGone) {
+                    mDismissView.cancelAnimation();
+                } else {
+                    mDismissView.setInvisible();
+                    mDismissView.setVisibility(newVisibility);
+                }
+                mDismissWillBeGone = false;
+            } else {
+                mDismissWillBeGone = true;
+                mDismissView.performVisibilityAnimation(false, new Runnable() {
+                    @Override
+                    public void run() {
+                        mDismissView.setVisibility(GONE);
+                        mDismissWillBeGone = false;
+                    }
+                });
+            }
+        }
+    }
+
+    public void setDismissAllInProgress(boolean dismissAllInProgress) {
+        mDismissAllInProgress = dismissAllInProgress;
+    }
+
     /**
      * A listener that is notified when some child locations might have changed.
      */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java
index f48739c..76115a7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java
@@ -21,6 +21,8 @@
 import android.view.View;
 import android.view.ViewGroup;
 
+import com.android.systemui.R;
+import com.android.systemui.statusbar.DismissView;
 import com.android.systemui.statusbar.ExpandableView;
 import com.android.systemui.statusbar.SpeedBumpView;
 
@@ -38,10 +40,13 @@
     private final ViewGroup mHostView;
     private Map<ExpandableView, ViewState> mStateMap;
     private final Rect mClipRect = new Rect();
+    private final int mClearAllTopPadding;
 
     public StackScrollState(ViewGroup hostView) {
         mHostView = hostView;
         mStateMap = new HashMap<ExpandableView, ViewState>();
+        mClearAllTopPadding = hostView.getContext().getResources().getDimensionPixelSize(
+                R.dimen.clear_all_padding_top);
     }
 
     public ViewGroup getHostView() {
@@ -90,6 +95,7 @@
             if (!state.gone) {
                 float alpha = child.getAlpha();
                 float yTranslation = child.getTranslationY();
+                float xTranslation = child.getTranslationX();
                 float zTranslation = child.getTranslationZ();
                 float scale = child.getScaleX();
                 int height = child.getActualHeight();
@@ -99,7 +105,7 @@
                 float newScale = state.scale;
                 int newHeight = state.height;
                 boolean becomesInvisible = newAlpha == 0.0f;
-                if (alpha != newAlpha) {
+                if (alpha != newAlpha && xTranslation == 0) {
                     // apply layer type
                     boolean becomesFullyVisible = newAlpha == 1.0f;
                     boolean newLayerTypeIsHardware = !becomesInvisible && !becomesFullyVisible;
@@ -167,6 +173,10 @@
                 if(child instanceof SpeedBumpView) {
                     float lineEnd = newYTranslation + newHeight / 2;
                     performSpeedBumpAnimation(i, (SpeedBumpView) child, lineEnd);
+                } else if (child instanceof DismissView) {
+                    DismissView dismissView = (DismissView) child;
+                    boolean visible = state.topOverLap < mClearAllTopPadding;
+                    dismissView.performVisibilityAnimation(visible);
                 }
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java
index 0c84675..71524ec 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java
@@ -159,7 +159,7 @@
         }
 
         // start alpha animation
-        if (alphaChanging) {
+        if (alphaChanging && child.getTranslationX() == 0) {
             startAlphaAnimation(child, viewState, delay);
         }
 
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index f38b280..3f95ae2 100755
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -3098,7 +3098,8 @@
         final BatteryStatsImpl stats = mBatteryStatsService.getActiveStatistics();
         if (resumed) {
             if (mUsageStatsService != null) {
-                mUsageStatsService.reportEvent(component.realActivity, System.currentTimeMillis(),
+                mUsageStatsService.reportEvent(component.realActivity, component.userId,
+                        System.currentTimeMillis(),
                         UsageStats.Event.MOVE_TO_FOREGROUND);
             }
             synchronized (stats) {
@@ -3106,7 +3107,8 @@
             }
         } else {
             if (mUsageStatsService != null) {
-                mUsageStatsService.reportEvent(component.realActivity, System.currentTimeMillis(),
+                mUsageStatsService.reportEvent(component.realActivity, component.userId,
+                        System.currentTimeMillis(),
                         UsageStats.Event.MOVE_TO_BACKGROUND);
             }
             synchronized (stats) {
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index 390fbba..1c0057b 100755
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -3789,7 +3789,9 @@
                 if (r.app == app) {
                     Slog.w(TAG, "  Force finishing activity "
                             + r.intent.getComponent().flattenToShortString());
-                    finishActivityLocked(r, Activity.RESULT_CANCELED, null, "crashed", false);
+                    // Force the destroy to skip right to removal.
+                    r.app = null;
+                    finishCurrentActivityLocked(r, FINISH_IMMEDIATELY, false);
                 }
             }
         }
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index db915e2..6036bcf 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -24,10 +24,11 @@
 import android.content.Context;
 import android.content.pm.IPackageDeleteObserver;
 import android.content.pm.IPackageInstaller;
-import android.content.pm.IPackageInstallerObserver;
+import android.content.pm.IPackageInstallerCallback;
 import android.content.pm.IPackageInstallerSession;
 import android.content.pm.InstallSessionInfo;
 import android.content.pm.InstallSessionParams;
+import android.content.pm.PackageManager;
 import android.os.Binder;
 import android.os.FileUtils;
 import android.os.HandlerThread;
@@ -54,6 +55,7 @@
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Objects;
 
 public class PackageInstallerService extends IPackageInstaller.Stub {
     private static final String TAG = "PackageInstaller";
@@ -80,7 +82,7 @@
     @GuardedBy("mSessions")
     private final SparseArray<PackageInstallerSession> mHistoricalSessions = new SparseArray<>();
 
-    private RemoteCallbackList<IPackageInstallerObserver> mObservers = new RemoteCallbackList<>();
+    private RemoteCallbackList<IPackageInstallerCallback> mCallbacks = new RemoteCallbackList<>();
 
     private static final FilenameFilter sStageFilter = new FilenameFilter() {
         @Override
@@ -152,8 +154,7 @@
     }
 
     @Override
-    public int createSession(String installerPackageName, InstallSessionParams params,
-            int userId) {
+    public int createSession(InstallSessionParams params, String installerPackageName, int userId) {
         final int callingUid = Binder.getCallingUid();
         mPm.enforceCrossUserPermission(callingUid, userId, true, "createSession");
 
@@ -172,14 +173,18 @@
             params.installFlags |= INSTALL_REPLACE_EXISTING;
         }
 
-        if (params.mode == InstallSessionParams.MODE_INVALID) {
-            throw new IllegalArgumentException("Params must have valid mode set");
+        switch (params.mode) {
+            case InstallSessionParams.MODE_FULL_INSTALL:
+            case InstallSessionParams.MODE_INHERIT_EXISTING:
+                break;
+            default:
+                throw new IllegalArgumentException("Params must have valid mode set");
         }
 
         // Sanity check that install could fit
-        if (params.deltaSize > 0) {
+        if (params.sizeBytes > 0) {
             try {
-                mPm.freeStorage(params.deltaSize);
+                mPm.freeStorage(params.sizeBytes);
             } catch (IOException e) {
                 throw ExceptionUtils.wrap(e);
             }
@@ -248,8 +253,22 @@
     }
 
     @Override
-    public List<InstallSessionInfo> getSessions(int userId) {
-        mPm.enforceCrossUserPermission(Binder.getCallingUid(), userId, true, "getSessions");
+    public InstallSessionInfo getSessionInfo(int sessionId) {
+        synchronized (mSessions) {
+            final PackageInstallerSession session = mSessions.get(sessionId);
+            final boolean isOwner = (session != null)
+                    && (session.installerUid == Binder.getCallingUid());
+            if (!isOwner) {
+                enforceCallerCanReadSessions();
+            }
+            return session != null ? session.generateInfo() : null;
+        }
+    }
+
+    @Override
+    public List<InstallSessionInfo> getAllSessions(int userId) {
+        mPm.enforceCrossUserPermission(Binder.getCallingUid(), userId, true, "getAllSessions");
+        enforceCallerCanReadSessions();
 
         final List<InstallSessionInfo> result = new ArrayList<>();
         synchronized (mSessions) {
@@ -264,9 +283,29 @@
     }
 
     @Override
+    public List<InstallSessionInfo> getMySessions(String installerPackageName, int userId) {
+        mPm.enforceCrossUserPermission(Binder.getCallingUid(), userId, true, "getMySessions");
+        mAppOps.checkPackage(Binder.getCallingUid(), installerPackageName);
+
+        final List<InstallSessionInfo> result = new ArrayList<>();
+        synchronized (mSessions) {
+            for (int i = 0; i < mSessions.size(); i++) {
+                final PackageInstallerSession session = mSessions.valueAt(i);
+                if (Objects.equals(session.installerPackageName, installerPackageName)
+                        && session.userId == userId) {
+                    result.add(session.generateInfo());
+                }
+            }
+        }
+        return result;
+    }
+
+    @Override
     public void uninstall(String packageName, int flags, IPackageDeleteObserver observer,
             int userId) {
         mPm.enforceCrossUserPermission(Binder.getCallingUid(), userId, true, "uninstall");
+
+        // TODO: enforce installer of record or permission
         mPm.deletePackageAsUser(packageName, observer, userId, flags);
     }
 
@@ -280,17 +319,16 @@
     }
 
     @Override
-    public void registerObserver(IPackageInstallerObserver observer, int userId) {
-        mPm.enforceCrossUserPermission(Binder.getCallingUid(), userId, true, "registerObserver");
+    public void registerCallback(IPackageInstallerCallback callback, int userId) {
+        mPm.enforceCrossUserPermission(Binder.getCallingUid(), userId, true, "registerCallback");
+        enforceCallerCanReadSessions();
 
-        // TODO: consider restricting to active launcher app only
-        mObservers.register(observer, new UserHandle(userId));
+        mCallbacks.register(callback, new UserHandle(userId));
     }
 
     @Override
-    public void unregisterObserver(IPackageInstallerObserver observer, int userId) {
-        mPm.enforceCrossUserPermission(Binder.getCallingUid(), userId, true, "unregisterObserver");
-        mObservers.unregister(observer);
+    public void unregisterCallback(IPackageInstallerCallback callback) {
+        mCallbacks.unregister(callback);
     }
 
     private int getSessionUserId(int sessionId) {
@@ -299,52 +337,68 @@
         }
     }
 
-    private void notifySessionCreated(InstallSessionInfo info) {
-        final int userId = getSessionUserId(info.sessionId);
-        final int n = mObservers.beginBroadcast();
-        for (int i = 0; i < n; i++) {
-            final IPackageInstallerObserver observer = mObservers.getBroadcastItem(i);
-            final UserHandle user = (UserHandle) mObservers.getBroadcastCookie(i);
-            if (userId == user.getIdentifier()) {
-                try {
-                    observer.onSessionCreated(info);
-                } catch (RemoteException ignored) {
-                }
-            }
+    /**
+     * We allow those with permission, or the current home app.
+     */
+    private void enforceCallerCanReadSessions() {
+        final boolean hasPermission = (mContext.checkCallingOrSelfPermission(
+                android.Manifest.permission.READ_INSTALL_SESSIONS)
+                == PackageManager.PERMISSION_GRANTED);
+        final boolean isHomeApp = mPm.checkCallerIsHomeApp();
+        if (hasPermission || isHomeApp) {
+            return;
+        } else {
+            throw new SecurityException("Caller must be current home app to read install sessions");
         }
-        mObservers.finishBroadcast();
     }
 
-    private void notifySessionProgress(int sessionId, int progress) {
-        final int userId = getSessionUserId(sessionId);
-        final int n = mObservers.beginBroadcast();
+    private void notifySessionCreated(InstallSessionInfo info) {
+        final int userId = getSessionUserId(info.sessionId);
+        final int n = mCallbacks.beginBroadcast();
         for (int i = 0; i < n; i++) {
-            final IPackageInstallerObserver observer = mObservers.getBroadcastItem(i);
-            final UserHandle user = (UserHandle) mObservers.getBroadcastCookie(i);
+            final IPackageInstallerCallback callback = mCallbacks.getBroadcastItem(i);
+            final UserHandle user = (UserHandle) mCallbacks.getBroadcastCookie(i);
+            // TODO: dispatch notifications for slave profiles
             if (userId == user.getIdentifier()) {
                 try {
-                    observer.onSessionProgress(sessionId, progress);
+                    callback.onSessionCreated(info.sessionId);
                 } catch (RemoteException ignored) {
                 }
             }
         }
-        mObservers.finishBroadcast();
+        mCallbacks.finishBroadcast();
+    }
+
+    private void notifySessionProgressChanged(int sessionId, float progress) {
+        final int userId = getSessionUserId(sessionId);
+        final int n = mCallbacks.beginBroadcast();
+        for (int i = 0; i < n; i++) {
+            final IPackageInstallerCallback callback = mCallbacks.getBroadcastItem(i);
+            final UserHandle user = (UserHandle) mCallbacks.getBroadcastCookie(i);
+            if (userId == user.getIdentifier()) {
+                try {
+                    callback.onSessionProgressChanged(sessionId, progress);
+                } catch (RemoteException ignored) {
+                }
+            }
+        }
+        mCallbacks.finishBroadcast();
     }
 
     private void notifySessionFinished(int sessionId, boolean success) {
         final int userId = getSessionUserId(sessionId);
-        final int n = mObservers.beginBroadcast();
+        final int n = mCallbacks.beginBroadcast();
         for (int i = 0; i < n; i++) {
-            final IPackageInstallerObserver observer = mObservers.getBroadcastItem(i);
-            final UserHandle user = (UserHandle) mObservers.getBroadcastCookie(i);
+            final IPackageInstallerCallback callback = mCallbacks.getBroadcastItem(i);
+            final UserHandle user = (UserHandle) mCallbacks.getBroadcastCookie(i);
             if (userId == user.getIdentifier()) {
                 try {
-                    observer.onSessionFinished(sessionId, success);
+                    callback.onSessionFinished(sessionId, success);
                 } catch (RemoteException ignored) {
                 }
             }
         }
-        mObservers.finishBroadcast();
+        mCallbacks.finishBroadcast();
     }
 
     void dump(IndentingPrintWriter pw) {
@@ -374,8 +428,8 @@
     }
 
     class Callback {
-        public void onSessionProgress(PackageInstallerSession session, int progress) {
-            notifySessionProgress(session.sessionId, progress);
+        public void onSessionProgressChanged(PackageInstallerSession session, float progress) {
+            notifySessionProgressChanged(session.sessionId, progress);
         }
 
         public void onSessionFinished(PackageInstallerSession session, boolean success) {
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 0e6a3f0..06e1d53 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -71,6 +71,8 @@
     // TODO: enforce INSTALL_ALLOW_DOWNGRADE
     // TODO: handle INSTALL_EXTERNAL, INSTALL_INTERNAL
 
+    // TODO: treat INHERIT_EXISTING as installExistingPackage()
+
     private final PackageInstallerService.Callback mCallback;
     private final PackageManagerService mPm;
     private final Handler mHandler;
@@ -84,7 +86,7 @@
     public final long createdMillis;
     public final File sessionStageDir;
 
-    private static final int MSG_INSTALL = 0;
+    private static final int MSG_COMMIT = 0;
 
     private Handler.Callback mHandlerCallback = new Handler.Callback() {
         @Override
@@ -95,7 +97,7 @@
                 }
 
                 try {
-                    installLocked();
+                    commitLocked();
                 } catch (PackageManagerException e) {
                     Slog.e(TAG, "Install failed: " + e);
                     destroyInternal();
@@ -114,8 +116,8 @@
 
     private final Object mLock = new Object();
 
-    private int mClientProgress;
-    private int mProgress = 0;
+    private float mClientProgress;
+    private float mProgress = 0;
 
     private String mPackageName;
     private int mVersionCode;
@@ -168,23 +170,23 @@
         info.progress = mProgress;
 
         info.mode = params.mode;
-        info.packageName = params.packageName;
-        info.icon = params.icon;
-        info.title = params.title;
+        info.sizeBytes = params.sizeBytes;
+        info.appPackageName = params.appPackageName;
+        info.appIcon = params.appIcon;
+        info.appLabel = params.appLabel;
 
         return info;
     }
 
     @Override
-    public void setClientProgress(int progress) {
+    public void setClientProgress(float progress) {
         mClientProgress = progress;
-        mProgress = MathUtils.constrain(
-                (int) (((float) mClientProgress) / ((float) params.progressMax)) * 80, 0, 80);
-        mCallback.onSessionProgress(this, mProgress);
+        mProgress = MathUtils.constrain(mClientProgress * 0.8f, 0f, 0.8f);
+        mCallback.onSessionProgressChanged(this, mProgress);
     }
 
     @Override
-    public void addClientProgress(int progress) {
+    public void addClientProgress(float progress) {
         setClientProgress(mClientProgress + progress);
     }
 
@@ -250,12 +252,12 @@
     }
 
     @Override
-    public void install(IPackageInstallObserver2 observer) {
+    public void commit(IPackageInstallObserver2 observer) {
         Preconditions.checkNotNull(observer);
-        mHandler.obtainMessage(MSG_INSTALL, observer).sendToTarget();
+        mHandler.obtainMessage(MSG_COMMIT, observer).sendToTarget();
     }
 
-    private void installLocked() throws PackageManagerException {
+    private void commitLocked() throws PackageManagerException {
         if (mInvalid) {
             throw new PackageManagerException(INSTALL_FAILED_ALREADY_EXISTS, "Invalid session");
         }
@@ -295,7 +297,7 @@
         }
 
         // TODO: surface more granular state from dexopt
-        mCallback.onSessionProgress(this, 90);
+        mCallback.onSessionProgressChanged(this, 0.9f);
 
         // TODO: for ASEC based applications, grow and stream in packages
 
@@ -458,7 +460,12 @@
     }
 
     @Override
-    public void destroy() {
+    public void close() {
+        // Currently ignored
+    }
+
+    @Override
+    public void abandon() {
         try {
             destroyInternal();
         } finally {
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 7ddde62..9878d1c 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -11686,6 +11686,47 @@
                         preferred.activityInfo.name);
     }
 
+    /**
+     * Check if calling UID is the current home app. This handles both the case
+     * where the user has selected a specific home app, and where there is only
+     * one home app.
+     */
+    public boolean checkCallerIsHomeApp() {
+        final Intent intent = new Intent(Intent.ACTION_MAIN);
+        intent.addCategory(Intent.CATEGORY_HOME);
+
+        final int callingUid = Binder.getCallingUid();
+        final int callingUserId = UserHandle.getCallingUserId();
+        final List<ResolveInfo> allHomes = queryIntentActivities(intent, null, 0, callingUserId);
+        final ResolveInfo preferredHome = findPreferredActivity(intent, null, 0, allHomes, 0, true,
+                false, false, callingUserId);
+
+        if (preferredHome != null) {
+            if (callingUid == preferredHome.activityInfo.applicationInfo.uid) {
+                return true;
+            }
+        } else {
+            for (ResolveInfo info : allHomes) {
+                if (callingUid == info.activityInfo.applicationInfo.uid) {
+                    return true;
+                }
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Enforce that calling UID is the current home app. This handles both the
+     * case where the user has selected a specific home app, and where there is
+     * only one home app.
+     */
+    public void enforceCallerIsHomeApp() {
+        if (!checkCallerIsHomeApp()) {
+            throw new SecurityException("Caller is not currently selected home app");
+        }
+    }
+
     @Override
     public void setApplicationEnabledSetting(String appPackageName,
             int newState, int flags, int userId, String callingPackage) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index e0612eb..dc55e6d 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -4291,9 +4291,11 @@
             if (Settings.Secure.getIntForUser(resolver, Settings.Secure.USER_SETUP_COMPLETE, 0,
                     userHandle) != 0) {
                 DevicePolicyData policy = getUserData(userHandle);
-                policy.mUserSetupComplete = true;
-                synchronized (this) {
-                    saveSettingsLocked(userHandle);
+                if (!policy.mUserSetupComplete) {
+                    policy.mUserSetupComplete = true;
+                    synchronized (this) {
+                        saveSettingsLocked(userHandle);
+                    }
                 }
             }
         }
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 9e67908..0cc6e0f 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -352,6 +352,9 @@
         mFirstBoot = mPackageManagerService.isFirstBoot();
         mPackageManager = mSystemContext.getPackageManager();
 
+        Slog.i(TAG, "User Service");
+        ServiceManager.addService(Context.USER_SERVICE, UserManagerService.getInstance());
+
         // Initialize attribute cache used to cache resources from packages.
         AttributeCache.init(mSystemContext);
 
@@ -434,10 +437,6 @@
             Slog.i(TAG, "Entropy Mixer");
             ServiceManager.addService("entropy", new EntropyMixer(context));
 
-            Slog.i(TAG, "User Service");
-            ServiceManager.addService(Context.USER_SERVICE,
-                    UserManagerService.getInstance());
-
             mContentResolver = context.getContentResolver();
 
             // The AccountManager must come before the ContentService
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 4018def..1c20d5d 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -19,52 +19,56 @@
 import android.Manifest;
 import android.app.AppOpsManager;
 import android.app.usage.IUsageStatsManager;
-import android.app.usage.PackageUsageStats;
-import android.app.usage.TimeSparseArray;
 import android.app.usage.UsageStats;
 import android.app.usage.UsageStatsManager;
 import android.app.usage.UsageStatsManagerInternal;
+import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.pm.PackageManager;
+import android.content.pm.UserInfo;
 import android.os.Binder;
 import android.os.Environment;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
+import android.os.UserHandle;
+import android.os.UserManager;
 import android.util.ArraySet;
 import android.util.Slog;
+import android.util.SparseArray;
+
 import com.android.internal.os.BackgroundThread;
 import com.android.server.SystemService;
 
 import java.io.File;
-import java.io.IOException;
-import java.text.SimpleDateFormat;
-import java.util.Calendar;
+import java.util.Arrays;
+import java.util.List;
 
-public class UsageStatsService extends SystemService {
+public class UsageStatsService extends SystemService implements
+        UserUsageStatsService.StatsUpdatedListener {
     static final String TAG = "UsageStatsService";
 
     static final boolean DEBUG = false;
     private static final long TEN_SECONDS = 10 * 1000;
     private static final long TWENTY_MINUTES = 20 * 60 * 1000;
     private static final long FLUSH_INTERVAL = DEBUG ? TEN_SECONDS : TWENTY_MINUTES;
-    private static final int USAGE_STAT_RESULT_LIMIT = 10;
-    private static final SimpleDateFormat sDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+    static final int USAGE_STAT_RESULT_LIMIT = 10;
 
     // Handler message types.
     static final int MSG_REPORT_EVENT = 0;
     static final int MSG_FLUSH_TO_DISK = 1;
+    static final int MSG_REMOVE_USER = 2;
 
-    final Object mLock = new Object();
+    private final Object mLock = new Object();
     Handler mHandler;
     AppOpsManager mAppOps;
+    UserManager mUserManager;
 
-    private UsageStatsDatabase mDatabase;
-    private UsageStats[] mCurrentStats = new UsageStats[UsageStatsManager.BUCKET_COUNT];
-    private TimeSparseArray<UsageStats.Event> mCurrentEvents = new TimeSparseArray<>();
-    private boolean mStatsChanged = false;
-    private Calendar mDailyExpiryDate;
+    private final SparseArray<UserUsageStatsService> mUserState = new SparseArray<>();
+    private File mUsageStatsDir;
 
     public UsageStatsService(Context context) {
         super(context);
@@ -72,115 +76,96 @@
 
     @Override
     public void onStart() {
-        mDailyExpiryDate = Calendar.getInstance();
         mAppOps = (AppOpsManager) getContext().getSystemService(Context.APP_OPS_SERVICE);
+        mUserManager = (UserManager) getContext().getSystemService(Context.USER_SERVICE);
         mHandler = new H(BackgroundThread.get().getLooper());
 
         File systemDataDir = new File(Environment.getDataDirectory(), "system");
-        mDatabase = new UsageStatsDatabase(new File(systemDataDir, "usagestats"));
-        mDatabase.init();
+        mUsageStatsDir = new File(systemDataDir, "usagestats");
+        mUsageStatsDir.mkdirs();
+        if (!mUsageStatsDir.exists()) {
+            throw new IllegalStateException("Usage stats directory does not exist: "
+                    + mUsageStatsDir.getAbsolutePath());
+        }
+
+        getContext().registerReceiver(new UserRemovedReceiver(),
+                new IntentFilter(Intent.ACTION_USER_REMOVED));
 
         synchronized (mLock) {
-            initLocked();
+            cleanUpRemovedUsersLocked();
         }
 
         publishLocalService(UsageStatsManagerInternal.class, new LocalService());
         publishBinderService(Context.USAGE_STATS_SERVICE, new BinderService());
     }
 
-    private void initLocked() {
-        int nullCount = 0;
-        for (int i = 0; i < mCurrentStats.length; i++) {
-            mCurrentStats[i] = mDatabase.getLatestUsageStats(i);
-            if (mCurrentStats[i] == null) {
-                nullCount++;
-            }
-        }
+    private class UserRemovedReceiver extends BroadcastReceiver {
 
-        if (nullCount > 0) {
-            if (nullCount != mCurrentStats.length) {
-                // This is weird, but we shouldn't fail if something like this
-                // happens.
-                Slog.w(TAG, "Some stats have no latest available");
-            } else {
-                // This must be first boot.
-            }
-
-            // By calling loadActiveStatsLocked, we will
-            // generate new stats for each bucket.
-            loadActiveStatsLocked();
-        } else {
-            // Set up the expiry date to be one day from the latest daily stat.
-            // This may actually be today and we will rollover on the first event
-            // that is reported.
-            mDailyExpiryDate.setTimeInMillis(
-                    mCurrentStats[UsageStatsManager.DAILY_BUCKET].mBeginTimeStamp);
-            mDailyExpiryDate.add(Calendar.DAY_OF_YEAR, 1);
-            UsageStatsUtils.truncateDateTo(UsageStatsManager.DAILY_BUCKET, mDailyExpiryDate);
-            Slog.i(TAG, "Rollover scheduled for " + sDateFormat.format(mDailyExpiryDate.getTime()));
-        }
-
-        // Now close off any events that were open at the time this was saved.
-        for (UsageStats stat : mCurrentStats) {
-            final int pkgCount = stat.getPackageCount();
-            for (int i = 0; i < pkgCount; i++) {
-                PackageUsageStats pkgStats = stat.getPackage(i);
-                if (pkgStats.mLastEvent == UsageStats.Event.MOVE_TO_FOREGROUND ||
-                        pkgStats.mLastEvent == UsageStats.Event.CONTINUE_PREVIOUS_DAY) {
-                    updateStatsLocked(stat, pkgStats.mPackageName, stat.mLastTimeSaved,
-                            UsageStats.Event.END_OF_DAY);
-                    notifyStatsChangedLocked();
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (intent != null && intent.getAction().equals(Intent.ACTION_USER_REMOVED)) {
+                final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
+                if (userId >= 0) {
+                    mHandler.obtainMessage(MSG_REMOVE_USER, userId, 0).sendToTarget();
                 }
             }
         }
     }
 
-    private void rolloverStatsLocked() {
-        final long startTime = System.currentTimeMillis();
-        Slog.i(TAG, "Rolling over usage stats");
-
-        // Finish any ongoing events with an END_OF_DAY event. Make a note of which components
-        // need a new CONTINUE_PREVIOUS_DAY entry.
-        ArraySet<String> continuePreviousDay = new ArraySet<>();
-        for (UsageStats stat : mCurrentStats) {
-            final int pkgCount = stat.getPackageCount();
-            for (int i = 0; i < pkgCount; i++) {
-                PackageUsageStats pkgStats = stat.getPackage(i);
-                if (pkgStats.mLastEvent == UsageStats.Event.MOVE_TO_FOREGROUND ||
-                        pkgStats.mLastEvent == UsageStats.Event.CONTINUE_PREVIOUS_DAY) {
-                    continuePreviousDay.add(pkgStats.mPackageName);
-                    updateStatsLocked(stat, pkgStats.mPackageName,
-                            mDailyExpiryDate.getTimeInMillis() - 1, UsageStats.Event.END_OF_DAY);
-                    mStatsChanged = true;
-                }
-            }
-        }
-
-        persistActiveStatsLocked();
-        mDatabase.prune();
-        loadActiveStatsLocked();
-
-        final int continueCount = continuePreviousDay.size();
-        for (int i = 0; i < continueCount; i++) {
-            String name = continuePreviousDay.valueAt(i);
-            for (UsageStats stat : mCurrentStats) {
-                updateStatsLocked(stat, name,
-                        mCurrentStats[UsageStatsManager.DAILY_BUCKET].mBeginTimeStamp,
-                        UsageStats.Event.CONTINUE_PREVIOUS_DAY);
-                mStatsChanged = true;
-            }
-        }
-        persistActiveStatsLocked();
-
-        final long totalTime = System.currentTimeMillis() - startTime;
-        Slog.i(TAG, "Rolling over usage stats complete. Took " + totalTime + " milliseconds");
+    @Override
+    public void onStatsUpdated() {
+        mHandler.sendEmptyMessageDelayed(MSG_FLUSH_TO_DISK, FLUSH_INTERVAL);
     }
 
-    private void notifyStatsChangedLocked() {
-        if (!mStatsChanged) {
-            mStatsChanged = true;
-            mHandler.sendEmptyMessageDelayed(MSG_FLUSH_TO_DISK, FLUSH_INTERVAL);
+    private void cleanUpRemovedUsersLocked() {
+        final List<UserInfo> users = mUserManager.getUsers(true);
+        if (users == null || users.size() == 0) {
+            throw new IllegalStateException("There can't be no users");
         }
+
+        ArraySet<String> toDelete = new ArraySet<>();
+        String[] fileNames = mUsageStatsDir.list();
+        if (fileNames == null) {
+            // No users to delete.
+            return;
+        }
+
+        toDelete.addAll(Arrays.asList(fileNames));
+
+        final int userCount = users.size();
+        for (int i = 0; i < userCount; i++) {
+            final UserInfo userInfo = users.get(i);
+            toDelete.remove(Integer.toString(userInfo.id));
+        }
+
+        final int deleteCount = toDelete.size();
+        for (int i = 0; i < deleteCount; i++) {
+            deleteRecursively(new File(mUsageStatsDir, toDelete.valueAt(i)));
+        }
+    }
+
+    private static void deleteRecursively(File f) {
+        File[] files = f.listFiles();
+        if (files != null) {
+            for (File subFile : files) {
+                deleteRecursively(subFile);
+            }
+        }
+
+        if (!f.delete()) {
+            Slog.e(TAG, "Failed to delete " + f);
+        }
+    }
+
+    private UserUsageStatsService getUserDataAndInitializeIfNeededLocked(int userId) {
+        UserUsageStatsService service = mUserState.get(userId);
+        if (service == null) {
+            service = new UserUsageStatsService(userId,
+                    new File(mUsageStatsDir, Integer.toString(userId)), this);
+            service.init();
+            mUserState.put(userId, service);
+        }
+        return service;
     }
 
     /**
@@ -189,58 +174,45 @@
     void shutdown() {
         synchronized (mLock) {
             mHandler.removeMessages(MSG_REPORT_EVENT);
-            mHandler.removeMessages(MSG_FLUSH_TO_DISK);
-            persistActiveStatsLocked();
-        }
-    }
-
-    private static String eventToString(int eventType) {
-        switch (eventType) {
-            case UsageStats.Event.NONE:
-                return "NONE";
-            case UsageStats.Event.MOVE_TO_BACKGROUND:
-                return "MOVE_TO_BACKGROUND";
-            case UsageStats.Event.MOVE_TO_FOREGROUND:
-                return "MOVE_TO_FOREGROUND";
-            case UsageStats.Event.END_OF_DAY:
-                return "END_OF_DAY";
-            case UsageStats.Event.CONTINUE_PREVIOUS_DAY:
-                return "CONTINUE_PREVIOUS_DAY";
-            default:
-                return "UNKNOWN";
+            flushToDiskLocked();
         }
     }
 
     /**
      * Called by the Binder stub.
      */
-    void reportEvent(UsageStats.Event event) {
+    void reportEvent(UsageStats.Event event, int userId) {
         synchronized (mLock) {
-            if (DEBUG) {
-                Slog.d(TAG, "Got usage event for " + event.packageName
-                        + "[" + event.timeStamp + "]: "
-                        + eventToString(event.eventType));
-            }
-
-            if (event.timeStamp >= mDailyExpiryDate.getTimeInMillis()) {
-                // Need to rollover
-                rolloverStatsLocked();
-            }
-
-            mCurrentEvents.append(event.timeStamp, event);
-
-            for (UsageStats stats : mCurrentStats) {
-                updateStatsLocked(stats, event.packageName, event.timeStamp, event.eventType);
-            }
-            notifyStatsChangedLocked();
+            final UserUsageStatsService service = getUserDataAndInitializeIfNeededLocked(userId);
+            service.reportEvent(event);
         }
     }
 
     /**
      * Called by the Binder stub.
      */
-    UsageStats[] getUsageStats(int bucketType, long beginTime) {
-        if (bucketType < 0 || bucketType >= mCurrentStats.length) {
+    void flushToDisk() {
+        synchronized (mLock) {
+            flushToDiskLocked();
+        }
+    }
+
+    /**
+     * Called by the Binder stub.
+     */
+    void removeUser(int userId) {
+        synchronized (mLock) {
+            Slog.i(TAG, "Removing user " + userId + " and all data.");
+            mUserState.remove(userId);
+            cleanUpRemovedUsersLocked();
+        }
+    }
+
+    /**
+     * Called by the Binder stub.
+     */
+    UsageStats[] getUsageStats(int userId, int bucketType, long beginTime) {
+        if (bucketType < 0 || bucketType >= UsageStatsManager.BUCKET_COUNT) {
             return UsageStats.EMPTY_STATS;
         }
 
@@ -250,110 +222,26 @@
         }
 
         synchronized (mLock) {
-            if (beginTime >= mCurrentStats[bucketType].mEndTimeStamp) {
-                if (DEBUG) {
-                    Slog.d(TAG, "Requesting stats after " + beginTime + " but latest is "
-                            + mCurrentStats[bucketType].mEndTimeStamp);
-                }
-                // Nothing newer available.
-                return UsageStats.EMPTY_STATS;
-            } else if (beginTime >= mCurrentStats[bucketType].mBeginTimeStamp) {
-                if (DEBUG) {
-                    Slog.d(TAG, "Returning in-memory stats");
-                }
-                // Fast path for retrieving in-memory state.
-                // TODO(adamlesinski): This copy just to parcel the object is wasteful.
-                // It would be nice to parcel it here and send that back, but the Binder API
-                // would need to change.
-                return new UsageStats[] { new UsageStats(mCurrentStats[bucketType]) };
-            } else {
-                // Flush any changes that were made to disk before we do a disk query.
-                persistActiveStatsLocked();
-            }
+            UserUsageStatsService service = getUserDataAndInitializeIfNeededLocked(userId);
+            return service.getUsageStats(bucketType, beginTime);
         }
-
-        if (DEBUG) {
-            Slog.d(TAG, "SELECT * FROM " + bucketType + " WHERE beginTime >= "
-                    + beginTime + " LIMIT " + USAGE_STAT_RESULT_LIMIT);
-        }
-
-        UsageStats[] results = mDatabase.getUsageStats(bucketType, beginTime,
-                USAGE_STAT_RESULT_LIMIT);
-
-        if (DEBUG) {
-            Slog.d(TAG, "Results: " + results.length);
-        }
-        return results;
     }
 
     /**
      * Called by the Binder stub.
      */
-    UsageStats.Event[] getEvents(long time) {
+    UsageStats.Event[] getEvents(int userId, long time) {
         return UsageStats.Event.EMPTY_EVENTS;
     }
 
-    private void loadActiveStatsLocked() {
-        final long timeNow = System.currentTimeMillis();
-
-        Calendar tempCal = mDailyExpiryDate;
-        for (int i = 0; i < mCurrentStats.length; i++) {
-            tempCal.setTimeInMillis(timeNow);
-            UsageStatsUtils.truncateDateTo(i, tempCal);
-
-            if (mCurrentStats[i] != null &&
-                    mCurrentStats[i].mBeginTimeStamp == tempCal.getTimeInMillis()) {
-                // These are the same, no need to load them (in memory stats are always newer
-                // than persisted stats).
-                continue;
-            }
-
-            UsageStats[] stats = mDatabase.getUsageStats(i, timeNow, 1);
-            if (stats != null && stats.length > 0) {
-                mCurrentStats[i] = stats[stats.length - 1];
-            } else {
-                mCurrentStats[i] = UsageStats.create(tempCal.getTimeInMillis(), timeNow);
-            }
+    private void flushToDiskLocked() {
+        final int userCount = mUserState.size();
+        for (int i = 0; i < userCount; i++) {
+            UserUsageStatsService service = mUserState.valueAt(i);
+            service.persistActiveStats();
         }
-        mStatsChanged = false;
-        mDailyExpiryDate.setTimeInMillis(timeNow);
-        mDailyExpiryDate.add(Calendar.DAY_OF_YEAR, 1);
-        UsageStatsUtils.truncateDateTo(UsageStatsManager.DAILY_BUCKET, mDailyExpiryDate);
-        Slog.i(TAG, "Rollover scheduled for " + sDateFormat.format(mDailyExpiryDate.getTime()));
-    }
 
-
-    private void persistActiveStatsLocked() {
-        if (mStatsChanged) {
-            Slog.i(TAG, "Flushing usage stats to disk");
-            try {
-                for (int i = 0; i < mCurrentStats.length; i++) {
-                    mDatabase.putUsageStats(i, mCurrentStats[i]);
-                }
-                mStatsChanged = false;
-                mHandler.removeMessages(MSG_FLUSH_TO_DISK);
-            } catch (IOException e) {
-                Slog.e(TAG, "Failed to persist active stats", e);
-            }
-        }
-    }
-
-    private void updateStatsLocked(UsageStats stats, String packageName, long timeStamp,
-            int eventType) {
-        PackageUsageStats pkgStats = stats.getOrCreatePackageUsageStats(packageName);
-
-        // TODO(adamlesinski): Ensure that we recover from incorrect event sequences
-        // like double MOVE_TO_BACKGROUND, etc.
-        if (eventType == UsageStats.Event.MOVE_TO_BACKGROUND ||
-                eventType == UsageStats.Event.END_OF_DAY) {
-            if (pkgStats.mLastEvent == UsageStats.Event.MOVE_TO_FOREGROUND ||
-                    pkgStats.mLastEvent == UsageStats.Event.CONTINUE_PREVIOUS_DAY) {
-                pkgStats.mTotalTimeSpent += timeStamp - pkgStats.mLastTimeUsed;
-            }
-        }
-        pkgStats.mLastEvent = eventType;
-        pkgStats.mLastTimeUsed = timeStamp;
-        stats.mEndTimeStamp = timeStamp;
+        mHandler.removeMessages(MSG_FLUSH_TO_DISK);
     }
 
     class H extends Handler {
@@ -365,13 +253,15 @@
         public void handleMessage(Message msg) {
             switch (msg.what) {
                 case MSG_REPORT_EVENT:
-                    reportEvent((UsageStats.Event) msg.obj);
+                    reportEvent((UsageStats.Event) msg.obj, msg.arg1);
                     break;
 
                 case MSG_FLUSH_TO_DISK:
-                    synchronized (mLock) {
-                        persistActiveStatsLocked();
-                    }
+                    flushToDisk();
+                    break;
+
+                case MSG_REMOVE_USER:
+                    removeUser(msg.arg1);
                     break;
 
                 default:
@@ -401,9 +291,10 @@
                 return UsageStats.EMPTY_STATS;
             }
 
-            long token = Binder.clearCallingIdentity();
+            final int userId = UserHandle.getCallingUserId();
+            final long token = Binder.clearCallingIdentity();
             try {
-                return getUsageStats(bucketType, time);
+                return getUsageStats(userId, bucketType, time);
             } finally {
                 Binder.restoreCallingIdentity(token);
             }
@@ -415,9 +306,10 @@
                 return UsageStats.Event.EMPTY_EVENTS;
             }
 
-            long token = Binder.clearCallingIdentity();
+            final int userId = UserHandle.getCallingUserId();
+            final long token = Binder.clearCallingIdentity();
             try {
-                return getEvents(time);
+                return getEvents(userId, time);
             } finally {
                 Binder.restoreCallingIdentity(token);
             }
@@ -432,10 +324,11 @@
     private class LocalService extends UsageStatsManagerInternal {
 
         @Override
-        public void reportEvent(ComponentName component, long timeStamp, int eventType) {
+        public void reportEvent(ComponentName component, int userId,
+                long timeStamp, int eventType) {
             UsageStats.Event event = new UsageStats.Event(component.getPackageName(), timeStamp,
                     eventType);
-            mHandler.obtainMessage(MSG_REPORT_EVENT, event).sendToTarget();
+            mHandler.obtainMessage(MSG_REPORT_EVENT, userId, 0, event).sendToTarget();
         }
 
         @Override
diff --git a/services/usage/java/com/android/server/usage/UserUsageStatsService.java b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
new file mode 100644
index 0000000..d124188
--- /dev/null
+++ b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
@@ -0,0 +1,275 @@
+package com.android.server.usage;
+
+import android.app.usage.PackageUsageStats;
+import android.app.usage.UsageStats;
+import android.app.usage.UsageStatsManager;
+import android.util.ArraySet;
+import android.util.Slog;
+
+import java.io.File;
+import java.io.IOException;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+
+/**
+ * A per-user UsageStatsService. All methods are meant to be called with the main lock held
+ * in UsageStatsService.
+ */
+class UserUsageStatsService {
+    private static final String TAG = "UsageStatsService";
+    private static final boolean DEBUG = UsageStatsService.DEBUG;
+    private static final SimpleDateFormat sDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+
+    private final UsageStatsDatabase mDatabase;
+    private final UsageStats[] mCurrentStats = new UsageStats[UsageStatsManager.BUCKET_COUNT];
+    private boolean mStatsChanged = false;
+    private final Calendar mDailyExpiryDate;
+    private final StatsUpdatedListener mListener;
+    private final String mLogPrefix;
+
+    interface StatsUpdatedListener {
+        void onStatsUpdated();
+    }
+
+    UserUsageStatsService(int userId, File usageStatsDir, StatsUpdatedListener listener) {
+        mDailyExpiryDate = Calendar.getInstance();
+        mDatabase = new UsageStatsDatabase(usageStatsDir);
+        mListener = listener;
+        mLogPrefix = "User[" + Integer.toString(userId) + "] ";
+    }
+
+    void init() {
+        mDatabase.init();
+
+        int nullCount = 0;
+        for (int i = 0; i < mCurrentStats.length; i++) {
+            mCurrentStats[i] = mDatabase.getLatestUsageStats(i);
+            if (mCurrentStats[i] == null) {
+                nullCount++;
+            }
+        }
+
+        if (nullCount > 0) {
+            if (nullCount != mCurrentStats.length) {
+                // This is weird, but we shouldn't fail if something like this
+                // happens.
+                Slog.w(TAG, mLogPrefix + "Some stats have no latest available");
+            } else {
+                // This must be first boot.
+            }
+
+            // By calling loadActiveStats, we will
+            // generate new stats for each bucket.
+            loadActiveStats();
+        } else {
+            // Set up the expiry date to be one day from the latest daily stat.
+            // This may actually be today and we will rollover on the first event
+            // that is reported.
+            mDailyExpiryDate.setTimeInMillis(
+                    mCurrentStats[UsageStatsManager.DAILY_BUCKET].mBeginTimeStamp);
+            mDailyExpiryDate.add(Calendar.DAY_OF_YEAR, 1);
+            UsageStatsUtils.truncateDateTo(UsageStatsManager.DAILY_BUCKET, mDailyExpiryDate);
+            Slog.i(TAG, mLogPrefix + "Rollover scheduled for "
+                    + sDateFormat.format(mDailyExpiryDate.getTime()));
+        }
+
+        // Now close off any events that were open at the time this was saved.
+        for (UsageStats stat : mCurrentStats) {
+            final int pkgCount = stat.getPackageCount();
+            for (int i = 0; i < pkgCount; i++) {
+                PackageUsageStats pkgStats = stat.getPackage(i);
+                if (pkgStats.mLastEvent == UsageStats.Event.MOVE_TO_FOREGROUND ||
+                        pkgStats.mLastEvent == UsageStats.Event.CONTINUE_PREVIOUS_DAY) {
+                    updateStats(stat, pkgStats.mPackageName, stat.mLastTimeSaved,
+                            UsageStats.Event.END_OF_DAY);
+                    notifyStatsChanged();
+                }
+            }
+        }
+    }
+
+    void reportEvent(UsageStats.Event event) {
+        if (DEBUG) {
+            Slog.d(TAG, mLogPrefix + "Got usage event for " + event.packageName
+                    + "[" + event.timeStamp + "]: "
+                    + eventToString(event.eventType));
+        }
+
+        if (event.timeStamp >= mDailyExpiryDate.getTimeInMillis()) {
+            // Need to rollover
+            rolloverStats();
+        }
+
+        for (UsageStats stats : mCurrentStats) {
+            updateStats(stats, event.packageName, event.timeStamp, event.eventType);
+        }
+
+        notifyStatsChanged();
+    }
+
+    UsageStats[] getUsageStats(int bucketType, long beginTime) {
+        if (beginTime >= mCurrentStats[bucketType].mEndTimeStamp) {
+            if (DEBUG) {
+                Slog.d(TAG, mLogPrefix + "Requesting stats after " + beginTime + " but latest is "
+                        + mCurrentStats[bucketType].mEndTimeStamp);
+            }
+            // Nothing newer available.
+            return UsageStats.EMPTY_STATS;
+
+        } else if (beginTime >= mCurrentStats[bucketType].mBeginTimeStamp) {
+            if (DEBUG) {
+                Slog.d(TAG, mLogPrefix + "Returning in-memory stats");
+            }
+            // Fast path for retrieving in-memory state.
+            // TODO(adamlesinski): This copy just to parcel the object is wasteful.
+            // It would be nice to parcel it here and send that back, but the Binder API
+            // would need to change.
+            return new UsageStats[] { new UsageStats(mCurrentStats[bucketType]) };
+
+        } else {
+            // Flush any changes that were made to disk before we do a disk query.
+            persistActiveStats();
+        }
+
+        if (DEBUG) {
+            Slog.d(TAG, mLogPrefix + "SELECT * FROM " + bucketType + " WHERE beginTime >= "
+                    + beginTime + " LIMIT " + UsageStatsService.USAGE_STAT_RESULT_LIMIT);
+        }
+
+        final UsageStats[] results = mDatabase.getUsageStats(bucketType, beginTime,
+                UsageStatsService.USAGE_STAT_RESULT_LIMIT);
+
+        if (DEBUG) {
+            Slog.d(TAG, mLogPrefix + "Results: " + results.length);
+        }
+        return results;
+    }
+
+    void persistActiveStats() {
+        if (mStatsChanged) {
+            Slog.i(TAG, mLogPrefix + "Flushing usage stats to disk");
+            try {
+                for (int i = 0; i < mCurrentStats.length; i++) {
+                    mDatabase.putUsageStats(i, mCurrentStats[i]);
+                }
+                mStatsChanged = false;
+            } catch (IOException e) {
+                Slog.e(TAG, mLogPrefix + "Failed to persist active stats", e);
+            }
+        }
+    }
+
+    private void rolloverStats() {
+        final long startTime = System.currentTimeMillis();
+        Slog.i(TAG, mLogPrefix + "Rolling over usage stats");
+
+        // Finish any ongoing events with an END_OF_DAY event. Make a note of which components
+        // need a new CONTINUE_PREVIOUS_DAY entry.
+        ArraySet<String> continuePreviousDay = new ArraySet<>();
+        for (UsageStats stat : mCurrentStats) {
+            final int pkgCount = stat.getPackageCount();
+            for (int i = 0; i < pkgCount; i++) {
+                PackageUsageStats pkgStats = stat.getPackage(i);
+                if (pkgStats.mLastEvent == UsageStats.Event.MOVE_TO_FOREGROUND ||
+                        pkgStats.mLastEvent == UsageStats.Event.CONTINUE_PREVIOUS_DAY) {
+                    continuePreviousDay.add(pkgStats.mPackageName);
+                    updateStats(stat, pkgStats.mPackageName,
+                            mDailyExpiryDate.getTimeInMillis() - 1, UsageStats.Event.END_OF_DAY);
+                    mStatsChanged = true;
+                }
+            }
+        }
+
+        persistActiveStats();
+        mDatabase.prune();
+        loadActiveStats();
+
+        final int continueCount = continuePreviousDay.size();
+        for (int i = 0; i < continueCount; i++) {
+            String name = continuePreviousDay.valueAt(i);
+            for (UsageStats stat : mCurrentStats) {
+                updateStats(stat, name,
+                        mCurrentStats[UsageStatsManager.DAILY_BUCKET].mBeginTimeStamp,
+                        UsageStats.Event.CONTINUE_PREVIOUS_DAY);
+                mStatsChanged = true;
+            }
+        }
+        persistActiveStats();
+
+        final long totalTime = System.currentTimeMillis() - startTime;
+        Slog.i(TAG, mLogPrefix + "Rolling over usage stats complete. Took " + totalTime
+                + " milliseconds");
+    }
+
+    private void notifyStatsChanged() {
+        if (!mStatsChanged) {
+            mStatsChanged = true;
+            mListener.onStatsUpdated();
+        }
+    }
+
+    private void loadActiveStats() {
+        final long timeNow = System.currentTimeMillis();
+
+        Calendar tempCal = mDailyExpiryDate;
+        for (int i = 0; i < mCurrentStats.length; i++) {
+            tempCal.setTimeInMillis(timeNow);
+            UsageStatsUtils.truncateDateTo(i, tempCal);
+
+            if (mCurrentStats[i] != null &&
+                    mCurrentStats[i].mBeginTimeStamp == tempCal.getTimeInMillis()) {
+                // These are the same, no need to load them (in memory stats are always newer
+                // than persisted stats).
+                continue;
+            }
+
+            UsageStats[] stats = mDatabase.getUsageStats(i, timeNow, 1);
+            if (stats != null && stats.length > 0) {
+                mCurrentStats[i] = stats[stats.length - 1];
+            } else {
+                mCurrentStats[i] = UsageStats.create(tempCal.getTimeInMillis(), timeNow);
+            }
+        }
+        mStatsChanged = false;
+        mDailyExpiryDate.setTimeInMillis(timeNow);
+        mDailyExpiryDate.add(Calendar.DAY_OF_YEAR, 1);
+        UsageStatsUtils.truncateDateTo(UsageStatsManager.DAILY_BUCKET, mDailyExpiryDate);
+        Slog.i(TAG, mLogPrefix + "Rollover scheduled for "
+                + sDateFormat.format(mDailyExpiryDate.getTime()));
+    }
+
+    private void updateStats(UsageStats stats, String packageName, long timeStamp,
+            int eventType) {
+        PackageUsageStats pkgStats = stats.getOrCreatePackageUsageStats(packageName);
+
+        // TODO(adamlesinski): Ensure that we recover from incorrect event sequences
+        // like double MOVE_TO_BACKGROUND, etc.
+        if (eventType == UsageStats.Event.MOVE_TO_BACKGROUND ||
+                eventType == UsageStats.Event.END_OF_DAY) {
+            if (pkgStats.mLastEvent == UsageStats.Event.MOVE_TO_FOREGROUND ||
+                    pkgStats.mLastEvent == UsageStats.Event.CONTINUE_PREVIOUS_DAY) {
+                pkgStats.mTotalTimeSpent += timeStamp - pkgStats.mLastTimeUsed;
+            }
+        }
+        pkgStats.mLastEvent = eventType;
+        pkgStats.mLastTimeUsed = timeStamp;
+        stats.mEndTimeStamp = timeStamp;
+    }
+
+    private static String eventToString(int eventType) {
+        switch (eventType) {
+            case UsageStats.Event.NONE:
+                return "NONE";
+            case UsageStats.Event.MOVE_TO_BACKGROUND:
+                return "MOVE_TO_BACKGROUND";
+            case UsageStats.Event.MOVE_TO_FOREGROUND:
+                return "MOVE_TO_FOREGROUND";
+            case UsageStats.Event.END_OF_DAY:
+                return "END_OF_DAY";
+            case UsageStats.Event.CONTINUE_PREVIOUS_DAY:
+                return "CONTINUE_PREVIOUS_DAY";
+            default:
+                return "UNKNOWN";
+        }
+    }
+}
diff --git a/telecomm/java/android/telecomm/VideoCallProvider.java b/telecomm/java/android/telecomm/VideoCallProvider.java
index f3fec11..de0126d 100644
--- a/telecomm/java/android/telecomm/VideoCallProvider.java
+++ b/telecomm/java/android/telecomm/VideoCallProvider.java
@@ -51,6 +51,7 @@
             switch (msg.what) {
                 case MSG_SET_VIDEO_CALL_LISTENER:
                     mVideoCallListener = IVideoCallCallback.Stub.asInterface((IBinder) msg.obj);
+                    break;
                 case MSG_SET_CAMERA:
                     onSetCamera((String) msg.obj);
                     break;
diff --git a/test-runner/src/android/test/mock/MockPackageManager.java b/test-runner/src/android/test/mock/MockPackageManager.java
index 36c90f2..8ce7888 100644
--- a/test-runner/src/android/test/mock/MockPackageManager.java
+++ b/test-runner/src/android/test/mock/MockPackageManager.java
@@ -750,7 +750,7 @@
     }
 
     /** {@hide} */
-    public PackageInstaller getInstaller() {
+    public PackageInstaller getPackageInstaller() {
         throw new UnsupportedOperationException();
     }
 
diff --git a/tests/MusicServiceDemo/src/com/example/android/musicservicedemo/BrowserService.java b/tests/MusicServiceDemo/src/com/example/android/musicservicedemo/BrowserService.java
index 0e7fe13..035b3ea 100644
--- a/tests/MusicServiceDemo/src/com/example/android/musicservicedemo/BrowserService.java
+++ b/tests/MusicServiceDemo/src/com/example/android/musicservicedemo/BrowserService.java
@@ -116,12 +116,12 @@
     }
 
     @Override
-    protected BrowserRoot onGetRoot(String clientPackageName, int clientUid, Bundle rootHints) {
+    public BrowserRoot onGetRoot(String clientPackageName, int clientUid, Bundle rootHints) {
         return new BrowserRoot(BROWSE_URI, null);
     }
 
     @Override
-    protected void onLoadChildren(final Uri parentUri,
+    public void onLoadChildren(final Uri parentUri,
             final Result<List<MediaBrowserItem>> result) {
         new Handler().postDelayed(new Runnable() {
                 public void run() {
@@ -142,7 +142,7 @@
     }
 
     @Override
-    protected void onLoadThumbnail(Uri uri, int width, int height, Result<Bitmap> result) {
+    public void onLoadThumbnail(Uri uri, int width, int height, Result<Bitmap> result) {
         result.sendResult(null);
     }