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