blob: 56caeeaadff2a59264814496d176221e44c00dfd [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;
21import android.content.ComponentName;
22import android.content.Context;
23import android.content.Intent;
24import android.content.ServiceConnection;
25import android.content.pm.PackageManager;
26import android.content.pm.ResolveInfo;
27import android.content.pm.ServiceInfo;
28import android.os.Binder;
Christopher Tate22b87872009-05-04 16:41:53 -070029import android.os.Environment;
Christopher Tate487529a2009-04-29 14:03:25 -070030import android.os.Handler;
31import android.os.IBinder;
32import android.os.Message;
Christopher Tate22b87872009-05-04 16:41:53 -070033import android.os.ParcelFileDescriptor;
Christopher Tate487529a2009-04-29 14:03:25 -070034import android.os.RemoteException;
35import android.util.Log;
36import android.util.SparseArray;
37
38import android.backup.IBackupManager;
39
Christopher Tate22b87872009-05-04 16:41:53 -070040import java.io.File;
41import java.io.FileNotFoundException;
Christopher Tate487529a2009-04-29 14:03:25 -070042import java.lang.String;
43import java.util.HashSet;
44import java.util.List;
45
46class BackupManagerService extends IBackupManager.Stub {
47 private static final String TAG = "BackupManagerService";
48 private static final boolean DEBUG = true;
49
50 private static final long COLLECTION_INTERVAL = 3 * 60 * 1000;
51
52 private static final int MSG_RUN_BACKUP = 1;
53
54 private Context mContext;
55 private PackageManager mPackageManager;
56 private final BackupHandler mBackupHandler = new BackupHandler();
57 // map UIDs to the set of backup client services within that UID's app set
58 private SparseArray<HashSet<ServiceInfo>> mBackupParticipants
59 = new SparseArray<HashSet<ServiceInfo>>();
60 // set of backup services that have pending changes
Christopher Tate46758122009-05-06 11:22:00 -070061 private class BackupRequest {
62 public ServiceInfo service;
63 public boolean fullBackup;
64
65 BackupRequest(ServiceInfo svc, boolean isFull) {
66 service = svc;
67 fullBackup = isFull;
68 }
69 }
70 private HashSet<BackupRequest> mPendingBackups = new HashSet<BackupRequest>();
Christopher Tate487529a2009-04-29 14:03:25 -070071 private final Object mQueueLock = new Object();
72
Christopher Tate22b87872009-05-04 16:41:53 -070073 private File mStateDir;
Christopher Tatef4172472009-05-05 15:50:03 -070074 private File mDataDir;
Christopher Tate487529a2009-04-29 14:03:25 -070075
76 // ----- Handler that runs the actual backup process asynchronously -----
77
78 private class BackupHandler extends Handler implements ServiceConnection {
79 private volatile Object mBindSignaller = new Object();
80 private volatile boolean mBinding = false;
81 private IBackupService mTargetService = null;
82
83 public void handleMessage(Message msg) {
84
85 switch (msg.what) {
86 case MSG_RUN_BACKUP:
87 {
88 // snapshot the pending-backup set and work on that
Christopher Tate46758122009-05-06 11:22:00 -070089 HashSet<BackupRequest> queue;
Christopher Tate487529a2009-04-29 14:03:25 -070090 synchronized (mQueueLock) {
91 queue = mPendingBackups;
Christopher Tate46758122009-05-06 11:22:00 -070092 mPendingBackups = new HashSet<BackupRequest>();
Christopher Tate487529a2009-04-29 14:03:25 -070093 // !!! TODO: start a new backup-queue journal file too
94 }
95
96 // Walk the set of pending backups, setting up the relevant files and
97 // invoking the backup service in each participant
98 Intent backupIntent = new Intent(BackupService.SERVICE_ACTION);
Christopher Tate46758122009-05-06 11:22:00 -070099 for (BackupRequest request : queue) {
Christopher Tate487529a2009-04-29 14:03:25 -0700100 mBinding = true;
101 mTargetService = null;
102
Christopher Tate46758122009-05-06 11:22:00 -0700103 backupIntent.setClassName(request.service.packageName, request.service.name);
Christopher Tate487529a2009-04-29 14:03:25 -0700104 Log.d(TAG, "binding to " + backupIntent);
105 if (mContext.bindService(backupIntent, this, 0)) {
106 synchronized (mBindSignaller) {
107 while (mTargetService == null && mBinding == true) {
108 try {
109 mBindSignaller.wait();
110 } catch (InterruptedException e) {
111 }
112 }
113 }
114 if (mTargetService != null) {
115 try {
116 Log.d(TAG, "invoking doBackup() on " + backupIntent);
Christopher Tate22b87872009-05-04 16:41:53 -0700117
Christopher Tate46758122009-05-06 11:22:00 -0700118 // !!! TODO right now these naming schemes limit applications to
119 // one backup service per package
120 File savedStateName = new File(mStateDir,
121 request.service.packageName);
Christopher Tate46758122009-05-06 11:22:00 -0700122 File backupDataName = new File(mDataDir,
123 request.service.packageName + ".data");
124 File newStateName = new File(mStateDir,
125 request.service.packageName + ".new");
Christopher Tate22b87872009-05-04 16:41:53 -0700126
Christopher Tateb1d790b2009-05-06 12:38:21 -0700127 // In a full backup, we pass a null ParcelFileDescriptor as
128 // the saved-state "file"
129 ParcelFileDescriptor savedState = (request.fullBackup) ? null
130 : ParcelFileDescriptor.open(savedStateName,
Christopher Tate46758122009-05-06 11:22:00 -0700131 ParcelFileDescriptor.MODE_READ_ONLY |
132 ParcelFileDescriptor.MODE_CREATE);
133
Christopher Tatef4172472009-05-05 15:50:03 -0700134 backupDataName.delete();
Christopher Tate22b87872009-05-04 16:41:53 -0700135 ParcelFileDescriptor backupData =
136 ParcelFileDescriptor.open(backupDataName,
137 ParcelFileDescriptor.MODE_READ_WRITE |
138 ParcelFileDescriptor.MODE_CREATE);
Christopher Tatef4172472009-05-05 15:50:03 -0700139
140 newStateName.delete();
Christopher Tate22b87872009-05-04 16:41:53 -0700141 ParcelFileDescriptor newState =
142 ParcelFileDescriptor.open(newStateName,
143 ParcelFileDescriptor.MODE_READ_WRITE |
144 ParcelFileDescriptor.MODE_CREATE);
145
Christopher Tatef4172472009-05-05 15:50:03 -0700146 // Run the target's backup pass
147 try {
148 mTargetService.doBackup(savedState, backupData, newState);
149 } finally {
150 savedState.close();
151 backupData.close();
152 newState.close();
153 }
Christopher Tate22b87872009-05-04 16:41:53 -0700154
155 // !!! TODO: Now propagate the newly-backed-up data to the transport
156
Christopher Tatef4172472009-05-05 15:50:03 -0700157 // !!! TODO: After successful transport, delete the now-stale data
158 // and juggle the files so that next time the new state is passed
159 backupDataName.delete();
160 newStateName.renameTo(savedStateName);
Christopher Tate22b87872009-05-04 16:41:53 -0700161
162 } catch (FileNotFoundException fnf) {
163 Log.d(TAG, "File not found on backup: ");
164 fnf.printStackTrace();
Christopher Tate487529a2009-04-29 14:03:25 -0700165 } catch (RemoteException e) {
166 Log.d(TAG, "Remote target " + backupIntent
167 + " threw during backup:");
168 e.printStackTrace();
Christopher Tate22b87872009-05-04 16:41:53 -0700169 } catch (Exception e) {
170 Log.w(TAG, "Final exception guard in backup: ");
171 e.printStackTrace();
Christopher Tate487529a2009-04-29 14:03:25 -0700172 }
173 mContext.unbindService(this);
174 }
175 } else {
176 Log.d(TAG, "Unable to bind to " + backupIntent);
177 }
178 }
179 }
180 break;
181 }
182 }
183
184 public void onServiceConnected(ComponentName name, IBinder service) {
185 synchronized (mBindSignaller) {
186 mTargetService = IBackupService.Stub.asInterface(service);
187 mBinding = false;
188 mBindSignaller.notifyAll();
189 }
190 }
191
192 public void onServiceDisconnected(ComponentName name) {
193 synchronized (mBindSignaller) {
194 mTargetService = null;
195 mBinding = false;
196 mBindSignaller.notifyAll();
197 }
198 }
199 }
200
201 public BackupManagerService(Context context) {
202 mContext = context;
203 mPackageManager = context.getPackageManager();
204
Christopher Tate22b87872009-05-04 16:41:53 -0700205 // Set up our bookkeeping
Christopher Tatef4172472009-05-05 15:50:03 -0700206 mStateDir = new File(Environment.getDataDirectory(), "backup");
Christopher Tate22b87872009-05-04 16:41:53 -0700207 mStateDir.mkdirs();
Christopher Tatef4172472009-05-05 15:50:03 -0700208 mDataDir = Environment.getDownloadCacheDirectory();
Christopher Tate22b87872009-05-04 16:41:53 -0700209
Christopher Tate487529a2009-04-29 14:03:25 -0700210 // Identify the backup participants
211 // !!! TODO: also watch package-install to keep this up to date
212 List<ResolveInfo> services = mPackageManager.queryIntentServices(
213 new Intent(BackupService.SERVICE_ACTION), 0);
214 if (DEBUG) {
215 Log.v(TAG, "Backup participants: " + services.size());
216 for (ResolveInfo ri : services) {
217 Log.v(TAG, " " + ri + " : " + ri.filter);
218 }
219 }
220
221 // Build our mapping of uid to backup client services
222 for (ResolveInfo ri : services) {
223 int uid = ri.serviceInfo.applicationInfo.uid;
224 HashSet<ServiceInfo> set = mBackupParticipants.get(uid);
225 if (set == null) {
226 set = new HashSet<ServiceInfo>();
227 mBackupParticipants.put(uid, set);
228 }
229 set.add(ri.serviceInfo);
230 }
231 }
232
233
234 // ----- IBackupManager binder interface -----
235
Christopher Tatea8bf8152009-04-30 11:36:21 -0700236 public void dataChanged(String packageName) throws RemoteException {
Christopher Tate487529a2009-04-29 14:03:25 -0700237 // Record that we need a backup pass for the caller. Since multiple callers
238 // may share a uid, we need to note all candidates within that uid and schedule
239 // a backup pass for each of them.
240
241 HashSet<ServiceInfo> targets = mBackupParticipants.get(Binder.getCallingUid());
242 if (targets != null) {
243 synchronized (mQueueLock) {
244 // Note that this client has made data changes that need to be backed up
Christopher Tate487529a2009-04-29 14:03:25 -0700245 for (ServiceInfo service : targets) {
Christopher Tatea8bf8152009-04-30 11:36:21 -0700246 // validate the caller-supplied package name against the known set of
247 // packages associated with this uid
248 if (service.packageName.equals(packageName)) {
249 // add the caller to the set of pending backups
Christopher Tate46758122009-05-06 11:22:00 -0700250 if (mPendingBackups.add(new BackupRequest(service, false))) {
Christopher Tatea8bf8152009-04-30 11:36:21 -0700251 // !!! TODO: write to the pending-backup journal file in case of crash
252 }
Christopher Tate487529a2009-04-29 14:03:25 -0700253 }
254 }
255
256 // Schedule a backup pass in a few minutes. As backup-eligible data
257 // keeps changing, continue to defer the backup pass until things
258 // settle down, to avoid extra overhead.
Christopher Tate487529a2009-04-29 14:03:25 -0700259 mBackupHandler.sendEmptyMessageDelayed(MSG_RUN_BACKUP, COLLECTION_INTERVAL);
260 }
261 }
262 }
Christopher Tate46758122009-05-06 11:22:00 -0700263
264 // Schedule a backup pass for a given package, even if the caller is not part of
265 // that uid or package itself.
266 public void scheduleFullBackup(String packageName) throws RemoteException {
267 // !!! TODO: protect with a signature-or-system permission?
268 HashSet<ServiceInfo> targets = new HashSet<ServiceInfo>();
269 synchronized (mQueueLock) {
270 int numKeys = mBackupParticipants.size();
271 for (int index = 0; index < numKeys; index++) {
272 int uid = mBackupParticipants.keyAt(index);
273 HashSet<ServiceInfo> servicesAtUid = mBackupParticipants.get(uid);
274 for (ServiceInfo service: servicesAtUid) {
275 if (service.packageName.equals(packageName)) {
276 mPendingBackups.add(new BackupRequest(service, true));
277 }
278 }
279 }
280 }
281 }
Christopher Tate487529a2009-04-29 14:03:25 -0700282}