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 4b0eeedbe1255..1dec2b2cbe321 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -1570,6 +1570,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) { @@ -1584,7 +1585,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) @@ -1592,7 +1594,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 @@ -1788,6 +1791,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 @@ -1872,6 +1876,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 f20a36e1f3c02..1040f853763aa 100644 --- a/services/core/java/com/android/server/pm/Settings.java +++ b/services/core/java/com/android/server/pm/Settings.java @@ -4133,7 +4133,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); @@ -4158,9 +4158,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); }