blob: 342576553b616bedb5fb6fcd791aa62c567076db [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 Tate45281862010-03-05 15:46:30 -080020import android.app.backup.IBackupManager;
Christopher Tate181fafa2009-05-14 11:12:14 -070021import android.content.Context;
22import android.content.ContextWrapper;
Christopher Tate79ec80d2011-06-24 14:58:49 -070023import android.content.pm.ApplicationInfo;
Christopher Tate19024922010-01-22 16:39:53 -080024import android.os.Binder;
Christopher Tate487529a2009-04-29 14:03:25 -070025import android.os.IBinder;
Christopher Tate22b87872009-05-04 16:41:53 -070026import android.os.ParcelFileDescriptor;
Christopher Tate5cb5c332013-02-21 14:32:12 -080027import android.os.Process;
Christopher Tate487529a2009-04-29 14:03:25 -070028import android.os.RemoteException;
29import android.util.Log;
30
Christopher Tate79ec80d2011-06-24 14:58:49 -070031import java.io.File;
Christopher Tate7926a692011-07-11 11:31:57 -070032import java.io.FileOutputStream;
Joe Onorato83248c42009-06-17 17:55:20 -070033import java.io.IOException;
Christopher Tate79ec80d2011-06-24 14:58:49 -070034import java.util.HashSet;
35import java.util.LinkedList;
36
37import libcore.io.ErrnoException;
38import libcore.io.Libcore;
39import libcore.io.OsConstants;
40import libcore.io.StructStat;
Joe Onorato83248c42009-06-17 17:55:20 -070041
Christopher Tate487529a2009-04-29 14:03:25 -070042/**
Scott Maind17da432010-04-29 21:42:58 -070043 * Provides the central interface between an
Christopher Tate4e14a822010-04-08 12:54:23 -070044 * application and Android's data backup infrastructure. An application that wishes
45 * to participate in the backup and restore mechanism will declare a subclass of
46 * {@link android.app.backup.BackupAgent}, implement the
Scott Maind17da432010-04-29 21:42:58 -070047 * {@link #onBackup(ParcelFileDescriptor, BackupDataOutput, ParcelFileDescriptor) onBackup()}
48 * and {@link #onRestore(BackupDataInput, int, ParcelFileDescriptor) onRestore()} methods,
49 * and provide the name of its backup agent class in its {@code AndroidManifest.xml} file via
Joe Fernandez61fd1e82011-10-26 13:39:11 -070050 * the <code>
51 * <a href="{@docRoot}guide/topics/manifest/application-element.html">&lt;application&gt;</a></code>
Scott Maind17da432010-04-29 21:42:58 -070052 * tag's {@code android:backupAgent} attribute.
Joe Fernandez61fd1e82011-10-26 13:39:11 -070053 *
54 * <div class="special reference">
55 * <h3>Developer Guides</h3>
56 * <p>For more information about using BackupAgent, read the
57 * <a href="{@docRoot}guide/topics/data/backup.html">Data Backup</a> developer guide.</p></div>
58 *
Scott Maind17da432010-04-29 21:42:58 -070059 * <h3>Basic Operation</h3>
Kenny Root5a20ea12010-02-23 18:49:11 -080060 * <p>
Christopher Tate4e14a822010-04-08 12:54:23 -070061 * When the application makes changes to data that it wishes to keep backed up,
62 * it should call the
63 * {@link android.app.backup.BackupManager#dataChanged() BackupManager.dataChanged()} method.
Scott Maind17da432010-04-29 21:42:58 -070064 * This notifies the Android Backup Manager that the application needs an opportunity
65 * to update its backup image. The Backup Manager, in turn, schedules a
Christopher Tate4e14a822010-04-08 12:54:23 -070066 * backup pass to be performed at an opportune time.
67 * <p>
Scott Maind17da432010-04-29 21:42:58 -070068 * Restore operations are typically performed only when applications are first
Christopher Tate4e14a822010-04-08 12:54:23 -070069 * installed on a device. At that time, the operating system checks to see whether
Scott Maind17da432010-04-29 21:42:58 -070070 * there is a previously-saved data set available for the application being installed, and if so,
71 * begins an immediate restore pass to deliver the backup data as part of the installation
Christopher Tate4e14a822010-04-08 12:54:23 -070072 * process.
73 * <p>
Scott Maind17da432010-04-29 21:42:58 -070074 * When a backup or restore pass is run, the application's process is launched
75 * (if not already running), the manifest-declared backup agent class (in the {@code
76 * android:backupAgent} attribute) is instantiated within
77 * that process, and the agent's {@link #onCreate()} method is invoked. This prepares the
Christopher Tate4e14a822010-04-08 12:54:23 -070078 * agent instance to run the actual backup or restore logic. At this point the
79 * agent's
80 * {@link #onBackup(ParcelFileDescriptor, BackupDataOutput, ParcelFileDescriptor) onBackup()} or
81 * {@link #onRestore(BackupDataInput, int, ParcelFileDescriptor) onRestore()} method will be
82 * invoked as appropriate for the operation being performed.
83 * <p>
Scott Maind17da432010-04-29 21:42:58 -070084 * A backup data set consists of one or more "entities," flattened binary data
85 * records that are each identified with a key string unique within the data set. Adding a
86 * record to the active data set or updating an existing record is done by simply
Christopher Tate4e14a822010-04-08 12:54:23 -070087 * writing new entity data under the desired key. Deleting an entity from the data set
88 * is done by writing an entity under that key with header specifying a negative data
89 * size, and no actual entity data.
90 * <p>
91 * <b>Helper Classes</b>
92 * <p>
93 * An extensible agent based on convenient helper classes is available in
94 * {@link android.app.backup.BackupAgentHelper}. That class is particularly
95 * suited to handling of simple file or {@link android.content.SharedPreferences}
96 * backup and restore.
97 *
98 * @see android.app.backup.BackupManager
99 * @see android.app.backup.BackupAgentHelper
100 * @see android.app.backup.BackupDataInput
101 * @see android.app.backup.BackupDataOutput
Christopher Tate487529a2009-04-29 14:03:25 -0700102 */
Christopher Tate181fafa2009-05-14 11:12:14 -0700103public abstract class BackupAgent extends ContextWrapper {
Joe Onorato83248c42009-06-17 17:55:20 -0700104 private static final String TAG = "BackupAgent";
Christopher Tate4a627c72011-04-01 14:43:32 -0700105 private static final boolean DEBUG = true;
Joe Onorato83248c42009-06-17 17:55:20 -0700106
Christopher Tate79ec80d2011-06-24 14:58:49 -0700107 /** @hide */
108 public static final int TYPE_EOF = 0;
109
110 /**
111 * During a full restore, indicates that the file system object being restored
112 * is an ordinary file.
113 */
114 public static final int TYPE_FILE = 1;
115
116 /**
117 * During a full restore, indicates that the file system object being restored
118 * is a directory.
119 */
120 public static final int TYPE_DIRECTORY = 2;
121
122 /** @hide */
123 public static final int TYPE_SYMLINK = 3;
124
Christopher Tate181fafa2009-05-14 11:12:14 -0700125 public BackupAgent() {
126 super(null);
127 }
Christopher Tate487529a2009-04-29 14:03:25 -0700128
Christopher Tate4e14a822010-04-08 12:54:23 -0700129 /**
130 * Provided as a convenience for agent implementations that need an opportunity
131 * to do one-time initialization before the actual backup or restore operation
132 * is begun.
133 * <p>
134 * Agents do not need to override this method.
135 */
Christopher Tate181fafa2009-05-14 11:12:14 -0700136 public void onCreate() {
137 }
138
Christopher Tate4e14a822010-04-08 12:54:23 -0700139 /**
140 * Provided as a convenience for agent implementations that need to do some
141 * sort of shutdown process after backup or restore is completed.
142 * <p>
143 * Agents do not need to override this method.
144 */
Christopher Tate181fafa2009-05-14 11:12:14 -0700145 public void onDestroy() {
146 }
Christopher Tate487529a2009-04-29 14:03:25 -0700147
148 /**
Kenny Root5a20ea12010-02-23 18:49:11 -0800149 * The application is being asked to write any data changed since the last
150 * time it performed a backup operation. The state data recorded during the
151 * last backup pass is provided in the <code>oldState</code> file
152 * descriptor. If <code>oldState</code> is <code>null</code>, no old state
153 * is available and the application should perform a full backup. In both
154 * cases, a representation of the final backup state after this pass should
155 * be written to the file pointed to by the file descriptor wrapped in
156 * <code>newState</code>.
Christopher Tate4e14a822010-04-08 12:54:23 -0700157 * <p>
158 * Each entity written to the {@link android.app.backup.BackupDataOutput}
159 * <code>data</code> stream will be transmitted
160 * over the current backup transport and stored in the remote data set under
161 * the key supplied as part of the entity. Writing an entity with a negative
162 * data size instructs the transport to delete whatever entity currently exists
163 * under that key from the remote data set.
Kenny Root5a20ea12010-02-23 18:49:11 -0800164 *
165 * @param oldState An open, read-only ParcelFileDescriptor pointing to the
166 * last backup state provided by the application. May be
167 * <code>null</code>, in which case no prior state is being
168 * provided and the application should perform a full backup.
169 * @param data A structured wrapper around an open, read/write
Christopher Tate4e14a822010-04-08 12:54:23 -0700170 * file descriptor pointing to the backup data destination.
Kenny Root5a20ea12010-02-23 18:49:11 -0800171 * Typically the application will use backup helper classes to
172 * write to this file.
173 * @param newState An open, read/write ParcelFileDescriptor pointing to an
174 * empty file. The application should record the final backup
Christopher Tate4e14a822010-04-08 12:54:23 -0700175 * state here after writing the requested data to the <code>data</code>
176 * output stream.
Christopher Tate487529a2009-04-29 14:03:25 -0700177 */
Joe Onorato290bb012009-05-13 18:57:29 -0400178 public abstract void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
Joe Onorato4ababd92009-06-25 18:29:18 -0400179 ParcelFileDescriptor newState) throws IOException;
Kenny Root5a20ea12010-02-23 18:49:11 -0800180
Christopher Tate487529a2009-04-29 14:03:25 -0700181 /**
Kenny Root5a20ea12010-02-23 18:49:11 -0800182 * The application is being restored from backup and should replace any
183 * existing data with the contents of the backup. The backup data is
Christopher Tate4e14a822010-04-08 12:54:23 -0700184 * provided through the <code>data</code> parameter. Once
Kenny Root5a20ea12010-02-23 18:49:11 -0800185 * the restore is finished, the application should write a representation of
186 * the final state to the <code>newState</code> file descriptor.
187 * <p>
188 * The application is responsible for properly erasing its old data and
189 * replacing it with the data supplied to this method. No "clear user data"
190 * operation will be performed automatically by the operating system. The
191 * exception to this is in the case of a failed restore attempt: if
192 * onRestore() throws an exception, the OS will assume that the
193 * application's data may now be in an incoherent state, and will clear it
194 * before proceeding.
195 *
196 * @param data A structured wrapper around an open, read-only
Christopher Tate4e14a822010-04-08 12:54:23 -0700197 * file descriptor pointing to a full snapshot of the
198 * application's data. The application should consume every
199 * entity represented in this data stream.
Scott Mainb83a2832010-04-29 13:26:53 -0700200 * @param appVersionCode The value of the <a
201 * href="{@docRoot}guide/topics/manifest/manifest-element.html#vcode">{@code
202 * android:versionCode}</a> manifest attribute,
203 * from the application that backed up this particular data set. This
Christopher Tate4e14a822010-04-08 12:54:23 -0700204 * makes it possible for an application's agent to distinguish among any
Kenny Root5a20ea12010-02-23 18:49:11 -0800205 * possible older data versions when asked to perform the restore
206 * operation.
207 * @param newState An open, read/write ParcelFileDescriptor pointing to an
208 * empty file. The application should record the final backup
Christopher Tate4e14a822010-04-08 12:54:23 -0700209 * state here after restoring its data from the <code>data</code> stream.
Christopher Tate4a627c72011-04-01 14:43:32 -0700210 * When a full-backup dataset is being restored, this will be <code>null</code>.
Christopher Tate487529a2009-04-29 14:03:25 -0700211 */
Christopher Tate5cbbf562009-06-22 16:44:51 -0700212 public abstract void onRestore(BackupDataInput data, int appVersionCode,
213 ParcelFileDescriptor newState)
Joe Onorato83248c42009-06-17 17:55:20 -0700214 throws IOException;
Christopher Tate487529a2009-04-29 14:03:25 -0700215
Christopher Tate4a627c72011-04-01 14:43:32 -0700216 /**
Christopher Tate79ec80d2011-06-24 14:58:49 -0700217 * The default implementation backs up the entirety of the application's "owned"
218 * file system trees to the output.
Christopher Tate75a99702011-05-18 16:28:19 -0700219 */
Christopher Tate79ec80d2011-06-24 14:58:49 -0700220 public void onFullBackup(FullBackupDataOutput data) throws IOException {
221 ApplicationInfo appInfo = getApplicationInfo();
222
Christopher Tate2efd2db2011-07-19 16:32:49 -0700223 String rootDir = new File(appInfo.dataDir).getCanonicalPath();
224 String filesDir = getFilesDir().getCanonicalPath();
225 String databaseDir = getDatabasePath("foo").getParentFile().getCanonicalPath();
226 String sharedPrefsDir = getSharedPrefsFile("foo").getParentFile().getCanonicalPath();
227 String cacheDir = getCacheDir().getCanonicalPath();
Christopher Tate79ec80d2011-06-24 14:58:49 -0700228 String libDir = (appInfo.nativeLibraryDir != null)
Christopher Tate2efd2db2011-07-19 16:32:49 -0700229 ? new File(appInfo.nativeLibraryDir).getCanonicalPath()
Christopher Tate79ec80d2011-06-24 14:58:49 -0700230 : null;
231
232 // Filters, the scan queue, and the set of resulting entities
233 HashSet<String> filterSet = new HashSet<String>();
234 String packageName = getPackageName();
235
236 // Okay, start with the app's root tree, but exclude all of the canonical subdirs
237 if (libDir != null) {
238 filterSet.add(libDir);
239 }
240 filterSet.add(cacheDir);
241 filterSet.add(databaseDir);
242 filterSet.add(sharedPrefsDir);
243 filterSet.add(filesDir);
244 fullBackupFileTree(packageName, FullBackup.ROOT_TREE_TOKEN, rootDir, filterSet, data);
245
246 // Now do the same for the files dir, db dir, and shared prefs dir
247 filterSet.add(rootDir);
248 filterSet.remove(filesDir);
249 fullBackupFileTree(packageName, FullBackup.DATA_TREE_TOKEN, filesDir, filterSet, data);
250
251 filterSet.add(filesDir);
252 filterSet.remove(databaseDir);
253 fullBackupFileTree(packageName, FullBackup.DATABASE_TREE_TOKEN, databaseDir, filterSet, data);
254
255 filterSet.add(databaseDir);
256 filterSet.remove(sharedPrefsDir);
257 fullBackupFileTree(packageName, FullBackup.SHAREDPREFS_TREE_TOKEN, sharedPrefsDir, filterSet, data);
Christopher Tate416c39e2013-02-14 16:55:46 -0800258
259 // getExternalFilesDir() location associated with this app. Technically there should
260 // not be any files here if the app does not properly have permission to access
261 // external storage, but edge cases happen. fullBackupFileTree() catches
Christopher Tate5cb5c332013-02-21 14:32:12 -0800262 // IOExceptions and similar, and treats them as non-fatal, so we rely on that; and
263 // we know a priori that processes running as the system UID are not permitted to
264 // access external storage, so we check for that as well to avoid nastygrams in
265 // the log.
266 if (Process.myUid() != Process.SYSTEM_UID) {
267 File efLocation = getExternalFilesDir(null);
268 if (efLocation != null) {
269 fullBackupFileTree(packageName, FullBackup.MANAGED_EXTERNAL_TREE_TOKEN,
270 efLocation.getCanonicalPath(), null, data);
271 }
272 }
Christopher Tate75a99702011-05-18 16:28:19 -0700273 }
274
275 /**
Christopher Tate79ec80d2011-06-24 14:58:49 -0700276 * Write an entire file as part of a full-backup operation. The file's contents
277 * will be delivered to the backup destination along with the metadata necessary
278 * to place it with the proper location and permissions on the device where the
279 * data is restored.
Christopher Tate79ec80d2011-06-24 14:58:49 -0700280 *
Christopher Tate79ec80d2011-06-24 14:58:49 -0700281 * @param file The file to be backed up. The file must exist and be readable by
282 * the caller.
283 * @param output The destination to which the backed-up file data will be sent.
Christopher Tate4a627c72011-04-01 14:43:32 -0700284 */
Christopher Tate79ec80d2011-06-24 14:58:49 -0700285 public final void fullBackupFile(File file, FullBackupDataOutput output) {
286 // Look up where all of our various well-defined dir trees live on this device
287 String mainDir;
288 String filesDir;
289 String dbDir;
290 String spDir;
291 String cacheDir;
292 String libDir;
Christopher Tate5cb5c332013-02-21 14:32:12 -0800293 String efDir = null;
Christopher Tate2efd2db2011-07-19 16:32:49 -0700294 String filePath;
Christopher Tate79ec80d2011-06-24 14:58:49 -0700295
296 ApplicationInfo appInfo = getApplicationInfo();
297
Christopher Tate2efd2db2011-07-19 16:32:49 -0700298 try {
299 mainDir = new File(appInfo.dataDir).getCanonicalPath();
300 filesDir = getFilesDir().getCanonicalPath();
301 dbDir = getDatabasePath("foo").getParentFile().getCanonicalPath();
302 spDir = getSharedPrefsFile("foo").getParentFile().getCanonicalPath();
303 cacheDir = getCacheDir().getCanonicalPath();
304 libDir = (appInfo.nativeLibraryDir == null)
305 ? null
306 : new File(appInfo.nativeLibraryDir).getCanonicalPath();
Christopher Tate5cb5c332013-02-21 14:32:12 -0800307
308 // may or may not have external files access to attempt backup/restore there
309 if (Process.myUid() != Process.SYSTEM_UID) {
310 File efLocation = getExternalFilesDir(null);
311 if (efLocation != null) {
312 efDir = efLocation.getCanonicalPath();
313 }
314 }
Christopher Tate79ec80d2011-06-24 14:58:49 -0700315
Christopher Tate2efd2db2011-07-19 16:32:49 -0700316 // Now figure out which well-defined tree the file is placed in, working from
317 // most to least specific. We also specifically exclude the lib and cache dirs.
318 filePath = file.getCanonicalPath();
319 } catch (IOException e) {
320 Log.w(TAG, "Unable to obtain canonical paths");
321 return;
322 }
Christopher Tate79ec80d2011-06-24 14:58:49 -0700323
324 if (filePath.startsWith(cacheDir) || filePath.startsWith(libDir)) {
325 Log.w(TAG, "lib and cache files are not backed up");
326 return;
327 }
328
329 final String domain;
330 String rootpath = null;
331 if (filePath.startsWith(dbDir)) {
332 domain = FullBackup.DATABASE_TREE_TOKEN;
333 rootpath = dbDir;
334 } else if (filePath.startsWith(spDir)) {
335 domain = FullBackup.SHAREDPREFS_TREE_TOKEN;
336 rootpath = spDir;
337 } else if (filePath.startsWith(filesDir)) {
338 domain = FullBackup.DATA_TREE_TOKEN;
339 rootpath = filesDir;
340 } else if (filePath.startsWith(mainDir)) {
341 domain = FullBackup.ROOT_TREE_TOKEN;
342 rootpath = mainDir;
Christopher Tate5cb5c332013-02-21 14:32:12 -0800343 } else if ((efDir != null) && filePath.startsWith(efDir)) {
Christopher Tate416c39e2013-02-14 16:55:46 -0800344 domain = FullBackup.MANAGED_EXTERNAL_TREE_TOKEN;
345 rootpath = efDir;
Christopher Tate79ec80d2011-06-24 14:58:49 -0700346 } else {
347 Log.w(TAG, "File " + filePath + " is in an unsupported location; skipping");
348 return;
349 }
350
351 // And now that we know where it lives, semantically, back it up appropriately
352 Log.i(TAG, "backupFile() of " + filePath + " => domain=" + domain
353 + " rootpath=" + rootpath);
354 FullBackup.backupToTar(getPackageName(), domain, null, rootpath, filePath,
355 output.getData());
356 }
357
358 /**
359 * Scan the dir tree (if it actually exists) and process each entry we find. If the
360 * 'excludes' parameter is non-null, it is consulted each time a new file system entity
361 * is visited to see whether that entity (and its subtree, if appropriate) should be
362 * omitted from the backup process.
363 *
364 * @hide
365 */
366 protected final void fullBackupFileTree(String packageName, String domain, String rootPath,
367 HashSet<String> excludes, FullBackupDataOutput output) {
368 File rootFile = new File(rootPath);
369 if (rootFile.exists()) {
370 LinkedList<File> scanQueue = new LinkedList<File>();
371 scanQueue.add(rootFile);
372
373 while (scanQueue.size() > 0) {
374 File file = scanQueue.remove(0);
Christopher Tate2efd2db2011-07-19 16:32:49 -0700375 String filePath;
Christopher Tate79ec80d2011-06-24 14:58:49 -0700376 try {
Christopher Tate2efd2db2011-07-19 16:32:49 -0700377 filePath = file.getCanonicalPath();
378
379 // prune this subtree?
380 if (excludes != null && excludes.contains(filePath)) {
381 continue;
382 }
383
384 // If it's a directory, enqueue its contents for scanning.
Christopher Tate79ec80d2011-06-24 14:58:49 -0700385 StructStat stat = Libcore.os.lstat(filePath);
386 if (OsConstants.S_ISLNK(stat.st_mode)) {
387 if (DEBUG) Log.i(TAG, "Symlink (skipping)!: " + file);
388 continue;
389 } else if (OsConstants.S_ISDIR(stat.st_mode)) {
390 File[] contents = file.listFiles();
391 if (contents != null) {
392 for (File entry : contents) {
393 scanQueue.add(0, entry);
394 }
395 }
396 }
Christopher Tate2efd2db2011-07-19 16:32:49 -0700397 } catch (IOException e) {
398 if (DEBUG) Log.w(TAG, "Error canonicalizing path of " + file);
399 continue;
Christopher Tate79ec80d2011-06-24 14:58:49 -0700400 } catch (ErrnoException e) {
401 if (DEBUG) Log.w(TAG, "Error scanning file " + file + " : " + e);
402 continue;
403 }
404
405 // Finally, back this file up before proceeding
406 FullBackup.backupToTar(packageName, domain, null, rootPath, filePath,
407 output.getData());
408 }
409 }
410 }
411
412 /**
413 * Handle the data delivered via the given file descriptor during a full restore
414 * operation. The agent is given the path to the file's original location as well
415 * as its size and metadata.
416 * <p>
417 * The file descriptor can only be read for {@code size} bytes; attempting to read
418 * more data has undefined behavior.
419 * <p>
420 * The default implementation creates the destination file/directory and populates it
421 * with the data from the file descriptor, then sets the file's access mode and
422 * modification time to match the restore arguments.
423 *
424 * @param data A read-only file descriptor from which the agent can read {@code size}
425 * bytes of file data.
426 * @param size The number of bytes of file content to be restored to the given
427 * destination. If the file system object being restored is a directory, {@code size}
428 * will be zero.
429 * @param destination The File on disk to be restored with the given data.
430 * @param type The kind of file system object being restored. This will be either
431 * {@link BackupAgent#TYPE_FILE} or {@link BackupAgent#TYPE_DIRECTORY}.
432 * @param mode The access mode to be assigned to the destination after its data is
433 * written. This is in the standard format used by {@code chmod()}.
434 * @param mtime The modification time of the file when it was backed up, suitable to
435 * be assigned to the file after its data is written.
436 * @throws IOException
437 */
438 public void onRestoreFile(ParcelFileDescriptor data, long size,
439 File destination, int type, long mode, long mtime)
440 throws IOException {
441 FullBackup.restoreFile(data, size, type, mode, mtime, destination);
442 }
443
444 /**
445 * Only specialized platform agents should overload this entry point to support
446 * restores to crazy non-app locations.
447 * @hide
448 */
449 protected void onRestoreFile(ParcelFileDescriptor data, long size,
450 int type, String domain, String path, long mode, long mtime)
451 throws IOException {
452 String basePath = null;
453
454 if (DEBUG) Log.d(TAG, "onRestoreFile() size=" + size + " type=" + type
455 + " domain=" + domain + " relpath=" + path + " mode=" + mode
456 + " mtime=" + mtime);
457
458 // Parse out the semantic domains into the correct physical location
459 if (domain.equals(FullBackup.DATA_TREE_TOKEN)) {
Christopher Tate2efd2db2011-07-19 16:32:49 -0700460 basePath = getFilesDir().getCanonicalPath();
Christopher Tate79ec80d2011-06-24 14:58:49 -0700461 } else if (domain.equals(FullBackup.DATABASE_TREE_TOKEN)) {
Christopher Tate2efd2db2011-07-19 16:32:49 -0700462 basePath = getDatabasePath("foo").getParentFile().getCanonicalPath();
Christopher Tate79ec80d2011-06-24 14:58:49 -0700463 } else if (domain.equals(FullBackup.ROOT_TREE_TOKEN)) {
Christopher Tate2efd2db2011-07-19 16:32:49 -0700464 basePath = new File(getApplicationInfo().dataDir).getCanonicalPath();
Christopher Tate79ec80d2011-06-24 14:58:49 -0700465 } else if (domain.equals(FullBackup.SHAREDPREFS_TREE_TOKEN)) {
Christopher Tate2efd2db2011-07-19 16:32:49 -0700466 basePath = getSharedPrefsFile("foo").getParentFile().getCanonicalPath();
Christopher Tate79ec80d2011-06-24 14:58:49 -0700467 } else if (domain.equals(FullBackup.CACHE_TREE_TOKEN)) {
Christopher Tate2efd2db2011-07-19 16:32:49 -0700468 basePath = getCacheDir().getCanonicalPath();
Christopher Tate416c39e2013-02-14 16:55:46 -0800469 } else if (domain.equals(FullBackup.MANAGED_EXTERNAL_TREE_TOKEN)) {
Christopher Tate5cb5c332013-02-21 14:32:12 -0800470 // make sure we can try to restore here before proceeding
471 if (Process.myUid() != Process.SYSTEM_UID) {
472 File efLocation = getExternalFilesDir(null);
473 if (efLocation != null) {
474 basePath = getExternalFilesDir(null).getCanonicalPath();
Christopher Tate294b5122013-02-19 14:08:59 -0800475 mode = -1; // < 0 is a token to skip attempting a chmod()
Christopher Tate5cb5c332013-02-21 14:32:12 -0800476 }
477 }
Christopher Tate79ec80d2011-06-24 14:58:49 -0700478 } else {
479 // Not a supported location
480 Log.i(TAG, "Data restored from non-app domain " + domain + ", ignoring");
481 }
482
483 // Now that we've figured out where the data goes, send it on its way
484 if (basePath != null) {
485 File outFile = new File(basePath, path);
486 if (DEBUG) Log.i(TAG, "[" + domain + " : " + path + "] mapped to " + outFile.getPath());
487 onRestoreFile(data, size, outFile, type, mode, mtime);
488 } else {
489 // Not a supported output location? We need to consume the data
490 // anyway, so just use the default "copy the data out" implementation
491 // with a null destination.
492 if (DEBUG) Log.i(TAG, "[ skipping data from unsupported domain " + domain + "]");
493 FullBackup.restoreFile(data, size, type, mode, mtime, null);
494 }
Christopher Tate4a627c72011-04-01 14:43:32 -0700495 }
Christopher Tate487529a2009-04-29 14:03:25 -0700496
497 // ----- Core implementation -----
Christopher Tate44a27902010-01-27 17:15:49 -0800498
499 /** @hide */
500 public final IBinder onBind() {
Christopher Tate181fafa2009-05-14 11:12:14 -0700501 return mBinder;
Christopher Tate487529a2009-04-29 14:03:25 -0700502 }
503
504 private final IBinder mBinder = new BackupServiceBinder().asBinder();
505
Christopher Tate181fafa2009-05-14 11:12:14 -0700506 /** @hide */
507 public void attach(Context context) {
508 attachBaseContext(context);
509 }
510
Christopher Tate487529a2009-04-29 14:03:25 -0700511 // ----- IBackupService binder interface -----
Christopher Tate181fafa2009-05-14 11:12:14 -0700512 private class BackupServiceBinder extends IBackupAgent.Stub {
513 private static final String TAG = "BackupServiceBinder";
514
Christopher Tate75a99702011-05-18 16:28:19 -0700515 @Override
Christopher Tate22b87872009-05-04 16:41:53 -0700516 public void doBackup(ParcelFileDescriptor oldState,
517 ParcelFileDescriptor data,
Christopher Tate44a27902010-01-27 17:15:49 -0800518 ParcelFileDescriptor newState,
519 int token, IBackupManager callbackBinder) throws RemoteException {
Christopher Tate19024922010-01-22 16:39:53 -0800520 // Ensure that we're running with the app's normal permission level
Christopher Tate44a27902010-01-27 17:15:49 -0800521 long ident = Binder.clearCallingIdentity();
Christopher Tate19024922010-01-22 16:39:53 -0800522
Christopher Tate436344a2009-09-30 16:17:37 -0700523 if (DEBUG) Log.v(TAG, "doBackup() invoked");
Joe Onorato83248c42009-06-17 17:55:20 -0700524 BackupDataOutput output = new BackupDataOutput(data.getFileDescriptor());
Christopher Tate4a627c72011-04-01 14:43:32 -0700525
Joe Onorato290bb012009-05-13 18:57:29 -0400526 try {
Christopher Tate181fafa2009-05-14 11:12:14 -0700527 BackupAgent.this.onBackup(oldState, output, newState);
Joe Onorato4ababd92009-06-25 18:29:18 -0400528 } catch (IOException ex) {
529 Log.d(TAG, "onBackup (" + BackupAgent.this.getClass().getName() + ") threw", ex);
530 throw new RuntimeException(ex);
Joe Onorato290bb012009-05-13 18:57:29 -0400531 } catch (RuntimeException ex) {
Joe Onorato83248c42009-06-17 17:55:20 -0700532 Log.d(TAG, "onBackup (" + BackupAgent.this.getClass().getName() + ") threw", ex);
Joe Onorato290bb012009-05-13 18:57:29 -0400533 throw ex;
Christopher Tate19024922010-01-22 16:39:53 -0800534 } finally {
Christopher Tate44a27902010-01-27 17:15:49 -0800535 Binder.restoreCallingIdentity(ident);
536 try {
537 callbackBinder.opComplete(token);
538 } catch (RemoteException e) {
539 // we'll time out anyway, so we're safe
540 }
Joe Onorato290bb012009-05-13 18:57:29 -0400541 }
Christopher Tate487529a2009-04-29 14:03:25 -0700542 }
543
Christopher Tate75a99702011-05-18 16:28:19 -0700544 @Override
Christopher Tate5cbbf562009-06-22 16:44:51 -0700545 public void doRestore(ParcelFileDescriptor data, int appVersionCode,
Christopher Tate44a27902010-01-27 17:15:49 -0800546 ParcelFileDescriptor newState,
547 int token, IBackupManager callbackBinder) throws RemoteException {
Christopher Tate19024922010-01-22 16:39:53 -0800548 // Ensure that we're running with the app's normal permission level
Christopher Tate44a27902010-01-27 17:15:49 -0800549 long ident = Binder.clearCallingIdentity();
Christopher Tate19024922010-01-22 16:39:53 -0800550
Christopher Tate436344a2009-09-30 16:17:37 -0700551 if (DEBUG) Log.v(TAG, "doRestore() invoked");
Joe Onorato83248c42009-06-17 17:55:20 -0700552 BackupDataInput input = new BackupDataInput(data.getFileDescriptor());
553 try {
Christopher Tate5cbbf562009-06-22 16:44:51 -0700554 BackupAgent.this.onRestore(input, appVersionCode, newState);
Joe Onorato83248c42009-06-17 17:55:20 -0700555 } catch (IOException ex) {
556 Log.d(TAG, "onRestore (" + BackupAgent.this.getClass().getName() + ") threw", ex);
557 throw new RuntimeException(ex);
558 } catch (RuntimeException ex) {
559 Log.d(TAG, "onRestore (" + BackupAgent.this.getClass().getName() + ") threw", ex);
560 throw ex;
Christopher Tate19024922010-01-22 16:39:53 -0800561 } finally {
Christopher Tate44a27902010-01-27 17:15:49 -0800562 Binder.restoreCallingIdentity(ident);
563 try {
564 callbackBinder.opComplete(token);
565 } catch (RemoteException e) {
566 // we'll time out anyway, so we're safe
567 }
Joe Onorato83248c42009-06-17 17:55:20 -0700568 }
Christopher Tate487529a2009-04-29 14:03:25 -0700569 }
Christopher Tate75a99702011-05-18 16:28:19 -0700570
571 @Override
Christopher Tate79ec80d2011-06-24 14:58:49 -0700572 public void doFullBackup(ParcelFileDescriptor data,
573 int token, IBackupManager callbackBinder) {
574 // Ensure that we're running with the app's normal permission level
575 long ident = Binder.clearCallingIdentity();
576
577 if (DEBUG) Log.v(TAG, "doFullBackup() invoked");
Christopher Tate79ec80d2011-06-24 14:58:49 -0700578
579 try {
580 BackupAgent.this.onFullBackup(new FullBackupDataOutput(data));
581 } catch (IOException ex) {
582 Log.d(TAG, "onBackup (" + BackupAgent.this.getClass().getName() + ") threw", ex);
583 throw new RuntimeException(ex);
584 } catch (RuntimeException ex) {
585 Log.d(TAG, "onBackup (" + BackupAgent.this.getClass().getName() + ") threw", ex);
586 throw ex;
587 } finally {
Christopher Tate7926a692011-07-11 11:31:57 -0700588 // Send the EOD marker indicating that there is no more data
589 // forthcoming from this agent.
590 try {
591 FileOutputStream out = new FileOutputStream(data.getFileDescriptor());
592 byte[] buf = new byte[4];
593 out.write(buf);
594 } catch (IOException e) {
595 Log.e(TAG, "Unable to finalize backup stream!");
596 }
597
Christopher Tate79ec80d2011-06-24 14:58:49 -0700598 Binder.restoreCallingIdentity(ident);
599 try {
600 callbackBinder.opComplete(token);
601 } catch (RemoteException e) {
602 // we'll time out anyway, so we're safe
603 }
604 }
605 }
606
607 @Override
Christopher Tate75a99702011-05-18 16:28:19 -0700608 public void doRestoreFile(ParcelFileDescriptor data, long size,
609 int type, String domain, String path, long mode, long mtime,
610 int token, IBackupManager callbackBinder) throws RemoteException {
611 long ident = Binder.clearCallingIdentity();
612 try {
Christopher Tate75a99702011-05-18 16:28:19 -0700613 BackupAgent.this.onRestoreFile(data, size, type, domain, path, mode, mtime);
614 } catch (IOException e) {
615 throw new RuntimeException(e);
616 } finally {
617 Binder.restoreCallingIdentity(ident);
618 try {
619 callbackBinder.opComplete(token);
620 } catch (RemoteException e) {
621 // we'll time out anyway, so we're safe
622 }
623 }
624 }
Christopher Tate487529a2009-04-29 14:03:25 -0700625 }
626}