More restore plumbing, plus add suggested-backoff to transport API
Adds most of the code for a background-thread restore process, structured much like the backup thread. Broke some common functionality out into a helper function for doing a synchronous wait for a requested agent to attach. Added a method to IBackupTransport whereby the transport will be asked for an opinion on whether this is a good time for a backup to happen. It will reply with the results of its policymaking around backoff intervals, time-of-day selection, etc.
This commit is contained in:
@@ -13,6 +13,10 @@ import android.os.RemoteException;
|
|||||||
|
|
||||||
public class AdbTransport extends IBackupTransport.Stub {
|
public class AdbTransport extends IBackupTransport.Stub {
|
||||||
|
|
||||||
|
public long requestBackupTime() throws RemoteException {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
public int startSession() throws RemoteException {
|
public int startSession() throws RemoteException {
|
||||||
// TODO Auto-generated method stub
|
// TODO Auto-generated method stub
|
||||||
return 0;
|
return 0;
|
||||||
|
|||||||
@@ -11,6 +11,10 @@ import android.os.RemoteException;
|
|||||||
|
|
||||||
public class GoogleTransport extends IBackupTransport.Stub {
|
public class GoogleTransport extends IBackupTransport.Stub {
|
||||||
|
|
||||||
|
public long requestBackupTime() throws RemoteException {
|
||||||
|
return 0; // !!! TODO: implement real backoff policy
|
||||||
|
}
|
||||||
|
|
||||||
public int startSession() throws RemoteException {
|
public int startSession() throws RemoteException {
|
||||||
// TODO Auto-generated method stub
|
// TODO Auto-generated method stub
|
||||||
return 0;
|
return 0;
|
||||||
|
|||||||
@@ -40,6 +40,16 @@ interface IBackupTransport {
|
|||||||
- cloud: tear down connection etc
|
- cloud: tear down connection etc
|
||||||
- adb: close the file
|
- adb: close the file
|
||||||
*/
|
*/
|
||||||
|
/**
|
||||||
|
* Verify that this is a suitable time for a backup pass. This should return zero
|
||||||
|
* if a backup is reasonable right now, false otherwise. This method will be called
|
||||||
|
* outside of the {@link #startSession}/{@link #endSession} pair.
|
||||||
|
*
|
||||||
|
* <p>If this is not a suitable time for a backup, the transport should suggest a
|
||||||
|
* backoff delay, in milliseconds, after which the Backup Manager should try again.
|
||||||
|
*/
|
||||||
|
long requestBackupTime();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Establish a connection to the back-end data repository, if necessary. If the transport
|
* Establish a connection to the back-end data repository, if necessary. If the transport
|
||||||
* needs to initialize state that is not tied to individual applications' backup operations,
|
* needs to initialize state that is not tied to individual applications' backup operations,
|
||||||
@@ -64,33 +74,30 @@ interface IBackupTransport {
|
|||||||
/**
|
/**
|
||||||
* Get the set of backups currently available over this transport.
|
* Get the set of backups currently available over this transport.
|
||||||
*
|
*
|
||||||
* @return A bundle containing two elements: an int array under the key
|
* @return Descriptions of the set of restore images available for this device.
|
||||||
* "tokens" whose entries are a transport-private identifier for each backup set;
|
|
||||||
* and a String array under the key "names" whose entries are the user-meaningful
|
|
||||||
* names corresponding to the backup sets at each index in the tokens array.
|
|
||||||
**/
|
**/
|
||||||
RestoreSet[] getAvailableRestoreSets();
|
RestoreSet[] getAvailableRestoreSets();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the set of applications from a given backup image.
|
* Get the set of applications from a given restore image.
|
||||||
*
|
*
|
||||||
* @param token A backup token as returned by {@link availableBackups}.
|
* @param token A backup token as returned by {@link #getAvailableRestoreSets}.
|
||||||
* @return An array of PackageInfo objects describing all of the applications
|
* @return An array of PackageInfo objects describing all of the applications
|
||||||
* available for restore from the given backup set. This should include the list
|
* available for restore from this restore image. This should include the list
|
||||||
* of signatures for each package so that the Backup Manager can filter using that
|
* of signatures for each package so that the Backup Manager can filter using that
|
||||||
* information.
|
* information.
|
||||||
*/
|
*/
|
||||||
PackageInfo[] getAppSet(int token);
|
PackageInfo[] getAppSet(int token);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve one application's data from the backup destination.
|
* Retrieve one application's data from the backing store.
|
||||||
*
|
*
|
||||||
* @param token The backup record from which a restore is being requested.
|
* @param token The backup record from which a restore is being requested.
|
||||||
* @param packageInfo The identity of the application whose data is being restored.
|
* @param packageInfo The identity of the application whose data is being restored.
|
||||||
* This must include the signature list for the package; it is up to the transport
|
* This must include the signature list for the package; it is up to the transport
|
||||||
* to verify that the requested app's signatures match the saved backup record
|
* to verify that the requested app's signatures match the saved backup record
|
||||||
* because the transport cannot necessarily trust the client device.
|
* because the transport cannot necessarily trust the client device.
|
||||||
* @param data An open, writeable file into which the backup image should be stored.
|
* @param data An open, writable file into which the backup image should be stored.
|
||||||
* @return Zero on success; a nonzero error code on failure.
|
* @return Zero on success; a nonzero error code on failure.
|
||||||
*/
|
*/
|
||||||
int getRestoreData(int token, in PackageInfo packageInfo, in ParcelFileDescriptor data);
|
int getRestoreData(int token, in PackageInfo packageInfo, in ParcelFileDescriptor data);
|
||||||
|
|||||||
@@ -215,7 +215,8 @@ class BackupManagerService extends IBackupManager.Stub {
|
|||||||
// Look up the package info & signatures. This is first so that if it
|
// Look up the package info & signatures. This is first so that if it
|
||||||
// throws an exception, there's no file setup yet that would need to
|
// throws an exception, there's no file setup yet that would need to
|
||||||
// be unraveled.
|
// be unraveled.
|
||||||
PackageInfo packInfo = mPackageManager.getPackageInfo(packageName, PackageManager.GET_SIGNATURES);
|
PackageInfo packInfo = mPackageManager.getPackageInfo(packageName,
|
||||||
|
PackageManager.GET_SIGNATURES);
|
||||||
|
|
||||||
// !!! TODO: get the state file dir from the transport
|
// !!! TODO: get the state file dir from the transport
|
||||||
File savedStateName = new File(mStateDir, packageName);
|
File savedStateName = new File(mStateDir, packageName);
|
||||||
@@ -367,7 +368,8 @@ class BackupManagerService extends IBackupManager.Stub {
|
|||||||
if (N > 0) {
|
if (N > 0) {
|
||||||
for (int a = N-1; a >= 0; a--) {
|
for (int a = N-1; a >= 0; a--) {
|
||||||
ApplicationInfo app = allApps.get(a);
|
ApplicationInfo app = allApps.get(a);
|
||||||
if (app.backupAgentName == null) {
|
if (((app.flags&ApplicationInfo.FLAG_ALLOW_BACKUP) == 0)
|
||||||
|
|| app.backupAgentName == null) {
|
||||||
allApps.remove(a);
|
allApps.remove(a);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -411,6 +413,40 @@ class BackupManagerService extends IBackupManager.Stub {
|
|||||||
return transport;
|
return transport;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fire off a backup agent, blocking until it attaches or times out
|
||||||
|
IBackupAgent bindToAgentSynchronous(ApplicationInfo app, int mode) {
|
||||||
|
IBackupAgent agent = null;
|
||||||
|
synchronized(mAgentConnectLock) {
|
||||||
|
mConnecting = true;
|
||||||
|
mConnectedAgent = null;
|
||||||
|
try {
|
||||||
|
if (mActivityManager.bindBackupAgent(app, mode)) {
|
||||||
|
Log.d(TAG, "awaiting agent for " + app);
|
||||||
|
|
||||||
|
// success; wait for the agent to arrive
|
||||||
|
while (mConnecting && mConnectedAgent == null) {
|
||||||
|
try {
|
||||||
|
mAgentConnectLock.wait(10000);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
// just retry
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if we timed out with no connect, abort and move on
|
||||||
|
if (mConnecting == true) {
|
||||||
|
Log.w(TAG, "Timeout waiting for agent " + app);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
agent = mConnectedAgent;
|
||||||
|
}
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
// can't happen
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return agent;
|
||||||
|
}
|
||||||
|
|
||||||
// ----- Back up a set of applications via a worker thread -----
|
// ----- Back up a set of applications via a worker thread -----
|
||||||
|
|
||||||
class PerformBackupThread extends Thread {
|
class PerformBackupThread extends Thread {
|
||||||
@@ -425,15 +461,6 @@ class BackupManagerService extends IBackupManager.Stub {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
/*
|
|
||||||
* 1. start up the current transport
|
|
||||||
* 2. for each item in the queue:
|
|
||||||
* 2a. bind the agent [wait for async attach]
|
|
||||||
* 2b. set up the files and call doBackup()
|
|
||||||
* 2c. unbind the agent
|
|
||||||
* 3. tear down the transport
|
|
||||||
* 4. done!
|
|
||||||
*/
|
|
||||||
if (DEBUG) Log.v(TAG, "Beginning backup of " + mQueue.size() + " targets");
|
if (DEBUG) Log.v(TAG, "Beginning backup of " + mQueue.size() + " targets");
|
||||||
|
|
||||||
// stand up the current transport
|
// stand up the current transport
|
||||||
@@ -442,6 +469,15 @@ class BackupManagerService extends IBackupManager.Stub {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// start up the transport
|
||||||
|
try {
|
||||||
|
transport.startSession();
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "Error session transport");
|
||||||
|
e.printStackTrace();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// The transport is up and running; now run all the backups in our queue
|
// The transport is up and running; now run all the backups in our queue
|
||||||
doQueuedBackups(transport);
|
doQueuedBackups(transport);
|
||||||
|
|
||||||
@@ -456,62 +492,155 @@ class BackupManagerService extends IBackupManager.Stub {
|
|||||||
|
|
||||||
private void doQueuedBackups(IBackupTransport transport) {
|
private void doQueuedBackups(IBackupTransport transport) {
|
||||||
for (BackupRequest request : mQueue) {
|
for (BackupRequest request : mQueue) {
|
||||||
Log.d(TAG, "starting agent for " + request);
|
Log.d(TAG, "starting agent for backup of " + request);
|
||||||
// !!! TODO: need to handle the restore case?
|
|
||||||
|
|
||||||
IBackupAgent agent = null;
|
IBackupAgent agent = null;
|
||||||
int mode = (request.fullBackup)
|
int mode = (request.fullBackup)
|
||||||
? IApplicationThread.BACKUP_MODE_FULL
|
? IApplicationThread.BACKUP_MODE_FULL
|
||||||
: IApplicationThread.BACKUP_MODE_INCREMENTAL;
|
: IApplicationThread.BACKUP_MODE_INCREMENTAL;
|
||||||
try {
|
try {
|
||||||
synchronized(mAgentConnectLock) {
|
agent = bindToAgentSynchronous(request.appInfo, mode);
|
||||||
mConnecting = true;
|
if (agent != null) {
|
||||||
mConnectedAgent = null;
|
processOneBackup(request, agent, transport);
|
||||||
if (mActivityManager.bindBackupAgent(request.appInfo, mode)) {
|
|
||||||
Log.d(TAG, "awaiting agent for " + request);
|
|
||||||
|
|
||||||
// success; wait for the agent to arrive
|
|
||||||
while (mConnecting && mConnectedAgent == null) {
|
|
||||||
try {
|
|
||||||
mAgentConnectLock.wait(10000);
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
// just retry
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// if we timed out with no connect, abort and move on
|
|
||||||
if (mConnecting == true) {
|
|
||||||
Log.w(TAG, "Timeout waiting for agent " + request);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
agent = mConnectedAgent;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (RemoteException e) {
|
|
||||||
// can't happen; activity manager is local
|
// unbind even on timeout, just in case
|
||||||
|
mActivityManager.unbindBackupAgent(request.appInfo);
|
||||||
} catch (SecurityException ex) {
|
} catch (SecurityException ex) {
|
||||||
// Try for the next one.
|
// Try for the next one.
|
||||||
Log.d(TAG, "error in bind", ex);
|
Log.d(TAG, "error in bind", ex);
|
||||||
}
|
|
||||||
|
|
||||||
// successful bind? run the backup for this agent
|
|
||||||
if (agent != null) {
|
|
||||||
processOneBackup(request, agent, transport);
|
|
||||||
}
|
|
||||||
|
|
||||||
// send the unbind even on timeout, just in case
|
|
||||||
try {
|
|
||||||
mActivityManager.unbindBackupAgent(request.appInfo);
|
|
||||||
} catch (RemoteException e) {
|
} catch (RemoteException e) {
|
||||||
// can't happen
|
// can't happen
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ----- Restore handling -----
|
||||||
|
|
||||||
|
// Is the given package restorable on this device? Returns the on-device app's
|
||||||
|
// ApplicationInfo struct if it is; null if not.
|
||||||
|
//
|
||||||
|
// !!! TODO: also consider signatures
|
||||||
|
ApplicationInfo isRestorable(PackageInfo packageInfo) {
|
||||||
|
if (packageInfo.packageName != null) {
|
||||||
|
try {
|
||||||
|
ApplicationInfo app = mPackageManager.getApplicationInfo(packageInfo.packageName,
|
||||||
|
PackageManager.GET_SIGNATURES);
|
||||||
|
if ((app.flags & ApplicationInfo.FLAG_ALLOW_BACKUP) != 0) {
|
||||||
|
return app;
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
// doesn't exist on this device, or other error -- just ignore it.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
class PerformRestoreThread extends Thread {
|
||||||
|
private IBackupTransport mTransport;
|
||||||
|
|
||||||
|
PerformRestoreThread(IBackupTransport transport) {
|
||||||
|
mTransport = transport;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
/**
|
||||||
|
* Restore sequence:
|
||||||
|
*
|
||||||
|
* 1. start up the transport session
|
||||||
|
* 2. get the restore set description for our identity
|
||||||
|
* 3. for each app in the restore set:
|
||||||
|
* 3.a. if it's restorable on this device, add it to the restore queue
|
||||||
|
* 4. for each app in the restore queue:
|
||||||
|
* 4.b. get the restore data for the app from the transport
|
||||||
|
* 4.c. launch the backup agent for the app
|
||||||
|
* 4.d. agent.doRestore() with the data from the server
|
||||||
|
* 4.e. unbind the agent [and kill the app?]
|
||||||
|
* 5. shut down the transport
|
||||||
|
*/
|
||||||
|
|
||||||
|
int err = -1;
|
||||||
|
try {
|
||||||
|
err = mTransport.startSession();
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "Error starting transport for restore");
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (err == 0) {
|
||||||
|
// build the set of apps to restore
|
||||||
|
try {
|
||||||
|
RestoreSet[] images = mTransport.getAvailableRestoreSets();
|
||||||
|
if (images.length > 0) {
|
||||||
|
// !!! for now we always take the first set
|
||||||
|
RestoreSet image = images[0];
|
||||||
|
|
||||||
|
// build the set of apps we will attempt to restore
|
||||||
|
PackageInfo[] packages = mTransport.getAppSet(image.token);
|
||||||
|
HashSet<ApplicationInfo> appsToRestore = new HashSet<ApplicationInfo>();
|
||||||
|
for (PackageInfo pkg: packages) {
|
||||||
|
ApplicationInfo app = isRestorable(pkg);
|
||||||
|
if (app != null) {
|
||||||
|
appsToRestore.add(app);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// now run the restore queue
|
||||||
|
doQueuedRestores(appsToRestore);
|
||||||
|
}
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
// can't happen; transports run locally
|
||||||
|
}
|
||||||
|
|
||||||
|
// done; shut down the transport
|
||||||
|
try {
|
||||||
|
mTransport.endSession();
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "Error ending transport for restore");
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// even if the initial session startup failed, report that we're done here
|
||||||
|
}
|
||||||
|
|
||||||
|
// restore each app in the queue
|
||||||
|
void doQueuedRestores(HashSet<ApplicationInfo> appsToRestore) {
|
||||||
|
for (ApplicationInfo app : appsToRestore) {
|
||||||
|
Log.d(TAG, "starting agent for restore of " + app);
|
||||||
|
|
||||||
|
IBackupAgent agent = null;
|
||||||
|
try {
|
||||||
|
agent = bindToAgentSynchronous(app, IApplicationThread.BACKUP_MODE_RESTORE);
|
||||||
|
if (agent != null) {
|
||||||
|
processOneRestore(app, agent);
|
||||||
|
}
|
||||||
|
|
||||||
|
// unbind even on timeout, just in case
|
||||||
|
mActivityManager.unbindBackupAgent(app);
|
||||||
|
} catch (SecurityException ex) {
|
||||||
|
// Try for the next one.
|
||||||
|
Log.d(TAG, "error in bind", ex);
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
// can't happen
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// do the guts of a restore
|
||||||
|
void processOneRestore(ApplicationInfo app, IBackupAgent agent) {
|
||||||
|
// !!! TODO: actually run the restore through mTransport
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// ----- IBackupManager binder interface -----
|
// ----- IBackupManager binder interface -----
|
||||||
|
|
||||||
public void dataChanged(String packageName) throws RemoteException {
|
public void dataChanged(String packageName) throws RemoteException {
|
||||||
// Record that we need a backup pass for the caller. Since multiple callers
|
// Record that we need a backup pass for the caller. Since multiple callers
|
||||||
// may share a uid, we need to note all candidates within that uid and schedule
|
// may share a uid, we need to note all candidates within that uid and schedule
|
||||||
@@ -548,6 +677,8 @@ class BackupManagerService extends IBackupManager.Stub {
|
|||||||
mBackupHandler.removeMessages(MSG_RUN_BACKUP);
|
mBackupHandler.removeMessages(MSG_RUN_BACKUP);
|
||||||
mBackupHandler.sendEmptyMessageDelayed(MSG_RUN_BACKUP, COLLECTION_INTERVAL);
|
mBackupHandler.sendEmptyMessageDelayed(MSG_RUN_BACKUP, COLLECTION_INTERVAL);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
Log.w(TAG, "dataChanged but no participant pkg " + packageName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user