From 2c1ba9a961d4f96c26df260ee437655ad9e7c03e Mon Sep 17 00:00:00 2001 From: Jeff Sharkey Date: Wed, 17 Feb 2016 15:29:38 -0700 Subject: [PATCH] Make BackupManager encryption aware. Backup requires both CE and DE storage to be available, so delay spinning up the backup system until the user is unlocked, since that's when CE storage becomes available. Note that devices without FBE immediately transition USER_SYSTEM into the unlocked state, since their CE is always available. Offer to backup and restore files under both CE and DE. Since DE is effectively the same as CE, most logic is simply duplicated for now, but it could be simplified in the future. Since system apps can force their default storage location to DE, we always build explicit CE and DE paths. Add getDataDir() to give clean access to the top-level private data directory, but disclaim that apps shouldn't create files there. Bug: 26279618 Change-Id: Ic34a4b330223725db93b1d0f5c9dffc88002c61f --- api/current.txt | 3 + api/system-current.txt | 3 + api/test-current.txt | 3 + core/java/android/app/ContextImpl.java | 17 +-- core/java/android/app/backup/BackupAgent.java | 152 ++++++++++++++++----- core/java/android/app/backup/FullBackup.java | 91 ++++++++++-- core/java/android/content/Context.java | 19 +++ core/java/android/content/ContextWrapper.java | 5 + .../src/android/app/backup/FullBackupTest.java | 33 +++-- .../server/backup/BackupManagerService.java | 15 +- test-runner/src/android/test/mock/MockContext.java | 5 + .../layoutlib/bridge/android/BridgeContext.java | 6 + 12 files changed, 279 insertions(+), 73 deletions(-) diff --git a/api/current.txt b/api/current.txt index 6110d1c584fe..1ebec4e56d4d 100644 --- a/api/current.txt +++ b/api/current.txt @@ -8006,6 +8006,7 @@ package android.content { method public final int getColor(int); method public final android.content.res.ColorStateList getColorStateList(int); method public abstract android.content.ContentResolver getContentResolver(); + method public abstract java.io.File getDataDir(); method public abstract java.io.File getDatabasePath(java.lang.String); method public abstract java.io.File getDir(java.lang.String, int); method public final android.graphics.drawable.Drawable getDrawable(int); @@ -8198,6 +8199,7 @@ package android.content { method public java.lang.ClassLoader getClassLoader(); method public java.io.File getCodeCacheDir(); method public android.content.ContentResolver getContentResolver(); + method public java.io.File getDataDir(); method public java.io.File getDatabasePath(java.lang.String); method public java.io.File getDir(java.lang.String, int); method public java.io.File getExternalCacheDir(); @@ -37632,6 +37634,7 @@ package android.test.mock { method public java.lang.ClassLoader getClassLoader(); method public java.io.File getCodeCacheDir(); method public android.content.ContentResolver getContentResolver(); + method public java.io.File getDataDir(); method public java.io.File getDatabasePath(java.lang.String); method public java.io.File getDir(java.lang.String, int); method public java.io.File getExternalCacheDir(); diff --git a/api/system-current.txt b/api/system-current.txt index c01f1c32d646..55df364e4da9 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -8297,6 +8297,7 @@ package android.content { method public final int getColor(int); method public final android.content.res.ColorStateList getColorStateList(int); method public abstract android.content.ContentResolver getContentResolver(); + method public abstract java.io.File getDataDir(); method public abstract java.io.File getDatabasePath(java.lang.String); method public abstract java.io.File getDir(java.lang.String, int); method public final android.graphics.drawable.Drawable getDrawable(int); @@ -8499,6 +8500,7 @@ package android.content { method public java.lang.ClassLoader getClassLoader(); method public java.io.File getCodeCacheDir(); method public android.content.ContentResolver getContentResolver(); + method public java.io.File getDataDir(); method public java.io.File getDatabasePath(java.lang.String); method public java.io.File getDir(java.lang.String, int); method public java.io.File getExternalCacheDir(); @@ -40380,6 +40382,7 @@ package android.test.mock { method public java.lang.ClassLoader getClassLoader(); method public java.io.File getCodeCacheDir(); method public android.content.ContentResolver getContentResolver(); + method public java.io.File getDataDir(); method public java.io.File getDatabasePath(java.lang.String); method public java.io.File getDir(java.lang.String, int); method public java.io.File getExternalCacheDir(); diff --git a/api/test-current.txt b/api/test-current.txt index 96d29d1f9c48..8fcb9bdc72ee 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -8009,6 +8009,7 @@ package android.content { method public final int getColor(int); method public final android.content.res.ColorStateList getColorStateList(int); method public abstract android.content.ContentResolver getContentResolver(); + method public abstract java.io.File getDataDir(); method public abstract java.io.File getDatabasePath(java.lang.String); method public abstract java.io.File getDir(java.lang.String, int); method public final android.graphics.drawable.Drawable getDrawable(int); @@ -8202,6 +8203,7 @@ package android.content { method public java.lang.ClassLoader getClassLoader(); method public java.io.File getCodeCacheDir(); method public android.content.ContentResolver getContentResolver(); + method public java.io.File getDataDir(); method public java.io.File getDatabasePath(java.lang.String); method public java.io.File getDir(java.lang.String, int); method public java.io.File getExternalCacheDir(); @@ -37647,6 +37649,7 @@ package android.test.mock { method public java.lang.ClassLoader getClassLoader(); method public java.io.File getCodeCacheDir(); method public android.content.ContentResolver getContentResolver(); + method public java.io.File getDataDir(); method public java.io.File getDatabasePath(java.lang.String); method public java.io.File getDir(java.lang.String, int); method public java.io.File getExternalCacheDir(); diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index 5e8d1908eb18..8884949f361c 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -460,7 +460,7 @@ class ContextImpl extends Context { private File getPreferencesDir() { synchronized (mSync) { if (mPreferencesDir == null) { - mPreferencesDir = new File(getDataDirFile(), "shared_prefs"); + mPreferencesDir = new File(getDataDir(), "shared_prefs"); } return ensurePrivateDirExists(mPreferencesDir); } @@ -525,7 +525,7 @@ class ContextImpl extends Context { public File getFilesDir() { synchronized (mSync) { if (mFilesDir == null) { - mFilesDir = new File(getDataDirFile(), "files"); + mFilesDir = new File(getDataDir(), "files"); } return ensurePrivateDirExists(mFilesDir); } @@ -535,7 +535,7 @@ class ContextImpl extends Context { public File getNoBackupFilesDir() { synchronized (mSync) { if (mNoBackupFilesDir == null) { - mNoBackupFilesDir = new File(getDataDirFile(), "no_backup"); + mNoBackupFilesDir = new File(getDataDir(), "no_backup"); } return ensurePrivateDirExists(mNoBackupFilesDir); } @@ -587,7 +587,7 @@ class ContextImpl extends Context { public File getCacheDir() { synchronized (mSync) { if (mCacheDir == null) { - mCacheDir = new File(getDataDirFile(), "cache"); + mCacheDir = new File(getDataDir(), "cache"); } return ensurePrivateDirExists(mCacheDir); } @@ -597,7 +597,7 @@ class ContextImpl extends Context { public File getCodeCacheDir() { synchronized (mSync) { if (mCodeCacheDir == null) { - mCodeCacheDir = new File(getDataDirFile(), "code_cache"); + mCodeCacheDir = new File(getDataDir(), "code_cache"); } return ensurePrivateDirExists(mCodeCacheDir); } @@ -724,7 +724,7 @@ class ContextImpl extends Context { if ("android".equals(getPackageName())) { mDatabasesDir = new File("/data/system"); } else { - mDatabasesDir = new File(getDataDirFile(), "databases"); + mDatabasesDir = new File(getDataDir(), "databases"); } } return ensurePrivateDirExists(mDatabasesDir); @@ -1920,7 +1920,8 @@ class ContextImpl extends Context { return mDisplayAdjustments; } - private File getDataDirFile() { + @Override + public File getDataDir() { if (mPackageInfo != null) { File res = null; if (isCredentialEncryptedStorage()) { @@ -1947,7 +1948,7 @@ class ContextImpl extends Context { public File getDir(String name, int mode) { checkMode(mode); name = "app_" + name; - File file = makeFilename(getDataDirFile(), name); + File file = makeFilename(getDataDir(), name); if (!file.exists()) { file.mkdir(); setFilePermissionsFromMode(file.getPath(), mode, diff --git a/core/java/android/app/backup/BackupAgent.java b/core/java/android/app/backup/BackupAgent.java index 63f142552c28..aeb315611686 100644 --- a/core/java/android/app/backup/BackupAgent.java +++ b/core/java/android/app/backup/BackupAgent.java @@ -308,14 +308,31 @@ public abstract class BackupAgent extends ContextWrapper { final String packageName = getPackageName(); final ApplicationInfo appInfo = getApplicationInfo(); - String rootDir = new File(appInfo.dataDir).getCanonicalPath(); - String filesDir = getFilesDir().getCanonicalPath(); - String nobackupDir = getNoBackupFilesDir().getCanonicalPath(); - String databaseDir = getDatabasePath("foo").getParentFile().getCanonicalPath(); - String sharedPrefsDir = getSharedPrefsFile("foo").getParentFile().getCanonicalPath(); - String cacheDir = getCacheDir().getCanonicalPath(); - String codeCacheDir = getCodeCacheDir().getCanonicalPath(); - String libDir = (appInfo.nativeLibraryDir != null) + // System apps have control over where their default storage context + // is pointed, so we're always explicit when building paths. + final Context ceContext = createCredentialEncryptedStorageContext(); + final String rootDir = ceContext.getDataDir().getCanonicalPath(); + final String filesDir = ceContext.getFilesDir().getCanonicalPath(); + final String noBackupDir = ceContext.getNoBackupFilesDir().getCanonicalPath(); + final String databaseDir = ceContext.getDatabasePath("foo").getParentFile() + .getCanonicalPath(); + final String sharedPrefsDir = ceContext.getSharedPreferencesPath("foo").getParentFile() + .getCanonicalPath(); + final String cacheDir = ceContext.getCacheDir().getCanonicalPath(); + final String codeCacheDir = ceContext.getCodeCacheDir().getCanonicalPath(); + + final Context deContext = createDeviceEncryptedStorageContext(); + final String deviceRootDir = deContext.getDataDir().getCanonicalPath(); + final String deviceFilesDir = deContext.getFilesDir().getCanonicalPath(); + final String deviceNoBackupDir = deContext.getNoBackupFilesDir().getCanonicalPath(); + final String deviceDatabaseDir = deContext.getDatabasePath("foo").getParentFile() + .getCanonicalPath(); + final String deviceSharedPrefsDir = deContext.getSharedPreferencesPath("foo") + .getParentFile().getCanonicalPath(); + final String deviceCacheDir = deContext.getCacheDir().getCanonicalPath(); + final String deviceCodeCacheDir = deContext.getCodeCacheDir().getCanonicalPath(); + + final String libDir = (appInfo.nativeLibraryDir != null) ? new File(appInfo.nativeLibraryDir).getCanonicalPath() : null; @@ -325,30 +342,48 @@ public abstract class BackupAgent extends ContextWrapper { final ArraySet traversalExcludeSet = new ArraySet(); // Add the directories we always exclude. + traversalExcludeSet.add(filesDir); + traversalExcludeSet.add(noBackupDir); + traversalExcludeSet.add(databaseDir); + traversalExcludeSet.add(sharedPrefsDir); traversalExcludeSet.add(cacheDir); traversalExcludeSet.add(codeCacheDir); - traversalExcludeSet.add(nobackupDir); + + traversalExcludeSet.add(deviceFilesDir); + traversalExcludeSet.add(deviceNoBackupDir); + traversalExcludeSet.add(deviceDatabaseDir); + traversalExcludeSet.add(deviceSharedPrefsDir); + traversalExcludeSet.add(deviceCacheDir); + traversalExcludeSet.add(deviceCodeCacheDir); + if (libDir != null) { traversalExcludeSet.add(libDir); } - traversalExcludeSet.add(databaseDir); - traversalExcludeSet.add(sharedPrefsDir); - traversalExcludeSet.add(filesDir); - // Root dir first. applyXmlFiltersAndDoFullBackupForDomain( packageName, FullBackup.ROOT_TREE_TOKEN, manifestIncludeMap, manifestExcludeSet, traversalExcludeSet, data); traversalExcludeSet.add(rootDir); + applyXmlFiltersAndDoFullBackupForDomain( + packageName, FullBackup.DEVICE_ROOT_TREE_TOKEN, manifestIncludeMap, + manifestExcludeSet, traversalExcludeSet, data); + traversalExcludeSet.add(deviceRootDir); + // Data dir next. traversalExcludeSet.remove(filesDir); applyXmlFiltersAndDoFullBackupForDomain( - packageName, FullBackup.DATA_TREE_TOKEN, manifestIncludeMap, + packageName, FullBackup.FILES_TREE_TOKEN, manifestIncludeMap, manifestExcludeSet, traversalExcludeSet, data); traversalExcludeSet.add(filesDir); + traversalExcludeSet.remove(deviceFilesDir); + applyXmlFiltersAndDoFullBackupForDomain( + packageName, FullBackup.DEVICE_FILES_TREE_TOKEN, manifestIncludeMap, + manifestExcludeSet, traversalExcludeSet, data); + traversalExcludeSet.add(deviceFilesDir); + // Database directory. traversalExcludeSet.remove(databaseDir); applyXmlFiltersAndDoFullBackupForDomain( @@ -356,6 +391,12 @@ public abstract class BackupAgent extends ContextWrapper { manifestExcludeSet, traversalExcludeSet, data); traversalExcludeSet.add(databaseDir); + traversalExcludeSet.remove(deviceDatabaseDir); + applyXmlFiltersAndDoFullBackupForDomain( + packageName, FullBackup.DEVICE_DATABASE_TREE_TOKEN, manifestIncludeMap, + manifestExcludeSet, traversalExcludeSet, data); + traversalExcludeSet.add(deviceDatabaseDir); + // SharedPrefs. traversalExcludeSet.remove(sharedPrefsDir); applyXmlFiltersAndDoFullBackupForDomain( @@ -363,6 +404,12 @@ public abstract class BackupAgent extends ContextWrapper { manifestExcludeSet, traversalExcludeSet, data); traversalExcludeSet.add(sharedPrefsDir); + traversalExcludeSet.remove(deviceSharedPrefsDir); + applyXmlFiltersAndDoFullBackupForDomain( + packageName, FullBackup.DEVICE_SHAREDPREFS_TREE_TOKEN, manifestIncludeMap, + manifestExcludeSet, traversalExcludeSet, data); + traversalExcludeSet.add(deviceSharedPrefsDir); + // getExternalFilesDir() location associated with this app. Technically there should // not be any files here if the app does not properly have permission to access // external storage, but edge cases happen. fullBackupFileTree() catches @@ -445,27 +492,49 @@ public abstract class BackupAgent extends ContextWrapper { */ public final void fullBackupFile(File file, FullBackupDataOutput output) { // 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; - String codeCacheDir; - String libDir; + final String rootDir; + final String filesDir; + final String nbFilesDir; + final String dbDir; + final String spDir; + final String cacheDir; + final String codeCacheDir; + final String deviceRootDir; + final String deviceFilesDir; + final String deviceNbFilesDir; + final String deviceDbDir; + final String deviceSpDir; + final String deviceCacheDir; + final String deviceCodeCacheDir; + final String libDir; + String efDir = null; String filePath; ApplicationInfo appInfo = getApplicationInfo(); 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(); - codeCacheDir = getCodeCacheDir().getCanonicalPath(); + // System apps have control over where their default storage context + // is pointed, so we're always explicit when building paths. + final Context ceContext = createCredentialEncryptedStorageContext(); + rootDir = ceContext.getDataDir().getCanonicalPath(); + filesDir = ceContext.getFilesDir().getCanonicalPath(); + nbFilesDir = ceContext.getNoBackupFilesDir().getCanonicalPath(); + dbDir = ceContext.getDatabasePath("foo").getParentFile().getCanonicalPath(); + spDir = ceContext.getSharedPreferencesPath("foo").getParentFile().getCanonicalPath(); + cacheDir = ceContext.getCacheDir().getCanonicalPath(); + codeCacheDir = ceContext.getCodeCacheDir().getCanonicalPath(); + + final Context deContext = createDeviceEncryptedStorageContext(); + deviceRootDir = deContext.getDataDir().getCanonicalPath(); + deviceFilesDir = deContext.getFilesDir().getCanonicalPath(); + deviceNbFilesDir = deContext.getNoBackupFilesDir().getCanonicalPath(); + deviceDbDir = deContext.getDatabasePath("foo").getParentFile().getCanonicalPath(); + deviceSpDir = deContext.getSharedPreferencesPath("foo").getParentFile() + .getCanonicalPath(); + deviceCacheDir = deContext.getCacheDir().getCanonicalPath(); + deviceCodeCacheDir = deContext.getCodeCacheDir().getCanonicalPath(); + libDir = (appInfo.nativeLibraryDir == null) ? null : new File(appInfo.nativeLibraryDir).getCanonicalPath(); @@ -489,8 +558,11 @@ public abstract class BackupAgent extends ContextWrapper { if (filePath.startsWith(cacheDir) || filePath.startsWith(codeCacheDir) - || filePath.startsWith(libDir) - || filePath.startsWith(nbFilesDir)) { + || filePath.startsWith(nbFilesDir) + || filePath.startsWith(deviceCacheDir) + || filePath.startsWith(deviceCodeCacheDir) + || filePath.startsWith(deviceNbFilesDir) + || filePath.startsWith(libDir)) { Log.w(TAG, "lib, cache, code_cache, and no_backup files are not backed up"); return; } @@ -504,11 +576,23 @@ public abstract class BackupAgent extends ContextWrapper { domain = FullBackup.SHAREDPREFS_TREE_TOKEN; rootpath = spDir; } else if (filePath.startsWith(filesDir)) { - domain = FullBackup.DATA_TREE_TOKEN; + domain = FullBackup.FILES_TREE_TOKEN; rootpath = filesDir; - } else if (filePath.startsWith(mainDir)) { + } else if (filePath.startsWith(rootDir)) { domain = FullBackup.ROOT_TREE_TOKEN; - rootpath = mainDir; + rootpath = rootDir; + } else if (filePath.startsWith(deviceDbDir)) { + domain = FullBackup.DEVICE_DATABASE_TREE_TOKEN; + rootpath = deviceDbDir; + } else if (filePath.startsWith(deviceSpDir)) { + domain = FullBackup.DEVICE_SHAREDPREFS_TREE_TOKEN; + rootpath = deviceSpDir; + } else if (filePath.startsWith(deviceFilesDir)) { + domain = FullBackup.DEVICE_FILES_TREE_TOKEN; + rootpath = deviceFilesDir; + } else if (filePath.startsWith(deviceRootDir)) { + domain = FullBackup.DEVICE_ROOT_TREE_TOKEN; + rootpath = deviceRootDir; } else if ((efDir != null) && filePath.startsWith(efDir)) { domain = FullBackup.MANAGED_EXTERNAL_TREE_TOKEN; rootpath = efDir; diff --git a/core/java/android/app/backup/FullBackup.java b/core/java/android/app/backup/FullBackup.java index 9ea2ba286754..cdc80e3ba3c1 100644 --- a/core/java/android/app/backup/FullBackup.java +++ b/core/java/android/app/backup/FullBackup.java @@ -55,13 +55,22 @@ public class FullBackup { public static final String APK_TREE_TOKEN = "a"; 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 FILES_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 static final String CACHE_TREE_TOKEN = "c"; + + public static final String DEVICE_ROOT_TREE_TOKEN = "d_r"; + public static final String DEVICE_FILES_TREE_TOKEN = "d_f"; + public static final String DEVICE_NO_BACKUP_TREE_TOKEN = "d_nb"; + public static final String DEVICE_DATABASE_TREE_TOKEN = "d_db"; + public static final String DEVICE_SHAREDPREFS_TREE_TOKEN = "d_sp"; + public static final String DEVICE_CACHE_TREE_TOKEN = "d_c"; + + public static final String MANAGED_EXTERNAL_TREE_TOKEN = "ef"; public static final String SHARED_STORAGE_TOKEN = "shared"; public static final String APPS_PREFIX = "apps/"; @@ -201,10 +210,18 @@ public class FullBackup { private final File DATABASE_DIR; private final File ROOT_DIR; private final File SHAREDPREF_DIR; - private final File EXTERNAL_DIR; private final File CACHE_DIR; private final File NOBACKUP_DIR; + private final File DEVICE_FILES_DIR; + private final File DEVICE_DATABASE_DIR; + private final File DEVICE_ROOT_DIR; + private final File DEVICE_SHAREDPREF_DIR; + private final File DEVICE_CACHE_DIR; + private final File DEVICE_NOBACKUP_DIR; + + private final File EXTERNAL_DIR; + final int mFullBackupContent; final PackageManager mPackageManager; final String mPackageName; @@ -214,7 +231,7 @@ public class FullBackup { */ String tokenToDirectoryPath(String domainToken) { try { - if (domainToken.equals(FullBackup.DATA_TREE_TOKEN)) { + if (domainToken.equals(FullBackup.FILES_TREE_TOKEN)) { return FILES_DIR.getCanonicalPath(); } else if (domainToken.equals(FullBackup.DATABASE_TREE_TOKEN)) { return DATABASE_DIR.getCanonicalPath(); @@ -224,14 +241,26 @@ public class FullBackup { return SHAREDPREF_DIR.getCanonicalPath(); } else if (domainToken.equals(FullBackup.CACHE_TREE_TOKEN)) { return CACHE_DIR.getCanonicalPath(); + } else if (domainToken.equals(FullBackup.NO_BACKUP_TREE_TOKEN)) { + return NOBACKUP_DIR.getCanonicalPath(); + } else if (domainToken.equals(FullBackup.DEVICE_FILES_TREE_TOKEN)) { + return DEVICE_FILES_DIR.getCanonicalPath(); + } else if (domainToken.equals(FullBackup.DEVICE_DATABASE_TREE_TOKEN)) { + return DEVICE_DATABASE_DIR.getCanonicalPath(); + } else if (domainToken.equals(FullBackup.DEVICE_ROOT_TREE_TOKEN)) { + return DEVICE_ROOT_DIR.getCanonicalPath(); + } else if (domainToken.equals(FullBackup.DEVICE_SHAREDPREFS_TREE_TOKEN)) { + return DEVICE_SHAREDPREF_DIR.getCanonicalPath(); + } else if (domainToken.equals(FullBackup.DEVICE_CACHE_TREE_TOKEN)) { + return DEVICE_CACHE_DIR.getCanonicalPath(); + } else if (domainToken.equals(FullBackup.DEVICE_NO_BACKUP_TREE_TOKEN)) { + return DEVICE_NOBACKUP_DIR.getCanonicalPath(); } else if (domainToken.equals(FullBackup.MANAGED_EXTERNAL_TREE_TOKEN)) { if (EXTERNAL_DIR != null) { return EXTERNAL_DIR.getCanonicalPath(); } else { return null; } - } else if (domainToken.equals(FullBackup.NO_BACKUP_TREE_TOKEN)) { - return NOBACKUP_DIR.getCanonicalPath(); } // Not a supported location Log.i(TAG, "Unrecognized domain " + domainToken); @@ -257,12 +286,25 @@ public class FullBackup { mFullBackupContent = context.getApplicationInfo().fullBackupContent; mPackageManager = context.getPackageManager(); mPackageName = context.getPackageName(); - FILES_DIR = context.getFilesDir(); - DATABASE_DIR = context.getDatabasePath("foo").getParentFile(); - ROOT_DIR = new File(context.getApplicationInfo().dataDir); - SHAREDPREF_DIR = context.getSharedPrefsFile("foo").getParentFile(); - CACHE_DIR = context.getCacheDir(); - NOBACKUP_DIR = context.getNoBackupFilesDir(); + + // System apps have control over where their default storage context + // is pointed, so we're always explicit when building paths. + final Context ceContext = context.createCredentialEncryptedStorageContext(); + FILES_DIR = ceContext.getFilesDir(); + DATABASE_DIR = ceContext.getDatabasePath("foo").getParentFile(); + ROOT_DIR = ceContext.getDataDir(); + SHAREDPREF_DIR = ceContext.getSharedPreferencesPath("foo").getParentFile(); + CACHE_DIR = ceContext.getCacheDir(); + NOBACKUP_DIR = ceContext.getNoBackupFilesDir(); + + final Context deContext = context.createDeviceEncryptedStorageContext(); + DEVICE_FILES_DIR = deContext.getFilesDir(); + DEVICE_DATABASE_DIR = deContext.getDatabasePath("foo").getParentFile(); + DEVICE_ROOT_DIR = deContext.getDataDir(); + DEVICE_SHAREDPREF_DIR = deContext.getSharedPreferencesPath("foo").getParentFile(); + DEVICE_CACHE_DIR = deContext.getCacheDir(); + DEVICE_NOBACKUP_DIR = deContext.getNoBackupFilesDir(); + if (android.os.Process.myUid() != Process.SYSTEM_UID) { EXTERNAL_DIR = context.getExternalFilesDir(null); } else { @@ -403,6 +445,13 @@ public class FullBackup { Log.v(TAG_XML_PARSER, "...automatically generated " + canonicalJournalPath + ". Ignore if nonexistent."); } + final String canonicalWalPath = + canonicalFile.getCanonicalPath() + "-wal"; + activeSet.add(canonicalWalPath); + if (Log.isLoggable(TAG_XML_PARSER, Log.VERBOSE)) { + Log.v(TAG_XML_PARSER, "...automatically generated " + + canonicalWalPath + ". Ignore if nonexistent."); + } } // Special case for sharedpref files (not dirs) also add ".xml" suffix file. @@ -485,11 +534,19 @@ public class FullBackup { if ("root".equals(xmlDomain)) { return FullBackup.ROOT_TREE_TOKEN; } else if ("file".equals(xmlDomain)) { - return FullBackup.DATA_TREE_TOKEN; + return FullBackup.FILES_TREE_TOKEN; } else if ("database".equals(xmlDomain)) { return FullBackup.DATABASE_TREE_TOKEN; } else if ("sharedpref".equals(xmlDomain)) { return FullBackup.SHAREDPREFS_TREE_TOKEN; + } else if ("device_root".equals(xmlDomain)) { + return FullBackup.DEVICE_ROOT_TREE_TOKEN; + } else if ("device_file".equals(xmlDomain)) { + return FullBackup.DEVICE_FILES_TREE_TOKEN; + } else if ("device_database".equals(xmlDomain)) { + return FullBackup.DEVICE_DATABASE_TREE_TOKEN; + } else if ("device_sharedpref".equals(xmlDomain)) { + return FullBackup.DEVICE_SHAREDPREFS_TREE_TOKEN; } else if ("external".equals(xmlDomain)) { return FullBackup.MANAGED_EXTERNAL_TREE_TOKEN; } else { @@ -542,6 +599,14 @@ public class FullBackup { return ROOT_DIR; } else if ("sharedpref".equals(domain)) { return SHAREDPREF_DIR; + } else if ("device_file".equals(domain)) { + return DEVICE_FILES_DIR; + } else if ("device_database".equals(domain)) { + return DEVICE_DATABASE_DIR; + } else if ("device_root".equals(domain)) { + return DEVICE_ROOT_DIR; + } else if ("device_sharedpref".equals(domain)) { + return DEVICE_SHAREDPREF_DIR; } else if ("external".equals(domain)) { return EXTERNAL_DIR; } else { diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index fff0c14884c0..0cdbef0b770b 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -811,6 +811,25 @@ public abstract class Context { public abstract File getSharedPreferencesPath(String name); /** + * Returns the absolute path to the directory on the filesystem where all + * private files belonging to this app are stored. This is the top-level + * directory under which {@link #getFilesDir()}, {@link #getCacheDir()}, etc + * are contained. Apps should not create any files or directories + * as direct children of this directory, since it's a reserved namespace + * belonging to the platform. Instead, use {@link #getDir(String, int)} or + * other storage APIs. + *

+ * The returned path may change over time if the calling app is moved to an + * adopted storage device, so only relative paths should be persisted. + *

+ * No additional permissions are required for the calling app to read or + * write files under the returned path. + * + * @see #getDir(String, int) + */ + public abstract File getDataDir(); + + /** * Returns the absolute path to the directory on the filesystem where files * created with {@link #openFileOutput} are stored. *

diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java index 61b87a957d2a..323c9bfe8099 100644 --- a/core/java/android/content/ContextWrapper.java +++ b/core/java/android/content/ContextWrapper.java @@ -212,6 +212,11 @@ public class ContextWrapper extends Context { } @Override + public File getDataDir() { + return mBase.getDataDir(); + } + + @Override public File getFilesDir() { return mBase.getFilesDir(); } diff --git a/core/tests/coretests/src/android/app/backup/FullBackupTest.java b/core/tests/coretests/src/android/app/backup/FullBackupTest.java index c3afbf672ae2..3869cd29f69f 100644 --- a/core/tests/coretests/src/android/app/backup/FullBackupTest.java +++ b/core/tests/coretests/src/android/app/backup/FullBackupTest.java @@ -66,7 +66,7 @@ public class FullBackupTest extends AndroidTestCase { assertEquals("Excluding files when there was no tag.", 0, excludesSet.size()); assertEquals("Unexpected number of s", 1, includeMap.size()); - Set fileDomainIncludes = includeMap.get(FullBackup.DATA_TREE_TOKEN); + Set fileDomainIncludes = includeMap.get(FullBackup.FILES_TREE_TOKEN); assertEquals("Didn't find expected file domain include.", 1, fileDomainIncludes.size()); assertEquals("Invalid path parsed for ", new File(mContext.getFilesDir(), "onlyInclude.txt").getCanonicalPath(), @@ -99,7 +99,7 @@ public class FullBackupTest extends AndroidTestCase { FullBackup.BackupScheme bs = FullBackup.getBackupSchemeForTest(mContext); bs.parseBackupSchemeFromXmlLocked(mXpp, excludesSet, includeMap); - Set fileDomainIncludes = includeMap.get(FullBackup.DATA_TREE_TOKEN); + Set fileDomainIncludes = includeMap.get(FullBackup.FILES_TREE_TOKEN); assertEquals("Didn't find expected file domain include.", 1, fileDomainIncludes.size()); assertEquals("Invalid path parsed for ", new File(mContext.getFilesDir(), "include.txt").getCanonicalPath(), @@ -128,15 +128,16 @@ public class FullBackupTest extends AndroidTestCase { FullBackup.BackupScheme bs = FullBackup.getBackupSchemeForTest(mContext); bs.parseBackupSchemeFromXmlLocked(mXpp, excludesSet, includeMap); - Set fileDomainIncludes = includeMap.get(FullBackup.DATA_TREE_TOKEN); + Set fileDomainIncludes = includeMap.get(FullBackup.FILES_TREE_TOKEN); assertEquals("Didn't find expected file domain include.", 1, fileDomainIncludes.size()); assertEquals("Invalid path parsed for ", new File(mContext.getFilesDir(), "include1.txt").getCanonicalPath(), fileDomainIncludes.iterator().next()); Set databaseDomainIncludes = includeMap.get(FullBackup.DATABASE_TREE_TOKEN); + // Three expected here because of "-journal" and "-wal" files assertEquals("Didn't find expected database domain include.", - 2, databaseDomainIncludes.size()); // two expected here because of "-journal" file + 3, databaseDomainIncludes.size()); assertTrue("Invalid path parsed for ", databaseDomainIncludes.contains( new File(mContext.getDatabasePath("foo").getParentFile(), "include2.txt") @@ -147,6 +148,12 @@ public class FullBackupTest extends AndroidTestCase { mContext.getDatabasePath("foo").getParentFile(), "include2.txt-journal") .getCanonicalPath())); + assertTrue("Invalid path parsed for ", + databaseDomainIncludes.contains( + new File( + mContext.getDatabasePath("foo").getParentFile(), + "include2.txt-wal") + .getCanonicalPath())); List sharedPrefDomainIncludes = new ArrayList( includeMap.get(FullBackup.SHAREDPREFS_TREE_TOKEN)); @@ -168,7 +175,7 @@ public class FullBackupTest extends AndroidTestCase { sharedPrefDomainIncludes.get(2)); - assertEquals("Unexpected number of s", 6, excludesSet.size()); + assertEquals("Unexpected number of s", 7, excludesSet.size()); // Sets are annoying to iterate over b/c order isn't enforced - convert to an array and // sort lexicographically. List arrayedSet = new ArrayList(excludesSet); @@ -183,20 +190,24 @@ public class FullBackupTest extends AndroidTestCase { .getCanonicalPath(), arrayedSet.get(1)); assertEquals("Invalid path parsed for ", - new File(mContext.getFilesDir(), "exclude1.txt").getCanonicalPath(), + new File(mContext.getDatabasePath("foo").getParentFile(), "exclude2.txt-wal") + .getCanonicalPath(), arrayedSet.get(2)); assertEquals("Invalid path parsed for ", + new File(mContext.getFilesDir(), "exclude1.txt").getCanonicalPath(), + arrayedSet.get(3)); + assertEquals("Invalid path parsed for ", new File(mContext.getSharedPrefsFile("foo").getParentFile(), "exclude3") .getCanonicalPath(), - arrayedSet.get(3)); + arrayedSet.get(4)); assertEquals("Invalid path parsed for ", new File(mContext.getSharedPrefsFile("foo").getParentFile(), "exclude3.xml") .getCanonicalPath(), - arrayedSet.get(4)); + arrayedSet.get(5)); assertEquals("Invalid path parsed for ", new File(mContext.getSharedPrefsFile("foo").getParentFile(), "exclude4.xml") .getCanonicalPath(), - arrayedSet.get(5)); + arrayedSet.get(6)); } public void testParseBackupSchemeFromXml_invalidXmlFails() throws Exception { @@ -247,7 +258,7 @@ public class FullBackupTest extends AndroidTestCase { assertEquals("Didn't throw away invalid \"..\" path.", 0, includeMap.size()); - Set fileDomainIncludes = includeMap.get(FullBackup.DATA_TREE_TOKEN); + Set fileDomainIncludes = includeMap.get(FullBackup.FILES_TREE_TOKEN); assertNull("Didn't throw away invalid \"..\" path.", fileDomainIncludes); } public void testDoubleDotInPath_isIgnored() throws Exception { @@ -261,7 +272,7 @@ public class FullBackupTest extends AndroidTestCase { assertEquals("Didn't throw away invalid \"..\" path.", 0, includeMap.size()); - Set fileDomainIncludes = includeMap.get(FullBackup.DATA_TREE_TOKEN); + Set fileDomainIncludes = includeMap.get(FullBackup.FILES_TREE_TOKEN); assertNull("Didn't throw away invalid \"..\" path.", fileDomainIncludes); } diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java index eaee1d3e0602..a4fc2ecdc520 100644 --- a/services/backup/java/com/android/server/backup/BackupManagerService.java +++ b/services/backup/java/com/android/server/backup/BackupManagerService.java @@ -191,7 +191,8 @@ public class BackupManagerService { // 1 : initial release // 2 : no format change per se; version bump to facilitate PBKDF2 version skew detection // 3 : introduced "_meta" metadata file; no other format change per se - static final int BACKUP_FILE_VERSION = 3; + // 4 : added support for new device-encrypted storage locations + static final int BACKUP_FILE_VERSION = 4; static final String BACKUP_FILE_HEADER_MAGIC = "ANDROID BACKUP\n"; static final int BACKUP_PW_FILE_VERSION = 2; static final String BACKUP_METADATA_FILENAME = "_meta"; @@ -347,13 +348,13 @@ public class BackupManagerService { } @Override - public void onBootPhase(int phase) { - if (phase == PHASE_SYSTEM_SERVICES_READY) { - sInstance.initialize(UserHandle.USER_SYSTEM); - } else if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) { + public void onUnlockUser(int userId) { + if (userId == UserHandle.USER_SYSTEM) { + sInstance.initialize(userId); + ContentResolver r = sInstance.mContext.getContentResolver(); - boolean areEnabled = Settings.Secure.getInt(r, - Settings.Secure.BACKUP_ENABLED, 0) != 0; + boolean areEnabled = Settings.Secure.getIntForUser(r, + Settings.Secure.BACKUP_ENABLED, 0, userId) != 0; try { sInstance.setBackupEnabled(areEnabled); } catch (RemoteException e) { diff --git a/test-runner/src/android/test/mock/MockContext.java b/test-runner/src/android/test/mock/MockContext.java index bec1e4f76b9b..84cffe17d1d2 100644 --- a/test-runner/src/android/test/mock/MockContext.java +++ b/test-runner/src/android/test/mock/MockContext.java @@ -191,6 +191,11 @@ public class MockContext extends Context { } @Override + public File getDataDir() { + throw new UnsupportedOperationException(); + } + + @Override public File getFilesDir() { throw new UnsupportedOperationException(); } diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java index 4b89217a581e..17ab2ff5cf5e 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java @@ -1382,6 +1382,12 @@ public final class BridgeContext extends Context { } @Override + public File getDataDir() { + // pass + return null; + } + + @Override public File getFilesDir() { // pass return null; -- 2.11.0