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 
getHandler()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
run()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
waitForSharedPrefs()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 
BackupAgent()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      */
onCreate()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      */
onDestroy()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      */
onBackup(ParcelFileDescriptor oldState, BackupDataOutput data, ParcelFileDescriptor newState)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      */
onRestore(BackupDataInput data, int appVersionCode, ParcelFileDescriptor newState)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      */
onFullBackup(FullBackupDataOutput data)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      */
fullBackupFile(File file, FullBackupDataOutput output)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      */
fullBackupFileTree(String packageName, String domain, String rootPath, HashSet<String> excludes, FullBackupDataOutput output)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      */
onRestoreFile(ParcelFileDescriptor data, long size, File destination, int type, long mode, long mtime)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      */
onRestoreFile(ParcelFileDescriptor data, long size, int type, String domain, String path, long mode, long mtime)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      */
onRestoreFinished()588     public void onRestoreFinished() {
589     }
590 
591     // ----- Core implementation -----
592 
593     /** @hide */
onBind()594     public final IBinder onBind() {
595         return mBinder;
596     }
597 
598     private final IBinder mBinder = new BackupServiceBinder().asBinder();
599 
600     /** @hide */
attach(Context context)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
doBackup(ParcelFileDescriptor oldState, ParcelFileDescriptor data, ParcelFileDescriptor newState, int token, IBackupManager callbackBinder)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
doRestore(ParcelFileDescriptor data, int appVersionCode, ParcelFileDescriptor newState, int token, IBackupManager callbackBinder)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
doFullBackup(ParcelFileDescriptor data, int token, IBackupManager callbackBinder)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
doRestoreFile(ParcelFileDescriptor data, long size, int type, String domain, String path, long mode, long mtime, int token, IBackupManager callbackBinder)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
doRestoreFinished(int token, IBackupManager callbackBinder)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
fail(String message)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 
FailRunnable(String message)765         FailRunnable(String message) {
766             mMessage = message;
767         }
768 
769         @Override
run()770         public void run() {
771             throw new IllegalStateException(mMessage);
772         }
773     }
774 }
775