blob: 983329be5ea525f8b786c4a25a8b261a025a5241 [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) {
Joe Onorato290bb012009-05-13 18:57:29 -0400223 if (mBackupQueue == null) {
224 Log.d(TAG, "mBackupQueue is null. WHY?");
225 }
Joe Onorato8ad02812009-05-13 01:41:44 -0400226 request = mBackupQueue.get(0);
227 }
228
229 // !!! TODO right now these naming schemes limit applications to
230 // one backup service per package
231 File savedStateName = new File(mStateDir, request.service.packageName);
232 File backupDataName = new File(mDataDir, request.service.packageName + ".data");
233 File newStateName = new File(mStateDir, request.service.packageName + ".new");
234
235 // In a full backup, we pass a null ParcelFileDescriptor as
236 // the saved-state "file"
237 ParcelFileDescriptor savedState = (request.fullBackup) ? null
238 : ParcelFileDescriptor.open(savedStateName,
239 ParcelFileDescriptor.MODE_READ_ONLY |
240 ParcelFileDescriptor.MODE_CREATE);
241
242 backupDataName.delete();
243 ParcelFileDescriptor backupData =
244 ParcelFileDescriptor.open(backupDataName,
245 ParcelFileDescriptor.MODE_READ_WRITE |
246 ParcelFileDescriptor.MODE_CREATE);
247
248 newStateName.delete();
249 ParcelFileDescriptor newState =
250 ParcelFileDescriptor.open(newStateName,
251 ParcelFileDescriptor.MODE_READ_WRITE |
252 ParcelFileDescriptor.MODE_CREATE);
253
254 // Run the target's backup pass
255 try {
256 // TODO: Make this oneway
257 bs.doBackup(savedState, backupData, newState);
258 } finally {
259 if (savedState != null) {
260 savedState.close();
261 }
262 backupData.close();
263 newState.close();
264 }
265
266 // !!! TODO: Now propagate the newly-backed-up data to the transport
267
268 // !!! TODO: After successful transport, delete the now-stale data
269 // and juggle the files so that next time the new state is passed
270 backupDataName.delete();
271 newStateName.renameTo(savedStateName);
272
273 } catch (FileNotFoundException fnf) {
274 Log.d(TAG, "File not found on backup: ");
275 fnf.printStackTrace();
276 } catch (RemoteException e) {
277 Log.d(TAG, "Remote target " + name + " threw during backup:");
278 e.printStackTrace();
279 } catch (Exception e) {
280 Log.w(TAG, "Final exception guard in backup: ");
281 e.printStackTrace();
282 }
283 synchronized (mQueueLock) {
284 mBackupQueue.remove(0);
285 }
286 mContext.unbindService(mBackupHandler);
287 }
288
Christopher Tate3799bc22009-05-06 16:13:56 -0700289 // Add the backup services in the given package to our set of known backup participants.
290 // If 'packageName' is null, adds all backup services in the system.
291 void addPackageParticipantsLocked(String packageName) {
292 List<ResolveInfo> services = mPackageManager.queryIntentServices(
293 new Intent(BackupService.SERVICE_ACTION), 0);
294 addPackageParticipantsLockedInner(packageName, services);
295 }
296
297 private void addPackageParticipantsLockedInner(String packageName, List<ResolveInfo> services) {
298 for (ResolveInfo ri : services) {
299 if (packageName == null || ri.serviceInfo.packageName.equals(packageName)) {
300 int uid = ri.serviceInfo.applicationInfo.uid;
301 HashSet<ServiceInfo> set = mBackupParticipants.get(uid);
302 if (set == null) {
303 set = new HashSet<ServiceInfo>();
304 mBackupParticipants.put(uid, set);
305 }
306 if (DEBUG) {
307 Log.v(TAG, "Adding " + services.size() + " backup participants:");
308 for (ResolveInfo svc : services) {
309 Log.v(TAG, " " + svc + " : " + svc.filter);
310 }
311 }
312
313 set.add(ri.serviceInfo);
314 }
Christopher Tate487529a2009-04-29 14:03:25 -0700315 }
316 }
317
Christopher Tate3799bc22009-05-06 16:13:56 -0700318 // Remove the given package's backup services from our known active set. If
319 // 'packageName' is null, *all* backup services will be removed.
320 void removePackageParticipantsLocked(String packageName) {
321 List<ResolveInfo> services = mPackageManager.queryIntentServices(
322 new Intent(BackupService.SERVICE_ACTION), 0);
323 removePackageParticipantsLockedInner(packageName, services);
324 }
325
Joe Onorato8ad02812009-05-13 01:41:44 -0400326 private void removePackageParticipantsLockedInner(String packageName,
327 List<ResolveInfo> services) {
Christopher Tate3799bc22009-05-06 16:13:56 -0700328 for (ResolveInfo ri : services) {
329 if (packageName == null || ri.serviceInfo.packageName.equals(packageName)) {
330 int uid = ri.serviceInfo.applicationInfo.uid;
331 HashSet<ServiceInfo> set = mBackupParticipants.get(uid);
332 if (set != null) {
333 set.remove(ri.serviceInfo);
334 if (set.size() == 0) {
335 mBackupParticipants.put(uid, null);
336 }
337 }
338 }
339 }
340 }
341
342 // Reset the given package's known backup participants. Unlike add/remove, the update
343 // action cannot be passed a null package name.
344 void updatePackageParticipantsLocked(String packageName) {
345 if (packageName == null) {
346 Log.e(TAG, "updatePackageParticipants called with null package name");
347 return;
348 }
349
350 // brute force but small code size
351 List<ResolveInfo> services = mPackageManager.queryIntentServices(
352 new Intent(BackupService.SERVICE_ACTION), 0);
353 removePackageParticipantsLockedInner(packageName, services);
354 addPackageParticipantsLockedInner(packageName, services);
355 }
356
Christopher Tate487529a2009-04-29 14:03:25 -0700357 // ----- IBackupManager binder interface -----
358
Christopher Tatea8bf8152009-04-30 11:36:21 -0700359 public void dataChanged(String packageName) throws RemoteException {
Christopher Tate487529a2009-04-29 14:03:25 -0700360 // Record that we need a backup pass for the caller. Since multiple callers
361 // may share a uid, we need to note all candidates within that uid and schedule
362 // a backup pass for each of them.
Joe Onoratob1a7ffe2009-05-06 18:06:21 -0700363
364 Log.d(TAG, "dataChanged packageName=" + packageName);
Christopher Tate487529a2009-04-29 14:03:25 -0700365
366 HashSet<ServiceInfo> targets = mBackupParticipants.get(Binder.getCallingUid());
Joe Onoratob1a7ffe2009-05-06 18:06:21 -0700367 Log.d(TAG, "targets=" + targets);
Christopher Tate487529a2009-04-29 14:03:25 -0700368 if (targets != null) {
369 synchronized (mQueueLock) {
370 // Note that this client has made data changes that need to be backed up
Christopher Tate487529a2009-04-29 14:03:25 -0700371 for (ServiceInfo service : targets) {
Christopher Tatea8bf8152009-04-30 11:36:21 -0700372 // validate the caller-supplied package name against the known set of
373 // packages associated with this uid
374 if (service.packageName.equals(packageName)) {
Joe Onorato8ad02812009-05-13 01:41:44 -0400375 // Add the caller to the set of pending backups. If there is
376 // one already there, then overwrite it, but no harm done.
377 mPendingBackups.put(new ComponentName(service.packageName, service.name),
378 new BackupRequest(service, true));
379 // !!! TODO: write to the pending-backup journal file in case of crash
Christopher Tate487529a2009-04-29 14:03:25 -0700380 }
381 }
382
Joe Onoratob1a7ffe2009-05-06 18:06:21 -0700383 Log.d(TAG, "Scheduling backup for " + mPendingBackups.size() + " participants");
Christopher Tate487529a2009-04-29 14:03:25 -0700384 // Schedule a backup pass in a few minutes. As backup-eligible data
385 // keeps changing, continue to defer the backup pass until things
386 // settle down, to avoid extra overhead.
Christopher Tate487529a2009-04-29 14:03:25 -0700387 mBackupHandler.sendEmptyMessageDelayed(MSG_RUN_BACKUP, COLLECTION_INTERVAL);
388 }
389 }
390 }
Christopher Tate46758122009-05-06 11:22:00 -0700391
392 // Schedule a backup pass for a given package, even if the caller is not part of
393 // that uid or package itself.
394 public void scheduleFullBackup(String packageName) throws RemoteException {
395 // !!! TODO: protect with a signature-or-system permission?
396 HashSet<ServiceInfo> targets = new HashSet<ServiceInfo>();
397 synchronized (mQueueLock) {
398 int numKeys = mBackupParticipants.size();
399 for (int index = 0; index < numKeys; index++) {
400 int uid = mBackupParticipants.keyAt(index);
401 HashSet<ServiceInfo> servicesAtUid = mBackupParticipants.get(uid);
402 for (ServiceInfo service: servicesAtUid) {
403 if (service.packageName.equals(packageName)) {
Joe Onorato8ad02812009-05-13 01:41:44 -0400404 mPendingBackups.put(new ComponentName(service.packageName, service.name),
405 new BackupRequest(service, true));
Christopher Tate46758122009-05-06 11:22:00 -0700406 }
407 }
408 }
409 }
410 }
Joe Onoratob1a7ffe2009-05-06 18:06:21 -0700411
412
413 @Override
414 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
415 synchronized (mQueueLock) {
416 int N = mBackupParticipants.size();
417 pw.println("Participants:");
418 for (int i=0; i<N; i++) {
419 int uid = mBackupParticipants.keyAt(i);
420 pw.print(" uid: ");
421 pw.println(uid);
422 HashSet<ServiceInfo> services = mBackupParticipants.valueAt(i);
423 for (ServiceInfo s: services) {
424 pw.print(" ");
425 pw.println(s.toString());
426 }
427 }
428 }
429 }
Christopher Tate487529a2009-04-29 14:03:25 -0700430}