diff options
-rw-r--r-- | api/current.xml | 10 | ||||
-rw-r--r-- | core/java/android/backup/IBackupManager.aidl | 6 | ||||
-rw-r--r-- | core/java/android/content/pm/ApplicationInfo.java | 2 | ||||
-rw-r--r-- | core/java/android/content/pm/IPackageManager.aidl | 2 | ||||
-rw-r--r-- | services/java/com/android/server/BackupManagerService.java | 120 | ||||
-rw-r--r-- | services/java/com/android/server/PackageManagerService.java | 142 | ||||
-rw-r--r-- | tests/backup/Android.mk | 3 |
7 files changed, 221 insertions, 64 deletions
diff --git a/api/current.xml b/api/current.xml index 86043df..e123602 100644 --- a/api/current.xml +++ b/api/current.xml @@ -42639,6 +42639,16 @@ visibility="public" > </field> +<field name="backupAgentName" + type="java.lang.String" + transient="false" + volatile="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</field> <field name="className" type="java.lang.String" transient="false" diff --git a/core/java/android/backup/IBackupManager.aidl b/core/java/android/backup/IBackupManager.aidl index bf6c79f..d94b066 100644 --- a/core/java/android/backup/IBackupManager.aidl +++ b/core/java/android/backup/IBackupManager.aidl @@ -62,6 +62,12 @@ interface IBackupManager { void agentDisconnected(String packageName); /** + * Notify the Backup Manager Service that an application being installed will + * need a data-restore pass. This method is only invoked by the Package Manager. + */ + void restoreAtInstall(String packageName, int token); + + /** * Enable/disable the backup service entirely. When disabled, no backup * or restore operations will take place. Data-changed notifications will * still be observed and collected, however, so that changes made while the diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java index 123d9b7..2e405c1 100644 --- a/core/java/android/content/pm/ApplicationInfo.java +++ b/core/java/android/content/pm/ApplicationInfo.java @@ -68,8 +68,6 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { * will be null if the application does not specify it in its manifest. * * <p>If android:allowBackup is set to false, this attribute is ignored. - * - * {@hide} */ public String backupAgentName; diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl index 7a02a98..f793a00 100644 --- a/core/java/android/content/pm/IPackageManager.aidl +++ b/core/java/android/content/pm/IPackageManager.aidl @@ -154,6 +154,8 @@ interface IPackageManager { void installPackage(in Uri packageURI, IPackageInstallObserver observer, int flags, in String installerPackageName); + void finishPackageInstall(int token); + /** * Delete a package. * diff --git a/services/java/com/android/server/BackupManagerService.java b/services/java/com/android/server/BackupManagerService.java index ea0fc65..f79a02a 100644 --- a/services/java/com/android/server/BackupManagerService.java +++ b/services/java/com/android/server/BackupManagerService.java @@ -17,6 +17,7 @@ package com.android.server; import android.app.ActivityManagerNative; +import android.app.ActivityThread; import android.app.AlarmManager; import android.app.IActivityManager; import android.app.IApplicationThread; @@ -34,6 +35,7 @@ import android.content.IntentFilter; import android.content.ServiceConnection; import android.content.pm.ApplicationInfo; import android.content.pm.IPackageDataObserver; +import android.content.pm.IPackageManager; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.Signature; @@ -113,6 +115,7 @@ class BackupManagerService extends IBackupManager.Stub { private Context mContext; private PackageManager mPackageManager; + IPackageManager mPackageManagerBinder; private IActivityManager mActivityManager; private PowerManager mPowerManager; private AlarmManager mAlarmManager; @@ -179,13 +182,15 @@ class BackupManagerService extends IBackupManager.Stub { public IRestoreObserver observer; public long token; public PackageInfo pkgInfo; + public int pmToken; // in post-install restore, the PM's token for this transaction RestoreParams(IBackupTransport _transport, IRestoreObserver _obs, - long _token, PackageInfo _pkg) { + long _token, PackageInfo _pkg, int _pmToken) { transport = _transport; observer = _obs; token = _token; pkgInfo = _pkg; + pmToken = _pmToken; } RestoreParams(IBackupTransport _transport, IRestoreObserver _obs, long _token) { @@ -193,6 +198,7 @@ class BackupManagerService extends IBackupManager.Stub { observer = _obs; token = _token; pkgInfo = null; + pmToken = 0; } } @@ -302,7 +308,7 @@ class BackupManagerService extends IBackupManager.Stub { RestoreParams params = (RestoreParams)msg.obj; Log.d(TAG, "MSG_RUN_RESTORE observer=" + params.observer); (new PerformRestoreTask(params.transport, params.observer, - params.token, params.pkgInfo)).run(); + params.token, params.pkgInfo, params.pmToken)).run(); break; } @@ -349,6 +355,7 @@ class BackupManagerService extends IBackupManager.Stub { public BackupManagerService(Context context) { mContext = context; mPackageManager = context.getPackageManager(); + mPackageManagerBinder = ActivityThread.getPackageManager(); mActivityManager = ActivityManagerNative.getDefault(); mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); @@ -1115,6 +1122,18 @@ class BackupManagerService extends IBackupManager.Stub { } } + // Get the restore-set token for the best-available restore set for this package: + // the active set if possible, else the ancestral one. Returns zero if none available. + long getAvailableRestoreToken(String packageName) { + long token = mAncestralToken; + synchronized (mQueueLock) { + if (mEverStoredApps.contains(packageName)) { + token = mCurrentToken; + } + } + return token; + } + // ----- // Utility methods used by the asynchronous-with-timeout backup/restore operations boolean waitUntilOperationComplete(int token) { @@ -1132,12 +1151,14 @@ class BackupManagerService extends IBackupManager.Stub { } } mBackupHandler.removeMessages(MSG_TIMEOUT); - if (DEBUG) Log.v(TAG, "operation " + token + " complete: finalState=" + finalState); + if (DEBUG) Log.v(TAG, "operation " + Integer.toHexString(token) + + " complete: finalState=" + finalState); return finalState == OP_ACKNOWLEDGED; } void prepareOperationTimeout(int token, long interval) { - if (DEBUG) Log.v(TAG, "starting timeout: token=" + token + " interval=" + interval); + if (DEBUG) Log.v(TAG, "starting timeout: token=" + Integer.toHexString(token) + + " interval=" + interval); mCurrentOperations.put(token, OP_PENDING); Message msg = mBackupHandler.obtainMessage(MSG_TIMEOUT, token, 0); mBackupHandler.sendMessageDelayed(msg, interval); @@ -1476,6 +1497,7 @@ class BackupManagerService extends IBackupManager.Stub { private long mToken; private PackageInfo mTargetPackage; private File mStateDir; + private int mPmToken; class RestoreRequest { public PackageInfo app; @@ -1488,11 +1510,12 @@ class BackupManagerService extends IBackupManager.Stub { } PerformRestoreTask(IBackupTransport transport, IRestoreObserver observer, - long restoreSetToken, PackageInfo targetPackage) { + long restoreSetToken, PackageInfo targetPackage, int pmToken) { mTransport = transport; mObserver = observer; mToken = restoreSetToken; mTargetPackage = targetPackage; + mPmToken = pmToken; try { mStateDir = new File(mBaseStateDir, transport.transportDirName()); @@ -1505,25 +1528,7 @@ class BackupManagerService extends IBackupManager.Stub { long startRealtime = SystemClock.elapsedRealtime(); if (DEBUG) Log.v(TAG, "Beginning restore process mTransport=" + mTransport + " mObserver=" + mObserver + " mToken=" + Long.toHexString(mToken) - + " mTargetPackage=" + mTargetPackage); - /** - * Restore sequence: - * - * 1. get the restore set description for our identity - * 2. for each app in the restore set: - * 2.a. if it's restorable on this device, add it to the restore queue - * 3. for each app in the restore queue: - * 3.a. clear the app data - * 3.b. get the restore data for the app from the transport - * 3.c. launch the backup agent for the app - * 3.d. agent.doRestore() with the data from the server - * 3.e. unbind the agent [and kill the app?] - * 4. shut down the transport - * - * On errors, we try our best to recover and move on to the next - * application, but if necessary we abort the whole operation -- - * the user is waiting, after all. - */ + + " mTargetPackage=" + mTargetPackage + " mPmToken=" + mPmToken); PackageManagerBackupAgent pmAgent = null; int error = -1; // assume error @@ -1609,6 +1614,7 @@ class BackupManagerService extends IBackupManager.Stub { EventLog.writeEvent(EventLogTags.RESTORE_TRANSPORT_FAILURE); return; } else if (packageName.equals("")) { + if (DEBUG) Log.v(TAG, "No next package, finishing restore"); break; } @@ -1733,6 +1739,15 @@ class BackupManagerService extends IBackupManager.Stub { writeRestoreTokens(); } + // We must under all circumstances tell the Package Manager to + // proceed with install notifications if it's waiting for us. + if (mPmToken > 0) { + if (DEBUG) Log.v(TAG, "finishing PM token " + mPmToken); + try { + mPackageManagerBinder.finishPackageInstall(mPmToken); + } catch (RemoteException e) { /* can't happen */ } + } + // done; we can finally release the wakelock mWakelock.release(); } @@ -2169,7 +2184,7 @@ class BackupManagerService extends IBackupManager.Stub { public String getCurrentTransport() { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP, "getCurrentTransport"); - Log.v(TAG, "... getCurrentTransport() returning " + mCurrentTransport); + if (DEBUG) Log.v(TAG, "... getCurrentTransport() returning " + mCurrentTransport); return mCurrentTransport; } @@ -2248,6 +2263,45 @@ class BackupManagerService extends IBackupManager.Stub { } } + // An application being installed will need a restore pass, then the Package Manager + // will need to be told when the restore is finished. + public void restoreAtInstall(String packageName, int token) { + if (Binder.getCallingUid() != Process.SYSTEM_UID) { + Log.w(TAG, "Non-system process uid=" + Binder.getCallingUid() + + " attemping install-time restore"); + return; + } + + long restoreSet = getAvailableRestoreToken(packageName); + if (DEBUG) Log.v(TAG, "restoreAtInstall pkg=" + packageName + + " token=" + Integer.toHexString(token)); + + if (restoreSet != 0) { + // okay, we're going to attempt a restore of this package from this restore set. + // The eventual message back into the Package Manager to run the post-install + // steps for 'token' will be issued from the restore handling code. + + // We can use a synthetic PackageInfo here because: + // 1. We know it's valid, since the Package Manager supplied the name + // 2. Only the packageName field will be used by the restore code + PackageInfo pkg = new PackageInfo(); + pkg.packageName = packageName; + + mWakelock.acquire(); + Message msg = mBackupHandler.obtainMessage(MSG_RUN_RESTORE); + msg.obj = new RestoreParams(getTransport(mCurrentTransport), null, + restoreSet, pkg, token); + mBackupHandler.sendMessage(msg); + } else { + // No way to attempt a restore; just tell the Package Manager to proceed + // with the post-install handling for this package. + if (DEBUG) Log.v(TAG, "No restore set -- skipping restore"); + try { + mPackageManagerBinder.finishPackageInstall(token); + } catch (RemoteException e) { /* can't happen */ } + } + } + // Hand off a restore session public IRestoreSession beginRestoreSession(String transport) { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP, "beginRestoreSession"); @@ -2266,7 +2320,7 @@ class BackupManagerService extends IBackupManager.Stub { // completed the given outstanding asynchronous backup/restore operation. public void opComplete(int token) { synchronized (mCurrentOpLock) { - if (DEBUG) Log.v(TAG, "opComplete: " + token); + if (DEBUG) Log.v(TAG, "opComplete: " + Integer.toHexString(token)); mCurrentOperations.put(token, OP_ACKNOWLEDGED); mCurrentOpLock.notifyAll(); } @@ -2289,6 +2343,7 @@ class BackupManagerService extends IBackupManager.Stub { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP, "getAvailableRestoreSets"); + long oldId = Binder.clearCallingIdentity(); try { if (mRestoreTransport == null) { Log.w(TAG, "Null transport getting restore sets"); @@ -2302,6 +2357,8 @@ class BackupManagerService extends IBackupManager.Stub { } catch (Exception e) { Log.e(TAG, "Error in getAvailableRestoreSets", e); return null; + } finally { + Binder.restoreCallingIdentity(oldId); } } @@ -2360,12 +2417,7 @@ class BackupManagerService extends IBackupManager.Stub { // So far so good; we're allowed to try to restore this package. Now // check whether there is data for it in the current dataset, falling back // to the ancestral dataset if not. - long token = mAncestralToken; - synchronized (mQueueLock) { - if (mEverStoredApps.contains(packageName)) { - token = mCurrentToken; - } - } + long token = getAvailableRestoreToken(packageName); // If we didn't come up with a place to look -- no ancestral dataset and // the app has never been backed up from this device -- there's nothing @@ -2378,7 +2430,7 @@ class BackupManagerService extends IBackupManager.Stub { long oldId = Binder.clearCallingIdentity(); mWakelock.acquire(); Message msg = mBackupHandler.obtainMessage(MSG_RUN_RESTORE); - msg.obj = new RestoreParams(mRestoreTransport, observer, token, app); + msg.obj = new RestoreParams(mRestoreTransport, observer, token, app, 0); mBackupHandler.sendMessage(msg); Binder.restoreCallingIdentity(oldId); return 0; @@ -2391,12 +2443,14 @@ class BackupManagerService extends IBackupManager.Stub { if (DEBUG) Log.d(TAG, "endRestoreSession"); synchronized (this) { + long oldId = Binder.clearCallingIdentity(); try { if (mRestoreTransport != null) mRestoreTransport.finishRestore(); } catch (Exception e) { Log.e(TAG, "Error in finishRestore", e); } finally { mRestoreTransport = null; + Binder.restoreCallingIdentity(oldId); } } diff --git a/services/java/com/android/server/PackageManagerService.java b/services/java/com/android/server/PackageManagerService.java index 252f2a6..90a606d 100644 --- a/services/java/com/android/server/PackageManagerService.java +++ b/services/java/com/android/server/PackageManagerService.java @@ -29,6 +29,7 @@ import org.xmlpull.v1.XmlSerializer; import android.app.ActivityManagerNative; import android.app.IActivityManager; +import android.backup.IBackupManager; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -123,6 +124,7 @@ class PackageManagerService extends IPackageManager.Stub { private static final boolean DEBUG_SETTINGS = false; private static final boolean DEBUG_PREFERRED = false; private static final boolean DEBUG_UPGRADE = false; + private static final boolean DEBUG_INSTALL = false; private static final boolean MULTIPLE_APPLICATION_UIDS = true; private static final int RADIO_UID = Process.PHONE_UID; @@ -310,6 +312,7 @@ class PackageManagerService extends IPackageManager.Stub { static final int MCS_UNBIND = 6; static final int START_CLEANING_PACKAGE = 7; static final int FIND_INSTALL_LOC = 8; + static final int POST_INSTALL = 9; // Delay time in millisecs static final int BROADCAST_DELAY = 10 * 1000; private ServiceConnection mDefContainerConn = new ServiceConnection() { @@ -324,6 +327,20 @@ class PackageManagerService extends IPackageManager.Stub { } }; + // Recordkeeping of restore-after-install operations that are currently in flight + // between the Package Manager and the Backup Manager + class PostInstallData { + public InstallArgs args; + public PackageInstalledInfo res; + + PostInstallData(InstallArgs _a, PackageInstalledInfo _r) { + args = _a; + res = _r; + } + }; + final SparseArray<PostInstallData> mRunningInstalls = new SparseArray<PostInstallData>(); + int mNextInstallToken = 1; // nonzero; will be wrapped back to 1 when ++ overflows + class PackageHandler extends Handler { final ArrayList<HandlerParams> mPendingInstalls = new ArrayList<HandlerParams>(); @@ -422,6 +439,51 @@ class PackageManagerService extends IPackageManager.Stub { } startCleaningPackages(); } break; + case POST_INSTALL: { + if (DEBUG_INSTALL) Log.v(TAG, "Handling post-install for " + msg.arg1); + PostInstallData data = mRunningInstalls.get(msg.arg1); + mRunningInstalls.delete(msg.arg1); + + if (data != null) { + InstallArgs args = data.args; + PackageInstalledInfo res = data.res; + + if (res.returnCode == PackageManager.INSTALL_SUCCEEDED) { + res.removedInfo.sendBroadcast(false, true); + Bundle extras = new Bundle(1); + extras.putInt(Intent.EXTRA_UID, res.uid); + final boolean update = res.removedInfo.removedPackage != null; + if (update) { + extras.putBoolean(Intent.EXTRA_REPLACING, true); + } + sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, + res.pkg.applicationInfo.packageName, + extras); + if (update) { + sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED, + res.pkg.applicationInfo.packageName, + extras); + } + if (res.removedInfo.args != null) { + // Remove the replaced package's older resources safely now + synchronized (mInstallLock) { + res.removedInfo.args.doPostDeleteLI(true); + } + } + } + Runtime.getRuntime().gc(); + + if (args.observer != null) { + try { + args.observer.packageInstalled(res.name, res.returnCode); + } catch (RemoteException e) { + Log.i(TAG, "Observer no longer exists."); + } + } + } else { + Log.e(TAG, "Bogus post-install token " + msg.arg1); + } + } break; } } } @@ -4253,6 +4315,12 @@ class PackageManagerService extends IPackageManager.Stub { mHandler.sendMessage(msg); } + public void finishPackageInstall(int token) { + if (DEBUG_INSTALL) Log.v(TAG, "BM finishing package install for " + token); + Message msg = mHandler.obtainMessage(POST_INSTALL, token, 0); + mHandler.sendMessage(msg); + } + private void processPendingInstall(final InstallArgs args, final int currentStatus) { // Queue up an async operation since the package installation may take a little while. mHandler.post(new Runnable() { @@ -4271,38 +4339,54 @@ class PackageManagerService extends IPackageManager.Stub { } args.doPostInstall(res.returnCode); } - // There appears to be a subtle deadlock condition if the sendPackageBroadcast - // call appears in the synchronized block above. - if (res.returnCode == PackageManager.INSTALL_SUCCEEDED) { - res.removedInfo.sendBroadcast(false, true); - Bundle extras = new Bundle(1); - extras.putInt(Intent.EXTRA_UID, res.uid); - final boolean update = res.removedInfo.removedPackage != null; - if (update) { - extras.putBoolean(Intent.EXTRA_REPLACING, true); - } - sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, - res.pkg.applicationInfo.packageName, - extras); - if (update) { - sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED, - res.pkg.applicationInfo.packageName, - extras); - } - if (res.removedInfo.args != null) { - // Remove the replaced package's older resources safely now - synchronized (mInstallLock) { - res.removedInfo.args.doPostDeleteLI(true); + + // A restore should be performed at this point if (a) the install + // succeeded, (b) the operation is not an update, and (c) the new + // package has a backupAgent defined. + final boolean update = res.removedInfo.removedPackage != null; + boolean doRestore = (!update && res.pkg.applicationInfo.backupAgentName != null); + + // Set up the post-install work request bookkeeping. This will be used + // and cleaned up by the post-install event handling regardless of whether + // there's a restore pass performed. Token values are >= 1. + int token; + if (mNextInstallToken < 0) mNextInstallToken = 1; + token = mNextInstallToken++; + + PostInstallData data = new PostInstallData(args, res); + mRunningInstalls.put(token, data); + if (DEBUG_INSTALL) Log.v(TAG, "+ starting restore round-trip " + token); + + if (res.returnCode == PackageManager.INSTALL_SUCCEEDED && doRestore) { + // Pass responsibility to the Backup Manager. It will perform a + // restore if appropriate, then pass responsibility back to the + // Package Manager to run the post-install observer callbacks + // and broadcasts. + IBackupManager bm = IBackupManager.Stub.asInterface( + ServiceManager.getService(Context.BACKUP_SERVICE)); + if (bm != null) { + if (DEBUG_INSTALL) Log.v(TAG, "token " + token + + " to BM for possible restore"); + try { + bm.restoreAtInstall(res.pkg.applicationInfo.packageName, token); + } catch (RemoteException e) { + // can't happen; the backup manager is local + } catch (Exception e) { + Log.e(TAG, "Exception trying to enqueue restore", e); + doRestore = false; } + } else { + Log.e(TAG, "Backup Manager not found!"); + doRestore = false; } } - Runtime.getRuntime().gc(); - if (args.observer != null) { - try { - args.observer.packageInstalled(res.name, res.returnCode); - } catch (RemoteException e) { - Log.i(TAG, "Observer no longer exists."); - } + + if (!doRestore) { + // No restore possible, or the Backup Manager was mysteriously not + // available -- just fire the post-install work request directly. + if (DEBUG_INSTALL) Log.v(TAG, "No restore - queue post-install for " + token); + Message msg = mHandler.obtainMessage(POST_INSTALL, token, 0); + mHandler.sendMessage(msg); } } }); diff --git a/tests/backup/Android.mk b/tests/backup/Android.mk index 0813c35..498ec40 100644 --- a/tests/backup/Android.mk +++ b/tests/backup/Android.mk @@ -23,6 +23,7 @@ LOCAL_SRC_FILES := \ LOCAL_MODULE_TAGS := optional LOCAL_MODULE := backup_helper_test +LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS) LOCAL_SHARED_LIBRARIES := libutils include $(BUILD_EXECUTABLE) @@ -33,6 +34,8 @@ include $(CLEAR_VARS) LOCAL_MODULE_TAGS := optional +LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS) + LOCAL_SRC_FILES := $(call all-subdir-java-files) LOCAL_PACKAGE_NAME := BackupTest |