• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
BackupAgent()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      */
onCreate()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      */
onDestroy()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      */
onBackup(ParcelFileDescriptor oldState, BackupDataOutput data, ParcelFileDescriptor newState)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      */
onRestore(BackupDataInput data, int appVersionCode, ParcelFileDescriptor newState)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      */
onFullBackup(FullBackupDataOutput data)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      */
fullBackupFile(File file, FullBackupDataOutput output)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      */
fullBackupFileTree(String packageName, String domain, String rootPath, HashSet<String> excludes, FullBackupDataOutput output)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      */
onRestoreFile(ParcelFileDescriptor data, long size, File destination, int type, long mode, long mtime)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      */
onRestoreFile(ParcelFileDescriptor data, long size, int type, String domain, String path, long mode, long mtime)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 */
onBind()463     public final IBinder onBind() {
464         return mBinder;
465     }
466 
467     private final IBinder mBinder = new BackupServiceBinder().asBinder();
468 
469     /** @hide */
attach(Context context)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
doBackup(ParcelFileDescriptor oldState, ParcelFileDescriptor data, ParcelFileDescriptor newState, int token, IBackupManager callbackBinder)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
doRestore(ParcelFileDescriptor data, int appVersionCode, ParcelFileDescriptor newState, int token, IBackupManager callbackBinder)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
doFullBackup(ParcelFileDescriptor data, int token, IBackupManager callbackBinder)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
doRestoreFile(ParcelFileDescriptor data, long size, int type, String domain, String path, long mode, long mtime, int token, IBackupManager callbackBinder)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