Allow badging updates to install sessions.

For the system restore use-case, an installer may need to enqueue
their sessions quickly before badging details, like icons, have been
downloaded.  This change relaxes to allow an installer to update
their session badging after the session has been created.  Notify
observers when badging changes.

Rename callback registration methods to match style guide.  Relax
constraint that observers are home app.  Fix bug around internal
progress reporting.

Bug: 17376797, 17389236, 17334199
Change-Id: I5fb88508baea2f08e89a1504fcf5ef972afad4a7
diff --git a/api/current.txt b/api/current.txt
index c04b18d..e8e5fec 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -8637,15 +8637,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";
@@ -8677,6 +8679,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 d1bd2f0..85df6e2 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -11725,47 +11725,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) {