PackageInstaller changes based on feedback.

Mostly cosmetic changes from API council feedback.

Bug: 16543552
Change-Id: Ic926829b3f77c31f50a899c59b779353daf00d59
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/api/current.txt b/api/current.txt
index 9a77586..fe66f19 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;
@@ -30130,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);
@@ -30139,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;
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/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/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/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/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();
     }