blob: 04cd53f9278e9f8e3dee52071f5ee292724acf97 [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
61 private HashSet<ServiceInfo> mPendingBackups = new HashSet<ServiceInfo>();
62 private final Object mQueueLock = new Object();
63
Christopher Tate22b87872009-05-04 16:41:53 -070064 private File mStateDir;
Christopher Tatef4172472009-05-05 15:50:03 -070065 private File mDataDir;
Christopher Tate487529a2009-04-29 14:03:25 -070066
67 // ----- Handler that runs the actual backup process asynchronously -----
68
69 private class BackupHandler extends Handler implements ServiceConnection {
70 private volatile Object mBindSignaller = new Object();
71 private volatile boolean mBinding = false;
72 private IBackupService mTargetService = null;
73
74 public void handleMessage(Message msg) {
75
76 switch (msg.what) {
77 case MSG_RUN_BACKUP:
78 {
79 // snapshot the pending-backup set and work on that
80 HashSet<ServiceInfo> queue;
81 synchronized (mQueueLock) {
82 queue = mPendingBackups;
83 mPendingBackups = new HashSet<ServiceInfo>();
84 // !!! TODO: start a new backup-queue journal file too
85 }
86
87 // Walk the set of pending backups, setting up the relevant files and
88 // invoking the backup service in each participant
89 Intent backupIntent = new Intent(BackupService.SERVICE_ACTION);
90 for (ServiceInfo service : queue) {
91 mBinding = true;
92 mTargetService = null;
93
94 backupIntent.setClassName(service.packageName, service.name);
95 Log.d(TAG, "binding to " + backupIntent);
96 if (mContext.bindService(backupIntent, this, 0)) {
97 synchronized (mBindSignaller) {
98 while (mTargetService == null && mBinding == true) {
99 try {
100 mBindSignaller.wait();
101 } catch (InterruptedException e) {
102 }
103 }
104 }
105 if (mTargetService != null) {
106 try {
107 Log.d(TAG, "invoking doBackup() on " + backupIntent);
Christopher Tate22b87872009-05-04 16:41:53 -0700108
109 File savedStateName = new File(mStateDir, service.packageName);
Christopher Tatef4172472009-05-05 15:50:03 -0700110 File backupDataName = new File(mDataDir, service.packageName + ".data");
Christopher Tate22b87872009-05-04 16:41:53 -0700111 File newStateName = new File(mStateDir, service.packageName + ".new");
112
113 ParcelFileDescriptor savedState =
114 ParcelFileDescriptor.open(savedStateName,
115 ParcelFileDescriptor.MODE_READ_ONLY |
116 ParcelFileDescriptor.MODE_CREATE);
Christopher Tatef4172472009-05-05 15:50:03 -0700117
118 backupDataName.delete();
Christopher Tate22b87872009-05-04 16:41:53 -0700119 ParcelFileDescriptor backupData =
120 ParcelFileDescriptor.open(backupDataName,
121 ParcelFileDescriptor.MODE_READ_WRITE |
122 ParcelFileDescriptor.MODE_CREATE);
Christopher Tatef4172472009-05-05 15:50:03 -0700123
124 newStateName.delete();
Christopher Tate22b87872009-05-04 16:41:53 -0700125 ParcelFileDescriptor newState =
126 ParcelFileDescriptor.open(newStateName,
127 ParcelFileDescriptor.MODE_READ_WRITE |
128 ParcelFileDescriptor.MODE_CREATE);
129
Christopher Tatef4172472009-05-05 15:50:03 -0700130 // Run the target's backup pass
131 try {
132 mTargetService.doBackup(savedState, backupData, newState);
133 } finally {
134 savedState.close();
135 backupData.close();
136 newState.close();
137 }
Christopher Tate22b87872009-05-04 16:41:53 -0700138
139 // !!! TODO: Now propagate the newly-backed-up data to the transport
140
Christopher Tatef4172472009-05-05 15:50:03 -0700141 // !!! TODO: After successful transport, delete the now-stale data
142 // and juggle the files so that next time the new state is passed
143 backupDataName.delete();
144 newStateName.renameTo(savedStateName);
Christopher Tate22b87872009-05-04 16:41:53 -0700145
146 } catch (FileNotFoundException fnf) {
147 Log.d(TAG, "File not found on backup: ");
148 fnf.printStackTrace();
Christopher Tate487529a2009-04-29 14:03:25 -0700149 } catch (RemoteException e) {
150 Log.d(TAG, "Remote target " + backupIntent
151 + " threw during backup:");
152 e.printStackTrace();
Christopher Tate22b87872009-05-04 16:41:53 -0700153 } catch (Exception e) {
154 Log.w(TAG, "Final exception guard in backup: ");
155 e.printStackTrace();
Christopher Tate487529a2009-04-29 14:03:25 -0700156 }
157 mContext.unbindService(this);
158 }
159 } else {
160 Log.d(TAG, "Unable to bind to " + backupIntent);
161 }
162 }
163 }
164 break;
165 }
166 }
167
168 public void onServiceConnected(ComponentName name, IBinder service) {
169 synchronized (mBindSignaller) {
170 mTargetService = IBackupService.Stub.asInterface(service);
171 mBinding = false;
172 mBindSignaller.notifyAll();
173 }
174 }
175
176 public void onServiceDisconnected(ComponentName name) {
177 synchronized (mBindSignaller) {
178 mTargetService = null;
179 mBinding = false;
180 mBindSignaller.notifyAll();
181 }
182 }
183 }
184
185 public BackupManagerService(Context context) {
186 mContext = context;
187 mPackageManager = context.getPackageManager();
188
Christopher Tate22b87872009-05-04 16:41:53 -0700189 // Set up our bookkeeping
Christopher Tatef4172472009-05-05 15:50:03 -0700190 mStateDir = new File(Environment.getDataDirectory(), "backup");
Christopher Tate22b87872009-05-04 16:41:53 -0700191 mStateDir.mkdirs();
Christopher Tatef4172472009-05-05 15:50:03 -0700192 mDataDir = Environment.getDownloadCacheDirectory();
Christopher Tate22b87872009-05-04 16:41:53 -0700193
Christopher Tate487529a2009-04-29 14:03:25 -0700194 // Identify the backup participants
195 // !!! TODO: also watch package-install to keep this up to date
196 List<ResolveInfo> services = mPackageManager.queryIntentServices(
197 new Intent(BackupService.SERVICE_ACTION), 0);
198 if (DEBUG) {
199 Log.v(TAG, "Backup participants: " + services.size());
200 for (ResolveInfo ri : services) {
201 Log.v(TAG, " " + ri + " : " + ri.filter);
202 }
203 }
204
205 // Build our mapping of uid to backup client services
206 for (ResolveInfo ri : services) {
207 int uid = ri.serviceInfo.applicationInfo.uid;
208 HashSet<ServiceInfo> set = mBackupParticipants.get(uid);
209 if (set == null) {
210 set = new HashSet<ServiceInfo>();
211 mBackupParticipants.put(uid, set);
212 }
213 set.add(ri.serviceInfo);
214 }
215 }
216
217
218 // ----- IBackupManager binder interface -----
219
Christopher Tatea8bf8152009-04-30 11:36:21 -0700220 public void dataChanged(String packageName) throws RemoteException {
Christopher Tate487529a2009-04-29 14:03:25 -0700221 // Record that we need a backup pass for the caller. Since multiple callers
222 // may share a uid, we need to note all candidates within that uid and schedule
223 // a backup pass for each of them.
224
225 HashSet<ServiceInfo> targets = mBackupParticipants.get(Binder.getCallingUid());
226 if (targets != null) {
227 synchronized (mQueueLock) {
228 // Note that this client has made data changes that need to be backed up
Christopher Tate487529a2009-04-29 14:03:25 -0700229 for (ServiceInfo service : targets) {
Christopher Tatea8bf8152009-04-30 11:36:21 -0700230 // validate the caller-supplied package name against the known set of
231 // packages associated with this uid
232 if (service.packageName.equals(packageName)) {
233 // add the caller to the set of pending backups
234 if (mPendingBackups.add(service)) {
235 // !!! TODO: write to the pending-backup journal file in case of crash
236 }
Christopher Tate487529a2009-04-29 14:03:25 -0700237 }
238 }
239
240 // Schedule a backup pass in a few minutes. As backup-eligible data
241 // keeps changing, continue to defer the backup pass until things
242 // settle down, to avoid extra overhead.
243 mBackupHandler.removeMessages(MSG_RUN_BACKUP);
244 mBackupHandler.sendEmptyMessageDelayed(MSG_RUN_BACKUP, COLLECTION_INTERVAL);
245 }
246 }
247 }
248}