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