summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorChristopher Tate <ctate@google.com>2009-06-02 16:11:00 -0700
committerThe Android Open Source Project <initial-contribution@android.com>2009-06-04 09:57:18 -0700
commitce74edc7eab6b1270577ac7a369243a9cac333e6 (patch)
tree6bd54cd14a4ee948e91c805a37d3ad0fa41a8b9c
parent7fb952ac237bbb99d5ccc1455225a7ea07992312 (diff)
downloadframeworks_base-ce74edc7eab6b1270577ac7a369243a9cac333e6.zip
frameworks_base-ce74edc7eab6b1270577ac7a369243a9cac333e6.tar.gz
frameworks_base-ce74edc7eab6b1270577ac7a369243a9cac333e6.tar.bz2
More backup work
* Put in some permission enforcement around agent connection notification and full-backup scheduling. * Full backup now applies to any package, not just backup participants who have declared their own android:backupAgent * The process of running the backup operation on the set of apps who have been queued for it is now done in a separate thread, with a notification mechanism from the main Backup Manager service to pass along new-agent binding knowledge. There's no longer one do-backup message on the primary Handler per target application. * The new backup thread sets up the desired transport now and passes along the newly backed-up data to it for each backup target. Two transports have been defined so far, GoogleTransport and AdbTransport; both are stubs at present. Note that at present the backup data output file seems to be properly created, but after doBackup() is called on the test app's agent it's still zero size.
-rw-r--r--core/java/android/backup/BackupManager.java6
-rw-r--r--core/java/android/backup/IBackupManager.aidl16
-rw-r--r--core/java/com/android/internal/backup/AdbTransport.java28
-rw-r--r--core/java/com/android/internal/backup/GoogleTransport.java28
-rw-r--r--core/java/com/android/internal/backup/IBackupTransport.aidl1
-rw-r--r--services/java/com/android/server/BackupManagerService.java291
-rw-r--r--services/java/com/android/server/am/ActivityManagerService.java3
7 files changed, 308 insertions, 65 deletions
diff --git a/core/java/android/backup/BackupManager.java b/core/java/android/backup/BackupManager.java
index 6f0b2ee..30f781e 100644
--- a/core/java/android/backup/BackupManager.java
+++ b/core/java/android/backup/BackupManager.java
@@ -42,6 +42,12 @@ public class BackupManager {
private IBackupManager mService;
/**
+ * Defined backup transports understood by {@link IBackupManager.selectBackupTransport}.
+ */
+ public static final int TRANSPORT_ADB = 1;
+ public static final int TRANSPORT_GOOGLE = 2;
+
+ /**
* Constructs a BackupManager object through which the application can
* communicate with the Android backup system.
*
diff --git a/core/java/android/backup/IBackupManager.aidl b/core/java/android/backup/IBackupManager.aidl
index 3468d70..f5b82fe 100644
--- a/core/java/android/backup/IBackupManager.aidl
+++ b/core/java/android/backup/IBackupManager.aidl
@@ -36,20 +36,28 @@ interface IBackupManager {
/**
* Notifies the Backup Manager Service that an agent has become available. This
* method is only invoked by the Activity Manager.
- * !!! TODO: permission
*/
oneway void agentConnected(String packageName, IBinder agent);
/**
* Notify the Backup Manager Service that an agent has unexpectedly gone away.
* This method is only invoked by the Activity Manager.
- * !!! TODO: permission
*/
oneway void agentDisconnected(String packageName);
/**
- * Schedule a full backup of the given package.
- * !!! TODO: permission
+ * Schedule a full backup of the given package. Callers must hold the
+ * android.permission.BACKUP permission to use this method.
*/
oneway void scheduleFullBackup(String packageName);
+
+ /**
+ * Specify a default backup transport. Callers must hold the
+ * android.permission.BACKUP permission to use this method.
+ *
+ * @param transportID The ID of the transport to select. This should be one
+ * of {@link BackupManager.TRANSPORT_GOOGLE} or {@link BackupManager.TRANSPORT_ADB}.
+ * @return The ID of the previously selected transport.
+ */
+ int selectBackupTransport(int transportID);
}
diff --git a/core/java/com/android/internal/backup/AdbTransport.java b/core/java/com/android/internal/backup/AdbTransport.java
new file mode 100644
index 0000000..acb3273
--- /dev/null
+++ b/core/java/com/android/internal/backup/AdbTransport.java
@@ -0,0 +1,28 @@
+package com.android.internal.backup;
+
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+
+/**
+ * Backup transport for full backup over adb. This transport pipes everything to
+ * a file in a known location in /cache, which 'adb backup' then pulls to the desktop
+ * (deleting it afterwards).
+ */
+
+public class AdbTransport extends IBackupTransport.Stub {
+
+ public int startSession() throws RemoteException {
+ return 0;
+ }
+
+ public int endSession() throws RemoteException {
+ // TODO Auto-generated method stub
+ return 0;
+ }
+
+ public int performBackup(String packageName, ParcelFileDescriptor data)
+ throws RemoteException {
+ // TODO Auto-generated method stub
+ return 0;
+ }
+}
diff --git a/core/java/com/android/internal/backup/GoogleTransport.java b/core/java/com/android/internal/backup/GoogleTransport.java
new file mode 100644
index 0000000..85ab21e
--- /dev/null
+++ b/core/java/com/android/internal/backup/GoogleTransport.java
@@ -0,0 +1,28 @@
+package com.android.internal.backup;
+
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+
+/**
+ * Backup transport for saving data to Google cloud storage.
+ */
+
+public class GoogleTransport extends IBackupTransport.Stub {
+
+ public int endSession() throws RemoteException {
+ // TODO Auto-generated method stub
+ return 0;
+ }
+
+ public int performBackup(String packageName, ParcelFileDescriptor data)
+ throws RemoteException {
+ // TODO Auto-generated method stub
+ return 0;
+ }
+
+ public int startSession() throws RemoteException {
+ // TODO Auto-generated method stub
+ return 0;
+ }
+
+}
diff --git a/core/java/com/android/internal/backup/IBackupTransport.aidl b/core/java/com/android/internal/backup/IBackupTransport.aidl
index ce39768..2b44fe7 100644
--- a/core/java/com/android/internal/backup/IBackupTransport.aidl
+++ b/core/java/com/android/internal/backup/IBackupTransport.aidl
@@ -16,6 +16,7 @@
package com.android.internal.backup;
+import android.os.Bundle;
import android.os.ParcelFileDescriptor;
/** {@hide} */
diff --git a/services/java/com/android/server/BackupManagerService.java b/services/java/com/android/server/BackupManagerService.java
index 82ed1e3..b003e76 100644
--- a/services/java/com/android/server/BackupManagerService.java
+++ b/services/java/com/android/server/BackupManagerService.java
@@ -26,6 +26,7 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
import android.net.Uri;
import android.os.Binder;
import android.os.Bundle;
@@ -34,11 +35,17 @@ import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.ParcelFileDescriptor;
+import android.os.Process;
import android.os.RemoteException;
import android.util.Log;
import android.util.SparseArray;
import android.backup.IBackupManager;
+import android.backup.BackupManager;
+
+import com.android.internal.backup.AdbTransport;
+import com.android.internal.backup.GoogleTransport;
+import com.android.internal.backup.IBackupTransport;
import java.io.File;
import java.io.FileDescriptor;
@@ -59,6 +66,7 @@ class BackupManagerService extends IBackupManager.Stub {
//private static final long COLLECTION_INTERVAL = 3 * 60 * 1000;
private static final int MSG_RUN_BACKUP = 1;
+ private static final int MSG_RUN_FULL_BACKUP = 2;
private Context mContext;
private PackageManager mPackageManager;
@@ -89,6 +97,16 @@ class BackupManagerService extends IBackupManager.Stub {
private ArrayList<BackupRequest> mBackupQueue;
private final Object mQueueLock = new Object();
+ // The thread performing the sequence of queued backups binds to each app's agent
+ // in succession. Bind notifications are asynchronously delivered through the
+ // Activity Manager; use this lock object to signal when a requested binding has
+ // completed.
+ private final Object mAgentConnectLock = new Object();
+ private IBackupAgent mConnectedAgent;
+ private volatile boolean mConnecting;
+
+ private int mTransportId;
+
private File mStateDir;
private File mDataDir;
@@ -101,6 +119,7 @@ class BackupManagerService extends IBackupManager.Stub {
mStateDir = new File(Environment.getDataDirectory(), "backup");
mStateDir.mkdirs();
mDataDir = Environment.getDownloadCacheDirectory();
+ mTransportId = BackupManager.TRANSPORT_GOOGLE;
// Build our mapping of uid to backup client services
synchronized (mBackupParticipants) {
@@ -130,6 +149,8 @@ class BackupManagerService extends IBackupManager.Stub {
return;
}
+ // !!! TODO: this is buggy right now; we wind up with duplicate participant entries
+ // after using 'adb install -r' of a participating app
String action = intent.getAction();
if (Intent.ACTION_PACKAGE_ADDED.equals(action)) {
synchronized (mBackupParticipants) {
@@ -166,7 +187,7 @@ class BackupManagerService extends IBackupManager.Stub {
// snapshot the pending-backup set and work on that
synchronized (mQueueLock) {
if (mBackupQueue == null) {
- mBackupQueue = new ArrayList();
+ mBackupQueue = new ArrayList<BackupRequest>();
for (BackupRequest b: mPendingBackups.values()) {
mBackupQueue.add(b);
}
@@ -176,7 +197,11 @@ class BackupManagerService extends IBackupManager.Stub {
// WARNING: If we crash after this line, anything in mPendingBackups will
// be lost. FIX THIS.
}
- startOneAgent();
+ //startOneAgent();
+ (new PerformBackupThread(mTransportId, mBackupQueue)).run();
+ break;
+
+ case MSG_RUN_FULL_BACKUP:
break;
}
}
@@ -221,23 +246,15 @@ class BackupManagerService extends IBackupManager.Stub {
}
}
- void processOneBackup(String packageName, IBackupAgent bs) {
+ void processOneBackup(BackupRequest request, IBackupAgent agent, IBackupTransport transport) {
+ final String packageName = request.appInfo.packageName;
Log.d(TAG, "processOneBackup doBackup() on " + packageName);
- BackupRequest request;
- synchronized (mQueueLock) {
- if (mBackupQueue == null) {
- Log.d(TAG, "mBackupQueue is null. WHY?");
- }
- request = mBackupQueue.get(0);
- }
-
try {
- // !!! TODO right now these naming schemes limit applications to
- // one backup service per package
- File savedStateName = new File(mStateDir, request.appInfo.packageName);
- File backupDataName = new File(mDataDir, request.appInfo.packageName + ".data");
- File newStateName = new File(mStateDir, request.appInfo.packageName + ".new");
+ // !!! TODO: get the state file dir from the transport
+ File savedStateName = new File(mStateDir, packageName);
+ File backupDataName = new File(mDataDir, packageName + ".data");
+ File newStateName = new File(mStateDir, packageName + ".new");
// In a full backup, we pass a null ParcelFileDescriptor as
// the saved-state "file"
@@ -259,9 +276,10 @@ class BackupManagerService extends IBackupManager.Stub {
ParcelFileDescriptor.MODE_CREATE);
// Run the target's backup pass
+ boolean success = false;
try {
- // TODO: Make this oneway
- bs.doBackup(savedState, backupData, newState);
+ agent.doBackup(savedState, backupData, newState);
+ success = true;
} finally {
if (savedState != null) {
savedState.close();
@@ -270,43 +288,34 @@ class BackupManagerService extends IBackupManager.Stub {
newState.close();
}
- // !!! TODO: Now propagate the newly-backed-up data to the transport
-
- // !!! TODO: After successful transport, delete the now-stale data
- // and juggle the files so that next time the new state is passed
- //backupDataName.delete();
- newStateName.renameTo(savedStateName);
-
+ // Now propagate the newly-backed-up data to the transport
+ if (success) {
+ backupData =
+ ParcelFileDescriptor.open(backupDataName, ParcelFileDescriptor.MODE_READ_ONLY);
+ int error = transport.performBackup(packageName, backupData);
+
+ // !!! TODO: After successful transport, delete the now-stale data
+ // and juggle the files so that next time the new state is passed
+ //backupDataName.delete();
+ newStateName.renameTo(savedStateName);
+ }
} catch (FileNotFoundException fnf) {
Log.d(TAG, "File not found on backup: ");
fnf.printStackTrace();
} catch (RemoteException e) {
- Log.d(TAG, "Remote target " + packageName + " threw during backup:");
+ Log.d(TAG, "Remote target " + request.appInfo.packageName + " threw during backup:");
e.printStackTrace();
} catch (Exception e) {
Log.w(TAG, "Final exception guard in backup: ");
e.printStackTrace();
}
- synchronized (mQueueLock) {
- mBackupQueue.remove(0);
- }
-
- if (request != null) {
- try {
- mActivityManager.unbindBackupAgent(request.appInfo);
- } catch (RemoteException e) {
- // can't happen
- }
- }
-
- // start the next one
- startOneAgent();
}
// Add the backup agents in the given package to our set of known backup participants.
// If 'packageName' is null, adds all backup agents in the whole system.
void addPackageParticipantsLocked(String packageName) {
// Look for apps that define the android:backupAgent attribute
+ if (DEBUG) Log.v(TAG, "addPackageParticipantsLocked: " + packageName);
List<ApplicationInfo> targetApps = allAgentApps();
addPackageParticipantsLockedInner(packageName, targetApps);
}
@@ -336,6 +345,7 @@ class BackupManagerService extends IBackupManager.Stub {
// Remove the given package's backup services from our known active set. If
// 'packageName' is null, *all* backup services will be removed.
void removePackageParticipantsLocked(String packageName) {
+ if (DEBUG) Log.v(TAG, "removePackageParticipantsLocked: " + packageName);
List<ApplicationInfo> allApps = null;
if (packageName != null) {
allApps = new ArrayList<ApplicationInfo>();
@@ -354,6 +364,13 @@ class BackupManagerService extends IBackupManager.Stub {
private void removePackageParticipantsLockedInner(String packageName,
List<ApplicationInfo> agents) {
+ if (DEBUG) {
+ Log.v(TAG, "removePackageParticipantsLockedInner (" + packageName
+ + ") removing " + agents.size() + " entries");
+ for (ApplicationInfo a : agents) {
+ Log.v(TAG, " - " + a);
+ }
+ }
for (ApplicationInfo app : agents) {
if (packageName == null || app.packageName.equals(packageName)) {
int uid = app.uid;
@@ -361,8 +378,7 @@ class BackupManagerService extends IBackupManager.Stub {
if (set != null) {
set.remove(app);
if (set.size() == 0) {
- mBackupParticipants.put(uid, null);
- }
+ mBackupParticipants.delete(uid); }
}
}
}
@@ -390,6 +406,7 @@ class BackupManagerService extends IBackupManager.Stub {
Log.e(TAG, "updatePackageParticipants called with null package name");
return;
}
+ if (DEBUG) Log.v(TAG, "updatePackageParticipantsLocked: " + packageName);
// brute force but small code size
List<ApplicationInfo> allApps = allAgentApps();
@@ -397,6 +414,128 @@ class BackupManagerService extends IBackupManager.Stub {
addPackageParticipantsLockedInner(packageName, allApps);
}
+ // ----- Back up a set of applications via a worker thread -----
+
+ class PerformBackupThread extends Thread {
+ private static final String TAG = "PerformBackupThread";
+ int mTransport;
+ ArrayList<BackupRequest> mQueue;
+
+ public PerformBackupThread(int transportId, ArrayList<BackupRequest> queue) {
+ mTransport = transportId;
+ mQueue = queue;
+ }
+
+ @Override
+ 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");
+
+ // stand up the current transport
+ IBackupTransport transport = null;
+ switch (mTransport) {
+ case BackupManager.TRANSPORT_ADB:
+ if (DEBUG) Log.v(TAG, "Initializing adb transport");
+ transport = new AdbTransport();
+ break;
+
+ case BackupManager.TRANSPORT_GOOGLE:
+ if (DEBUG) Log.v(TAG, "Initializing Google transport");
+ //!!! TODO: stand up the google backup transport here
+ transport = new GoogleTransport();
+ break;
+
+ default:
+ Log.e(TAG, "Perform backup with unknown transport " + mTransport);
+ // !!! TODO: re-enqueue the backup queue for later?
+ return;
+ }
+
+ try {
+ transport.startSession();
+ } catch (Exception e) {
+ Log.e(TAG, "Error starting backup session");
+ e.printStackTrace();
+ // !!! TODO: re-enqueue the backup queue for later?
+ return;
+ }
+
+ // The transport is up and running; now run all the backups in our queue
+ doQueuedBackups(transport);
+
+ // Finally, tear down the transport
+ try {
+ transport.endSession();
+ } catch (Exception e) {
+ Log.e(TAG, "Error ending transport");
+ e.printStackTrace();
+ }
+ }
+
+ private void doQueuedBackups(IBackupTransport transport) {
+ for (BackupRequest request : mQueue) {
+ Log.d(TAG, "starting agent for " + request);
+ // !!! TODO: need to handle the restore case?
+
+ IBackupAgent agent = null;
+ int mode = (request.fullBackup)
+ ? IApplicationThread.BACKUP_MODE_FULL
+ : IApplicationThread.BACKUP_MODE_INCREMENTAL;
+ try {
+ synchronized(mAgentConnectLock) {
+ mConnecting = true;
+ mConnectedAgent = null;
+ 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
+ } catch (SecurityException ex) {
+ // Try for the next one.
+ 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) {
+ // can't happen
+ }
+ }
+ }
+ }
+
// ----- IBackupManager binder interface -----
public void dataChanged(String packageName) throws RemoteException {
@@ -432,45 +571,75 @@ class BackupManagerService extends IBackupManager.Stub {
// Schedule a backup pass in a few minutes. As backup-eligible data
// keeps changing, continue to defer the backup pass until things
// settle down, to avoid extra overhead.
+ mBackupHandler.removeMessages(MSG_RUN_BACKUP);
mBackupHandler.sendEmptyMessageDelayed(MSG_RUN_BACKUP, COLLECTION_INTERVAL);
}
}
}
- // Schedule a backup pass for a given package, even if the caller is not part of
- // that uid or package itself.
+ // Schedule a backup pass for a given package. This method will schedule a
+ // full backup even for apps that do not declare an android:backupAgent, so
+ // use with care.
public void scheduleFullBackup(String packageName) throws RemoteException {
- // !!! TODO: protect with a signature-or-system permission?
+ mContext.enforceCallingPermission("android.permission.BACKUP", "scheduleFullBackup");
+
+ if (DEBUG) Log.v(TAG, "Scheduling immediate full backup for " + packageName);
synchronized (mQueueLock) {
- int numKeys = mBackupParticipants.size();
- for (int index = 0; index < numKeys; index++) {
- int uid = mBackupParticipants.keyAt(index);
- HashSet<ApplicationInfo> servicesAtUid = mBackupParticipants.get(uid);
- for (ApplicationInfo app: servicesAtUid) {
- if (app.packageName.equals(packageName)) {
- mPendingBackups.put(app, new BackupRequest(app, true));
- }
- }
+ try {
+ ApplicationInfo app = mPackageManager.getApplicationInfo(packageName, 0);
+ mPendingBackups.put(app, new BackupRequest(app, true));
+ mBackupHandler.sendEmptyMessage(MSG_RUN_FULL_BACKUP);
+ } catch (NameNotFoundException e) {
+ Log.w(TAG, "Could not find app for " + packageName + " to schedule full backup");
}
}
}
- // Callback: a requested backup agent has been instantiated
+ // Select which transport to use for the next backup operation
+ public int selectBackupTransport(int transportId) {
+ mContext.enforceCallingPermission("android.permission.BACKUP", "selectBackupTransport");
+
+ int prevTransport = mTransportId;
+ mTransportId = transportId;
+ return prevTransport;
+ }
+
+ // Callback: a requested backup agent has been instantiated. This should only
+ // be called from the Activity Manager.
public void agentConnected(String packageName, IBinder agentBinder) {
- Log.d(TAG, "agentConnected pkg=" + packageName + " agent=" + agentBinder);
- IBackupAgent bs = IBackupAgent.Stub.asInterface(agentBinder);
- processOneBackup(packageName, bs);
+ synchronized(mAgentConnectLock) {
+ if (Binder.getCallingUid() == Process.SYSTEM_UID) {
+ Log.d(TAG, "agentConnected pkg=" + packageName + " agent=" + agentBinder);
+ IBackupAgent agent = IBackupAgent.Stub.asInterface(agentBinder);
+ mConnectedAgent = agent;
+ mConnecting = false;
+ } else {
+ Log.w(TAG, "Non-system process uid=" + Binder.getCallingUid()
+ + " claiming agent connected");
+ }
+ mAgentConnectLock.notifyAll();
+ }
}
// Callback: a backup agent has failed to come up, or has unexpectedly quit.
// If the agent failed to come up in the first place, the agentBinder argument
- // will be null.
+ // will be null. This should only be called from the Activity Manager.
public void agentDisconnected(String packageName) {
// TODO: handle backup being interrupted
+ synchronized(mAgentConnectLock) {
+ if (Binder.getCallingUid() == Process.SYSTEM_UID) {
+ mConnectedAgent = null;
+ mConnecting = false;
+ } else {
+ Log.w(TAG, "Non-system process uid=" + Binder.getCallingUid()
+ + " claiming agent disconnected");
+ }
+ mAgentConnectLock.notifyAll();
+ }
}
-
-
+
+
@Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
synchronized (mQueueLock) {
diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java
index 5202b6f..736c0da 100644
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -10377,6 +10377,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen
return;
}
+ long oldIdent = Binder.clearCallingIdentity();
try {
IBackupManager bm = IBackupManager.Stub.asInterface(
ServiceManager.getService(Context.BACKUP_SERVICE));
@@ -10386,6 +10387,8 @@ public final class ActivityManagerService extends ActivityManagerNative implemen
} catch (Exception e) {
Log.w(TAG, "Exception trying to deliver BackupAgent binding: ");
e.printStackTrace();
+ } finally {
+ Binder.restoreCallingIdentity(oldIdent);
}
}
}