2009-04-29 14:03:25 -07:00
|
|
|
/*
|
|
|
|
* Copyright (C) 2009 The Android Open Source Project
|
|
|
|
*
|
|
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
* you may not use this file except in compliance with the License.
|
|
|
|
* You may obtain a copy of the License at
|
|
|
|
*
|
|
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
*
|
|
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
* See the License for the specific language governing permissions and
|
|
|
|
* limitations under the License.
|
|
|
|
*/
|
|
|
|
|
|
|
|
package com.android.server;
|
|
|
|
|
|
|
|
import android.backup.BackupService;
|
|
|
|
import android.backup.IBackupService;
|
2009-05-06 16:13:56 -07:00
|
|
|
import android.content.BroadcastReceiver;
|
2009-04-29 14:03:25 -07:00
|
|
|
import android.content.ComponentName;
|
|
|
|
import android.content.Context;
|
|
|
|
import android.content.Intent;
|
2009-05-06 16:13:56 -07:00
|
|
|
import android.content.IntentFilter;
|
2009-04-29 14:03:25 -07:00
|
|
|
import android.content.ServiceConnection;
|
|
|
|
import android.content.pm.PackageManager;
|
|
|
|
import android.content.pm.ResolveInfo;
|
|
|
|
import android.content.pm.ServiceInfo;
|
2009-05-06 16:13:56 -07:00
|
|
|
import android.net.Uri;
|
2009-04-29 14:03:25 -07:00
|
|
|
import android.os.Binder;
|
2009-05-06 16:13:56 -07:00
|
|
|
import android.os.Bundle;
|
2009-05-04 16:41:53 -07:00
|
|
|
import android.os.Environment;
|
2009-04-29 14:03:25 -07:00
|
|
|
import android.os.Handler;
|
|
|
|
import android.os.IBinder;
|
|
|
|
import android.os.Message;
|
2009-05-04 16:41:53 -07:00
|
|
|
import android.os.ParcelFileDescriptor;
|
2009-04-29 14:03:25 -07:00
|
|
|
import android.os.RemoteException;
|
|
|
|
import android.util.Log;
|
|
|
|
import android.util.SparseArray;
|
|
|
|
|
|
|
|
import android.backup.IBackupManager;
|
|
|
|
|
2009-05-04 16:41:53 -07:00
|
|
|
import java.io.File;
|
|
|
|
import java.io.FileNotFoundException;
|
2009-04-29 14:03:25 -07:00
|
|
|
import java.lang.String;
|
|
|
|
import java.util.HashSet;
|
|
|
|
import java.util.List;
|
|
|
|
|
|
|
|
class BackupManagerService extends IBackupManager.Stub {
|
|
|
|
private static final String TAG = "BackupManagerService";
|
|
|
|
private static final boolean DEBUG = true;
|
|
|
|
|
|
|
|
private static final long COLLECTION_INTERVAL = 3 * 60 * 1000;
|
|
|
|
|
|
|
|
private static final int MSG_RUN_BACKUP = 1;
|
|
|
|
|
|
|
|
private Context mContext;
|
|
|
|
private PackageManager mPackageManager;
|
|
|
|
private final BackupHandler mBackupHandler = new BackupHandler();
|
|
|
|
// map UIDs to the set of backup client services within that UID's app set
|
|
|
|
private SparseArray<HashSet<ServiceInfo>> mBackupParticipants
|
|
|
|
= new SparseArray<HashSet<ServiceInfo>>();
|
|
|
|
// set of backup services that have pending changes
|
2009-05-06 11:22:00 -07:00
|
|
|
private class BackupRequest {
|
|
|
|
public ServiceInfo service;
|
|
|
|
public boolean fullBackup;
|
|
|
|
|
|
|
|
BackupRequest(ServiceInfo svc, boolean isFull) {
|
|
|
|
service = svc;
|
|
|
|
fullBackup = isFull;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
private HashSet<BackupRequest> mPendingBackups = new HashSet<BackupRequest>();
|
2009-04-29 14:03:25 -07:00
|
|
|
private final Object mQueueLock = new Object();
|
|
|
|
|
2009-05-04 16:41:53 -07:00
|
|
|
private File mStateDir;
|
2009-05-05 15:50:03 -07:00
|
|
|
private File mDataDir;
|
2009-04-29 14:03:25 -07:00
|
|
|
|
|
|
|
// ----- Handler that runs the actual backup process asynchronously -----
|
|
|
|
|
|
|
|
private class BackupHandler extends Handler implements ServiceConnection {
|
|
|
|
private volatile Object mBindSignaller = new Object();
|
|
|
|
private volatile boolean mBinding = false;
|
|
|
|
private IBackupService mTargetService = null;
|
|
|
|
|
|
|
|
public void handleMessage(Message msg) {
|
|
|
|
|
|
|
|
switch (msg.what) {
|
|
|
|
case MSG_RUN_BACKUP:
|
|
|
|
{
|
|
|
|
// snapshot the pending-backup set and work on that
|
2009-05-06 11:22:00 -07:00
|
|
|
HashSet<BackupRequest> queue;
|
2009-04-29 14:03:25 -07:00
|
|
|
synchronized (mQueueLock) {
|
|
|
|
queue = mPendingBackups;
|
2009-05-06 11:22:00 -07:00
|
|
|
mPendingBackups = new HashSet<BackupRequest>();
|
2009-04-29 14:03:25 -07:00
|
|
|
// !!! TODO: start a new backup-queue journal file too
|
|
|
|
}
|
|
|
|
|
|
|
|
// Walk the set of pending backups, setting up the relevant files and
|
|
|
|
// invoking the backup service in each participant
|
|
|
|
Intent backupIntent = new Intent(BackupService.SERVICE_ACTION);
|
2009-05-06 11:22:00 -07:00
|
|
|
for (BackupRequest request : queue) {
|
2009-04-29 14:03:25 -07:00
|
|
|
mBinding = true;
|
|
|
|
mTargetService = null;
|
|
|
|
|
2009-05-06 11:22:00 -07:00
|
|
|
backupIntent.setClassName(request.service.packageName, request.service.name);
|
2009-04-29 14:03:25 -07:00
|
|
|
Log.d(TAG, "binding to " + backupIntent);
|
|
|
|
if (mContext.bindService(backupIntent, this, 0)) {
|
|
|
|
synchronized (mBindSignaller) {
|
|
|
|
while (mTargetService == null && mBinding == true) {
|
|
|
|
try {
|
|
|
|
mBindSignaller.wait();
|
|
|
|
} catch (InterruptedException e) {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (mTargetService != null) {
|
|
|
|
try {
|
|
|
|
Log.d(TAG, "invoking doBackup() on " + backupIntent);
|
2009-05-04 16:41:53 -07:00
|
|
|
|
2009-05-06 11:22:00 -07:00
|
|
|
// !!! TODO right now these naming schemes limit applications to
|
|
|
|
// one backup service per package
|
|
|
|
File savedStateName = new File(mStateDir,
|
|
|
|
request.service.packageName);
|
|
|
|
File backupDataName = new File(mDataDir,
|
|
|
|
request.service.packageName + ".data");
|
|
|
|
File newStateName = new File(mStateDir,
|
|
|
|
request.service.packageName + ".new");
|
2009-05-05 15:50:03 -07:00
|
|
|
|
2009-05-06 12:38:21 -07:00
|
|
|
// In a full backup, we pass a null ParcelFileDescriptor as
|
|
|
|
// the saved-state "file"
|
|
|
|
ParcelFileDescriptor savedState = (request.fullBackup) ? null
|
|
|
|
: ParcelFileDescriptor.open(savedStateName,
|
2009-05-06 11:22:00 -07:00
|
|
|
ParcelFileDescriptor.MODE_READ_ONLY |
|
|
|
|
ParcelFileDescriptor.MODE_CREATE);
|
|
|
|
|
2009-05-05 15:50:03 -07:00
|
|
|
backupDataName.delete();
|
2009-05-04 16:41:53 -07:00
|
|
|
ParcelFileDescriptor backupData =
|
|
|
|
ParcelFileDescriptor.open(backupDataName,
|
|
|
|
ParcelFileDescriptor.MODE_READ_WRITE |
|
|
|
|
ParcelFileDescriptor.MODE_CREATE);
|
2009-05-05 15:50:03 -07:00
|
|
|
|
|
|
|
newStateName.delete();
|
2009-05-04 16:41:53 -07:00
|
|
|
ParcelFileDescriptor newState =
|
|
|
|
ParcelFileDescriptor.open(newStateName,
|
|
|
|
ParcelFileDescriptor.MODE_READ_WRITE |
|
|
|
|
ParcelFileDescriptor.MODE_CREATE);
|
|
|
|
|
2009-05-05 15:50:03 -07:00
|
|
|
// Run the target's backup pass
|
|
|
|
try {
|
|
|
|
mTargetService.doBackup(savedState, backupData, newState);
|
|
|
|
} finally {
|
|
|
|
savedState.close();
|
|
|
|
backupData.close();
|
|
|
|
newState.close();
|
|
|
|
}
|
2009-05-04 16:41:53 -07:00
|
|
|
|
|
|
|
// !!! TODO: Now propagate the newly-backed-up data to the transport
|
|
|
|
|
2009-05-05 15:50:03 -07:00
|
|
|
// !!! 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);
|
2009-05-04 16:41:53 -07:00
|
|
|
|
|
|
|
} catch (FileNotFoundException fnf) {
|
|
|
|
Log.d(TAG, "File not found on backup: ");
|
|
|
|
fnf.printStackTrace();
|
2009-04-29 14:03:25 -07:00
|
|
|
} catch (RemoteException e) {
|
|
|
|
Log.d(TAG, "Remote target " + backupIntent
|
|
|
|
+ " threw during backup:");
|
|
|
|
e.printStackTrace();
|
2009-05-04 16:41:53 -07:00
|
|
|
} catch (Exception e) {
|
|
|
|
Log.w(TAG, "Final exception guard in backup: ");
|
|
|
|
e.printStackTrace();
|
2009-04-29 14:03:25 -07:00
|
|
|
}
|
|
|
|
mContext.unbindService(this);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
Log.d(TAG, "Unable to bind to " + backupIntent);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public void onServiceConnected(ComponentName name, IBinder service) {
|
|
|
|
synchronized (mBindSignaller) {
|
|
|
|
mTargetService = IBackupService.Stub.asInterface(service);
|
|
|
|
mBinding = false;
|
|
|
|
mBindSignaller.notifyAll();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public void onServiceDisconnected(ComponentName name) {
|
|
|
|
synchronized (mBindSignaller) {
|
|
|
|
mTargetService = null;
|
|
|
|
mBinding = false;
|
|
|
|
mBindSignaller.notifyAll();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public BackupManagerService(Context context) {
|
|
|
|
mContext = context;
|
|
|
|
mPackageManager = context.getPackageManager();
|
|
|
|
|
2009-05-04 16:41:53 -07:00
|
|
|
// Set up our bookkeeping
|
2009-05-05 15:50:03 -07:00
|
|
|
mStateDir = new File(Environment.getDataDirectory(), "backup");
|
2009-05-04 16:41:53 -07:00
|
|
|
mStateDir.mkdirs();
|
2009-05-05 15:50:03 -07:00
|
|
|
mDataDir = Environment.getDownloadCacheDirectory();
|
2009-05-04 16:41:53 -07:00
|
|
|
|
2009-05-06 16:13:56 -07:00
|
|
|
// Build our mapping of uid to backup client services
|
|
|
|
synchronized (mBackupParticipants) {
|
|
|
|
addPackageParticipantsLocked(null);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Register for broadcasts about package install, etc., so we can
|
|
|
|
// update the provider list.
|
|
|
|
IntentFilter filter = new IntentFilter();
|
|
|
|
filter.addAction(Intent.ACTION_PACKAGE_ADDED);
|
|
|
|
filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
|
|
|
|
filter.addDataScheme("package");
|
|
|
|
mContext.registerReceiver(mBroadcastReceiver, filter);
|
|
|
|
}
|
|
|
|
|
|
|
|
// ----- Track installation/removal of packages -----
|
|
|
|
BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
|
|
|
|
public void onReceive(Context context, Intent intent) {
|
|
|
|
if (DEBUG) Log.d(TAG, "Received broadcast " + intent);
|
|
|
|
|
|
|
|
Uri uri = intent.getData();
|
|
|
|
if (uri == null) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
String pkgName = uri.getSchemeSpecificPart();
|
|
|
|
if (pkgName == null) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
String action = intent.getAction();
|
|
|
|
if (Intent.ACTION_PACKAGE_ADDED.equals(action)) {
|
|
|
|
synchronized (mBackupParticipants) {
|
|
|
|
Bundle extras = intent.getExtras();
|
|
|
|
if (extras != null && extras.getBoolean(Intent.EXTRA_REPLACING, false)) {
|
|
|
|
// The package was just upgraded
|
|
|
|
updatePackageParticipantsLocked(pkgName);
|
|
|
|
} else {
|
|
|
|
// The package was just added
|
|
|
|
addPackageParticipantsLocked(pkgName);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) {
|
|
|
|
Bundle extras = intent.getExtras();
|
|
|
|
if (extras != null && extras.getBoolean(Intent.EXTRA_REPLACING, false)) {
|
|
|
|
// The package is being updated. We'll receive a PACKAGE_ADDED shortly.
|
|
|
|
} else {
|
|
|
|
synchronized (mBackupParticipants) {
|
|
|
|
removePackageParticipantsLocked(pkgName);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
// Add the backup services in the given package to our set of known backup participants.
|
|
|
|
// If 'packageName' is null, adds all backup services in the system.
|
|
|
|
void addPackageParticipantsLocked(String packageName) {
|
2009-04-29 14:03:25 -07:00
|
|
|
List<ResolveInfo> services = mPackageManager.queryIntentServices(
|
|
|
|
new Intent(BackupService.SERVICE_ACTION), 0);
|
2009-05-06 16:13:56 -07:00
|
|
|
addPackageParticipantsLockedInner(packageName, services);
|
|
|
|
}
|
|
|
|
|
|
|
|
private void addPackageParticipantsLockedInner(String packageName, List<ResolveInfo> services) {
|
|
|
|
for (ResolveInfo ri : services) {
|
|
|
|
if (packageName == null || ri.serviceInfo.packageName.equals(packageName)) {
|
|
|
|
int uid = ri.serviceInfo.applicationInfo.uid;
|
|
|
|
HashSet<ServiceInfo> set = mBackupParticipants.get(uid);
|
|
|
|
if (set == null) {
|
|
|
|
set = new HashSet<ServiceInfo>();
|
|
|
|
mBackupParticipants.put(uid, set);
|
|
|
|
}
|
|
|
|
if (DEBUG) {
|
|
|
|
Log.v(TAG, "Adding " + services.size() + " backup participants:");
|
|
|
|
for (ResolveInfo svc : services) {
|
|
|
|
Log.v(TAG, " " + svc + " : " + svc.filter);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
set.add(ri.serviceInfo);
|
2009-04-29 14:03:25 -07:00
|
|
|
}
|
|
|
|
}
|
2009-05-06 16:13:56 -07:00
|
|
|
}
|
2009-04-29 14:03:25 -07:00
|
|
|
|
2009-05-06 16:13:56 -07:00
|
|
|
// 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) {
|
|
|
|
List<ResolveInfo> services = mPackageManager.queryIntentServices(
|
|
|
|
new Intent(BackupService.SERVICE_ACTION), 0);
|
|
|
|
removePackageParticipantsLockedInner(packageName, services);
|
|
|
|
}
|
|
|
|
|
|
|
|
private void removePackageParticipantsLockedInner(String packageName, List<ResolveInfo> services) {
|
2009-04-29 14:03:25 -07:00
|
|
|
for (ResolveInfo ri : services) {
|
2009-05-06 16:13:56 -07:00
|
|
|
if (packageName == null || ri.serviceInfo.packageName.equals(packageName)) {
|
|
|
|
int uid = ri.serviceInfo.applicationInfo.uid;
|
|
|
|
HashSet<ServiceInfo> set = mBackupParticipants.get(uid);
|
|
|
|
if (set != null) {
|
|
|
|
set.remove(ri.serviceInfo);
|
|
|
|
if (set.size() == 0) {
|
|
|
|
mBackupParticipants.put(uid, null);
|
|
|
|
}
|
|
|
|
}
|
2009-04-29 14:03:25 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-05-06 16:13:56 -07:00
|
|
|
// Reset the given package's known backup participants. Unlike add/remove, the update
|
|
|
|
// action cannot be passed a null package name.
|
|
|
|
void updatePackageParticipantsLocked(String packageName) {
|
|
|
|
if (packageName == null) {
|
|
|
|
Log.e(TAG, "updatePackageParticipants called with null package name");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// brute force but small code size
|
|
|
|
List<ResolveInfo> services = mPackageManager.queryIntentServices(
|
|
|
|
new Intent(BackupService.SERVICE_ACTION), 0);
|
|
|
|
removePackageParticipantsLockedInner(packageName, services);
|
|
|
|
addPackageParticipantsLockedInner(packageName, services);
|
|
|
|
}
|
|
|
|
|
2009-04-29 14:03:25 -07:00
|
|
|
// ----- IBackupManager binder interface -----
|
|
|
|
|
2009-04-30 11:36:21 -07:00
|
|
|
public void dataChanged(String packageName) throws RemoteException {
|
2009-04-29 14:03:25 -07:00
|
|
|
// 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
|
|
|
|
// a backup pass for each of them.
|
|
|
|
|
|
|
|
HashSet<ServiceInfo> targets = mBackupParticipants.get(Binder.getCallingUid());
|
|
|
|
if (targets != null) {
|
|
|
|
synchronized (mQueueLock) {
|
|
|
|
// Note that this client has made data changes that need to be backed up
|
|
|
|
for (ServiceInfo service : targets) {
|
2009-04-30 11:36:21 -07:00
|
|
|
// validate the caller-supplied package name against the known set of
|
|
|
|
// packages associated with this uid
|
|
|
|
if (service.packageName.equals(packageName)) {
|
|
|
|
// add the caller to the set of pending backups
|
2009-05-06 11:22:00 -07:00
|
|
|
if (mPendingBackups.add(new BackupRequest(service, false))) {
|
2009-04-30 11:36:21 -07:00
|
|
|
// !!! TODO: write to the pending-backup journal file in case of crash
|
|
|
|
}
|
2009-04-29 14:03:25 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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.sendEmptyMessageDelayed(MSG_RUN_BACKUP, COLLECTION_INTERVAL);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2009-05-06 11:22:00 -07:00
|
|
|
|
|
|
|
// Schedule a backup pass for a given package, even if the caller is not part of
|
|
|
|
// that uid or package itself.
|
|
|
|
public void scheduleFullBackup(String packageName) throws RemoteException {
|
|
|
|
// !!! TODO: protect with a signature-or-system permission?
|
|
|
|
HashSet<ServiceInfo> targets = new HashSet<ServiceInfo>();
|
|
|
|
synchronized (mQueueLock) {
|
|
|
|
int numKeys = mBackupParticipants.size();
|
|
|
|
for (int index = 0; index < numKeys; index++) {
|
|
|
|
int uid = mBackupParticipants.keyAt(index);
|
|
|
|
HashSet<ServiceInfo> servicesAtUid = mBackupParticipants.get(uid);
|
|
|
|
for (ServiceInfo service: servicesAtUid) {
|
|
|
|
if (service.packageName.equals(packageName)) {
|
|
|
|
mPendingBackups.add(new BackupRequest(service, true));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2009-04-29 14:03:25 -07:00
|
|
|
}
|