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