Home | History | Annotate | Download | only in backup
      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 
     17 package android.app.backup;
     18 
     19 import android.app.IBackupAgent;
     20 import android.app.QueuedWork;
     21 import android.app.backup.IBackupManager;
     22 import android.content.Context;
     23 import android.content.ContextWrapper;
     24 import android.content.pm.ApplicationInfo;
     25 import android.os.Binder;
     26 import android.os.Handler;
     27 import android.os.IBinder;
     28 import android.os.Looper;
     29 import android.os.ParcelFileDescriptor;
     30 import android.os.Process;
     31 import android.os.RemoteException;
     32 import android.system.ErrnoException;
     33 import android.system.Os;
     34 import android.system.OsConstants;
     35 import android.system.StructStat;
     36 import android.util.ArraySet;
     37 import android.util.Log;
     38 
     39 import java.io.File;
     40 import java.io.FileOutputStream;
     41 import java.io.IOException;
     42 import java.util.Collection;
     43 import java.util.LinkedList;
     44 import java.util.List;
     45 import java.util.Map;
     46 import java.util.Set;
     47 import java.util.concurrent.CountDownLatch;
     48 
     49 import 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  */
    112 public 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