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