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.backup.IBackupManager;
     21 import android.content.Context;
     22 import android.content.ContextWrapper;
     23 import android.content.pm.ApplicationInfo;
     24 import android.os.Binder;
     25 import android.os.IBinder;
     26 import android.os.ParcelFileDescriptor;
     27 import android.os.RemoteException;
     28 import android.util.Log;
     29 
     30 import java.io.File;
     31 import java.io.FileOutputStream;
     32 import java.io.IOException;
     33 import java.util.HashSet;
     34 import java.util.LinkedList;
     35 
     36 import libcore.io.ErrnoException;
     37 import libcore.io.Libcore;
     38 import libcore.io.OsConstants;
     39 import libcore.io.StructStat;
     40 
     41 /**
     42  * Provides the central interface between an
     43  * application and Android's data backup infrastructure.  An application that wishes
     44  * to participate in the backup and restore mechanism will declare a subclass of
     45  * {@link android.app.backup.BackupAgent}, implement the
     46  * {@link #onBackup(ParcelFileDescriptor, BackupDataOutput, ParcelFileDescriptor) onBackup()}
     47  * and {@link #onRestore(BackupDataInput, int, ParcelFileDescriptor) onRestore()} methods,
     48  * and provide the name of its backup agent class in its {@code AndroidManifest.xml} file via
     49  * the <code>
     50  * <a href="{@docRoot}guide/topics/manifest/application-element.html">&lt;application&gt;</a></code>
     51  * tag's {@code android:backupAgent} attribute.
     52  *
     53  * <div class="special reference">
     54  * <h3>Developer Guides</h3>
     55  * <p>For more information about using BackupAgent, read the
     56  * <a href="{@docRoot}guide/topics/data/backup.html">Data Backup</a> developer guide.</p></div>
     57  *
     58  * <h3>Basic Operation</h3>
     59  * <p>
     60  * When the application makes changes to data that it wishes to keep backed up,
     61  * it should call the
     62  * {@link android.app.backup.BackupManager#dataChanged() BackupManager.dataChanged()} method.
     63  * This notifies the Android Backup Manager that the application needs an opportunity
     64  * to update its backup image.  The Backup Manager, in turn, schedules a
     65  * backup pass to be performed at an opportune time.
     66  * <p>
     67  * Restore operations are typically performed only when applications are first
     68  * installed on a device.  At that time, the operating system checks to see whether
     69  * there is a previously-saved data set available for the application being installed, and if so,
     70  * begins an immediate restore pass to deliver the backup data as part of the installation
     71  * process.
     72  * <p>
     73  * When a backup or restore pass is run, the application's process is launched
     74  * (if not already running), the manifest-declared backup agent class (in the {@code
     75  * android:backupAgent} attribute) is instantiated within
     76  * that process, and the agent's {@link #onCreate()} method is invoked.  This prepares the
     77  * agent instance to run the actual backup or restore logic.  At this point the
     78  * agent's
     79  * {@link #onBackup(ParcelFileDescriptor, BackupDataOutput, ParcelFileDescriptor) onBackup()} or
     80  * {@link #onRestore(BackupDataInput, int, ParcelFileDescriptor) onRestore()} method will be
     81  * invoked as appropriate for the operation being performed.
     82  * <p>
     83  * A backup data set consists of one or more "entities," flattened binary data
     84  * records that are each identified with a key string unique within the data set.  Adding a
     85  * record to the active data set or updating an existing record is done by simply
     86  * writing new entity data under the desired key.  Deleting an entity from the data set
     87  * is done by writing an entity under that key with header specifying a negative data
     88  * size, and no actual entity data.
     89  * <p>
     90  * <b>Helper Classes</b>
     91  * <p>
     92  * An extensible agent based on convenient helper classes is available in
     93  * {@link android.app.backup.BackupAgentHelper}.  That class is particularly
     94  * suited to handling of simple file or {@link android.content.SharedPreferences}
     95  * backup and restore.
     96  *
     97  * @see android.app.backup.BackupManager
     98  * @see android.app.backup.BackupAgentHelper
     99  * @see android.app.backup.BackupDataInput
    100  * @see android.app.backup.BackupDataOutput
    101  */
    102 public abstract class BackupAgent extends ContextWrapper {
    103     private static final String TAG = "BackupAgent";
    104     private static final boolean DEBUG = true;
    105 
    106     /** @hide */
    107     public static final int TYPE_EOF = 0;
    108 
    109     /**
    110      * During a full restore, indicates that the file system object being restored
    111      * is an ordinary file.
    112      */
    113     public static final int TYPE_FILE = 1;
    114 
    115     /**
    116      * During a full restore, indicates that the file system object being restored
    117      * is a directory.
    118      */
    119     public static final int TYPE_DIRECTORY = 2;
    120 
    121     /** @hide */
    122     public static final int TYPE_SYMLINK = 3;
    123 
    124     public BackupAgent() {
    125         super(null);
    126     }
    127 
    128     /**
    129      * Provided as a convenience for agent implementations that need an opportunity
    130      * to do one-time initialization before the actual backup or restore operation
    131      * is begun.
    132      * <p>
    133      * Agents do not need to override this method.
    134      */
    135     public void onCreate() {
    136     }
    137 
    138     /**
    139      * Provided as a convenience for agent implementations that need to do some
    140      * sort of shutdown process after backup or restore is completed.
    141      * <p>
    142      * Agents do not need to override this method.
    143      */
    144     public void onDestroy() {
    145     }
    146 
    147     /**
    148      * The application is being asked to write any data changed since the last
    149      * time it performed a backup operation. The state data recorded during the
    150      * last backup pass is provided in the <code>oldState</code> file
    151      * descriptor. If <code>oldState</code> is <code>null</code>, no old state
    152      * is available and the application should perform a full backup. In both
    153      * cases, a representation of the final backup state after this pass should
    154      * be written to the file pointed to by the file descriptor wrapped in
    155      * <code>newState</code>.
    156      * <p>
    157      * Each entity written to the {@link android.app.backup.BackupDataOutput}
    158      * <code>data</code> stream will be transmitted
    159      * over the current backup transport and stored in the remote data set under
    160      * the key supplied as part of the entity.  Writing an entity with a negative
    161      * data size instructs the transport to delete whatever entity currently exists
    162      * under that key from the remote data set.
    163      *
    164      * @param oldState An open, read-only ParcelFileDescriptor pointing to the
    165      *            last backup state provided by the application. May be
    166      *            <code>null</code>, in which case no prior state is being
    167      *            provided and the application should perform a full backup.
    168      * @param data A structured wrapper around an open, read/write
    169      *            file descriptor pointing to the backup data destination.
    170      *            Typically the application will use backup helper classes to
    171      *            write to this file.
    172      * @param newState An open, read/write ParcelFileDescriptor pointing to an
    173      *            empty file. The application should record the final backup
    174      *            state here after writing the requested data to the <code>data</code>
    175      *            output stream.
    176      */
    177     public abstract void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
    178              ParcelFileDescriptor newState) throws IOException;
    179 
    180     /**
    181      * The application is being restored from backup and should replace any
    182      * existing data with the contents of the backup. The backup data is
    183      * provided through the <code>data</code> parameter. Once
    184      * the restore is finished, the application should write a representation of
    185      * the final state to the <code>newState</code> file descriptor.
    186      * <p>
    187      * The application is responsible for properly erasing its old data and
    188      * replacing it with the data supplied to this method. No "clear user data"
    189      * operation will be performed automatically by the operating system. The
    190      * exception to this is in the case of a failed restore attempt: if
    191      * onRestore() throws an exception, the OS will assume that the
    192      * application's data may now be in an incoherent state, and will clear it
    193      * before proceeding.
    194      *
    195      * @param data A structured wrapper around an open, read-only
    196      *            file descriptor pointing to a full snapshot of the
    197      *            application's data.  The application should consume every
    198      *            entity represented in this data stream.
    199      * @param appVersionCode The value of the <a
    200      * href="{@docRoot}guide/topics/manifest/manifest-element.html#vcode">{@code
    201      *            android:versionCode}</a> manifest attribute,
    202      *            from the application that backed up this particular data set. This
    203      *            makes it possible for an application's agent to distinguish among any
    204      *            possible older data versions when asked to perform the restore
    205      *            operation.
    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 restoring its data from the <code>data</code> stream.
    209      *            When a full-backup dataset is being restored, this will be <code>null</code>.
    210      */
    211     public abstract void onRestore(BackupDataInput data, int appVersionCode,
    212             ParcelFileDescriptor newState)
    213             throws IOException;
    214 
    215     /**
    216      * The default implementation backs up the entirety of the application's "owned"
    217      * file system trees to the output.
    218      */
    219     public void onFullBackup(FullBackupDataOutput data) throws IOException {
    220         ApplicationInfo appInfo = getApplicationInfo();
    221 
    222         String rootDir = new File(appInfo.dataDir).getCanonicalPath();
    223         String filesDir = getFilesDir().getCanonicalPath();
    224         String databaseDir = getDatabasePath("foo").getParentFile().getCanonicalPath();
    225         String sharedPrefsDir = getSharedPrefsFile("foo").getParentFile().getCanonicalPath();
    226         String cacheDir = getCacheDir().getCanonicalPath();
    227         String libDir = (appInfo.nativeLibraryDir != null)
    228                 ? new File(appInfo.nativeLibraryDir).getCanonicalPath()
    229                 : null;
    230 
    231         // Filters, the scan queue, and the set of resulting entities
    232         HashSet<String> filterSet = new HashSet<String>();
    233         String packageName = getPackageName();
    234 
    235         // Okay, start with the app's root tree, but exclude all of the canonical subdirs
    236         if (libDir != null) {
    237             filterSet.add(libDir);
    238         }
    239         filterSet.add(cacheDir);
    240         filterSet.add(databaseDir);
    241         filterSet.add(sharedPrefsDir);
    242         filterSet.add(filesDir);
    243         fullBackupFileTree(packageName, FullBackup.ROOT_TREE_TOKEN, rootDir, filterSet, data);
    244 
    245         // Now do the same for the files dir, db dir, and shared prefs dir
    246         filterSet.add(rootDir);
    247         filterSet.remove(filesDir);
    248         fullBackupFileTree(packageName, FullBackup.DATA_TREE_TOKEN, filesDir, filterSet, data);
    249 
    250         filterSet.add(filesDir);
    251         filterSet.remove(databaseDir);
    252         fullBackupFileTree(packageName, FullBackup.DATABASE_TREE_TOKEN, databaseDir, filterSet, data);
    253 
    254         filterSet.add(databaseDir);
    255         filterSet.remove(sharedPrefsDir);
    256         fullBackupFileTree(packageName, FullBackup.SHAREDPREFS_TREE_TOKEN, sharedPrefsDir, filterSet, data);
    257     }
    258 
    259     /**
    260      * Write an entire file as part of a full-backup operation.  The file's contents
    261      * will be delivered to the backup destination along with the metadata necessary
    262      * to place it with the proper location and permissions on the device where the
    263      * data is restored.
    264      *
    265      * @param file The file to be backed up.  The file must exist and be readable by
    266      *     the caller.
    267      * @param output The destination to which the backed-up file data will be sent.
    268      */
    269     public final void fullBackupFile(File file, FullBackupDataOutput output) {
    270         // Look up where all of our various well-defined dir trees live on this device
    271         String mainDir;
    272         String filesDir;
    273         String dbDir;
    274         String spDir;
    275         String cacheDir;
    276         String libDir;
    277         String filePath;
    278 
    279         ApplicationInfo appInfo = getApplicationInfo();
    280 
    281         try {
    282             mainDir = new File(appInfo.dataDir).getCanonicalPath();
    283             filesDir = getFilesDir().getCanonicalPath();
    284             dbDir = getDatabasePath("foo").getParentFile().getCanonicalPath();
    285             spDir = getSharedPrefsFile("foo").getParentFile().getCanonicalPath();
    286             cacheDir = getCacheDir().getCanonicalPath();
    287             libDir = (appInfo.nativeLibraryDir == null)
    288                     ? null
    289                     : new File(appInfo.nativeLibraryDir).getCanonicalPath();
    290 
    291             // Now figure out which well-defined tree the file is placed in, working from
    292             // most to least specific.  We also specifically exclude the lib and cache dirs.
    293             filePath = file.getCanonicalPath();
    294         } catch (IOException e) {
    295             Log.w(TAG, "Unable to obtain canonical paths");
    296             return;
    297         }
    298 
    299         if (filePath.startsWith(cacheDir) || filePath.startsWith(libDir)) {
    300             Log.w(TAG, "lib and cache files are not backed up");
    301             return;
    302         }
    303 
    304         final String domain;
    305         String rootpath = null;
    306         if (filePath.startsWith(dbDir)) {
    307             domain = FullBackup.DATABASE_TREE_TOKEN;
    308             rootpath = dbDir;
    309         } else if (filePath.startsWith(spDir)) {
    310             domain = FullBackup.SHAREDPREFS_TREE_TOKEN;
    311             rootpath = spDir;
    312         } else if (filePath.startsWith(filesDir)) {
    313             domain = FullBackup.DATA_TREE_TOKEN;
    314             rootpath = filesDir;
    315         } else if (filePath.startsWith(mainDir)) {
    316             domain = FullBackup.ROOT_TREE_TOKEN;
    317             rootpath = mainDir;
    318         } else {
    319             Log.w(TAG, "File " + filePath + " is in an unsupported location; skipping");
    320             return;
    321         }
    322 
    323         // And now that we know where it lives, semantically, back it up appropriately
    324         Log.i(TAG, "backupFile() of " + filePath + " => domain=" + domain
    325                 + " rootpath=" + rootpath);
    326         FullBackup.backupToTar(getPackageName(), domain, null, rootpath, filePath,
    327                 output.getData());
    328     }
    329 
    330     /**
    331      * Scan the dir tree (if it actually exists) and process each entry we find.  If the
    332      * 'excludes' parameter is non-null, it is consulted each time a new file system entity
    333      * is visited to see whether that entity (and its subtree, if appropriate) should be
    334      * omitted from the backup process.
    335      *
    336      * @hide
    337      */
    338     protected final void fullBackupFileTree(String packageName, String domain, String rootPath,
    339             HashSet<String> excludes, FullBackupDataOutput output) {
    340         File rootFile = new File(rootPath);
    341         if (rootFile.exists()) {
    342             LinkedList<File> scanQueue = new LinkedList<File>();
    343             scanQueue.add(rootFile);
    344 
    345             while (scanQueue.size() > 0) {
    346                 File file = scanQueue.remove(0);
    347                 String filePath;
    348                 try {
    349                     filePath = file.getCanonicalPath();
    350 
    351                     // prune this subtree?
    352                     if (excludes != null && excludes.contains(filePath)) {
    353                         continue;
    354                     }
    355 
    356                     // If it's a directory, enqueue its contents for scanning.
    357                     StructStat stat = Libcore.os.lstat(filePath);
    358                     if (OsConstants.S_ISLNK(stat.st_mode)) {
    359                         if (DEBUG) Log.i(TAG, "Symlink (skipping)!: " + file);
    360                         continue;
    361                     } else if (OsConstants.S_ISDIR(stat.st_mode)) {
    362                         File[] contents = file.listFiles();
    363                         if (contents != null) {
    364                             for (File entry : contents) {
    365                                 scanQueue.add(0, entry);
    366                             }
    367                         }
    368                     }
    369                 } catch (IOException e) {
    370                     if (DEBUG) Log.w(TAG, "Error canonicalizing path of " + file);
    371                     continue;
    372                 } catch (ErrnoException e) {
    373                     if (DEBUG) Log.w(TAG, "Error scanning file " + file + " : " + e);
    374                     continue;
    375                 }
    376 
    377                 // Finally, back this file up before proceeding
    378                 FullBackup.backupToTar(packageName, domain, null, rootPath, filePath,
    379                         output.getData());
    380             }
    381         }
    382     }
    383 
    384     /**
    385      * Handle the data delivered via the given file descriptor during a full restore
    386      * operation.  The agent is given the path to the file's original location as well
    387      * as its size and metadata.
    388      * <p>
    389      * The file descriptor can only be read for {@code size} bytes; attempting to read
    390      * more data has undefined behavior.
    391      * <p>
    392      * The default implementation creates the destination file/directory and populates it
    393      * with the data from the file descriptor, then sets the file's access mode and
    394      * modification time to match the restore arguments.
    395      *
    396      * @param data A read-only file descriptor from which the agent can read {@code size}
    397      *     bytes of file data.
    398      * @param size The number of bytes of file content to be restored to the given
    399      *     destination.  If the file system object being restored is a directory, {@code size}
    400      *     will be zero.
    401      * @param destination The File on disk to be restored with the given data.
    402      * @param type The kind of file system object being restored.  This will be either
    403      *     {@link BackupAgent#TYPE_FILE} or {@link BackupAgent#TYPE_DIRECTORY}.
    404      * @param mode The access mode to be assigned to the destination after its data is
    405      *     written.  This is in the standard format used by {@code chmod()}.
    406      * @param mtime The modification time of the file when it was backed up, suitable to
    407      *     be assigned to the file after its data is written.
    408      * @throws IOException
    409      */
    410     public void onRestoreFile(ParcelFileDescriptor data, long size,
    411             File destination, int type, long mode, long mtime)
    412             throws IOException {
    413         FullBackup.restoreFile(data, size, type, mode, mtime, destination);
    414     }
    415 
    416     /**
    417      * Only specialized platform agents should overload this entry point to support
    418      * restores to crazy non-app locations.
    419      * @hide
    420      */
    421     protected void onRestoreFile(ParcelFileDescriptor data, long size,
    422             int type, String domain, String path, long mode, long mtime)
    423             throws IOException {
    424         String basePath = null;
    425 
    426         if (DEBUG) Log.d(TAG, "onRestoreFile() size=" + size + " type=" + type
    427                 + " domain=" + domain + " relpath=" + path + " mode=" + mode
    428                 + " mtime=" + mtime);
    429 
    430         // Parse out the semantic domains into the correct physical location
    431         if (domain.equals(FullBackup.DATA_TREE_TOKEN)) {
    432             basePath = getFilesDir().getCanonicalPath();
    433         } else if (domain.equals(FullBackup.DATABASE_TREE_TOKEN)) {
    434             basePath = getDatabasePath("foo").getParentFile().getCanonicalPath();
    435         } else if (domain.equals(FullBackup.ROOT_TREE_TOKEN)) {
    436             basePath = new File(getApplicationInfo().dataDir).getCanonicalPath();
    437         } else if (domain.equals(FullBackup.SHAREDPREFS_TREE_TOKEN)) {
    438             basePath = getSharedPrefsFile("foo").getParentFile().getCanonicalPath();
    439         } else if (domain.equals(FullBackup.CACHE_TREE_TOKEN)) {
    440             basePath = getCacheDir().getCanonicalPath();
    441         } else {
    442             // Not a supported location
    443             Log.i(TAG, "Data restored from non-app domain " + domain + ", ignoring");
    444         }
    445 
    446         // Now that we've figured out where the data goes, send it on its way
    447         if (basePath != null) {
    448             File outFile = new File(basePath, path);
    449             if (DEBUG) Log.i(TAG, "[" + domain + " : " + path + "] mapped to " + outFile.getPath());
    450             onRestoreFile(data, size, outFile, type, mode, mtime);
    451         } else {
    452             // Not a supported output location?  We need to consume the data
    453             // anyway, so just use the default "copy the data out" implementation
    454             // with a null destination.
    455             if (DEBUG) Log.i(TAG, "[ skipping data from unsupported domain " + domain + "]");
    456             FullBackup.restoreFile(data, size, type, mode, mtime, null);
    457         }
    458     }
    459 
    460     // ----- Core implementation -----
    461 
    462     /** @hide */
    463     public final IBinder onBind() {
    464         return mBinder;
    465     }
    466 
    467     private final IBinder mBinder = new BackupServiceBinder().asBinder();
    468 
    469     /** @hide */
    470     public void attach(Context context) {
    471         attachBaseContext(context);
    472     }
    473 
    474     // ----- IBackupService binder interface -----
    475     private class BackupServiceBinder extends IBackupAgent.Stub {
    476         private static final String TAG = "BackupServiceBinder";
    477 
    478         @Override
    479         public void doBackup(ParcelFileDescriptor oldState,
    480                 ParcelFileDescriptor data,
    481                 ParcelFileDescriptor newState,
    482                 int token, IBackupManager callbackBinder) throws RemoteException {
    483             // Ensure that we're running with the app's normal permission level
    484             long ident = Binder.clearCallingIdentity();
    485 
    486             if (DEBUG) Log.v(TAG, "doBackup() invoked");
    487             BackupDataOutput output = new BackupDataOutput(data.getFileDescriptor());
    488 
    489             try {
    490                 BackupAgent.this.onBackup(oldState, output, newState);
    491             } catch (IOException ex) {
    492                 Log.d(TAG, "onBackup (" + BackupAgent.this.getClass().getName() + ") threw", ex);
    493                 throw new RuntimeException(ex);
    494             } catch (RuntimeException ex) {
    495                 Log.d(TAG, "onBackup (" + BackupAgent.this.getClass().getName() + ") threw", ex);
    496                 throw ex;
    497             } finally {
    498                 Binder.restoreCallingIdentity(ident);
    499                 try {
    500                     callbackBinder.opComplete(token);
    501                 } catch (RemoteException e) {
    502                     // we'll time out anyway, so we're safe
    503                 }
    504             }
    505         }
    506 
    507         @Override
    508         public void doRestore(ParcelFileDescriptor data, int appVersionCode,
    509                 ParcelFileDescriptor newState,
    510                 int token, IBackupManager callbackBinder) throws RemoteException {
    511             // Ensure that we're running with the app's normal permission level
    512             long ident = Binder.clearCallingIdentity();
    513 
    514             if (DEBUG) Log.v(TAG, "doRestore() invoked");
    515             BackupDataInput input = new BackupDataInput(data.getFileDescriptor());
    516             try {
    517                 BackupAgent.this.onRestore(input, appVersionCode, newState);
    518             } catch (IOException ex) {
    519                 Log.d(TAG, "onRestore (" + BackupAgent.this.getClass().getName() + ") threw", ex);
    520                 throw new RuntimeException(ex);
    521             } catch (RuntimeException ex) {
    522                 Log.d(TAG, "onRestore (" + BackupAgent.this.getClass().getName() + ") threw", ex);
    523                 throw ex;
    524             } finally {
    525                 Binder.restoreCallingIdentity(ident);
    526                 try {
    527                     callbackBinder.opComplete(token);
    528                 } catch (RemoteException e) {
    529                     // we'll time out anyway, so we're safe
    530                 }
    531             }
    532         }
    533 
    534         @Override
    535         public void doFullBackup(ParcelFileDescriptor data,
    536                 int token, IBackupManager callbackBinder) {
    537             // Ensure that we're running with the app's normal permission level
    538             long ident = Binder.clearCallingIdentity();
    539 
    540             if (DEBUG) Log.v(TAG, "doFullBackup() invoked");
    541 
    542             try {
    543                 BackupAgent.this.onFullBackup(new FullBackupDataOutput(data));
    544             } catch (IOException ex) {
    545                 Log.d(TAG, "onBackup (" + BackupAgent.this.getClass().getName() + ") threw", ex);
    546                 throw new RuntimeException(ex);
    547             } catch (RuntimeException ex) {
    548                 Log.d(TAG, "onBackup (" + BackupAgent.this.getClass().getName() + ") threw", ex);
    549                 throw ex;
    550             } finally {
    551                 // Send the EOD marker indicating that there is no more data
    552                 // forthcoming from this agent.
    553                 try {
    554                     FileOutputStream out = new FileOutputStream(data.getFileDescriptor());
    555                     byte[] buf = new byte[4];
    556                     out.write(buf);
    557                 } catch (IOException e) {
    558                     Log.e(TAG, "Unable to finalize backup stream!");
    559                 }
    560 
    561                 Binder.restoreCallingIdentity(ident);
    562                 try {
    563                     callbackBinder.opComplete(token);
    564                 } catch (RemoteException e) {
    565                     // we'll time out anyway, so we're safe
    566                 }
    567             }
    568         }
    569 
    570         @Override
    571         public void doRestoreFile(ParcelFileDescriptor data, long size,
    572                 int type, String domain, String path, long mode, long mtime,
    573                 int token, IBackupManager callbackBinder) throws RemoteException {
    574             long ident = Binder.clearCallingIdentity();
    575             try {
    576                 BackupAgent.this.onRestoreFile(data, size, type, domain, path, mode, mtime);
    577             } catch (IOException e) {
    578                 throw new RuntimeException(e);
    579             } finally {
    580                 Binder.restoreCallingIdentity(ident);
    581                 try {
    582                     callbackBinder.opComplete(token);
    583                 } catch (RemoteException e) {
    584                     // we'll time out anyway, so we're safe
    585                 }
    586             }
    587         }
    588     }
    589 }
    590