diff options
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); } } } |