• 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.content.Context;
22 import android.content.ContextWrapper;
23 import android.content.pm.ApplicationInfo;
24 import android.os.Binder;
25 import android.os.Handler;
26 import android.os.IBinder;
27 import android.os.Looper;
28 import android.os.ParcelFileDescriptor;
29 import android.os.Process;
30 import android.os.RemoteException;
31 import android.system.ErrnoException;
32 import android.system.Os;
33 import android.system.OsConstants;
34 import android.system.StructStat;
35 import android.util.ArraySet;
36 import android.util.Log;
37 
38 import libcore.io.IoUtils;
39 
40 import org.xmlpull.v1.XmlPullParserException;
41 
42 import java.io.File;
43 import java.io.FileOutputStream;
44 import java.io.IOException;
45 import java.util.Collection;
46 import java.util.LinkedList;
47 import java.util.Map;
48 import java.util.Set;
49 import java.util.concurrent.CountDownLatch;
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         // System apps have control over where their default storage context
312         // is pointed, so we're always explicit when building paths.
313         final Context ceContext = createCredentialProtectedStorageContext();
314         final String rootDir = ceContext.getDataDir().getCanonicalPath();
315         final String filesDir = ceContext.getFilesDir().getCanonicalPath();
316         final String noBackupDir = ceContext.getNoBackupFilesDir().getCanonicalPath();
317         final String databaseDir = ceContext.getDatabasePath("foo").getParentFile()
318                 .getCanonicalPath();
319         final String sharedPrefsDir = ceContext.getSharedPreferencesPath("foo").getParentFile()
320                 .getCanonicalPath();
321         final String cacheDir = ceContext.getCacheDir().getCanonicalPath();
322         final String codeCacheDir = ceContext.getCodeCacheDir().getCanonicalPath();
323 
324         final Context deContext = createDeviceProtectedStorageContext();
325         final String deviceRootDir = deContext.getDataDir().getCanonicalPath();
326         final String deviceFilesDir = deContext.getFilesDir().getCanonicalPath();
327         final String deviceNoBackupDir = deContext.getNoBackupFilesDir().getCanonicalPath();
328         final String deviceDatabaseDir = deContext.getDatabasePath("foo").getParentFile()
329                 .getCanonicalPath();
330         final String deviceSharedPrefsDir = deContext.getSharedPreferencesPath("foo")
331                 .getParentFile().getCanonicalPath();
332         final String deviceCacheDir = deContext.getCacheDir().getCanonicalPath();
333         final String deviceCodeCacheDir = deContext.getCodeCacheDir().getCanonicalPath();
334 
335         final String libDir = (appInfo.nativeLibraryDir != null)
336                 ? new File(appInfo.nativeLibraryDir).getCanonicalPath()
337                 : null;
338 
339         // Maintain a set of excluded directories so that as we traverse the tree we know we're not
340         // going places we don't expect, and so the manifest includes can't take precedence over
341         // what the framework decides is not to be included.
342         final ArraySet<String> traversalExcludeSet = new ArraySet<String>();
343 
344         // Add the directories we always exclude.
345         traversalExcludeSet.add(filesDir);
346         traversalExcludeSet.add(noBackupDir);
347         traversalExcludeSet.add(databaseDir);
348         traversalExcludeSet.add(sharedPrefsDir);
349         traversalExcludeSet.add(cacheDir);
350         traversalExcludeSet.add(codeCacheDir);
351 
352         traversalExcludeSet.add(deviceFilesDir);
353         traversalExcludeSet.add(deviceNoBackupDir);
354         traversalExcludeSet.add(deviceDatabaseDir);
355         traversalExcludeSet.add(deviceSharedPrefsDir);
356         traversalExcludeSet.add(deviceCacheDir);
357         traversalExcludeSet.add(deviceCodeCacheDir);
358 
359         if (libDir != null) {
360             traversalExcludeSet.add(libDir);
361         }
362 
363         // Root dir first.
364         applyXmlFiltersAndDoFullBackupForDomain(
365                 packageName, FullBackup.ROOT_TREE_TOKEN, manifestIncludeMap,
366                 manifestExcludeSet, traversalExcludeSet, data);
367         traversalExcludeSet.add(rootDir);
368 
369         applyXmlFiltersAndDoFullBackupForDomain(
370                 packageName, FullBackup.DEVICE_ROOT_TREE_TOKEN, manifestIncludeMap,
371                 manifestExcludeSet, traversalExcludeSet, data);
372         traversalExcludeSet.add(deviceRootDir);
373 
374         // Data dir next.
375         traversalExcludeSet.remove(filesDir);
376         applyXmlFiltersAndDoFullBackupForDomain(
377                 packageName, FullBackup.FILES_TREE_TOKEN, manifestIncludeMap,
378                 manifestExcludeSet, traversalExcludeSet, data);
379         traversalExcludeSet.add(filesDir);
380 
381         traversalExcludeSet.remove(deviceFilesDir);
382         applyXmlFiltersAndDoFullBackupForDomain(
383                 packageName, FullBackup.DEVICE_FILES_TREE_TOKEN, manifestIncludeMap,
384                 manifestExcludeSet, traversalExcludeSet, data);
385         traversalExcludeSet.add(deviceFilesDir);
386 
387         // Database directory.
388         traversalExcludeSet.remove(databaseDir);
389         applyXmlFiltersAndDoFullBackupForDomain(
390                 packageName, FullBackup.DATABASE_TREE_TOKEN, manifestIncludeMap,
391                 manifestExcludeSet, traversalExcludeSet, data);
392         traversalExcludeSet.add(databaseDir);
393 
394         traversalExcludeSet.remove(deviceDatabaseDir);
395         applyXmlFiltersAndDoFullBackupForDomain(
396                 packageName, FullBackup.DEVICE_DATABASE_TREE_TOKEN, manifestIncludeMap,
397                 manifestExcludeSet, traversalExcludeSet, data);
398         traversalExcludeSet.add(deviceDatabaseDir);
399 
400         // SharedPrefs.
401         traversalExcludeSet.remove(sharedPrefsDir);
402         applyXmlFiltersAndDoFullBackupForDomain(
403                 packageName, FullBackup.SHAREDPREFS_TREE_TOKEN, manifestIncludeMap,
404                 manifestExcludeSet, traversalExcludeSet, data);
405         traversalExcludeSet.add(sharedPrefsDir);
406 
407         traversalExcludeSet.remove(deviceSharedPrefsDir);
408         applyXmlFiltersAndDoFullBackupForDomain(
409                 packageName, FullBackup.DEVICE_SHAREDPREFS_TREE_TOKEN, manifestIncludeMap,
410                 manifestExcludeSet, traversalExcludeSet, data);
411         traversalExcludeSet.add(deviceSharedPrefsDir);
412 
413         // getExternalFilesDir() location associated with this app.  Technically there should
414         // not be any files here if the app does not properly have permission to access
415         // external storage, but edge cases happen. fullBackupFileTree() catches
416         // IOExceptions and similar, and treats them as non-fatal, so we rely on that; and
417         // we know a priori that processes running as the system UID are not permitted to
418         // access external storage, so we check for that as well to avoid nastygrams in
419         // the log.
420         if (Process.myUid() != Process.SYSTEM_UID) {
421             File efLocation = getExternalFilesDir(null);
422             if (efLocation != null) {
423                 applyXmlFiltersAndDoFullBackupForDomain(
424                         packageName, FullBackup.MANAGED_EXTERNAL_TREE_TOKEN, manifestIncludeMap,
425                         manifestExcludeSet, traversalExcludeSet, data);
426             }
427 
428         }
429     }
430 
431     /**
432      * Notification that the application's current backup operation causes it to exceed
433      * the maximum size permitted by the transport.  The ongoing backup operation is
434      * halted and rolled back: any data that had been stored by a previous backup operation
435      * is still intact.  Typically the quota-exceeded state will be detected before any data
436      * is actually transmitted over the network.
437      *
438      * <p>The {@code quotaBytes} value is the total data size currently permitted for this
439      * application.  If desired, the application can use this as a hint for determining
440      * how much data to store.  For example, a messaging application might choose to
441      * store only the newest messages, dropping enough older content to stay under
442      * the quota.
443      *
444      * <p class="note">Note that the maximum quota for the application can change over
445      * time.  In particular, in the future the quota may grow.  Applications that adapt
446      * to the quota when deciding what data to store should be aware of this and implement
447      * their data storage mechanisms in a way that can take advantage of additional
448      * quota.
449      *
450      * @param backupDataBytes The amount of data measured while initializing the backup
451      *    operation, if the total exceeds the app's alloted quota.  If initial measurement
452      *    suggested that the data would fit but then too much data was actually submitted
453      *    as part of the operation, then this value is the amount of data that had been
454      *    streamed into the transport at the time the quota was reached.
455      * @param quotaBytes The maximum data size that the transport currently permits
456      *    this application to store as a backup.
457      */
onQuotaExceeded(long backupDataBytes, long quotaBytes)458     public void onQuotaExceeded(long backupDataBytes, long quotaBytes) {
459     }
460 
461     /**
462      * Check whether the xml yielded any <include/> tag for the provided <code>domainToken</code>.
463      * If so, perform a {@link #fullBackupFileTree} which backs up the file or recurses if the path
464      * is a directory.
465      */
applyXmlFiltersAndDoFullBackupForDomain(String packageName, String domainToken, Map<String, Set<String>> includeMap, ArraySet<String> filterSet, ArraySet<String> traversalExcludeSet, FullBackupDataOutput data)466     private void applyXmlFiltersAndDoFullBackupForDomain(String packageName, String domainToken,
467                                                          Map<String, Set<String>> includeMap,
468                                                          ArraySet<String> filterSet,
469                                                          ArraySet<String> traversalExcludeSet,
470                                                          FullBackupDataOutput data)
471             throws IOException {
472         if (includeMap == null || includeMap.size() == 0) {
473             // Do entire sub-tree for the provided token.
474             fullBackupFileTree(packageName, domainToken,
475                     FullBackup.getBackupScheme(this).tokenToDirectoryPath(domainToken),
476                     filterSet, traversalExcludeSet, data);
477         } else if (includeMap.get(domainToken) != null) {
478             // This will be null if the xml parsing didn't yield any rules for
479             // this domain (there may still be rules for other domains).
480             for (String includeFile : includeMap.get(domainToken)) {
481                 fullBackupFileTree(packageName, domainToken, includeFile, filterSet,
482                         traversalExcludeSet, data);
483             }
484         }
485     }
486 
487     /**
488      * Write an entire file as part of a full-backup operation.  The file's contents
489      * will be delivered to the backup destination along with the metadata necessary
490      * to place it with the proper location and permissions on the device where the
491      * data is restored.
492      *
493      * <p class="note">Attempting to back up files in directories that are ignored by
494      * the backup system will have no effect.  For example, if the app calls this method
495      * with a file inside the {@link #getNoBackupFilesDir()} directory, it will be ignored.
496      * See {@link #onFullBackup(FullBackupDataOutput)} for details on what directories
497      * are excluded from backups.
498      *
499      * @param file The file to be backed up.  The file must exist and be readable by
500      *     the caller.
501      * @param output The destination to which the backed-up file data will be sent.
502      */
fullBackupFile(File file, FullBackupDataOutput output)503     public final void fullBackupFile(File file, FullBackupDataOutput output) {
504         // Look up where all of our various well-defined dir trees live on this device
505         final String rootDir;
506         final String filesDir;
507         final String nbFilesDir;
508         final String dbDir;
509         final String spDir;
510         final String cacheDir;
511         final String codeCacheDir;
512         final String deviceRootDir;
513         final String deviceFilesDir;
514         final String deviceNbFilesDir;
515         final String deviceDbDir;
516         final String deviceSpDir;
517         final String deviceCacheDir;
518         final String deviceCodeCacheDir;
519         final String libDir;
520 
521         String efDir = null;
522         String filePath;
523 
524         ApplicationInfo appInfo = getApplicationInfo();
525 
526         try {
527             // System apps have control over where their default storage context
528             // is pointed, so we're always explicit when building paths.
529             final Context ceContext = createCredentialProtectedStorageContext();
530             rootDir = ceContext.getDataDir().getCanonicalPath();
531             filesDir = ceContext.getFilesDir().getCanonicalPath();
532             nbFilesDir = ceContext.getNoBackupFilesDir().getCanonicalPath();
533             dbDir = ceContext.getDatabasePath("foo").getParentFile().getCanonicalPath();
534             spDir = ceContext.getSharedPreferencesPath("foo").getParentFile().getCanonicalPath();
535             cacheDir = ceContext.getCacheDir().getCanonicalPath();
536             codeCacheDir = ceContext.getCodeCacheDir().getCanonicalPath();
537 
538             final Context deContext = createDeviceProtectedStorageContext();
539             deviceRootDir = deContext.getDataDir().getCanonicalPath();
540             deviceFilesDir = deContext.getFilesDir().getCanonicalPath();
541             deviceNbFilesDir = deContext.getNoBackupFilesDir().getCanonicalPath();
542             deviceDbDir = deContext.getDatabasePath("foo").getParentFile().getCanonicalPath();
543             deviceSpDir = deContext.getSharedPreferencesPath("foo").getParentFile()
544                     .getCanonicalPath();
545             deviceCacheDir = deContext.getCacheDir().getCanonicalPath();
546             deviceCodeCacheDir = deContext.getCodeCacheDir().getCanonicalPath();
547 
548             libDir = (appInfo.nativeLibraryDir == null)
549                     ? null
550                     : new File(appInfo.nativeLibraryDir).getCanonicalPath();
551 
552             // may or may not have external files access to attempt backup/restore there
553             if (Process.myUid() != Process.SYSTEM_UID) {
554                 File efLocation = getExternalFilesDir(null);
555                 if (efLocation != null) {
556                     efDir = efLocation.getCanonicalPath();
557                 }
558             }
559 
560             // Now figure out which well-defined tree the file is placed in, working from
561             // most to least specific.  We also specifically exclude the lib, cache,
562             // and code_cache dirs.
563             filePath = file.getCanonicalPath();
564         } catch (IOException e) {
565             Log.w(TAG, "Unable to obtain canonical paths");
566             return;
567         }
568 
569         if (filePath.startsWith(cacheDir)
570                 || filePath.startsWith(codeCacheDir)
571                 || filePath.startsWith(nbFilesDir)
572                 || filePath.startsWith(deviceCacheDir)
573                 || filePath.startsWith(deviceCodeCacheDir)
574                 || filePath.startsWith(deviceNbFilesDir)
575                 || filePath.startsWith(libDir)) {
576             Log.w(TAG, "lib, cache, code_cache, and no_backup files are not backed up");
577             return;
578         }
579 
580         final String domain;
581         String rootpath = null;
582         if (filePath.startsWith(dbDir)) {
583             domain = FullBackup.DATABASE_TREE_TOKEN;
584             rootpath = dbDir;
585         } else if (filePath.startsWith(spDir)) {
586             domain = FullBackup.SHAREDPREFS_TREE_TOKEN;
587             rootpath = spDir;
588         } else if (filePath.startsWith(filesDir)) {
589             domain = FullBackup.FILES_TREE_TOKEN;
590             rootpath = filesDir;
591         } else if (filePath.startsWith(rootDir)) {
592             domain = FullBackup.ROOT_TREE_TOKEN;
593             rootpath = rootDir;
594         } else if (filePath.startsWith(deviceDbDir)) {
595             domain = FullBackup.DEVICE_DATABASE_TREE_TOKEN;
596             rootpath = deviceDbDir;
597         } else if (filePath.startsWith(deviceSpDir)) {
598             domain = FullBackup.DEVICE_SHAREDPREFS_TREE_TOKEN;
599             rootpath = deviceSpDir;
600         } else if (filePath.startsWith(deviceFilesDir)) {
601             domain = FullBackup.DEVICE_FILES_TREE_TOKEN;
602             rootpath = deviceFilesDir;
603         } else if (filePath.startsWith(deviceRootDir)) {
604             domain = FullBackup.DEVICE_ROOT_TREE_TOKEN;
605             rootpath = deviceRootDir;
606         } else if ((efDir != null) && filePath.startsWith(efDir)) {
607             domain = FullBackup.MANAGED_EXTERNAL_TREE_TOKEN;
608             rootpath = efDir;
609         } else {
610             Log.w(TAG, "File " + filePath + " is in an unsupported location; skipping");
611             return;
612         }
613 
614         // And now that we know where it lives, semantically, back it up appropriately
615         // In the measurement case, backupToTar() updates the size in output and returns
616         // without transmitting any file data.
617         if (DEBUG) Log.i(TAG, "backupFile() of " + filePath + " => domain=" + domain
618                 + " rootpath=" + rootpath);
619 
620         FullBackup.backupToTar(getPackageName(), domain, null, rootpath, filePath, output);
621     }
622 
623     /**
624      * Scan the dir tree (if it actually exists) and process each entry we find.  If the
625      * 'excludes' parameters are non-null, they are consulted each time a new file system entity
626      * is visited to see whether that entity (and its subtree, if appropriate) should be
627      * omitted from the backup process.
628      *
629      * @param systemExcludes An optional list of excludes.
630      * @hide
631      */
fullBackupFileTree(String packageName, String domain, String startingPath, ArraySet<String> manifestExcludes, ArraySet<String> systemExcludes, FullBackupDataOutput output)632     protected final void fullBackupFileTree(String packageName, String domain, String startingPath,
633                                             ArraySet<String> manifestExcludes,
634                                             ArraySet<String> systemExcludes,
635             FullBackupDataOutput output) {
636         // Pull out the domain and set it aside to use when making the tarball.
637         String domainPath = FullBackup.getBackupScheme(this).tokenToDirectoryPath(domain);
638         if (domainPath == null) {
639             // Should never happen.
640             return;
641         }
642 
643         File rootFile = new File(startingPath);
644         if (rootFile.exists()) {
645             LinkedList<File> scanQueue = new LinkedList<File>();
646             scanQueue.add(rootFile);
647 
648             while (scanQueue.size() > 0) {
649                 File file = scanQueue.remove(0);
650                 String filePath;
651                 try {
652                     // Ignore things that aren't "real" files or dirs
653                     StructStat stat = Os.lstat(file.getPath());
654                     if (!OsConstants.S_ISREG(stat.st_mode)
655                             && !OsConstants.S_ISDIR(stat.st_mode)) {
656                         if (DEBUG) Log.i(TAG, "Not a file/dir (skipping)!: " + file);
657                         continue;
658                     }
659 
660                     // For all other verification, look at the canonicalized path
661                     filePath = file.getCanonicalPath();
662 
663                     // prune this subtree?
664                     if (manifestExcludes != null && manifestExcludes.contains(filePath)) {
665                         continue;
666                     }
667                     if (systemExcludes != null && systemExcludes.contains(filePath)) {
668                         continue;
669                     }
670 
671                     // If it's a directory, enqueue its contents for scanning.
672                     if (OsConstants.S_ISDIR(stat.st_mode)) {
673                         File[] contents = file.listFiles();
674                         if (contents != null) {
675                             for (File entry : contents) {
676                                 scanQueue.add(0, entry);
677                             }
678                         }
679                     }
680                 } catch (IOException e) {
681                     if (DEBUG) Log.w(TAG, "Error canonicalizing path of " + file);
682                     if (Log.isLoggable(FullBackup.TAG_XML_PARSER, Log.VERBOSE)) {
683                         Log.v(FullBackup.TAG_XML_PARSER, "Error canonicalizing path of " + file);
684                     }
685                     continue;
686                 } catch (ErrnoException e) {
687                     if (DEBUG) Log.w(TAG, "Error scanning file " + file + " : " + e);
688                     if (Log.isLoggable(FullBackup.TAG_XML_PARSER, Log.VERBOSE)) {
689                         Log.v(FullBackup.TAG_XML_PARSER, "Error scanning file " + file + " : " + e);
690                     }
691                     continue;
692                 }
693 
694                 // Finally, back this file up (or measure it) before proceeding
695                 FullBackup.backupToTar(packageName, domain, null, domainPath, filePath, output);
696             }
697         }
698     }
699 
700     /**
701      * Handle the data delivered via the given file descriptor during a full restore
702      * operation.  The agent is given the path to the file's original location as well
703      * as its size and metadata.
704      * <p>
705      * The file descriptor can only be read for {@code size} bytes; attempting to read
706      * more data has undefined behavior.
707      * <p>
708      * The default implementation creates the destination file/directory and populates it
709      * with the data from the file descriptor, then sets the file's access mode and
710      * modification time to match the restore arguments.
711      *
712      * @param data A read-only file descriptor from which the agent can read {@code size}
713      *     bytes of file data.
714      * @param size The number of bytes of file content to be restored to the given
715      *     destination.  If the file system object being restored is a directory, {@code size}
716      *     will be zero.
717      * @param destination The File on disk to be restored with the given data.
718      * @param type The kind of file system object being restored.  This will be either
719      *     {@link BackupAgent#TYPE_FILE} or {@link BackupAgent#TYPE_DIRECTORY}.
720      * @param mode The access mode to be assigned to the destination after its data is
721      *     written.  This is in the standard format used by {@code chmod()}.
722      * @param mtime The modification time of the file when it was backed up, suitable to
723      *     be assigned to the file after its data is written.
724      * @throws IOException
725      */
onRestoreFile(ParcelFileDescriptor data, long size, File destination, int type, long mode, long mtime)726     public void onRestoreFile(ParcelFileDescriptor data, long size,
727             File destination, int type, long mode, long mtime)
728             throws IOException {
729 
730         final boolean accept = isFileEligibleForRestore(destination);
731         // If we don't accept the file, consume the bytes from the pipe anyway.
732         FullBackup.restoreFile(data, size, type, mode, mtime, accept ? destination : null);
733     }
734 
isFileEligibleForRestore(File destination)735     private boolean isFileEligibleForRestore(File destination) throws IOException {
736         FullBackup.BackupScheme bs = FullBackup.getBackupScheme(this);
737         if (!bs.isFullBackupContentEnabled()) {
738             if (Log.isLoggable(FullBackup.TAG_XML_PARSER, Log.VERBOSE)) {
739                 Log.v(FullBackup.TAG_XML_PARSER,
740                         "onRestoreFile \"" + destination.getCanonicalPath()
741                                 + "\" : fullBackupContent not enabled for " + getPackageName());
742             }
743             return false;
744         }
745 
746         Map<String, Set<String>> includes = null;
747         ArraySet<String> excludes = null;
748         final String destinationCanonicalPath = destination.getCanonicalPath();
749         try {
750             includes = bs.maybeParseAndGetCanonicalIncludePaths();
751             excludes = bs.maybeParseAndGetCanonicalExcludePaths();
752         } catch (XmlPullParserException e) {
753             if (Log.isLoggable(FullBackup.TAG_XML_PARSER, Log.VERBOSE)) {
754                 Log.v(FullBackup.TAG_XML_PARSER,
755                         "onRestoreFile \"" + destinationCanonicalPath
756                                 + "\" : Exception trying to parse fullBackupContent xml file!"
757                                 + " Aborting onRestoreFile.", e);
758             }
759             return false;
760         }
761 
762         if (excludes != null &&
763                 isFileSpecifiedInPathList(destination, excludes)) {
764             if (Log.isLoggable(FullBackup.TAG_XML_PARSER, Log.VERBOSE)) {
765                 Log.v(FullBackup.TAG_XML_PARSER,
766                         "onRestoreFile: \"" + destinationCanonicalPath + "\": listed in"
767                                 + " excludes; skipping.");
768             }
769             return false;
770         }
771 
772         if (includes != null && !includes.isEmpty()) {
773             // Rather than figure out the <include/> domain based on the path (a lot of code, and
774             // it's a small list), we'll go through and look for it.
775             boolean explicitlyIncluded = false;
776             for (Set<String> domainIncludes : includes.values()) {
777                 explicitlyIncluded |= isFileSpecifiedInPathList(destination, domainIncludes);
778                 if (explicitlyIncluded) {
779                     break;
780                 }
781             }
782             if (!explicitlyIncluded) {
783                 if (Log.isLoggable(FullBackup.TAG_XML_PARSER, Log.VERBOSE)) {
784                     Log.v(FullBackup.TAG_XML_PARSER,
785                             "onRestoreFile: Trying to restore \""
786                                     + destinationCanonicalPath + "\" but it isn't specified"
787                                     + " in the included files; skipping.");
788                 }
789                 return false;
790             }
791         }
792         return true;
793     }
794 
795     /**
796      * @return True if the provided file is either directly in the provided list, or the provided
797      * file is within a directory in the list.
798      */
isFileSpecifiedInPathList(File file, Collection<String> canonicalPathList)799     private boolean isFileSpecifiedInPathList(File file, Collection<String> canonicalPathList)
800             throws IOException {
801         for (String canonicalPath : canonicalPathList) {
802             File fileFromList = new File(canonicalPath);
803             if (fileFromList.isDirectory()) {
804                 if (file.isDirectory()) {
805                     // If they are both directories check exact equals.
806                     return file.equals(fileFromList);
807                 } else {
808                     // O/w we have to check if the file is within the directory from the list.
809                     return file.getCanonicalPath().startsWith(canonicalPath);
810                 }
811             } else {
812                 if (file.equals(fileFromList)) {
813                     // Need to check the explicit "equals" so we don't end up with substrings.
814                     return true;
815                 }
816             }
817         }
818         return false;
819     }
820 
821     /**
822      * Only specialized platform agents should overload this entry point to support
823      * restores to crazy non-app locations.
824      * @hide
825      */
onRestoreFile(ParcelFileDescriptor data, long size, int type, String domain, String path, long mode, long mtime)826     protected void onRestoreFile(ParcelFileDescriptor data, long size,
827             int type, String domain, String path, long mode, long mtime)
828             throws IOException {
829         String basePath = null;
830 
831         if (DEBUG) Log.d(TAG, "onRestoreFile() size=" + size + " type=" + type
832                 + " domain=" + domain + " relpath=" + path + " mode=" + mode
833                 + " mtime=" + mtime);
834 
835         basePath = FullBackup.getBackupScheme(this).tokenToDirectoryPath(domain);
836         if (domain.equals(FullBackup.MANAGED_EXTERNAL_TREE_TOKEN)) {
837             mode = -1;  // < 0 is a token to skip attempting a chmod()
838         }
839 
840         // Now that we've figured out where the data goes, send it on its way
841         if (basePath != null) {
842             // Canonicalize the nominal path and verify that it lies within the stated domain
843             File outFile = new File(basePath, path);
844             String outPath = outFile.getCanonicalPath();
845             if (outPath.startsWith(basePath + File.separatorChar)) {
846                 if (DEBUG) Log.i(TAG, "[" + domain + " : " + path + "] mapped to " + outPath);
847                 onRestoreFile(data, size, outFile, type, mode, mtime);
848                 return;
849             } else {
850                 // Attempt to restore to a path outside the file's nominal domain.
851                 if (DEBUG) {
852                     Log.e(TAG, "Cross-domain restore attempt: " + outPath);
853                 }
854             }
855         }
856 
857         // Not a supported output location, or bad path:  we need to consume the data
858         // anyway, so just use the default "copy the data out" implementation
859         // with a null destination.
860         if (DEBUG) Log.i(TAG, "[ skipping file " + path + "]");
861         FullBackup.restoreFile(data, size, type, mode, mtime, null);
862     }
863 
864     /**
865      * The application's restore operation has completed.  This method is called after
866      * all available data has been delivered to the application for restore (via either
867      * the {@link #onRestore(BackupDataInput, int, ParcelFileDescriptor) onRestore()} or
868      * {@link #onRestoreFile(ParcelFileDescriptor, long, File, int, long, long) onRestoreFile()}
869      * callbacks).  This provides the app with a stable end-of-restore opportunity to
870      * perform any appropriate post-processing on the data that was just delivered.
871      *
872      * @see #onRestore(BackupDataInput, int, ParcelFileDescriptor)
873      * @see #onRestoreFile(ParcelFileDescriptor, long, File, int, long, long)
874      */
onRestoreFinished()875     public void onRestoreFinished() {
876     }
877 
878     // ----- Core implementation -----
879 
880     /** @hide */
onBind()881     public final IBinder onBind() {
882         return mBinder;
883     }
884 
885     private final IBinder mBinder = new BackupServiceBinder().asBinder();
886 
887     /** @hide */
attach(Context context)888     public void attach(Context context) {
889         attachBaseContext(context);
890     }
891 
892     // ----- IBackupService binder interface -----
893     private class BackupServiceBinder extends IBackupAgent.Stub {
894         private static final String TAG = "BackupServiceBinder";
895 
896         @Override
doBackup(ParcelFileDescriptor oldState, ParcelFileDescriptor data, ParcelFileDescriptor newState, int token, IBackupManager callbackBinder)897         public void doBackup(ParcelFileDescriptor oldState,
898                 ParcelFileDescriptor data,
899                 ParcelFileDescriptor newState,
900                 int token, IBackupManager callbackBinder) throws RemoteException {
901             // Ensure that we're running with the app's normal permission level
902             long ident = Binder.clearCallingIdentity();
903 
904             if (DEBUG) Log.v(TAG, "doBackup() invoked");
905             BackupDataOutput output = new BackupDataOutput(data.getFileDescriptor());
906 
907             try {
908                 BackupAgent.this.onBackup(oldState, output, newState);
909             } catch (IOException ex) {
910                 Log.d(TAG, "onBackup (" + BackupAgent.this.getClass().getName() + ") threw", ex);
911                 throw new RuntimeException(ex);
912             } catch (RuntimeException ex) {
913                 Log.d(TAG, "onBackup (" + BackupAgent.this.getClass().getName() + ") threw", ex);
914                 throw ex;
915             } finally {
916                 // Ensure that any SharedPreferences writes have landed after the backup,
917                 // in case the app code has side effects (since apps cannot provide this
918                 // guarantee themselves).
919                 waitForSharedPrefs();
920 
921                 Binder.restoreCallingIdentity(ident);
922                 try {
923                     callbackBinder.opComplete(token, 0);
924                 } catch (RemoteException e) {
925                     // we'll time out anyway, so we're safe
926                 }
927 
928                 // Don't close the fd out from under the system service if this was local
929                 if (Binder.getCallingPid() != Process.myPid()) {
930                     IoUtils.closeQuietly(oldState);
931                     IoUtils.closeQuietly(data);
932                     IoUtils.closeQuietly(newState);
933                 }
934             }
935         }
936 
937         @Override
doRestore(ParcelFileDescriptor data, int appVersionCode, ParcelFileDescriptor newState, int token, IBackupManager callbackBinder)938         public void doRestore(ParcelFileDescriptor data, int appVersionCode,
939                 ParcelFileDescriptor newState,
940                 int token, IBackupManager callbackBinder) throws RemoteException {
941             // Ensure that we're running with the app's normal permission level
942             long ident = Binder.clearCallingIdentity();
943 
944             if (DEBUG) Log.v(TAG, "doRestore() invoked");
945             BackupDataInput input = new BackupDataInput(data.getFileDescriptor());
946             try {
947                 BackupAgent.this.onRestore(input, appVersionCode, newState);
948             } catch (IOException ex) {
949                 Log.d(TAG, "onRestore (" + BackupAgent.this.getClass().getName() + ") threw", ex);
950                 throw new RuntimeException(ex);
951             } catch (RuntimeException ex) {
952                 Log.d(TAG, "onRestore (" + BackupAgent.this.getClass().getName() + ") threw", ex);
953                 throw ex;
954             } finally {
955                 // Ensure that any side-effect SharedPreferences writes have landed
956                 waitForSharedPrefs();
957 
958                 Binder.restoreCallingIdentity(ident);
959                 try {
960                     callbackBinder.opComplete(token, 0);
961                 } catch (RemoteException e) {
962                     // we'll time out anyway, so we're safe
963                 }
964 
965                 if (Binder.getCallingPid() != Process.myPid()) {
966                     IoUtils.closeQuietly(data);
967                     IoUtils.closeQuietly(newState);
968                 }
969             }
970         }
971 
972         @Override
doFullBackup(ParcelFileDescriptor data, int token, IBackupManager callbackBinder)973         public void doFullBackup(ParcelFileDescriptor data,
974                 int token, IBackupManager callbackBinder) {
975             // Ensure that we're running with the app's normal permission level
976             long ident = Binder.clearCallingIdentity();
977 
978             if (DEBUG) Log.v(TAG, "doFullBackup() invoked");
979 
980             // Ensure that any SharedPreferences writes have landed *before*
981             // we potentially try to back up the underlying files directly.
982             waitForSharedPrefs();
983 
984             try {
985                 BackupAgent.this.onFullBackup(new FullBackupDataOutput(data));
986             } catch (IOException ex) {
987                 Log.d(TAG, "onFullBackup (" + BackupAgent.this.getClass().getName() + ") threw", ex);
988                 throw new RuntimeException(ex);
989             } catch (RuntimeException ex) {
990                 Log.d(TAG, "onFullBackup (" + BackupAgent.this.getClass().getName() + ") threw", ex);
991                 throw ex;
992             } finally {
993                 // ... and then again after, as in the doBackup() case
994                 waitForSharedPrefs();
995 
996                 // Send the EOD marker indicating that there is no more data
997                 // forthcoming from this agent.
998                 try {
999                     FileOutputStream out = new FileOutputStream(data.getFileDescriptor());
1000                     byte[] buf = new byte[4];
1001                     out.write(buf);
1002                 } catch (IOException e) {
1003                     Log.e(TAG, "Unable to finalize backup stream!");
1004                 }
1005 
1006                 Binder.restoreCallingIdentity(ident);
1007                 try {
1008                     callbackBinder.opComplete(token, 0);
1009                 } catch (RemoteException e) {
1010                     // we'll time out anyway, so we're safe
1011                 }
1012 
1013                 if (Binder.getCallingPid() != Process.myPid()) {
1014                     IoUtils.closeQuietly(data);
1015                 }
1016             }
1017         }
1018 
doMeasureFullBackup(int token, IBackupManager callbackBinder)1019         public void doMeasureFullBackup(int token, IBackupManager callbackBinder) {
1020             // Ensure that we're running with the app's normal permission level
1021             final long ident = Binder.clearCallingIdentity();
1022             FullBackupDataOutput measureOutput = new FullBackupDataOutput();
1023 
1024             waitForSharedPrefs();
1025             try {
1026                 BackupAgent.this.onFullBackup(measureOutput);
1027             } catch (IOException ex) {
1028                 Log.d(TAG, "onFullBackup[M] (" + BackupAgent.this.getClass().getName() + ") threw", ex);
1029                 throw new RuntimeException(ex);
1030             } catch (RuntimeException ex) {
1031                 Log.d(TAG, "onFullBackup[M] (" + BackupAgent.this.getClass().getName() + ") threw", ex);
1032                 throw ex;
1033             } finally {
1034                 Binder.restoreCallingIdentity(ident);
1035                 try {
1036                     callbackBinder.opComplete(token, measureOutput.getSize());
1037                 } catch (RemoteException e) {
1038                     // timeout, so we're safe
1039                 }
1040             }
1041         }
1042 
1043         @Override
doRestoreFile(ParcelFileDescriptor data, long size, int type, String domain, String path, long mode, long mtime, int token, IBackupManager callbackBinder)1044         public void doRestoreFile(ParcelFileDescriptor data, long size,
1045                 int type, String domain, String path, long mode, long mtime,
1046                 int token, IBackupManager callbackBinder) throws RemoteException {
1047             long ident = Binder.clearCallingIdentity();
1048             try {
1049                 BackupAgent.this.onRestoreFile(data, size, type, domain, path, mode, mtime);
1050             } catch (IOException e) {
1051                 Log.d(TAG, "onRestoreFile (" + BackupAgent.this.getClass().getName() + ") threw", e);
1052                 throw new RuntimeException(e);
1053             } finally {
1054                 // Ensure that any side-effect SharedPreferences writes have landed
1055                 waitForSharedPrefs();
1056 
1057                 Binder.restoreCallingIdentity(ident);
1058                 try {
1059                     callbackBinder.opComplete(token, 0);
1060                 } catch (RemoteException e) {
1061                     // we'll time out anyway, so we're safe
1062                 }
1063 
1064                 if (Binder.getCallingPid() != Process.myPid()) {
1065                     IoUtils.closeQuietly(data);
1066                 }
1067             }
1068         }
1069 
1070         @Override
doRestoreFinished(int token, IBackupManager callbackBinder)1071         public void doRestoreFinished(int token, IBackupManager callbackBinder) {
1072             long ident = Binder.clearCallingIdentity();
1073             try {
1074                 BackupAgent.this.onRestoreFinished();
1075             } catch (Exception e) {
1076                 Log.d(TAG, "onRestoreFinished (" + BackupAgent.this.getClass().getName() + ") threw", e);
1077                 throw e;
1078             } finally {
1079                 // Ensure that any side-effect SharedPreferences writes have landed
1080                 waitForSharedPrefs();
1081 
1082                 Binder.restoreCallingIdentity(ident);
1083                 try {
1084                     callbackBinder.opComplete(token, 0);
1085                 } catch (RemoteException e) {
1086                     // we'll time out anyway, so we're safe
1087                 }
1088             }
1089         }
1090 
1091         @Override
fail(String message)1092         public void fail(String message) {
1093             getHandler().post(new FailRunnable(message));
1094         }
1095 
1096         @Override
doQuotaExceeded(long backupDataBytes, long quotaBytes)1097         public void doQuotaExceeded(long backupDataBytes, long quotaBytes) {
1098             long ident = Binder.clearCallingIdentity();
1099             try {
1100                 BackupAgent.this.onQuotaExceeded(backupDataBytes, quotaBytes);
1101             } catch (Exception e) {
1102                 Log.d(TAG, "onQuotaExceeded(" + BackupAgent.this.getClass().getName() + ") threw",
1103                         e);
1104                 throw e;
1105             } finally {
1106                 waitForSharedPrefs();
1107                 Binder.restoreCallingIdentity(ident);
1108             }
1109         }
1110     }
1111 
1112     static class FailRunnable implements Runnable {
1113         private String mMessage;
1114 
FailRunnable(String message)1115         FailRunnable(String message) {
1116             mMessage = message;
1117         }
1118 
1119         @Override
run()1120         public void run() {
1121             throw new IllegalStateException(mMessage);
1122         }
1123     }
1124 }
1125