blob: 87d785a2223016c8247196cabbd8ce1b3b829b44 [file] [log] [blame]
Christopher Tate487529a2009-04-29 14:03:25 -07001/*
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
Christopher Tate45281862010-03-05 15:46:30 -080017package android.app.backup;
Christopher Tate487529a2009-04-29 14:03:25 -070018
Christopher Tate181fafa2009-05-14 11:12:14 -070019import android.app.IBackupAgent;
Christopher Tatef85f5b22013-04-18 16:57:43 -070020import android.app.QueuedWork;
Christopher Tate45281862010-03-05 15:46:30 -080021import android.app.backup.IBackupManager;
Christopher Tate181fafa2009-05-14 11:12:14 -070022import android.content.Context;
23import android.content.ContextWrapper;
Christopher Tate79ec80d2011-06-24 14:58:49 -070024import android.content.pm.ApplicationInfo;
Christopher Tate19024922010-01-22 16:39:53 -080025import android.os.Binder;
Christopher Tatef85f5b22013-04-18 16:57:43 -070026import android.os.Handler;
Christopher Tate487529a2009-04-29 14:03:25 -070027import android.os.IBinder;
Christopher Tatef85f5b22013-04-18 16:57:43 -070028import android.os.Looper;
Christopher Tate22b87872009-05-04 16:41:53 -070029import android.os.ParcelFileDescriptor;
Christopher Tate5cb5c332013-02-21 14:32:12 -080030import android.os.Process;
Christopher Tate487529a2009-04-29 14:03:25 -070031import android.os.RemoteException;
Elliott Hughes34385d32014-04-28 11:11:32 -070032import android.system.ErrnoException;
33import android.system.Os;
34import android.system.OsConstants;
35import android.system.StructStat;
Christopher Tate487529a2009-04-29 14:03:25 -070036import android.util.Log;
37
Christopher Tate79ec80d2011-06-24 14:58:49 -070038import java.io.File;
Christopher Tate7926a692011-07-11 11:31:57 -070039import java.io.FileOutputStream;
Joe Onorato83248c42009-06-17 17:55:20 -070040import java.io.IOException;
Christopher Tate79ec80d2011-06-24 14:58:49 -070041import java.util.HashSet;
42import java.util.LinkedList;
Christopher Tatef85f5b22013-04-18 16:57:43 -070043import java.util.concurrent.CountDownLatch;
Christopher Tate79ec80d2011-06-24 14:58:49 -070044
Christopher Tate487529a2009-04-29 14:03:25 -070045/**
Scott Maind17da432010-04-29 21:42:58 -070046 * Provides the central interface between an
Christopher Tate4e14a822010-04-08 12:54:23 -070047 * application and Android's data backup infrastructure. An application that wishes
48 * to participate in the backup and restore mechanism will declare a subclass of
49 * {@link android.app.backup.BackupAgent}, implement the
Scott Maind17da432010-04-29 21:42:58 -070050 * {@link #onBackup(ParcelFileDescriptor, BackupDataOutput, ParcelFileDescriptor) onBackup()}
51 * and {@link #onRestore(BackupDataInput, int, ParcelFileDescriptor) onRestore()} methods,
52 * and provide the name of its backup agent class in its {@code AndroidManifest.xml} file via
Joe Fernandez61fd1e82011-10-26 13:39:11 -070053 * the <code>
54 * <a href="{@docRoot}guide/topics/manifest/application-element.html">&lt;application&gt;</a></code>
Scott Maind17da432010-04-29 21:42:58 -070055 * tag's {@code android:backupAgent} attribute.
Joe Fernandez61fd1e82011-10-26 13:39:11 -070056 *
57 * <div class="special reference">
58 * <h3>Developer Guides</h3>
59 * <p>For more information about using BackupAgent, read the
60 * <a href="{@docRoot}guide/topics/data/backup.html">Data Backup</a> developer guide.</p></div>
61 *
Scott Maind17da432010-04-29 21:42:58 -070062 * <h3>Basic Operation</h3>
Kenny Root5a20ea12010-02-23 18:49:11 -080063 * <p>
Christopher Tate4e14a822010-04-08 12:54:23 -070064 * When the application makes changes to data that it wishes to keep backed up,
65 * it should call the
66 * {@link android.app.backup.BackupManager#dataChanged() BackupManager.dataChanged()} method.
Scott Maind17da432010-04-29 21:42:58 -070067 * This notifies the Android Backup Manager that the application needs an opportunity
68 * to update its backup image. The Backup Manager, in turn, schedules a
Christopher Tate4e14a822010-04-08 12:54:23 -070069 * backup pass to be performed at an opportune time.
70 * <p>
Scott Maind17da432010-04-29 21:42:58 -070071 * Restore operations are typically performed only when applications are first
Christopher Tate4e14a822010-04-08 12:54:23 -070072 * installed on a device. At that time, the operating system checks to see whether
Scott Maind17da432010-04-29 21:42:58 -070073 * there is a previously-saved data set available for the application being installed, and if so,
74 * begins an immediate restore pass to deliver the backup data as part of the installation
Christopher Tate4e14a822010-04-08 12:54:23 -070075 * process.
76 * <p>
Scott Maind17da432010-04-29 21:42:58 -070077 * When a backup or restore pass is run, the application's process is launched
78 * (if not already running), the manifest-declared backup agent class (in the {@code
79 * android:backupAgent} attribute) is instantiated within
80 * that process, and the agent's {@link #onCreate()} method is invoked. This prepares the
Christopher Tate4e14a822010-04-08 12:54:23 -070081 * agent instance to run the actual backup or restore logic. At this point the
82 * agent's
83 * {@link #onBackup(ParcelFileDescriptor, BackupDataOutput, ParcelFileDescriptor) onBackup()} or
84 * {@link #onRestore(BackupDataInput, int, ParcelFileDescriptor) onRestore()} method will be
85 * invoked as appropriate for the operation being performed.
86 * <p>
Scott Maind17da432010-04-29 21:42:58 -070087 * A backup data set consists of one or more "entities," flattened binary data
88 * records that are each identified with a key string unique within the data set. Adding a
89 * record to the active data set or updating an existing record is done by simply
Christopher Tate4e14a822010-04-08 12:54:23 -070090 * writing new entity data under the desired key. Deleting an entity from the data set
91 * is done by writing an entity under that key with header specifying a negative data
92 * size, and no actual entity data.
93 * <p>
94 * <b>Helper Classes</b>
95 * <p>
96 * An extensible agent based on convenient helper classes is available in
97 * {@link android.app.backup.BackupAgentHelper}. That class is particularly
98 * suited to handling of simple file or {@link android.content.SharedPreferences}
99 * backup and restore.
100 *
101 * @see android.app.backup.BackupManager
102 * @see android.app.backup.BackupAgentHelper
103 * @see android.app.backup.BackupDataInput
104 * @see android.app.backup.BackupDataOutput
Christopher Tate487529a2009-04-29 14:03:25 -0700105 */
Christopher Tate181fafa2009-05-14 11:12:14 -0700106public abstract class BackupAgent extends ContextWrapper {
Joe Onorato83248c42009-06-17 17:55:20 -0700107 private static final String TAG = "BackupAgent";
Christopher Tate4a627c72011-04-01 14:43:32 -0700108 private static final boolean DEBUG = true;
Joe Onorato83248c42009-06-17 17:55:20 -0700109
Christopher Tate79ec80d2011-06-24 14:58:49 -0700110 /** @hide */
111 public static final int TYPE_EOF = 0;
112
113 /**
114 * During a full restore, indicates that the file system object being restored
115 * is an ordinary file.
116 */
117 public static final int TYPE_FILE = 1;
118
119 /**
120 * During a full restore, indicates that the file system object being restored
121 * is a directory.
122 */
123 public static final int TYPE_DIRECTORY = 2;
124
125 /** @hide */
126 public static final int TYPE_SYMLINK = 3;
127
Christopher Tatef85f5b22013-04-18 16:57:43 -0700128 Handler mHandler = null;
129
Christopher Tatecba59412014-04-01 10:38:29 -0700130 Handler getHandler() {
131 if (mHandler == null) {
132 mHandler = new Handler(Looper.getMainLooper());
133 }
134 return mHandler;
135 }
136
Christopher Tatef85f5b22013-04-18 16:57:43 -0700137 class SharedPrefsSynchronizer implements Runnable {
138 public final CountDownLatch mLatch = new CountDownLatch(1);
139
140 @Override
141 public void run() {
142 QueuedWork.waitToFinish();
143 mLatch.countDown();
144 }
145 };
146
147 // Syncing shared preferences deferred writes needs to happen on the main looper thread
148 private void waitForSharedPrefs() {
Christopher Tatecba59412014-04-01 10:38:29 -0700149 Handler h = getHandler();
Christopher Tatef85f5b22013-04-18 16:57:43 -0700150 final SharedPrefsSynchronizer s = new SharedPrefsSynchronizer();
Christopher Tatecba59412014-04-01 10:38:29 -0700151 h.postAtFrontOfQueue(s);
Christopher Tatef85f5b22013-04-18 16:57:43 -0700152 try {
153 s.mLatch.await();
154 } catch (InterruptedException e) { /* ignored */ }
155 }
156
157
Christopher Tate181fafa2009-05-14 11:12:14 -0700158 public BackupAgent() {
159 super(null);
160 }
Christopher Tate487529a2009-04-29 14:03:25 -0700161
Christopher Tate4e14a822010-04-08 12:54:23 -0700162 /**
163 * Provided as a convenience for agent implementations that need an opportunity
164 * to do one-time initialization before the actual backup or restore operation
165 * is begun.
166 * <p>
167 * Agents do not need to override this method.
168 */
Christopher Tate181fafa2009-05-14 11:12:14 -0700169 public void onCreate() {
170 }
171
Christopher Tate4e14a822010-04-08 12:54:23 -0700172 /**
173 * Provided as a convenience for agent implementations that need to do some
174 * sort of shutdown process after backup or restore is completed.
175 * <p>
176 * Agents do not need to override this method.
177 */
Christopher Tate181fafa2009-05-14 11:12:14 -0700178 public void onDestroy() {
179 }
Christopher Tate487529a2009-04-29 14:03:25 -0700180
181 /**
Kenny Root5a20ea12010-02-23 18:49:11 -0800182 * The application is being asked to write any data changed since the last
183 * time it performed a backup operation. The state data recorded during the
184 * last backup pass is provided in the <code>oldState</code> file
185 * descriptor. If <code>oldState</code> is <code>null</code>, no old state
186 * is available and the application should perform a full backup. In both
187 * cases, a representation of the final backup state after this pass should
188 * be written to the file pointed to by the file descriptor wrapped in
189 * <code>newState</code>.
Christopher Tate4e14a822010-04-08 12:54:23 -0700190 * <p>
191 * Each entity written to the {@link android.app.backup.BackupDataOutput}
192 * <code>data</code> stream will be transmitted
193 * over the current backup transport and stored in the remote data set under
194 * the key supplied as part of the entity. Writing an entity with a negative
195 * data size instructs the transport to delete whatever entity currently exists
196 * under that key from the remote data set.
Elliott Hughes34385d32014-04-28 11:11:32 -0700197 *
Kenny Root5a20ea12010-02-23 18:49:11 -0800198 * @param oldState An open, read-only ParcelFileDescriptor pointing to the
199 * last backup state provided by the application. May be
200 * <code>null</code>, in which case no prior state is being
201 * provided and the application should perform a full backup.
202 * @param data A structured wrapper around an open, read/write
Christopher Tate4e14a822010-04-08 12:54:23 -0700203 * file descriptor pointing to the backup data destination.
Kenny Root5a20ea12010-02-23 18:49:11 -0800204 * Typically the application will use backup helper classes to
205 * write to this file.
206 * @param newState An open, read/write ParcelFileDescriptor pointing to an
207 * empty file. The application should record the final backup
Christopher Tate4e14a822010-04-08 12:54:23 -0700208 * state here after writing the requested data to the <code>data</code>
209 * output stream.
Christopher Tate487529a2009-04-29 14:03:25 -0700210 */
Joe Onorato290bb012009-05-13 18:57:29 -0400211 public abstract void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
Christopher Tate2e40d112014-07-15 12:37:38 -0700212 ParcelFileDescriptor newState) throws IOException;
Kenny Root5a20ea12010-02-23 18:49:11 -0800213
Christopher Tate487529a2009-04-29 14:03:25 -0700214 /**
Kenny Root5a20ea12010-02-23 18:49:11 -0800215 * The application is being restored from backup and should replace any
216 * existing data with the contents of the backup. The backup data is
Christopher Tate4e14a822010-04-08 12:54:23 -0700217 * provided through the <code>data</code> parameter. Once
Kenny Root5a20ea12010-02-23 18:49:11 -0800218 * the restore is finished, the application should write a representation of
219 * the final state to the <code>newState</code> file descriptor.
220 * <p>
221 * The application is responsible for properly erasing its old data and
222 * replacing it with the data supplied to this method. No "clear user data"
223 * operation will be performed automatically by the operating system. The
224 * exception to this is in the case of a failed restore attempt: if
225 * onRestore() throws an exception, the OS will assume that the
226 * application's data may now be in an incoherent state, and will clear it
227 * before proceeding.
Elliott Hughes34385d32014-04-28 11:11:32 -0700228 *
Kenny Root5a20ea12010-02-23 18:49:11 -0800229 * @param data A structured wrapper around an open, read-only
Christopher Tate4e14a822010-04-08 12:54:23 -0700230 * file descriptor pointing to a full snapshot of the
231 * application's data. The application should consume every
232 * entity represented in this data stream.
Scott Mainb83a2832010-04-29 13:26:53 -0700233 * @param appVersionCode The value of the <a
234 * href="{@docRoot}guide/topics/manifest/manifest-element.html#vcode">{@code
235 * android:versionCode}</a> manifest attribute,
236 * from the application that backed up this particular data set. This
Christopher Tate4e14a822010-04-08 12:54:23 -0700237 * makes it possible for an application's agent to distinguish among any
Kenny Root5a20ea12010-02-23 18:49:11 -0800238 * possible older data versions when asked to perform the restore
239 * operation.
240 * @param newState An open, read/write ParcelFileDescriptor pointing to an
241 * empty file. The application should record the final backup
Christopher Tate4e14a822010-04-08 12:54:23 -0700242 * state here after restoring its data from the <code>data</code> stream.
Christopher Tate4a627c72011-04-01 14:43:32 -0700243 * When a full-backup dataset is being restored, this will be <code>null</code>.
Christopher Tate487529a2009-04-29 14:03:25 -0700244 */
Christopher Tate5cbbf562009-06-22 16:44:51 -0700245 public abstract void onRestore(BackupDataInput data, int appVersionCode,
Christopher Tate2e40d112014-07-15 12:37:38 -0700246 ParcelFileDescriptor newState) throws IOException;
Christopher Tate487529a2009-04-29 14:03:25 -0700247
Christopher Tate4a627c72011-04-01 14:43:32 -0700248 /**
Christopher Tatea7835b62014-07-11 17:25:57 -0700249 * The application is having its entire file system contents backed up. {@code data}
250 * points to the backup destination, and the app has the opportunity to choose which
251 * files are to be stored. To commit a file as part of the backup, call the
252 * {@link #fullBackupFile(File, FullBackupDataOutput)} helper method. After all file
253 * data is written to the output, the agent returns from this method and the backup
254 * operation concludes.
255 *
256 * <p>Certain parts of the app's data are never backed up even if the app explicitly
257 * sends them to the output:
258 *
259 * <ul>
260 * <li>The contents of the {@link #getCacheDir()} directory</li>
261 * <li>The contents of the {@link #getNoBackupFilesDir()} directory</li>
262 * <li>The contents of the app's shared library directory</li>
263 * </ul>
264 *
265 * <p>The default implementation of this method backs up the entirety of the
266 * application's "owned" file system trees to the output other than the few exceptions
267 * listed above. Apps only need to override this method if they need to impose special
268 * limitations on which files are being stored beyond the control that
269 * {@link #getNoBackupFilesDir()} offers.
270 *
271 * @param data A structured wrapper pointing to the backup destination.
272 * @throws IOException
273 *
274 * @see Context#getNoBackupFilesDir()
275 * @see #fullBackupFile(File, FullBackupDataOutput)
276 * @see #onRestoreFile(ParcelFileDescriptor, long, File, int, long, long)
Christopher Tate75a99702011-05-18 16:28:19 -0700277 */
Christopher Tate79ec80d2011-06-24 14:58:49 -0700278 public void onFullBackup(FullBackupDataOutput data) throws IOException {
279 ApplicationInfo appInfo = getApplicationInfo();
280
Christopher Tatea7835b62014-07-11 17:25:57 -0700281 // Note that we don't need to think about the no_backup dir because it's outside
282 // all of the ones we will be traversing
Christopher Tate2efd2db2011-07-19 16:32:49 -0700283 String rootDir = new File(appInfo.dataDir).getCanonicalPath();
284 String filesDir = getFilesDir().getCanonicalPath();
285 String databaseDir = getDatabasePath("foo").getParentFile().getCanonicalPath();
286 String sharedPrefsDir = getSharedPrefsFile("foo").getParentFile().getCanonicalPath();
287 String cacheDir = getCacheDir().getCanonicalPath();
Christopher Tate79ec80d2011-06-24 14:58:49 -0700288 String libDir = (appInfo.nativeLibraryDir != null)
Christopher Tate2efd2db2011-07-19 16:32:49 -0700289 ? new File(appInfo.nativeLibraryDir).getCanonicalPath()
Christopher Tate79ec80d2011-06-24 14:58:49 -0700290 : null;
291
292 // Filters, the scan queue, and the set of resulting entities
293 HashSet<String> filterSet = new HashSet<String>();
294 String packageName = getPackageName();
295
296 // Okay, start with the app's root tree, but exclude all of the canonical subdirs
297 if (libDir != null) {
298 filterSet.add(libDir);
299 }
300 filterSet.add(cacheDir);
301 filterSet.add(databaseDir);
302 filterSet.add(sharedPrefsDir);
303 filterSet.add(filesDir);
304 fullBackupFileTree(packageName, FullBackup.ROOT_TREE_TOKEN, rootDir, filterSet, data);
305
306 // Now do the same for the files dir, db dir, and shared prefs dir
307 filterSet.add(rootDir);
308 filterSet.remove(filesDir);
309 fullBackupFileTree(packageName, FullBackup.DATA_TREE_TOKEN, filesDir, filterSet, data);
310
311 filterSet.add(filesDir);
312 filterSet.remove(databaseDir);
313 fullBackupFileTree(packageName, FullBackup.DATABASE_TREE_TOKEN, databaseDir, filterSet, data);
314
315 filterSet.add(databaseDir);
316 filterSet.remove(sharedPrefsDir);
317 fullBackupFileTree(packageName, FullBackup.SHAREDPREFS_TREE_TOKEN, sharedPrefsDir, filterSet, data);
Christopher Tate416c39e2013-02-14 16:55:46 -0800318
319 // getExternalFilesDir() location associated with this app. Technically there should
320 // not be any files here if the app does not properly have permission to access
321 // external storage, but edge cases happen. fullBackupFileTree() catches
Christopher Tate5cb5c332013-02-21 14:32:12 -0800322 // IOExceptions and similar, and treats them as non-fatal, so we rely on that; and
323 // we know a priori that processes running as the system UID are not permitted to
324 // access external storage, so we check for that as well to avoid nastygrams in
325 // the log.
326 if (Process.myUid() != Process.SYSTEM_UID) {
327 File efLocation = getExternalFilesDir(null);
328 if (efLocation != null) {
329 fullBackupFileTree(packageName, FullBackup.MANAGED_EXTERNAL_TREE_TOKEN,
330 efLocation.getCanonicalPath(), null, data);
331 }
332 }
Christopher Tate75a99702011-05-18 16:28:19 -0700333 }
334
335 /**
Christopher Tate79ec80d2011-06-24 14:58:49 -0700336 * Write an entire file as part of a full-backup operation. The file's contents
337 * will be delivered to the backup destination along with the metadata necessary
338 * to place it with the proper location and permissions on the device where the
339 * data is restored.
Christopher Tate79ec80d2011-06-24 14:58:49 -0700340 *
Christopher Tatea7835b62014-07-11 17:25:57 -0700341 * <p class="note">It is safe to explicitly back up files underneath your application's
342 * {@link #getNoBackupFilesDir()} directory, and they will be restored to that
343 * location correctly.
344 *
Christopher Tate79ec80d2011-06-24 14:58:49 -0700345 * @param file The file to be backed up. The file must exist and be readable by
346 * the caller.
347 * @param output The destination to which the backed-up file data will be sent.
Christopher Tate4a627c72011-04-01 14:43:32 -0700348 */
Christopher Tate79ec80d2011-06-24 14:58:49 -0700349 public final void fullBackupFile(File file, FullBackupDataOutput output) {
350 // Look up where all of our various well-defined dir trees live on this device
351 String mainDir;
352 String filesDir;
Christopher Tatea7835b62014-07-11 17:25:57 -0700353 String nbFilesDir;
Christopher Tate79ec80d2011-06-24 14:58:49 -0700354 String dbDir;
355 String spDir;
356 String cacheDir;
357 String libDir;
Christopher Tate5cb5c332013-02-21 14:32:12 -0800358 String efDir = null;
Christopher Tate2efd2db2011-07-19 16:32:49 -0700359 String filePath;
Christopher Tate79ec80d2011-06-24 14:58:49 -0700360
361 ApplicationInfo appInfo = getApplicationInfo();
362
Christopher Tate2efd2db2011-07-19 16:32:49 -0700363 try {
364 mainDir = new File(appInfo.dataDir).getCanonicalPath();
365 filesDir = getFilesDir().getCanonicalPath();
Christopher Tatea7835b62014-07-11 17:25:57 -0700366 nbFilesDir = getNoBackupFilesDir().getCanonicalPath();
Christopher Tate2efd2db2011-07-19 16:32:49 -0700367 dbDir = getDatabasePath("foo").getParentFile().getCanonicalPath();
368 spDir = getSharedPrefsFile("foo").getParentFile().getCanonicalPath();
369 cacheDir = getCacheDir().getCanonicalPath();
370 libDir = (appInfo.nativeLibraryDir == null)
371 ? null
372 : new File(appInfo.nativeLibraryDir).getCanonicalPath();
Christopher Tate5cb5c332013-02-21 14:32:12 -0800373
374 // may or may not have external files access to attempt backup/restore there
375 if (Process.myUid() != Process.SYSTEM_UID) {
376 File efLocation = getExternalFilesDir(null);
377 if (efLocation != null) {
378 efDir = efLocation.getCanonicalPath();
379 }
380 }
Christopher Tate79ec80d2011-06-24 14:58:49 -0700381
Christopher Tate2efd2db2011-07-19 16:32:49 -0700382 // Now figure out which well-defined tree the file is placed in, working from
383 // most to least specific. We also specifically exclude the lib and cache dirs.
384 filePath = file.getCanonicalPath();
385 } catch (IOException e) {
386 Log.w(TAG, "Unable to obtain canonical paths");
387 return;
388 }
Christopher Tate79ec80d2011-06-24 14:58:49 -0700389
Christopher Tatea7835b62014-07-11 17:25:57 -0700390 if (filePath.startsWith(cacheDir)
391 || filePath.startsWith(libDir)
392 || filePath.startsWith(nbFilesDir)) {
393 Log.w(TAG, "lib, cache, and no_backup files are not backed up");
Christopher Tate79ec80d2011-06-24 14:58:49 -0700394 return;
395 }
396
397 final String domain;
398 String rootpath = null;
399 if (filePath.startsWith(dbDir)) {
400 domain = FullBackup.DATABASE_TREE_TOKEN;
401 rootpath = dbDir;
402 } else if (filePath.startsWith(spDir)) {
403 domain = FullBackup.SHAREDPREFS_TREE_TOKEN;
404 rootpath = spDir;
405 } else if (filePath.startsWith(filesDir)) {
406 domain = FullBackup.DATA_TREE_TOKEN;
407 rootpath = filesDir;
408 } else if (filePath.startsWith(mainDir)) {
409 domain = FullBackup.ROOT_TREE_TOKEN;
410 rootpath = mainDir;
Christopher Tate5cb5c332013-02-21 14:32:12 -0800411 } else if ((efDir != null) && filePath.startsWith(efDir)) {
Christopher Tate416c39e2013-02-14 16:55:46 -0800412 domain = FullBackup.MANAGED_EXTERNAL_TREE_TOKEN;
413 rootpath = efDir;
Christopher Tate79ec80d2011-06-24 14:58:49 -0700414 } else {
415 Log.w(TAG, "File " + filePath + " is in an unsupported location; skipping");
416 return;
417 }
418
419 // And now that we know where it lives, semantically, back it up appropriately
420 Log.i(TAG, "backupFile() of " + filePath + " => domain=" + domain
421 + " rootpath=" + rootpath);
422 FullBackup.backupToTar(getPackageName(), domain, null, rootpath, filePath,
423 output.getData());
424 }
425
426 /**
427 * Scan the dir tree (if it actually exists) and process each entry we find. If the
428 * 'excludes' parameter is non-null, it is consulted each time a new file system entity
429 * is visited to see whether that entity (and its subtree, if appropriate) should be
430 * omitted from the backup process.
431 *
432 * @hide
433 */
434 protected final void fullBackupFileTree(String packageName, String domain, String rootPath,
435 HashSet<String> excludes, FullBackupDataOutput output) {
436 File rootFile = new File(rootPath);
437 if (rootFile.exists()) {
438 LinkedList<File> scanQueue = new LinkedList<File>();
439 scanQueue.add(rootFile);
440
441 while (scanQueue.size() > 0) {
442 File file = scanQueue.remove(0);
Christopher Tate2efd2db2011-07-19 16:32:49 -0700443 String filePath;
Christopher Tate79ec80d2011-06-24 14:58:49 -0700444 try {
Christopher Tate2efd2db2011-07-19 16:32:49 -0700445 filePath = file.getCanonicalPath();
446
447 // prune this subtree?
448 if (excludes != null && excludes.contains(filePath)) {
449 continue;
450 }
451
452 // If it's a directory, enqueue its contents for scanning.
Elliott Hughes34385d32014-04-28 11:11:32 -0700453 StructStat stat = Os.lstat(filePath);
Christopher Tate79ec80d2011-06-24 14:58:49 -0700454 if (OsConstants.S_ISLNK(stat.st_mode)) {
455 if (DEBUG) Log.i(TAG, "Symlink (skipping)!: " + file);
456 continue;
457 } else if (OsConstants.S_ISDIR(stat.st_mode)) {
458 File[] contents = file.listFiles();
459 if (contents != null) {
460 for (File entry : contents) {
461 scanQueue.add(0, entry);
462 }
463 }
464 }
Christopher Tate2efd2db2011-07-19 16:32:49 -0700465 } catch (IOException e) {
466 if (DEBUG) Log.w(TAG, "Error canonicalizing path of " + file);
467 continue;
Christopher Tate79ec80d2011-06-24 14:58:49 -0700468 } catch (ErrnoException e) {
469 if (DEBUG) Log.w(TAG, "Error scanning file " + file + " : " + e);
470 continue;
471 }
472
473 // Finally, back this file up before proceeding
474 FullBackup.backupToTar(packageName, domain, null, rootPath, filePath,
475 output.getData());
476 }
477 }
478 }
479
480 /**
481 * Handle the data delivered via the given file descriptor during a full restore
482 * operation. The agent is given the path to the file's original location as well
483 * as its size and metadata.
484 * <p>
485 * The file descriptor can only be read for {@code size} bytes; attempting to read
486 * more data has undefined behavior.
487 * <p>
488 * The default implementation creates the destination file/directory and populates it
489 * with the data from the file descriptor, then sets the file's access mode and
490 * modification time to match the restore arguments.
491 *
492 * @param data A read-only file descriptor from which the agent can read {@code size}
493 * bytes of file data.
494 * @param size The number of bytes of file content to be restored to the given
495 * destination. If the file system object being restored is a directory, {@code size}
496 * will be zero.
497 * @param destination The File on disk to be restored with the given data.
498 * @param type The kind of file system object being restored. This will be either
499 * {@link BackupAgent#TYPE_FILE} or {@link BackupAgent#TYPE_DIRECTORY}.
500 * @param mode The access mode to be assigned to the destination after its data is
501 * written. This is in the standard format used by {@code chmod()}.
502 * @param mtime The modification time of the file when it was backed up, suitable to
503 * be assigned to the file after its data is written.
504 * @throws IOException
505 */
506 public void onRestoreFile(ParcelFileDescriptor data, long size,
507 File destination, int type, long mode, long mtime)
508 throws IOException {
509 FullBackup.restoreFile(data, size, type, mode, mtime, destination);
510 }
511
512 /**
513 * Only specialized platform agents should overload this entry point to support
514 * restores to crazy non-app locations.
515 * @hide
516 */
517 protected void onRestoreFile(ParcelFileDescriptor data, long size,
518 int type, String domain, String path, long mode, long mtime)
519 throws IOException {
520 String basePath = null;
521
522 if (DEBUG) Log.d(TAG, "onRestoreFile() size=" + size + " type=" + type
523 + " domain=" + domain + " relpath=" + path + " mode=" + mode
524 + " mtime=" + mtime);
525
526 // Parse out the semantic domains into the correct physical location
527 if (domain.equals(FullBackup.DATA_TREE_TOKEN)) {
Christopher Tate2efd2db2011-07-19 16:32:49 -0700528 basePath = getFilesDir().getCanonicalPath();
Christopher Tate79ec80d2011-06-24 14:58:49 -0700529 } else if (domain.equals(FullBackup.DATABASE_TREE_TOKEN)) {
Christopher Tate2efd2db2011-07-19 16:32:49 -0700530 basePath = getDatabasePath("foo").getParentFile().getCanonicalPath();
Christopher Tate79ec80d2011-06-24 14:58:49 -0700531 } else if (domain.equals(FullBackup.ROOT_TREE_TOKEN)) {
Christopher Tate2efd2db2011-07-19 16:32:49 -0700532 basePath = new File(getApplicationInfo().dataDir).getCanonicalPath();
Christopher Tate79ec80d2011-06-24 14:58:49 -0700533 } else if (domain.equals(FullBackup.SHAREDPREFS_TREE_TOKEN)) {
Christopher Tate2efd2db2011-07-19 16:32:49 -0700534 basePath = getSharedPrefsFile("foo").getParentFile().getCanonicalPath();
Christopher Tate79ec80d2011-06-24 14:58:49 -0700535 } else if (domain.equals(FullBackup.CACHE_TREE_TOKEN)) {
Christopher Tate2efd2db2011-07-19 16:32:49 -0700536 basePath = getCacheDir().getCanonicalPath();
Christopher Tate416c39e2013-02-14 16:55:46 -0800537 } else if (domain.equals(FullBackup.MANAGED_EXTERNAL_TREE_TOKEN)) {
Christopher Tate5cb5c332013-02-21 14:32:12 -0800538 // make sure we can try to restore here before proceeding
539 if (Process.myUid() != Process.SYSTEM_UID) {
540 File efLocation = getExternalFilesDir(null);
541 if (efLocation != null) {
542 basePath = getExternalFilesDir(null).getCanonicalPath();
Christopher Tate294b5122013-02-19 14:08:59 -0800543 mode = -1; // < 0 is a token to skip attempting a chmod()
Christopher Tate5cb5c332013-02-21 14:32:12 -0800544 }
545 }
Christopher Tatea7835b62014-07-11 17:25:57 -0700546 } else if (domain.equals(FullBackup.NO_BACKUP_TREE_TOKEN)) {
547 basePath = getNoBackupFilesDir().getCanonicalPath();
Christopher Tate79ec80d2011-06-24 14:58:49 -0700548 } else {
549 // Not a supported location
Christopher Tate73237652013-03-25 10:06:34 -0700550 Log.i(TAG, "Unrecognized domain " + domain);
Christopher Tate79ec80d2011-06-24 14:58:49 -0700551 }
552
553 // Now that we've figured out where the data goes, send it on its way
554 if (basePath != null) {
Christopher Tate73237652013-03-25 10:06:34 -0700555 // Canonicalize the nominal path and verify that it lies within the stated domain
Christopher Tate79ec80d2011-06-24 14:58:49 -0700556 File outFile = new File(basePath, path);
Christopher Tate73237652013-03-25 10:06:34 -0700557 String outPath = outFile.getCanonicalPath();
558 if (outPath.startsWith(basePath + File.separatorChar)) {
559 if (DEBUG) Log.i(TAG, "[" + domain + " : " + path + "] mapped to " + outPath);
560 onRestoreFile(data, size, outFile, type, mode, mtime);
561 return;
562 } else {
563 // Attempt to restore to a path outside the file's nominal domain.
564 if (DEBUG) {
565 Log.e(TAG, "Cross-domain restore attempt: " + outPath);
566 }
567 }
Christopher Tate79ec80d2011-06-24 14:58:49 -0700568 }
Christopher Tate73237652013-03-25 10:06:34 -0700569
570 // Not a supported output location, or bad path: we need to consume the data
571 // anyway, so just use the default "copy the data out" implementation
572 // with a null destination.
573 if (DEBUG) Log.i(TAG, "[ skipping file " + path + "]");
574 FullBackup.restoreFile(data, size, type, mode, mtime, null);
Christopher Tate4a627c72011-04-01 14:43:32 -0700575 }
Christopher Tate487529a2009-04-29 14:03:25 -0700576
Christopher Tate2e40d112014-07-15 12:37:38 -0700577 /**
578 * The application's restore operation has completed. This method is called after
579 * all available data has been delivered to the application for restore (via either
580 * the {@link #onRestore(BackupDataInput, int, ParcelFileDescriptor) onRestore()} or
581 * {@link #onRestoreFile(ParcelFileDescriptor, long, File, int, long, long) onRestoreFile()}
582 * callbacks). This provides the app with a stable end-of-restore opportunity to
583 * perform any appropriate post-processing on the data that was just delivered.
584 *
585 * @see #onRestore(BackupDataInput, int, ParcelFileDescriptor)
586 * @see #onRestoreFile(ParcelFileDescriptor, long, File, int, long, long)
587 */
588 public void onRestoreFinished() {
589 }
590
Christopher Tate487529a2009-04-29 14:03:25 -0700591 // ----- Core implementation -----
Christopher Tate44a27902010-01-27 17:15:49 -0800592
593 /** @hide */
594 public final IBinder onBind() {
Christopher Tate181fafa2009-05-14 11:12:14 -0700595 return mBinder;
Christopher Tate487529a2009-04-29 14:03:25 -0700596 }
597
598 private final IBinder mBinder = new BackupServiceBinder().asBinder();
599
Christopher Tate181fafa2009-05-14 11:12:14 -0700600 /** @hide */
601 public void attach(Context context) {
602 attachBaseContext(context);
603 }
604
Christopher Tate487529a2009-04-29 14:03:25 -0700605 // ----- IBackupService binder interface -----
Christopher Tate181fafa2009-05-14 11:12:14 -0700606 private class BackupServiceBinder extends IBackupAgent.Stub {
607 private static final String TAG = "BackupServiceBinder";
608
Christopher Tate75a99702011-05-18 16:28:19 -0700609 @Override
Christopher Tate22b87872009-05-04 16:41:53 -0700610 public void doBackup(ParcelFileDescriptor oldState,
611 ParcelFileDescriptor data,
Christopher Tate44a27902010-01-27 17:15:49 -0800612 ParcelFileDescriptor newState,
613 int token, IBackupManager callbackBinder) throws RemoteException {
Christopher Tate19024922010-01-22 16:39:53 -0800614 // Ensure that we're running with the app's normal permission level
Christopher Tate44a27902010-01-27 17:15:49 -0800615 long ident = Binder.clearCallingIdentity();
Christopher Tate19024922010-01-22 16:39:53 -0800616
Christopher Tate436344a2009-09-30 16:17:37 -0700617 if (DEBUG) Log.v(TAG, "doBackup() invoked");
Joe Onorato83248c42009-06-17 17:55:20 -0700618 BackupDataOutput output = new BackupDataOutput(data.getFileDescriptor());
Christopher Tate4a627c72011-04-01 14:43:32 -0700619
Joe Onorato290bb012009-05-13 18:57:29 -0400620 try {
Christopher Tate181fafa2009-05-14 11:12:14 -0700621 BackupAgent.this.onBackup(oldState, output, newState);
Joe Onorato4ababd92009-06-25 18:29:18 -0400622 } catch (IOException ex) {
623 Log.d(TAG, "onBackup (" + BackupAgent.this.getClass().getName() + ") threw", ex);
624 throw new RuntimeException(ex);
Joe Onorato290bb012009-05-13 18:57:29 -0400625 } catch (RuntimeException ex) {
Joe Onorato83248c42009-06-17 17:55:20 -0700626 Log.d(TAG, "onBackup (" + BackupAgent.this.getClass().getName() + ") threw", ex);
Joe Onorato290bb012009-05-13 18:57:29 -0400627 throw ex;
Christopher Tate19024922010-01-22 16:39:53 -0800628 } finally {
Christopher Tatef85f5b22013-04-18 16:57:43 -0700629 // Ensure that any SharedPreferences writes have landed after the backup,
630 // in case the app code has side effects (since apps cannot provide this
631 // guarantee themselves).
632 waitForSharedPrefs();
633
Christopher Tate44a27902010-01-27 17:15:49 -0800634 Binder.restoreCallingIdentity(ident);
635 try {
636 callbackBinder.opComplete(token);
637 } catch (RemoteException e) {
638 // we'll time out anyway, so we're safe
639 }
Joe Onorato290bb012009-05-13 18:57:29 -0400640 }
Christopher Tate487529a2009-04-29 14:03:25 -0700641 }
642
Christopher Tate75a99702011-05-18 16:28:19 -0700643 @Override
Christopher Tate5cbbf562009-06-22 16:44:51 -0700644 public void doRestore(ParcelFileDescriptor data, int appVersionCode,
Christopher Tate44a27902010-01-27 17:15:49 -0800645 ParcelFileDescriptor newState,
646 int token, IBackupManager callbackBinder) throws RemoteException {
Christopher Tate19024922010-01-22 16:39:53 -0800647 // Ensure that we're running with the app's normal permission level
Christopher Tate44a27902010-01-27 17:15:49 -0800648 long ident = Binder.clearCallingIdentity();
Christopher Tate19024922010-01-22 16:39:53 -0800649
Christopher Tate436344a2009-09-30 16:17:37 -0700650 if (DEBUG) Log.v(TAG, "doRestore() invoked");
Joe Onorato83248c42009-06-17 17:55:20 -0700651 BackupDataInput input = new BackupDataInput(data.getFileDescriptor());
652 try {
Christopher Tate5cbbf562009-06-22 16:44:51 -0700653 BackupAgent.this.onRestore(input, appVersionCode, newState);
Joe Onorato83248c42009-06-17 17:55:20 -0700654 } catch (IOException ex) {
655 Log.d(TAG, "onRestore (" + BackupAgent.this.getClass().getName() + ") threw", ex);
656 throw new RuntimeException(ex);
657 } catch (RuntimeException ex) {
658 Log.d(TAG, "onRestore (" + BackupAgent.this.getClass().getName() + ") threw", ex);
659 throw ex;
Christopher Tate19024922010-01-22 16:39:53 -0800660 } finally {
Christopher Tatef85f5b22013-04-18 16:57:43 -0700661 // Ensure that any side-effect SharedPreferences writes have landed
662 waitForSharedPrefs();
663
Christopher Tate44a27902010-01-27 17:15:49 -0800664 Binder.restoreCallingIdentity(ident);
665 try {
666 callbackBinder.opComplete(token);
667 } catch (RemoteException e) {
668 // we'll time out anyway, so we're safe
669 }
Joe Onorato83248c42009-06-17 17:55:20 -0700670 }
Christopher Tate487529a2009-04-29 14:03:25 -0700671 }
Christopher Tate75a99702011-05-18 16:28:19 -0700672
673 @Override
Christopher Tate79ec80d2011-06-24 14:58:49 -0700674 public void doFullBackup(ParcelFileDescriptor data,
675 int token, IBackupManager callbackBinder) {
676 // Ensure that we're running with the app's normal permission level
677 long ident = Binder.clearCallingIdentity();
678
679 if (DEBUG) Log.v(TAG, "doFullBackup() invoked");
Christopher Tate79ec80d2011-06-24 14:58:49 -0700680
Christopher Tatef85f5b22013-04-18 16:57:43 -0700681 // Ensure that any SharedPreferences writes have landed *before*
682 // we potentially try to back up the underlying files directly.
683 waitForSharedPrefs();
684
Christopher Tate79ec80d2011-06-24 14:58:49 -0700685 try {
686 BackupAgent.this.onFullBackup(new FullBackupDataOutput(data));
687 } catch (IOException ex) {
688 Log.d(TAG, "onBackup (" + BackupAgent.this.getClass().getName() + ") threw", ex);
689 throw new RuntimeException(ex);
690 } catch (RuntimeException ex) {
691 Log.d(TAG, "onBackup (" + BackupAgent.this.getClass().getName() + ") threw", ex);
692 throw ex;
693 } finally {
Christopher Tatef85f5b22013-04-18 16:57:43 -0700694 // ... and then again after, as in the doBackup() case
695 waitForSharedPrefs();
696
Christopher Tate7926a692011-07-11 11:31:57 -0700697 // Send the EOD marker indicating that there is no more data
698 // forthcoming from this agent.
699 try {
700 FileOutputStream out = new FileOutputStream(data.getFileDescriptor());
701 byte[] buf = new byte[4];
702 out.write(buf);
703 } catch (IOException e) {
704 Log.e(TAG, "Unable to finalize backup stream!");
705 }
706
Christopher Tate79ec80d2011-06-24 14:58:49 -0700707 Binder.restoreCallingIdentity(ident);
708 try {
709 callbackBinder.opComplete(token);
710 } catch (RemoteException e) {
711 // we'll time out anyway, so we're safe
712 }
713 }
714 }
715
716 @Override
Christopher Tate75a99702011-05-18 16:28:19 -0700717 public void doRestoreFile(ParcelFileDescriptor data, long size,
718 int type, String domain, String path, long mode, long mtime,
719 int token, IBackupManager callbackBinder) throws RemoteException {
720 long ident = Binder.clearCallingIdentity();
721 try {
Christopher Tate75a99702011-05-18 16:28:19 -0700722 BackupAgent.this.onRestoreFile(data, size, type, domain, path, mode, mtime);
723 } catch (IOException e) {
724 throw new RuntimeException(e);
725 } finally {
Christopher Tatef85f5b22013-04-18 16:57:43 -0700726 // Ensure that any side-effect SharedPreferences writes have landed
727 waitForSharedPrefs();
728
Christopher Tate75a99702011-05-18 16:28:19 -0700729 Binder.restoreCallingIdentity(ident);
730 try {
731 callbackBinder.opComplete(token);
732 } catch (RemoteException e) {
733 // we'll time out anyway, so we're safe
734 }
735 }
736 }
Christopher Tatecba59412014-04-01 10:38:29 -0700737
738 @Override
Christopher Tate2e40d112014-07-15 12:37:38 -0700739 public void doRestoreFinished(int token, IBackupManager callbackBinder) {
740 long ident = Binder.clearCallingIdentity();
741 try {
742 BackupAgent.this.onRestoreFinished();
743 } finally {
744 // Ensure that any side-effect SharedPreferences writes have landed
745 waitForSharedPrefs();
746
747 Binder.restoreCallingIdentity(ident);
748 try {
749 callbackBinder.opComplete(token);
750 } catch (RemoteException e) {
751 // we'll time out anyway, so we're safe
752 }
753 }
754 }
755
756 @Override
Christopher Tatecba59412014-04-01 10:38:29 -0700757 public void fail(String message) {
758 getHandler().post(new FailRunnable(message));
759 }
760 }
761
762 static class FailRunnable implements Runnable {
763 private String mMessage;
764
765 FailRunnable(String message) {
766 mMessage = message;
767 }
768
769 @Override
770 public void run() {
771 throw new IllegalStateException(mMessage);
772 }
Christopher Tate487529a2009-04-29 14:03:25 -0700773 }
774}