BackupAgent.java revision cebb55ea9d98dc7ba93eed520b874ac24ff96800
1/*
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 android.app.backup;
18
19import android.app.IBackupAgent;
20import android.app.QueuedWork;
21import android.app.backup.FullBackup.BackupScheme.PathWithRequiredFlags;
22import android.content.Context;
23import android.content.ContextWrapper;
24import android.content.pm.ApplicationInfo;
25import android.os.Binder;
26import android.os.Handler;
27import android.os.IBinder;
28import android.os.Looper;
29import android.os.ParcelFileDescriptor;
30import android.os.Process;
31import android.os.RemoteException;
32import android.system.ErrnoException;
33import android.system.Os;
34import android.system.OsConstants;
35import android.system.StructStat;
36import android.util.ArraySet;
37import android.util.Log;
38
39import libcore.io.IoUtils;
40
41import org.xmlpull.v1.XmlPullParserException;
42
43import java.io.File;
44import java.io.FileOutputStream;
45import java.io.IOException;
46import java.util.Collection;
47import java.util.LinkedList;
48import java.util.Map;
49import java.util.Set;
50import java.util.concurrent.CountDownLatch;
51
52/**
53 * Provides the central interface between an
54 * application and Android's data backup infrastructure.  An application that wishes
55 * to participate in the backup and restore mechanism will declare a subclass of
56 * {@link android.app.backup.BackupAgent}, implement the
57 * {@link #onBackup(ParcelFileDescriptor, BackupDataOutput, ParcelFileDescriptor) onBackup()}
58 * and {@link #onRestore(BackupDataInput, int, ParcelFileDescriptor) onRestore()} methods,
59 * and provide the name of its backup agent class in its {@code AndroidManifest.xml} file via
60 * the <code>
61 * <a href="{@docRoot}guide/topics/manifest/application-element.html">&lt;application&gt;</a></code>
62 * tag's {@code android:backupAgent} attribute.
63 *
64 * <div class="special reference">
65 * <h3>Developer Guides</h3>
66 * <p>For more information about using BackupAgent, read the
67 * <a href="{@docRoot}guide/topics/data/backup.html">Data Backup</a> developer guide.</p></div>
68 *
69 * <h3>Basic Operation</h3>
70 * <p>
71 * When the application makes changes to data that it wishes to keep backed up,
72 * it should call the
73 * {@link android.app.backup.BackupManager#dataChanged() BackupManager.dataChanged()} method.
74 * This notifies the Android Backup Manager that the application needs an opportunity
75 * to update its backup image.  The Backup Manager, in turn, schedules a
76 * backup pass to be performed at an opportune time.
77 * <p>
78 * Restore operations are typically performed only when applications are first
79 * installed on a device.  At that time, the operating system checks to see whether
80 * there is a previously-saved data set available for the application being installed, and if so,
81 * begins an immediate restore pass to deliver the backup data as part of the installation
82 * process.
83 * <p>
84 * When a backup or restore pass is run, the application's process is launched
85 * (if not already running), the manifest-declared backup agent class (in the {@code
86 * android:backupAgent} attribute) is instantiated within
87 * that process, and the agent's {@link #onCreate()} method is invoked.  This prepares the
88 * agent instance to run the actual backup or restore logic.  At this point the
89 * agent's
90 * {@link #onBackup(ParcelFileDescriptor, BackupDataOutput, ParcelFileDescriptor) onBackup()} or
91 * {@link #onRestore(BackupDataInput, int, ParcelFileDescriptor) onRestore()} method will be
92 * invoked as appropriate for the operation being performed.
93 * <p>
94 * A backup data set consists of one or more "entities," flattened binary data
95 * records that are each identified with a key string unique within the data set.  Adding a
96 * record to the active data set or updating an existing record is done by simply
97 * writing new entity data under the desired key.  Deleting an entity from the data set
98 * is done by writing an entity under that key with header specifying a negative data
99 * size, and no actual entity data.
100 * <p>
101 * <b>Helper Classes</b>
102 * <p>
103 * An extensible agent based on convenient helper classes is available in
104 * {@link android.app.backup.BackupAgentHelper}.  That class is particularly
105 * suited to handling of simple file or {@link android.content.SharedPreferences}
106 * backup and restore.
107 * <p>
108 * <b>Threading</b>
109 * <p>
110 * The constructor, as well as {@link #onCreate()} and {@link #onDestroy()} lifecycle callbacks run
111 * on the main thread (UI thread) of the application that implements the BackupAgent.
112 * The data-handling callbacks:
113 * {@link #onBackup(ParcelFileDescriptor, BackupDataOutput, ParcelFileDescriptor) onBackup()},
114 * {@link #onFullBackup(FullBackupDataOutput)},
115 * {@link #onRestore(BackupDataInput, int, ParcelFileDescriptor) onRestore()},
116 * {@link #onRestoreFile(ParcelFileDescriptor, long, File, int, long, long) onRestoreFile()},
117 * {@link #onRestoreFinished()}, and {@link #onQuotaExceeded(long, long) onQuotaExceeded()}
118 * run on binder pool threads.
119 *
120 * @see android.app.backup.BackupManager
121 * @see android.app.backup.BackupAgentHelper
122 * @see android.app.backup.BackupDataInput
123 * @see android.app.backup.BackupDataOutput
124 */
125public abstract class BackupAgent extends ContextWrapper {
126    private static final String TAG = "BackupAgent";
127    private static final boolean DEBUG = false;
128
129    /** @hide */
130    public static final int TYPE_EOF = 0;
131
132    /**
133     * During a full restore, indicates that the file system object being restored
134     * is an ordinary file.
135     */
136    public static final int TYPE_FILE = 1;
137
138    /**
139     * During a full restore, indicates that the file system object being restored
140     * is a directory.
141     */
142    public static final int TYPE_DIRECTORY = 2;
143
144    /** @hide */
145    public static final int TYPE_SYMLINK = 3;
146
147    /**
148     * Flag for {@link BackupDataOutput#getTransportFlags()} and
149     * {@link FullBackupDataOutput#getTransportFlags()} only.
150     *
151     * <p>The transport has client-side encryption enabled. i.e., the user's backup is encrypted
152     * with a key known only to the device, and not to the remote storage solution where the backup
153     * data is stored. The key may be synced to a remote trusted hardware module if it has
154     * protections equivalent to those described in the
155     * <a href="https://developer.android.com/preview/features/security/ckv-whitepaper.html">Google
156     * Cloud Key Vault Service whitepaper</a>. Having direct access to the trusted hardware module
157     * must be insufficient to decrypt the user's backup data.
158     *
159     * <p>The backup data itself must be encrypted using an AES/GCM/NoPadding cipher. The key
160     * material must be randomly generated using {@link java.security.SecureRandom}, and must have
161     * at least 256 bits of entropy.
162     */
163    public static final int FLAG_CLIENT_SIDE_ENCRYPTION_ENABLED = 1;
164
165    /**
166     * Flag for {@link BackupDataOutput#getTransportFlags()} and
167     * {@link FullBackupDataOutput#getTransportFlags()} only.
168     *
169     * <p>The transport is for a device-to-device transfer. There is no third party or intermediate
170     * storage. The user's backup data is sent directly to another device over e.g., USB or WiFi.
171     */
172    public static final int FLAG_DEVICE_TO_DEVICE_TRANSFER = 2;
173
174    /**
175     * Flag for {@link BackupDataOutput#getTransportFlags()} and
176     * {@link FullBackupDataOutput#getTransportFlags()} only.
177     *
178     * <p>Used for internal testing only. Do not check this flag in production code.
179     *
180     * @hide
181     */
182    public static final int FLAG_FAKE_CLIENT_SIDE_ENCRYPTION_ENABLED = 1 << 31;
183
184    Handler mHandler = null;
185
186    Handler getHandler() {
187        if (mHandler == null) {
188            mHandler = new Handler(Looper.getMainLooper());
189        }
190        return mHandler;
191    }
192
193    class SharedPrefsSynchronizer implements Runnable {
194        public final CountDownLatch mLatch = new CountDownLatch(1);
195
196        @Override
197        public void run() {
198            QueuedWork.waitToFinish();
199            mLatch.countDown();
200        }
201    };
202
203    // Syncing shared preferences deferred writes needs to happen on the main looper thread
204    private void waitForSharedPrefs() {
205        Handler h = getHandler();
206        final SharedPrefsSynchronizer s = new SharedPrefsSynchronizer();
207        h.postAtFrontOfQueue(s);
208        try {
209            s.mLatch.await();
210        } catch (InterruptedException e) { /* ignored */ }
211    }
212
213
214    public BackupAgent() {
215        super(null);
216    }
217
218    /**
219     * Provided as a convenience for agent implementations that need an opportunity
220     * to do one-time initialization before the actual backup or restore operation
221     * is begun.
222     * <p>
223     */
224    public void onCreate() {
225    }
226
227    /**
228     * Provided as a convenience for agent implementations that need to do some
229     * sort of shutdown process after backup or restore is completed.
230     * <p>
231     * Agents do not need to override this method.
232     */
233    public void onDestroy() {
234    }
235
236    /**
237     * The application is being asked to write any data changed since the last
238     * time it performed a backup operation. The state data recorded during the
239     * last backup pass is provided in the <code>oldState</code> file
240     * descriptor. If <code>oldState</code> is <code>null</code>, no old state
241     * is available and the application should perform a full backup. In both
242     * cases, a representation of the final backup state after this pass should
243     * be written to the file pointed to by the file descriptor wrapped in
244     * <code>newState</code>.
245     * <p>
246     * Each entity written to the {@link android.app.backup.BackupDataOutput}
247     * <code>data</code> stream will be transmitted
248     * over the current backup transport and stored in the remote data set under
249     * the key supplied as part of the entity.  Writing an entity with a negative
250     * data size instructs the transport to delete whatever entity currently exists
251     * under that key from the remote data set.
252     *
253     * @param oldState An open, read-only ParcelFileDescriptor pointing to the
254     *            last backup state provided by the application. May be
255     *            <code>null</code>, in which case no prior state is being
256     *            provided and the application should perform a full backup.
257     * @param data A structured wrapper around an open, read/write
258     *            file descriptor pointing to the backup data destination.
259     *            Typically the application will use backup helper classes to
260     *            write to this file.
261     * @param newState An open, read/write ParcelFileDescriptor pointing to an
262     *            empty file. The application should record the final backup
263     *            state here after writing the requested data to the <code>data</code>
264     *            output stream.
265     */
266    public abstract void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
267            ParcelFileDescriptor newState) throws IOException;
268
269    /**
270     * The application is being restored from backup and should replace any
271     * existing data with the contents of the backup. The backup data is
272     * provided through the <code>data</code> parameter. Once
273     * the restore is finished, the application should write a representation of
274     * the final state to the <code>newState</code> file descriptor.
275     * <p>
276     * The application is responsible for properly erasing its old data and
277     * replacing it with the data supplied to this method. No "clear user data"
278     * operation will be performed automatically by the operating system. The
279     * exception to this is in the case of a failed restore attempt: if
280     * onRestore() throws an exception, the OS will assume that the
281     * application's data may now be in an incoherent state, and will clear it
282     * before proceeding.
283     *
284     * @param data A structured wrapper around an open, read-only
285     *            file descriptor pointing to a full snapshot of the
286     *            application's data.  The application should consume every
287     *            entity represented in this data stream.
288     * @param appVersionCode The value of the <a
289     * href="{@docRoot}guide/topics/manifest/manifest-element.html#vcode">{@code
290     *            android:versionCode}</a> manifest attribute,
291     *            from the application that backed up this particular data set. This
292     *            makes it possible for an application's agent to distinguish among any
293     *            possible older data versions when asked to perform the restore
294     *            operation.
295     * @param newState An open, read/write ParcelFileDescriptor pointing to an
296     *            empty file. The application should record the final backup
297     *            state here after restoring its data from the <code>data</code> stream.
298     *            When a full-backup dataset is being restored, this will be <code>null</code>.
299     */
300    public abstract void onRestore(BackupDataInput data, int appVersionCode,
301            ParcelFileDescriptor newState) throws IOException;
302
303    /**
304     * New version of {@link #onRestore(BackupDataInput, int, android.os.ParcelFileDescriptor)}
305     * that handles a long app version code.  Default implementation casts the version code to
306     * an int and calls {@link #onRestore(BackupDataInput, int, android.os.ParcelFileDescriptor)}.
307     */
308    public void onRestore(BackupDataInput data, long appVersionCode,
309            ParcelFileDescriptor newState)
310            throws IOException {
311        onRestore(data, (int) appVersionCode, newState);
312    }
313
314    /**
315     * The application is having its entire file system contents backed up.  {@code data}
316     * points to the backup destination, and the app has the opportunity to choose which
317     * files are to be stored.  To commit a file as part of the backup, call the
318     * {@link #fullBackupFile(File, FullBackupDataOutput)} helper method.  After all file
319     * data is written to the output, the agent returns from this method and the backup
320     * operation concludes.
321     *
322     * <p>Certain parts of the app's data are never backed up even if the app explicitly
323     * sends them to the output:
324     *
325     * <ul>
326     * <li>The contents of the {@link #getCacheDir()} directory</li>
327     * <li>The contents of the {@link #getCodeCacheDir()} directory</li>
328     * <li>The contents of the {@link #getNoBackupFilesDir()} directory</li>
329     * <li>The contents of the app's shared library directory</li>
330     * </ul>
331     *
332     * <p>The default implementation of this method backs up the entirety of the
333     * application's "owned" file system trees to the output other than the few exceptions
334     * listed above.  Apps only need to override this method if they need to impose special
335     * limitations on which files are being stored beyond the control that
336     * {@link #getNoBackupFilesDir()} offers.
337     * Alternatively they can provide an xml resource to specify what data to include or exclude.
338     *
339     *
340     * @param data A structured wrapper pointing to the backup destination.
341     * @throws IOException
342     *
343     * @see Context#getNoBackupFilesDir()
344     * @see ApplicationInfo#fullBackupContent
345     * @see #fullBackupFile(File, FullBackupDataOutput)
346     * @see #onRestoreFile(ParcelFileDescriptor, long, File, int, long, long)
347     */
348    public void onFullBackup(FullBackupDataOutput data) throws IOException {
349        FullBackup.BackupScheme backupScheme = FullBackup.getBackupScheme(this);
350        if (!backupScheme.isFullBackupContentEnabled()) {
351            return;
352        }
353
354        Map<String, Set<PathWithRequiredFlags>> manifestIncludeMap;
355        ArraySet<PathWithRequiredFlags> manifestExcludeSet;
356        try {
357            manifestIncludeMap =
358                    backupScheme.maybeParseAndGetCanonicalIncludePaths();
359            manifestExcludeSet = backupScheme.maybeParseAndGetCanonicalExcludePaths();
360        } catch (IOException | XmlPullParserException e) {
361            if (Log.isLoggable(FullBackup.TAG_XML_PARSER, Log.VERBOSE)) {
362                Log.v(FullBackup.TAG_XML_PARSER,
363                        "Exception trying to parse fullBackupContent xml file!"
364                                + " Aborting full backup.", e);
365            }
366            return;
367        }
368
369        final String packageName = getPackageName();
370        final ApplicationInfo appInfo = getApplicationInfo();
371
372        // System apps have control over where their default storage context
373        // is pointed, so we're always explicit when building paths.
374        final Context ceContext = createCredentialProtectedStorageContext();
375        final String rootDir = ceContext.getDataDir().getCanonicalPath();
376        final String filesDir = ceContext.getFilesDir().getCanonicalPath();
377        final String noBackupDir = ceContext.getNoBackupFilesDir().getCanonicalPath();
378        final String databaseDir = ceContext.getDatabasePath("foo").getParentFile()
379                .getCanonicalPath();
380        final String sharedPrefsDir = ceContext.getSharedPreferencesPath("foo").getParentFile()
381                .getCanonicalPath();
382        final String cacheDir = ceContext.getCacheDir().getCanonicalPath();
383        final String codeCacheDir = ceContext.getCodeCacheDir().getCanonicalPath();
384
385        final Context deContext = createDeviceProtectedStorageContext();
386        final String deviceRootDir = deContext.getDataDir().getCanonicalPath();
387        final String deviceFilesDir = deContext.getFilesDir().getCanonicalPath();
388        final String deviceNoBackupDir = deContext.getNoBackupFilesDir().getCanonicalPath();
389        final String deviceDatabaseDir = deContext.getDatabasePath("foo").getParentFile()
390                .getCanonicalPath();
391        final String deviceSharedPrefsDir = deContext.getSharedPreferencesPath("foo")
392                .getParentFile().getCanonicalPath();
393        final String deviceCacheDir = deContext.getCacheDir().getCanonicalPath();
394        final String deviceCodeCacheDir = deContext.getCodeCacheDir().getCanonicalPath();
395
396        final String libDir = (appInfo.nativeLibraryDir != null)
397                ? new File(appInfo.nativeLibraryDir).getCanonicalPath()
398                : null;
399
400        // Maintain a set of excluded directories so that as we traverse the tree we know we're not
401        // going places we don't expect, and so the manifest includes can't take precedence over
402        // what the framework decides is not to be included.
403        final ArraySet<String> traversalExcludeSet = new ArraySet<String>();
404
405        // Add the directories we always exclude.
406        traversalExcludeSet.add(filesDir);
407        traversalExcludeSet.add(noBackupDir);
408        traversalExcludeSet.add(databaseDir);
409        traversalExcludeSet.add(sharedPrefsDir);
410        traversalExcludeSet.add(cacheDir);
411        traversalExcludeSet.add(codeCacheDir);
412
413        traversalExcludeSet.add(deviceFilesDir);
414        traversalExcludeSet.add(deviceNoBackupDir);
415        traversalExcludeSet.add(deviceDatabaseDir);
416        traversalExcludeSet.add(deviceSharedPrefsDir);
417        traversalExcludeSet.add(deviceCacheDir);
418        traversalExcludeSet.add(deviceCodeCacheDir);
419
420        if (libDir != null) {
421            traversalExcludeSet.add(libDir);
422        }
423
424        // Root dir first.
425        applyXmlFiltersAndDoFullBackupForDomain(
426                packageName, FullBackup.ROOT_TREE_TOKEN, manifestIncludeMap,
427                manifestExcludeSet, traversalExcludeSet, data);
428        traversalExcludeSet.add(rootDir);
429
430        applyXmlFiltersAndDoFullBackupForDomain(
431                packageName, FullBackup.DEVICE_ROOT_TREE_TOKEN, manifestIncludeMap,
432                manifestExcludeSet, traversalExcludeSet, data);
433        traversalExcludeSet.add(deviceRootDir);
434
435        // Data dir next.
436        traversalExcludeSet.remove(filesDir);
437        applyXmlFiltersAndDoFullBackupForDomain(
438                packageName, FullBackup.FILES_TREE_TOKEN, manifestIncludeMap,
439                manifestExcludeSet, traversalExcludeSet, data);
440        traversalExcludeSet.add(filesDir);
441
442        traversalExcludeSet.remove(deviceFilesDir);
443        applyXmlFiltersAndDoFullBackupForDomain(
444                packageName, FullBackup.DEVICE_FILES_TREE_TOKEN, manifestIncludeMap,
445                manifestExcludeSet, traversalExcludeSet, data);
446        traversalExcludeSet.add(deviceFilesDir);
447
448        // Database directory.
449        traversalExcludeSet.remove(databaseDir);
450        applyXmlFiltersAndDoFullBackupForDomain(
451                packageName, FullBackup.DATABASE_TREE_TOKEN, manifestIncludeMap,
452                manifestExcludeSet, traversalExcludeSet, data);
453        traversalExcludeSet.add(databaseDir);
454
455        traversalExcludeSet.remove(deviceDatabaseDir);
456        applyXmlFiltersAndDoFullBackupForDomain(
457                packageName, FullBackup.DEVICE_DATABASE_TREE_TOKEN, manifestIncludeMap,
458                manifestExcludeSet, traversalExcludeSet, data);
459        traversalExcludeSet.add(deviceDatabaseDir);
460
461        // SharedPrefs.
462        traversalExcludeSet.remove(sharedPrefsDir);
463        applyXmlFiltersAndDoFullBackupForDomain(
464                packageName, FullBackup.SHAREDPREFS_TREE_TOKEN, manifestIncludeMap,
465                manifestExcludeSet, traversalExcludeSet, data);
466        traversalExcludeSet.add(sharedPrefsDir);
467
468        traversalExcludeSet.remove(deviceSharedPrefsDir);
469        applyXmlFiltersAndDoFullBackupForDomain(
470                packageName, FullBackup.DEVICE_SHAREDPREFS_TREE_TOKEN, manifestIncludeMap,
471                manifestExcludeSet, traversalExcludeSet, data);
472        traversalExcludeSet.add(deviceSharedPrefsDir);
473
474        // getExternalFilesDir() location associated with this app.  Technically there should
475        // not be any files here if the app does not properly have permission to access
476        // external storage, but edge cases happen. fullBackupFileTree() catches
477        // IOExceptions and similar, and treats them as non-fatal, so we rely on that; and
478        // we know a priori that processes running as the system UID are not permitted to
479        // access external storage, so we check for that as well to avoid nastygrams in
480        // the log.
481        if (Process.myUid() != Process.SYSTEM_UID) {
482            File efLocation = getExternalFilesDir(null);
483            if (efLocation != null) {
484                applyXmlFiltersAndDoFullBackupForDomain(
485                        packageName, FullBackup.MANAGED_EXTERNAL_TREE_TOKEN, manifestIncludeMap,
486                        manifestExcludeSet, traversalExcludeSet, data);
487            }
488
489        }
490    }
491
492    /**
493     * Notification that the application's current backup operation causes it to exceed
494     * the maximum size permitted by the transport.  The ongoing backup operation is
495     * halted and rolled back: any data that had been stored by a previous backup operation
496     * is still intact.  Typically the quota-exceeded state will be detected before any data
497     * is actually transmitted over the network.
498     *
499     * <p>The {@code quotaBytes} value is the total data size currently permitted for this
500     * application.  If desired, the application can use this as a hint for determining
501     * how much data to store.  For example, a messaging application might choose to
502     * store only the newest messages, dropping enough older content to stay under
503     * the quota.
504     *
505     * <p class="note">Note that the maximum quota for the application can change over
506     * time.  In particular, in the future the quota may grow.  Applications that adapt
507     * to the quota when deciding what data to store should be aware of this and implement
508     * their data storage mechanisms in a way that can take advantage of additional
509     * quota.
510     *
511     * @param backupDataBytes The amount of data measured while initializing the backup
512     *    operation, if the total exceeds the app's alloted quota.  If initial measurement
513     *    suggested that the data would fit but then too much data was actually submitted
514     *    as part of the operation, then this value is the amount of data that had been
515     *    streamed into the transport at the time the quota was reached.
516     * @param quotaBytes The maximum data size that the transport currently permits
517     *    this application to store as a backup.
518     */
519    public void onQuotaExceeded(long backupDataBytes, long quotaBytes) {
520    }
521
522    /**
523     * Check whether the xml yielded any <include/> tag for the provided <code>domainToken</code>.
524     * If so, perform a {@link #fullBackupFileTree} which backs up the file or recurses if the path
525     * is a directory, but only if all the required flags of the include rule are satisfied by
526     * the transport.
527     */
528    private void applyXmlFiltersAndDoFullBackupForDomain(String packageName, String domainToken,
529            Map<String, Set<PathWithRequiredFlags>> includeMap,
530            ArraySet<PathWithRequiredFlags> filterSet, ArraySet<String> traversalExcludeSet,
531            FullBackupDataOutput data) throws IOException {
532        if (includeMap == null || includeMap.size() == 0) {
533            // Do entire sub-tree for the provided token.
534            fullBackupFileTree(packageName, domainToken,
535                    FullBackup.getBackupScheme(this).tokenToDirectoryPath(domainToken),
536                    filterSet, traversalExcludeSet, data);
537        } else if (includeMap.get(domainToken) != null) {
538            // This will be null if the xml parsing didn't yield any rules for
539            // this domain (there may still be rules for other domains).
540            for (PathWithRequiredFlags includeFile : includeMap.get(domainToken)) {
541                if (areIncludeRequiredTransportFlagsSatisfied(includeFile.getRequiredFlags(),
542                        data.getTransportFlags())) {
543                    fullBackupFileTree(packageName, domainToken, includeFile.getPath(), filterSet,
544                            traversalExcludeSet, data);
545                }
546            }
547        }
548    }
549
550    private boolean areIncludeRequiredTransportFlagsSatisfied(int includeFlags,
551            int transportFlags) {
552        // all bits that are set in includeFlags must also be set in transportFlags
553        return (transportFlags & includeFlags) == includeFlags;
554    }
555
556    /**
557     * Write an entire file as part of a full-backup operation.  The file's contents
558     * will be delivered to the backup destination along with the metadata necessary
559     * to place it with the proper location and permissions on the device where the
560     * data is restored.
561     *
562     * <p class="note">Attempting to back up files in directories that are ignored by
563     * the backup system will have no effect.  For example, if the app calls this method
564     * with a file inside the {@link #getNoBackupFilesDir()} directory, it will be ignored.
565     * See {@link #onFullBackup(FullBackupDataOutput)} for details on what directories
566     * are excluded from backups.
567     *
568     * @param file The file to be backed up.  The file must exist and be readable by
569     *     the caller.
570     * @param output The destination to which the backed-up file data will be sent.
571     */
572    public final void fullBackupFile(File file, FullBackupDataOutput output) {
573        // Look up where all of our various well-defined dir trees live on this device
574        final String rootDir;
575        final String filesDir;
576        final String nbFilesDir;
577        final String dbDir;
578        final String spDir;
579        final String cacheDir;
580        final String codeCacheDir;
581        final String deviceRootDir;
582        final String deviceFilesDir;
583        final String deviceNbFilesDir;
584        final String deviceDbDir;
585        final String deviceSpDir;
586        final String deviceCacheDir;
587        final String deviceCodeCacheDir;
588        final String libDir;
589
590        String efDir = null;
591        String filePath;
592
593        ApplicationInfo appInfo = getApplicationInfo();
594
595        try {
596            // System apps have control over where their default storage context
597            // is pointed, so we're always explicit when building paths.
598            final Context ceContext = createCredentialProtectedStorageContext();
599            rootDir = ceContext.getDataDir().getCanonicalPath();
600            filesDir = ceContext.getFilesDir().getCanonicalPath();
601            nbFilesDir = ceContext.getNoBackupFilesDir().getCanonicalPath();
602            dbDir = ceContext.getDatabasePath("foo").getParentFile().getCanonicalPath();
603            spDir = ceContext.getSharedPreferencesPath("foo").getParentFile().getCanonicalPath();
604            cacheDir = ceContext.getCacheDir().getCanonicalPath();
605            codeCacheDir = ceContext.getCodeCacheDir().getCanonicalPath();
606
607            final Context deContext = createDeviceProtectedStorageContext();
608            deviceRootDir = deContext.getDataDir().getCanonicalPath();
609            deviceFilesDir = deContext.getFilesDir().getCanonicalPath();
610            deviceNbFilesDir = deContext.getNoBackupFilesDir().getCanonicalPath();
611            deviceDbDir = deContext.getDatabasePath("foo").getParentFile().getCanonicalPath();
612            deviceSpDir = deContext.getSharedPreferencesPath("foo").getParentFile()
613                    .getCanonicalPath();
614            deviceCacheDir = deContext.getCacheDir().getCanonicalPath();
615            deviceCodeCacheDir = deContext.getCodeCacheDir().getCanonicalPath();
616
617            libDir = (appInfo.nativeLibraryDir == null)
618                    ? null
619                    : new File(appInfo.nativeLibraryDir).getCanonicalPath();
620
621            // may or may not have external files access to attempt backup/restore there
622            if (Process.myUid() != Process.SYSTEM_UID) {
623                File efLocation = getExternalFilesDir(null);
624                if (efLocation != null) {
625                    efDir = efLocation.getCanonicalPath();
626                }
627            }
628
629            // Now figure out which well-defined tree the file is placed in, working from
630            // most to least specific.  We also specifically exclude the lib, cache,
631            // and code_cache dirs.
632            filePath = file.getCanonicalPath();
633        } catch (IOException e) {
634            Log.w(TAG, "Unable to obtain canonical paths");
635            return;
636        }
637
638        if (filePath.startsWith(cacheDir)
639                || filePath.startsWith(codeCacheDir)
640                || filePath.startsWith(nbFilesDir)
641                || filePath.startsWith(deviceCacheDir)
642                || filePath.startsWith(deviceCodeCacheDir)
643                || filePath.startsWith(deviceNbFilesDir)
644                || filePath.startsWith(libDir)) {
645            Log.w(TAG, "lib, cache, code_cache, and no_backup files are not backed up");
646            return;
647        }
648
649        final String domain;
650        String rootpath = null;
651        if (filePath.startsWith(dbDir)) {
652            domain = FullBackup.DATABASE_TREE_TOKEN;
653            rootpath = dbDir;
654        } else if (filePath.startsWith(spDir)) {
655            domain = FullBackup.SHAREDPREFS_TREE_TOKEN;
656            rootpath = spDir;
657        } else if (filePath.startsWith(filesDir)) {
658            domain = FullBackup.FILES_TREE_TOKEN;
659            rootpath = filesDir;
660        } else if (filePath.startsWith(rootDir)) {
661            domain = FullBackup.ROOT_TREE_TOKEN;
662            rootpath = rootDir;
663        } else if (filePath.startsWith(deviceDbDir)) {
664            domain = FullBackup.DEVICE_DATABASE_TREE_TOKEN;
665            rootpath = deviceDbDir;
666        } else if (filePath.startsWith(deviceSpDir)) {
667            domain = FullBackup.DEVICE_SHAREDPREFS_TREE_TOKEN;
668            rootpath = deviceSpDir;
669        } else if (filePath.startsWith(deviceFilesDir)) {
670            domain = FullBackup.DEVICE_FILES_TREE_TOKEN;
671            rootpath = deviceFilesDir;
672        } else if (filePath.startsWith(deviceRootDir)) {
673            domain = FullBackup.DEVICE_ROOT_TREE_TOKEN;
674            rootpath = deviceRootDir;
675        } else if ((efDir != null) && filePath.startsWith(efDir)) {
676            domain = FullBackup.MANAGED_EXTERNAL_TREE_TOKEN;
677            rootpath = efDir;
678        } else {
679            Log.w(TAG, "File " + filePath + " is in an unsupported location; skipping");
680            return;
681        }
682
683        // And now that we know where it lives, semantically, back it up appropriately
684        // In the measurement case, backupToTar() updates the size in output and returns
685        // without transmitting any file data.
686        if (DEBUG) Log.i(TAG, "backupFile() of " + filePath + " => domain=" + domain
687                + " rootpath=" + rootpath);
688
689        FullBackup.backupToTar(getPackageName(), domain, null, rootpath, filePath, output);
690    }
691
692    /**
693     * Scan the dir tree (if it actually exists) and process each entry we find.  If the
694     * 'excludes' parameters are non-null, they are consulted each time a new file system entity
695     * is visited to see whether that entity (and its subtree, if appropriate) should be
696     * omitted from the backup process.
697     *
698     * @param systemExcludes An optional list of excludes.
699     * @hide
700     */
701    protected final void fullBackupFileTree(String packageName, String domain, String startingPath,
702                                            ArraySet<PathWithRequiredFlags> manifestExcludes,
703                                            ArraySet<String> systemExcludes,
704            FullBackupDataOutput output) {
705        // Pull out the domain and set it aside to use when making the tarball.
706        String domainPath = FullBackup.getBackupScheme(this).tokenToDirectoryPath(domain);
707        if (domainPath == null) {
708            // Should never happen.
709            return;
710        }
711
712        File rootFile = new File(startingPath);
713        if (rootFile.exists()) {
714            LinkedList<File> scanQueue = new LinkedList<File>();
715            scanQueue.add(rootFile);
716
717            while (scanQueue.size() > 0) {
718                File file = scanQueue.remove(0);
719                String filePath;
720                try {
721                    // Ignore things that aren't "real" files or dirs
722                    StructStat stat = Os.lstat(file.getPath());
723                    if (!OsConstants.S_ISREG(stat.st_mode)
724                            && !OsConstants.S_ISDIR(stat.st_mode)) {
725                        if (DEBUG) Log.i(TAG, "Not a file/dir (skipping)!: " + file);
726                        continue;
727                    }
728
729                    // For all other verification, look at the canonicalized path
730                    filePath = file.getCanonicalPath();
731
732                    // prune this subtree?
733                    if (manifestExcludes != null
734                            && manifestExcludesContainFilePath(manifestExcludes, filePath)) {
735                        continue;
736                    }
737                    if (systemExcludes != null && systemExcludes.contains(filePath)) {
738                        continue;
739                    }
740
741                    // If it's a directory, enqueue its contents for scanning.
742                    if (OsConstants.S_ISDIR(stat.st_mode)) {
743                        File[] contents = file.listFiles();
744                        if (contents != null) {
745                            for (File entry : contents) {
746                                scanQueue.add(0, entry);
747                            }
748                        }
749                    }
750                } catch (IOException e) {
751                    if (DEBUG) Log.w(TAG, "Error canonicalizing path of " + file);
752                    if (Log.isLoggable(FullBackup.TAG_XML_PARSER, Log.VERBOSE)) {
753                        Log.v(FullBackup.TAG_XML_PARSER, "Error canonicalizing path of " + file);
754                    }
755                    continue;
756                } catch (ErrnoException e) {
757                    if (DEBUG) Log.w(TAG, "Error scanning file " + file + " : " + e);
758                    if (Log.isLoggable(FullBackup.TAG_XML_PARSER, Log.VERBOSE)) {
759                        Log.v(FullBackup.TAG_XML_PARSER, "Error scanning file " + file + " : " + e);
760                    }
761                    continue;
762                }
763
764                // Finally, back this file up (or measure it) before proceeding
765                FullBackup.backupToTar(packageName, domain, null, domainPath, filePath, output);
766            }
767        }
768    }
769
770    private boolean manifestExcludesContainFilePath(
771        ArraySet<PathWithRequiredFlags> manifestExcludes, String filePath) {
772        for (PathWithRequiredFlags exclude : manifestExcludes) {
773            String excludePath = exclude.getPath();
774            if (excludePath != null && excludePath.equals(filePath)) {
775                return true;
776            }
777        }
778        return false;
779    }
780
781    /**
782     * Handle the data delivered via the given file descriptor during a full restore
783     * operation.  The agent is given the path to the file's original location as well
784     * as its size and metadata.
785     * <p>
786     * The file descriptor can only be read for {@code size} bytes; attempting to read
787     * more data has undefined behavior.
788     * <p>
789     * The default implementation creates the destination file/directory and populates it
790     * with the data from the file descriptor, then sets the file's access mode and
791     * modification time to match the restore arguments.
792     *
793     * @param data A read-only file descriptor from which the agent can read {@code size}
794     *     bytes of file data.
795     * @param size The number of bytes of file content to be restored to the given
796     *     destination.  If the file system object being restored is a directory, {@code size}
797     *     will be zero.
798     * @param destination The File on disk to be restored with the given data.
799     * @param type The kind of file system object being restored.  This will be either
800     *     {@link BackupAgent#TYPE_FILE} or {@link BackupAgent#TYPE_DIRECTORY}.
801     * @param mode The access mode to be assigned to the destination after its data is
802     *     written.  This is in the standard format used by {@code chmod()}.
803     * @param mtime The modification time of the file when it was backed up, suitable to
804     *     be assigned to the file after its data is written.
805     * @throws IOException
806     */
807    public void onRestoreFile(ParcelFileDescriptor data, long size,
808            File destination, int type, long mode, long mtime)
809            throws IOException {
810
811        final boolean accept = isFileEligibleForRestore(destination);
812        // If we don't accept the file, consume the bytes from the pipe anyway.
813        FullBackup.restoreFile(data, size, type, mode, mtime, accept ? destination : null);
814    }
815
816    private boolean isFileEligibleForRestore(File destination) throws IOException {
817        FullBackup.BackupScheme bs = FullBackup.getBackupScheme(this);
818        if (!bs.isFullBackupContentEnabled()) {
819            if (Log.isLoggable(FullBackup.TAG_XML_PARSER, Log.VERBOSE)) {
820                Log.v(FullBackup.TAG_XML_PARSER,
821                        "onRestoreFile \"" + destination.getCanonicalPath()
822                                + "\" : fullBackupContent not enabled for " + getPackageName());
823            }
824            return false;
825        }
826
827        Map<String, Set<PathWithRequiredFlags>> includes = null;
828        ArraySet<PathWithRequiredFlags> excludes = null;
829        final String destinationCanonicalPath = destination.getCanonicalPath();
830        try {
831            includes = bs.maybeParseAndGetCanonicalIncludePaths();
832            excludes = bs.maybeParseAndGetCanonicalExcludePaths();
833        } catch (XmlPullParserException e) {
834            if (Log.isLoggable(FullBackup.TAG_XML_PARSER, Log.VERBOSE)) {
835                Log.v(FullBackup.TAG_XML_PARSER,
836                        "onRestoreFile \"" + destinationCanonicalPath
837                                + "\" : Exception trying to parse fullBackupContent xml file!"
838                                + " Aborting onRestoreFile.", e);
839            }
840            return false;
841        }
842
843        if (excludes != null &&
844                isFileSpecifiedInPathList(destination, excludes)) {
845            if (Log.isLoggable(FullBackup.TAG_XML_PARSER, Log.VERBOSE)) {
846                Log.v(FullBackup.TAG_XML_PARSER,
847                        "onRestoreFile: \"" + destinationCanonicalPath + "\": listed in"
848                                + " excludes; skipping.");
849            }
850            return false;
851        }
852
853        if (includes != null && !includes.isEmpty()) {
854            // Rather than figure out the <include/> domain based on the path (a lot of code, and
855            // it's a small list), we'll go through and look for it.
856            boolean explicitlyIncluded = false;
857            for (Set<PathWithRequiredFlags> domainIncludes : includes.values()) {
858                explicitlyIncluded |= isFileSpecifiedInPathList(destination, domainIncludes);
859                if (explicitlyIncluded) {
860                    break;
861                }
862            }
863            if (!explicitlyIncluded) {
864                if (Log.isLoggable(FullBackup.TAG_XML_PARSER, Log.VERBOSE)) {
865                    Log.v(FullBackup.TAG_XML_PARSER,
866                            "onRestoreFile: Trying to restore \""
867                                    + destinationCanonicalPath + "\" but it isn't specified"
868                                    + " in the included files; skipping.");
869                }
870                return false;
871            }
872        }
873        return true;
874    }
875
876    /**
877     * @return True if the provided file is either directly in the provided list, or the provided
878     * file is within a directory in the list.
879     */
880    private boolean isFileSpecifiedInPathList(File file,
881            Collection<PathWithRequiredFlags> canonicalPathList) throws IOException {
882        for (PathWithRequiredFlags canonical : canonicalPathList) {
883            String canonicalPath = canonical.getPath();
884            File fileFromList = new File(canonicalPath);
885            if (fileFromList.isDirectory()) {
886                if (file.isDirectory()) {
887                    // If they are both directories check exact equals.
888                    return file.equals(fileFromList);
889                } else {
890                    // O/w we have to check if the file is within the directory from the list.
891                    return file.getCanonicalPath().startsWith(canonicalPath);
892                }
893            } else {
894                if (file.equals(fileFromList)) {
895                    // Need to check the explicit "equals" so we don't end up with substrings.
896                    return true;
897                }
898            }
899        }
900        return false;
901    }
902
903    /**
904     * Only specialized platform agents should overload this entry point to support
905     * restores to crazy non-app locations.
906     * @hide
907     */
908    protected void onRestoreFile(ParcelFileDescriptor data, long size,
909            int type, String domain, String path, long mode, long mtime)
910            throws IOException {
911        String basePath = null;
912
913        if (DEBUG) Log.d(TAG, "onRestoreFile() size=" + size + " type=" + type
914                + " domain=" + domain + " relpath=" + path + " mode=" + mode
915                + " mtime=" + mtime);
916
917        basePath = FullBackup.getBackupScheme(this).tokenToDirectoryPath(domain);
918        if (domain.equals(FullBackup.MANAGED_EXTERNAL_TREE_TOKEN)) {
919            mode = -1;  // < 0 is a token to skip attempting a chmod()
920        }
921
922        // Now that we've figured out where the data goes, send it on its way
923        if (basePath != null) {
924            // Canonicalize the nominal path and verify that it lies within the stated domain
925            File outFile = new File(basePath, path);
926            String outPath = outFile.getCanonicalPath();
927            if (outPath.startsWith(basePath + File.separatorChar)) {
928                if (DEBUG) Log.i(TAG, "[" + domain + " : " + path + "] mapped to " + outPath);
929                onRestoreFile(data, size, outFile, type, mode, mtime);
930                return;
931            } else {
932                // Attempt to restore to a path outside the file's nominal domain.
933                if (DEBUG) {
934                    Log.e(TAG, "Cross-domain restore attempt: " + outPath);
935                }
936            }
937        }
938
939        // Not a supported output location, or bad path:  we need to consume the data
940        // anyway, so just use the default "copy the data out" implementation
941        // with a null destination.
942        if (DEBUG) Log.i(TAG, "[ skipping file " + path + "]");
943        FullBackup.restoreFile(data, size, type, mode, mtime, null);
944    }
945
946    /**
947     * The application's restore operation has completed.  This method is called after
948     * all available data has been delivered to the application for restore (via either
949     * the {@link #onRestore(BackupDataInput, int, ParcelFileDescriptor) onRestore()} or
950     * {@link #onRestoreFile(ParcelFileDescriptor, long, File, int, long, long) onRestoreFile()}
951     * callbacks).  This provides the app with a stable end-of-restore opportunity to
952     * perform any appropriate post-processing on the data that was just delivered.
953     *
954     * @see #onRestore(BackupDataInput, int, ParcelFileDescriptor)
955     * @see #onRestoreFile(ParcelFileDescriptor, long, File, int, long, long)
956     */
957    public void onRestoreFinished() {
958    }
959
960    // ----- Core implementation -----
961
962    /** @hide */
963    public final IBinder onBind() {
964        return mBinder;
965    }
966
967    private final IBinder mBinder = new BackupServiceBinder().asBinder();
968
969    /** @hide */
970    public void attach(Context context) {
971        attachBaseContext(context);
972    }
973
974    // ----- IBackupService binder interface -----
975    private class BackupServiceBinder extends IBackupAgent.Stub {
976        private static final String TAG = "BackupServiceBinder";
977
978        @Override
979        public void doBackup(ParcelFileDescriptor oldState,
980                ParcelFileDescriptor data,
981                ParcelFileDescriptor newState,
982                long quotaBytes, int token, IBackupManager callbackBinder, int transportFlags)
983                throws RemoteException {
984            // Ensure that we're running with the app's normal permission level
985            long ident = Binder.clearCallingIdentity();
986
987            if (DEBUG) Log.v(TAG, "doBackup() invoked");
988            BackupDataOutput output = new BackupDataOutput(
989                    data.getFileDescriptor(), quotaBytes, transportFlags);
990
991            try {
992                BackupAgent.this.onBackup(oldState, output, newState);
993            } catch (IOException ex) {
994                Log.d(TAG, "onBackup (" + BackupAgent.this.getClass().getName() + ") threw", ex);
995                throw new RuntimeException(ex);
996            } catch (RuntimeException ex) {
997                Log.d(TAG, "onBackup (" + BackupAgent.this.getClass().getName() + ") threw", ex);
998                throw ex;
999            } finally {
1000                // Ensure that any SharedPreferences writes have landed after the backup,
1001                // in case the app code has side effects (since apps cannot provide this
1002                // guarantee themselves).
1003                waitForSharedPrefs();
1004
1005                Binder.restoreCallingIdentity(ident);
1006                try {
1007                    callbackBinder.opComplete(token, 0);
1008                } catch (RemoteException e) {
1009                    // we'll time out anyway, so we're safe
1010                }
1011
1012                // Don't close the fd out from under the system service if this was local
1013                if (Binder.getCallingPid() != Process.myPid()) {
1014                    IoUtils.closeQuietly(oldState);
1015                    IoUtils.closeQuietly(data);
1016                    IoUtils.closeQuietly(newState);
1017                }
1018            }
1019        }
1020
1021        @Override
1022        public void doRestore(ParcelFileDescriptor data, long appVersionCode,
1023                ParcelFileDescriptor newState,
1024                int token, IBackupManager callbackBinder) throws RemoteException {
1025            // Ensure that we're running with the app's normal permission level
1026            long ident = Binder.clearCallingIdentity();
1027
1028            if (DEBUG) Log.v(TAG, "doRestore() invoked");
1029
1030            // Ensure that any side-effect SharedPreferences writes have landed *before*
1031            // we may be about to rewrite the file out from underneath
1032            waitForSharedPrefs();
1033
1034            BackupDataInput input = new BackupDataInput(data.getFileDescriptor());
1035            try {
1036                BackupAgent.this.onRestore(input, appVersionCode, newState);
1037            } catch (IOException ex) {
1038                Log.d(TAG, "onRestore (" + BackupAgent.this.getClass().getName() + ") threw", ex);
1039                throw new RuntimeException(ex);
1040            } catch (RuntimeException ex) {
1041                Log.d(TAG, "onRestore (" + BackupAgent.this.getClass().getName() + ") threw", ex);
1042                throw ex;
1043            } finally {
1044                // And bring live SharedPreferences instances up to date
1045                reloadSharedPreferences();
1046
1047                Binder.restoreCallingIdentity(ident);
1048                try {
1049                    callbackBinder.opComplete(token, 0);
1050                } catch (RemoteException e) {
1051                    // we'll time out anyway, so we're safe
1052                }
1053
1054                if (Binder.getCallingPid() != Process.myPid()) {
1055                    IoUtils.closeQuietly(data);
1056                    IoUtils.closeQuietly(newState);
1057                }
1058            }
1059        }
1060
1061        @Override
1062        public void doFullBackup(ParcelFileDescriptor data,
1063                long quotaBytes, int token, IBackupManager callbackBinder, int transportFlags) {
1064            // Ensure that we're running with the app's normal permission level
1065            long ident = Binder.clearCallingIdentity();
1066
1067            if (DEBUG) Log.v(TAG, "doFullBackup() invoked");
1068
1069            // Ensure that any SharedPreferences writes have landed *before*
1070            // we potentially try to back up the underlying files directly.
1071            waitForSharedPrefs();
1072
1073            try {
1074                BackupAgent.this.onFullBackup(new FullBackupDataOutput(
1075                        data, quotaBytes, transportFlags));
1076            } catch (IOException ex) {
1077                Log.d(TAG, "onFullBackup (" + BackupAgent.this.getClass().getName() + ") threw", ex);
1078                throw new RuntimeException(ex);
1079            } catch (RuntimeException ex) {
1080                Log.d(TAG, "onFullBackup (" + BackupAgent.this.getClass().getName() + ") threw", ex);
1081                throw ex;
1082            } finally {
1083                // ... and then again after, as in the doBackup() case
1084                waitForSharedPrefs();
1085
1086                // Send the EOD marker indicating that there is no more data
1087                // forthcoming from this agent.
1088                try {
1089                    FileOutputStream out = new FileOutputStream(data.getFileDescriptor());
1090                    byte[] buf = new byte[4];
1091                    out.write(buf);
1092                } catch (IOException e) {
1093                    Log.e(TAG, "Unable to finalize backup stream!");
1094                }
1095
1096                Binder.restoreCallingIdentity(ident);
1097                try {
1098                    callbackBinder.opComplete(token, 0);
1099                } catch (RemoteException e) {
1100                    // we'll time out anyway, so we're safe
1101                }
1102
1103                if (Binder.getCallingPid() != Process.myPid()) {
1104                    IoUtils.closeQuietly(data);
1105                }
1106            }
1107        }
1108
1109        public void doMeasureFullBackup(long quotaBytes, int token, IBackupManager callbackBinder,
1110                int transportFlags) {
1111            // Ensure that we're running with the app's normal permission level
1112            final long ident = Binder.clearCallingIdentity();
1113            FullBackupDataOutput measureOutput =
1114                    new FullBackupDataOutput(quotaBytes, transportFlags);
1115
1116            waitForSharedPrefs();
1117            try {
1118                BackupAgent.this.onFullBackup(measureOutput);
1119            } catch (IOException ex) {
1120                Log.d(TAG, "onFullBackup[M] (" + BackupAgent.this.getClass().getName() + ") threw", ex);
1121                throw new RuntimeException(ex);
1122            } catch (RuntimeException ex) {
1123                Log.d(TAG, "onFullBackup[M] (" + BackupAgent.this.getClass().getName() + ") threw", ex);
1124                throw ex;
1125            } finally {
1126                Binder.restoreCallingIdentity(ident);
1127                try {
1128                    callbackBinder.opComplete(token, measureOutput.getSize());
1129                } catch (RemoteException e) {
1130                    // timeout, so we're safe
1131                }
1132            }
1133        }
1134
1135        @Override
1136        public void doRestoreFile(ParcelFileDescriptor data, long size,
1137                int type, String domain, String path, long mode, long mtime,
1138                int token, IBackupManager callbackBinder) throws RemoteException {
1139            long ident = Binder.clearCallingIdentity();
1140            try {
1141                BackupAgent.this.onRestoreFile(data, size, type, domain, path, mode, mtime);
1142            } catch (IOException e) {
1143                Log.d(TAG, "onRestoreFile (" + BackupAgent.this.getClass().getName() + ") threw", e);
1144                throw new RuntimeException(e);
1145            } finally {
1146                // Ensure that any side-effect SharedPreferences writes have landed
1147                waitForSharedPrefs();
1148                // And bring live SharedPreferences instances up to date
1149                reloadSharedPreferences();
1150
1151                Binder.restoreCallingIdentity(ident);
1152                try {
1153                    callbackBinder.opComplete(token, 0);
1154                } catch (RemoteException e) {
1155                    // we'll time out anyway, so we're safe
1156                }
1157
1158                if (Binder.getCallingPid() != Process.myPid()) {
1159                    IoUtils.closeQuietly(data);
1160                }
1161            }
1162        }
1163
1164        @Override
1165        public void doRestoreFinished(int token, IBackupManager callbackBinder) {
1166            long ident = Binder.clearCallingIdentity();
1167            try {
1168                BackupAgent.this.onRestoreFinished();
1169            } catch (Exception e) {
1170                Log.d(TAG, "onRestoreFinished (" + BackupAgent.this.getClass().getName() + ") threw", e);
1171                throw e;
1172            } finally {
1173                // Ensure that any side-effect SharedPreferences writes have landed
1174                waitForSharedPrefs();
1175
1176                Binder.restoreCallingIdentity(ident);
1177                try {
1178                    callbackBinder.opComplete(token, 0);
1179                } catch (RemoteException e) {
1180                    // we'll time out anyway, so we're safe
1181                }
1182            }
1183        }
1184
1185        @Override
1186        public void fail(String message) {
1187            getHandler().post(new FailRunnable(message));
1188        }
1189
1190        @Override
1191        public void doQuotaExceeded(long backupDataBytes, long quotaBytes) {
1192            long ident = Binder.clearCallingIdentity();
1193            try {
1194                BackupAgent.this.onQuotaExceeded(backupDataBytes, quotaBytes);
1195            } catch (Exception e) {
1196                Log.d(TAG, "onQuotaExceeded(" + BackupAgent.this.getClass().getName() + ") threw",
1197                        e);
1198                throw e;
1199            } finally {
1200                waitForSharedPrefs();
1201                Binder.restoreCallingIdentity(ident);
1202            }
1203        }
1204    }
1205
1206    static class FailRunnable implements Runnable {
1207        private String mMessage;
1208
1209        FailRunnable(String message) {
1210            mMessage = message;
1211        }
1212
1213        @Override
1214        public void run() {
1215            throw new IllegalStateException(mMessage);
1216        }
1217    }
1218}
1219