method public abstract java.io.File getFileStreamPath(java.lang.String);
method public abstract java.io.File getFilesDir();
method public abstract android.os.Looper getMainLooper();
+ method public abstract java.io.File getNoBackupFilesDir();
method public abstract java.io.File getObbDir();
method public abstract java.io.File[] getObbDirs();
method public abstract java.lang.String getPackageCodePath();
method public java.io.File getFileStreamPath(java.lang.String);
method public java.io.File getFilesDir();
method public android.os.Looper getMainLooper();
+ method public java.io.File getNoBackupFilesDir();
method public java.io.File getObbDir();
method public java.io.File[] getObbDirs();
method public java.lang.String getPackageCodePath();
method public java.io.File getFileStreamPath(java.lang.String);
method public java.io.File getFilesDir();
method public android.os.Looper getMainLooper();
+ method public java.io.File getNoBackupFilesDir();
method public java.io.File getObbDir();
method public java.io.File[] getObbDirs();
method public java.lang.String getPackageCodePath();
@GuardedBy("mSync")
private File mFilesDir;
@GuardedBy("mSync")
+ private File mNoBackupFilesDir;
+ @GuardedBy("mSync")
private File mCacheDir;
@GuardedBy("mSync")
return f.delete();
}
+ // Common-path handling of app data dir creation
+ private static File createFilesDirLocked(File file) {
+ if (!file.exists()) {
+ if (!file.mkdirs()) {
+ if (file.exists()) {
+ // spurious failure; probably racing with another process for this app
+ return file;
+ }
+ Log.w(TAG, "Unable to create files subdir " + file.getPath());
+ return null;
+ }
+ FileUtils.setPermissions(
+ file.getPath(),
+ FileUtils.S_IRWXU|FileUtils.S_IRWXG|FileUtils.S_IXOTH,
+ -1, -1);
+ }
+ return file;
+ }
+
@Override
public File getFilesDir() {
synchronized (mSync) {
if (mFilesDir == null) {
mFilesDir = new File(getDataDirFile(), "files");
}
- if (!mFilesDir.exists()) {
- if(!mFilesDir.mkdirs()) {
- if (mFilesDir.exists()) {
- // spurious failure; probably racing with another process for this app
- return mFilesDir;
- }
- Log.w(TAG, "Unable to create files directory " + mFilesDir.getPath());
- return null;
- }
- FileUtils.setPermissions(
- mFilesDir.getPath(),
- FileUtils.S_IRWXU|FileUtils.S_IRWXG|FileUtils.S_IXOTH,
- -1, -1);
+ return createFilesDirLocked(mFilesDir);
+ }
+ }
+
+ @Override
+ public File getNoBackupFilesDir() {
+ synchronized (mSync) {
+ if (mNoBackupFilesDir == null) {
+ mNoBackupFilesDir = new File(getDataDirFile(), "no_backup");
}
- return mFilesDir;
+ return createFilesDirLocked(mNoBackupFilesDir);
}
}
if (mCacheDir == null) {
mCacheDir = new File(getDataDirFile(), "cache");
}
- if (!mCacheDir.exists()) {
- if(!mCacheDir.mkdirs()) {
- if (mCacheDir.exists()) {
- // spurious failure; probably racing with another process for this app
- return mCacheDir;
- }
- Log.w(TAG, "Unable to create cache directory " + mCacheDir.getAbsolutePath());
- return null;
- }
- FileUtils.setPermissions(
- mCacheDir.getPath(),
- FileUtils.S_IRWXU|FileUtils.S_IRWXG|FileUtils.S_IXOTH,
- -1, -1);
- }
+ return createFilesDirLocked(mCacheDir);
}
- return mCacheDir;
}
@Override
throws IOException;
/**
- * The default implementation backs up the entirety of the application's "owned"
- * file system trees to the output.
+ * The application is having its entire file system contents backed up. {@code data}
+ * points to the backup destination, and the app has the opportunity to choose which
+ * files are to be stored. To commit a file as part of the backup, call the
+ * {@link #fullBackupFile(File, FullBackupDataOutput)} helper method. After all file
+ * data is written to the output, the agent returns from this method and the backup
+ * operation concludes.
+ *
+ * <p>Certain parts of the app's data are never backed up even if the app explicitly
+ * sends them to the output:
+ *
+ * <ul>
+ * <li>The contents of the {@link #getCacheDir()} directory</li>
+ * <li>The contents of the {@link #getNoBackupFilesDir()} directory</li>
+ * <li>The contents of the app's shared library directory</li>
+ * </ul>
+ *
+ * <p>The default implementation of this method backs up the entirety of the
+ * application's "owned" file system trees to the output other than the few exceptions
+ * listed above. Apps only need to override this method if they need to impose special
+ * limitations on which files are being stored beyond the control that
+ * {@link #getNoBackupFilesDir()} offers.
+ *
+ * @param data A structured wrapper pointing to the backup destination.
+ * @throws IOException
+ *
+ * @see Context#getNoBackupFilesDir()
+ * @see #fullBackupFile(File, FullBackupDataOutput)
+ * @see #onRestoreFile(ParcelFileDescriptor, long, File, int, long, long)
*/
public void onFullBackup(FullBackupDataOutput data) throws IOException {
ApplicationInfo appInfo = getApplicationInfo();
+ // Note that we don't need to think about the no_backup dir because it's outside
+ // all of the ones we will be traversing
String rootDir = new File(appInfo.dataDir).getCanonicalPath();
String filesDir = getFilesDir().getCanonicalPath();
String databaseDir = getDatabasePath("foo").getParentFile().getCanonicalPath();
* to place it with the proper location and permissions on the device where the
* data is restored.
*
+ * <p class="note">It is safe to explicitly back up files underneath your application's
+ * {@link #getNoBackupFilesDir()} directory, and they will be restored to that
+ * location correctly.
+ *
* @param file The file to be backed up. The file must exist and be readable by
* the caller.
* @param output The destination to which the backed-up file data will be sent.
// Look up where all of our various well-defined dir trees live on this device
String mainDir;
String filesDir;
+ String nbFilesDir;
String dbDir;
String spDir;
String cacheDir;
try {
mainDir = new File(appInfo.dataDir).getCanonicalPath();
filesDir = getFilesDir().getCanonicalPath();
+ nbFilesDir = getNoBackupFilesDir().getCanonicalPath();
dbDir = getDatabasePath("foo").getParentFile().getCanonicalPath();
spDir = getSharedPrefsFile("foo").getParentFile().getCanonicalPath();
cacheDir = getCacheDir().getCanonicalPath();
return;
}
- if (filePath.startsWith(cacheDir) || filePath.startsWith(libDir)) {
- Log.w(TAG, "lib and cache files are not backed up");
+ if (filePath.startsWith(cacheDir)
+ || filePath.startsWith(libDir)
+ || filePath.startsWith(nbFilesDir)) {
+ Log.w(TAG, "lib, cache, and no_backup files are not backed up");
return;
}
mode = -1; // < 0 is a token to skip attempting a chmod()
}
}
+ } else if (domain.equals(FullBackup.NO_BACKUP_TREE_TOKEN)) {
+ basePath = getNoBackupFilesDir().getCanonicalPath();
} else {
// Not a supported location
Log.i(TAG, "Unrecognized domain " + domain);
public static final String OBB_TREE_TOKEN = "obb";
public static final String ROOT_TREE_TOKEN = "r";
public static final String DATA_TREE_TOKEN = "f";
+ public static final String NO_BACKUP_TREE_TOKEN = "nb";
public static final String DATABASE_TREE_TOKEN = "db";
public static final String SHAREDPREFS_TREE_TOKEN = "sp";
public static final String MANAGED_EXTERNAL_TREE_TOKEN = "ef";
public abstract File getFilesDir();
/**
+ * Returns the absolute path to the directory on the filesystem similar to
+ * {@link #getFilesDir()}. The difference is that files placed under this
+ * directory will be excluded from automatic backup to remote storage. See
+ * {@link android.app.backup.BackupAgent BackupAgent} for a full discussion
+ * of the automatic backup mechanism in Android.
+ *
+ * <p>No permissions are required to read or write to the returned path, since this
+ * path is internal storage.
+ *
+ * @return The path of the directory holding application files that will not be
+ * automatically backed up to remote storage.
+ *
+ * @see #openFileOutput
+ * @see #getFileStreamPath
+ * @see #getDir
+ * @see android.app.backup.BackupAgent
+ */
+ public abstract File getNoBackupFilesDir();
+
+ /**
* Returns the absolute path to the directory on the primary external filesystem
* (that is somewhere on {@link android.os.Environment#getExternalStorageDirectory()
* Environment.getExternalStorageDirectory()}) where the application can
public File getFilesDir() {
return mBase.getFilesDir();
}
-
+
+ @Override
+ public File getNoBackupFilesDir() {
+ return mBase.getNoBackupFilesDir();
+ }
+
@Override
public File getExternalFilesDir(String type) {
return mBase.getExternalFilesDir(type);
break;
}
+ // Is it a *file* we need to drop?
+ if (!isRestorableFile(info)) {
+ okay = false;
+ }
+
// If the policy is satisfied, go ahead and set up to pipe the
// data to the agent.
if (DEBUG && okay && mAgent != null) {
}
}
- // Problems setting up the agent communication, or an already-
- // ignored package: skip to the next tar stream entry by
- // reading and discarding this file.
+ // Problems setting up the agent communication, an explicitly
+ // dropped file, or an already-ignored package: skip to the
+ // next stream entry by reading and discarding this file.
if (!okay) {
if (DEBUG) Slog.d(TAG, "[discarding file content]");
long bytesToConsume = (info.size + 511) & ~511;
return info;
}
+ private boolean isRestorableFile(FileMetadata info) {
+ if (FullBackup.CACHE_TREE_TOKEN.equals(info.domain)) {
+ if (MORE_DEBUG) {
+ Slog.i(TAG, "Dropping cache file path " + info.path);
+ }
+ return false;
+ }
+
+ if (FullBackup.ROOT_TREE_TOKEN.equals(info.domain)) {
+ // It's possible this is "no-backup" dir contents in an archive stream
+ // produced on a device running a version of the OS that predates that
+ // API. Respect the no-backup intention and don't let the data get to
+ // the app.
+ if (info.path.startsWith("no_backup/")) {
+ if (MORE_DEBUG) {
+ Slog.i(TAG, "Dropping no_backup file path " + info.path);
+ }
+ return false;
+ }
+ }
+
+ // Otherwise we think this file is good to go
+ return true;
+ }
+
private void HEXLOG(byte[] block) {
int offset = 0;
int todo = block.length;
}
@Override
+ public File getNoBackupFilesDir() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
public File getExternalFilesDir(String type) {
throw new UnsupportedOperationException();
}