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.IBackupManager;
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 java.io.File;
40import java.io.FileOutputStream;
41import java.io.IOException;
42import java.util.Collection;
43import java.util.LinkedList;
44import java.util.List;
45import java.util.Map;
46import java.util.Set;
47import java.util.concurrent.CountDownLatch;
48
49import org.xmlpull.v1.XmlPullParserException;
50
51/**
52 * Provides the central interface between an
53 * application and Android's data backup infrastructure.  An application that wishes
54 * to participate in the backup and restore mechanism will declare a subclass of
55 * {@link android.app.backup.BackupAgent}, implement the
56 * {@link #onBackup(ParcelFileDescriptor, BackupDataOutput, ParcelFileDescriptor) onBackup()}
57 * and {@link #onRestore(BackupDataInput, int, ParcelFileDescriptor) onRestore()} methods,
58 * and provide the name of its backup agent class in its {@code AndroidManifest.xml} file via
59 * the <code>
60 * <a href="{@docRoot}guide/topics/manifest/application-element.html">&lt;application&gt;</a></code>
61 * tag's {@code android:backupAgent} attribute.
62 *
63 * <div class="special reference">
64 * <h3>Developer Guides</h3>
65 * <p>For more information about using BackupAgent, read the
66 * <a href="{@docRoot}guide/topics/data/backup.html">Data Backup</a> developer guide.</p></div>
67 *
68 * <h3>Basic Operation</h3>
69 * <p>
70 * When the application makes changes to data that it wishes to keep backed up,
71 * it should call the
72 * {@link android.app.backup.BackupManager#dataChanged() BackupManager.dataChanged()} method.
73 * This notifies the Android Backup Manager that the application needs an opportunity
74 * to update its backup image.  The Backup Manager, in turn, schedules a
75 * backup pass to be performed at an opportune time.
76 * <p>
77 * Restore operations are typically performed only when applications are first
78 * installed on a device.  At that time, the operating system checks to see whether
79 * there is a previously-saved data set available for the application being installed, and if so,
80 * begins an immediate restore pass to deliver the backup data as part of the installation
81 * process.
82 * <p>
83 * When a backup or restore pass is run, the application's process is launched
84 * (if not already running), the manifest-declared backup agent class (in the {@code
85 * android:backupAgent} attribute) is instantiated within
86 * that process, and the agent's {@link #onCreate()} method is invoked.  This prepares the
87 * agent instance to run the actual backup or restore logic.  At this point the
88 * agent's
89 * {@link #onBackup(ParcelFileDescriptor, BackupDataOutput, ParcelFileDescriptor) onBackup()} or
90 * {@link #onRestore(BackupDataInput, int, ParcelFileDescriptor) onRestore()} method will be
91 * invoked as appropriate for the operation being performed.
92 * <p>
93 * A backup data set consists of one or more "entities," flattened binary data
94 * records that are each identified with a key string unique within the data set.  Adding a
95 * record to the active data set or updating an existing record is done by simply
96 * writing new entity data under the desired key.  Deleting an entity from the data set
97 * is done by writing an entity under that key with header specifying a negative data
98 * size, and no actual entity data.
99 * <p>
100 * <b>Helper Classes</b>
101 * <p>
102 * An extensible agent based on convenient helper classes is available in
103 * {@link android.app.backup.BackupAgentHelper}.  That class is particularly
104 * suited to handling of simple file or {@link android.content.SharedPreferences}
105 * backup and restore.
106 *
107 * @see android.app.backup.BackupManager
108 * @see android.app.backup.BackupAgentHelper
109 * @see android.app.backup.BackupDataInput
110 * @see android.app.backup.BackupDataOutput
111 */
112public abstract class BackupAgent extends ContextWrapper {
113    private static final String TAG = "BackupAgent";
114    private static final boolean DEBUG = false;
115
116    /** @hide */
117    public static final int TYPE_EOF = 0;
118
119    /**
120     * During a full restore, indicates that the file system object being restored
121     * is an ordinary file.
122     */
123    public static final int TYPE_FILE = 1;
124
125    /**
126     * During a full restore, indicates that the file system object being restored
127     * is a directory.
128     */
129    public static final int TYPE_DIRECTORY = 2;
130
131    /** @hide */
132    public static final int TYPE_SYMLINK = 3;
133
134    Handler mHandler = null;
135
136    Handler getHandler() {
137        if (mHandler == null) {
138            mHandler = new Handler(Looper.getMainLooper());
139        }
140        return mHandler;
141    }
142
143    class SharedPrefsSynchronizer implements Runnable {
144        public final CountDownLatch mLatch = new CountDownLatch(1);
145
146        @Override
147        public void run() {
148            QueuedWork.waitToFinish();
149            mLatch.countDown();
150        }
151    };
152
153    // Syncing shared preferences deferred writes needs to happen on the main looper thread
154    private void waitForSharedPrefs() {
155        Handler h = getHandler();
156        final SharedPrefsSynchronizer s = new SharedPrefsSynchronizer();
157        h.postAtFrontOfQueue(s);
158        try {
159            s.mLatch.await();
160        } catch (InterruptedException e) { /* ignored */ }
161    }
162
163
164    public BackupAgent() {
165        super(null);
166    }
167
168    /**
169     * Provided as a convenience for agent implementations that need an opportunity
170     * to do one-time initialization before the actual backup or restore operation
171     * is begun.
172     * <p>
173     */
174    public void onCreate() {
175    }
176
177    /**
178     * Provided as a convenience for agent implementations that need to do some
179     * sort of shutdown process after backup or restore is completed.
180     * <p>
181     * Agents do not need to override this method.
182     */
183    public void onDestroy() {
184    }
185
186    /**
187     * The application is being asked to write any data changed since the last
188     * time it performed a backup operation. The state data recorded during the
189     * last backup pass is provided in the <code>oldState</code> file
190     * descriptor. If <code>oldState</code> is <code>null</code>, no old state
191     * is available and the application should perform a full backup. In both
192     * cases, a representation of the final backup state after this pass should
193     * be written to the file pointed to by the file descriptor wrapped in
194     * <code>newState</code>.
195     * <p>
196     * Each entity written to the {@link android.app.backup.BackupDataOutput}
197     * <code>data</code> stream will be transmitted
198     * over the current backup transport and stored in the remote data set under
199     * the key supplied as part of the entity.  Writing an entity with a negative
200     * data size instructs the transport to delete whatever entity currently exists
201     * under that key from the remote data set.
202     *
203     * @param oldState An open, read-only ParcelFileDescriptor pointing to the
204     *            last backup state provided by the application. May be
205     *            <code>null</code>, in which case no prior state is being
206     *            provided and the application should perform a full backup.
207     * @param data A structured wrapper around an open, read/write
208     *            file descriptor pointing to the backup data destination.
209     *            Typically the application will use backup helper classes to
210     *            write to this file.
211     * @param newState An open, read/write ParcelFileDescriptor pointing to an
212     *            empty file. The application should record the final backup
213     *            state here after writing the requested data to the <code>data</code>
214     *            output stream.
215     */
216    public abstract void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
217            ParcelFileDescriptor newState) throws IOException;
218
219    /**
220     * The application is being restored from backup and should replace any
221     * existing data with the contents of the backup. The backup data is
222     * provided through the <code>data</code> parameter. Once
223     * the restore is finished, the application should write a representation of
224     * the final state to the <code>newState</code> file descriptor.
225     * <p>
226     * The application is responsible for properly erasing its old data and
227     * replacing it with the data supplied to this method. No "clear user data"
228     * operation will be performed automatically by the operating system. The
229     * exception to this is in the case of a failed restore attempt: if
230     * onRestore() throws an exception, the OS will assume that the
231     * application's data may now be in an incoherent state, and will clear it
232     * before proceeding.
233     *
234     * @param data A structured wrapper around an open, read-only
235     *            file descriptor pointing to a full snapshot of the
236     *            application's data.  The application should consume every
237     *            entity represented in this data stream.
238     * @param appVersionCode The value of the <a
239     * href="{@docRoot}guide/topics/manifest/manifest-element.html#vcode">{@code
240     *            android:versionCode}</a> manifest attribute,
241     *            from the application that backed up this particular data set. This
242     *            makes it possible for an application's agent to distinguish among any
243     *            possible older data versions when asked to perform the restore
244     *            operation.
245     * @param newState An open, read/write ParcelFileDescriptor pointing to an
246     *            empty file. The application should record the final backup
247     *            state here after restoring its data from the <code>data</code> stream.
248     *            When a full-backup dataset is being restored, this will be <code>null</code>.
249     */
250    public abstract void onRestore(BackupDataInput data, int appVersionCode,
251            ParcelFileDescriptor newState) throws IOException;
252
253    /**
254     * The application is having its entire file system contents backed up.  {@code data}
255     * points to the backup destination, and the app has the opportunity to choose which
256     * files are to be stored.  To commit a file as part of the backup, call the
257     * {@link #fullBackupFile(File, FullBackupDataOutput)} helper method.  After all file
258     * data is written to the output, the agent returns from this method and the backup
259     * operation concludes.
260     *
261     * <p>Certain parts of the app's data are never backed up even if the app explicitly
262     * sends them to the output:
263     *
264     * <ul>
265     * <li>The contents of the {@link #getCacheDir()} directory</li>
266     * <li>The contents of the {@link #getCodeCacheDir()} directory</li>
267     * <li>The contents of the {@link #getNoBackupFilesDir()} directory</li>
268     * <li>The contents of the app's shared library directory</li>
269     * </ul>
270     *
271     * <p>The default implementation of this method backs up the entirety of the
272     * application's "owned" file system trees to the output other than the few exceptions
273     * listed above.  Apps only need to override this method if they need to impose special
274     * limitations on which files are being stored beyond the control that
275     * {@link #getNoBackupFilesDir()} offers.
276     * Alternatively they can provide an xml resource to specify what data to include or exclude.
277     *
278     *
279     * @param data A structured wrapper pointing to the backup destination.
280     * @throws IOException
281     *
282     * @see Context#getNoBackupFilesDir()
283     * @see ApplicationInfo#fullBackupContent
284     * @see #fullBackupFile(File, FullBackupDataOutput)
285     * @see #onRestoreFile(ParcelFileDescriptor, long, File, int, long, long)
286     */
287    public void onFullBackup(FullBackupDataOutput data) throws IOException {
288        FullBackup.BackupScheme backupScheme = FullBackup.getBackupScheme(this);
289        if (!backupScheme.isFullBackupContentEnabled()) {
290            return;
291        }
292
293        Map<String, Set<String>> manifestIncludeMap;
294        ArraySet<String> manifestExcludeSet;
295        try {
296            manifestIncludeMap =
297                    backupScheme.maybeParseAndGetCanonicalIncludePaths();
298            manifestExcludeSet = backupScheme.maybeParseAndGetCanonicalExcludePaths();
299        } catch (IOException | XmlPullParserException e) {
300            if (Log.isLoggable(FullBackup.TAG_XML_PARSER, Log.VERBOSE)) {
301                Log.v(FullBackup.TAG_XML_PARSER,
302                        "Exception trying to parse fullBackupContent xml file!"
303                                + " Aborting full backup.", e);
304            }
305            return;
306        }
307
308        final String packageName = getPackageName();
309        final ApplicationInfo appInfo = getApplicationInfo();
310
311        String rootDir = new File(appInfo.dataDir).getCanonicalPath();
312        String filesDir = getFilesDir().getCanonicalPath();
313        String nobackupDir = getNoBackupFilesDir().getCanonicalPath();
314        String databaseDir = getDatabasePath("foo").getParentFile().getCanonicalPath();
315        String sharedPrefsDir = getSharedPrefsFile("foo").getParentFile().getCanonicalPath();
316        String cacheDir = getCacheDir().getCanonicalPath();
317        String codeCacheDir = getCodeCacheDir().getCanonicalPath();
318        String libDir = (appInfo.nativeLibraryDir != null)
319                ? new File(appInfo.nativeLibraryDir).getCanonicalPath()
320                : null;
321
322        // Maintain a set of excluded directories so that as we traverse the tree we know we're not
323        // going places we don't expect, and so the manifest includes can't take precedence over
324        // what the framework decides is not to be included.
325        final ArraySet<String> traversalExcludeSet = new ArraySet<String>();
326
327        // Add the directories we always exclude.
328        traversalExcludeSet.add(cacheDir);
329        traversalExcludeSet.add(codeCacheDir);
330        traversalExcludeSet.add(nobackupDir);
331        if (libDir != null) {
332            traversalExcludeSet.add(libDir);
333        }
334
335        traversalExcludeSet.add(databaseDir);
336        traversalExcludeSet.add(sharedPrefsDir);
337        traversalExcludeSet.add(filesDir);
338
339        // Root dir first.
340        applyXmlFiltersAndDoFullBackupForDomain(
341                packageName, FullBackup.ROOT_TREE_TOKEN, manifestIncludeMap,
342                manifestExcludeSet, traversalExcludeSet, data);
343        traversalExcludeSet.add(rootDir);
344
345        // Data dir next.
346        traversalExcludeSet.remove(filesDir);
347        applyXmlFiltersAndDoFullBackupForDomain(
348                packageName, FullBackup.DATA_TREE_TOKEN, manifestIncludeMap,
349                manifestExcludeSet, traversalExcludeSet, data);
350        traversalExcludeSet.add(filesDir);
351
352        // Database directory.
353        traversalExcludeSet.remove(databaseDir);
354        applyXmlFiltersAndDoFullBackupForDomain(
355                packageName, FullBackup.DATABASE_TREE_TOKEN, manifestIncludeMap,
356                manifestExcludeSet, traversalExcludeSet, data);
357        traversalExcludeSet.add(databaseDir);
358
359        // SharedPrefs.
360        traversalExcludeSet.remove(sharedPrefsDir);
361        applyXmlFiltersAndDoFullBackupForDomain(
362                packageName, FullBackup.SHAREDPREFS_TREE_TOKEN, manifestIncludeMap,
363                manifestExcludeSet, traversalExcludeSet, data);
364        traversalExcludeSet.add(sharedPrefsDir);
365
366        // getExternalFilesDir() location associated with this app.  Technically there should
367        // not be any files here if the app does not properly have permission to access
368        // external storage, but edge cases happen. fullBackupFileTree() catches
369        // IOExceptions and similar, and treats them as non-fatal, so we rely on that; and
370        // we know a priori that processes running as the system UID are not permitted to
371        // access external storage, so we check for that as well to avoid nastygrams in
372        // the log.
373        if (Process.myUid() != Process.SYSTEM_UID) {
374            File efLocation = getExternalFilesDir(null);
375            if (efLocation != null) {
376                applyXmlFiltersAndDoFullBackupForDomain(
377                        packageName, FullBackup.MANAGED_EXTERNAL_TREE_TOKEN, manifestIncludeMap,
378                        manifestExcludeSet, traversalExcludeSet, data);
379            }
380
381        }
382    }
383
384    /**
385     * Check whether the xml yielded any <include/> tag for the provided <code>domainToken</code>.
386     * If so, perform a {@link #fullBackupFileTree} which backs up the file or recurses if the path
387     * is a directory.
388     */
389    private void applyXmlFiltersAndDoFullBackupForDomain(String packageName, String domainToken,
390                                                         Map<String, Set<String>> includeMap,
391                                                         ArraySet<String> filterSet,
392                                                         ArraySet<String> traversalExcludeSet,
393                                                         FullBackupDataOutput data)
394            throws IOException {
395        if (includeMap == null || includeMap.size() == 0) {
396            // Do entire sub-tree for the provided token.
397            fullBackupFileTree(packageName, domainToken,
398                    FullBackup.getBackupScheme(this).tokenToDirectoryPath(domainToken),
399                    filterSet, traversalExcludeSet, data);
400        } else if (includeMap.get(domainToken) != null) {
401            // This will be null if the xml parsing didn't yield any rules for
402            // this domain (there may still be rules for other domains).
403            for (String includeFile : includeMap.get(domainToken)) {
404                fullBackupFileTree(packageName, domainToken, includeFile, filterSet,
405                        traversalExcludeSet, data);
406            }
407        }
408    }
409
410    /**
411     * Write an entire file as part of a full-backup operation.  The file's contents
412     * will be delivered to the backup destination along with the metadata necessary
413     * to place it with the proper location and permissions on the device where the
414     * data is restored.
415     *
416     * <p class="note">It is safe to explicitly back up files underneath your application's
417     * {@link #getNoBackupFilesDir()} directory, and they will be restored to that
418     * location correctly.
419     *
420     * @param file The file to be backed up.  The file must exist and be readable by
421     *     the caller.
422     * @param output The destination to which the backed-up file data will be sent.
423     */
424    public final void fullBackupFile(File file, FullBackupDataOutput output) {
425        // Look up where all of our various well-defined dir trees live on this device
426        String mainDir;
427        String filesDir;
428        String nbFilesDir;
429        String dbDir;
430        String spDir;
431        String cacheDir;
432        String codeCacheDir;
433        String libDir;
434        String efDir = null;
435        String filePath;
436
437        ApplicationInfo appInfo = getApplicationInfo();
438
439        try {
440            mainDir = new File(appInfo.dataDir).getCanonicalPath();
441            filesDir = getFilesDir().getCanonicalPath();
442            nbFilesDir = getNoBackupFilesDir().getCanonicalPath();
443            dbDir = getDatabasePath("foo").getParentFile().getCanonicalPath();
444            spDir = getSharedPrefsFile("foo").getParentFile().getCanonicalPath();
445            cacheDir = getCacheDir().getCanonicalPath();
446            codeCacheDir = getCodeCacheDir().getCanonicalPath();
447            libDir = (appInfo.nativeLibraryDir == null)
448                    ? null
449                    : new File(appInfo.nativeLibraryDir).getCanonicalPath();
450
451            // may or may not have external files access to attempt backup/restore there
452            if (Process.myUid() != Process.SYSTEM_UID) {
453                File efLocation = getExternalFilesDir(null);
454                if (efLocation != null) {
455                    efDir = efLocation.getCanonicalPath();
456                }
457            }
458
459            // Now figure out which well-defined tree the file is placed in, working from
460            // most to least specific.  We also specifically exclude the lib, cache,
461            // and code_cache dirs.
462            filePath = file.getCanonicalPath();
463        } catch (IOException e) {
464            Log.w(TAG, "Unable to obtain canonical paths");
465            return;
466        }
467
468        if (filePath.startsWith(cacheDir)
469                || filePath.startsWith(codeCacheDir)
470                || filePath.startsWith(libDir)
471                || filePath.startsWith(nbFilesDir)) {
472            Log.w(TAG, "lib, cache, code_cache, and no_backup files are not backed up");
473            return;
474        }
475
476        final String domain;
477        String rootpath = null;
478        if (filePath.startsWith(dbDir)) {
479            domain = FullBackup.DATABASE_TREE_TOKEN;
480            rootpath = dbDir;
481        } else if (filePath.startsWith(spDir)) {
482            domain = FullBackup.SHAREDPREFS_TREE_TOKEN;
483            rootpath = spDir;
484        } else if (filePath.startsWith(filesDir)) {
485            domain = FullBackup.DATA_TREE_TOKEN;
486            rootpath = filesDir;
487        } else if (filePath.startsWith(mainDir)) {
488            domain = FullBackup.ROOT_TREE_TOKEN;
489            rootpath = mainDir;
490        } else if ((efDir != null) && filePath.startsWith(efDir)) {
491            domain = FullBackup.MANAGED_EXTERNAL_TREE_TOKEN;
492            rootpath = efDir;
493        } else {
494            Log.w(TAG, "File " + filePath + " is in an unsupported location; skipping");
495            return;
496        }
497
498        // And now that we know where it lives, semantically, back it up appropriately
499        // In the measurement case, backupToTar() updates the size in output and returns
500        // without transmitting any file data.
501        if (DEBUG) Log.i(TAG, "backupFile() of " + filePath + " => domain=" + domain
502                + " rootpath=" + rootpath);
503
504        FullBackup.backupToTar(getPackageName(), domain, null, rootpath, filePath, output);
505    }
506
507    /**
508     * Scan the dir tree (if it actually exists) and process each entry we find.  If the
509     * 'excludes' parameters are non-null, they are consulted each time a new file system entity
510     * is visited to see whether that entity (and its subtree, if appropriate) should be
511     * omitted from the backup process.
512     *
513     * @param systemExcludes An optional list of excludes.
514     * @hide
515     */
516    protected final void fullBackupFileTree(String packageName, String domain, String startingPath,
517                                            ArraySet<String> manifestExcludes,
518                                            ArraySet<String> systemExcludes,
519            FullBackupDataOutput output) {
520        // Pull out the domain and set it aside to use when making the tarball.
521        String domainPath = FullBackup.getBackupScheme(this).tokenToDirectoryPath(domain);
522        if (domainPath == null) {
523            // Should never happen.
524            return;
525        }
526
527        File rootFile = new File(startingPath);
528        if (rootFile.exists()) {
529            LinkedList<File> scanQueue = new LinkedList<File>();
530            scanQueue.add(rootFile);
531
532            while (scanQueue.size() > 0) {
533                File file = scanQueue.remove(0);
534                String filePath;
535                try {
536                    filePath = file.getCanonicalPath();
537
538                    // prune this subtree?
539                    if (manifestExcludes != null && manifestExcludes.contains(filePath)) {
540                        continue;
541                    }
542                    if (systemExcludes != null && systemExcludes.contains(filePath)) {
543                        continue;
544                    }
545
546                    // If it's a directory, enqueue its contents for scanning.
547                    StructStat stat = Os.lstat(filePath);
548                    if (OsConstants.S_ISLNK(stat.st_mode)) {
549                        if (DEBUG) Log.i(TAG, "Symlink (skipping)!: " + file);
550                        continue;
551                    } else if (OsConstants.S_ISDIR(stat.st_mode)) {
552                        File[] contents = file.listFiles();
553                        if (contents != null) {
554                            for (File entry : contents) {
555                                scanQueue.add(0, entry);
556                            }
557                        }
558                    }
559                } catch (IOException e) {
560                    if (DEBUG) Log.w(TAG, "Error canonicalizing path of " + file);
561                    if (Log.isLoggable(FullBackup.TAG_XML_PARSER, Log.VERBOSE)) {
562                        Log.v(FullBackup.TAG_XML_PARSER, "Error canonicalizing path of " + file);
563                    }
564                    continue;
565                } catch (ErrnoException e) {
566                    if (DEBUG) Log.w(TAG, "Error scanning file " + file + " : " + e);
567                    if (Log.isLoggable(FullBackup.TAG_XML_PARSER, Log.VERBOSE)) {
568                        Log.v(FullBackup.TAG_XML_PARSER, "Error scanning file " + file + " : " + e);
569                    }
570                    continue;
571                }
572
573                // Finally, back this file up (or measure it) before proceeding
574                FullBackup.backupToTar(packageName, domain, null, domainPath, filePath, output);
575            }
576        }
577    }
578
579    /**
580     * Handle the data delivered via the given file descriptor during a full restore
581     * operation.  The agent is given the path to the file's original location as well
582     * as its size and metadata.
583     * <p>
584     * The file descriptor can only be read for {@code size} bytes; attempting to read
585     * more data has undefined behavior.
586     * <p>
587     * The default implementation creates the destination file/directory and populates it
588     * with the data from the file descriptor, then sets the file's access mode and
589     * modification time to match the restore arguments.
590     *
591     * @param data A read-only file descriptor from which the agent can read {@code size}
592     *     bytes of file data.
593     * @param size The number of bytes of file content to be restored to the given
594     *     destination.  If the file system object being restored is a directory, {@code size}
595     *     will be zero.
596     * @param destination The File on disk to be restored with the given data.
597     * @param type The kind of file system object being restored.  This will be either
598     *     {@link BackupAgent#TYPE_FILE} or {@link BackupAgent#TYPE_DIRECTORY}.
599     * @param mode The access mode to be assigned to the destination after its data is
600     *     written.  This is in the standard format used by {@code chmod()}.
601     * @param mtime The modification time of the file when it was backed up, suitable to
602     *     be assigned to the file after its data is written.
603     * @throws IOException
604     */
605    public void onRestoreFile(ParcelFileDescriptor data, long size,
606            File destination, int type, long mode, long mtime)
607            throws IOException {
608
609        final boolean accept = isFileEligibleForRestore(destination);
610        // If we don't accept the file, consume the bytes from the pipe anyway.
611        FullBackup.restoreFile(data, size, type, mode, mtime, accept ? destination : null);
612    }
613
614    private boolean isFileEligibleForRestore(File destination) throws IOException {
615        FullBackup.BackupScheme bs = FullBackup.getBackupScheme(this);
616        if (!bs.isFullBackupContentEnabled()) {
617            if (Log.isLoggable(FullBackup.TAG_XML_PARSER, Log.VERBOSE)) {
618                Log.v(FullBackup.TAG_XML_PARSER,
619                        "onRestoreFile \"" + destination.getCanonicalPath()
620                                + "\" : fullBackupContent not enabled for " + getPackageName());
621            }
622            return false;
623        }
624
625        Map<String, Set<String>> includes = null;
626        ArraySet<String> excludes = null;
627        final String destinationCanonicalPath = destination.getCanonicalPath();
628        try {
629            includes = bs.maybeParseAndGetCanonicalIncludePaths();
630            excludes = bs.maybeParseAndGetCanonicalExcludePaths();
631        } catch (XmlPullParserException e) {
632            if (Log.isLoggable(FullBackup.TAG_XML_PARSER, Log.VERBOSE)) {
633                Log.v(FullBackup.TAG_XML_PARSER,
634                        "onRestoreFile \"" + destinationCanonicalPath
635                                + "\" : Exception trying to parse fullBackupContent xml file!"
636                                + " Aborting onRestoreFile.", e);
637            }
638            return false;
639        }
640
641        if (excludes != null &&
642                isFileSpecifiedInPathList(destination, excludes)) {
643            if (Log.isLoggable(FullBackup.TAG_XML_PARSER, Log.VERBOSE)) {
644                Log.v(FullBackup.TAG_XML_PARSER,
645                        "onRestoreFile: \"" + destinationCanonicalPath + "\": listed in"
646                                + " excludes; skipping.");
647            }
648            return false;
649        }
650
651        if (includes != null && !includes.isEmpty()) {
652            // Rather than figure out the <include/> domain based on the path (a lot of code, and
653            // it's a small list), we'll go through and look for it.
654            boolean explicitlyIncluded = false;
655            for (Set<String> domainIncludes : includes.values()) {
656                explicitlyIncluded |= isFileSpecifiedInPathList(destination, domainIncludes);
657                if (explicitlyIncluded) {
658                    break;
659                }
660            }
661            if (!explicitlyIncluded) {
662                if (Log.isLoggable(FullBackup.TAG_XML_PARSER, Log.VERBOSE)) {
663                    Log.v(FullBackup.TAG_XML_PARSER,
664                            "onRestoreFile: Trying to restore \""
665                                    + destinationCanonicalPath + "\" but it isn't specified"
666                                    + " in the included files; skipping.");
667                }
668                return false;
669            }
670        }
671        return true;
672    }
673
674    /**
675     * @return True if the provided file is either directly in the provided list, or the provided
676     * file is within a directory in the list.
677     */
678    private boolean isFileSpecifiedInPathList(File file, Collection<String> canonicalPathList)
679            throws IOException {
680        for (String canonicalPath : canonicalPathList) {
681            File fileFromList = new File(canonicalPath);
682            if (fileFromList.isDirectory()) {
683                if (file.isDirectory()) {
684                    // If they are both directories check exact equals.
685                    return file.equals(fileFromList);
686                } else {
687                    // O/w we have to check if the file is within the directory from the list.
688                    return file.getCanonicalPath().startsWith(canonicalPath);
689                }
690            } else {
691                if (file.equals(fileFromList)) {
692                    // Need to check the explicit "equals" so we don't end up with substrings.
693                    return true;
694                }
695            }
696        }
697        return false;
698    }
699
700    /**
701     * Only specialized platform agents should overload this entry point to support
702     * restores to crazy non-app locations.
703     * @hide
704     */
705    protected void onRestoreFile(ParcelFileDescriptor data, long size,
706            int type, String domain, String path, long mode, long mtime)
707            throws IOException {
708        String basePath = null;
709
710        if (DEBUG) Log.d(TAG, "onRestoreFile() size=" + size + " type=" + type
711                + " domain=" + domain + " relpath=" + path + " mode=" + mode
712                + " mtime=" + mtime);
713
714        basePath = FullBackup.getBackupScheme(this).tokenToDirectoryPath(domain);
715        if (domain.equals(FullBackup.MANAGED_EXTERNAL_TREE_TOKEN)) {
716            mode = -1;  // < 0 is a token to skip attempting a chmod()
717        }
718
719        // Now that we've figured out where the data goes, send it on its way
720        if (basePath != null) {
721            // Canonicalize the nominal path and verify that it lies within the stated domain
722            File outFile = new File(basePath, path);
723            String outPath = outFile.getCanonicalPath();
724            if (outPath.startsWith(basePath + File.separatorChar)) {
725                if (DEBUG) Log.i(TAG, "[" + domain + " : " + path + "] mapped to " + outPath);
726                onRestoreFile(data, size, outFile, type, mode, mtime);
727                return;
728            } else {
729                // Attempt to restore to a path outside the file's nominal domain.
730                if (DEBUG) {
731                    Log.e(TAG, "Cross-domain restore attempt: " + outPath);
732                }
733            }
734        }
735
736        // Not a supported output location, or bad path:  we need to consume the data
737        // anyway, so just use the default "copy the data out" implementation
738        // with a null destination.
739        if (DEBUG) Log.i(TAG, "[ skipping file " + path + "]");
740        FullBackup.restoreFile(data, size, type, mode, mtime, null);
741    }
742
743    /**
744     * The application's restore operation has completed.  This method is called after
745     * all available data has been delivered to the application for restore (via either
746     * the {@link #onRestore(BackupDataInput, int, ParcelFileDescriptor) onRestore()} or
747     * {@link #onRestoreFile(ParcelFileDescriptor, long, File, int, long, long) onRestoreFile()}
748     * callbacks).  This provides the app with a stable end-of-restore opportunity to
749     * perform any appropriate post-processing on the data that was just delivered.
750     *
751     * @see #onRestore(BackupDataInput, int, ParcelFileDescriptor)
752     * @see #onRestoreFile(ParcelFileDescriptor, long, File, int, long, long)
753     */
754    public void onRestoreFinished() {
755    }
756
757    // ----- Core implementation -----
758
759    /** @hide */
760    public final IBinder onBind() {
761        return mBinder;
762    }
763
764    private final IBinder mBinder = new BackupServiceBinder().asBinder();
765
766    /** @hide */
767    public void attach(Context context) {
768        attachBaseContext(context);
769    }
770
771    // ----- IBackupService binder interface -----
772    private class BackupServiceBinder extends IBackupAgent.Stub {
773        private static final String TAG = "BackupServiceBinder";
774
775        @Override
776        public void doBackup(ParcelFileDescriptor oldState,
777                ParcelFileDescriptor data,
778                ParcelFileDescriptor newState,
779                int token, IBackupManager callbackBinder) throws RemoteException {
780            // Ensure that we're running with the app's normal permission level
781            long ident = Binder.clearCallingIdentity();
782
783            if (DEBUG) Log.v(TAG, "doBackup() invoked");
784            BackupDataOutput output = new BackupDataOutput(data.getFileDescriptor());
785
786            try {
787                BackupAgent.this.onBackup(oldState, output, newState);
788            } catch (IOException ex) {
789                Log.d(TAG, "onBackup (" + BackupAgent.this.getClass().getName() + ") threw", ex);
790                throw new RuntimeException(ex);
791            } catch (RuntimeException ex) {
792                Log.d(TAG, "onBackup (" + BackupAgent.this.getClass().getName() + ") threw", ex);
793                throw ex;
794            } finally {
795                // Ensure that any SharedPreferences writes have landed after the backup,
796                // in case the app code has side effects (since apps cannot provide this
797                // guarantee themselves).
798                waitForSharedPrefs();
799
800                Binder.restoreCallingIdentity(ident);
801                try {
802                    callbackBinder.opComplete(token, 0);
803                } catch (RemoteException e) {
804                    // we'll time out anyway, so we're safe
805                }
806            }
807        }
808
809        @Override
810        public void doRestore(ParcelFileDescriptor data, int appVersionCode,
811                ParcelFileDescriptor newState,
812                int token, IBackupManager callbackBinder) throws RemoteException {
813            // Ensure that we're running with the app's normal permission level
814            long ident = Binder.clearCallingIdentity();
815
816            if (DEBUG) Log.v(TAG, "doRestore() invoked");
817            BackupDataInput input = new BackupDataInput(data.getFileDescriptor());
818            try {
819                BackupAgent.this.onRestore(input, appVersionCode, newState);
820            } catch (IOException ex) {
821                Log.d(TAG, "onRestore (" + BackupAgent.this.getClass().getName() + ") threw", ex);
822                throw new RuntimeException(ex);
823            } catch (RuntimeException ex) {
824                Log.d(TAG, "onRestore (" + BackupAgent.this.getClass().getName() + ") threw", ex);
825                throw ex;
826            } finally {
827                // Ensure that any side-effect SharedPreferences writes have landed
828                waitForSharedPrefs();
829
830                Binder.restoreCallingIdentity(ident);
831                try {
832                    callbackBinder.opComplete(token, 0);
833                } catch (RemoteException e) {
834                    // we'll time out anyway, so we're safe
835                }
836            }
837        }
838
839        @Override
840        public void doFullBackup(ParcelFileDescriptor data,
841                int token, IBackupManager callbackBinder) {
842            // Ensure that we're running with the app's normal permission level
843            long ident = Binder.clearCallingIdentity();
844
845            if (DEBUG) Log.v(TAG, "doFullBackup() invoked");
846
847            // Ensure that any SharedPreferences writes have landed *before*
848            // we potentially try to back up the underlying files directly.
849            waitForSharedPrefs();
850
851            try {
852                BackupAgent.this.onFullBackup(new FullBackupDataOutput(data));
853            } catch (IOException ex) {
854                Log.d(TAG, "onFullBackup (" + BackupAgent.this.getClass().getName() + ") threw", ex);
855                throw new RuntimeException(ex);
856            } catch (RuntimeException ex) {
857                Log.d(TAG, "onFullBackup (" + BackupAgent.this.getClass().getName() + ") threw", ex);
858                throw ex;
859            } finally {
860                // ... and then again after, as in the doBackup() case
861                waitForSharedPrefs();
862
863                // Send the EOD marker indicating that there is no more data
864                // forthcoming from this agent.
865                try {
866                    FileOutputStream out = new FileOutputStream(data.getFileDescriptor());
867                    byte[] buf = new byte[4];
868                    out.write(buf);
869                } catch (IOException e) {
870                    Log.e(TAG, "Unable to finalize backup stream!");
871                }
872
873                Binder.restoreCallingIdentity(ident);
874                try {
875                    callbackBinder.opComplete(token, 0);
876                } catch (RemoteException e) {
877                    // we'll time out anyway, so we're safe
878                }
879            }
880        }
881
882        public void doMeasureFullBackup(int token, IBackupManager callbackBinder) {
883            // Ensure that we're running with the app's normal permission level
884            final long ident = Binder.clearCallingIdentity();
885            FullBackupDataOutput measureOutput = new FullBackupDataOutput();
886
887            waitForSharedPrefs();
888            try {
889                BackupAgent.this.onFullBackup(measureOutput);
890            } catch (IOException ex) {
891                Log.d(TAG, "onFullBackup[M] (" + BackupAgent.this.getClass().getName() + ") threw", ex);
892                throw new RuntimeException(ex);
893            } catch (RuntimeException ex) {
894                Log.d(TAG, "onFullBackup[M] (" + BackupAgent.this.getClass().getName() + ") threw", ex);
895                throw ex;
896            } finally {
897                Binder.restoreCallingIdentity(ident);
898                try {
899                    callbackBinder.opComplete(token, measureOutput.getSize());
900                } catch (RemoteException e) {
901                    // timeout, so we're safe
902                }
903            }
904        }
905
906        @Override
907        public void doRestoreFile(ParcelFileDescriptor data, long size,
908                int type, String domain, String path, long mode, long mtime,
909                int token, IBackupManager callbackBinder) throws RemoteException {
910            long ident = Binder.clearCallingIdentity();
911            try {
912                BackupAgent.this.onRestoreFile(data, size, type, domain, path, mode, mtime);
913            } catch (IOException e) {
914                Log.d(TAG, "onRestoreFile (" + BackupAgent.this.getClass().getName() + ") threw", e);
915                throw new RuntimeException(e);
916            } finally {
917                // Ensure that any side-effect SharedPreferences writes have landed
918                waitForSharedPrefs();
919
920                Binder.restoreCallingIdentity(ident);
921                try {
922                    callbackBinder.opComplete(token, 0);
923                } catch (RemoteException e) {
924                    // we'll time out anyway, so we're safe
925                }
926            }
927        }
928
929        @Override
930        public void doRestoreFinished(int token, IBackupManager callbackBinder) {
931            long ident = Binder.clearCallingIdentity();
932            try {
933                BackupAgent.this.onRestoreFinished();
934            } catch (Exception e) {
935                Log.d(TAG, "onRestoreFinished (" + BackupAgent.this.getClass().getName() + ") threw", e);
936                throw e;
937            } finally {
938                // Ensure that any side-effect SharedPreferences writes have landed
939                waitForSharedPrefs();
940
941                Binder.restoreCallingIdentity(ident);
942                try {
943                    callbackBinder.opComplete(token, 0);
944                } catch (RemoteException e) {
945                    // we'll time out anyway, so we're safe
946                }
947            }
948        }
949
950        @Override
951        public void fail(String message) {
952            getHandler().post(new FailRunnable(message));
953        }
954    }
955
956    static class FailRunnable implements Runnable {
957        private String mMessage;
958
959        FailRunnable(String message) {
960            mMessage = message;
961        }
962
963        @Override
964        public void run() {
965            throw new IllegalStateException(mMessage);
966        }
967    }
968}
969