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