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