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