• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2009 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.app.backup;
18 
19 import android.annotation.Nullable;
20 import android.app.IBackupAgent;
21 import android.app.QueuedWork;
22 import android.app.backup.FullBackup.BackupScheme.PathWithRequiredFlags;
23 import android.content.Context;
24 import android.content.ContextWrapper;
25 import android.content.pm.ApplicationInfo;
26 import android.os.Binder;
27 import android.os.Handler;
28 import android.os.IBinder;
29 import android.os.Looper;
30 import android.os.ParcelFileDescriptor;
31 import android.os.Process;
32 import android.os.RemoteException;
33 import android.os.UserHandle;
34 import android.system.ErrnoException;
35 import android.system.Os;
36 import android.system.OsConstants;
37 import android.system.StructStat;
38 import android.util.ArraySet;
39 import android.util.Log;
40 
41 import libcore.io.IoUtils;
42 
43 import org.xmlpull.v1.XmlPullParserException;
44 
45 import java.io.File;
46 import java.io.FileOutputStream;
47 import java.io.IOException;
48 import java.util.LinkedList;
49 import java.util.Map;
50 import java.util.Set;
51 import java.util.concurrent.CountDownLatch;
52 
53 /**
54  * Provides the central interface between an
55  * application and Android's data backup infrastructure.  An application that wishes
56  * to participate in the backup and restore mechanism will declare a subclass of
57  * {@link android.app.backup.BackupAgent}, implement the
58  * {@link #onBackup(ParcelFileDescriptor, BackupDataOutput, ParcelFileDescriptor) onBackup()}
59  * and {@link #onRestore(BackupDataInput, int, ParcelFileDescriptor) onRestore()} methods,
60  * and provide the name of its backup agent class in its {@code AndroidManifest.xml} file via
61  * the <code>
62  * <a href="{@docRoot}guide/topics/manifest/application-element.html">&lt;application&gt;</a></code>
63  * tag's {@code android:backupAgent} attribute.
64  *
65  * <div class="special reference">
66  * <h3>Developer Guides</h3>
67  * <p>For more information about using BackupAgent, read the
68  * <a href="{@docRoot}guide/topics/data/backup.html">Data Backup</a> developer guide.</p></div>
69  *
70  * <h3>Basic Operation</h3>
71  * <p>
72  * When the application makes changes to data that it wishes to keep backed up,
73  * it should call the
74  * {@link android.app.backup.BackupManager#dataChanged() BackupManager.dataChanged()} method.
75  * This notifies the Android Backup Manager that the application needs an opportunity
76  * to update its backup image.  The Backup Manager, in turn, schedules a
77  * backup pass to be performed at an opportune time.
78  * <p>
79  * Restore operations are typically performed only when applications are first
80  * installed on a device.  At that time, the operating system checks to see whether
81  * there is a previously-saved data set available for the application being installed, and if so,
82  * begins an immediate restore pass to deliver the backup data as part of the installation
83  * process.
84  * <p>
85  * When a backup or restore pass is run, the application's process is launched
86  * (if not already running), the manifest-declared backup agent class (in the {@code
87  * android:backupAgent} attribute) is instantiated within
88  * that process, and the agent's {@link #onCreate()} method is invoked.  This prepares the
89  * agent instance to run the actual backup or restore logic.  At this point the
90  * agent's
91  * {@link #onBackup(ParcelFileDescriptor, BackupDataOutput, ParcelFileDescriptor) onBackup()} or
92  * {@link #onRestore(BackupDataInput, int, ParcelFileDescriptor) onRestore()} method will be
93  * invoked as appropriate for the operation being performed.
94  * <p>
95  * A backup data set consists of one or more "entities," flattened binary data
96  * records that are each identified with a key string unique within the data set.  Adding a
97  * record to the active data set or updating an existing record is done by simply
98  * writing new entity data under the desired key.  Deleting an entity from the data set
99  * is done by writing an entity under that key with header specifying a negative data
100  * size, and no actual entity data.
101  * <p>
102  * <b>Helper Classes</b>
103  * <p>
104  * An extensible agent based on convenient helper classes is available in
105  * {@link android.app.backup.BackupAgentHelper}.  That class is particularly
106  * suited to handling of simple file or {@link android.content.SharedPreferences}
107  * backup and restore.
108  * <p>
109  * <b>Threading</b>
110  * <p>
111  * The constructor, as well as {@link #onCreate()} and {@link #onDestroy()} lifecycle callbacks run
112  * on the main thread (UI thread) of the application that implements the BackupAgent.
113  * The data-handling callbacks:
114  * {@link #onBackup(ParcelFileDescriptor, BackupDataOutput, ParcelFileDescriptor) onBackup()},
115  * {@link #onFullBackup(FullBackupDataOutput)},
116  * {@link #onRestore(BackupDataInput, int, ParcelFileDescriptor) onRestore()},
117  * {@link #onRestoreFile(ParcelFileDescriptor, long, File, int, long, long) onRestoreFile()},
118  * {@link #onRestoreFinished()}, and {@link #onQuotaExceeded(long, long) onQuotaExceeded()}
119  * run on binder pool threads.
120  *
121  * @see android.app.backup.BackupManager
122  * @see android.app.backup.BackupAgentHelper
123  * @see android.app.backup.BackupDataInput
124  * @see android.app.backup.BackupDataOutput
125  */
126 public abstract class BackupAgent extends ContextWrapper {
127     private static final String TAG = "BackupAgent";
128     private static final boolean DEBUG = false;
129 
130     /** @hide */
131     public static final int RESULT_SUCCESS = 0;
132     /** @hide */
133     public static final int RESULT_ERROR = -1;
134 
135     /** @hide */
136     public static final int TYPE_EOF = 0;
137 
138     /**
139      * During a full restore, indicates that the file system object being restored
140      * is an ordinary file.
141      */
142     public static final int TYPE_FILE = 1;
143 
144     /**
145      * During a full restore, indicates that the file system object being restored
146      * is a directory.
147      */
148     public static final int TYPE_DIRECTORY = 2;
149 
150     /** @hide */
151     public static final int TYPE_SYMLINK = 3;
152 
153     /**
154      * Flag for {@link BackupDataOutput#getTransportFlags()} and
155      * {@link FullBackupDataOutput#getTransportFlags()} only.
156      *
157      * <p>The transport has client-side encryption enabled. i.e., the user's backup has been
158      * encrypted with a key known only to the device, and not to the remote storage solution. Even
159      * if an attacker had root access to the remote storage provider they should not be able to
160      * decrypt the user's backup data.
161      */
162     public static final int FLAG_CLIENT_SIDE_ENCRYPTION_ENABLED = 1;
163 
164     /**
165      * Flag for {@link BackupDataOutput#getTransportFlags()} and
166      * {@link FullBackupDataOutput#getTransportFlags()} only.
167      *
168      * <p>The transport is for a device-to-device transfer. There is no third party or intermediate
169      * storage. The user's backup data is sent directly to another device over e.g., USB or WiFi.
170      */
171     public static final int FLAG_DEVICE_TO_DEVICE_TRANSFER = 2;
172 
173     /**
174      * Flag for {@link BackupDataOutput#getTransportFlags()} and
175      * {@link FullBackupDataOutput#getTransportFlags()} only.
176      *
177      * <p>Used for internal testing only. Do not check this flag in production code.
178      *
179      * @hide
180      */
181     public static final int FLAG_FAKE_CLIENT_SIDE_ENCRYPTION_ENABLED = 1 << 31;
182 
183     Handler mHandler = null;
184 
185     @Nullable private UserHandle mUser;
186 
getHandler()187     Handler getHandler() {
188         if (mHandler == null) {
189             mHandler = new Handler(Looper.getMainLooper());
190         }
191         return mHandler;
192     }
193 
194     class SharedPrefsSynchronizer implements Runnable {
195         public final CountDownLatch mLatch = new CountDownLatch(1);
196 
197         @Override
run()198         public void run() {
199             QueuedWork.waitToFinish();
200             mLatch.countDown();
201         }
202     };
203 
204     // Syncing shared preferences deferred writes needs to happen on the main looper thread
waitForSharedPrefs()205     private void waitForSharedPrefs() {
206         Handler h = getHandler();
207         final SharedPrefsSynchronizer s = new SharedPrefsSynchronizer();
208         h.postAtFrontOfQueue(s);
209         try {
210             s.mLatch.await();
211         } catch (InterruptedException e) { /* ignored */ }
212     }
213 
214 
BackupAgent()215     public BackupAgent() {
216         super(null);
217     }
218 
219     /**
220      * Provided as a convenience for agent implementations that need an opportunity
221      * to do one-time initialization before the actual backup or restore operation
222      * is begun.
223      * <p>
224      */
onCreate()225     public void onCreate() {
226     }
227 
228     /**
229      * Provided as a convenience for agent implementations that need an opportunity
230      * to do one-time initialization before the actual backup or restore operation
231      * is begun with information about the calling user.
232      * <p>
233      *
234      * @hide
235      */
onCreate(UserHandle user)236     public void onCreate(UserHandle user) {
237         onCreate();
238 
239         mUser = user;
240     }
241 
242     /**
243      * Provided as a convenience for agent implementations that need to do some
244      * sort of shutdown process after backup or restore is completed.
245      * <p>
246      * Agents do not need to override this method.
247      */
onDestroy()248     public void onDestroy() {
249     }
250 
251     /**
252      * The application is being asked to write any data changed since the last
253      * time it performed a backup operation. The state data recorded during the
254      * last backup pass is provided in the <code>oldState</code> file
255      * descriptor. If <code>oldState</code> is <code>null</code>, no old state
256      * is available and the application should perform a full backup. In both
257      * cases, a representation of the final backup state after this pass should
258      * be written to the file pointed to by the file descriptor wrapped in
259      * <code>newState</code>.
260      * <p>
261      * Each entity written to the {@link android.app.backup.BackupDataOutput}
262      * <code>data</code> stream will be transmitted
263      * over the current backup transport and stored in the remote data set under
264      * the key supplied as part of the entity.  Writing an entity with a negative
265      * data size instructs the transport to delete whatever entity currently exists
266      * under that key from the remote data set.
267      *
268      * @param oldState An open, read-only ParcelFileDescriptor pointing to the
269      *            last backup state provided by the application. May be
270      *            <code>null</code>, in which case no prior state is being
271      *            provided and the application should perform a full backup.
272      * @param data A structured wrapper around an open, read/write
273      *            file descriptor pointing to the backup data destination.
274      *            Typically the application will use backup helper classes to
275      *            write to this file.
276      * @param newState An open, read/write ParcelFileDescriptor pointing to an
277      *            empty file. The application should record the final backup
278      *            state here after writing the requested data to the <code>data</code>
279      *            output stream.
280      */
onBackup(ParcelFileDescriptor oldState, BackupDataOutput data, ParcelFileDescriptor newState)281     public abstract void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
282             ParcelFileDescriptor newState) throws IOException;
283 
284     /**
285      * The application is being restored from backup and should replace any
286      * existing data with the contents of the backup. The backup data is
287      * provided through the <code>data</code> parameter. Once
288      * the restore is finished, the application should write a representation of
289      * the final state to the <code>newState</code> file descriptor.
290      * <p>
291      * The application is responsible for properly erasing its old data and
292      * replacing it with the data supplied to this method. No "clear user data"
293      * operation will be performed automatically by the operating system. The
294      * exception to this is in the case of a failed restore attempt: if
295      * onRestore() throws an exception, the OS will assume that the
296      * application's data may now be in an incoherent state, and will clear it
297      * before proceeding.
298      *
299      * @param data A structured wrapper around an open, read-only
300      *            file descriptor pointing to a full snapshot of the
301      *            application's data.  The application should consume every
302      *            entity represented in this data stream.
303      * @param appVersionCode The value of the <a
304      * href="{@docRoot}guide/topics/manifest/manifest-element.html#vcode">{@code
305      *            android:versionCode}</a> manifest attribute,
306      *            from the application that backed up this particular data set. This
307      *            makes it possible for an application's agent to distinguish among any
308      *            possible older data versions when asked to perform the restore
309      *            operation.
310      * @param newState An open, read/write ParcelFileDescriptor pointing to an
311      *            empty file. The application should record the final backup
312      *            state here after restoring its data from the <code>data</code> stream.
313      *            When a full-backup dataset is being restored, this will be <code>null</code>.
314      */
onRestore(BackupDataInput data, int appVersionCode, ParcelFileDescriptor newState)315     public abstract void onRestore(BackupDataInput data, int appVersionCode,
316             ParcelFileDescriptor newState) throws IOException;
317 
318     /**
319      * New version of {@link #onRestore(BackupDataInput, int, android.os.ParcelFileDescriptor)}
320      * that handles a long app version code.  Default implementation casts the version code to
321      * an int and calls {@link #onRestore(BackupDataInput, int, android.os.ParcelFileDescriptor)}.
322      */
onRestore(BackupDataInput data, long appVersionCode, ParcelFileDescriptor newState)323     public void onRestore(BackupDataInput data, long appVersionCode,
324             ParcelFileDescriptor newState)
325             throws IOException {
326         onRestore(data, (int) appVersionCode, newState);
327     }
328 
329     /**
330      * The application is having its entire file system contents backed up.  {@code data}
331      * points to the backup destination, and the app has the opportunity to choose which
332      * files are to be stored.  To commit a file as part of the backup, call the
333      * {@link #fullBackupFile(File, FullBackupDataOutput)} helper method.  After all file
334      * data is written to the output, the agent returns from this method and the backup
335      * operation concludes.
336      *
337      * <p>Certain parts of the app's data are never backed up even if the app explicitly
338      * sends them to the output:
339      *
340      * <ul>
341      * <li>The contents of the {@link #getCacheDir()} directory</li>
342      * <li>The contents of the {@link #getCodeCacheDir()} directory</li>
343      * <li>The contents of the {@link #getNoBackupFilesDir()} directory</li>
344      * <li>The contents of the app's shared library directory</li>
345      * </ul>
346      *
347      * <p>The default implementation of this method backs up the entirety of the
348      * application's "owned" file system trees to the output other than the few exceptions
349      * listed above.  Apps only need to override this method if they need to impose special
350      * limitations on which files are being stored beyond the control that
351      * {@link #getNoBackupFilesDir()} offers.
352      * Alternatively they can provide an xml resource to specify what data to include or exclude.
353      *
354      *
355      * @param data A structured wrapper pointing to the backup destination.
356      * @throws IOException
357      *
358      * @see Context#getNoBackupFilesDir()
359      * @see #fullBackupFile(File, FullBackupDataOutput)
360      * @see #onRestoreFile(ParcelFileDescriptor, long, File, int, long, long)
361      */
onFullBackup(FullBackupDataOutput data)362     public void onFullBackup(FullBackupDataOutput data) throws IOException {
363         FullBackup.BackupScheme backupScheme = FullBackup.getBackupScheme(this);
364         if (!backupScheme.isFullBackupContentEnabled()) {
365             return;
366         }
367 
368         Map<String, Set<PathWithRequiredFlags>> manifestIncludeMap;
369         ArraySet<PathWithRequiredFlags> manifestExcludeSet;
370         try {
371             manifestIncludeMap =
372                     backupScheme.maybeParseAndGetCanonicalIncludePaths();
373             manifestExcludeSet = backupScheme.maybeParseAndGetCanonicalExcludePaths();
374         } catch (IOException | XmlPullParserException e) {
375             if (Log.isLoggable(FullBackup.TAG_XML_PARSER, Log.VERBOSE)) {
376                 Log.v(FullBackup.TAG_XML_PARSER,
377                         "Exception trying to parse fullBackupContent xml file!"
378                                 + " Aborting full backup.", e);
379             }
380             return;
381         }
382 
383         final String packageName = getPackageName();
384         final ApplicationInfo appInfo = getApplicationInfo();
385 
386         // System apps have control over where their default storage context
387         // is pointed, so we're always explicit when building paths.
388         final Context ceContext = createCredentialProtectedStorageContext();
389         final String rootDir = ceContext.getDataDir().getCanonicalPath();
390         final String filesDir = ceContext.getFilesDir().getCanonicalPath();
391         final String noBackupDir = ceContext.getNoBackupFilesDir().getCanonicalPath();
392         final String databaseDir = ceContext.getDatabasePath("foo").getParentFile()
393                 .getCanonicalPath();
394         final String sharedPrefsDir = ceContext.getSharedPreferencesPath("foo").getParentFile()
395                 .getCanonicalPath();
396         final String cacheDir = ceContext.getCacheDir().getCanonicalPath();
397         final String codeCacheDir = ceContext.getCodeCacheDir().getCanonicalPath();
398 
399         final Context deContext = createDeviceProtectedStorageContext();
400         final String deviceRootDir = deContext.getDataDir().getCanonicalPath();
401         final String deviceFilesDir = deContext.getFilesDir().getCanonicalPath();
402         final String deviceNoBackupDir = deContext.getNoBackupFilesDir().getCanonicalPath();
403         final String deviceDatabaseDir = deContext.getDatabasePath("foo").getParentFile()
404                 .getCanonicalPath();
405         final String deviceSharedPrefsDir = deContext.getSharedPreferencesPath("foo")
406                 .getParentFile().getCanonicalPath();
407         final String deviceCacheDir = deContext.getCacheDir().getCanonicalPath();
408         final String deviceCodeCacheDir = deContext.getCodeCacheDir().getCanonicalPath();
409 
410         final String libDir = (appInfo.nativeLibraryDir != null)
411                 ? new File(appInfo.nativeLibraryDir).getCanonicalPath()
412                 : null;
413 
414         // Maintain a set of excluded directories so that as we traverse the tree we know we're not
415         // going places we don't expect, and so the manifest includes can't take precedence over
416         // what the framework decides is not to be included.
417         final ArraySet<String> traversalExcludeSet = new ArraySet<String>();
418 
419         // Add the directories we always exclude.
420         traversalExcludeSet.add(filesDir);
421         traversalExcludeSet.add(noBackupDir);
422         traversalExcludeSet.add(databaseDir);
423         traversalExcludeSet.add(sharedPrefsDir);
424         traversalExcludeSet.add(cacheDir);
425         traversalExcludeSet.add(codeCacheDir);
426 
427         traversalExcludeSet.add(deviceFilesDir);
428         traversalExcludeSet.add(deviceNoBackupDir);
429         traversalExcludeSet.add(deviceDatabaseDir);
430         traversalExcludeSet.add(deviceSharedPrefsDir);
431         traversalExcludeSet.add(deviceCacheDir);
432         traversalExcludeSet.add(deviceCodeCacheDir);
433 
434         if (libDir != null) {
435             traversalExcludeSet.add(libDir);
436         }
437 
438         // Root dir first.
439         applyXmlFiltersAndDoFullBackupForDomain(
440                 packageName, FullBackup.ROOT_TREE_TOKEN, manifestIncludeMap,
441                 manifestExcludeSet, traversalExcludeSet, data);
442         traversalExcludeSet.add(rootDir);
443 
444         applyXmlFiltersAndDoFullBackupForDomain(
445                 packageName, FullBackup.DEVICE_ROOT_TREE_TOKEN, manifestIncludeMap,
446                 manifestExcludeSet, traversalExcludeSet, data);
447         traversalExcludeSet.add(deviceRootDir);
448 
449         // Data dir next.
450         traversalExcludeSet.remove(filesDir);
451         applyXmlFiltersAndDoFullBackupForDomain(
452                 packageName, FullBackup.FILES_TREE_TOKEN, manifestIncludeMap,
453                 manifestExcludeSet, traversalExcludeSet, data);
454         traversalExcludeSet.add(filesDir);
455 
456         traversalExcludeSet.remove(deviceFilesDir);
457         applyXmlFiltersAndDoFullBackupForDomain(
458                 packageName, FullBackup.DEVICE_FILES_TREE_TOKEN, manifestIncludeMap,
459                 manifestExcludeSet, traversalExcludeSet, data);
460         traversalExcludeSet.add(deviceFilesDir);
461 
462         // Database directory.
463         traversalExcludeSet.remove(databaseDir);
464         applyXmlFiltersAndDoFullBackupForDomain(
465                 packageName, FullBackup.DATABASE_TREE_TOKEN, manifestIncludeMap,
466                 manifestExcludeSet, traversalExcludeSet, data);
467         traversalExcludeSet.add(databaseDir);
468 
469         traversalExcludeSet.remove(deviceDatabaseDir);
470         applyXmlFiltersAndDoFullBackupForDomain(
471                 packageName, FullBackup.DEVICE_DATABASE_TREE_TOKEN, manifestIncludeMap,
472                 manifestExcludeSet, traversalExcludeSet, data);
473         traversalExcludeSet.add(deviceDatabaseDir);
474 
475         // SharedPrefs.
476         traversalExcludeSet.remove(sharedPrefsDir);
477         applyXmlFiltersAndDoFullBackupForDomain(
478                 packageName, FullBackup.SHAREDPREFS_TREE_TOKEN, manifestIncludeMap,
479                 manifestExcludeSet, traversalExcludeSet, data);
480         traversalExcludeSet.add(sharedPrefsDir);
481 
482         traversalExcludeSet.remove(deviceSharedPrefsDir);
483         applyXmlFiltersAndDoFullBackupForDomain(
484                 packageName, FullBackup.DEVICE_SHAREDPREFS_TREE_TOKEN, manifestIncludeMap,
485                 manifestExcludeSet, traversalExcludeSet, data);
486         traversalExcludeSet.add(deviceSharedPrefsDir);
487 
488         // getExternalFilesDir() location associated with this app.  Technically there should
489         // not be any files here if the app does not properly have permission to access
490         // external storage, but edge cases happen. fullBackupFileTree() catches
491         // IOExceptions and similar, and treats them as non-fatal, so we rely on that; and
492         // we know a priori that processes running as the system UID are not permitted to
493         // access external storage, so we check for that as well to avoid nastygrams in
494         // the log.
495         if (Process.myUid() != Process.SYSTEM_UID) {
496             File efLocation = getExternalFilesDir(null);
497             if (efLocation != null) {
498                 applyXmlFiltersAndDoFullBackupForDomain(
499                         packageName, FullBackup.MANAGED_EXTERNAL_TREE_TOKEN, manifestIncludeMap,
500                         manifestExcludeSet, traversalExcludeSet, data);
501             }
502 
503         }
504     }
505 
506     /**
507      * Notification that the application's current backup operation causes it to exceed
508      * the maximum size permitted by the transport.  The ongoing backup operation is
509      * halted and rolled back: any data that had been stored by a previous backup operation
510      * is still intact.  Typically the quota-exceeded state will be detected before any data
511      * is actually transmitted over the network.
512      *
513      * <p>The {@code quotaBytes} value is the total data size currently permitted for this
514      * application.  If desired, the application can use this as a hint for determining
515      * how much data to store.  For example, a messaging application might choose to
516      * store only the newest messages, dropping enough older content to stay under
517      * the quota.
518      *
519      * <p class="note">Note that the maximum quota for the application can change over
520      * time.  In particular, in the future the quota may grow.  Applications that adapt
521      * to the quota when deciding what data to store should be aware of this and implement
522      * their data storage mechanisms in a way that can take advantage of additional
523      * quota.
524      *
525      * @param backupDataBytes The amount of data measured while initializing the backup
526      *    operation, if the total exceeds the app's alloted quota.  If initial measurement
527      *    suggested that the data would fit but then too much data was actually submitted
528      *    as part of the operation, then this value is the amount of data that had been
529      *    streamed into the transport at the time the quota was reached.
530      * @param quotaBytes The maximum data size that the transport currently permits
531      *    this application to store as a backup.
532      */
onQuotaExceeded(long backupDataBytes, long quotaBytes)533     public void onQuotaExceeded(long backupDataBytes, long quotaBytes) {
534     }
535 
getBackupUserId()536     private int getBackupUserId() {
537         return mUser == null ? super.getUserId() : mUser.getIdentifier();
538     }
539 
540     /**
541      * Check whether the xml yielded any <include/> tag for the provided <code>domainToken</code>.
542      * If so, perform a {@link #fullBackupFileTree} which backs up the file or recurses if the path
543      * is a directory, but only if all the required flags of the include rule are satisfied by
544      * the transport.
545      */
applyXmlFiltersAndDoFullBackupForDomain(String packageName, String domainToken, Map<String, Set<PathWithRequiredFlags>> includeMap, ArraySet<PathWithRequiredFlags> filterSet, ArraySet<String> traversalExcludeSet, FullBackupDataOutput data)546     private void applyXmlFiltersAndDoFullBackupForDomain(String packageName, String domainToken,
547             Map<String, Set<PathWithRequiredFlags>> includeMap,
548             ArraySet<PathWithRequiredFlags> filterSet, ArraySet<String> traversalExcludeSet,
549             FullBackupDataOutput data) throws IOException {
550         if (includeMap == null || includeMap.size() == 0) {
551             // Do entire sub-tree for the provided token.
552             fullBackupFileTree(packageName, domainToken,
553                     FullBackup.getBackupScheme(this).tokenToDirectoryPath(domainToken),
554                     filterSet, traversalExcludeSet, data);
555         } else if (includeMap.get(domainToken) != null) {
556             // This will be null if the xml parsing didn't yield any rules for
557             // this domain (there may still be rules for other domains).
558             for (PathWithRequiredFlags includeFile : includeMap.get(domainToken)) {
559                 if (areIncludeRequiredTransportFlagsSatisfied(includeFile.getRequiredFlags(),
560                         data.getTransportFlags())) {
561                     fullBackupFileTree(packageName, domainToken, includeFile.getPath(), filterSet,
562                             traversalExcludeSet, data);
563                 }
564             }
565         }
566     }
567 
areIncludeRequiredTransportFlagsSatisfied(int includeFlags, int transportFlags)568     private boolean areIncludeRequiredTransportFlagsSatisfied(int includeFlags,
569             int transportFlags) {
570         // all bits that are set in includeFlags must also be set in transportFlags
571         return (transportFlags & includeFlags) == includeFlags;
572     }
573 
574     /**
575      * Write an entire file as part of a full-backup operation.  The file's contents
576      * will be delivered to the backup destination along with the metadata necessary
577      * to place it with the proper location and permissions on the device where the
578      * data is restored.
579      *
580      * <p class="note">Attempting to back up files in directories that are ignored by
581      * the backup system will have no effect.  For example, if the app calls this method
582      * with a file inside the {@link #getNoBackupFilesDir()} directory, it will be ignored.
583      * See {@link #onFullBackup(FullBackupDataOutput)} for details on what directories
584      * are excluded from backups.
585      *
586      * @param file The file to be backed up.  The file must exist and be readable by
587      *     the caller.
588      * @param output The destination to which the backed-up file data will be sent.
589      */
fullBackupFile(File file, FullBackupDataOutput output)590     public final void fullBackupFile(File file, FullBackupDataOutput output) {
591         // Look up where all of our various well-defined dir trees live on this device
592         final String rootDir;
593         final String filesDir;
594         final String nbFilesDir;
595         final String dbDir;
596         final String spDir;
597         final String cacheDir;
598         final String codeCacheDir;
599         final String deviceRootDir;
600         final String deviceFilesDir;
601         final String deviceNbFilesDir;
602         final String deviceDbDir;
603         final String deviceSpDir;
604         final String deviceCacheDir;
605         final String deviceCodeCacheDir;
606         final String libDir;
607 
608         String efDir = null;
609         String filePath;
610 
611         ApplicationInfo appInfo = getApplicationInfo();
612 
613         try {
614             // System apps have control over where their default storage context
615             // is pointed, so we're always explicit when building paths.
616             final Context ceContext = createCredentialProtectedStorageContext();
617             rootDir = ceContext.getDataDir().getCanonicalPath();
618             filesDir = ceContext.getFilesDir().getCanonicalPath();
619             nbFilesDir = ceContext.getNoBackupFilesDir().getCanonicalPath();
620             dbDir = ceContext.getDatabasePath("foo").getParentFile().getCanonicalPath();
621             spDir = ceContext.getSharedPreferencesPath("foo").getParentFile().getCanonicalPath();
622             cacheDir = ceContext.getCacheDir().getCanonicalPath();
623             codeCacheDir = ceContext.getCodeCacheDir().getCanonicalPath();
624 
625             final Context deContext = createDeviceProtectedStorageContext();
626             deviceRootDir = deContext.getDataDir().getCanonicalPath();
627             deviceFilesDir = deContext.getFilesDir().getCanonicalPath();
628             deviceNbFilesDir = deContext.getNoBackupFilesDir().getCanonicalPath();
629             deviceDbDir = deContext.getDatabasePath("foo").getParentFile().getCanonicalPath();
630             deviceSpDir = deContext.getSharedPreferencesPath("foo").getParentFile()
631                     .getCanonicalPath();
632             deviceCacheDir = deContext.getCacheDir().getCanonicalPath();
633             deviceCodeCacheDir = deContext.getCodeCacheDir().getCanonicalPath();
634 
635             libDir = (appInfo.nativeLibraryDir == null)
636                     ? null
637                     : new File(appInfo.nativeLibraryDir).getCanonicalPath();
638 
639             // may or may not have external files access to attempt backup/restore there
640             if (Process.myUid() != Process.SYSTEM_UID) {
641                 File efLocation = getExternalFilesDir(null);
642                 if (efLocation != null) {
643                     efDir = efLocation.getCanonicalPath();
644                 }
645             }
646 
647             // Now figure out which well-defined tree the file is placed in, working from
648             // most to least specific.  We also specifically exclude the lib, cache,
649             // and code_cache dirs.
650             filePath = file.getCanonicalPath();
651         } catch (IOException e) {
652             Log.w(TAG, "Unable to obtain canonical paths");
653             return;
654         }
655 
656         if (filePath.startsWith(cacheDir)
657                 || filePath.startsWith(codeCacheDir)
658                 || filePath.startsWith(nbFilesDir)
659                 || filePath.startsWith(deviceCacheDir)
660                 || filePath.startsWith(deviceCodeCacheDir)
661                 || filePath.startsWith(deviceNbFilesDir)
662                 || filePath.startsWith(libDir)) {
663             Log.w(TAG, "lib, cache, code_cache, and no_backup files are not backed up");
664             return;
665         }
666 
667         final String domain;
668         String rootpath = null;
669         if (filePath.startsWith(dbDir)) {
670             domain = FullBackup.DATABASE_TREE_TOKEN;
671             rootpath = dbDir;
672         } else if (filePath.startsWith(spDir)) {
673             domain = FullBackup.SHAREDPREFS_TREE_TOKEN;
674             rootpath = spDir;
675         } else if (filePath.startsWith(filesDir)) {
676             domain = FullBackup.FILES_TREE_TOKEN;
677             rootpath = filesDir;
678         } else if (filePath.startsWith(rootDir)) {
679             domain = FullBackup.ROOT_TREE_TOKEN;
680             rootpath = rootDir;
681         } else if (filePath.startsWith(deviceDbDir)) {
682             domain = FullBackup.DEVICE_DATABASE_TREE_TOKEN;
683             rootpath = deviceDbDir;
684         } else if (filePath.startsWith(deviceSpDir)) {
685             domain = FullBackup.DEVICE_SHAREDPREFS_TREE_TOKEN;
686             rootpath = deviceSpDir;
687         } else if (filePath.startsWith(deviceFilesDir)) {
688             domain = FullBackup.DEVICE_FILES_TREE_TOKEN;
689             rootpath = deviceFilesDir;
690         } else if (filePath.startsWith(deviceRootDir)) {
691             domain = FullBackup.DEVICE_ROOT_TREE_TOKEN;
692             rootpath = deviceRootDir;
693         } else if ((efDir != null) && filePath.startsWith(efDir)) {
694             domain = FullBackup.MANAGED_EXTERNAL_TREE_TOKEN;
695             rootpath = efDir;
696         } else {
697             Log.w(TAG, "File " + filePath + " is in an unsupported location; skipping");
698             return;
699         }
700 
701         // And now that we know where it lives, semantically, back it up appropriately
702         // In the measurement case, backupToTar() updates the size in output and returns
703         // without transmitting any file data.
704         if (DEBUG) Log.i(TAG, "backupFile() of " + filePath + " => domain=" + domain
705                 + " rootpath=" + rootpath);
706 
707         FullBackup.backupToTar(getPackageName(), domain, null, rootpath, filePath, output);
708     }
709 
710     /**
711      * Scan the dir tree (if it actually exists) and process each entry we find.  If the
712      * 'excludes' parameters are non-null, they are consulted each time a new file system entity
713      * is visited to see whether that entity (and its subtree, if appropriate) should be
714      * omitted from the backup process.
715      *
716      * @param systemExcludes An optional list of excludes.
717      * @hide
718      */
fullBackupFileTree(String packageName, String domain, String startingPath, ArraySet<PathWithRequiredFlags> manifestExcludes, ArraySet<String> systemExcludes, FullBackupDataOutput output)719     protected final void fullBackupFileTree(String packageName, String domain, String startingPath,
720                                             ArraySet<PathWithRequiredFlags> manifestExcludes,
721                                             ArraySet<String> systemExcludes,
722             FullBackupDataOutput output) {
723         // Pull out the domain and set it aside to use when making the tarball.
724         String domainPath = FullBackup.getBackupScheme(this).tokenToDirectoryPath(domain);
725         if (domainPath == null) {
726             // Should never happen.
727             return;
728         }
729 
730         File rootFile = new File(startingPath);
731         if (rootFile.exists()) {
732             LinkedList<File> scanQueue = new LinkedList<File>();
733             scanQueue.add(rootFile);
734 
735             while (scanQueue.size() > 0) {
736                 File file = scanQueue.remove(0);
737                 String filePath;
738                 try {
739                     // Ignore things that aren't "real" files or dirs
740                     StructStat stat = Os.lstat(file.getPath());
741                     if (!OsConstants.S_ISREG(stat.st_mode)
742                             && !OsConstants.S_ISDIR(stat.st_mode)) {
743                         if (DEBUG) Log.i(TAG, "Not a file/dir (skipping)!: " + file);
744                         continue;
745                     }
746 
747                     // For all other verification, look at the canonicalized path
748                     filePath = file.getCanonicalPath();
749 
750                     // prune this subtree?
751                     if (manifestExcludes != null
752                             && manifestExcludesContainFilePath(manifestExcludes, filePath)) {
753                         continue;
754                     }
755                     if (systemExcludes != null && systemExcludes.contains(filePath)) {
756                         continue;
757                     }
758 
759                     // If it's a directory, enqueue its contents for scanning.
760                     if (OsConstants.S_ISDIR(stat.st_mode)) {
761                         File[] contents = file.listFiles();
762                         if (contents != null) {
763                             for (File entry : contents) {
764                                 scanQueue.add(0, entry);
765                             }
766                         }
767                     }
768                 } catch (IOException e) {
769                     if (DEBUG) Log.w(TAG, "Error canonicalizing path of " + file);
770                     if (Log.isLoggable(FullBackup.TAG_XML_PARSER, Log.VERBOSE)) {
771                         Log.v(FullBackup.TAG_XML_PARSER, "Error canonicalizing path of " + file);
772                     }
773                     continue;
774                 } catch (ErrnoException e) {
775                     if (DEBUG) Log.w(TAG, "Error scanning file " + file + " : " + e);
776                     if (Log.isLoggable(FullBackup.TAG_XML_PARSER, Log.VERBOSE)) {
777                         Log.v(FullBackup.TAG_XML_PARSER, "Error scanning file " + file + " : " + e);
778                     }
779                     continue;
780                 }
781 
782                 // Finally, back this file up (or measure it) before proceeding
783                 FullBackup.backupToTar(packageName, domain, null, domainPath, filePath, output);
784             }
785         }
786     }
787 
manifestExcludesContainFilePath( ArraySet<PathWithRequiredFlags> manifestExcludes, String filePath)788     private boolean manifestExcludesContainFilePath(
789         ArraySet<PathWithRequiredFlags> manifestExcludes, String filePath) {
790         for (PathWithRequiredFlags exclude : manifestExcludes) {
791             String excludePath = exclude.getPath();
792             if (excludePath != null && excludePath.equals(filePath)) {
793                 return true;
794             }
795         }
796         return false;
797     }
798 
799     /**
800      * Handle the data delivered via the given file descriptor during a full restore
801      * operation.  The agent is given the path to the file's original location as well
802      * as its size and metadata.
803      * <p>
804      * The file descriptor can only be read for {@code size} bytes; attempting to read
805      * more data has undefined behavior.
806      * <p>
807      * The default implementation creates the destination file/directory and populates it
808      * with the data from the file descriptor, then sets the file's access mode and
809      * modification time to match the restore arguments.
810      *
811      * @param data A read-only file descriptor from which the agent can read {@code size}
812      *     bytes of file data.
813      * @param size The number of bytes of file content to be restored to the given
814      *     destination.  If the file system object being restored is a directory, {@code size}
815      *     will be zero.
816      * @param destination The File on disk to be restored with the given data.
817      * @param type The kind of file system object being restored.  This will be either
818      *     {@link BackupAgent#TYPE_FILE} or {@link BackupAgent#TYPE_DIRECTORY}.
819      * @param mode The access mode to be assigned to the destination after its data is
820      *     written.  This is in the standard format used by {@code chmod()}.
821      * @param mtime The modification time of the file when it was backed up, suitable to
822      *     be assigned to the file after its data is written.
823      * @throws IOException
824      */
onRestoreFile(ParcelFileDescriptor data, long size, File destination, int type, long mode, long mtime)825     public void onRestoreFile(ParcelFileDescriptor data, long size,
826             File destination, int type, long mode, long mtime)
827             throws IOException {
828 
829         final boolean accept = isFileEligibleForRestore(destination);
830         // If we don't accept the file, consume the bytes from the pipe anyway.
831         FullBackup.restoreFile(data, size, type, mode, mtime, accept ? destination : null);
832     }
833 
isFileEligibleForRestore(File destination)834     private boolean isFileEligibleForRestore(File destination) throws IOException {
835         FullBackup.BackupScheme bs = FullBackup.getBackupScheme(this);
836         if (!bs.isFullBackupContentEnabled()) {
837             if (Log.isLoggable(FullBackup.TAG_XML_PARSER, Log.VERBOSE)) {
838                 Log.v(FullBackup.TAG_XML_PARSER,
839                         "onRestoreFile \"" + destination.getCanonicalPath()
840                                 + "\" : fullBackupContent not enabled for " + getPackageName());
841             }
842             return false;
843         }
844 
845         Map<String, Set<PathWithRequiredFlags>> includes = null;
846         ArraySet<PathWithRequiredFlags> excludes = null;
847         final String destinationCanonicalPath = destination.getCanonicalPath();
848         try {
849             includes = bs.maybeParseAndGetCanonicalIncludePaths();
850             excludes = bs.maybeParseAndGetCanonicalExcludePaths();
851         } catch (XmlPullParserException e) {
852             if (Log.isLoggable(FullBackup.TAG_XML_PARSER, Log.VERBOSE)) {
853                 Log.v(FullBackup.TAG_XML_PARSER,
854                         "onRestoreFile \"" + destinationCanonicalPath
855                                 + "\" : Exception trying to parse fullBackupContent xml file!"
856                                 + " Aborting onRestoreFile.", e);
857             }
858             return false;
859         }
860 
861         if (excludes != null &&
862                 BackupUtils.isFileSpecifiedInPathList(destination, excludes)) {
863             if (Log.isLoggable(FullBackup.TAG_XML_PARSER, Log.VERBOSE)) {
864                 Log.v(FullBackup.TAG_XML_PARSER,
865                         "onRestoreFile: \"" + destinationCanonicalPath + "\": listed in"
866                                 + " excludes; skipping.");
867             }
868             return false;
869         }
870 
871         if (includes != null && !includes.isEmpty()) {
872             // Rather than figure out the <include/> domain based on the path (a lot of code, and
873             // it's a small list), we'll go through and look for it.
874             boolean explicitlyIncluded = false;
875             for (Set<PathWithRequiredFlags> domainIncludes : includes.values()) {
876                 explicitlyIncluded |=
877                         BackupUtils.isFileSpecifiedInPathList(destination, domainIncludes);
878                 if (explicitlyIncluded) {
879                     break;
880                 }
881             }
882             if (!explicitlyIncluded) {
883                 if (Log.isLoggable(FullBackup.TAG_XML_PARSER, Log.VERBOSE)) {
884                     Log.v(FullBackup.TAG_XML_PARSER,
885                             "onRestoreFile: Trying to restore \""
886                                     + destinationCanonicalPath + "\" but it isn't specified"
887                                     + " in the included files; skipping.");
888                 }
889                 return false;
890             }
891         }
892         return true;
893     }
894 
895     /**
896      * Only specialized platform agents should overload this entry point to support
897      * restores to crazy non-app locations.
898      * @hide
899      */
onRestoreFile(ParcelFileDescriptor data, long size, int type, String domain, String path, long mode, long mtime)900     protected void onRestoreFile(ParcelFileDescriptor data, long size,
901             int type, String domain, String path, long mode, long mtime)
902             throws IOException {
903         String basePath = null;
904 
905         if (DEBUG) Log.d(TAG, "onRestoreFile() size=" + size + " type=" + type
906                 + " domain=" + domain + " relpath=" + path + " mode=" + mode
907                 + " mtime=" + mtime);
908 
909         basePath = FullBackup.getBackupScheme(this).tokenToDirectoryPath(domain);
910         if (domain.equals(FullBackup.MANAGED_EXTERNAL_TREE_TOKEN)) {
911             mode = -1;  // < 0 is a token to skip attempting a chmod()
912         }
913 
914         // Now that we've figured out where the data goes, send it on its way
915         if (basePath != null) {
916             // Canonicalize the nominal path and verify that it lies within the stated domain
917             File outFile = new File(basePath, path);
918             String outPath = outFile.getCanonicalPath();
919             if (outPath.startsWith(basePath + File.separatorChar)) {
920                 if (DEBUG) Log.i(TAG, "[" + domain + " : " + path + "] mapped to " + outPath);
921                 onRestoreFile(data, size, outFile, type, mode, mtime);
922                 return;
923             } else {
924                 // Attempt to restore to a path outside the file's nominal domain.
925                 if (DEBUG) {
926                     Log.e(TAG, "Cross-domain restore attempt: " + outPath);
927                 }
928             }
929         }
930 
931         // Not a supported output location, or bad path:  we need to consume the data
932         // anyway, so just use the default "copy the data out" implementation
933         // with a null destination.
934         if (DEBUG) Log.i(TAG, "[ skipping file " + path + "]");
935         FullBackup.restoreFile(data, size, type, mode, mtime, null);
936     }
937 
938     /**
939      * The application's restore operation has completed.  This method is called after
940      * all available data has been delivered to the application for restore (via either
941      * the {@link #onRestore(BackupDataInput, int, ParcelFileDescriptor) onRestore()} or
942      * {@link #onRestoreFile(ParcelFileDescriptor, long, File, int, long, long) onRestoreFile()}
943      * callbacks).  This provides the app with a stable end-of-restore opportunity to
944      * perform any appropriate post-processing on the data that was just delivered.
945      *
946      * @see #onRestore(BackupDataInput, int, ParcelFileDescriptor)
947      * @see #onRestoreFile(ParcelFileDescriptor, long, File, int, long, long)
948      */
onRestoreFinished()949     public void onRestoreFinished() {
950     }
951 
952     // ----- Core implementation -----
953 
954     /** @hide */
onBind()955     public final IBinder onBind() {
956         return mBinder;
957     }
958 
959     private final IBinder mBinder = new BackupServiceBinder().asBinder();
960 
961     /** @hide */
attach(Context context)962     public void attach(Context context) {
963         attachBaseContext(context);
964     }
965 
966     // ----- IBackupService binder interface -----
967     private class BackupServiceBinder extends IBackupAgent.Stub {
968         private static final String TAG = "BackupServiceBinder";
969 
970         @Override
doBackup( ParcelFileDescriptor oldState, ParcelFileDescriptor data, ParcelFileDescriptor newState, long quotaBytes, IBackupCallback callbackBinder, int transportFlags)971         public void doBackup(
972                 ParcelFileDescriptor oldState,
973                 ParcelFileDescriptor data,
974                 ParcelFileDescriptor newState,
975                 long quotaBytes,
976                 IBackupCallback callbackBinder,
977                 int transportFlags) throws RemoteException {
978             // Ensure that we're running with the app's normal permission level
979             long ident = Binder.clearCallingIdentity();
980 
981             if (DEBUG) Log.v(TAG, "doBackup() invoked");
982             BackupDataOutput output = new BackupDataOutput(
983                     data.getFileDescriptor(), quotaBytes, transportFlags);
984 
985             long result = RESULT_ERROR;
986             try {
987                 BackupAgent.this.onBackup(oldState, output, newState);
988                 result = RESULT_SUCCESS;
989             } catch (IOException ex) {
990                 Log.d(TAG, "onBackup (" + BackupAgent.this.getClass().getName() + ") threw", ex);
991                 throw new RuntimeException(ex);
992             } catch (RuntimeException ex) {
993                 Log.d(TAG, "onBackup (" + BackupAgent.this.getClass().getName() + ") threw", ex);
994                 throw ex;
995             } finally {
996                 // Ensure that any SharedPreferences writes have landed after the backup,
997                 // in case the app code has side effects (since apps cannot provide this
998                 // guarantee themselves).
999                 waitForSharedPrefs();
1000 
1001                 Binder.restoreCallingIdentity(ident);
1002                 try {
1003                     callbackBinder.operationComplete(result);
1004                 } catch (RemoteException e) {
1005                     // We will time out anyway.
1006                 }
1007 
1008                 // Don't close the fd out from under the system service if this was local
1009                 if (Binder.getCallingPid() != Process.myPid()) {
1010                     IoUtils.closeQuietly(oldState);
1011                     IoUtils.closeQuietly(data);
1012                     IoUtils.closeQuietly(newState);
1013                 }
1014             }
1015         }
1016 
1017         @Override
doRestore(ParcelFileDescriptor data, long appVersionCode, ParcelFileDescriptor newState, int token, IBackupManager callbackBinder)1018         public void doRestore(ParcelFileDescriptor data, long appVersionCode,
1019                 ParcelFileDescriptor newState,
1020                 int token, IBackupManager callbackBinder) throws RemoteException {
1021             // Ensure that we're running with the app's normal permission level
1022             long ident = Binder.clearCallingIdentity();
1023 
1024             if (DEBUG) Log.v(TAG, "doRestore() invoked");
1025 
1026             // Ensure that any side-effect SharedPreferences writes have landed *before*
1027             // we may be about to rewrite the file out from underneath
1028             waitForSharedPrefs();
1029 
1030             BackupDataInput input = new BackupDataInput(data.getFileDescriptor());
1031             try {
1032                 BackupAgent.this.onRestore(input, appVersionCode, newState);
1033             } catch (IOException ex) {
1034                 Log.d(TAG, "onRestore (" + BackupAgent.this.getClass().getName() + ") threw", ex);
1035                 throw new RuntimeException(ex);
1036             } catch (RuntimeException ex) {
1037                 Log.d(TAG, "onRestore (" + BackupAgent.this.getClass().getName() + ") threw", ex);
1038                 throw ex;
1039             } finally {
1040                 // And bring live SharedPreferences instances up to date
1041                 reloadSharedPreferences();
1042 
1043                 Binder.restoreCallingIdentity(ident);
1044                 try {
1045                     callbackBinder.opCompleteForUser(getBackupUserId(), token, 0);
1046                 } catch (RemoteException e) {
1047                     // we'll time out anyway, so we're safe
1048                 }
1049 
1050                 if (Binder.getCallingPid() != Process.myPid()) {
1051                     IoUtils.closeQuietly(data);
1052                     IoUtils.closeQuietly(newState);
1053                 }
1054             }
1055         }
1056 
1057         @Override
doFullBackup(ParcelFileDescriptor data, long quotaBytes, int token, IBackupManager callbackBinder, int transportFlags)1058         public void doFullBackup(ParcelFileDescriptor data,
1059                 long quotaBytes, int token, IBackupManager callbackBinder, int transportFlags) {
1060             // Ensure that we're running with the app's normal permission level
1061             long ident = Binder.clearCallingIdentity();
1062 
1063             if (DEBUG) Log.v(TAG, "doFullBackup() invoked");
1064 
1065             // Ensure that any SharedPreferences writes have landed *before*
1066             // we potentially try to back up the underlying files directly.
1067             waitForSharedPrefs();
1068 
1069             try {
1070                 BackupAgent.this.onFullBackup(new FullBackupDataOutput(
1071                         data, quotaBytes, transportFlags));
1072             } catch (IOException ex) {
1073                 Log.d(TAG, "onFullBackup (" + BackupAgent.this.getClass().getName() + ") threw", ex);
1074                 throw new RuntimeException(ex);
1075             } catch (RuntimeException ex) {
1076                 Log.d(TAG, "onFullBackup (" + BackupAgent.this.getClass().getName() + ") threw", ex);
1077                 throw ex;
1078             } finally {
1079                 // ... and then again after, as in the doBackup() case
1080                 waitForSharedPrefs();
1081 
1082                 // Send the EOD marker indicating that there is no more data
1083                 // forthcoming from this agent.
1084                 try {
1085                     FileOutputStream out = new FileOutputStream(data.getFileDescriptor());
1086                     byte[] buf = new byte[4];
1087                     out.write(buf);
1088                 } catch (IOException e) {
1089                     Log.e(TAG, "Unable to finalize backup stream!");
1090                 }
1091 
1092                 Binder.restoreCallingIdentity(ident);
1093                 try {
1094                     callbackBinder.opCompleteForUser(getBackupUserId(), token, 0);
1095                 } catch (RemoteException e) {
1096                     // we'll time out anyway, so we're safe
1097                 }
1098 
1099                 if (Binder.getCallingPid() != Process.myPid()) {
1100                     IoUtils.closeQuietly(data);
1101                 }
1102             }
1103         }
1104 
doMeasureFullBackup(long quotaBytes, int token, IBackupManager callbackBinder, int transportFlags)1105         public void doMeasureFullBackup(long quotaBytes, int token, IBackupManager callbackBinder,
1106                 int transportFlags) {
1107             // Ensure that we're running with the app's normal permission level
1108             final long ident = Binder.clearCallingIdentity();
1109             FullBackupDataOutput measureOutput =
1110                     new FullBackupDataOutput(quotaBytes, transportFlags);
1111 
1112             waitForSharedPrefs();
1113             try {
1114                 BackupAgent.this.onFullBackup(measureOutput);
1115             } catch (IOException ex) {
1116                 Log.d(TAG, "onFullBackup[M] (" + BackupAgent.this.getClass().getName() + ") threw", ex);
1117                 throw new RuntimeException(ex);
1118             } catch (RuntimeException ex) {
1119                 Log.d(TAG, "onFullBackup[M] (" + BackupAgent.this.getClass().getName() + ") threw", ex);
1120                 throw ex;
1121             } finally {
1122                 Binder.restoreCallingIdentity(ident);
1123                 try {
1124                     callbackBinder.opCompleteForUser(getBackupUserId(), token,
1125                             measureOutput.getSize());
1126                 } catch (RemoteException e) {
1127                     // timeout, so we're safe
1128                 }
1129             }
1130         }
1131 
1132         @Override
doRestoreFile(ParcelFileDescriptor data, long size, int type, String domain, String path, long mode, long mtime, int token, IBackupManager callbackBinder)1133         public void doRestoreFile(ParcelFileDescriptor data, long size,
1134                 int type, String domain, String path, long mode, long mtime,
1135                 int token, IBackupManager callbackBinder) throws RemoteException {
1136             long ident = Binder.clearCallingIdentity();
1137             try {
1138                 BackupAgent.this.onRestoreFile(data, size, type, domain, path, mode, mtime);
1139             } catch (IOException e) {
1140                 Log.d(TAG, "onRestoreFile (" + BackupAgent.this.getClass().getName() + ") threw", e);
1141                 throw new RuntimeException(e);
1142             } finally {
1143                 // Ensure that any side-effect SharedPreferences writes have landed
1144                 waitForSharedPrefs();
1145                 // And bring live SharedPreferences instances up to date
1146                 reloadSharedPreferences();
1147 
1148                 Binder.restoreCallingIdentity(ident);
1149                 try {
1150                     callbackBinder.opCompleteForUser(getBackupUserId(), token, 0);
1151                 } catch (RemoteException e) {
1152                     // we'll time out anyway, so we're safe
1153                 }
1154 
1155                 if (Binder.getCallingPid() != Process.myPid()) {
1156                     IoUtils.closeQuietly(data);
1157                 }
1158             }
1159         }
1160 
1161         @Override
doRestoreFinished(int token, IBackupManager callbackBinder)1162         public void doRestoreFinished(int token, IBackupManager callbackBinder) {
1163             long ident = Binder.clearCallingIdentity();
1164             try {
1165                 BackupAgent.this.onRestoreFinished();
1166             } catch (Exception e) {
1167                 Log.d(TAG, "onRestoreFinished (" + BackupAgent.this.getClass().getName() + ") threw", e);
1168                 throw e;
1169             } finally {
1170                 // Ensure that any side-effect SharedPreferences writes have landed
1171                 waitForSharedPrefs();
1172 
1173                 Binder.restoreCallingIdentity(ident);
1174                 try {
1175                     callbackBinder.opCompleteForUser(getBackupUserId(), token, 0);
1176                 } catch (RemoteException e) {
1177                     // we'll time out anyway, so we're safe
1178                 }
1179             }
1180         }
1181 
1182         @Override
fail(String message)1183         public void fail(String message) {
1184             getHandler().post(new FailRunnable(message));
1185         }
1186 
1187         @Override
doQuotaExceeded( long backupDataBytes, long quotaBytes, IBackupCallback callbackBinder)1188         public void doQuotaExceeded(
1189                 long backupDataBytes,
1190                 long quotaBytes,
1191                 IBackupCallback callbackBinder) {
1192             long ident = Binder.clearCallingIdentity();
1193 
1194             long result = RESULT_ERROR;
1195             try {
1196                 BackupAgent.this.onQuotaExceeded(backupDataBytes, quotaBytes);
1197                 result = RESULT_SUCCESS;
1198             } catch (Exception e) {
1199                 Log.d(TAG, "onQuotaExceeded(" + BackupAgent.this.getClass().getName() + ") threw",
1200                         e);
1201                 throw e;
1202             } finally {
1203                 waitForSharedPrefs();
1204                 Binder.restoreCallingIdentity(ident);
1205 
1206                 try {
1207                     callbackBinder.operationComplete(result);
1208                 } catch (RemoteException e) {
1209                     // We will time out anyway.
1210                 }
1211             }
1212         }
1213     }
1214 
1215     static class FailRunnable implements Runnable {
1216         private String mMessage;
1217 
FailRunnable(String message)1218         FailRunnable(String message) {
1219             mMessage = message;
1220         }
1221 
1222         @Override
run()1223         public void run() {
1224             throw new IllegalStateException(mMessage);
1225         }
1226     }
1227 }
1228