blob: db1deaece0ec4e768e19f8aee47cc4511ae2446a [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) {
161 mBackupQueue = new ArrayList();
162 for (BackupRequest b: mPendingBackups.values()) {
163 mBackupQueue.add(b);
164 }
165 mPendingBackups = new HashMap<ComponentName,BackupRequest>();
166 // !!! TODO: start a new backup-queue journal file too
167 // WARNING: If we crash after this line, anything in mPendingBackups will
168 // be lost. FIX THIS.
169 }
170 startOneService();
171 break;
172 }
173 }
174
175 public void onServiceConnected(ComponentName name, IBinder service) {
176 Log.d(TAG, "onServiceConnected name=" + name + " service=" + service);
177 IBackupService bs = IBackupService.Stub.asInterface(service);
178 processOneBackup(name, bs);
179 }
180
181 public void onServiceDisconnected(ComponentName name) {
182 // TODO: handle backup being interrupted
183 }
184 }
185
186 void startOneService() {
187 // Loop until we find someone to start or the queue empties out.
188 Intent intent = new Intent(BackupService.SERVICE_ACTION);
189 while (true) {
190 BackupRequest request;
191 synchronized (mQueueLock) {
192 int queueSize = mBackupQueue.size();
193 if (queueSize == 0) {
194 mBackupQueue = null;
195 // TODO: Anything else to do here?
196 return;
197 }
198 request = mBackupQueue.get(0);
199 // Take it off the queue when we're done.
200 }
201
202 intent.setClassName(request.service.packageName, request.service.name);
203 Log.d(TAG, "binding to " + intent);
204 try {
205 if (mContext.bindService(intent, mBackupHandler, Context.BIND_AUTO_CREATE)) {
206 Log.d(TAG, "awaiting service object for " + intent);
207 // success
208 return;
209 }
210 } catch (SecurityException ex) {
211 // Try for the next one.
212 Log.d(TAG, "error in bind", ex);
213 }
214 }
215 }
216
217 void processOneBackup(ComponentName name, IBackupService bs) {
218 try {
219 Log.d(TAG, "processOneBackup doBackup() on " + name);
220
221 BackupRequest request;
222 synchronized (mQueueLock) {
223 request = mBackupQueue.get(0);
224 }
225
226 // !!! TODO right now these naming schemes limit applications to
227 // one backup service per package
228 File savedStateName = new File(mStateDir, request.service.packageName);
229 File backupDataName = new File(mDataDir, request.service.packageName + ".data");
230 File newStateName = new File(mStateDir, request.service.packageName + ".new");
231
232 // In a full backup, we pass a null ParcelFileDescriptor as
233 // the saved-state "file"
234 ParcelFileDescriptor savedState = (request.fullBackup) ? null
235 : ParcelFileDescriptor.open(savedStateName,
236 ParcelFileDescriptor.MODE_READ_ONLY |
237 ParcelFileDescriptor.MODE_CREATE);
238
239 backupDataName.delete();
240 ParcelFileDescriptor backupData =
241 ParcelFileDescriptor.open(backupDataName,
242 ParcelFileDescriptor.MODE_READ_WRITE |
243 ParcelFileDescriptor.MODE_CREATE);
244
245 newStateName.delete();
246 ParcelFileDescriptor newState =
247 ParcelFileDescriptor.open(newStateName,
248 ParcelFileDescriptor.MODE_READ_WRITE |
249 ParcelFileDescriptor.MODE_CREATE);
250
251 // Run the target's backup pass
252 try {
253 // TODO: Make this oneway
254 bs.doBackup(savedState, backupData, newState);
255 } finally {
256 if (savedState != null) {
257 savedState.close();
258 }
259 backupData.close();
260 newState.close();
261 }
262
263 // !!! TODO: Now propagate the newly-backed-up data to the transport
264
265 // !!! TODO: After successful transport, delete the now-stale data
266 // and juggle the files so that next time the new state is passed
267 backupDataName.delete();
268 newStateName.renameTo(savedStateName);
269
270 } catch (FileNotFoundException fnf) {
271 Log.d(TAG, "File not found on backup: ");
272 fnf.printStackTrace();
273 } catch (RemoteException e) {
274 Log.d(TAG, "Remote target " + name + " threw during backup:");
275 e.printStackTrace();
276 } catch (Exception e) {
277 Log.w(TAG, "Final exception guard in backup: ");
278 e.printStackTrace();
279 }
280 synchronized (mQueueLock) {
281 mBackupQueue.remove(0);
282 }
283 mContext.unbindService(mBackupHandler);
284 }
285
Christopher Tate3799bc22009-05-06 16:13:56 -0700286 // Add the backup services in the given package to our set of known backup participants.
287 // If 'packageName' is null, adds all backup services in the system.
288 void addPackageParticipantsLocked(String packageName) {
289 List<ResolveInfo> services = mPackageManager.queryIntentServices(
290 new Intent(BackupService.SERVICE_ACTION), 0);
291 addPackageParticipantsLockedInner(packageName, services);
292 }
293
294 private void addPackageParticipantsLockedInner(String packageName, List<ResolveInfo> services) {
295 for (ResolveInfo ri : services) {
296 if (packageName == null || ri.serviceInfo.packageName.equals(packageName)) {
297 int uid = ri.serviceInfo.applicationInfo.uid;
298 HashSet<ServiceInfo> set = mBackupParticipants.get(uid);
299 if (set == null) {
300 set = new HashSet<ServiceInfo>();
301 mBackupParticipants.put(uid, set);
302 }
303 if (DEBUG) {
304 Log.v(TAG, "Adding " + services.size() + " backup participants:");
305 for (ResolveInfo svc : services) {
306 Log.v(TAG, " " + svc + " : " + svc.filter);
307 }
308 }
309
310 set.add(ri.serviceInfo);
311 }
Christopher Tate487529a2009-04-29 14:03:25 -0700312 }
313 }
314
Christopher Tate3799bc22009-05-06 16:13:56 -0700315 // Remove the given package's backup services from our known active set. If
316 // 'packageName' is null, *all* backup services will be removed.
317 void removePackageParticipantsLocked(String packageName) {
318 List<ResolveInfo> services = mPackageManager.queryIntentServices(
319 new Intent(BackupService.SERVICE_ACTION), 0);
320 removePackageParticipantsLockedInner(packageName, services);
321 }
322
Joe Onorato8ad02812009-05-13 01:41:44 -0400323 private void removePackageParticipantsLockedInner(String packageName,
324 List<ResolveInfo> services) {
Christopher Tate3799bc22009-05-06 16:13:56 -0700325 for (ResolveInfo ri : services) {
326 if (packageName == null || ri.serviceInfo.packageName.equals(packageName)) {
327 int uid = ri.serviceInfo.applicationInfo.uid;
328 HashSet<ServiceInfo> set = mBackupParticipants.get(uid);
329 if (set != null) {
330 set.remove(ri.serviceInfo);
331 if (set.size() == 0) {
332 mBackupParticipants.put(uid, null);
333 }
334 }
335 }
336 }
337 }
338
339 // Reset the given package's known backup participants. Unlike add/remove, the update
340 // action cannot be passed a null package name.
341 void updatePackageParticipantsLocked(String packageName) {
342 if (packageName == null) {
343 Log.e(TAG, "updatePackageParticipants called with null package name");
344 return;
345 }
346
347 // brute force but small code size
348 List<ResolveInfo> services = mPackageManager.queryIntentServices(
349 new Intent(BackupService.SERVICE_ACTION), 0);
350 removePackageParticipantsLockedInner(packageName, services);
351 addPackageParticipantsLockedInner(packageName, services);
352 }
353
Christopher Tate487529a2009-04-29 14:03:25 -0700354 // ----- IBackupManager binder interface -----
355
Christopher Tatea8bf8152009-04-30 11:36:21 -0700356 public void dataChanged(String packageName) throws RemoteException {
Christopher Tate487529a2009-04-29 14:03:25 -0700357 // Record that we need a backup pass for the caller. Since multiple callers
358 // may share a uid, we need to note all candidates within that uid and schedule
359 // a backup pass for each of them.
Joe Onoratob1a7ffe2009-05-06 18:06:21 -0700360
361 Log.d(TAG, "dataChanged packageName=" + packageName);
Christopher Tate487529a2009-04-29 14:03:25 -0700362
363 HashSet<ServiceInfo> targets = mBackupParticipants.get(Binder.getCallingUid());
Joe Onoratob1a7ffe2009-05-06 18:06:21 -0700364 Log.d(TAG, "targets=" + targets);
Christopher Tate487529a2009-04-29 14:03:25 -0700365 if (targets != null) {
366 synchronized (mQueueLock) {
367 // Note that this client has made data changes that need to be backed up
Christopher Tate487529a2009-04-29 14:03:25 -0700368 for (ServiceInfo service : targets) {
Christopher Tatea8bf8152009-04-30 11:36:21 -0700369 // validate the caller-supplied package name against the known set of
370 // packages associated with this uid
371 if (service.packageName.equals(packageName)) {
Joe Onorato8ad02812009-05-13 01:41:44 -0400372 // Add the caller to the set of pending backups. If there is
373 // one already there, then overwrite it, but no harm done.
374 mPendingBackups.put(new ComponentName(service.packageName, service.name),
375 new BackupRequest(service, true));
376 // !!! TODO: write to the pending-backup journal file in case of crash
Christopher Tate487529a2009-04-29 14:03:25 -0700377 }
378 }
379
Joe Onoratob1a7ffe2009-05-06 18:06:21 -0700380 Log.d(TAG, "Scheduling backup for " + mPendingBackups.size() + " participants");
Christopher Tate487529a2009-04-29 14:03:25 -0700381 // Schedule a backup pass in a few minutes. As backup-eligible data
382 // keeps changing, continue to defer the backup pass until things
383 // settle down, to avoid extra overhead.
Christopher Tate487529a2009-04-29 14:03:25 -0700384 mBackupHandler.sendEmptyMessageDelayed(MSG_RUN_BACKUP, COLLECTION_INTERVAL);
385 }
386 }
387 }
Christopher Tate46758122009-05-06 11:22:00 -0700388
389 // Schedule a backup pass for a given package, even if the caller is not part of
390 // that uid or package itself.
391 public void scheduleFullBackup(String packageName) throws RemoteException {
392 // !!! TODO: protect with a signature-or-system permission?
393 HashSet<ServiceInfo> targets = new HashSet<ServiceInfo>();
394 synchronized (mQueueLock) {
395 int numKeys = mBackupParticipants.size();
396 for (int index = 0; index < numKeys; index++) {
397 int uid = mBackupParticipants.keyAt(index);
398 HashSet<ServiceInfo> servicesAtUid = mBackupParticipants.get(uid);
399 for (ServiceInfo service: servicesAtUid) {
400 if (service.packageName.equals(packageName)) {
Joe Onorato8ad02812009-05-13 01:41:44 -0400401 mPendingBackups.put(new ComponentName(service.packageName, service.name),
402 new BackupRequest(service, true));
Christopher Tate46758122009-05-06 11:22:00 -0700403 }
404 }
405 }
406 }
407 }
Joe Onoratob1a7ffe2009-05-06 18:06:21 -0700408
409
410 @Override
411 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
412 synchronized (mQueueLock) {
413 int N = mBackupParticipants.size();
414 pw.println("Participants:");
415 for (int i=0; i<N; i++) {
416 int uid = mBackupParticipants.keyAt(i);
417 pw.print(" uid: ");
418 pw.println(uid);
419 HashSet<ServiceInfo> services = mBackupParticipants.valueAt(i);
420 for (ServiceInfo s: services) {
421 pw.print(" ");
422 pw.println(s.toString());
423 }
424 }
425 }
426 }
Christopher Tate487529a2009-04-29 14:03:25 -0700427}