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
17package android.app.backup;
18
19import android.app.IBackupAgent;
20import android.app.QueuedWork;
21import android.app.backup.IBackupManager;
22import android.content.Context;
23import android.content.ContextWrapper;
24import android.content.pm.ApplicationInfo;
25import android.os.Binder;
26import android.os.Handler;
27import android.os.IBinder;
28import android.os.Looper;
29import android.os.ParcelFileDescriptor;
30import android.os.Process;
31import android.os.RemoteException;
32import android.util.Log;
33
34import java.io.File;
35import java.io.FileOutputStream;
36import java.io.IOException;
37import java.util.HashSet;
38import java.util.LinkedList;
39import java.util.concurrent.CountDownLatch;
40
41import libcore.io.ErrnoException;
42import libcore.io.Libcore;
43import libcore.io.OsConstants;
44import 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 */
107public 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