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