blob: e582fb15c603c2900345c30222e7fdc9abaa59bc [file] [log] [blame]
Christopher Tate487529a2009-04-29 14:03:25 -07001/*
2 * Copyright (C) 2009 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.server;
18
19import android.backup.BackupService;
20import android.backup.IBackupService;
Christopher Tate3799bc22009-05-06 16:13:56 -070021import android.content.BroadcastReceiver;
Christopher Tate487529a2009-04-29 14:03:25 -070022import android.content.ComponentName;
23import android.content.Context;
24import android.content.Intent;
Christopher Tate3799bc22009-05-06 16:13:56 -070025import android.content.IntentFilter;
Christopher Tate487529a2009-04-29 14:03:25 -070026import android.content.ServiceConnection;
27import android.content.pm.PackageManager;
28import android.content.pm.ResolveInfo;
29import android.content.pm.ServiceInfo;
Christopher Tate3799bc22009-05-06 16:13:56 -070030import android.net.Uri;
Christopher Tate487529a2009-04-29 14:03:25 -070031import android.os.Binder;
Christopher Tate3799bc22009-05-06 16:13:56 -070032import android.os.Bundle;
Christopher Tate22b87872009-05-04 16:41:53 -070033import android.os.Environment;
Christopher Tate487529a2009-04-29 14:03:25 -070034import android.os.Handler;
35import android.os.IBinder;
36import android.os.Message;
Christopher Tate22b87872009-05-04 16:41:53 -070037import android.os.ParcelFileDescriptor;
Christopher Tate487529a2009-04-29 14:03:25 -070038import android.os.RemoteException;
39import android.util.Log;
40import android.util.SparseArray;
41
42import android.backup.IBackupManager;
43
Christopher Tate22b87872009-05-04 16:41:53 -070044import java.io.File;
Joe Onoratob1a7ffe2009-05-06 18:06:21 -070045import java.io.FileDescriptor;
Christopher Tate22b87872009-05-04 16:41:53 -070046import java.io.FileNotFoundException;
Joe Onoratob1a7ffe2009-05-06 18:06:21 -070047import java.io.PrintWriter;
Christopher Tate487529a2009-04-29 14:03:25 -070048import java.lang.String;
49import java.util.HashSet;
50import java.util.List;
51
52class BackupManagerService extends IBackupManager.Stub {
53 private static final String TAG = "BackupManagerService";
54 private static final boolean DEBUG = true;
55
Joe Onoratob1a7ffe2009-05-06 18:06:21 -070056 private static final long COLLECTION_INTERVAL = 1000;
57 //private static final long COLLECTION_INTERVAL = 3 * 60 * 1000;
Christopher Tate487529a2009-04-29 14:03:25 -070058
59 private static final int MSG_RUN_BACKUP = 1;
60
61 private Context mContext;
62 private PackageManager mPackageManager;
63 private final BackupHandler mBackupHandler = new BackupHandler();
64 // map UIDs to the set of backup client services within that UID's app set
65 private SparseArray<HashSet<ServiceInfo>> mBackupParticipants
66 = new SparseArray<HashSet<ServiceInfo>>();
67 // set of backup services that have pending changes
Christopher Tate46758122009-05-06 11:22:00 -070068 private class BackupRequest {
69 public ServiceInfo service;
70 public boolean fullBackup;
71
72 BackupRequest(ServiceInfo svc, boolean isFull) {
73 service = svc;
74 fullBackup = isFull;
75 }
76 }
77 private HashSet<BackupRequest> mPendingBackups = new HashSet<BackupRequest>();
Christopher Tate487529a2009-04-29 14:03:25 -070078 private final Object mQueueLock = new Object();
79
Christopher Tate22b87872009-05-04 16:41:53 -070080 private File mStateDir;
Christopher Tatef4172472009-05-05 15:50:03 -070081 private File mDataDir;
Christopher Tate487529a2009-04-29 14:03:25 -070082
83 // ----- Handler that runs the actual backup process asynchronously -----
84
85 private class BackupHandler extends Handler implements ServiceConnection {
86 private volatile Object mBindSignaller = new Object();
87 private volatile boolean mBinding = false;
88 private IBackupService mTargetService = null;
89
90 public void handleMessage(Message msg) {
91
92 switch (msg.what) {
93 case MSG_RUN_BACKUP:
94 {
95 // snapshot the pending-backup set and work on that
Christopher Tate46758122009-05-06 11:22:00 -070096 HashSet<BackupRequest> queue;
Christopher Tate487529a2009-04-29 14:03:25 -070097 synchronized (mQueueLock) {
98 queue = mPendingBackups;
Christopher Tate46758122009-05-06 11:22:00 -070099 mPendingBackups = new HashSet<BackupRequest>();
Christopher Tate487529a2009-04-29 14:03:25 -0700100 // !!! TODO: start a new backup-queue journal file too
101 }
102
103 // Walk the set of pending backups, setting up the relevant files and
104 // invoking the backup service in each participant
105 Intent backupIntent = new Intent(BackupService.SERVICE_ACTION);
Christopher Tate46758122009-05-06 11:22:00 -0700106 for (BackupRequest request : queue) {
Christopher Tate487529a2009-04-29 14:03:25 -0700107 mBinding = true;
108 mTargetService = null;
109
Christopher Tate46758122009-05-06 11:22:00 -0700110 backupIntent.setClassName(request.service.packageName, request.service.name);
Christopher Tate487529a2009-04-29 14:03:25 -0700111 Log.d(TAG, "binding to " + backupIntent);
112 if (mContext.bindService(backupIntent, this, 0)) {
113 synchronized (mBindSignaller) {
114 while (mTargetService == null && mBinding == true) {
115 try {
116 mBindSignaller.wait();
117 } catch (InterruptedException e) {
118 }
119 }
120 }
121 if (mTargetService != null) {
122 try {
123 Log.d(TAG, "invoking doBackup() on " + backupIntent);
Christopher Tate22b87872009-05-04 16:41:53 -0700124
Christopher Tate46758122009-05-06 11:22:00 -0700125 // !!! TODO right now these naming schemes limit applications to
126 // one backup service per package
127 File savedStateName = new File(mStateDir,
128 request.service.packageName);
Christopher Tate46758122009-05-06 11:22:00 -0700129 File backupDataName = new File(mDataDir,
130 request.service.packageName + ".data");
131 File newStateName = new File(mStateDir,
132 request.service.packageName + ".new");
Christopher Tate22b87872009-05-04 16:41:53 -0700133
Christopher Tateb1d790b2009-05-06 12:38:21 -0700134 // In a full backup, we pass a null ParcelFileDescriptor as
135 // the saved-state "file"
136 ParcelFileDescriptor savedState = (request.fullBackup) ? null
137 : ParcelFileDescriptor.open(savedStateName,
Christopher Tate46758122009-05-06 11:22:00 -0700138 ParcelFileDescriptor.MODE_READ_ONLY |
139 ParcelFileDescriptor.MODE_CREATE);
140
Christopher Tatef4172472009-05-05 15:50:03 -0700141 backupDataName.delete();
Christopher Tate22b87872009-05-04 16:41:53 -0700142 ParcelFileDescriptor backupData =
143 ParcelFileDescriptor.open(backupDataName,
144 ParcelFileDescriptor.MODE_READ_WRITE |
145 ParcelFileDescriptor.MODE_CREATE);
Christopher Tatef4172472009-05-05 15:50:03 -0700146
147 newStateName.delete();
Christopher Tate22b87872009-05-04 16:41:53 -0700148 ParcelFileDescriptor newState =
149 ParcelFileDescriptor.open(newStateName,
150 ParcelFileDescriptor.MODE_READ_WRITE |
151 ParcelFileDescriptor.MODE_CREATE);
152
Christopher Tatef4172472009-05-05 15:50:03 -0700153 // Run the target's backup pass
154 try {
155 mTargetService.doBackup(savedState, backupData, newState);
156 } finally {
157 savedState.close();
158 backupData.close();
159 newState.close();
160 }
Christopher Tate22b87872009-05-04 16:41:53 -0700161
162 // !!! TODO: Now propagate the newly-backed-up data to the transport
163
Christopher Tatef4172472009-05-05 15:50:03 -0700164 // !!! TODO: After successful transport, delete the now-stale data
165 // and juggle the files so that next time the new state is passed
166 backupDataName.delete();
167 newStateName.renameTo(savedStateName);
Christopher Tate22b87872009-05-04 16:41:53 -0700168
169 } catch (FileNotFoundException fnf) {
170 Log.d(TAG, "File not found on backup: ");
171 fnf.printStackTrace();
Christopher Tate487529a2009-04-29 14:03:25 -0700172 } catch (RemoteException e) {
173 Log.d(TAG, "Remote target " + backupIntent
174 + " threw during backup:");
175 e.printStackTrace();
Christopher Tate22b87872009-05-04 16:41:53 -0700176 } catch (Exception e) {
177 Log.w(TAG, "Final exception guard in backup: ");
178 e.printStackTrace();
Christopher Tate487529a2009-04-29 14:03:25 -0700179 }
180 mContext.unbindService(this);
181 }
182 } else {
183 Log.d(TAG, "Unable to bind to " + backupIntent);
184 }
185 }
186 }
187 break;
188 }
189 }
190
191 public void onServiceConnected(ComponentName name, IBinder service) {
192 synchronized (mBindSignaller) {
193 mTargetService = IBackupService.Stub.asInterface(service);
194 mBinding = false;
195 mBindSignaller.notifyAll();
196 }
197 }
198
199 public void onServiceDisconnected(ComponentName name) {
200 synchronized (mBindSignaller) {
201 mTargetService = null;
202 mBinding = false;
203 mBindSignaller.notifyAll();
204 }
205 }
206 }
207
208 public BackupManagerService(Context context) {
209 mContext = context;
210 mPackageManager = context.getPackageManager();
211
Christopher Tate22b87872009-05-04 16:41:53 -0700212 // Set up our bookkeeping
Christopher Tatef4172472009-05-05 15:50:03 -0700213 mStateDir = new File(Environment.getDataDirectory(), "backup");
Christopher Tate22b87872009-05-04 16:41:53 -0700214 mStateDir.mkdirs();
Christopher Tatef4172472009-05-05 15:50:03 -0700215 mDataDir = Environment.getDownloadCacheDirectory();
Christopher Tate22b87872009-05-04 16:41:53 -0700216
Christopher Tate3799bc22009-05-06 16:13:56 -0700217 // Build our mapping of uid to backup client services
218 synchronized (mBackupParticipants) {
219 addPackageParticipantsLocked(null);
Christopher Tate487529a2009-04-29 14:03:25 -0700220 }
221
Christopher Tate3799bc22009-05-06 16:13:56 -0700222 // Register for broadcasts about package install, etc., so we can
223 // update the provider list.
224 IntentFilter filter = new IntentFilter();
225 filter.addAction(Intent.ACTION_PACKAGE_ADDED);
226 filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
227 filter.addDataScheme("package");
228 mContext.registerReceiver(mBroadcastReceiver, filter);
229 }
230
231 // ----- Track installation/removal of packages -----
232 BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
233 public void onReceive(Context context, Intent intent) {
234 if (DEBUG) Log.d(TAG, "Received broadcast " + intent);
235
236 Uri uri = intent.getData();
237 if (uri == null) {
238 return;
Christopher Tate487529a2009-04-29 14:03:25 -0700239 }
Christopher Tate3799bc22009-05-06 16:13:56 -0700240 String pkgName = uri.getSchemeSpecificPart();
241 if (pkgName == null) {
242 return;
243 }
244
245 String action = intent.getAction();
246 if (Intent.ACTION_PACKAGE_ADDED.equals(action)) {
247 synchronized (mBackupParticipants) {
248 Bundle extras = intent.getExtras();
249 if (extras != null && extras.getBoolean(Intent.EXTRA_REPLACING, false)) {
250 // The package was just upgraded
251 updatePackageParticipantsLocked(pkgName);
252 } else {
253 // The package was just added
254 addPackageParticipantsLocked(pkgName);
255 }
256 }
257 }
258 else if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) {
259 Bundle extras = intent.getExtras();
260 if (extras != null && extras.getBoolean(Intent.EXTRA_REPLACING, false)) {
261 // The package is being updated. We'll receive a PACKAGE_ADDED shortly.
262 } else {
263 synchronized (mBackupParticipants) {
264 removePackageParticipantsLocked(pkgName);
265 }
266 }
267 }
268 }
269 };
270
271 // Add the backup services in the given package to our set of known backup participants.
272 // If 'packageName' is null, adds all backup services in the system.
273 void addPackageParticipantsLocked(String packageName) {
274 List<ResolveInfo> services = mPackageManager.queryIntentServices(
275 new Intent(BackupService.SERVICE_ACTION), 0);
276 addPackageParticipantsLockedInner(packageName, services);
277 }
278
279 private void addPackageParticipantsLockedInner(String packageName, List<ResolveInfo> services) {
280 for (ResolveInfo ri : services) {
281 if (packageName == null || ri.serviceInfo.packageName.equals(packageName)) {
282 int uid = ri.serviceInfo.applicationInfo.uid;
283 HashSet<ServiceInfo> set = mBackupParticipants.get(uid);
284 if (set == null) {
285 set = new HashSet<ServiceInfo>();
286 mBackupParticipants.put(uid, set);
287 }
288 if (DEBUG) {
289 Log.v(TAG, "Adding " + services.size() + " backup participants:");
290 for (ResolveInfo svc : services) {
291 Log.v(TAG, " " + svc + " : " + svc.filter);
292 }
293 }
294
295 set.add(ri.serviceInfo);
296 }
Christopher Tate487529a2009-04-29 14:03:25 -0700297 }
298 }
299
Christopher Tate3799bc22009-05-06 16:13:56 -0700300 // Remove the given package's backup services from our known active set. If
301 // 'packageName' is null, *all* backup services will be removed.
302 void removePackageParticipantsLocked(String packageName) {
303 List<ResolveInfo> services = mPackageManager.queryIntentServices(
304 new Intent(BackupService.SERVICE_ACTION), 0);
305 removePackageParticipantsLockedInner(packageName, services);
306 }
307
308 private void removePackageParticipantsLockedInner(String packageName, List<ResolveInfo> services) {
309 for (ResolveInfo ri : services) {
310 if (packageName == null || ri.serviceInfo.packageName.equals(packageName)) {
311 int uid = ri.serviceInfo.applicationInfo.uid;
312 HashSet<ServiceInfo> set = mBackupParticipants.get(uid);
313 if (set != null) {
314 set.remove(ri.serviceInfo);
315 if (set.size() == 0) {
316 mBackupParticipants.put(uid, null);
317 }
318 }
319 }
320 }
321 }
322
323 // Reset the given package's known backup participants. Unlike add/remove, the update
324 // action cannot be passed a null package name.
325 void updatePackageParticipantsLocked(String packageName) {
326 if (packageName == null) {
327 Log.e(TAG, "updatePackageParticipants called with null package name");
328 return;
329 }
330
331 // brute force but small code size
332 List<ResolveInfo> services = mPackageManager.queryIntentServices(
333 new Intent(BackupService.SERVICE_ACTION), 0);
334 removePackageParticipantsLockedInner(packageName, services);
335 addPackageParticipantsLockedInner(packageName, services);
336 }
337
Christopher Tate487529a2009-04-29 14:03:25 -0700338 // ----- IBackupManager binder interface -----
339
Christopher Tatea8bf8152009-04-30 11:36:21 -0700340 public void dataChanged(String packageName) throws RemoteException {
Christopher Tate487529a2009-04-29 14:03:25 -0700341 // Record that we need a backup pass for the caller. Since multiple callers
342 // may share a uid, we need to note all candidates within that uid and schedule
343 // a backup pass for each of them.
Joe Onoratob1a7ffe2009-05-06 18:06:21 -0700344
345 Log.d(TAG, "dataChanged packageName=" + packageName);
Christopher Tate487529a2009-04-29 14:03:25 -0700346
347 HashSet<ServiceInfo> targets = mBackupParticipants.get(Binder.getCallingUid());
Joe Onoratob1a7ffe2009-05-06 18:06:21 -0700348 Log.d(TAG, "targets=" + targets);
Christopher Tate487529a2009-04-29 14:03:25 -0700349 if (targets != null) {
350 synchronized (mQueueLock) {
351 // Note that this client has made data changes that need to be backed up
Christopher Tate487529a2009-04-29 14:03:25 -0700352 for (ServiceInfo service : targets) {
Christopher Tatea8bf8152009-04-30 11:36:21 -0700353 // validate the caller-supplied package name against the known set of
354 // packages associated with this uid
355 if (service.packageName.equals(packageName)) {
356 // add the caller to the set of pending backups
Christopher Tate46758122009-05-06 11:22:00 -0700357 if (mPendingBackups.add(new BackupRequest(service, false))) {
Christopher Tatea8bf8152009-04-30 11:36:21 -0700358 // !!! TODO: write to the pending-backup journal file in case of crash
359 }
Christopher Tate487529a2009-04-29 14:03:25 -0700360 }
361 }
362
Joe Onoratob1a7ffe2009-05-06 18:06:21 -0700363 Log.d(TAG, "Scheduling backup for " + mPendingBackups.size() + " participants");
Christopher Tate487529a2009-04-29 14:03:25 -0700364 // Schedule a backup pass in a few minutes. As backup-eligible data
365 // keeps changing, continue to defer the backup pass until things
366 // settle down, to avoid extra overhead.
Christopher Tate487529a2009-04-29 14:03:25 -0700367 mBackupHandler.sendEmptyMessageDelayed(MSG_RUN_BACKUP, COLLECTION_INTERVAL);
368 }
369 }
370 }
Christopher Tate46758122009-05-06 11:22:00 -0700371
372 // Schedule a backup pass for a given package, even if the caller is not part of
373 // that uid or package itself.
374 public void scheduleFullBackup(String packageName) throws RemoteException {
375 // !!! TODO: protect with a signature-or-system permission?
376 HashSet<ServiceInfo> targets = new HashSet<ServiceInfo>();
377 synchronized (mQueueLock) {
378 int numKeys = mBackupParticipants.size();
379 for (int index = 0; index < numKeys; index++) {
380 int uid = mBackupParticipants.keyAt(index);
381 HashSet<ServiceInfo> servicesAtUid = mBackupParticipants.get(uid);
382 for (ServiceInfo service: servicesAtUid) {
383 if (service.packageName.equals(packageName)) {
384 mPendingBackups.add(new BackupRequest(service, true));
385 }
386 }
387 }
388 }
389 }
Joe Onoratob1a7ffe2009-05-06 18:06:21 -0700390
391
392 @Override
393 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
394 synchronized (mQueueLock) {
395 int N = mBackupParticipants.size();
396 pw.println("Participants:");
397 for (int i=0; i<N; i++) {
398 int uid = mBackupParticipants.keyAt(i);
399 pw.print(" uid: ");
400 pw.println(uid);
401 HashSet<ServiceInfo> services = mBackupParticipants.valueAt(i);
402 for (ServiceInfo s: services) {
403 pw.print(" ");
404 pw.println(s.toString());
405 }
406 }
407 }
408 }
Christopher Tate487529a2009-04-29 14:03:25 -0700409}