diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 3242ed2c335b7..9cf532477e041 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -52,6 +52,10 @@
+
+
+
+
diff --git a/libs/utils/BackupData.cpp b/libs/utils/BackupData.cpp
index 0cef35aed5d6b..2535094a89d74 100644
--- a/libs/utils/BackupData.cpp
+++ b/libs/utils/BackupData.cpp
@@ -196,8 +196,9 @@ BackupDataReader::Status()
m_done = true; \
} else { \
m_status = errno; \
+ LOGD("CHECK_SIZE(a=%ld e=%ld) failed at line %d m_status='%s'", \
+ long(actual), long(expected), __LINE__, strerror(m_status)); \
} \
- LOGD("CHECK_SIZE failed with at line %d m_status='%s'", __LINE__, strerror(m_status)); \
return m_status; \
} \
} while(0)
diff --git a/services/java/com/android/server/BackupManagerService.java b/services/java/com/android/server/BackupManagerService.java
index 2fa18bf98c5f0..83b55c5351432 100644
--- a/services/java/com/android/server/BackupManagerService.java
+++ b/services/java/com/android/server/BackupManagerService.java
@@ -31,7 +31,6 @@ import android.content.ServiceConnection;
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageDataObserver;
import android.content.pm.PackageInfo;
-import android.content.pm.PermissionInfo;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.PackageManager;
import android.content.pm.Signature;
@@ -67,6 +66,7 @@ import com.android.server.PackageManagerBackupAgent.Metadata;
import java.io.EOFException;
import java.io.File;
import java.io.FileDescriptor;
+import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.RandomAccessFile;
@@ -89,11 +89,14 @@ class BackupManagerService extends IBackupManager.Stub {
// the first backup pass.
private static final long FIRST_BACKUP_INTERVAL = 12 * AlarmManager.INTERVAL_HOUR;
- private static final String RUN_BACKUP_ACTION = "_backup_run_";
+ private static final String RUN_BACKUP_ACTION = "android.backup.intent.RUN";
+ private static final String RUN_INITIALIZE_ACTION = "android.backup.intent.INIT";
+ private static final String RUN_CLEAR_ACTION = "android.backup.intent.CLEAR";
private static final int MSG_RUN_BACKUP = 1;
private static final int MSG_RUN_FULL_BACKUP = 2;
private static final int MSG_RUN_RESTORE = 3;
private static final int MSG_RUN_CLEAR = 4;
+ private static final int MSG_RUN_INITIALIZE = 5;
// Event tags -- see system/core/logcat/event-log-tags
private static final int BACKUP_DATA_CHANGED_EVENT = 2820;
@@ -123,9 +126,8 @@ class BackupManagerService extends IBackupManager.Stub {
boolean mProvisioned;
PowerManager.WakeLock mWakelock;
final BackupHandler mBackupHandler = new BackupHandler();
- PendingIntent mRunBackupIntent;
- BroadcastReceiver mRunBackupReceiver;
- IntentFilter mRunBackupFilter;
+ PendingIntent mRunBackupIntent, mRunInitIntent;
+ BroadcastReceiver mRunBackupReceiver, mRunInitReceiver;
// map UIDs to the set of backup client services within that UID's app set
final SparseArray> mBackupParticipants
= new SparseArray>();
@@ -206,6 +208,10 @@ class BackupManagerService extends IBackupManager.Stub {
private RandomAccessFile mEverStoredStream;
HashSet mEverStoredApps = new HashSet();
+ // Persistently track the need to do a full init
+ static final String INIT_SENTINEL_FILE_NAME = "_need_init_";
+ HashSet mPendingInits = new HashSet(); // transport names
+ boolean mInitInProgress = false;
public BackupManagerService(Context context) {
mContext = context;
@@ -218,23 +224,32 @@ class BackupManagerService extends IBackupManager.Stub {
// Set up our bookkeeping
boolean areEnabled = Settings.Secure.getInt(context.getContentResolver(),
Settings.Secure.BACKUP_ENABLED, 0) != 0;
- // !!! TODO: mProvisioned needs to default to 0, not 1.
mProvisioned = Settings.Secure.getInt(context.getContentResolver(),
Settings.Secure.BACKUP_PROVISIONED, 0) != 0;
mBaseStateDir = new File(Environment.getDataDirectory(), "backup");
mDataDir = Environment.getDownloadCacheDirectory();
+ // Alarm receivers for scheduled backups & initialization operations
mRunBackupReceiver = new RunBackupReceiver();
- mRunBackupFilter = new IntentFilter();
- mRunBackupFilter.addAction(RUN_BACKUP_ACTION);
- context.registerReceiver(mRunBackupReceiver, mRunBackupFilter);
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(RUN_BACKUP_ACTION);
+ context.registerReceiver(mRunBackupReceiver, filter,
+ android.Manifest.permission.BACKUP, null);
+
+ mRunInitReceiver = new RunInitializeReceiver();
+ filter = new IntentFilter();
+ filter.addAction(RUN_INITIALIZE_ACTION);
+ context.registerReceiver(mRunInitReceiver, filter,
+ android.Manifest.permission.BACKUP, null);
Intent backupIntent = new Intent(RUN_BACKUP_ACTION);
- // !!! TODO: restrict delivery to our receiver; the naive setClass() doesn't seem to work
- //backupIntent.setClass(context, mRunBackupReceiver.getClass());
backupIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
mRunBackupIntent = PendingIntent.getBroadcast(context, MSG_RUN_BACKUP, backupIntent, 0);
+ Intent initIntent = new Intent(RUN_INITIALIZE_ACTION);
+ backupIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+ mRunInitIntent = PendingIntent.getBroadcast(context, MSG_RUN_INITIALIZE, initIntent, 0);
+
// Set up the backup-request journaling
mJournalDir = new File(mBaseStateDir, "pending");
mJournalDir.mkdirs(); // creates mBaseStateDir along the way
@@ -287,14 +302,52 @@ class BackupManagerService extends IBackupManager.Stub {
private class RunBackupReceiver extends BroadcastReceiver {
public void onReceive(Context context, Intent intent) {
if (RUN_BACKUP_ACTION.equals(intent.getAction())) {
- if (DEBUG) Log.v(TAG, "Running a backup pass");
-
synchronized (mQueueLock) {
- // acquire a wakelock and pass it to the backup thread. it will
- // be released once backup concludes.
+ if (mPendingInits.size() > 0) {
+ // If there are pending init operations, we process those
+ // and then settle into the usual periodic backup schedule.
+ if (DEBUG) Log.v(TAG, "Init pending at scheduled backup");
+ try {
+ mAlarmManager.cancel(mRunInitIntent);
+ mRunInitIntent.send();
+ } catch (PendingIntent.CanceledException ce) {
+ Log.e(TAG, "Run init intent cancelled");
+ // can't really do more than bail here
+ }
+ } else {
+ // Don't run backups now if we're disabled, not yet
+ // fully set up, or racing with an initialize pass.
+ if (mEnabled && mProvisioned && !mInitInProgress) {
+ if (DEBUG) Log.v(TAG, "Running a backup pass");
+
+ // Acquire the wakelock and pass it to the backup thread. it will
+ // be released once backup concludes.
+ mWakelock.acquire();
+
+ Message msg = mBackupHandler.obtainMessage(MSG_RUN_BACKUP);
+ mBackupHandler.sendMessage(msg);
+ } else {
+ Log.w(TAG, "Backup pass but e=" + mEnabled + " p=" + mProvisioned
+ + " i=" + mInitInProgress);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private class RunInitializeReceiver extends BroadcastReceiver {
+ public void onReceive(Context context, Intent intent) {
+ if (RUN_INITIALIZE_ACTION.equals(intent.getAction())) {
+ synchronized (mQueueLock) {
+ if (DEBUG) Log.v(TAG, "Running a device init");
+ mInitInProgress = true;
+
+ // Acquire the wakelock and pass it to the init thread. it will
+ // be released once init concludes.
mWakelock.acquire();
- Message msg = mBackupHandler.obtainMessage(MSG_RUN_BACKUP);
+ Message msg = mBackupHandler.obtainMessage(MSG_RUN_INITIALIZE);
mBackupHandler.sendMessage(msg);
}
}
@@ -408,6 +461,37 @@ class BackupManagerService extends IBackupManager.Stub {
}
}
+ // Maintain persistent state around whether need to do an initialize operation.
+ // Must be called with the queue lock held.
+ void recordInitPendingLocked(boolean isPending, String transportName) {
+ if (DEBUG) Log.i(TAG, "recordInitPendingLocked: " + isPending
+ + " on transport " + transportName);
+ try {
+ IBackupTransport transport = getTransport(transportName);
+ String transportDirName = transport.transportDirName();
+ File stateDir = new File(mBaseStateDir, transportDirName);
+ File initPendingFile = new File(stateDir, INIT_SENTINEL_FILE_NAME);
+
+ if (isPending) {
+ // We need an init before we can proceed with sending backup data.
+ // Record that with an entry in our set of pending inits, as well as
+ // journaling it via creation of a sentinel file.
+ mPendingInits.add(transportName);
+ try {
+ (new FileOutputStream(initPendingFile)).close();
+ } catch (IOException ioe) {
+ // Something is badly wrong with our permissions; just try to move on
+ }
+ } else {
+ // No more initialization needed; wipe the journal and reset our state.
+ initPendingFile.delete();
+ mPendingInits.remove(transportName);
+ }
+ } catch (RemoteException e) {
+ // can't happen; the transport is local
+ }
+ }
+
// Reset all of our bookkeeping, in response to having been told that
// the backend data has been wiped [due to idle expiry, for example],
// so we must re-upload all saved settings.
@@ -430,7 +514,10 @@ class BackupManagerService extends IBackupManager.Stub {
// Remove all the state files
for (File sf : stateFileDir.listFiles()) {
- sf.delete();
+ // ... but don't touch the needs-init sentinel
+ if (!sf.getName().equals(INIT_SENTINEL_FILE_NAME)) {
+ sf.delete();
+ }
}
// Enqueue a new backup of every participant
@@ -455,6 +542,29 @@ class BackupManagerService extends IBackupManager.Stub {
if (DEBUG) Log.v(TAG, "Registering transport " + name + " = " + transport);
mTransports.put(name, transport);
}
+
+ // If the init sentinel file exists, we need to be sure to perform the init
+ // as soon as practical. We also create the state directory at registration
+ // time to ensure it's present from the outset.
+ try {
+ String transportName = transport.transportDirName();
+ File stateDir = new File(mBaseStateDir, transportName);
+ stateDir.mkdirs();
+
+ File initSentinel = new File(stateDir, INIT_SENTINEL_FILE_NAME);
+ if (initSentinel.exists()) {
+ synchronized (mQueueLock) {
+ mPendingInits.add(transportName);
+
+ // TODO: pick a better starting time than now + 1 minute
+ long delay = 1000 * 60; // one minute, in milliseconds
+ mAlarmManager.set(AlarmManager.RTC_WAKEUP,
+ System.currentTimeMillis() + delay, mRunInitIntent);
+ }
+ }
+ } catch (RemoteException e) {
+ // can't happen, the transport is local
+ }
}
// ----- Track installation/removal of packages -----
@@ -581,6 +691,20 @@ class BackupManagerService extends IBackupManager.Stub {
(new PerformClearThread(params.transport, params.packageInfo)).start();
break;
}
+
+ case MSG_RUN_INITIALIZE:
+ {
+ HashSet queue;
+
+ // Snapshot the pending-init queue and work on that
+ synchronized (mQueueLock) {
+ queue = new HashSet(mPendingInits);
+ mPendingInits.clear();
+ }
+
+ (new PerformInitializeThread(queue)).start();
+ break;
+ }
}
}
}
@@ -907,7 +1031,6 @@ class BackupManagerService extends IBackupManager.Stub {
} catch (RemoteException e) {
// can't happen; the transport is local
}
- mStateDir.mkdirs();
}
@Override
@@ -1206,7 +1329,6 @@ class BackupManagerService extends IBackupManager.Stub {
} catch (RemoteException e) {
// can't happen; the transport is local
}
- mStateDir.mkdirs();
}
@Override
@@ -1552,6 +1674,80 @@ class BackupManagerService extends IBackupManager.Stub {
}
}
+ class PerformInitializeThread extends Thread {
+ HashSet mQueue;
+
+ PerformInitializeThread(HashSet transportNames) {
+ mQueue = transportNames;
+ }
+
+ @Override
+ public void run() {
+ int status;
+ try {
+ for (String transportName : mQueue) {
+ IBackupTransport transport = getTransport(transportName);
+ if (transport == null) {
+ Log.e(TAG, "Requested init for " + transportName + " but not found");
+ continue;
+ }
+
+ status = BackupConstants.TRANSPORT_OK;
+ File stateDir = null;
+
+ Log.i(TAG, "Device init on " + transport.transportDirName());
+
+ stateDir = new File(mBaseStateDir, transport.transportDirName());
+
+ status = transport.initializeDevice();
+ if (status != BackupConstants.TRANSPORT_OK) {
+ Log.e(TAG, "Error from initializeDevice: " + status);
+ }
+ if (status == BackupConstants.TRANSPORT_OK) {
+ status = transport.finishBackup();
+ }
+
+ // Okay, the wipe really happened. Clean up our local bookkeeping.
+ if (status == BackupConstants.TRANSPORT_OK) {
+ resetBackupState(stateDir);
+ synchronized (mQueueLock) {
+ recordInitPendingLocked(false, transportName);
+ }
+ }
+
+ // If this didn't work, requeue this one and try again
+ // after a suitable interval
+ if (status != BackupConstants.TRANSPORT_OK) {
+ Log.i(TAG, "Device init failed");
+ synchronized (mQueueLock) {
+ recordInitPendingLocked(true, transportName);
+ }
+ // do this via another alarm to make sure of the wakelock states
+ long delay = transport.requestBackupTime();
+ if (DEBUG) Log.w(TAG, "init failed on "
+ + transportName + " resched in " + delay);
+ mAlarmManager.set(AlarmManager.RTC_WAKEUP,
+ System.currentTimeMillis() + delay, mRunInitIntent);
+ } else {
+ // success!
+ Log.i(TAG, "Device init successful");
+ }
+
+ }
+ } catch (RemoteException e) {
+ // can't happen; the transports are local
+ } catch (Exception e) {
+ Log.e(TAG, "Unexpected error performing init", e);
+ } finally {
+ // Done; indicate that we're finished and release the wakelock
+ synchronized (mQueueLock) {
+ mInitInProgress = false;
+ }
+ mWakelock.release();
+ }
+ }
+ }
+
// ----- IBackupManager binder interface -----
@@ -1694,6 +1890,8 @@ class BackupManagerService extends IBackupManager.Stub {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
"setBackupEnabled");
+ Log.i(TAG, "Backup enabled => " + enable);
+
boolean wasEnabled = mEnabled;
synchronized (this) {
Settings.Secure.putInt(mContext.getContentResolver(),
@@ -1707,7 +1905,27 @@ class BackupManagerService extends IBackupManager.Stub {
startBackupAlarmsLocked(BACKUP_INTERVAL);
} else if (!enable) {
// No longer enabled, so stop running backups
+ if (DEBUG) Log.i(TAG, "Opting out of backup");
+
mAlarmManager.cancel(mRunBackupIntent);
+
+ // This also constitutes an opt-out, so we wipe any data for
+ // this device from the backend. We start that process with
+ // an alarm in order to guarantee wakelock states.
+ if (wasEnabled && mProvisioned) {
+ // NOTE: we currently flush every registered transport, not just
+ // the currently-active one.
+ HashSet allTransports;
+ synchronized (mTransports) {
+ allTransports = new HashSet(mTransports.keySet());
+ }
+ // build the set of transports for which we are posting an init
+ for (String transport : allTransports) {
+ recordInitPendingLocked(true, transport);
+ }
+ mAlarmManager.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis(),
+ mRunInitIntent);
+ }
}
}
}