Merge "Allow badging updates to install sessions." into lmp-dev
diff --git a/api/current.txt b/api/current.txt
index fb52648d..dab4f64 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -8681,15 +8681,17 @@
 
   public class PackageInstaller {
     method public void abandonSession(int);
-    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.PackageInstaller.SessionParams) throws java.io.IOException;
     method public java.util.List<android.content.pm.PackageInstaller.SessionInfo> getAllSessions();
     method public java.util.List<android.content.pm.PackageInstaller.SessionInfo> getMySessions();
     method public android.content.pm.PackageInstaller.SessionInfo getSessionInfo(int);
     method public android.content.pm.PackageInstaller.Session openSession(int);
-    method public void removeSessionCallback(android.content.pm.PackageInstaller.SessionCallback);
+    method public void registerSessionCallback(android.content.pm.PackageInstaller.SessionCallback);
+    method public void registerSessionCallback(android.content.pm.PackageInstaller.SessionCallback, android.os.Handler);
     method public void uninstall(java.lang.String, android.content.IntentSender);
+    method public void unregisterSessionCallback(android.content.pm.PackageInstaller.SessionCallback);
+    method public void updateSessionAppIcon(int, android.graphics.Bitmap);
+    method public void updateSessionAppLabel(int, java.lang.CharSequence);
     field public static final java.lang.String ACTION_SESSION_DETAILS = "android.content.pm.action.SESSION_DETAILS";
     field public static final java.lang.String EXTRA_OTHER_PACKAGE_NAME = "android.content.pm.extra.OTHER_PACKAGE_NAME";
     field public static final java.lang.String EXTRA_PACKAGE_NAME = "android.content.pm.extra.PACKAGE_NAME";
@@ -8721,6 +8723,7 @@
 
   public static abstract class PackageInstaller.SessionCallback {
     ctor public PackageInstaller.SessionCallback();
+    method public abstract void onBadgingChanged(int);
     method public abstract void onClosed(int);
     method public abstract void onCreated(int);
     method public abstract void onFinished(int, boolean);
diff --git a/core/java/android/content/pm/IPackageInstaller.aidl b/core/java/android/content/pm/IPackageInstaller.aidl
index c6c0ff6..6daefc8 100644
--- a/core/java/android/content/pm/IPackageInstaller.aidl
+++ b/core/java/android/content/pm/IPackageInstaller.aidl
@@ -22,14 +22,21 @@
 import android.content.pm.PackageInstaller;
 import android.content.IntentSender;
 
+import android.graphics.Bitmap;
+
 /** {@hide} */
 interface IPackageInstaller {
     int createSession(in PackageInstaller.SessionParams params, String installerPackageName, int userId);
+
+    void updateSessionAppIcon(int sessionId, in Bitmap appIcon);
+    void updateSessionAppLabel(int sessionId, String appLabel);
+
     void abandonSession(int sessionId);
 
     IPackageInstallerSession openSession(int sessionId);
 
     PackageInstaller.SessionInfo getSessionInfo(int sessionId);
+
     List<PackageInstaller.SessionInfo> getAllSessions(int userId);
     List<PackageInstaller.SessionInfo> getMySessions(String installerPackageName, int userId);
 
diff --git a/core/java/android/content/pm/IPackageInstallerCallback.aidl b/core/java/android/content/pm/IPackageInstallerCallback.aidl
index 39ae1a0..fe98ee7 100644
--- a/core/java/android/content/pm/IPackageInstallerCallback.aidl
+++ b/core/java/android/content/pm/IPackageInstallerCallback.aidl
@@ -19,6 +19,7 @@
 /** {@hide} */
 oneway interface IPackageInstallerCallback {
     void onSessionCreated(int sessionId);
+    void onSessionBadgingChanged(int sessionId);
     void onSessionOpened(int sessionId);
     void onSessionProgressChanged(int sessionId, float progress);
     void onSessionClosed(int sessionId);
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index 44e24b1..7c34a65 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -312,6 +312,32 @@
         }
     }
 
+    /**
+     * Update the icon representing the app being installed in a specific
+     * session. This should be roughly
+     * {@link ActivityManager#getLauncherLargeIconSize()} in both dimensions.
+     */
+    public void updateSessionAppIcon(int sessionId, @Nullable Bitmap appIcon) {
+        try {
+            mInstaller.updateSessionAppIcon(sessionId, appIcon);
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
+    }
+
+    /**
+     * Update the label representing the app being installed in a specific
+     * session.
+     */
+    public void updateSessionAppLabel(int sessionId, @Nullable CharSequence appLabel) {
+        try {
+            final String val = (appLabel != null) ? appLabel.toString() : null;
+            mInstaller.updateSessionAppLabel(sessionId, val);
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
+    }
+
     public void abandonSession(int sessionId) {
         try {
             mInstaller.abandonSession(sessionId);
@@ -321,8 +347,7 @@
     }
 
     /**
-     * Return details for a specific session. To succeed, the caller must either
-     * own this session, or be the current home app.
+     * Return details for a specific session.
      */
     public @Nullable SessionInfo getSessionInfo(int sessionId) {
         try {
@@ -334,7 +359,6 @@
 
     /**
      * Return list of all active install sessions, regardless of the installer.
-     * To succeed, the caller must be the current home app.
      */
     public @NonNull List<SessionInfo> getAllSessions() {
         final ApplicationInfo info = mContext.getApplicationInfo();
@@ -406,6 +430,12 @@
         public abstract void onCreated(int sessionId);
 
         /**
+         * Badging details for an existing session has changed. For example, the
+         * app icon or label has been updated.
+         */
+        public abstract void onBadgingChanged(int sessionId);
+
+        /**
          * Session has been opened. A session is usually opened when the
          * installer is actively writing data.
          */
@@ -436,10 +466,11 @@
     private static class SessionCallbackDelegate extends IPackageInstallerCallback.Stub implements
             Handler.Callback {
         private static final int MSG_SESSION_CREATED = 1;
-        private static final int MSG_SESSION_OPENED = 2;
-        private static final int MSG_SESSION_PROGRESS_CHANGED = 3;
-        private static final int MSG_SESSION_CLOSED = 4;
-        private static final int MSG_SESSION_FINISHED = 5;
+        private static final int MSG_SESSION_BADGING_CHANGED = 2;
+        private static final int MSG_SESSION_OPENED = 3;
+        private static final int MSG_SESSION_PROGRESS_CHANGED = 4;
+        private static final int MSG_SESSION_CLOSED = 5;
+        private static final int MSG_SESSION_FINISHED = 6;
 
         final SessionCallback mCallback;
         final Handler mHandler;
@@ -455,6 +486,9 @@
                 case MSG_SESSION_CREATED:
                     mCallback.onCreated(msg.arg1);
                     return true;
+                case MSG_SESSION_BADGING_CHANGED:
+                    mCallback.onBadgingChanged(msg.arg1);
+                    return true;
                 case MSG_SESSION_OPENED:
                     mCallback.onOpened(msg.arg1);
                     return true;
@@ -477,6 +511,11 @@
         }
 
         @Override
+        public void onSessionBadgingChanged(int sessionId) {
+            mHandler.obtainMessage(MSG_SESSION_BADGING_CHANGED, sessionId, 0).sendToTarget();
+        }
+
+        @Override
         public void onSessionOpened(int sessionId) {
             mHandler.obtainMessage(MSG_SESSION_OPENED, sessionId, 0).sendToTarget();
         }
@@ -499,22 +538,32 @@
         }
     }
 
-    /**
-     * Register to watch for session lifecycle events. To succeed, the caller
-     * must be the current home app.
-     */
+    /** {@hide} */
+    @Deprecated
     public void addSessionCallback(@NonNull SessionCallback callback) {
-        addSessionCallback(callback, new Handler());
+        registerSessionCallback(callback);
     }
 
     /**
-     * Register to watch for session lifecycle events. To succeed, the caller
-     * must be the current home app.
+     * Register to watch for session lifecycle events.
+     */
+    public void registerSessionCallback(@NonNull SessionCallback callback) {
+        registerSessionCallback(callback, new Handler());
+    }
+
+    /** {@hide} */
+    @Deprecated
+    public void addSessionCallback(@NonNull SessionCallback callback, @NonNull Handler handler) {
+        registerSessionCallback(callback, handler);
+    }
+
+    /**
+     * Register to watch for session lifecycle events.
      *
      * @param handler to dispatch callback events through, otherwise uses
      *            calling thread.
      */
-    public void addSessionCallback(@NonNull SessionCallback callback, @NonNull Handler handler) {
+    public void registerSessionCallback(@NonNull SessionCallback callback, @NonNull Handler handler) {
         // TODO: remove this temporary guard once we have new prebuilts
         final ApplicationInfo info = mContext.getApplicationInfo();
         if ("com.google.android.googlequicksearchbox".equals(info.packageName)
@@ -535,10 +584,16 @@
         }
     }
 
+    /** {@hide} */
+    @Deprecated
+    public void removeSessionCallback(@NonNull SessionCallback callback) {
+        unregisterSessionCallback(callback);
+    }
+
     /**
      * Unregister an existing callback.
      */
-    public void removeSessionCallback(@NonNull SessionCallback callback) {
+    public void unregisterSessionCallback(@NonNull SessionCallback callback) {
         synchronized (mDelegates) {
             for (Iterator<SessionCallbackDelegate> i = mDelegates.iterator(); i.hasNext();) {
                 final SessionCallbackDelegate delegate = i.next();
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 3f7a607..9a00923 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -47,12 +47,12 @@
 import android.content.pm.IPackageInstallerCallback;
 import android.content.pm.IPackageInstallerSession;
 import android.content.pm.PackageInstaller;
-import android.content.pm.PackageParser;
 import android.content.pm.PackageInstaller.SessionInfo;
 import android.content.pm.PackageInstaller.SessionParams;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageParser;
 import android.content.pm.PackageParser.PackageLite;
 import android.content.pm.PackageParser.PackageParserException;
-import android.content.pm.PackageManager;
 import android.graphics.Bitmap;
 import android.net.Uri;
 import android.os.Binder;
@@ -581,6 +581,30 @@
     }
 
     @Override
+    public void updateSessionAppIcon(int sessionId, Bitmap appIcon) {
+        synchronized (mSessions) {
+            final PackageInstallerSession session = mSessions.get(sessionId);
+            if (session == null || !isCallingUidOwner(session)) {
+                throw new SecurityException("Caller has no access to session " + sessionId);
+            }
+            session.params.appIcon = appIcon;
+            mInternalCallback.onSessionBadgingChanged(session);
+        }
+    }
+
+    @Override
+    public void updateSessionAppLabel(int sessionId, String appLabel) {
+        synchronized (mSessions) {
+            final PackageInstallerSession session = mSessions.get(sessionId);
+            if (session == null || !isCallingUidOwner(session)) {
+                throw new SecurityException("Caller has no access to session " + sessionId);
+            }
+            session.params.appLabel = appLabel;
+            mInternalCallback.onSessionBadgingChanged(session);
+        }
+    }
+
+    @Override
     public void abandonSession(int sessionId) {
         synchronized (mSessions) {
             final PackageInstallerSession session = mSessions.get(sessionId);
@@ -681,9 +705,6 @@
     public SessionInfo getSessionInfo(int sessionId) {
         synchronized (mSessions) {
             final PackageInstallerSession session = mSessions.get(sessionId);
-            if (!isCallingUidOwner(session)) {
-                enforceCallerCanReadSessions();
-            }
             return session != null ? session.generateInfo() : null;
         }
     }
@@ -691,7 +712,6 @@
     @Override
     public List<SessionInfo> getAllSessions(int userId) {
         mPm.enforceCrossUserPermission(Binder.getCallingUid(), userId, true, "getAllSessions");
-        enforceCallerCanReadSessions();
 
         final List<SessionInfo> result = new ArrayList<>();
         synchronized (mSessions) {
@@ -755,8 +775,6 @@
     @Override
     public void registerCallback(IPackageInstallerCallback callback, int userId) {
         mPm.enforceCrossUserPermission(Binder.getCallingUid(), userId, true, "registerCallback");
-        enforceCallerCanReadSessions();
-
         mCallbacks.register(callback, userId);
     }
 
@@ -787,21 +805,6 @@
         }
     }
 
-    /**
-     * 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");
-        }
-    }
-
     static class PackageDeleteObserverAdapter extends PackageDeleteObserver {
         private final Context mContext;
         private final IntentSender mTarget;
@@ -893,10 +896,11 @@
 
     private static class Callbacks extends Handler {
         private static final int MSG_SESSION_CREATED = 1;
-        private static final int MSG_SESSION_OPENED = 2;
-        private static final int MSG_SESSION_PROGRESS_CHANGED = 3;
-        private static final int MSG_SESSION_CLOSED = 4;
-        private static final int MSG_SESSION_FINISHED = 5;
+        private static final int MSG_SESSION_BADGING_CHANGED = 2;
+        private static final int MSG_SESSION_OPENED = 3;
+        private static final int MSG_SESSION_PROGRESS_CHANGED = 4;
+        private static final int MSG_SESSION_CLOSED = 5;
+        private static final int MSG_SESSION_FINISHED = 6;
 
         private final RemoteCallbackList<IPackageInstallerCallback>
                 mCallbacks = new RemoteCallbackList<>();
@@ -938,6 +942,9 @@
                 case MSG_SESSION_CREATED:
                     callback.onSessionCreated(sessionId);
                     break;
+                case MSG_SESSION_BADGING_CHANGED:
+                    callback.onSessionBadgingChanged(sessionId);
+                    break;
                 case MSG_SESSION_OPENED:
                     callback.onSessionOpened(sessionId);
                     break;
@@ -957,6 +964,10 @@
             obtainMessage(MSG_SESSION_CREATED, sessionId, userId).sendToTarget();
         }
 
+        private void notifySessionBadgingChanged(int sessionId, int userId) {
+            obtainMessage(MSG_SESSION_BADGING_CHANGED, sessionId, userId).sendToTarget();
+        }
+
         private void notifySessionOpened(int sessionId, int userId) {
             obtainMessage(MSG_SESSION_OPENED, sessionId, userId).sendToTarget();
         }
@@ -1006,14 +1017,19 @@
     }
 
     class InternalCallback {
-        public void onSessionProgressChanged(PackageInstallerSession session, float progress) {
-            mCallbacks.notifySessionProgressChanged(session.sessionId, session.userId, progress);
+        public void onSessionBadgingChanged(PackageInstallerSession session) {
+            mCallbacks.notifySessionBadgingChanged(session.sessionId, session.userId);
+            writeSessionsAsync();
         }
 
         public void onSessionOpened(PackageInstallerSession session) {
             mCallbacks.notifySessionOpened(session.sessionId, session.userId);
         }
 
+        public void onSessionProgressChanged(PackageInstallerSession session, float progress) {
+            mCallbacks.notifySessionProgressChanged(session.sessionId, session.userId, progress);
+        }
+
         public void onSessionClosed(PackageInstallerSession session) {
             mCallbacks.notifySessionClosed(session.sessionId, session.userId);
         }
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 5264fc4..85ff54e 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -294,7 +294,9 @@
     }
 
     private void computeProgressLocked() {
-        mProgress = MathUtils.constrain(mClientProgress * 0.8f, 0f, 0.8f);
+        if (mProgress <= 0.8f) {
+            mProgress = MathUtils.constrain(mClientProgress * 0.8f, 0f, 0.8f);
+        }
     }
 
     private void maybePublishProgress() {
@@ -485,7 +487,8 @@
         }
 
         // TODO: surface more granular state from dexopt
-        mCallback.onSessionProgressChanged(this, 0.9f);
+        mProgress = 0.9f;
+        maybePublishProgress();
 
         // Unpack native libraries
         extractNativeLibraries(mResolvedStageDir, params.abiOverride);
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index a4d4914..d2a627e 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -11746,47 +11746,6 @@
                         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) {