From ec9bad2015c6d3bc91bab66f0824043c1e24d013 Mon Sep 17 00:00:00 2001 From: Jeff Sharkey Date: Fri, 5 Sep 2014 09:45:20 -0700 Subject: [PATCH] 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 --- api/current.txt | 9 +- .../android/content/pm/IPackageInstaller.aidl | 7 ++ .../content/pm/IPackageInstallerCallback.aidl | 1 + .../android/content/pm/PackageInstaller.java | 87 +++++++++++++++---- .../server/pm/PackageInstallerService.java | 74 +++++++++------- .../server/pm/PackageInstallerSession.java | 7 +- .../server/pm/PackageManagerService.java | 41 --------- 7 files changed, 135 insertions(+), 91 deletions(-) diff --git a/api/current.txt b/api/current.txt index c04b18d95c26a..e8e5fec0ba5f9 100644 --- a/api/current.txt +++ b/api/current.txt @@ -8637,15 +8637,17 @@ package android.content.pm { 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 getAllSessions(); method public java.util.List 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 @@ package android.content.pm { 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 c6c0ff6163d3a..6daefc8b63507 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.IPackageInstallerSession; 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 getAllSessions(int userId); List getMySessions(String installerPackageName, int userId); diff --git a/core/java/android/content/pm/IPackageInstallerCallback.aidl b/core/java/android/content/pm/IPackageInstallerCallback.aidl index 39ae1a050c3e5..fe98ee77d159d 100644 --- a/core/java/android/content/pm/IPackageInstallerCallback.aidl +++ b/core/java/android/content/pm/IPackageInstallerCallback.aidl @@ -19,6 +19,7 @@ package android.content.pm; /** {@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 44e24b14e1586..7c34a6512b8e3 100644 --- a/core/java/android/content/pm/PackageInstaller.java +++ b/core/java/android/content/pm/PackageInstaller.java @@ -312,6 +312,32 @@ public class PackageInstaller { } } + /** + * 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 @@ public class PackageInstaller { } /** - * 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 @@ public class PackageInstaller { /** * Return list of all active install sessions, regardless of the installer. - * To succeed, the caller must be the current home app. */ public @NonNull List getAllSessions() { final ApplicationInfo info = mContext.getApplicationInfo(); @@ -405,6 +429,12 @@ public class PackageInstaller { */ 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 @@ public class PackageInstaller { 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 @@ public class PackageInstaller { 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; @@ -476,6 +510,11 @@ public class PackageInstaller { mHandler.obtainMessage(MSG_SESSION_CREATED, sessionId, 0).sendToTarget(); } + @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 @@ public class PackageInstaller { } } - /** - * 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 @@ public class PackageInstaller { } } + /** {@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 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 3f7a607efce36..9a00923effcb0 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.IPackageInstaller; 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; @@ -580,6 +580,30 @@ public class PackageInstallerService extends IPackageInstaller.Stub { return sessionId; } + @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) { @@ -681,9 +705,6 @@ public class PackageInstallerService extends IPackageInstaller.Stub { 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 @@ public class PackageInstallerService extends IPackageInstaller.Stub { @Override public List getAllSessions(int userId) { mPm.enforceCrossUserPermission(Binder.getCallingUid(), userId, true, "getAllSessions"); - enforceCallerCanReadSessions(); final List result = new ArrayList<>(); synchronized (mSessions) { @@ -755,8 +775,6 @@ public class PackageInstallerService extends IPackageInstaller.Stub { @Override public void registerCallback(IPackageInstallerCallback callback, int userId) { mPm.enforceCrossUserPermission(Binder.getCallingUid(), userId, true, "registerCallback"); - enforceCallerCanReadSessions(); - mCallbacks.register(callback, userId); } @@ -787,21 +805,6 @@ public class PackageInstallerService extends IPackageInstaller.Stub { } } - /** - * 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 @@ public class PackageInstallerService extends IPackageInstaller.Stub { 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 mCallbacks = new RemoteCallbackList<>(); @@ -938,6 +942,9 @@ public class PackageInstallerService extends IPackageInstaller.Stub { 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 @@ public class PackageInstallerService extends IPackageInstaller.Stub { 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 @@ public class PackageInstallerService extends IPackageInstaller.Stub { } 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 5264fc4b13fd8..85ff54e444c25 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 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } 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 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } // 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 d1bd2f0110b90..85df6e25b7c1f 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 @@ public class PackageManagerService extends IPackageManager.Stub { 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 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) {