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