blob: 63fc871e8ec963294fd38ae2a6f796eb9a659447 [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;
45import java.io.FileNotFoundException;
Christopher Tate487529a2009-04-29 14:03:25 -070046import java.lang.String;
47import java.util.HashSet;
48import java.util.List;
49
50class BackupManagerService extends IBackupManager.Stub {
51 private static final String TAG = "BackupManagerService";
52 private static final boolean DEBUG = true;
53
54 private static final long COLLECTION_INTERVAL = 3 * 60 * 1000;
55
56 private static final int MSG_RUN_BACKUP = 1;
57
58 private Context mContext;
59 private PackageManager mPackageManager;
60 private final BackupHandler mBackupHandler = new BackupHandler();
61 // map UIDs to the set of backup client services within that UID's app set
62 private SparseArray<HashSet<ServiceInfo>> mBackupParticipants
63 = new SparseArray<HashSet<ServiceInfo>>();
64 // set of backup services that have pending changes
Christopher Tate46758122009-05-06 11:22:00 -070065 private class BackupRequest {
66 public ServiceInfo service;
67 public boolean fullBackup;
68
69 BackupRequest(ServiceInfo svc, boolean isFull) {
70 service = svc;
71 fullBackup = isFull;
72 }
73 }
74 private HashSet<BackupRequest> mPendingBackups = new HashSet<BackupRequest>();
Christopher Tate487529a2009-04-29 14:03:25 -070075 private final Object mQueueLock = new Object();
76
Christopher Tate22b87872009-05-04 16:41:53 -070077 private File mStateDir;
Christopher Tatef4172472009-05-05 15:50:03 -070078 private File mDataDir;
Christopher Tate487529a2009-04-29 14:03:25 -070079
80 // ----- Handler that runs the actual backup process asynchronously -----
81
82 private class BackupHandler extends Handler implements ServiceConnection {
83 private volatile Object mBindSignaller = new Object();
84 private volatile boolean mBinding = false;
85 private IBackupService mTargetService = null;
86
87 public void handleMessage(Message msg) {
88
89 switch (msg.what) {
90 case MSG_RUN_BACKUP:
91 {
92 // snapshot the pending-backup set and work on that
Christopher Tate46758122009-05-06 11:22:00 -070093 HashSet<BackupRequest> queue;
Christopher Tate487529a2009-04-29 14:03:25 -070094 synchronized (mQueueLock) {
95 queue = mPendingBackups;
Christopher Tate46758122009-05-06 11:22:00 -070096 mPendingBackups = new HashSet<BackupRequest>();
Christopher Tate487529a2009-04-29 14:03:25 -070097 // !!! TODO: start a new backup-queue journal file too
98 }
99
100 // Walk the set of pending backups, setting up the relevant files and
101 // invoking the backup service in each participant
102 Intent backupIntent = new Intent(BackupService.SERVICE_ACTION);
Christopher Tate46758122009-05-06 11:22:00 -0700103 for (BackupRequest request : queue) {
Christopher Tate487529a2009-04-29 14:03:25 -0700104 mBinding = true;
105 mTargetService = null;
106
Christopher Tate46758122009-05-06 11:22:00 -0700107 backupIntent.setClassName(request.service.packageName, request.service.name);
Christopher Tate487529a2009-04-29 14:03:25 -0700108 Log.d(TAG, "binding to " + backupIntent);
109 if (mContext.bindService(backupIntent, this, 0)) {
110 synchronized (mBindSignaller) {
111 while (mTargetService == null && mBinding == true) {
112 try {
113 mBindSignaller.wait();
114 } catch (InterruptedException e) {
115 }
116 }
117 }
118 if (mTargetService != null) {
119 try {
120 Log.d(TAG, "invoking doBackup() on " + backupIntent);
Christopher Tate22b87872009-05-04 16:41:53 -0700121
Christopher Tate46758122009-05-06 11:22:00 -0700122 // !!! TODO right now these naming schemes limit applications to
123 // one backup service per package
124 File savedStateName = new File(mStateDir,
125 request.service.packageName);
Christopher Tate46758122009-05-06 11:22:00 -0700126 File backupDataName = new File(mDataDir,
127 request.service.packageName + ".data");
128 File newStateName = new File(mStateDir,
129 request.service.packageName + ".new");
Christopher Tate22b87872009-05-04 16:41:53 -0700130
Christopher Tateb1d790b2009-05-06 12:38:21 -0700131 // In a full backup, we pass a null ParcelFileDescriptor as
132 // the saved-state "file"
133 ParcelFileDescriptor savedState = (request.fullBackup) ? null
134 : ParcelFileDescriptor.open(savedStateName,
Christopher Tate46758122009-05-06 11:22:00 -0700135 ParcelFileDescriptor.MODE_READ_ONLY |
136 ParcelFileDescriptor.MODE_CREATE);
137
Christopher Tatef4172472009-05-05 15:50:03 -0700138 backupDataName.delete();
Christopher Tate22b87872009-05-04 16:41:53 -0700139 ParcelFileDescriptor backupData =
140 ParcelFileDescriptor.open(backupDataName,
141 ParcelFileDescriptor.MODE_READ_WRITE |
142 ParcelFileDescriptor.MODE_CREATE);
Christopher Tatef4172472009-05-05 15:50:03 -0700143
144 newStateName.delete();
Christopher Tate22b87872009-05-04 16:41:53 -0700145 ParcelFileDescriptor newState =
146 ParcelFileDescriptor.open(newStateName,
147 ParcelFileDescriptor.MODE_READ_WRITE |
148 ParcelFileDescriptor.MODE_CREATE);
149
Christopher Tatef4172472009-05-05 15:50:03 -0700150 // Run the target's backup pass
151 try {
152 mTargetService.doBackup(savedState, backupData, newState);
153 } finally {
154 savedState.close();
155 backupData.close();
156 newState.close();
157 }
Christopher Tate22b87872009-05-04 16:41:53 -0700158
159 // !!! TODO: Now propagate the newly-backed-up data to the transport
160
Christopher Tatef4172472009-05-05 15:50:03 -0700161 // !!! TODO: After successful transport, delete the now-stale data
162 // and juggle the files so that next time the new state is passed
163 backupDataName.delete();
164 newStateName.renameTo(savedStateName);
Christopher Tate22b87872009-05-04 16:41:53 -0700165
166 } catch (FileNotFoundException fnf) {
167 Log.d(TAG, "File not found on backup: ");
168 fnf.printStackTrace();
Christopher Tate487529a2009-04-29 14:03:25 -0700169 } catch (RemoteException e) {
170 Log.d(TAG, "Remote target " + backupIntent
171 + " threw during backup:");
172 e.printStackTrace();
Christopher Tate22b87872009-05-04 16:41:53 -0700173 } catch (Exception e) {
174 Log.w(TAG, "Final exception guard in backup: ");
175 e.printStackTrace();
Christopher Tate487529a2009-04-29 14:03:25 -0700176 }
177 mContext.unbindService(this);
178 }
179 } else {
180 Log.d(TAG, "Unable to bind to " + backupIntent);
181 }
182 }
183 }
184 break;
185 }
186 }
187
188 public void onServiceConnected(ComponentName name, IBinder service) {
189 synchronized (mBindSignaller) {
190 mTargetService = IBackupService.Stub.asInterface(service);
191 mBinding = false;
192 mBindSignaller.notifyAll();
193 }
194 }
195
196 public void onServiceDisconnected(ComponentName name) {
197 synchronized (mBindSignaller) {
198 mTargetService = null;
199 mBinding = false;
200 mBindSignaller.notifyAll();
201 }
202 }
203 }
204
205 public BackupManagerService(Context context) {
206 mContext = context;
207 mPackageManager = context.getPackageManager();
208
Christopher Tate22b87872009-05-04 16:41:53 -0700209 // Set up our bookkeeping
Christopher Tatef4172472009-05-05 15:50:03 -0700210 mStateDir = new File(Environment.getDataDirectory(), "backup");
Christopher Tate22b87872009-05-04 16:41:53 -0700211 mStateDir.mkdirs();
Christopher Tatef4172472009-05-05 15:50:03 -0700212 mDataDir = Environment.getDownloadCacheDirectory();
Christopher Tate22b87872009-05-04 16:41:53 -0700213
Christopher Tate3799bc22009-05-06 16:13:56 -0700214 // Build our mapping of uid to backup client services
215 synchronized (mBackupParticipants) {
216 addPackageParticipantsLocked(null);
Christopher Tate487529a2009-04-29 14:03:25 -0700217 }
218
Christopher Tate3799bc22009-05-06 16:13:56 -0700219 // Register for broadcasts about package install, etc., so we can
220 // update the provider list.
221 IntentFilter filter = new IntentFilter();
222 filter.addAction(Intent.ACTION_PACKAGE_ADDED);
223 filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
224 filter.addDataScheme("package");
225 mContext.registerReceiver(mBroadcastReceiver, filter);
226 }
227
228 // ----- Track installation/removal of packages -----
229 BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
230 public void onReceive(Context context, Intent intent) {
231 if (DEBUG) Log.d(TAG, "Received broadcast " + intent);
232
233 Uri uri = intent.getData();
234 if (uri == null) {
235 return;
Christopher Tate487529a2009-04-29 14:03:25 -0700236 }
Christopher Tate3799bc22009-05-06 16:13:56 -0700237 String pkgName = uri.getSchemeSpecificPart();
238 if (pkgName == null) {
239 return;
240 }
241
242 String action = intent.getAction();
243 if (Intent.ACTION_PACKAGE_ADDED.equals(action)) {
244 synchronized (mBackupParticipants) {
245 Bundle extras = intent.getExtras();
246 if (extras != null && extras.getBoolean(Intent.EXTRA_REPLACING, false)) {
247 // The package was just upgraded
248 updatePackageParticipantsLocked(pkgName);
249 } else {
250 // The package was just added
251 addPackageParticipantsLocked(pkgName);
252 }
253 }
254 }
255 else if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) {
256 Bundle extras = intent.getExtras();
257 if (extras != null && extras.getBoolean(Intent.EXTRA_REPLACING, false)) {
258 // The package is being updated. We'll receive a PACKAGE_ADDED shortly.
259 } else {
260 synchronized (mBackupParticipants) {
261 removePackageParticipantsLocked(pkgName);
262 }
263 }
264 }
265 }
266 };
267
268 // Add the backup services in the given package to our set of known backup participants.
269 // If 'packageName' is null, adds all backup services in the system.
270 void addPackageParticipantsLocked(String packageName) {
271 List<ResolveInfo> services = mPackageManager.queryIntentServices(
272 new Intent(BackupService.SERVICE_ACTION), 0);
273 addPackageParticipantsLockedInner(packageName, services);
274 }
275
276 private void addPackageParticipantsLockedInner(String packageName, List<ResolveInfo> services) {
277 for (ResolveInfo ri : services) {
278 if (packageName == null || ri.serviceInfo.packageName.equals(packageName)) {
279 int uid = ri.serviceInfo.applicationInfo.uid;
280 HashSet<ServiceInfo> set = mBackupParticipants.get(uid);
281 if (set == null) {
282 set = new HashSet<ServiceInfo>();
283 mBackupParticipants.put(uid, set);
284 }
285 if (DEBUG) {
286 Log.v(TAG, "Adding " + services.size() + " backup participants:");
287 for (ResolveInfo svc : services) {
288 Log.v(TAG, " " + svc + " : " + svc.filter);
289 }
290 }
291
292 set.add(ri.serviceInfo);
293 }
Christopher Tate487529a2009-04-29 14:03:25 -0700294 }
295 }
296
Christopher Tate3799bc22009-05-06 16:13:56 -0700297 // Remove the given package's backup services from our known active set. If
298 // 'packageName' is null, *all* backup services will be removed.
299 void removePackageParticipantsLocked(String packageName) {
300 List<ResolveInfo> services = mPackageManager.queryIntentServices(
301 new Intent(BackupService.SERVICE_ACTION), 0);
302 removePackageParticipantsLockedInner(packageName, services);
303 }
304
305 private void removePackageParticipantsLockedInner(String packageName, List<ResolveInfo> services) {
306 for (ResolveInfo ri : services) {
307 if (packageName == null || ri.serviceInfo.packageName.equals(packageName)) {
308 int uid = ri.serviceInfo.applicationInfo.uid;
309 HashSet<ServiceInfo> set = mBackupParticipants.get(uid);
310 if (set != null) {
311 set.remove(ri.serviceInfo);
312 if (set.size() == 0) {
313 mBackupParticipants.put(uid, null);
314 }
315 }
316 }
317 }
318 }
319
320 // Reset the given package's known backup participants. Unlike add/remove, the update
321 // action cannot be passed a null package name.
322 void updatePackageParticipantsLocked(String packageName) {
323 if (packageName == null) {
324 Log.e(TAG, "updatePackageParticipants called with null package name");
325 return;
326 }
327
328 // brute force but small code size
329 List<ResolveInfo> services = mPackageManager.queryIntentServices(
330 new Intent(BackupService.SERVICE_ACTION), 0);
331 removePackageParticipantsLockedInner(packageName, services);
332 addPackageParticipantsLockedInner(packageName, services);
333 }
334
Christopher Tate487529a2009-04-29 14:03:25 -0700335 // ----- IBackupManager binder interface -----
336
Christopher Tatea8bf8152009-04-30 11:36:21 -0700337 public void dataChanged(String packageName) throws RemoteException {
Christopher Tate487529a2009-04-29 14:03:25 -0700338 // Record that we need a backup pass for the caller. Since multiple callers
339 // may share a uid, we need to note all candidates within that uid and schedule
340 // a backup pass for each of them.
341
342 HashSet<ServiceInfo> targets = mBackupParticipants.get(Binder.getCallingUid());
343 if (targets != null) {
344 synchronized (mQueueLock) {
345 // Note that this client has made data changes that need to be backed up
Christopher Tate487529a2009-04-29 14:03:25 -0700346 for (ServiceInfo service : targets) {
Christopher Tatea8bf8152009-04-30 11:36:21 -0700347 // validate the caller-supplied package name against the known set of
348 // packages associated with this uid
349 if (service.packageName.equals(packageName)) {
350 // add the caller to the set of pending backups
Christopher Tate46758122009-05-06 11:22:00 -0700351 if (mPendingBackups.add(new BackupRequest(service, false))) {
Christopher Tatea8bf8152009-04-30 11:36:21 -0700352 // !!! TODO: write to the pending-backup journal file in case of crash
353 }
Christopher Tate487529a2009-04-29 14:03:25 -0700354 }
355 }
356
357 // Schedule a backup pass in a few minutes. As backup-eligible data
358 // keeps changing, continue to defer the backup pass until things
359 // settle down, to avoid extra overhead.
Christopher Tate487529a2009-04-29 14:03:25 -0700360 mBackupHandler.sendEmptyMessageDelayed(MSG_RUN_BACKUP, COLLECTION_INTERVAL);
361 }
362 }
363 }
Christopher Tate46758122009-05-06 11:22:00 -0700364
365 // Schedule a backup pass for a given package, even if the caller is not part of
366 // that uid or package itself.
367 public void scheduleFullBackup(String packageName) throws RemoteException {
368 // !!! TODO: protect with a signature-or-system permission?
369 HashSet<ServiceInfo> targets = new HashSet<ServiceInfo>();
370 synchronized (mQueueLock) {
371 int numKeys = mBackupParticipants.size();
372 for (int index = 0; index < numKeys; index++) {
373 int uid = mBackupParticipants.keyAt(index);
374 HashSet<ServiceInfo> servicesAtUid = mBackupParticipants.get(uid);
375 for (ServiceInfo service: servicesAtUid) {
376 if (service.packageName.equals(packageName)) {
377 mPendingBackups.add(new BackupRequest(service, true));
378 }
379 }
380 }
381 }
382 }
Christopher Tate487529a2009-04-29 14:03:25 -0700383}