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