From 5cf5578a457e448dda9fd47943e91f0f3b67690f Mon Sep 17 00:00:00 2001 From: Christopher Tate Date: Mon, 25 Apr 2016 17:08:56 -0700 Subject: [PATCH] Make sure FIRST_LAUNCH is after PACKAGE_ADDED If an app undergoes restore during install, it is considered 'started' and the FIRST_LAUNCH broadcast needs to go out. However, this must not take place until after the restore operation has fully completed, in order to avoid publishing the app's existence while it may still be in an incoherent state. We now make this broadcast part of POST_INSTALL in the restore case. Bundled apps are in the 'started' state regardless, so no FIRST_LAUNCH broadcast is ever sent for them -- this CL does not change that existing behavior even in the case of setup-time data restore of factory-installed packages. Bug 28173625 Change-Id: Ibcc3758576662dc447b75476173a0d008a9fe4da --- .../android/content/pm/IPackageManager.aidl | 2 +- .../server/backup/BackupManagerService.java | 17 ++++- .../server/pm/PackageManagerService.java | 68 +++++++++++++++++-- .../java/com/android/server/pm/Settings.java | 6 +- 4 files changed, 82 insertions(+), 11 deletions(-) diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl index 0c793120d0a85..03f83d622cde1 100644 --- a/core/java/android/content/pm/IPackageManager.aidl +++ b/core/java/android/content/pm/IPackageManager.aidl @@ -222,7 +222,7 @@ interface IPackageManager { in String installerPackageName, int userId); - void finishPackageInstall(int token); + void finishPackageInstall(int token, boolean didLaunch); void setInstallerPackageName(in String targetPackage, in String installerPackageName); diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java index 6288b56d1f1d7..95f9e2dc0935b 100644 --- a/services/backup/java/com/android/server/backup/BackupManagerService.java +++ b/services/backup/java/com/android/server/backup/BackupManagerService.java @@ -7706,6 +7706,11 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF // when we're finished. private int mPmToken; + // When this is restore-during-install, we need to tell the package manager + // whether we actually launched the app, because this affects notifications + // around externally-visible state transitions. + private boolean mDidLaunch; + // Is this a whole-system restore, i.e. are we establishing a new ancestral // dataset to base future restore-at-install operations from? private boolean mIsSystemRestore; @@ -7769,6 +7774,7 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF mTargetPackage = targetPackage; mIsSystemRestore = isFullSystemRestore; mFinished = false; + mDidLaunch = false; if (targetPackage != null) { // Single package restore @@ -8149,6 +8155,9 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF return; } + // Whatever happens next, we've launched the target app now; remember that. + mDidLaunch = true; + // And then finally start the restore on this agent try { initiateOneRestore(mCurrentPackage, metaInfo.versionCode); @@ -8430,6 +8439,10 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF // Now we're really done with this one too IoUtils.closeQuietly(mEnginePipes[0]); + // In all cases we want to remember whether we launched + // the target app as part of our work so far. + mDidLaunch = (mEngine.getAgent() != null); + // If we hit a transport-level error, we are done with everything; // if we hit an agent error we just go back to running the queue. if (status == BackupTransport.TRANSPORT_OK) { @@ -8522,7 +8535,7 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF if (mPmToken > 0) { if (MORE_DEBUG) Slog.v(TAG, "finishing PM token " + mPmToken); try { - mPackageManagerBinder.finishPackageInstall(mPmToken); + mPackageManagerBinder.finishPackageInstall(mPmToken, mDidLaunch); } catch (RemoteException e) { /* can't happen */ } } else { // We were invoked via an active restore session, not by the Package @@ -9682,7 +9695,7 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF // Manager to proceed with the post-install handling for this package. if (DEBUG) Slog.v(TAG, "Finishing install immediately"); try { - mPackageManagerBinder.finishPackageInstall(token); + mPackageManagerBinder.finishPackageInstall(token, false); } catch (RemoteException e) { /* can't happen */ } } } diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 547379d9cafaa..7699d3d49d01d 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -1574,6 +1574,7 @@ public class PackageManagerService extends IPackageManager.Stub { if (DEBUG_INSTALL) Log.v(TAG, "Handling post-install for " + msg.arg1); PostInstallData data = mRunningInstalls.get(msg.arg1); + final boolean didRestore = (msg.arg2 != 0); mRunningInstalls.delete(msg.arg1); if (data != null) { @@ -1588,7 +1589,8 @@ public class PackageManagerService extends IPackageManager.Stub { // Handle the parent package handlePackagePostInstall(parentRes, grantPermissions, killApp, - grantedPermissions, args.observer); + grantedPermissions, didRestore, args.installerPackageName, + args.observer); // Handle the child packages final int childCount = (parentRes.addedChildPackages != null) @@ -1596,7 +1598,8 @@ public class PackageManagerService extends IPackageManager.Stub { for (int i = 0; i < childCount; i++) { PackageInstalledInfo childRes = parentRes.addedChildPackages.valueAt(i); handlePackagePostInstall(childRes, grantPermissions, killApp, - grantedPermissions, args.observer); + grantedPermissions, false, args.installerPackageName, + args.observer); } // Log tracing if needed @@ -1792,6 +1795,7 @@ public class PackageManagerService extends IPackageManager.Stub { private void handlePackagePostInstall(PackageInstalledInfo res, boolean grantPermissions, boolean killApp, String[] grantedPermissions, + boolean launchedForRestore, String installerPackage, IPackageInstallObserver2 installObserver) { if (res.returnCode == PackageManager.INSTALL_SUCCEEDED) { // Send the removed broadcasts @@ -1876,6 +1880,14 @@ public class PackageManagerService extends IPackageManager.Stub { null /*package*/, null /*extras*/, 0 /*flags*/, packageName /*targetPackage*/, null /*finishedReceiver*/, updateUsers); + } else if (launchedForRestore && !isSystemApp(res.pkg)) { + // First-install and we did a restore, so we're responsible for the + // first-launch broadcast. + if (DEBUG_BACKUP) { + Slog.i(TAG, "Post-restore of " + packageName + + " sending FIRST_LAUNCH in " + Arrays.toString(firstUsers)); + } + sendFirstLaunchBroadcast(packageName, installerPackage, firstUsers); } // Send broadcast package appeared if forward locked/external for all users @@ -11723,7 +11735,7 @@ public class PackageManagerService extends IPackageManager.Stub { } @Override - public void finishPackageInstall(int token) { + public void finishPackageInstall(int token, boolean didLaunch) { enforceSystemOrRoot("Only the system is allowed to finish installs"); if (DEBUG_INSTALL) { @@ -11731,7 +11743,7 @@ public class PackageManagerService extends IPackageManager.Stub { } Trace.asyncTraceEnd(TRACE_TAG_PACKAGE_MANAGER, "restore", token); - final Message msg = mHandler.obtainMessage(POST_INSTALL, token, 0); + final Message msg = mHandler.obtainMessage(POST_INSTALL, token, didLaunch ? 1 : 0); mHandler.sendMessage(msg); } @@ -12060,6 +12072,54 @@ public class PackageManagerService extends IPackageManager.Stub { }); } + /** + * Callback from PackageSettings whenever an app is first transitioned out of the + * 'stopped' state. Normally we just issue the broadcast, but we can't do that if + * the app was "launched" for a restoreAtInstall operation. Therefore we check + * here whether the app is the target of an ongoing install, and only send the + * broadcast immediately if it is not in that state. If it *is* undergoing a restore, + * the first-launch broadcast will be sent implicitly on that basis in POST_INSTALL + * handling. + */ + void notifyFirstLaunch(final String pkgName, final String installerPackage, final int userId) { + // Serialize this with the rest of the install-process message chain. In the + // restore-at-install case, this Runnable will necessarily run before the + // POST_INSTALL message is processed, so the contents of mRunningInstalls + // are coherent. In the non-restore case, the app has already completed install + // and been launched through some other means, so it is not in a problematic + // state for observers to see the FIRST_LAUNCH signal. + mHandler.post(new Runnable() { + @Override + public void run() { + for (int i = 0; i < mRunningInstalls.size(); i++) { + final PostInstallData data = mRunningInstalls.valueAt(i); + if (pkgName.equals(data.res.pkg.applicationInfo.packageName)) { + // right package; but is it for the right user? + for (int uIndex = 0; uIndex < data.res.newUsers.length; uIndex++) { + if (userId == data.res.newUsers[uIndex]) { + if (DEBUG_BACKUP) { + Slog.i(TAG, "Package " + pkgName + + " being restored so deferring FIRST_LAUNCH"); + } + return; + } + } + } + } + // didn't find it, so not being restored + if (DEBUG_BACKUP) { + Slog.i(TAG, "Package " + pkgName + " sending normal FIRST_LAUNCH"); + } + sendFirstLaunchBroadcast(pkgName, installerPackage, new int[] {userId}); + } + }); + } + + private void sendFirstLaunchBroadcast(String pkgName, String installerPkg, int[] userIds) { + sendPackageBroadcast(Intent.ACTION_PACKAGE_FIRST_LAUNCH, pkgName, null, 0, + installerPkg, null, userIds); + } + private abstract class HandlerParams { private static final int MAX_RETRIES = 4; diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java index 7debf9b5b7fba..db0180163d1e1 100644 --- a/services/core/java/com/android/server/pm/Settings.java +++ b/services/core/java/com/android/server/pm/Settings.java @@ -4118,7 +4118,7 @@ final class Settings { return pkg.getCurrentEnabledStateLPr(classNameStr, userId); } - boolean setPackageStoppedStateLPw(PackageManagerService yucky, String packageName, + boolean setPackageStoppedStateLPw(PackageManagerService pm, String packageName, boolean stopped, boolean allowedByPermission, int uid, int userId) { int appId = UserHandle.getAppId(uid); final PackageSetting pkgSetting = mPackages.get(packageName); @@ -4143,9 +4143,7 @@ final class Settings { // pkgSetting.pkg.mSetStopped = stopped; if (pkgSetting.getNotLaunched(userId)) { if (pkgSetting.installerPackageName != null) { - yucky.sendPackageBroadcast(Intent.ACTION_PACKAGE_FIRST_LAUNCH, - pkgSetting.name, null, 0, - pkgSetting.installerPackageName, null, new int[] {userId}); + pm.notifyFirstLaunch(pkgSetting.name, pkgSetting.installerPackageName, userId); } pkgSetting.setNotLaunched(false, userId); }