blob: e3fff81f35466e0a95e0b1b06691b76dea88a1ff [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;
Joe Onorato8ad02812009-05-13 01:41:44 -040049import java.util.ArrayList;
50import java.util.HashMap;
Christopher Tate487529a2009-04-29 14:03:25 -070051import java.util.HashSet;
52import java.util.List;
53
54class BackupManagerService extends IBackupManager.Stub {
55 private static final String TAG = "BackupManagerService";
56 private static final boolean DEBUG = true;
57
Joe Onoratob1a7ffe2009-05-06 18:06:21 -070058 private static final long COLLECTION_INTERVAL = 1000;
59 //private static final long COLLECTION_INTERVAL = 3 * 60 * 1000;
Christopher Tate487529a2009-04-29 14:03:25 -070060
61 private static final int MSG_RUN_BACKUP = 1;
62
63 private Context mContext;
64 private PackageManager mPackageManager;
65 private final BackupHandler mBackupHandler = new BackupHandler();
66 // map UIDs to the set of backup client services within that UID's app set
67 private SparseArray<HashSet<ServiceInfo>> mBackupParticipants
68 = new SparseArray<HashSet<ServiceInfo>>();
69 // set of backup services that have pending changes
Christopher Tate46758122009-05-06 11:22:00 -070070 private class BackupRequest {
71 public ServiceInfo service;
72 public boolean fullBackup;
73
74 BackupRequest(ServiceInfo svc, boolean isFull) {
75 service = svc;
76 fullBackup = isFull;
77 }
78 }
Joe Onorato8ad02812009-05-13 01:41:44 -040079 // Backups that we haven't started yet.
80 private HashMap<ComponentName,BackupRequest> mPendingBackups = new HashMap();
81 // Backups that we have started. These are separate to prevent starvation
82 // if an app keeps re-enqueuing itself.
83 private ArrayList<BackupRequest> mBackupQueue;
Christopher Tate487529a2009-04-29 14:03:25 -070084 private final Object mQueueLock = new Object();
85
Christopher Tate22b87872009-05-04 16:41:53 -070086 private File mStateDir;
Christopher Tatef4172472009-05-05 15:50:03 -070087 private File mDataDir;
Christopher Tate487529a2009-04-29 14:03:25 -070088
Christopher Tate487529a2009-04-29 14:03:25 -070089 public BackupManagerService(Context context) {
90 mContext = context;
91 mPackageManager = context.getPackageManager();
92
Christopher Tate22b87872009-05-04 16:41:53 -070093 // Set up our bookkeeping
Christopher Tatef4172472009-05-05 15:50:03 -070094 mStateDir = new File(Environment.getDataDirectory(), "backup");
Christopher Tate22b87872009-05-04 16:41:53 -070095 mStateDir.mkdirs();
Christopher Tatef4172472009-05-05 15:50:03 -070096 mDataDir = Environment.getDownloadCacheDirectory();
Christopher Tate22b87872009-05-04 16:41:53 -070097
Christopher Tate3799bc22009-05-06 16:13:56 -070098 // Build our mapping of uid to backup client services
99 synchronized (mBackupParticipants) {
100 addPackageParticipantsLocked(null);
Christopher Tate487529a2009-04-29 14:03:25 -0700101 }
102
Christopher Tate3799bc22009-05-06 16:13:56 -0700103 // Register for broadcasts about package install, etc., so we can
104 // update the provider list.
105 IntentFilter filter = new IntentFilter();
106 filter.addAction(Intent.ACTION_PACKAGE_ADDED);
107 filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
108 filter.addDataScheme("package");
109 mContext.registerReceiver(mBroadcastReceiver, filter);
110 }
111
112 // ----- Track installation/removal of packages -----
113 BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
114 public void onReceive(Context context, Intent intent) {
115 if (DEBUG) Log.d(TAG, "Received broadcast " + intent);
116
117 Uri uri = intent.getData();
118 if (uri == null) {
119 return;
Christopher Tate487529a2009-04-29 14:03:25 -0700120 }
Christopher Tate3799bc22009-05-06 16:13:56 -0700121 String pkgName = uri.getSchemeSpecificPart();
122 if (pkgName == null) {
123 return;
124 }
125
126 String action = intent.getAction();
127 if (Intent.ACTION_PACKAGE_ADDED.equals(action)) {
128 synchronized (mBackupParticipants) {
129 Bundle extras = intent.getExtras();
130 if (extras != null && extras.getBoolean(Intent.EXTRA_REPLACING, false)) {
131 // The package was just upgraded
132 updatePackageParticipantsLocked(pkgName);
133 } else {
134 // The package was just added
135 addPackageParticipantsLocked(pkgName);
136 }
137 }
138 }
139 else if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) {
140 Bundle extras = intent.getExtras();
141 if (extras != null && extras.getBoolean(Intent.EXTRA_REPLACING, false)) {
142 // The package is being updated. We'll receive a PACKAGE_ADDED shortly.
143 } else {
144 synchronized (mBackupParticipants) {
145 removePackageParticipantsLocked(pkgName);
146 }
147 }
148 }
149 }
150 };
151
Joe Onorato8ad02812009-05-13 01:41:44 -0400152 // ----- Run the actual backup process asynchronously -----
153
154 private class BackupHandler extends Handler implements ServiceConnection {
155 public void handleMessage(Message msg) {
156
157 switch (msg.what) {
158 case MSG_RUN_BACKUP:
159 // snapshot the pending-backup set and work on that
160 synchronized (mQueueLock) {
Joe Onoratod2110db2009-05-19 13:41:21 -0700161 if (mBackupQueue == null) {
162 mBackupQueue = new ArrayList();
163 for (BackupRequest b: mPendingBackups.values()) {
164 mBackupQueue.add(b);
165 }
166 mPendingBackups = new HashMap<ComponentName,BackupRequest>();
Joe Onorato8ad02812009-05-13 01:41:44 -0400167 }
Joe Onorato8ad02812009-05-13 01:41:44 -0400168 // !!! TODO: start a new backup-queue journal file too
169 // WARNING: If we crash after this line, anything in mPendingBackups will
170 // be lost. FIX THIS.
171 }
172 startOneService();
173 break;
174 }
175 }
176
177 public void onServiceConnected(ComponentName name, IBinder service) {
178 Log.d(TAG, "onServiceConnected name=" + name + " service=" + service);
179 IBackupService bs = IBackupService.Stub.asInterface(service);
180 processOneBackup(name, bs);
181 }
182
183 public void onServiceDisconnected(ComponentName name) {
184 // TODO: handle backup being interrupted
185 }
186 }
187
188 void startOneService() {
189 // Loop until we find someone to start or the queue empties out.
190 Intent intent = new Intent(BackupService.SERVICE_ACTION);
191 while (true) {
192 BackupRequest request;
193 synchronized (mQueueLock) {
194 int queueSize = mBackupQueue.size();
Joe Onoratod2110db2009-05-19 13:41:21 -0700195 Log.d(TAG, "mBackupQueue.size=" + queueSize);
Joe Onorato8ad02812009-05-13 01:41:44 -0400196 if (queueSize == 0) {
197 mBackupQueue = null;
Joe Onoratod2110db2009-05-19 13:41:21 -0700198 // if there are pending backups, start those after a short delay
199 if (mPendingBackups.size() > 0) {
200 mBackupHandler.sendEmptyMessageDelayed(MSG_RUN_BACKUP, COLLECTION_INTERVAL);
201 }
Joe Onorato8ad02812009-05-13 01:41:44 -0400202 return;
203 }
204 request = mBackupQueue.get(0);
205 // Take it off the queue when we're done.
206 }
207
208 intent.setClassName(request.service.packageName, request.service.name);
209 Log.d(TAG, "binding to " + intent);
210 try {
211 if (mContext.bindService(intent, mBackupHandler, Context.BIND_AUTO_CREATE)) {
212 Log.d(TAG, "awaiting service object for " + intent);
213 // success
214 return;
215 }
216 } catch (SecurityException ex) {
217 // Try for the next one.
218 Log.d(TAG, "error in bind", ex);
219 }
220 }
221 }
222
223 void processOneBackup(ComponentName name, IBackupService bs) {
224 try {
225 Log.d(TAG, "processOneBackup doBackup() on " + name);
226
227 BackupRequest request;
228 synchronized (mQueueLock) {
Joe Onorato290bb012009-05-13 18:57:29 -0400229 if (mBackupQueue == null) {
230 Log.d(TAG, "mBackupQueue is null. WHY?");
231 }
Joe Onorato8ad02812009-05-13 01:41:44 -0400232 request = mBackupQueue.get(0);
233 }
234
235 // !!! TODO right now these naming schemes limit applications to
236 // one backup service per package
237 File savedStateName = new File(mStateDir, request.service.packageName);
238 File backupDataName = new File(mDataDir, request.service.packageName + ".data");
239 File newStateName = new File(mStateDir, request.service.packageName + ".new");
240
241 // In a full backup, we pass a null ParcelFileDescriptor as
242 // the saved-state "file"
243 ParcelFileDescriptor savedState = (request.fullBackup) ? null
244 : ParcelFileDescriptor.open(savedStateName,
245 ParcelFileDescriptor.MODE_READ_ONLY |
246 ParcelFileDescriptor.MODE_CREATE);
247
248 backupDataName.delete();
249 ParcelFileDescriptor backupData =
250 ParcelFileDescriptor.open(backupDataName,
251 ParcelFileDescriptor.MODE_READ_WRITE |
252 ParcelFileDescriptor.MODE_CREATE);
253
254 newStateName.delete();
255 ParcelFileDescriptor newState =
256 ParcelFileDescriptor.open(newStateName,
257 ParcelFileDescriptor.MODE_READ_WRITE |
258 ParcelFileDescriptor.MODE_CREATE);
259
260 // Run the target's backup pass
261 try {
262 // TODO: Make this oneway
263 bs.doBackup(savedState, backupData, newState);
264 } finally {
265 if (savedState != null) {
266 savedState.close();
267 }
268 backupData.close();
269 newState.close();
270 }
271
272 // !!! TODO: Now propagate the newly-backed-up data to the transport
273
274 // !!! TODO: After successful transport, delete the now-stale data
275 // and juggle the files so that next time the new state is passed
Joe Onoratod2110db2009-05-19 13:41:21 -0700276 //backupDataName.delete();
Joe Onorato8ad02812009-05-13 01:41:44 -0400277 newStateName.renameTo(savedStateName);
278
279 } catch (FileNotFoundException fnf) {
280 Log.d(TAG, "File not found on backup: ");
281 fnf.printStackTrace();
282 } catch (RemoteException e) {
283 Log.d(TAG, "Remote target " + name + " threw during backup:");
284 e.printStackTrace();
285 } catch (Exception e) {
286 Log.w(TAG, "Final exception guard in backup: ");
287 e.printStackTrace();
288 }
289 synchronized (mQueueLock) {
290 mBackupQueue.remove(0);
291 }
292 mContext.unbindService(mBackupHandler);
Joe Onoratod2110db2009-05-19 13:41:21 -0700293
294 // start the next one
295 startOneService();
Joe Onorato8ad02812009-05-13 01:41:44 -0400296 }
297
Christopher Tate3799bc22009-05-06 16:13:56 -0700298 // Add the backup services in the given package to our set of known backup participants.
299 // If 'packageName' is null, adds all backup services in the system.
300 void addPackageParticipantsLocked(String packageName) {
301 List<ResolveInfo> services = mPackageManager.queryIntentServices(
302 new Intent(BackupService.SERVICE_ACTION), 0);
303 addPackageParticipantsLockedInner(packageName, services);
304 }
305
306 private void addPackageParticipantsLockedInner(String packageName, List<ResolveInfo> services) {
307 for (ResolveInfo ri : services) {
308 if (packageName == null || ri.serviceInfo.packageName.equals(packageName)) {
309 int uid = ri.serviceInfo.applicationInfo.uid;
310 HashSet<ServiceInfo> set = mBackupParticipants.get(uid);
311 if (set == null) {
312 set = new HashSet<ServiceInfo>();
313 mBackupParticipants.put(uid, set);
314 }
315 if (DEBUG) {
316 Log.v(TAG, "Adding " + services.size() + " backup participants:");
317 for (ResolveInfo svc : services) {
318 Log.v(TAG, " " + svc + " : " + svc.filter);
319 }
320 }
321
322 set.add(ri.serviceInfo);
323 }
Christopher Tate487529a2009-04-29 14:03:25 -0700324 }
325 }
326
Christopher Tate3799bc22009-05-06 16:13:56 -0700327 // Remove the given package's backup services from our known active set. If
328 // 'packageName' is null, *all* backup services will be removed.
329 void removePackageParticipantsLocked(String packageName) {
330 List<ResolveInfo> services = mPackageManager.queryIntentServices(
331 new Intent(BackupService.SERVICE_ACTION), 0);
332 removePackageParticipantsLockedInner(packageName, services);
333 }
334
Joe Onorato8ad02812009-05-13 01:41:44 -0400335 private void removePackageParticipantsLockedInner(String packageName,
336 List<ResolveInfo> services) {
Christopher Tate3799bc22009-05-06 16:13:56 -0700337 for (ResolveInfo ri : services) {
338 if (packageName == null || ri.serviceInfo.packageName.equals(packageName)) {
339 int uid = ri.serviceInfo.applicationInfo.uid;
340 HashSet<ServiceInfo> set = mBackupParticipants.get(uid);
341 if (set != null) {
342 set.remove(ri.serviceInfo);
343 if (set.size() == 0) {
344 mBackupParticipants.put(uid, null);
345 }
346 }
347 }
348 }
349 }
350
351 // Reset the given package's known backup participants. Unlike add/remove, the update
352 // action cannot be passed a null package name.
353 void updatePackageParticipantsLocked(String packageName) {
354 if (packageName == null) {
355 Log.e(TAG, "updatePackageParticipants called with null package name");
356 return;
357 }
358
359 // brute force but small code size
360 List<ResolveInfo> services = mPackageManager.queryIntentServices(
361 new Intent(BackupService.SERVICE_ACTION), 0);
362 removePackageParticipantsLockedInner(packageName, services);
363 addPackageParticipantsLockedInner(packageName, services);
364 }
365
Christopher Tate487529a2009-04-29 14:03:25 -0700366 // ----- IBackupManager binder interface -----
367
Christopher Tatea8bf8152009-04-30 11:36:21 -0700368 public void dataChanged(String packageName) throws RemoteException {
Christopher Tate487529a2009-04-29 14:03:25 -0700369 // Record that we need a backup pass for the caller. Since multiple callers
370 // may share a uid, we need to note all candidates within that uid and schedule
371 // a backup pass for each of them.
Joe Onoratob1a7ffe2009-05-06 18:06:21 -0700372
373 Log.d(TAG, "dataChanged packageName=" + packageName);
Christopher Tate487529a2009-04-29 14:03:25 -0700374
375 HashSet<ServiceInfo> targets = mBackupParticipants.get(Binder.getCallingUid());
Joe Onoratob1a7ffe2009-05-06 18:06:21 -0700376 Log.d(TAG, "targets=" + targets);
Christopher Tate487529a2009-04-29 14:03:25 -0700377 if (targets != null) {
378 synchronized (mQueueLock) {
379 // Note that this client has made data changes that need to be backed up
Christopher Tate487529a2009-04-29 14:03:25 -0700380 for (ServiceInfo service : targets) {
Christopher Tatea8bf8152009-04-30 11:36:21 -0700381 // validate the caller-supplied package name against the known set of
382 // packages associated with this uid
383 if (service.packageName.equals(packageName)) {
Joe Onorato8ad02812009-05-13 01:41:44 -0400384 // Add the caller to the set of pending backups. If there is
385 // one already there, then overwrite it, but no harm done.
386 mPendingBackups.put(new ComponentName(service.packageName, service.name),
387 new BackupRequest(service, true));
388 // !!! TODO: write to the pending-backup journal file in case of crash
Christopher Tate487529a2009-04-29 14:03:25 -0700389 }
390 }
391
Joe Onoratob1a7ffe2009-05-06 18:06:21 -0700392 Log.d(TAG, "Scheduling backup for " + mPendingBackups.size() + " participants");
Christopher Tate487529a2009-04-29 14:03:25 -0700393 // Schedule a backup pass in a few minutes. As backup-eligible data
394 // keeps changing, continue to defer the backup pass until things
395 // settle down, to avoid extra overhead.
Christopher Tate487529a2009-04-29 14:03:25 -0700396 mBackupHandler.sendEmptyMessageDelayed(MSG_RUN_BACKUP, COLLECTION_INTERVAL);
397 }
398 }
399 }
Christopher Tate46758122009-05-06 11:22:00 -0700400
401 // Schedule a backup pass for a given package, even if the caller is not part of
402 // that uid or package itself.
403 public void scheduleFullBackup(String packageName) throws RemoteException {
404 // !!! TODO: protect with a signature-or-system permission?
405 HashSet<ServiceInfo> targets = new HashSet<ServiceInfo>();
406 synchronized (mQueueLock) {
407 int numKeys = mBackupParticipants.size();
408 for (int index = 0; index < numKeys; index++) {
409 int uid = mBackupParticipants.keyAt(index);
410 HashSet<ServiceInfo> servicesAtUid = mBackupParticipants.get(uid);
411 for (ServiceInfo service: servicesAtUid) {
412 if (service.packageName.equals(packageName)) {
Joe Onorato8ad02812009-05-13 01:41:44 -0400413 mPendingBackups.put(new ComponentName(service.packageName, service.name),
414 new BackupRequest(service, true));
Christopher Tate46758122009-05-06 11:22:00 -0700415 }
416 }
417 }
418 }
419 }
Joe Onoratob1a7ffe2009-05-06 18:06:21 -0700420
421
422 @Override
423 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
424 synchronized (mQueueLock) {
425 int N = mBackupParticipants.size();
426 pw.println("Participants:");
427 for (int i=0; i<N; i++) {
428 int uid = mBackupParticipants.keyAt(i);
429 pw.print(" uid: ");
430 pw.println(uid);
431 HashSet<ServiceInfo> services = mBackupParticipants.valueAt(i);
432 for (ServiceInfo s: services) {
433 pw.print(" ");
434 pw.println(s.toString());
435 }
436 }
437 }
438 }
Christopher Tate487529a2009-04-29 14:03:25 -0700439}