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