blob: 3b82284c0c4bc91adc2bff72f5cdcd98178d0e99 [file] [log] [blame]
Jean-Baptiste Queru843ef362009-05-20 11:28:04 -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
Jean-Baptiste Querucf4550c2009-07-21 11:16:54 -070019import android.app.ActivityManagerNative;
20import android.app.AlarmManager;
21import android.app.IActivityManager;
22import android.app.IApplicationThread;
23import android.app.IBackupAgent;
24import android.app.PendingIntent;
Jean-Baptiste Queru843ef362009-05-20 11:28:04 -070025import android.content.BroadcastReceiver;
26import android.content.ComponentName;
27import android.content.Context;
28import android.content.Intent;
29import android.content.IntentFilter;
30import android.content.ServiceConnection;
Jean-Baptiste Querucf4550c2009-07-21 11:16:54 -070031import android.content.pm.ApplicationInfo;
32import android.content.pm.IPackageDataObserver;
33import android.content.pm.PackageInfo;
34import android.content.pm.PackageManager.NameNotFoundException;
Jean-Baptiste Queru843ef362009-05-20 11:28:04 -070035import android.content.pm.PackageManager;
Jean-Baptiste Querucf4550c2009-07-21 11:16:54 -070036import android.content.pm.Signature;
Jean-Baptiste Queru843ef362009-05-20 11:28:04 -070037import android.net.Uri;
Jean-Baptiste Querucf4550c2009-07-21 11:16:54 -070038import android.provider.Settings;
Jean-Baptiste Queru843ef362009-05-20 11:28:04 -070039import android.os.Binder;
40import android.os.Bundle;
41import android.os.Environment;
42import android.os.Handler;
43import android.os.IBinder;
44import android.os.Message;
45import android.os.ParcelFileDescriptor;
Jean-Baptiste Querucf4550c2009-07-21 11:16:54 -070046import android.os.PowerManager;
47import android.os.Process;
Jean-Baptiste Queru843ef362009-05-20 11:28:04 -070048import android.os.RemoteException;
49import android.util.Log;
50import android.util.SparseArray;
51
52import android.backup.IBackupManager;
Jean-Baptiste Querucf4550c2009-07-21 11:16:54 -070053import android.backup.IRestoreObserver;
54import android.backup.IRestoreSession;
55import android.backup.RestoreSet;
Jean-Baptiste Queru843ef362009-05-20 11:28:04 -070056
Jean-Baptiste Querucf4550c2009-07-21 11:16:54 -070057import com.android.internal.backup.LocalTransport;
58import com.android.internal.backup.IBackupTransport;
59
60import com.android.server.PackageManagerBackupAgent;
61import com.android.server.PackageManagerBackupAgent.Metadata;
62
63import java.io.EOFException;
Jean-Baptiste Queru843ef362009-05-20 11:28:04 -070064import java.io.File;
65import java.io.FileDescriptor;
Jean-Baptiste Querucf4550c2009-07-21 11:16:54 -070066import java.io.IOException;
Jean-Baptiste Queru843ef362009-05-20 11:28:04 -070067import java.io.PrintWriter;
Jean-Baptiste Querucf4550c2009-07-21 11:16:54 -070068import java.io.RandomAccessFile;
Jean-Baptiste Queru843ef362009-05-20 11:28:04 -070069import java.lang.String;
70import java.util.ArrayList;
71import java.util.HashMap;
72import java.util.HashSet;
73import java.util.List;
Jean-Baptiste Querucf4550c2009-07-21 11:16:54 -070074import java.util.Map;
Jean-Baptiste Queru843ef362009-05-20 11:28:04 -070075
76class BackupManagerService extends IBackupManager.Stub {
77 private static final String TAG = "BackupManagerService";
78 private static final boolean DEBUG = true;
Jean-Baptiste Queru843ef362009-05-20 11:28:04 -070079
Jean-Baptiste Querucf4550c2009-07-21 11:16:54 -070080 // How often we perform a backup pass. Privileged external callers can
81 // trigger an immediate pass.
82 private static final long BACKUP_INTERVAL = AlarmManager.INTERVAL_HOUR;
83
84 // The amount of time between the initial provisioning of the device and
85 // the first backup pass.
86 private static final long FIRST_BACKUP_INTERVAL = 12 * AlarmManager.INTERVAL_HOUR;
87
88 private static final String RUN_BACKUP_ACTION = "_backup_run_";
Jean-Baptiste Queru843ef362009-05-20 11:28:04 -070089 private static final int MSG_RUN_BACKUP = 1;
Jean-Baptiste Querucf4550c2009-07-21 11:16:54 -070090 private static final int MSG_RUN_FULL_BACKUP = 2;
91 private static final int MSG_RUN_RESTORE = 3;
92 private static final int MSG_RUN_CLEAR = 4;
93
94 // Timeout interval for deciding that a bind or clear-data has taken too long
95 static final long TIMEOUT_INTERVAL = 10 * 1000;
96
Jean-Baptiste Queru843ef362009-05-20 11:28:04 -070097 private Context mContext;
98 private PackageManager mPackageManager;
Jean-Baptiste Querucf4550c2009-07-21 11:16:54 -070099 private IActivityManager mActivityManager;
100 private PowerManager mPowerManager;
101 private AlarmManager mAlarmManager;
102
103 private boolean mEnabled; // access to this is synchronized on 'this'
104 private boolean mProvisioned;
105 private PowerManager.WakeLock mWakelock;
Jean-Baptiste Queru843ef362009-05-20 11:28:04 -0700106 private final BackupHandler mBackupHandler = new BackupHandler();
Jean-Baptiste Querucf4550c2009-07-21 11:16:54 -0700107 private PendingIntent mRunBackupIntent;
108 private BroadcastReceiver mRunBackupReceiver;
109 private IntentFilter mRunBackupFilter;
Jean-Baptiste Queru843ef362009-05-20 11:28:04 -0700110 // map UIDs to the set of backup client services within that UID's app set
Jean-Baptiste Querucf4550c2009-07-21 11:16:54 -0700111 private final SparseArray<HashSet<ApplicationInfo>> mBackupParticipants
112 = new SparseArray<HashSet<ApplicationInfo>>();
Jean-Baptiste Queru843ef362009-05-20 11:28:04 -0700113 // set of backup services that have pending changes
114 private class BackupRequest {
Jean-Baptiste Querucf4550c2009-07-21 11:16:54 -0700115 public ApplicationInfo appInfo;
Jean-Baptiste Queru843ef362009-05-20 11:28:04 -0700116 public boolean fullBackup;
Jean-Baptiste Querucf4550c2009-07-21 11:16:54 -0700117
118 BackupRequest(ApplicationInfo app, boolean isFull) {
119 appInfo = app;
Jean-Baptiste Queru843ef362009-05-20 11:28:04 -0700120 fullBackup = isFull;
121 }
Jean-Baptiste Querucf4550c2009-07-21 11:16:54 -0700122
123 public String toString() {
124 return "BackupRequest{app=" + appInfo + " full=" + fullBackup + "}";
125 }
Jean-Baptiste Queru843ef362009-05-20 11:28:04 -0700126 }
127 // Backups that we haven't started yet.
Jean-Baptiste Querucf4550c2009-07-21 11:16:54 -0700128 private HashMap<ApplicationInfo,BackupRequest> mPendingBackups
129 = new HashMap<ApplicationInfo,BackupRequest>();
130
131 // Pseudoname that we use for the Package Manager metadata "package"
132 private static final String PACKAGE_MANAGER_SENTINEL = "@pm@";
133
134 // locking around the pending-backup management
Jean-Baptiste Queru843ef362009-05-20 11:28:04 -0700135 private final Object mQueueLock = new Object();
136
Jean-Baptiste Querucf4550c2009-07-21 11:16:54 -0700137 // The thread performing the sequence of queued backups binds to each app's agent
138 // in succession. Bind notifications are asynchronously delivered through the
139 // Activity Manager; use this lock object to signal when a requested binding has
140 // completed.
141 private final Object mAgentConnectLock = new Object();
142 private IBackupAgent mConnectedAgent;
143 private volatile boolean mConnecting;
144
145 // A similar synchronicity mechanism around clearing apps' data for restore
146 private final Object mClearDataLock = new Object();
147 private volatile boolean mClearingData;
148
149 // Transport bookkeeping
150 private final HashMap<String,IBackupTransport> mTransports
151 = new HashMap<String,IBackupTransport>();
152 private String mCurrentTransport;
153 private IBackupTransport mLocalTransport, mGoogleTransport;
154 private RestoreSession mActiveRestoreSession;
155
156 private class RestoreParams {
157 public IBackupTransport transport;
158 public IRestoreObserver observer;
159 public long token;
160
161 RestoreParams(IBackupTransport _transport, IRestoreObserver _obs, long _token) {
162 transport = _transport;
163 observer = _obs;
164 token = _token;
165 }
166 }
167
168 private class ClearParams {
169 public IBackupTransport transport;
170 public PackageInfo packageInfo;
171
172 ClearParams(IBackupTransport _transport, PackageInfo _info) {
173 transport = _transport;
174 packageInfo = _info;
175 }
176 }
177
178 // Where we keep our journal files and other bookkeeping
179 private File mBaseStateDir;
Jean-Baptiste Queru843ef362009-05-20 11:28:04 -0700180 private File mDataDir;
Jean-Baptiste Querucf4550c2009-07-21 11:16:54 -0700181 private File mJournalDir;
182 private File mJournal;
183 private RandomAccessFile mJournalStream;
184
Jean-Baptiste Queru843ef362009-05-20 11:28:04 -0700185 public BackupManagerService(Context context) {
186 mContext = context;
187 mPackageManager = context.getPackageManager();
Jean-Baptiste Querucf4550c2009-07-21 11:16:54 -0700188 mActivityManager = ActivityManagerNative.getDefault();
189
190 mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
191 mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
Jean-Baptiste Queru843ef362009-05-20 11:28:04 -0700192
193 // Set up our bookkeeping
Jean-Baptiste Querucf4550c2009-07-21 11:16:54 -0700194 boolean areEnabled = Settings.Secure.getInt(context.getContentResolver(),
195 Settings.Secure.BACKUP_ENABLED, 0) != 0;
196 // !!! TODO: mProvisioned needs to default to 0, not 1.
197 mProvisioned = Settings.Secure.getInt(context.getContentResolver(),
198 Settings.Secure.BACKUP_PROVISIONED, 1) != 0;
199 mBaseStateDir = new File(Environment.getDataDirectory(), "backup");
Jean-Baptiste Queru843ef362009-05-20 11:28:04 -0700200 mDataDir = Environment.getDownloadCacheDirectory();
Jean-Baptiste Querucf4550c2009-07-21 11:16:54 -0700201
202 mRunBackupReceiver = new RunBackupReceiver();
203 mRunBackupFilter = new IntentFilter();
204 mRunBackupFilter.addAction(RUN_BACKUP_ACTION);
205 context.registerReceiver(mRunBackupReceiver, mRunBackupFilter);
206
207 Intent backupIntent = new Intent(RUN_BACKUP_ACTION);
208 // !!! TODO: restrict delivery to our receiver; the naive setClass() doesn't seem to work
209 //backupIntent.setClass(context, mRunBackupReceiver.getClass());
210 backupIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
211 mRunBackupIntent = PendingIntent.getBroadcast(context, MSG_RUN_BACKUP, backupIntent, 0);
212
213 // Set up the backup-request journaling
214 mJournalDir = new File(mBaseStateDir, "pending");
215 mJournalDir.mkdirs(); // creates mBaseStateDir along the way
216 makeJournalLocked(); // okay because no other threads are running yet
217
218 // Build our mapping of uid to backup client services. This implicitly
219 // schedules a backup pass on the Package Manager metadata the first
220 // time anything needs to be backed up.
Jean-Baptiste Queru843ef362009-05-20 11:28:04 -0700221 synchronized (mBackupParticipants) {
222 addPackageParticipantsLocked(null);
223 }
224
Jean-Baptiste Querucf4550c2009-07-21 11:16:54 -0700225 // Set up our transport options and initialize the default transport
226 // TODO: Have transports register themselves somehow?
227 // TODO: Don't create transports that we don't need to?
228 mLocalTransport = new LocalTransport(context); // This is actually pretty cheap
229 ComponentName localName = new ComponentName(context, LocalTransport.class);
230 registerTransport(localName.flattenToShortString(), mLocalTransport);
231
232 mGoogleTransport = null;
233 mCurrentTransport = Settings.Secure.getString(context.getContentResolver(),
234 Settings.Secure.BACKUP_TRANSPORT);
235 if ("".equals(mCurrentTransport)) {
236 mCurrentTransport = null;
237 }
238 if (DEBUG) Log.v(TAG, "Starting with transport " + mCurrentTransport);
239
240 // Attach to the Google backup transport. When this comes up, it will set
241 // itself as the current transport because we explicitly reset mCurrentTransport
242 // to null.
243 Intent intent = new Intent().setComponent(new ComponentName(
244 "com.google.android.backup",
245 "com.google.android.backup.BackupTransportService"));
246 context.bindService(intent, mGoogleConnection, Context.BIND_AUTO_CREATE);
247
248 // Now that we know about valid backup participants, parse any
249 // leftover journal files into the pending backup set
250 parseLeftoverJournals();
251
Jean-Baptiste Queru843ef362009-05-20 11:28:04 -0700252 // Register for broadcasts about package install, etc., so we can
253 // update the provider list.
254 IntentFilter filter = new IntentFilter();
255 filter.addAction(Intent.ACTION_PACKAGE_ADDED);
256 filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
257 filter.addDataScheme("package");
258 mContext.registerReceiver(mBroadcastReceiver, filter);
Jean-Baptiste Querucf4550c2009-07-21 11:16:54 -0700259
260 // Power management
261 mWakelock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "backup");
262
263 // Start the backup passes going
264 setBackupEnabled(areEnabled);
265 }
266
267 private class RunBackupReceiver extends BroadcastReceiver {
268 public void onReceive(Context context, Intent intent) {
269 if (RUN_BACKUP_ACTION.equals(intent.getAction())) {
270 if (DEBUG) Log.v(TAG, "Running a backup pass");
271
272 synchronized (mQueueLock) {
273 // acquire a wakelock and pass it to the backup thread. it will
274 // be released once backup concludes.
275 mWakelock.acquire();
276
277 Message msg = mBackupHandler.obtainMessage(MSG_RUN_BACKUP);
278 mBackupHandler.sendMessage(msg);
279 }
280 }
281 }
282 }
283
284 private void makeJournalLocked() {
285 try {
286 mJournal = File.createTempFile("journal", null, mJournalDir);
287 mJournalStream = new RandomAccessFile(mJournal, "rwd");
288 } catch (IOException e) {
289 Log.e(TAG, "Unable to write backup journals");
290 mJournal = null;
291 mJournalStream = null;
292 }
293 }
294
295 private void parseLeftoverJournals() {
296 if (mJournal != null) {
297 File[] allJournals = mJournalDir.listFiles();
298 for (File f : allJournals) {
299 if (f.compareTo(mJournal) != 0) {
300 // This isn't the current journal, so it must be a leftover. Read
301 // out the package names mentioned there and schedule them for
302 // backup.
303 try {
304 Log.i(TAG, "Found stale backup journal, scheduling:");
305 RandomAccessFile in = new RandomAccessFile(f, "r");
306 while (true) {
307 String packageName = in.readUTF();
308 Log.i(TAG, " + " + packageName);
309 dataChanged(packageName);
310 }
311 } catch (EOFException e) {
312 // no more data; we're done
313 } catch (Exception e) {
314 // can't read it or other error; just skip it
315 } finally {
316 // close/delete the file
317 f.delete();
318 }
319 }
320 }
321 }
322 }
323
324 // Add a transport to our set of available backends
325 private void registerTransport(String name, IBackupTransport transport) {
326 synchronized (mTransports) {
327 if (DEBUG) Log.v(TAG, "Registering transport " + name + " = " + transport);
328 mTransports.put(name, transport);
329 }
Jean-Baptiste Queru843ef362009-05-20 11:28:04 -0700330 }
331
332 // ----- Track installation/removal of packages -----
333 BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
334 public void onReceive(Context context, Intent intent) {
335 if (DEBUG) Log.d(TAG, "Received broadcast " + intent);
336
337 Uri uri = intent.getData();
338 if (uri == null) {
339 return;
340 }
341 String pkgName = uri.getSchemeSpecificPart();
342 if (pkgName == null) {
343 return;
344 }
345
346 String action = intent.getAction();
347 if (Intent.ACTION_PACKAGE_ADDED.equals(action)) {
348 synchronized (mBackupParticipants) {
349 Bundle extras = intent.getExtras();
350 if (extras != null && extras.getBoolean(Intent.EXTRA_REPLACING, false)) {
351 // The package was just upgraded
352 updatePackageParticipantsLocked(pkgName);
353 } else {
354 // The package was just added
355 addPackageParticipantsLocked(pkgName);
356 }
357 }
358 }
359 else if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) {
360 Bundle extras = intent.getExtras();
361 if (extras != null && extras.getBoolean(Intent.EXTRA_REPLACING, false)) {
362 // The package is being updated. We'll receive a PACKAGE_ADDED shortly.
363 } else {
364 synchronized (mBackupParticipants) {
365 removePackageParticipantsLocked(pkgName);
366 }
367 }
368 }
369 }
370 };
371
Jean-Baptiste Querucf4550c2009-07-21 11:16:54 -0700372 // ----- Track connection to GoogleBackupTransport service -----
373 ServiceConnection mGoogleConnection = new ServiceConnection() {
374 public void onServiceConnected(ComponentName name, IBinder service) {
375 if (DEBUG) Log.v(TAG, "Connected to Google transport");
376 mGoogleTransport = IBackupTransport.Stub.asInterface(service);
377 registerTransport(name.flattenToShortString(), mGoogleTransport);
378 }
379
380 public void onServiceDisconnected(ComponentName name) {
381 if (DEBUG) Log.v(TAG, "Disconnected from Google transport");
382 mGoogleTransport = null;
383 registerTransport(name.flattenToShortString(), null);
384 }
385 };
386
Jean-Baptiste Queru843ef362009-05-20 11:28:04 -0700387 // ----- Run the actual backup process asynchronously -----
388
Jean-Baptiste Querucf4550c2009-07-21 11:16:54 -0700389 private class BackupHandler extends Handler {
Jean-Baptiste Queru843ef362009-05-20 11:28:04 -0700390 public void handleMessage(Message msg) {
391
392 switch (msg.what) {
393 case MSG_RUN_BACKUP:
Jean-Baptiste Querucf4550c2009-07-21 11:16:54 -0700394 {
395 IBackupTransport transport = getTransport(mCurrentTransport);
396 if (transport == null) {
397 Log.v(TAG, "Backup requested but no transport available");
398 mWakelock.release();
399 break;
Jean-Baptiste Queru843ef362009-05-20 11:28:04 -0700400 }
Jean-Baptiste Querucf4550c2009-07-21 11:16:54 -0700401
402 // snapshot the pending-backup set and work on that
403 ArrayList<BackupRequest> queue = new ArrayList<BackupRequest>();
404 File oldJournal = mJournal;
405 synchronized (mQueueLock) {
406 // Do we have any work to do?
407 if (mPendingBackups.size() > 0) {
408 for (BackupRequest b: mPendingBackups.values()) {
409 queue.add(b);
410 }
411 Log.v(TAG, "clearing pending backups");
412 mPendingBackups.clear();
413
414 // Start a new backup-queue journal file too
415 if (mJournalStream != null) {
416 try {
417 mJournalStream.close();
418 } catch (IOException e) {
419 // don't need to do anything
420 }
421 makeJournalLocked();
422 }
423
424 // At this point, we have started a new journal file, and the old
425 // file identity is being passed to the backup processing thread.
426 // When it completes successfully, that old journal file will be
427 // deleted. If we crash prior to that, the old journal is parsed
428 // at next boot and the journaled requests fulfilled.
429 (new PerformBackupThread(transport, queue, oldJournal)).start();
430 } else {
431 Log.v(TAG, "Backup requested but nothing pending");
432 mWakelock.release();
433 }
434 }
Jean-Baptiste Queru843ef362009-05-20 11:28:04 -0700435 break;
436 }
Jean-Baptiste Queru843ef362009-05-20 11:28:04 -0700437
Jean-Baptiste Querucf4550c2009-07-21 11:16:54 -0700438 case MSG_RUN_FULL_BACKUP:
439 break;
Jean-Baptiste Queru843ef362009-05-20 11:28:04 -0700440
Jean-Baptiste Querucf4550c2009-07-21 11:16:54 -0700441 case MSG_RUN_RESTORE:
442 {
443 RestoreParams params = (RestoreParams)msg.obj;
444 Log.d(TAG, "MSG_RUN_RESTORE observer=" + params.observer);
445 (new PerformRestoreThread(params.transport, params.observer,
446 params.token)).start();
447 break;
Jean-Baptiste Queru843ef362009-05-20 11:28:04 -0700448 }
Jean-Baptiste Querucf4550c2009-07-21 11:16:54 -0700449
450 case MSG_RUN_CLEAR:
451 {
452 ClearParams params = (ClearParams)msg.obj;
453 (new PerformClearThread(params.transport, params.packageInfo)).start();
454 break;
455 }
Jean-Baptiste Queru843ef362009-05-20 11:28:04 -0700456 }
457 }
458 }
459
Jean-Baptiste Querucf4550c2009-07-21 11:16:54 -0700460 // Add the backup agents in the given package to our set of known backup participants.
461 // If 'packageName' is null, adds all backup agents in the whole system.
Jean-Baptiste Queru843ef362009-05-20 11:28:04 -0700462 void addPackageParticipantsLocked(String packageName) {
Jean-Baptiste Querucf4550c2009-07-21 11:16:54 -0700463 // Look for apps that define the android:backupAgent attribute
464 if (DEBUG) Log.v(TAG, "addPackageParticipantsLocked: " + packageName);
465 List<PackageInfo> targetApps = allAgentPackages();
466 addPackageParticipantsLockedInner(packageName, targetApps);
Jean-Baptiste Queru843ef362009-05-20 11:28:04 -0700467 }
468
Jean-Baptiste Querucf4550c2009-07-21 11:16:54 -0700469 private void addPackageParticipantsLockedInner(String packageName,
470 List<PackageInfo> targetPkgs) {
471 if (DEBUG) {
472 Log.v(TAG, "Adding " + targetPkgs.size() + " backup participants:");
473 for (PackageInfo p : targetPkgs) {
474 Log.v(TAG, " " + p + " agent=" + p.applicationInfo.backupAgentName
475 + " uid=" + p.applicationInfo.uid);
476 }
477 }
478
479 for (PackageInfo pkg : targetPkgs) {
480 if (packageName == null || pkg.packageName.equals(packageName)) {
481 int uid = pkg.applicationInfo.uid;
482 HashSet<ApplicationInfo> set = mBackupParticipants.get(uid);
Jean-Baptiste Queru843ef362009-05-20 11:28:04 -0700483 if (set == null) {
Jean-Baptiste Querucf4550c2009-07-21 11:16:54 -0700484 set = new HashSet<ApplicationInfo>();
Jean-Baptiste Queru843ef362009-05-20 11:28:04 -0700485 mBackupParticipants.put(uid, set);
486 }
Jean-Baptiste Querucf4550c2009-07-21 11:16:54 -0700487 set.add(pkg.applicationInfo);
Jean-Baptiste Queru843ef362009-05-20 11:28:04 -0700488 }
489 }
490 }
491
Jean-Baptiste Querucf4550c2009-07-21 11:16:54 -0700492 // Remove the given package's entry from our known active set. If
493 // 'packageName' is null, *all* participating apps will be removed.
Jean-Baptiste Queru843ef362009-05-20 11:28:04 -0700494 void removePackageParticipantsLocked(String packageName) {
Jean-Baptiste Querucf4550c2009-07-21 11:16:54 -0700495 if (DEBUG) Log.v(TAG, "removePackageParticipantsLocked: " + packageName);
496 List<PackageInfo> allApps = null;
497 if (packageName != null) {
498 allApps = new ArrayList<PackageInfo>();
499 try {
500 int flags = PackageManager.GET_SIGNATURES;
501 allApps.add(mPackageManager.getPackageInfo(packageName, flags));
502 } catch (Exception e) {
503 // just skip it (???)
504 }
505 } else {
506 // all apps with agents
507 allApps = allAgentPackages();
508 }
509 removePackageParticipantsLockedInner(packageName, allApps);
Jean-Baptiste Queru843ef362009-05-20 11:28:04 -0700510 }
511
512 private void removePackageParticipantsLockedInner(String packageName,
Jean-Baptiste Querucf4550c2009-07-21 11:16:54 -0700513 List<PackageInfo> agents) {
514 if (DEBUG) {
515 Log.v(TAG, "removePackageParticipantsLockedInner (" + packageName
516 + ") removing " + agents.size() + " entries");
517 for (PackageInfo p : agents) {
518 Log.v(TAG, " - " + p);
519 }
520 }
521 for (PackageInfo pkg : agents) {
522 if (packageName == null || pkg.packageName.equals(packageName)) {
523 int uid = pkg.applicationInfo.uid;
524 HashSet<ApplicationInfo> set = mBackupParticipants.get(uid);
Jean-Baptiste Queru843ef362009-05-20 11:28:04 -0700525 if (set != null) {
Jean-Baptiste Querucf4550c2009-07-21 11:16:54 -0700526 // Find the existing entry with the same package name, and remove it.
527 // We can't just remove(app) because the instances are different.
528 for (ApplicationInfo entry: set) {
529 if (entry.packageName.equals(pkg.packageName)) {
530 set.remove(entry);
531 break;
532 }
533 }
Jean-Baptiste Queru843ef362009-05-20 11:28:04 -0700534 if (set.size() == 0) {
Jean-Baptiste Querucf4550c2009-07-21 11:16:54 -0700535 mBackupParticipants.delete(uid);
Jean-Baptiste Queru843ef362009-05-20 11:28:04 -0700536 }
537 }
538 }
539 }
540 }
541
Jean-Baptiste Querucf4550c2009-07-21 11:16:54 -0700542 // Returns the set of all applications that define an android:backupAgent attribute
543 private List<PackageInfo> allAgentPackages() {
544 // !!! TODO: cache this and regenerate only when necessary
545 int flags = PackageManager.GET_SIGNATURES;
546 List<PackageInfo> packages = mPackageManager.getInstalledPackages(flags);
547 int N = packages.size();
548 for (int a = N-1; a >= 0; a--) {
549 ApplicationInfo app = packages.get(a).applicationInfo;
550 if (((app.flags&ApplicationInfo.FLAG_ALLOW_BACKUP) == 0)
551 || app.backupAgentName == null) {
552 packages.remove(a);
553 }
554 }
555 return packages;
556 }
557
Jean-Baptiste Queru843ef362009-05-20 11:28:04 -0700558 // Reset the given package's known backup participants. Unlike add/remove, the update
559 // action cannot be passed a null package name.
560 void updatePackageParticipantsLocked(String packageName) {
561 if (packageName == null) {
562 Log.e(TAG, "updatePackageParticipants called with null package name");
563 return;
564 }
Jean-Baptiste Querucf4550c2009-07-21 11:16:54 -0700565 if (DEBUG) Log.v(TAG, "updatePackageParticipantsLocked: " + packageName);
Jean-Baptiste Queru843ef362009-05-20 11:28:04 -0700566
567 // brute force but small code size
Jean-Baptiste Querucf4550c2009-07-21 11:16:54 -0700568 List<PackageInfo> allApps = allAgentPackages();
569 removePackageParticipantsLockedInner(packageName, allApps);
570 addPackageParticipantsLockedInner(packageName, allApps);
Jean-Baptiste Queru843ef362009-05-20 11:28:04 -0700571 }
572
Jean-Baptiste Querucf4550c2009-07-21 11:16:54 -0700573 // Return the given transport
574 private IBackupTransport getTransport(String transportName) {
575 synchronized (mTransports) {
576 IBackupTransport transport = mTransports.get(transportName);
577 if (transport == null) {
578 Log.w(TAG, "Requested unavailable transport: " + transportName);
579 }
580 return transport;
581 }
582 }
583
584 // fire off a backup agent, blocking until it attaches or times out
585 IBackupAgent bindToAgentSynchronous(ApplicationInfo app, int mode) {
586 IBackupAgent agent = null;
587 synchronized(mAgentConnectLock) {
588 mConnecting = true;
589 mConnectedAgent = null;
590 try {
591 if (mActivityManager.bindBackupAgent(app, mode)) {
592 Log.d(TAG, "awaiting agent for " + app);
593
594 // success; wait for the agent to arrive
595 // only wait 10 seconds for the clear data to happen
596 long timeoutMark = System.currentTimeMillis() + TIMEOUT_INTERVAL;
597 while (mConnecting && mConnectedAgent == null
598 && (System.currentTimeMillis() < timeoutMark)) {
599 try {
600 mAgentConnectLock.wait(5000);
601 } catch (InterruptedException e) {
602 // just bail
603 return null;
604 }
605 }
606
607 // if we timed out with no connect, abort and move on
608 if (mConnecting == true) {
609 Log.w(TAG, "Timeout waiting for agent " + app);
610 return null;
611 }
612 agent = mConnectedAgent;
613 }
614 } catch (RemoteException e) {
615 // can't happen
616 }
617 }
618 return agent;
619 }
620
621 // clear an application's data, blocking until the operation completes or times out
622 void clearApplicationDataSynchronous(String packageName) {
623 // Don't wipe packages marked allowClearUserData=false
624 try {
625 PackageInfo info = mPackageManager.getPackageInfo(packageName, 0);
626 if ((info.applicationInfo.flags & ApplicationInfo.FLAG_ALLOW_CLEAR_USER_DATA) == 0) {
627 if (DEBUG) Log.i(TAG, "allowClearUserData=false so not wiping "
628 + packageName);
629 return;
630 }
631 } catch (NameNotFoundException e) {
632 Log.w(TAG, "Tried to clear data for " + packageName + " but not found");
633 return;
634 }
635
636 ClearDataObserver observer = new ClearDataObserver();
637
638 synchronized(mClearDataLock) {
639 mClearingData = true;
640 mPackageManager.clearApplicationUserData(packageName, observer);
641
642 // only wait 10 seconds for the clear data to happen
643 long timeoutMark = System.currentTimeMillis() + TIMEOUT_INTERVAL;
644 while (mClearingData && (System.currentTimeMillis() < timeoutMark)) {
645 try {
646 mClearDataLock.wait(5000);
647 } catch (InterruptedException e) {
648 // won't happen, but still.
649 mClearingData = false;
650 }
651 }
652 }
653 }
654
655 class ClearDataObserver extends IPackageDataObserver.Stub {
656 public void onRemoveCompleted(String packageName, boolean succeeded)
657 throws android.os.RemoteException {
658 synchronized(mClearDataLock) {
659 mClearingData = false;
660 mClearDataLock.notifyAll();
661 }
662 }
663 }
664
665 // ----- Back up a set of applications via a worker thread -----
666
667 class PerformBackupThread extends Thread {
668 private static final String TAG = "PerformBackupThread";
669 IBackupTransport mTransport;
670 ArrayList<BackupRequest> mQueue;
671 File mStateDir;
672 File mJournal;
673
674 public PerformBackupThread(IBackupTransport transport, ArrayList<BackupRequest> queue,
675 File journal) {
676 mTransport = transport;
677 mQueue = queue;
678 mJournal = journal;
679
680 try {
681 mStateDir = new File(mBaseStateDir, transport.transportDirName());
682 } catch (RemoteException e) {
683 // can't happen; the transport is local
684 }
685 mStateDir.mkdirs();
686 }
687
688 @Override
689 public void run() {
690 if (DEBUG) Log.v(TAG, "Beginning backup of " + mQueue.size() + " targets");
691
692 // Backups run at background priority
693 Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
694
695 // The package manager doesn't have a proper <application> etc, but since
696 // it's running here in the system process we can just set up its agent
697 // directly and use a synthetic BackupRequest. We always run this pass
698 // because it's cheap and this way we guarantee that we don't get out of
699 // step even if we're selecting among various transports at run time.
700 PackageManagerBackupAgent pmAgent = new PackageManagerBackupAgent(
701 mPackageManager, allAgentPackages());
702 BackupRequest pmRequest = new BackupRequest(new ApplicationInfo(), false);
703 pmRequest.appInfo.packageName = PACKAGE_MANAGER_SENTINEL;
704 processOneBackup(pmRequest,
705 IBackupAgent.Stub.asInterface(pmAgent.onBind()),
706 mTransport);
707
708 // Now run all the backups in our queue
709 doQueuedBackups(mTransport);
710
711 // Finally, tear down the transport
712 try {
713 if (!mTransport.finishBackup()) {
714 // STOPSHIP TODO: handle errors
715 Log.e(TAG, "Backup failure in finishBackup()");
716 }
717 } catch (RemoteException e) {
718 Log.e(TAG, "Error in finishBackup()", e);
719 }
720
721 if (!mJournal.delete()) {
722 Log.e(TAG, "Unable to remove backup journal file " + mJournal.getAbsolutePath());
723 }
724
725 // Only once we're entirely finished do we release the wakelock
726 mWakelock.release();
727 }
728
729 private void doQueuedBackups(IBackupTransport transport) {
730 for (BackupRequest request : mQueue) {
731 Log.d(TAG, "starting agent for backup of " + request);
732
733 IBackupAgent agent = null;
734 int mode = (request.fullBackup)
735 ? IApplicationThread.BACKUP_MODE_FULL
736 : IApplicationThread.BACKUP_MODE_INCREMENTAL;
737 try {
738 agent = bindToAgentSynchronous(request.appInfo, mode);
739 if (agent != null) {
740 processOneBackup(request, agent, transport);
741 }
742
743 // unbind even on timeout, just in case
744 mActivityManager.unbindBackupAgent(request.appInfo);
745 } catch (SecurityException ex) {
746 // Try for the next one.
747 Log.d(TAG, "error in bind/backup", ex);
748 } catch (RemoteException e) {
749 Log.v(TAG, "bind/backup threw");
750 e.printStackTrace();
751 }
752
753 }
754 }
755
756 void processOneBackup(BackupRequest request, IBackupAgent agent, IBackupTransport transport) {
757 final String packageName = request.appInfo.packageName;
758 Log.d(TAG, "processOneBackup doBackup() on " + packageName);
759
760 try {
761 // Look up the package info & signatures. This is first so that if it
762 // throws an exception, there's no file setup yet that would need to
763 // be unraveled.
764 PackageInfo packInfo;
765 if (packageName.equals(PACKAGE_MANAGER_SENTINEL)) {
766 // The metadata 'package' is synthetic
767 packInfo = new PackageInfo();
768 packInfo.packageName = packageName;
769 } else {
770 packInfo = mPackageManager.getPackageInfo(packageName,
771 PackageManager.GET_SIGNATURES);
772 }
773
774 // !!! TODO: get the state file dir from the transport
775 File savedStateName = new File(mStateDir, packageName);
776 File backupDataName = new File(mDataDir, packageName + ".data");
777 File newStateName = new File(mStateDir, packageName + ".new");
778
779 // In a full backup, we pass a null ParcelFileDescriptor as
780 // the saved-state "file"
781 ParcelFileDescriptor savedState = (request.fullBackup) ? null
782 : ParcelFileDescriptor.open(savedStateName,
783 ParcelFileDescriptor.MODE_READ_ONLY |
784 ParcelFileDescriptor.MODE_CREATE);
785
786 backupDataName.delete();
787 ParcelFileDescriptor backupData =
788 ParcelFileDescriptor.open(backupDataName,
789 ParcelFileDescriptor.MODE_READ_WRITE |
790 ParcelFileDescriptor.MODE_CREATE);
791
792 newStateName.delete();
793 ParcelFileDescriptor newState =
794 ParcelFileDescriptor.open(newStateName,
795 ParcelFileDescriptor.MODE_READ_WRITE |
796 ParcelFileDescriptor.MODE_CREATE);
797
798 // Run the target's backup pass
799 boolean success = false;
800 try {
801 agent.doBackup(savedState, backupData, newState);
802 success = true;
803 } finally {
804 if (savedState != null) {
805 savedState.close();
806 }
807 backupData.close();
808 newState.close();
809 }
810
811 // Now propagate the newly-backed-up data to the transport
812 if (success) {
813 if (DEBUG) Log.v(TAG, "doBackup() success; calling transport");
814 backupData =
815 ParcelFileDescriptor.open(backupDataName, ParcelFileDescriptor.MODE_READ_ONLY);
816 if (!transport.performBackup(packInfo, backupData)) {
817 // STOPSHIP TODO: handle errors
818 Log.e(TAG, "Backup failure in performBackup()");
819 }
820
821 // !!! TODO: After successful transport, delete the now-stale data
822 // and juggle the files so that next time the new state is passed
823 //backupDataName.delete();
824 newStateName.renameTo(savedStateName);
825 }
826 } catch (Exception e) {
827 Log.e(TAG, "Error backing up " + packageName, e);
828 }
829 }
830 }
831
832
833 // ----- Restore handling -----
834
835 private boolean signaturesMatch(Signature[] storedSigs, Signature[] deviceSigs) {
836 // Allow unsigned apps, but not signed on one device and unsigned on the other
837 // !!! TODO: is this the right policy?
838 if (DEBUG) Log.v(TAG, "signaturesMatch(): stored=" + storedSigs
839 + " device=" + deviceSigs);
840 if ((storedSigs == null || storedSigs.length == 0)
841 && (deviceSigs == null || deviceSigs.length == 0)) {
842 return true;
843 }
844 if (storedSigs == null || deviceSigs == null) {
845 return false;
846 }
847
848 // !!! TODO: this demands that every stored signature match one
849 // that is present on device, and does not demand the converse.
850 // Is this this right policy?
851 int nStored = storedSigs.length;
852 int nDevice = deviceSigs.length;
853
854 for (int i=0; i < nStored; i++) {
855 boolean match = false;
856 for (int j=0; j < nDevice; j++) {
857 if (storedSigs[i].equals(deviceSigs[j])) {
858 match = true;
859 break;
860 }
861 }
862 if (!match) {
863 return false;
864 }
865 }
866 return true;
867 }
868
869 class PerformRestoreThread extends Thread {
870 private IBackupTransport mTransport;
871 private IRestoreObserver mObserver;
872 private long mToken;
873 private RestoreSet mImage;
874 private File mStateDir;
875
876 class RestoreRequest {
877 public PackageInfo app;
878 public int storedAppVersion;
879
880 RestoreRequest(PackageInfo _app, int _version) {
881 app = _app;
882 storedAppVersion = _version;
883 }
884 }
885
886 PerformRestoreThread(IBackupTransport transport, IRestoreObserver observer,
887 long restoreSetToken) {
888 mTransport = transport;
889 Log.d(TAG, "PerformRestoreThread mObserver=" + mObserver);
890 mObserver = observer;
891 mToken = restoreSetToken;
892
893 try {
894 mStateDir = new File(mBaseStateDir, transport.transportDirName());
895 } catch (RemoteException e) {
896 // can't happen; the transport is local
897 }
898 mStateDir.mkdirs();
899 }
900
901 @Override
902 public void run() {
903 if (DEBUG) Log.v(TAG, "Beginning restore process mTransport=" + mTransport
904 + " mObserver=" + mObserver + " mToken=" + mToken);
905 /**
906 * Restore sequence:
907 *
908 * 1. get the restore set description for our identity
909 * 2. for each app in the restore set:
910 * 3.a. if it's restorable on this device, add it to the restore queue
911 * 3. for each app in the restore queue:
912 * 3.a. clear the app data
913 * 3.b. get the restore data for the app from the transport
914 * 3.c. launch the backup agent for the app
915 * 3.d. agent.doRestore() with the data from the server
916 * 3.e. unbind the agent [and kill the app?]
917 * 4. shut down the transport
918 */
919
920 int error = -1; // assume error
921
922 // build the set of apps to restore
923 try {
924 RestoreSet[] images = mTransport.getAvailableRestoreSets();
925 if (images == null) {
926 // STOPSHIP TODO: Handle the failure somehow?
927 Log.e(TAG, "Error getting restore sets");
928 return;
929 }
930
931 if (images.length == 0) {
932 Log.i(TAG, "No restore sets available");
933 return;
934 }
935
936 mImage = images[0];
937
938 // Get the list of all packages which have backup enabled.
939 // (Include the Package Manager metadata pseudo-package first.)
940 ArrayList<PackageInfo> restorePackages = new ArrayList<PackageInfo>();
941 PackageInfo omPackage = new PackageInfo();
942 omPackage.packageName = PACKAGE_MANAGER_SENTINEL;
943 restorePackages.add(omPackage);
944
945 List<PackageInfo> agentPackages = allAgentPackages();
946 restorePackages.addAll(agentPackages);
947
948 // let the observer know that we're running
949 if (mObserver != null) {
950 try {
951 // !!! TODO: get an actual count from the transport after
952 // its startRestore() runs?
953 mObserver.restoreStarting(restorePackages.size());
954 } catch (RemoteException e) {
955 Log.d(TAG, "Restore observer died at restoreStarting");
956 mObserver = null;
957 }
958 }
959
960 if (!mTransport.startRestore(mToken, restorePackages.toArray(new PackageInfo[0]))) {
961 // STOPSHIP TODO: Handle the failure somehow?
962 Log.e(TAG, "Error starting restore operation");
963 return;
964 }
965
966 String packageName = mTransport.nextRestorePackage();
967 if (packageName == null) {
968 // STOPSHIP TODO: Handle the failure somehow?
969 Log.e(TAG, "Error getting first restore package");
970 return;
971 } else if (packageName.equals("")) {
972 Log.i(TAG, "No restore data available");
973 return;
974 } else if (!packageName.equals(PACKAGE_MANAGER_SENTINEL)) {
975 Log.e(TAG, "Expected restore data for \"" + PACKAGE_MANAGER_SENTINEL
976 + "\", found only \"" + packageName + "\"");
977 return;
978 }
979
980 // Pull the Package Manager metadata from the restore set first
981 PackageManagerBackupAgent pmAgent = new PackageManagerBackupAgent(
982 mPackageManager, agentPackages);
983 processOneRestore(omPackage, 0, IBackupAgent.Stub.asInterface(pmAgent.onBind()));
984
985 // Verify that the backup set includes metadata. If not, we can't do
986 // signature/version verification etc, so we simply do not proceed with
987 // the restore operation.
988 if (!pmAgent.hasMetadata()) {
989 Log.i(TAG, "No restore metadata available, so not restoring settings");
990 return;
991 }
992
993 int count = 0;
994 for (;;) {
995 packageName = mTransport.nextRestorePackage();
996 if (packageName == null) {
997 // STOPSHIP TODO: Handle the failure somehow?
998 Log.e(TAG, "Error getting next restore package");
999 return;
1000 } else if (packageName.equals("")) {
1001 break;
1002 }
1003
1004 if (mObserver != null) {
1005 ++count;
1006 try {
1007 mObserver.onUpdate(count);
1008 } catch (RemoteException e) {
1009 Log.d(TAG, "Restore observer died in onUpdate");
1010 mObserver = null;
1011 }
1012 }
1013
1014 Metadata metaInfo = pmAgent.getRestoredMetadata(packageName);
1015 if (metaInfo == null) {
1016 Log.e(TAG, "Missing metadata for " + packageName);
1017 continue;
1018 }
1019
1020 int flags = PackageManager.GET_SIGNATURES;
1021 PackageInfo packageInfo = mPackageManager.getPackageInfo(packageName, flags);
1022 if (metaInfo.versionCode > packageInfo.versionCode) {
1023 Log.w(TAG, "Package " + packageName
1024 + " restore version [" + metaInfo.versionCode
1025 + "] is too new for installed version ["
1026 + packageInfo.versionCode + "]");
1027 continue;
1028 }
1029
1030 if (!signaturesMatch(metaInfo.signatures, packageInfo.signatures)) {
1031 Log.w(TAG, "Signature mismatch restoring " + packageName);
1032 continue;
1033 }
1034
1035 if (DEBUG) Log.v(TAG, "Package " + packageName
1036 + " restore version [" + metaInfo.versionCode
1037 + "] is compatible with installed version ["
1038 + packageInfo.versionCode + "]");
1039
1040 // Now perform the actual restore
1041 clearApplicationDataSynchronous(packageName);
1042 IBackupAgent agent = bindToAgentSynchronous(
1043 packageInfo.applicationInfo,
1044 IApplicationThread.BACKUP_MODE_RESTORE);
1045 if (agent == null) {
1046 Log.w(TAG, "Can't find backup agent for " + packageName);
1047 continue;
1048 }
1049
1050 try {
1051 processOneRestore(packageInfo, metaInfo.versionCode, agent);
1052 } finally {
1053 // unbind even on timeout or failure, just in case
1054 mActivityManager.unbindBackupAgent(packageInfo.applicationInfo);
1055 }
1056 }
1057
1058 // if we get this far, report success to the observer
1059 error = 0;
1060 } catch (NameNotFoundException e) {
1061 // STOPSHIP TODO: Handle the failure somehow?
1062 Log.e(TAG, "Invalid paackage restoring data", e);
1063 } catch (RemoteException e) {
1064 // STOPSHIP TODO: Handle the failure somehow?
1065 Log.e(TAG, "Error restoring data", e);
1066 } finally {
1067 try {
1068 mTransport.finishRestore();
1069 } catch (RemoteException e) {
1070 Log.e(TAG, "Error finishing restore", e);
1071 }
1072
1073 Log.d(TAG, "finishing restore mObserver=" + mObserver);
1074
1075 if (mObserver != null) {
1076 try {
1077 mObserver.restoreFinished(error);
1078 } catch (RemoteException e) {
1079 Log.d(TAG, "Restore observer died at restoreFinished");
1080 }
1081 }
1082
1083 // done; we can finally release the wakelock
1084 mWakelock.release();
1085 }
1086 }
1087
1088 // Do the guts of a restore of one application, using mTransport.getRestoreData().
1089 void processOneRestore(PackageInfo app, int appVersionCode, IBackupAgent agent) {
1090 // !!! TODO: actually run the restore through mTransport
1091 final String packageName = app.packageName;
1092
1093 Log.d(TAG, "processOneRestore packageName=" + packageName);
1094
1095 // !!! TODO: get the dirs from the transport
1096 File backupDataName = new File(mDataDir, packageName + ".restore");
1097 backupDataName.delete();
1098 try {
1099 ParcelFileDescriptor backupData =
1100 ParcelFileDescriptor.open(backupDataName,
1101 ParcelFileDescriptor.MODE_READ_WRITE |
1102 ParcelFileDescriptor.MODE_CREATE);
1103
1104 // Run the transport's restore pass
1105 // Run the target's backup pass
1106 try {
1107 if (!mTransport.getRestoreData(backupData)) {
1108 // STOPSHIP TODO: Handle this error somehow?
1109 Log.e(TAG, "Error getting restore data for " + packageName);
1110 return;
1111 }
1112 } finally {
1113 backupData.close();
1114 }
1115
1116 // Okay, we have the data. Now have the agent do the restore.
1117 File newStateName = new File(mStateDir, packageName + ".new");
1118 ParcelFileDescriptor newState =
1119 ParcelFileDescriptor.open(newStateName,
1120 ParcelFileDescriptor.MODE_READ_WRITE |
1121 ParcelFileDescriptor.MODE_CREATE);
1122
1123 backupData = ParcelFileDescriptor.open(backupDataName,
1124 ParcelFileDescriptor.MODE_READ_ONLY);
1125
1126 try {
1127 agent.doRestore(backupData, appVersionCode, newState);
1128 } finally {
1129 newState.close();
1130 backupData.close();
1131 }
1132
1133 // if everything went okay, remember the recorded state now
1134 File savedStateName = new File(mStateDir, packageName);
1135 newStateName.renameTo(savedStateName);
1136 } catch (Exception e) {
1137 Log.e(TAG, "Error restoring data for " + packageName, e);
1138 }
1139 }
1140 }
1141
1142 class PerformClearThread extends Thread {
1143 IBackupTransport mTransport;
1144 PackageInfo mPackage;
1145
1146 PerformClearThread(IBackupTransport transport, PackageInfo packageInfo) {
1147 mTransport = transport;
1148 mPackage = packageInfo;
1149 }
1150
1151 @Override
1152 public void run() {
1153 try {
1154 // Clear the on-device backup state to ensure a full backup next time
1155 File stateDir = new File(mBaseStateDir, mTransport.transportDirName());
1156 File stateFile = new File(stateDir, mPackage.packageName);
1157 stateFile.delete();
1158
1159 // Tell the transport to remove all the persistent storage for the app
1160 mTransport.clearBackupData(mPackage);
1161 } catch (RemoteException e) {
1162 // can't happen; the transport is local
1163 } finally {
1164 try {
1165 mTransport.finishBackup();
1166 } catch (RemoteException e) {
1167 // can't happen; the transport is local
1168 }
1169
1170 // Last but not least, release the cpu
1171 mWakelock.release();
1172 }
1173 }
1174 }
1175
1176
Jean-Baptiste Queru843ef362009-05-20 11:28:04 -07001177 // ----- IBackupManager binder interface -----
Jean-Baptiste Querucf4550c2009-07-21 11:16:54 -07001178
Jean-Baptiste Queru843ef362009-05-20 11:28:04 -07001179 public void dataChanged(String packageName) throws RemoteException {
1180 // Record that we need a backup pass for the caller. Since multiple callers
1181 // may share a uid, we need to note all candidates within that uid and schedule
1182 // a backup pass for each of them.
1183
Jean-Baptiste Querucf4550c2009-07-21 11:16:54 -07001184 // If the caller does not hold the BACKUP permission, it can only request a
1185 // backup of its own data.
1186 HashSet<ApplicationInfo> targets;
1187 if ((mContext.checkPermission(android.Manifest.permission.BACKUP, Binder.getCallingPid(),
1188 Binder.getCallingUid())) == PackageManager.PERMISSION_DENIED) {
1189 targets = mBackupParticipants.get(Binder.getCallingUid());
1190 } else {
1191 // a caller with full permission can ask to back up any participating app
1192 // !!! TODO: allow backup of ANY app?
1193 targets = new HashSet<ApplicationInfo>();
1194 int N = mBackupParticipants.size();
1195 for (int i = 0; i < N; i++) {
1196 HashSet<ApplicationInfo> s = mBackupParticipants.valueAt(i);
1197 if (s != null) {
1198 targets.addAll(s);
1199 }
1200 }
1201 }
Jean-Baptiste Queru843ef362009-05-20 11:28:04 -07001202 if (targets != null) {
1203 synchronized (mQueueLock) {
1204 // Note that this client has made data changes that need to be backed up
Jean-Baptiste Querucf4550c2009-07-21 11:16:54 -07001205 for (ApplicationInfo app : targets) {
Jean-Baptiste Queru843ef362009-05-20 11:28:04 -07001206 // validate the caller-supplied package name against the known set of
1207 // packages associated with this uid
Jean-Baptiste Querucf4550c2009-07-21 11:16:54 -07001208 if (app.packageName.equals(packageName)) {
Jean-Baptiste Queru843ef362009-05-20 11:28:04 -07001209 // Add the caller to the set of pending backups. If there is
1210 // one already there, then overwrite it, but no harm done.
Jean-Baptiste Querucf4550c2009-07-21 11:16:54 -07001211 BackupRequest req = new BackupRequest(app, false);
1212 if (mPendingBackups.put(app, req) == null) {
1213 // Journal this request in case of crash. The put()
1214 // operation returned null when this package was not already
1215 // in the set; we want to avoid touching the disk redundantly.
1216 writeToJournalLocked(packageName);
1217
1218 if (DEBUG) {
1219 int numKeys = mPendingBackups.size();
1220 Log.d(TAG, "Now awaiting backup for " + numKeys + " participants:");
1221 for (BackupRequest b : mPendingBackups.values()) {
1222 Log.d(TAG, " + " + b + " agent=" + b.appInfo.backupAgentName);
1223 }
1224 }
1225 }
Jean-Baptiste Queru843ef362009-05-20 11:28:04 -07001226 }
1227 }
Jean-Baptiste Querucf4550c2009-07-21 11:16:54 -07001228 }
1229 } else {
1230 Log.w(TAG, "dataChanged but no participant pkg='" + packageName + "'");
1231 }
1232 }
Jean-Baptiste Queru843ef362009-05-20 11:28:04 -07001233
Jean-Baptiste Querucf4550c2009-07-21 11:16:54 -07001234 private void writeToJournalLocked(String str) {
1235 if (mJournalStream != null) {
1236 try {
1237 mJournalStream.writeUTF(str);
1238 } catch (IOException e) {
1239 Log.e(TAG, "Error writing to backup journal");
1240 mJournalStream = null;
1241 mJournal = null;
Jean-Baptiste Queru843ef362009-05-20 11:28:04 -07001242 }
1243 }
1244 }
1245
Jean-Baptiste Querucf4550c2009-07-21 11:16:54 -07001246 // Clear the given package's backup data from the current transport
1247 public void clearBackupData(String packageName) {
1248 if (DEBUG) Log.v(TAG, "clearBackupData() of " + packageName);
1249 PackageInfo info;
1250 try {
1251 info = mPackageManager.getPackageInfo(packageName, PackageManager.GET_SIGNATURES);
1252 } catch (NameNotFoundException e) {
1253 Log.d(TAG, "No such package '" + packageName + "' - not clearing backup data");
1254 return;
1255 }
1256
1257 // If the caller does not hold the BACKUP permission, it can only request a
1258 // wipe of its own backed-up data.
1259 HashSet<ApplicationInfo> apps;
1260 if ((mContext.checkPermission(android.Manifest.permission.BACKUP, Binder.getCallingPid(),
1261 Binder.getCallingUid())) == PackageManager.PERMISSION_DENIED) {
1262 apps = mBackupParticipants.get(Binder.getCallingUid());
1263 } else {
1264 // a caller with full permission can ask to back up any participating app
1265 // !!! TODO: allow data-clear of ANY app?
1266 if (DEBUG) Log.v(TAG, "Privileged caller, allowing clear of other apps");
1267 apps = new HashSet<ApplicationInfo>();
1268 int N = mBackupParticipants.size();
1269 for (int i = 0; i < N; i++) {
1270 HashSet<ApplicationInfo> s = mBackupParticipants.valueAt(i);
1271 if (s != null) {
1272 apps.addAll(s);
1273 }
1274 }
1275 }
1276
1277 // now find the given package in the set of candidate apps
1278 for (ApplicationInfo app : apps) {
1279 if (app.packageName.equals(packageName)) {
1280 if (DEBUG) Log.v(TAG, "Found the app - running clear process");
1281 // found it; fire off the clear request
1282 synchronized (mQueueLock) {
1283 mWakelock.acquire();
1284 Message msg = mBackupHandler.obtainMessage(MSG_RUN_CLEAR,
1285 new ClearParams(getTransport(mCurrentTransport), info));
1286 mBackupHandler.sendMessage(msg);
1287 }
1288 break;
1289 }
1290 }
1291 }
1292
1293 // Run a backup pass immediately for any applications that have declared
1294 // that they have pending updates.
1295 public void backupNow() throws RemoteException {
1296 mContext.enforceCallingPermission(android.Manifest.permission.BACKUP, "backupNow");
1297
1298 if (DEBUG) Log.v(TAG, "Scheduling immediate backup pass");
Jean-Baptiste Queru843ef362009-05-20 11:28:04 -07001299 synchronized (mQueueLock) {
Jean-Baptiste Querucf4550c2009-07-21 11:16:54 -07001300 try {
1301 if (DEBUG) Log.v(TAG, "sending immediate backup broadcast");
1302 mRunBackupIntent.send();
1303 } catch (PendingIntent.CanceledException e) {
1304 // should never happen
1305 Log.e(TAG, "run-backup intent cancelled!");
1306 }
1307 }
1308 }
1309
1310 // Enable/disable the backup service
1311 public void setBackupEnabled(boolean enable) {
1312 mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
1313 "setBackupEnabled");
1314
1315 boolean wasEnabled = mEnabled;
1316 synchronized (this) {
1317 Settings.Secure.putInt(mContext.getContentResolver(),
1318 Settings.Secure.BACKUP_ENABLED, enable ? 1 : 0);
1319 mEnabled = enable;
1320 }
1321
1322 synchronized (mQueueLock) {
1323 if (enable && !wasEnabled && mProvisioned) {
1324 // if we've just been enabled, start scheduling backup passes
1325 startBackupAlarmsLocked(BACKUP_INTERVAL);
1326 } else if (!enable) {
1327 // No longer enabled, so stop running backups
1328 mAlarmManager.cancel(mRunBackupIntent);
1329 }
1330 }
1331 }
1332
1333 // Mark the backup service as having been provisioned
1334 public void setBackupProvisioned(boolean available) {
1335 mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
1336 "setBackupProvisioned");
1337
1338 boolean wasProvisioned = mProvisioned;
1339 synchronized (this) {
1340 Settings.Secure.putInt(mContext.getContentResolver(),
1341 Settings.Secure.BACKUP_PROVISIONED, available ? 1 : 0);
1342 mProvisioned = available;
1343 }
1344
1345 synchronized (mQueueLock) {
1346 if (available && !wasProvisioned && mEnabled) {
1347 // we're now good to go, so start the backup alarms
1348 startBackupAlarmsLocked(FIRST_BACKUP_INTERVAL);
1349 } else if (!available) {
1350 // No longer enabled, so stop running backups
1351 Log.w(TAG, "Backup service no longer provisioned");
1352 mAlarmManager.cancel(mRunBackupIntent);
1353 }
1354 }
1355 }
1356
1357 private void startBackupAlarmsLocked(long delayBeforeFirstBackup) {
1358 long when = System.currentTimeMillis() + delayBeforeFirstBackup;
1359 mAlarmManager.setInexactRepeating(AlarmManager.RTC_WAKEUP, when,
1360 BACKUP_INTERVAL, mRunBackupIntent);
1361 }
1362
1363 // Report whether the backup mechanism is currently enabled
1364 public boolean isBackupEnabled() {
1365 mContext.enforceCallingPermission(android.Manifest.permission.BACKUP, "isBackupEnabled");
1366 return mEnabled; // no need to synchronize just to read it
1367 }
1368
1369 // Report the name of the currently active transport
1370 public String getCurrentTransport() {
1371 mContext.enforceCallingPermission(android.Manifest.permission.BACKUP,
1372 "getCurrentTransport");
1373 Log.v(TAG, "... getCurrentTransport() returning " + mCurrentTransport);
1374 return mCurrentTransport;
1375 }
1376
1377 // Report all known, available backup transports
1378 public String[] listAllTransports() {
1379 mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP, "listAllTransports");
1380
1381 String[] list = null;
1382 ArrayList<String> known = new ArrayList<String>();
1383 for (Map.Entry<String, IBackupTransport> entry : mTransports.entrySet()) {
1384 if (entry.getValue() != null) {
1385 known.add(entry.getKey());
1386 }
1387 }
1388
1389 if (known.size() > 0) {
1390 list = new String[known.size()];
1391 known.toArray(list);
1392 }
1393 return list;
1394 }
1395
1396 // Select which transport to use for the next backup operation. If the given
1397 // name is not one of the available transports, no action is taken and the method
1398 // returns null.
1399 public String selectBackupTransport(String transport) {
1400 mContext.enforceCallingPermission(android.Manifest.permission.BACKUP, "selectBackupTransport");
1401
1402 synchronized (mTransports) {
1403 String prevTransport = null;
1404 if (mTransports.get(transport) != null) {
1405 prevTransport = mCurrentTransport;
1406 mCurrentTransport = transport;
1407 Settings.Secure.putString(mContext.getContentResolver(),
1408 Settings.Secure.BACKUP_TRANSPORT, transport);
1409 Log.v(TAG, "selectBackupTransport() set " + mCurrentTransport
1410 + " returning " + prevTransport);
1411 } else {
1412 Log.w(TAG, "Attempt to select unavailable transport " + transport);
1413 }
1414 return prevTransport;
1415 }
1416 }
1417
1418 // Callback: a requested backup agent has been instantiated. This should only
1419 // be called from the Activity Manager.
1420 public void agentConnected(String packageName, IBinder agentBinder) {
1421 synchronized(mAgentConnectLock) {
1422 if (Binder.getCallingUid() == Process.SYSTEM_UID) {
1423 Log.d(TAG, "agentConnected pkg=" + packageName + " agent=" + agentBinder);
1424 IBackupAgent agent = IBackupAgent.Stub.asInterface(agentBinder);
1425 mConnectedAgent = agent;
1426 mConnecting = false;
1427 } else {
1428 Log.w(TAG, "Non-system process uid=" + Binder.getCallingUid()
1429 + " claiming agent connected");
1430 }
1431 mAgentConnectLock.notifyAll();
1432 }
1433 }
1434
1435 // Callback: a backup agent has failed to come up, or has unexpectedly quit.
1436 // If the agent failed to come up in the first place, the agentBinder argument
1437 // will be null. This should only be called from the Activity Manager.
1438 public void agentDisconnected(String packageName) {
1439 // TODO: handle backup being interrupted
1440 synchronized(mAgentConnectLock) {
1441 if (Binder.getCallingUid() == Process.SYSTEM_UID) {
1442 mConnectedAgent = null;
1443 mConnecting = false;
1444 } else {
1445 Log.w(TAG, "Non-system process uid=" + Binder.getCallingUid()
1446 + " claiming agent disconnected");
1447 }
1448 mAgentConnectLock.notifyAll();
1449 }
1450 }
1451
1452 // Hand off a restore session
1453 public IRestoreSession beginRestoreSession(String transport) {
1454 mContext.enforceCallingPermission(android.Manifest.permission.BACKUP, "beginRestoreSession");
1455
1456 synchronized(this) {
1457 if (mActiveRestoreSession != null) {
1458 Log.d(TAG, "Restore session requested but one already active");
1459 return null;
1460 }
1461 mActiveRestoreSession = new RestoreSession(transport);
1462 }
1463 return mActiveRestoreSession;
1464 }
1465
1466 // ----- Restore session -----
1467
1468 class RestoreSession extends IRestoreSession.Stub {
1469 private static final String TAG = "RestoreSession";
1470
1471 private IBackupTransport mRestoreTransport = null;
1472 RestoreSet[] mRestoreSets = null;
1473
1474 RestoreSession(String transport) {
1475 mRestoreTransport = getTransport(transport);
1476 }
1477
1478 // --- Binder interface ---
1479 public RestoreSet[] getAvailableRestoreSets() throws android.os.RemoteException {
1480 mContext.enforceCallingPermission(android.Manifest.permission.BACKUP,
1481 "getAvailableRestoreSets");
1482
1483 try {
1484 synchronized(this) {
1485 if (mRestoreSets == null) {
1486 mRestoreSets = mRestoreTransport.getAvailableRestoreSets();
1487 }
1488 return mRestoreSets;
1489 }
1490 } catch (RuntimeException e) {
1491 Log.d(TAG, "getAvailableRestoreSets exception");
1492 e.printStackTrace();
1493 throw e;
1494 }
1495 }
1496
1497 public int performRestore(long token, IRestoreObserver observer)
1498 throws android.os.RemoteException {
1499 mContext.enforceCallingPermission(android.Manifest.permission.BACKUP, "performRestore");
1500
1501 Log.d(TAG, "performRestore token=" + token + " observer=" + observer);
1502
1503 if (mRestoreSets != null) {
1504 for (int i = 0; i < mRestoreSets.length; i++) {
1505 if (token == mRestoreSets[i].token) {
1506 mWakelock.acquire();
1507 Message msg = mBackupHandler.obtainMessage(MSG_RUN_RESTORE);
1508 msg.obj = new RestoreParams(mRestoreTransport, observer, token);
1509 mBackupHandler.sendMessage(msg);
1510 return 0;
Jean-Baptiste Queru843ef362009-05-20 11:28:04 -07001511 }
1512 }
Jean-Baptiste Querucf4550c2009-07-21 11:16:54 -07001513 } else {
1514 if (DEBUG) Log.v(TAG, "No current restore set, not doing restore");
1515 }
1516 return -1;
1517 }
1518
1519 public void endRestoreSession() throws android.os.RemoteException {
1520 mContext.enforceCallingPermission(android.Manifest.permission.BACKUP,
1521 "endRestoreSession");
1522
1523 Log.d(TAG, "endRestoreSession");
1524
1525 mRestoreTransport.finishRestore();
1526 mRestoreTransport = null;
1527 synchronized(BackupManagerService.this) {
1528 if (BackupManagerService.this.mActiveRestoreSession == this) {
1529 BackupManagerService.this.mActiveRestoreSession = null;
1530 } else {
1531 Log.e(TAG, "ending non-current restore session");
1532 }
Jean-Baptiste Queru843ef362009-05-20 11:28:04 -07001533 }
1534 }
1535 }
1536
Jean-Baptiste Querucf4550c2009-07-21 11:16:54 -07001537
Jean-Baptiste Queru843ef362009-05-20 11:28:04 -07001538 @Override
1539 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
1540 synchronized (mQueueLock) {
Jean-Baptiste Querucf4550c2009-07-21 11:16:54 -07001541 long oldId = Binder.clearCallingIdentity();
1542
1543 pw.println("Backup Manager is " + (mEnabled ? "enabled" : "disabled")
1544 + " / " + (!mProvisioned ? "not " : "") + "provisioned");
1545 pw.println("Available transports:");
1546 for (String t : listAllTransports()) {
1547 String pad = (t.equals(mCurrentTransport)) ? " * " : " ";
1548 pw.println(pad + t);
1549 }
Jean-Baptiste Queru843ef362009-05-20 11:28:04 -07001550 int N = mBackupParticipants.size();
Jean-Baptiste Querucf4550c2009-07-21 11:16:54 -07001551 pw.println("Participants: " + N);
Jean-Baptiste Queru843ef362009-05-20 11:28:04 -07001552 for (int i=0; i<N; i++) {
1553 int uid = mBackupParticipants.keyAt(i);
1554 pw.print(" uid: ");
1555 pw.println(uid);
Jean-Baptiste Querucf4550c2009-07-21 11:16:54 -07001556 HashSet<ApplicationInfo> participants = mBackupParticipants.valueAt(i);
1557 for (ApplicationInfo app: participants) {
1558 pw.println(" " + app.toString());
Jean-Baptiste Queru843ef362009-05-20 11:28:04 -07001559 }
1560 }
Jean-Baptiste Querucf4550c2009-07-21 11:16:54 -07001561 pw.println("Pending: " + mPendingBackups.size());
1562 for (BackupRequest req : mPendingBackups.values()) {
1563 pw.println(" " + req);
1564 }
1565
1566 Binder.restoreCallingIdentity(oldId);
Jean-Baptiste Queru843ef362009-05-20 11:28:04 -07001567 }
1568 }
1569}