am 303650c9: Add full backup criteria to android manifest

* commit '303650c9cdb7cec88e7ec20747b161d9fff10719':
  Add full backup criteria to android manifest
This commit is contained in:
Matthew Williams
2015-05-03 23:30:52 +00:00
committed by Android Git Automerger
11 changed files with 955 additions and 100 deletions

View File

@@ -280,7 +280,7 @@ package android {
field public static final int allowParallelSyncs = 16843570; // 0x1010332
field public static final int allowSingleTap = 16843353; // 0x1010259
field public static final int allowTaskReparenting = 16843268; // 0x1010204
field public static final int allowUndo = 16844005; // 0x10104e5
field public static final int allowUndo = 16844006; // 0x10104e6
field public static final int alpha = 16843551; // 0x101031f
field public static final int alphabeticShortcut = 16843235; // 0x10101e3
field public static final int alwaysDrawnWithCache = 16842991; // 0x10100ef
@@ -301,7 +301,7 @@ package android {
field public static final int anyDensity = 16843372; // 0x101026c
field public static final int apduServiceBanner = 16843757; // 0x10103ed
field public static final int apiKey = 16843281; // 0x1010211
field public static final int assistBlocked = 16844019; // 0x10104f3
field public static final int assistBlocked = 16844020; // 0x10104f4
field public static final int author = 16843444; // 0x10102b4
field public static final int authorities = 16842776; // 0x1010018
field public static final int autoAdvanceViewId = 16843535; // 0x101030f
@@ -312,7 +312,7 @@ package android {
field public static final int autoStart = 16843445; // 0x10102b5
field public static final deprecated int autoText = 16843114; // 0x101016a
field public static final int autoUrlDetect = 16843404; // 0x101028c
field public static final int autoVerify = 16844009; // 0x10104e9
field public static final int autoVerify = 16844010; // 0x10104ea
field public static final int background = 16842964; // 0x10100d4
field public static final int backgroundDimAmount = 16842802; // 0x1010032
field public static final int backgroundDimEnabled = 16843295; // 0x101021f
@@ -336,7 +336,7 @@ package android {
field public static final int bottomRightRadius = 16843180; // 0x10101ac
field public static final int breadCrumbShortTitle = 16843524; // 0x1010304
field public static final int breadCrumbTitle = 16843523; // 0x1010303
field public static final int breakStrategy = 16844010; // 0x10104ea
field public static final int breakStrategy = 16844011; // 0x10104eb
field public static final int bufferType = 16843086; // 0x101014e
field public static final int button = 16843015; // 0x1010107
field public static final int buttonBarButtonStyle = 16843567; // 0x101032f
@@ -398,7 +398,7 @@ package android {
field public static final int colorActivatedHighlight = 16843664; // 0x1010390
field public static final int colorBackground = 16842801; // 0x1010031
field public static final int colorBackgroundCacheHint = 16843435; // 0x10102ab
field public static final int colorBackgroundFloating = 16844006; // 0x10104e6
field public static final int colorBackgroundFloating = 16844007; // 0x10104e7
field public static final int colorButtonNormal = 16843819; // 0x101042b
field public static final int colorControlActivated = 16843818; // 0x101042a
field public static final int colorControlHighlight = 16843820; // 0x101042c
@@ -507,7 +507,7 @@ package android {
field public static final int dropDownWidth = 16843362; // 0x1010262
field public static final int duplicateParentState = 16842985; // 0x10100e9
field public static final int duration = 16843160; // 0x1010198
field public static final int dynamicResources = 16844018; // 0x10104f2
field public static final int dynamicResources = 16844019; // 0x10104f3
field public static final int editTextBackground = 16843602; // 0x1010352
field public static final int editTextColor = 16843601; // 0x1010351
field public static final int editTextPreferenceStyle = 16842898; // 0x1010092
@@ -541,7 +541,7 @@ package android {
field public static final int expandableListViewWhiteStyle = 16843446; // 0x10102b6
field public static final int exported = 16842768; // 0x1010010
field public static final int extraTension = 16843371; // 0x101026b
field public static final int extractNativeLibs = 16844007; // 0x10104e7
field public static final int extractNativeLibs = 16844008; // 0x10104e8
field public static final int factor = 16843219; // 0x10101d3
field public static final int fadeDuration = 16843384; // 0x1010278
field public static final int fadeEnabled = 16843390; // 0x101027e
@@ -610,6 +610,7 @@ package android {
field public static final int fromXScale = 16843202; // 0x10101c2
field public static final int fromYDelta = 16843208; // 0x10101c8
field public static final int fromYScale = 16843204; // 0x10101c4
field public static final int fullBackupContent = 16844005; // 0x10104e5
field public static final int fullBackupOnly = 16843891; // 0x1010473
field public static final int fullBright = 16842954; // 0x10100ca
field public static final int fullDark = 16842950; // 0x10100c6
@@ -796,7 +797,7 @@ package android {
field public static final int layout_x = 16843135; // 0x101017f
field public static final int layout_y = 16843136; // 0x1010180
field public static final int left = 16843181; // 0x10101ad
field public static final int leftIndents = 16844015; // 0x10104ef
field public static final int leftIndents = 16844016; // 0x10104f0
field public static final int letterSpacing = 16843958; // 0x10104b6
field public static final int lineSpacingExtra = 16843287; // 0x1010217
field public static final int lineSpacingMultiplier = 16843288; // 0x1010218
@@ -819,7 +820,7 @@ package android {
field public static final int listSeparatorTextViewStyle = 16843272; // 0x1010208
field public static final int listViewStyle = 16842868; // 0x1010074
field public static final int listViewWhiteStyle = 16842869; // 0x1010075
field public static final int lockTaskMode = 16844014; // 0x10104ee
field public static final int lockTaskMode = 16844015; // 0x10104ef
field public static final int logo = 16843454; // 0x10102be
field public static final int longClickable = 16842982; // 0x10100e6
field public static final int loopViews = 16843527; // 0x1010307
@@ -997,7 +998,7 @@ package android {
field public static final int readPermission = 16842759; // 0x1010007
field public static final int recognitionService = 16843932; // 0x101049c
field public static final int relinquishTaskIdentity = 16843894; // 0x1010476
field public static final int removeBeforeMRelease = 16844013; // 0x10104ed
field public static final int removeBeforeMRelease = 16844014; // 0x10104ee
field public static final int reparent = 16843964; // 0x10104bc
field public static final int reparentWithOverlay = 16843965; // 0x10104bd
field public static final int repeatCount = 16843199; // 0x10101bf
@@ -1025,7 +1026,7 @@ package android {
field public static final int reversible = 16843851; // 0x101044b
field public static final int revisionCode = 16843989; // 0x10104d5
field public static final int right = 16843183; // 0x10101af
field public static final int rightIndents = 16844016; // 0x10104f0
field public static final int rightIndents = 16844017; // 0x10104f1
field public static final int ringtonePreferenceStyle = 16842899; // 0x1010093
field public static final int ringtoneType = 16843257; // 0x10101f9
field public static final int rotation = 16843558; // 0x1010326
@@ -1101,7 +1102,7 @@ package android {
field public static final int showAsAction = 16843481; // 0x10102d9
field public static final int showDefault = 16843258; // 0x10101fa
field public static final int showDividers = 16843561; // 0x1010329
field public static final int showForAllUsers = 16844017; // 0x10104f1
field public static final int showForAllUsers = 16844018; // 0x10104f2
field public static final deprecated int showOnLockScreen = 16843721; // 0x10103c9
field public static final int showSilent = 16843259; // 0x10101fb
field public static final int showText = 16843949; // 0x10104ad
@@ -1173,7 +1174,7 @@ package android {
field public static final int strokeLineJoin = 16843788; // 0x101040c
field public static final int strokeMiterLimit = 16843789; // 0x101040d
field public static final int strokeWidth = 16843783; // 0x1010407
field public static final int stylusButtonPressable = 16844020; // 0x10104f4
field public static final int stylusButtonPressable = 16844021; // 0x10104f5
field public static final int submitBackground = 16843912; // 0x1010488
field public static final int subtitle = 16843473; // 0x10102d1
field public static final int subtitleTextAppearance = 16843823; // 0x101042f
@@ -1188,7 +1189,7 @@ package android {
field public static final int summaryColumn = 16843426; // 0x10102a2
field public static final int summaryOff = 16843248; // 0x10101f0
field public static final int summaryOn = 16843247; // 0x10101ef
field public static final int supportsAssist = 16844011; // 0x10104eb
field public static final int supportsAssist = 16844012; // 0x10104ec
field public static final int supportsRtl = 16843695; // 0x10103af
field public static final int supportsSwitchingToNextInputMethod = 16843755; // 0x10103eb
field public static final int supportsUploading = 16843419; // 0x101029b
@@ -1289,7 +1290,7 @@ package android {
field public static final int thicknessRatio = 16843164; // 0x101019c
field public static final int thumb = 16843074; // 0x1010142
field public static final int thumbOffset = 16843075; // 0x1010143
field public static final int thumbPosition = 16844012; // 0x10104ec
field public static final int thumbPosition = 16844013; // 0x10104ed
field public static final int thumbTextPadding = 16843634; // 0x1010372
field public static final int thumbTint = 16843889; // 0x1010471
field public static final int thumbTintMode = 16843890; // 0x1010472
@@ -1353,7 +1354,7 @@ package android {
field public static final int useIntrinsicSizeAsMinimum = 16843536; // 0x1010310
field public static final int useLevel = 16843167; // 0x101019f
field public static final int userVisible = 16843409; // 0x1010291
field public static final int usesCleartextTraffic = 16844008; // 0x10104e8
field public static final int usesCleartextTraffic = 16844009; // 0x10104e9
field public static final int value = 16842788; // 0x1010024
field public static final int valueFrom = 16843486; // 0x10102de
field public static final int valueTo = 16843487; // 0x10102df
@@ -9010,6 +9011,7 @@ package android.content.pm {
field public int descriptionRes;
field public boolean enabled;
field public int flags;
field public int fullBackupContent;
field public boolean hardwareAccelerated;
field public int largestWidthLimitDp;
field public java.lang.String manageSpaceActivityName;

View File

@@ -351,7 +351,7 @@ package android {
field public static final int allowParallelSyncs = 16843570; // 0x1010332
field public static final int allowSingleTap = 16843353; // 0x1010259
field public static final int allowTaskReparenting = 16843268; // 0x1010204
field public static final int allowUndo = 16844005; // 0x10104e5
field public static final int allowUndo = 16844006; // 0x10104e6
field public static final int alpha = 16843551; // 0x101031f
field public static final int alphabeticShortcut = 16843235; // 0x10101e3
field public static final int alwaysDrawnWithCache = 16842991; // 0x10100ef
@@ -372,7 +372,7 @@ package android {
field public static final int anyDensity = 16843372; // 0x101026c
field public static final int apduServiceBanner = 16843757; // 0x10103ed
field public static final int apiKey = 16843281; // 0x1010211
field public static final int assistBlocked = 16844019; // 0x10104f3
field public static final int assistBlocked = 16844020; // 0x10104f4
field public static final int author = 16843444; // 0x10102b4
field public static final int authorities = 16842776; // 0x1010018
field public static final int autoAdvanceViewId = 16843535; // 0x101030f
@@ -383,7 +383,7 @@ package android {
field public static final int autoStart = 16843445; // 0x10102b5
field public static final deprecated int autoText = 16843114; // 0x101016a
field public static final int autoUrlDetect = 16843404; // 0x101028c
field public static final int autoVerify = 16844009; // 0x10104e9
field public static final int autoVerify = 16844010; // 0x10104ea
field public static final int background = 16842964; // 0x10100d4
field public static final int backgroundDimAmount = 16842802; // 0x1010032
field public static final int backgroundDimEnabled = 16843295; // 0x101021f
@@ -407,7 +407,7 @@ package android {
field public static final int bottomRightRadius = 16843180; // 0x10101ac
field public static final int breadCrumbShortTitle = 16843524; // 0x1010304
field public static final int breadCrumbTitle = 16843523; // 0x1010303
field public static final int breakStrategy = 16844010; // 0x10104ea
field public static final int breakStrategy = 16844011; // 0x10104eb
field public static final int bufferType = 16843086; // 0x101014e
field public static final int button = 16843015; // 0x1010107
field public static final int buttonBarButtonStyle = 16843567; // 0x101032f
@@ -469,7 +469,7 @@ package android {
field public static final int colorActivatedHighlight = 16843664; // 0x1010390
field public static final int colorBackground = 16842801; // 0x1010031
field public static final int colorBackgroundCacheHint = 16843435; // 0x10102ab
field public static final int colorBackgroundFloating = 16844006; // 0x10104e6
field public static final int colorBackgroundFloating = 16844007; // 0x10104e7
field public static final int colorButtonNormal = 16843819; // 0x101042b
field public static final int colorControlActivated = 16843818; // 0x101042a
field public static final int colorControlHighlight = 16843820; // 0x101042c
@@ -578,7 +578,7 @@ package android {
field public static final int dropDownWidth = 16843362; // 0x1010262
field public static final int duplicateParentState = 16842985; // 0x10100e9
field public static final int duration = 16843160; // 0x1010198
field public static final int dynamicResources = 16844018; // 0x10104f2
field public static final int dynamicResources = 16844019; // 0x10104f3
field public static final int editTextBackground = 16843602; // 0x1010352
field public static final int editTextColor = 16843601; // 0x1010351
field public static final int editTextPreferenceStyle = 16842898; // 0x1010092
@@ -612,7 +612,7 @@ package android {
field public static final int expandableListViewWhiteStyle = 16843446; // 0x10102b6
field public static final int exported = 16842768; // 0x1010010
field public static final int extraTension = 16843371; // 0x101026b
field public static final int extractNativeLibs = 16844007; // 0x10104e7
field public static final int extractNativeLibs = 16844008; // 0x10104e8
field public static final int factor = 16843219; // 0x10101d3
field public static final int fadeDuration = 16843384; // 0x1010278
field public static final int fadeEnabled = 16843390; // 0x101027e
@@ -681,6 +681,7 @@ package android {
field public static final int fromXScale = 16843202; // 0x10101c2
field public static final int fromYDelta = 16843208; // 0x10101c8
field public static final int fromYScale = 16843204; // 0x10101c4
field public static final int fullBackupContent = 16844005; // 0x10104e5
field public static final int fullBackupOnly = 16843891; // 0x1010473
field public static final int fullBright = 16842954; // 0x10100ca
field public static final int fullDark = 16842950; // 0x10100c6
@@ -867,7 +868,7 @@ package android {
field public static final int layout_x = 16843135; // 0x101017f
field public static final int layout_y = 16843136; // 0x1010180
field public static final int left = 16843181; // 0x10101ad
field public static final int leftIndents = 16844015; // 0x10104ef
field public static final int leftIndents = 16844016; // 0x10104f0
field public static final int letterSpacing = 16843958; // 0x10104b6
field public static final int lineSpacingExtra = 16843287; // 0x1010217
field public static final int lineSpacingMultiplier = 16843288; // 0x1010218
@@ -890,7 +891,7 @@ package android {
field public static final int listSeparatorTextViewStyle = 16843272; // 0x1010208
field public static final int listViewStyle = 16842868; // 0x1010074
field public static final int listViewWhiteStyle = 16842869; // 0x1010075
field public static final int lockTaskMode = 16844014; // 0x10104ee
field public static final int lockTaskMode = 16844015; // 0x10104ef
field public static final int logo = 16843454; // 0x10102be
field public static final int longClickable = 16842982; // 0x10100e6
field public static final int loopViews = 16843527; // 0x1010307
@@ -1068,7 +1069,7 @@ package android {
field public static final int readPermission = 16842759; // 0x1010007
field public static final int recognitionService = 16843932; // 0x101049c
field public static final int relinquishTaskIdentity = 16843894; // 0x1010476
field public static final int removeBeforeMRelease = 16844013; // 0x10104ed
field public static final int removeBeforeMRelease = 16844014; // 0x10104ee
field public static final int reparent = 16843964; // 0x10104bc
field public static final int reparentWithOverlay = 16843965; // 0x10104bd
field public static final int repeatCount = 16843199; // 0x10101bf
@@ -1096,7 +1097,7 @@ package android {
field public static final int reversible = 16843851; // 0x101044b
field public static final int revisionCode = 16843989; // 0x10104d5
field public static final int right = 16843183; // 0x10101af
field public static final int rightIndents = 16844016; // 0x10104f0
field public static final int rightIndents = 16844017; // 0x10104f1
field public static final int ringtonePreferenceStyle = 16842899; // 0x1010093
field public static final int ringtoneType = 16843257; // 0x10101f9
field public static final int rotation = 16843558; // 0x1010326
@@ -1176,7 +1177,7 @@ package android {
field public static final int showAsAction = 16843481; // 0x10102d9
field public static final int showDefault = 16843258; // 0x10101fa
field public static final int showDividers = 16843561; // 0x1010329
field public static final int showForAllUsers = 16844017; // 0x10104f1
field public static final int showForAllUsers = 16844018; // 0x10104f2
field public static final deprecated int showOnLockScreen = 16843721; // 0x10103c9
field public static final int showSilent = 16843259; // 0x10101fb
field public static final int showText = 16843949; // 0x10104ad
@@ -1248,7 +1249,7 @@ package android {
field public static final int strokeLineJoin = 16843788; // 0x101040c
field public static final int strokeMiterLimit = 16843789; // 0x101040d
field public static final int strokeWidth = 16843783; // 0x1010407
field public static final int stylusButtonPressable = 16844020; // 0x10104f4
field public static final int stylusButtonPressable = 16844021; // 0x10104f5
field public static final int submitBackground = 16843912; // 0x1010488
field public static final int subtitle = 16843473; // 0x10102d1
field public static final int subtitleTextAppearance = 16843823; // 0x101042f
@@ -1263,7 +1264,7 @@ package android {
field public static final int summaryColumn = 16843426; // 0x10102a2
field public static final int summaryOff = 16843248; // 0x10101f0
field public static final int summaryOn = 16843247; // 0x10101ef
field public static final int supportsAssist = 16844011; // 0x10104eb
field public static final int supportsAssist = 16844012; // 0x10104ec
field public static final int supportsRtl = 16843695; // 0x10103af
field public static final int supportsSwitchingToNextInputMethod = 16843755; // 0x10103eb
field public static final int supportsUploading = 16843419; // 0x101029b
@@ -1364,7 +1365,7 @@ package android {
field public static final int thicknessRatio = 16843164; // 0x101019c
field public static final int thumb = 16843074; // 0x1010142
field public static final int thumbOffset = 16843075; // 0x1010143
field public static final int thumbPosition = 16844012; // 0x10104ec
field public static final int thumbPosition = 16844013; // 0x10104ed
field public static final int thumbTextPadding = 16843634; // 0x1010372
field public static final int thumbTint = 16843889; // 0x1010471
field public static final int thumbTintMode = 16843890; // 0x1010472
@@ -1428,7 +1429,7 @@ package android {
field public static final int useIntrinsicSizeAsMinimum = 16843536; // 0x1010310
field public static final int useLevel = 16843167; // 0x101019f
field public static final int userVisible = 16843409; // 0x1010291
field public static final int usesCleartextTraffic = 16844008; // 0x10104e8
field public static final int usesCleartextTraffic = 16844009; // 0x10104e9
field public static final int value = 16842788; // 0x1010024
field public static final int valueFrom = 16843486; // 0x10102de
field public static final int valueTo = 16843487; // 0x10102df
@@ -9239,6 +9240,7 @@ package android.content.pm {
field public int descriptionRes;
field public boolean enabled;
field public int flags;
field public int fullBackupContent;
field public boolean hardwareAccelerated;
field public int largestWidthLimitDp;
field public java.lang.String manageSpaceActivityName;

View File

@@ -33,15 +33,21 @@ import android.system.ErrnoException;
import android.system.Os;
import android.system.OsConstants;
import android.system.StructStat;
import android.util.ArraySet;
import android.util.Log;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.HashSet;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import org.xmlpull.v1.XmlPullParserException;
/**
* Provides the central interface between an
* application and Android's data backup infrastructure. An application that wishes
@@ -164,7 +170,6 @@ public abstract class BackupAgent extends ContextWrapper {
* to do one-time initialization before the actual backup or restore operation
* is begun.
* <p>
* Agents do not need to override this method.
*/
public void onCreate() {
}
@@ -268,19 +273,41 @@ public abstract class BackupAgent extends ContextWrapper {
* 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.
* Alternatively they can provide an xml resource to specify what data to include or exclude.
*
*
* @param data A structured wrapper pointing to the backup destination.
* @throws IOException
*
* @see Context#getNoBackupFilesDir()
* @see ApplicationInfo#fullBackupContent
* @see #fullBackupFile(File, FullBackupDataOutput)
* @see #onRestoreFile(ParcelFileDescriptor, long, File, int, long, long)
*/
public void onFullBackup(FullBackupDataOutput data) throws IOException {
ApplicationInfo appInfo = getApplicationInfo();
FullBackup.BackupScheme backupScheme = FullBackup.getBackupScheme(this);
if (!backupScheme.isFullBackupContentEnabled()) {
return;
}
Map<String, Set<String>> manifestIncludeMap;
ArraySet<String> manifestExcludeSet;
try {
manifestIncludeMap =
backupScheme.maybeParseAndGetCanonicalIncludePaths();
manifestExcludeSet = backupScheme.maybeParseAndGetCanonicalExcludePaths();
} catch (IOException | XmlPullParserException e) {
if (Log.isLoggable(FullBackup.TAG_XML_PARSER, Log.VERBOSE)) {
Log.v(FullBackup.TAG_XML_PARSER,
"Exception trying to parse fullBackupContent xml file!"
+ " Aborting full backup.", e);
}
return;
}
final String packageName = getPackageName();
final 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 nobackupDir = getNoBackupFilesDir().getCanonicalPath();
@@ -292,34 +319,49 @@ public abstract class BackupAgent extends ContextWrapper {
? new File(appInfo.nativeLibraryDir).getCanonicalPath()
: null;
// Filters, the scan queue, and the set of resulting entities
HashSet<String> filterSet = new HashSet<String>();
String packageName = getPackageName();
// Maintain a set of excluded directories so that as we traverse the tree we know we're not
// going places we don't expect, and so the manifest includes can't take precedence over
// what the framework decides is not to be included.
final ArraySet<String> traversalExcludeSet = new ArraySet<String>();
// Okay, start with the app's root tree, but exclude all of the canonical subdirs
// Add the directories we always exclude.
traversalExcludeSet.add(cacheDir);
traversalExcludeSet.add(codeCacheDir);
traversalExcludeSet.add(nobackupDir);
if (libDir != null) {
filterSet.add(libDir);
traversalExcludeSet.add(libDir);
}
filterSet.add(cacheDir);
filterSet.add(codeCacheDir);
filterSet.add(databaseDir);
filterSet.add(sharedPrefsDir);
filterSet.add(filesDir);
filterSet.add(nobackupDir);
fullBackupFileTree(packageName, FullBackup.ROOT_TREE_TOKEN, rootDir, filterSet, data);
// Now do the same for the files dir, db dir, and shared prefs dir
filterSet.add(rootDir);
filterSet.remove(filesDir);
fullBackupFileTree(packageName, FullBackup.DATA_TREE_TOKEN, filesDir, filterSet, data);
traversalExcludeSet.add(databaseDir);
traversalExcludeSet.add(sharedPrefsDir);
traversalExcludeSet.add(filesDir);
filterSet.add(filesDir);
filterSet.remove(databaseDir);
fullBackupFileTree(packageName, FullBackup.DATABASE_TREE_TOKEN, databaseDir, filterSet, data);
// Root dir first.
applyXmlFiltersAndDoFullBackupForDomain(
packageName, FullBackup.ROOT_TREE_TOKEN, manifestIncludeMap,
manifestExcludeSet, traversalExcludeSet, data);
traversalExcludeSet.add(rootDir);
filterSet.add(databaseDir);
filterSet.remove(sharedPrefsDir);
fullBackupFileTree(packageName, FullBackup.SHAREDPREFS_TREE_TOKEN, sharedPrefsDir, filterSet, data);
// Data dir next.
traversalExcludeSet.remove(filesDir);
applyXmlFiltersAndDoFullBackupForDomain(
packageName, FullBackup.DATA_TREE_TOKEN, manifestIncludeMap,
manifestExcludeSet, traversalExcludeSet, data);
traversalExcludeSet.add(filesDir);
// Database directory.
traversalExcludeSet.remove(databaseDir);
applyXmlFiltersAndDoFullBackupForDomain(
packageName, FullBackup.DATABASE_TREE_TOKEN, manifestIncludeMap,
manifestExcludeSet, traversalExcludeSet, data);
traversalExcludeSet.add(databaseDir);
// SharedPrefs.
traversalExcludeSet.remove(sharedPrefsDir);
applyXmlFiltersAndDoFullBackupForDomain(
packageName, FullBackup.SHAREDPREFS_TREE_TOKEN, manifestIncludeMap,
manifestExcludeSet, traversalExcludeSet, data);
traversalExcludeSet.add(sharedPrefsDir);
// getExternalFilesDir() location associated with this app. Technically there should
// not be any files here if the app does not properly have permission to access
@@ -331,8 +373,36 @@ public abstract class BackupAgent extends ContextWrapper {
if (Process.myUid() != Process.SYSTEM_UID) {
File efLocation = getExternalFilesDir(null);
if (efLocation != null) {
fullBackupFileTree(packageName, FullBackup.MANAGED_EXTERNAL_TREE_TOKEN,
efLocation.getCanonicalPath(), null, data);
applyXmlFiltersAndDoFullBackupForDomain(
packageName, FullBackup.MANAGED_EXTERNAL_TREE_TOKEN, manifestIncludeMap,
manifestExcludeSet, traversalExcludeSet, data);
}
}
}
/**
* Check whether the xml yielded any <include/> tag for the provided <code>domainToken</code>.
* If so, perform a {@link #fullBackupFileTree} which backs up the file or recurses if the path
* is a directory.
*/
private void applyXmlFiltersAndDoFullBackupForDomain(String packageName, String domainToken,
Map<String, Set<String>> includeMap,
ArraySet<String> filterSet,
ArraySet<String> traversalExcludeSet,
FullBackupDataOutput data)
throws IOException {
if (includeMap == null || includeMap.size() == 0) {
// Do entire sub-tree for the provided token.
fullBackupFileTree(packageName, domainToken,
FullBackup.getBackupScheme(this).tokenToDirectoryPath(domainToken),
filterSet, traversalExcludeSet, data);
} else if (includeMap.get(domainToken) != null) {
// This will be null if the xml parsing didn't yield any rules for
// this domain (there may still be rules for other domains).
for (String includeFile : includeMap.get(domainToken)) {
fullBackupFileTree(packageName, domainToken, includeFile, filterSet,
traversalExcludeSet, data);
}
}
}
@@ -430,21 +500,31 @@ public abstract class BackupAgent extends ContextWrapper {
// without transmitting any file data.
if (DEBUG) Log.i(TAG, "backupFile() of " + filePath + " => domain=" + domain
+ " rootpath=" + rootpath);
FullBackup.backupToTar(getPackageName(), domain, null, rootpath, filePath, output);
}
/**
* Scan the dir tree (if it actually exists) and process each entry we find. If the
* 'excludes' parameter is non-null, it is consulted each time a new file system entity
* 'excludes' parameters are non-null, they are consulted each time a new file system entity
* is visited to see whether that entity (and its subtree, if appropriate) should be
* omitted from the backup process.
*
* @param systemExcludes An optional list of excludes.
* @hide
*/
protected final void fullBackupFileTree(String packageName, String domain, String rootPath,
HashSet<String> excludes, FullBackupDataOutput output) {
File rootFile = new File(rootPath);
protected final void fullBackupFileTree(String packageName, String domain, String startingPath,
ArraySet<String> manifestExcludes,
ArraySet<String> systemExcludes,
FullBackupDataOutput output) {
// Pull out the domain and set it aside to use when making the tarball.
String domainPath = FullBackup.getBackupScheme(this).tokenToDirectoryPath(domain);
if (domainPath == null) {
// Should never happen.
return;
}
File rootFile = new File(startingPath);
if (rootFile.exists()) {
LinkedList<File> scanQueue = new LinkedList<File>();
scanQueue.add(rootFile);
@@ -456,7 +536,10 @@ public abstract class BackupAgent extends ContextWrapper {
filePath = file.getCanonicalPath();
// prune this subtree?
if (excludes != null && excludes.contains(filePath)) {
if (manifestExcludes != null && manifestExcludes.contains(filePath)) {
continue;
}
if (systemExcludes != null && systemExcludes.contains(filePath)) {
continue;
}
@@ -475,14 +558,20 @@ public abstract class BackupAgent extends ContextWrapper {
}
} catch (IOException e) {
if (DEBUG) Log.w(TAG, "Error canonicalizing path of " + file);
if (Log.isLoggable(FullBackup.TAG_XML_PARSER, Log.VERBOSE)) {
Log.v(FullBackup.TAG_XML_PARSER, "Error canonicalizing path of " + file);
}
continue;
} catch (ErrnoException e) {
if (DEBUG) Log.w(TAG, "Error scanning file " + file + " : " + e);
if (Log.isLoggable(FullBackup.TAG_XML_PARSER, Log.VERBOSE)) {
Log.v(FullBackup.TAG_XML_PARSER, "Error scanning file " + file + " : " + e);
}
continue;
}
// Finally, back this file up (or measure it) before proceeding
FullBackup.backupToTar(packageName, domain, null, rootPath, filePath, output);
FullBackup.backupToTar(packageName, domain, null, domainPath, filePath, output);
}
}
}
@@ -516,9 +605,90 @@ public abstract class BackupAgent extends ContextWrapper {
public void onRestoreFile(ParcelFileDescriptor data, long size,
File destination, int type, long mode, long mtime)
throws IOException {
FullBackup.BackupScheme bs = FullBackup.getBackupScheme(this);
if (!bs.isFullBackupContentEnabled()) {
if (Log.isLoggable(FullBackup.TAG_XML_PARSER, Log.VERBOSE)) {
Log.v(FullBackup.TAG_XML_PARSER,
"onRestoreFile \"" + destination.getCanonicalPath()
+ "\" : fullBackupContent not enabled for " + getPackageName());
}
return;
}
Map<String, Set<String>> includes = null;
ArraySet<String> excludes = null;
final String destinationCanonicalPath = destination.getCanonicalPath();
try {
includes = bs.maybeParseAndGetCanonicalIncludePaths();
excludes = bs.maybeParseAndGetCanonicalExcludePaths();
} catch (XmlPullParserException e) {
if (Log.isLoggable(FullBackup.TAG_XML_PARSER, Log.VERBOSE)) {
Log.v(FullBackup.TAG_XML_PARSER,
"onRestoreFile \"" + destinationCanonicalPath
+ "\" : Exception trying to parse fullBackupContent xml file!"
+ " Aborting onRestoreFile.", e);
}
return;
}
if (excludes != null &&
isFileSpecifiedInPathList(destination, excludes)) {
if (Log.isLoggable(FullBackup.TAG_XML_PARSER, Log.VERBOSE)) {
Log.v(FullBackup.TAG_XML_PARSER,
"onRestoreFile: \"" + destinationCanonicalPath + "\": listed in"
+ " excludes; skipping.");
}
return;
}
if (includes != null && !includes.isEmpty()) {
// Rather than figure out the <include/> domain based on the path (a lot of code, and
// it's a small list), we'll go through and look for it.
boolean explicitlyIncluded = false;
for (Set<String> domainIncludes : includes.values()) {
explicitlyIncluded |= isFileSpecifiedInPathList(destination, domainIncludes);
if (explicitlyIncluded) {
break;
}
}
if (!explicitlyIncluded) {
if (Log.isLoggable(FullBackup.TAG_XML_PARSER, Log.VERBOSE)) {
Log.v(FullBackup.TAG_XML_PARSER,
"onRestoreFile: Trying to restore \""
+ destinationCanonicalPath + "\" but it isn't specified"
+ " in the included files; skipping.");
}
return;
}
}
FullBackup.restoreFile(data, size, type, mode, mtime, destination);
}
/**
* @return True if the provided file is either directly in the provided list, or the provided
* file is within a directory in the list.
*/
private boolean isFileSpecifiedInPathList(File file, Collection<String> canonicalPathList)
throws IOException {
for (String canonicalPath : canonicalPathList) {
File fileFromList = new File(canonicalPath);
if (fileFromList.isDirectory()) {
if (file.isDirectory()) {
// If they are both directories check exact equals.
return file.equals(fileFromList);
} else {
// O/w we have to check if the file is within the directory from the list.
return file.getCanonicalPath().startsWith(canonicalPath);
}
} else {
if (file.equals(fileFromList)) {
// Need to check the explicit "equals" so we don't end up with substrings.
return true;
}
}
}
return false;
}
/**
* Only specialized platform agents should overload this entry point to support
* restores to crazy non-app locations.
@@ -533,31 +703,9 @@ public abstract class BackupAgent extends ContextWrapper {
+ " domain=" + domain + " relpath=" + path + " mode=" + mode
+ " mtime=" + mtime);
// Parse out the semantic domains into the correct physical location
if (domain.equals(FullBackup.DATA_TREE_TOKEN)) {
basePath = getFilesDir().getCanonicalPath();
} else if (domain.equals(FullBackup.DATABASE_TREE_TOKEN)) {
basePath = getDatabasePath("foo").getParentFile().getCanonicalPath();
} else if (domain.equals(FullBackup.ROOT_TREE_TOKEN)) {
basePath = new File(getApplicationInfo().dataDir).getCanonicalPath();
} else if (domain.equals(FullBackup.SHAREDPREFS_TREE_TOKEN)) {
basePath = getSharedPrefsFile("foo").getParentFile().getCanonicalPath();
} else if (domain.equals(FullBackup.CACHE_TREE_TOKEN)) {
basePath = getCacheDir().getCanonicalPath();
} else if (domain.equals(FullBackup.MANAGED_EXTERNAL_TREE_TOKEN)) {
// make sure we can try to restore here before proceeding
if (Process.myUid() != Process.SYSTEM_UID) {
File efLocation = getExternalFilesDir(null);
if (efLocation != null) {
basePath = getExternalFilesDir(null).getCanonicalPath();
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);
basePath = FullBackup.getBackupScheme(this).tokenToDirectoryPath(domain);
if (domain.equals(FullBackup.MANAGED_EXTERNAL_TREE_TOKEN)) {
mode = -1; // < 0 is a token to skip attempting a chmod()
}
// Now that we've figured out where the data goes, send it on its way

View File

@@ -16,16 +16,31 @@
package android.app.backup;
import android.os.ParcelFileDescriptor;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.res.XmlResourceParser;
import android.os.*;
import android.os.Process;
import android.system.ErrnoException;
import android.system.Os;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import org.xmlpull.v1.XmlPullParser;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.xmlpull.v1.XmlPullParserException;
/**
* Global constant definitions et cetera related to the full-backup-to-fd
* binary format. Nothing in this namespace is part of any API; it's all
@@ -35,6 +50,8 @@ import java.io.IOException;
*/
public class FullBackup {
static final String TAG = "FullBackup";
/** Enable this log tag to get verbose information while parsing the client xml. */
static final String TAG_XML_PARSER = "BackupXmlParserLogging";
public static final String APK_TREE_TOKEN = "a";
public static final String OBB_TREE_TOKEN = "obb";
@@ -60,6 +77,27 @@ public class FullBackup {
static public native int backupToTar(String packageName, String domain,
String linkdomain, String rootpath, String path, FullBackupDataOutput output);
private static final Map<String, BackupScheme> kPackageBackupSchemeMap =
new ArrayMap<String, BackupScheme>();
static synchronized BackupScheme getBackupScheme(Context context) {
BackupScheme backupSchemeForPackage =
kPackageBackupSchemeMap.get(context.getPackageName());
if (backupSchemeForPackage == null) {
backupSchemeForPackage = new BackupScheme(context);
kPackageBackupSchemeMap.put(context.getPackageName(), backupSchemeForPackage);
}
return backupSchemeForPackage;
}
public static BackupScheme getBackupSchemeForTest(Context context) {
BackupScheme testing = new BackupScheme(context);
testing.mExcludes = new ArraySet();
testing.mIncludes = new ArrayMap();
return testing;
}
/**
* Copy data from a socket to the given File location on permanent storage. The
* modification time and access mode of the resulting file will be set if desired,
@@ -106,6 +144,8 @@ public class FullBackup {
if (!parent.exists()) {
// in practice this will only be for the default semantic directories,
// and using the default mode for those is appropriate.
// This can also happen for the case where a parent directory has been
// excluded, but a file within that directory has been included.
parent.mkdirs();
}
out = new FileOutputStream(outFile);
@@ -154,4 +194,363 @@ public class FullBackup {
outFile.setLastModified(mtime);
}
}
@VisibleForTesting
public static class BackupScheme {
private final File FILES_DIR;
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;
final int mFullBackupContent;
final PackageManager mPackageManager;
final String mPackageName;
/**
* Parse out the semantic domains into the correct physical location.
*/
String tokenToDirectoryPath(String domainToken) {
try {
if (domainToken.equals(FullBackup.DATA_TREE_TOKEN)) {
return FILES_DIR.getCanonicalPath();
} else if (domainToken.equals(FullBackup.DATABASE_TREE_TOKEN)) {
return DATABASE_DIR.getCanonicalPath();
} else if (domainToken.equals(FullBackup.ROOT_TREE_TOKEN)) {
return ROOT_DIR.getCanonicalPath();
} else if (domainToken.equals(FullBackup.SHAREDPREFS_TREE_TOKEN)) {
return SHAREDPREF_DIR.getCanonicalPath();
} else if (domainToken.equals(FullBackup.CACHE_TREE_TOKEN)) {
return CACHE_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);
return null;
} catch (IOException e) {
Log.i(TAG, "Error reading directory for domain: " + domainToken);
return null;
}
}
/**
* A map of domain -> list of canonical file names in that domain that are to be included.
* We keep track of the domain so that we can go through the file system in order later on.
*/
Map<String, Set<String>> mIncludes;
/**e
* List that will be populated with the canonical names of each file or directory that is
* to be excluded.
*/
ArraySet<String> mExcludes;
BackupScheme(Context context) {
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();
if (android.os.Process.myUid() != Process.SYSTEM_UID) {
EXTERNAL_DIR = context.getExternalFilesDir(null);
} else {
EXTERNAL_DIR = null;
}
}
boolean isFullBackupContentEnabled() {
if (mFullBackupContent < 0) {
// android:fullBackupContent="false", bail.
if (Log.isLoggable(FullBackup.TAG_XML_PARSER, Log.VERBOSE)) {
Log.v(FullBackup.TAG_XML_PARSER, "android:fullBackupContent - \"false\"");
}
return false;
}
return true;
}
/**
* @return A mapping of domain -> canonical paths within that domain. Each of these paths
* specifies a file that the client has explicitly included in their backup set. If this
* map is empty we will back up the entire data directory (including managed external
* storage).
*/
public synchronized Map<String, Set<String>> maybeParseAndGetCanonicalIncludePaths()
throws IOException, XmlPullParserException {
if (mIncludes == null) {
maybeParseBackupSchemeLocked();
}
return mIncludes;
}
/**
* @return A set of canonical paths that are to be excluded from the backup/restore set.
*/
public synchronized ArraySet<String> maybeParseAndGetCanonicalExcludePaths()
throws IOException, XmlPullParserException {
if (mExcludes == null) {
maybeParseBackupSchemeLocked();
}
return mExcludes;
}
private void maybeParseBackupSchemeLocked() throws IOException, XmlPullParserException {
// This not being null is how we know that we've tried to parse the xml already.
mIncludes = new ArrayMap<String, Set<String>>();
mExcludes = new ArraySet<String>();
if (mFullBackupContent == 0) {
// android:fullBackupContent="true" which means that we'll do everything.
if (Log.isLoggable(FullBackup.TAG_XML_PARSER, Log.VERBOSE)) {
Log.v(FullBackup.TAG_XML_PARSER, "android:fullBackupContent - \"true\"");
}
} else {
// android:fullBackupContent="@xml/some_resource".
if (Log.isLoggable(FullBackup.TAG_XML_PARSER, Log.VERBOSE)) {
Log.v(FullBackup.TAG_XML_PARSER,
"android:fullBackupContent - found xml resource");
}
XmlResourceParser parser = null;
try {
parser = mPackageManager
.getResourcesForApplication(mPackageName)
.getXml(mFullBackupContent);
parseBackupSchemeFromXmlLocked(parser, mExcludes, mIncludes);
} catch (PackageManager.NameNotFoundException e) {
// Throw it as an IOException
throw new IOException(e);
} finally {
if (parser != null) {
parser.close();
}
}
}
}
@VisibleForTesting
public void parseBackupSchemeFromXmlLocked(XmlPullParser parser,
Set<String> excludes,
Map<String, Set<String>> includes)
throws IOException, XmlPullParserException {
int event = parser.getEventType(); // START_DOCUMENT
while (event != XmlPullParser.START_TAG) {
event = parser.next();
}
if (!"full-backup-content".equals(parser.getName())) {
throw new XmlPullParserException("Xml file didn't start with correct tag" +
" (<full-backup-content>). Found \"" + parser.getName() + "\"");
}
if (Log.isLoggable(TAG_XML_PARSER, Log.VERBOSE)) {
Log.v(TAG_XML_PARSER, "\n");
Log.v(TAG_XML_PARSER, "====================================================");
Log.v(TAG_XML_PARSER, "Found valid fullBackupContent; parsing xml resource.");
Log.v(TAG_XML_PARSER, "====================================================");
Log.v(TAG_XML_PARSER, "");
}
while ((event = parser.next()) != XmlPullParser.END_DOCUMENT) {
switch (event) {
case XmlPullParser.START_TAG:
validateInnerTagContents(parser);
final String domainFromXml = parser.getAttributeValue(null, "domain");
final File domainDirectory =
getDirectoryForCriteriaDomain(domainFromXml);
if (domainDirectory == null) {
if (Log.isLoggable(TAG_XML_PARSER, Log.VERBOSE)) {
Log.v(TAG_XML_PARSER, "...parsing \"" + parser.getName() + "\": "
+ "domain=\"" + domainFromXml + "\" invalid; skipping");
}
break;
}
final File canonicalFile =
extractCanonicalFile(domainDirectory,
parser.getAttributeValue(null, "path"));
if (canonicalFile == null) {
break;
}
Set<String> activeSet = parseCurrentTagForDomain(
parser, excludes, includes, domainFromXml);
activeSet.add(canonicalFile.getCanonicalPath());
if (Log.isLoggable(TAG_XML_PARSER, Log.VERBOSE)) {
Log.v(TAG_XML_PARSER, "...parsed " + canonicalFile.getCanonicalPath()
+ " for domain \"" + domainFromXml + "\"");
}
// Special case journal files (not dirs) for sqlite database. frowny-face.
// Note that for a restore, the file is never a directory (b/c it doesn't
// exist). We have no way of knowing a priori whether or not to expect a
// dir, so we add the -journal anyway to be safe.
if ("database".equals(domainFromXml) && !canonicalFile.isDirectory()) {
final String canonicalJournalPath =
canonicalFile.getCanonicalPath() + "-journal";
activeSet.add(canonicalJournalPath);
if (Log.isLoggable(TAG_XML_PARSER, Log.VERBOSE)) {
Log.v(TAG_XML_PARSER, "...automatically generated "
+ canonicalJournalPath + ". Ignore if nonexistant.");
}
}
}
}
if (Log.isLoggable(TAG_XML_PARSER, Log.VERBOSE)) {
Log.v(TAG_XML_PARSER, "\n");
Log.v(TAG_XML_PARSER, "Xml resource parsing complete.");
Log.v(TAG_XML_PARSER, "Final tally.");
Log.v(TAG_XML_PARSER, "Includes:");
if (includes.isEmpty()) {
Log.v(TAG_XML_PARSER, " ...nothing specified (This means the entirety of app"
+ " data minus excludes)");
} else {
for (Map.Entry<String, Set<String>> entry : includes.entrySet()) {
Log.v(TAG_XML_PARSER, " domain=" + entry.getKey());
for (String includeData : entry.getValue()) {
Log.v(TAG_XML_PARSER, " " + includeData);
}
}
}
Log.v(TAG_XML_PARSER, "Excludes:");
if (excludes.isEmpty()) {
Log.v(TAG_XML_PARSER, " ...nothing to exclude.");
} else {
for (String excludeData : excludes) {
Log.v(TAG_XML_PARSER, " " + excludeData);
}
}
Log.v(TAG_XML_PARSER, " ");
Log.v(TAG_XML_PARSER, "====================================================");
Log.v(TAG_XML_PARSER, "\n");
}
}
private Set<String> parseCurrentTagForDomain(XmlPullParser parser,
Set<String> excludes,
Map<String, Set<String>> includes,
String domain)
throws XmlPullParserException {
if ("include".equals(parser.getName())) {
final String domainToken = getTokenForXmlDomain(domain);
Set<String> includeSet = includes.get(domainToken);
if (includeSet == null) {
includeSet = new ArraySet<String>();
includes.put(domainToken, includeSet);
}
return includeSet;
} else if ("exclude".equals(parser.getName())) {
return excludes;
} else {
// Unrecognised tag => hard failure.
if (Log.isLoggable(TAG_XML_PARSER, Log.VERBOSE)) {
Log.v(TAG_XML_PARSER, "Invalid tag found in xml \""
+ parser.getName() + "\"; aborting operation.");
}
throw new XmlPullParserException("Unrecognised tag in backup" +
" criteria xml (" + parser.getName() + ")");
}
}
/**
* Map xml specified domain (human-readable, what clients put in their manifest's xml) to
* BackupAgent internal data token.
* @return null if the xml domain was invalid.
*/
private String getTokenForXmlDomain(String xmlDomain) {
if ("root".equals(xmlDomain)) {
return FullBackup.ROOT_TREE_TOKEN;
} else if ("file".equals(xmlDomain)) {
return FullBackup.DATA_TREE_TOKEN;
} else if ("database".equals(xmlDomain)) {
return FullBackup.DATABASE_TREE_TOKEN;
} else if ("sharedpref".equals(xmlDomain)) {
return FullBackup.SHAREDPREFS_TREE_TOKEN;
} else if ("external".equals(xmlDomain)) {
return FullBackup.MANAGED_EXTERNAL_TREE_TOKEN;
} else {
return null;
}
}
/**
*
* @param domain Directory where the specified file should exist. Not null.
* @param filePathFromXml parsed from xml. Not sanitised before calling this function so may be
* null.
* @return The canonical path of the file specified or null if no such file exists.
*/
private File extractCanonicalFile(File domain, String filePathFromXml) {
if (filePathFromXml == null) {
// Allow things like <include domain="sharedpref"/>
filePathFromXml = "";
}
if (filePathFromXml.contains("..")) {
if (Log.isLoggable(TAG_XML_PARSER, Log.VERBOSE)) {
Log.v(TAG_XML_PARSER, "...resolved \"" + domain.getPath() + " " + filePathFromXml
+ "\", but the \"..\" path is not permitted; skipping.");
}
return null;
}
if (filePathFromXml.contains("//")) {
if (Log.isLoggable(TAG_XML_PARSER, Log.VERBOSE)) {
Log.v(TAG_XML_PARSER, "...resolved \"" + domain.getPath() + " " + filePathFromXml
+ "\", which contains the invalid \"//\" sequence; skipping.");
}
return null;
}
return new File(domain, filePathFromXml);
}
/**
* @param domain parsed from xml. Not sanitised before calling this function so may be null.
* @return The directory relevant to the domain specified.
*/
private File getDirectoryForCriteriaDomain(String domain) {
if (TextUtils.isEmpty(domain)) {
return null;
}
if ("file".equals(domain)) {
return FILES_DIR;
} else if ("database".equals(domain)) {
return DATABASE_DIR;
} else if ("root".equals(domain)) {
return ROOT_DIR;
} else if ("sharedpref".equals(domain)) {
return SHAREDPREF_DIR;
} else if ("external".equals(domain)) {
return EXTERNAL_DIR;
} else {
return null;
}
}
/**
* Let's be strict about the type of xml the client can write. If we see anything untoward,
* throw an XmlPullParserException.
*/
private void validateInnerTagContents(XmlPullParser parser)
throws XmlPullParserException {
if (parser.getAttributeCount() > 2) {
throw new XmlPullParserException("At most 2 tag attributes allowed for \""
+ parser.getName() + "\" tag (\"domain\" & \"path\".");
}
if (!"include".equals(parser.getName()) && !"exclude".equals(parser.getName())) {
throw new XmlPullParserException("A valid tag is one of \"<include/>\" or" +
" \"<exclude/>. You provided \"" + parser.getName() + "\"");
}
}
}
}

View File

@@ -95,6 +95,21 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
*/
public String backupAgentName;
/**
* An optional attribute that indicates the app supports automatic backup of app data.
* <p>0 is the default and means the app's entire data folder + managed external storage will
* be backed up;
* Any negative value indicates the app does not support full-data backup, though it may still
* want to participate via the traditional key/value backup API;
* A positive number specifies an xml resource in which the application has defined its backup
* include/exclude criteria.
* <p>If android:allowBackup is set to false, this attribute is ignored.
*
* @see {@link android.content.Context#getNoBackupFilesDir}
* @see {@link #FLAG_ALLOW_BACKUP}
*/
public int fullBackupContent = 0;
/**
* The default extra UI options for activities in this application.
* Set from the {@link android.R.attr#uiOptions} attribute in the
@@ -686,6 +701,11 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
pw.println(prefix + "uiOptions=0x" + Integer.toHexString(uiOptions));
}
pw.println(prefix + "supportsRtl=" + (hasRtlSupport() ? "true" : "false"));
if (fullBackupContent > 0) {
pw.println(prefix + "fullBackupContent=@xml/" + fullBackupContent);
} else {
pw.println(prefix + "fullBackupContent=" + (fullBackupContent < 0 ? "false" : "true"));
}
super.dumpBack(pw, prefix);
}
@@ -763,6 +783,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
uiOptions = orig.uiOptions;
backupAgentName = orig.backupAgentName;
hardwareAccelerated = orig.hardwareAccelerated;
fullBackupContent = orig.fullBackupContent;
}
@@ -816,6 +837,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
dest.writeInt(descriptionRes);
dest.writeInt(uiOptions);
dest.writeInt(hardwareAccelerated ? 1 : 0);
dest.writeInt(fullBackupContent);
}
public static final Parcelable.Creator<ApplicationInfo> CREATOR
@@ -868,6 +890,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
descriptionRes = source.readInt();
uiOptions = source.readInt();
hardwareAccelerated = source.readInt() != 0;
fullBackupContent = source.readInt();
}
/**

View File

@@ -2421,8 +2421,8 @@ public class PackageParser {
if (allowBackup) {
ai.flags |= ApplicationInfo.FLAG_ALLOW_BACKUP;
// backupAgent, killAfterRestore, and restoreAnyVersion are only relevant
// if backup is possible for the given application.
// backupAgent, killAfterRestore, fullBackupContent and restoreAnyVersion are only
// relevant if backup is possible for the given application.
String backupAgent = sa.getNonConfigurationString(
com.android.internal.R.styleable.AndroidManifestApplication_backupAgent,
Configuration.NATIVE_CONFIG_VERSION);
@@ -2449,6 +2449,20 @@ public class PackageParser {
ai.flags |= ApplicationInfo.FLAG_FULL_BACKUP_ONLY;
}
}
TypedValue v = sa.peekValue(
com.android.internal.R.styleable.AndroidManifestApplication_fullBackupContent);
if (v != null && (ai.fullBackupContent = v.resourceId) == 0) {
if (DEBUG_BACKUP) {
Slog.v(TAG, "fullBackupContent specified as boolean=" +
(v.data == 0 ? "false" : "true"));
}
// "false" => -1, "true" => 0
ai.fullBackupContent = (v.data == 0 ? -1 : 0);
}
if (DEBUG_BACKUP) {
Slog.v(TAG, "fullBackupContent=" + ai.fullBackupContent + " for " + pkgName);
}
}
TypedValue v = sa.peekValue(

View File

@@ -842,6 +842,11 @@
via adb. The default value of this attribute is <code>true</code>. -->
<attr name="allowBackup" format="boolean" />
<!-- Applications will set this in their manifest to opt-in to or out of full app data back-up
and restore. Alternatively they can set it to an xml resource within their app that will
be parsed by the BackupAgent to selectively backup files indicated within that xml. -->
<attr name="fullBackupContent" format="reference|boolean" />
<!-- Indicates that even though the application provides a <code>BackupAgent</code>,
only full-data streaming backup operations are to be performed to save the app's
data. This lets the app rely on full-data backups while still participating in
@@ -1189,6 +1194,7 @@
<attr name="backupAgent" />
<attr name="allowBackup" />
<attr name="fullBackupOnly" />
<attr name="fullBackupContent" />
<attr name="killAfterRestore" />
<attr name="restoreNeedsApplication" />
<attr name="restoreAnyVersion" />

View File

@@ -2620,6 +2620,7 @@
<public type="attr" name="overflowTintMode" />
<public type="attr" name="navigationTint" />
<public type="attr" name="navigationTintMode" />
<public type="attr" name="fullBackupContent" />
<public type="style" name="Widget.Material.Button.Colored" />

View File

@@ -0,0 +1,258 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.app.backup;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import android.content.Context;
import android.test.AndroidTestCase;
import android.util.ArrayMap;
import android.util.ArraySet;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlPullParserFactory;
import java.io.File;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class FullBackupTest extends AndroidTestCase {
private XmlPullParserFactory mFactory;
private XmlPullParser mXpp;
private Context mContext;
Map<String, Set<String>> includeMap;
Set<String> excludesSet;
@Override
public void setUp() throws Exception {
mFactory = XmlPullParserFactory.newInstance();
mXpp = mFactory.newPullParser();
mContext = getContext();
includeMap = new ArrayMap();
excludesSet = new ArraySet();
}
public void testparseBackupSchemeFromXml_onlyInclude() throws Exception {
mXpp.setInput(new StringReader(
"<full-backup-content>" +
"<include path=\"onlyInclude.txt\" domain=\"file\"/>" +
"</full-backup-content>"));
FullBackup.BackupScheme bs = FullBackup.getBackupSchemeForTest(mContext);
bs.parseBackupSchemeFromXmlLocked(mXpp, excludesSet, includeMap);
assertEquals("Excluding files when there was no <exclude/> tag.", 0, excludesSet.size());
assertEquals("Unexpected number of <include/>s", 1, includeMap.size());
Set<String> fileDomainIncludes = includeMap.get(FullBackup.DATA_TREE_TOKEN);
assertEquals("Didn't find expected file domain include.", 1, fileDomainIncludes.size());
assertEquals("Invalid path parsed for <include/>",
new File(mContext.getFilesDir(), "onlyInclude.txt").getCanonicalPath(),
fileDomainIncludes.iterator().next());
}
public void testparseBackupSchemeFromXml_onlyExclude() throws Exception {
mXpp.setInput(new StringReader(
"<full-backup-content>" +
"<exclude path=\"onlyExclude.txt\" domain=\"file\"/>" +
"</full-backup-content>"));
FullBackup.BackupScheme bs = FullBackup.getBackupSchemeForTest(mContext);
bs.parseBackupSchemeFromXmlLocked(mXpp, excludesSet, includeMap);
assertEquals("Including files when there was no <include/> tag.", 0, includeMap.size());
assertEquals("Unexpected number of <exclude/>s", 1, excludesSet.size());
assertEquals("Invalid path parsed for <exclude/>",
new File(mContext.getFilesDir(), "onlyExclude.txt").getCanonicalPath(),
excludesSet.iterator().next());
}
public void testparseBackupSchemeFromXml_includeAndExclude() throws Exception {
mXpp.setInput(new StringReader(
"<full-backup-content>" +
"<exclude path=\"exclude.txt\" domain=\"file\"/>" +
"<include path=\"include.txt\" domain=\"file\"/>" +
"</full-backup-content>"));
FullBackup.BackupScheme bs = FullBackup.getBackupSchemeForTest(mContext);
bs.parseBackupSchemeFromXmlLocked(mXpp, excludesSet, includeMap);
Set<String> fileDomainIncludes = includeMap.get(FullBackup.DATA_TREE_TOKEN);
assertEquals("Didn't find expected file domain include.", 1, fileDomainIncludes.size());
assertEquals("Invalid path parsed for <include/>",
new File(mContext.getFilesDir(), "include.txt").getCanonicalPath(),
fileDomainIncludes.iterator().next());
assertEquals("Unexpected number of <exclude/>s", 1, excludesSet.size());
assertEquals("Invalid path parsed for <exclude/>",
new File(mContext.getFilesDir(), "exclude.txt").getCanonicalPath(),
excludesSet.iterator().next());
}
public void testparseBackupSchemeFromXml_lotsOfIncludesAndExcludes() throws Exception {
mXpp.setInput(new StringReader(
"<full-backup-content>" +
"<exclude path=\"exclude1.txt\" domain=\"file\"/>" +
"<include path=\"include1.txt\" domain=\"file\"/>" +
"<exclude path=\"exclude2.txt\" domain=\"database\"/>" +
"<include path=\"include2.txt\" domain=\"database\"/>" +
"<exclude path=\"exclude3.txt\" domain=\"sharedpref\"/>" +
"<include path=\"include3.txt\" domain=\"sharedpref\"/>" +
"</full-backup-content>"));
FullBackup.BackupScheme bs = FullBackup.getBackupSchemeForTest(mContext);
bs.parseBackupSchemeFromXmlLocked(mXpp, excludesSet, includeMap);
Set<String> fileDomainIncludes = includeMap.get(FullBackup.DATA_TREE_TOKEN);
assertEquals("Didn't find expected file domain include.", 1, fileDomainIncludes.size());
assertEquals("Invalid path parsed for <include/>",
new File(mContext.getFilesDir(), "include1.txt").getCanonicalPath(),
fileDomainIncludes.iterator().next());
Set<String> databaseDomainIncludes = includeMap.get(FullBackup.DATABASE_TREE_TOKEN);
assertEquals("Didn't find expected database domain include.",
2, databaseDomainIncludes.size()); // two expected here because of "-journal" file
assertTrue("Invalid path parsed for <include/>",
databaseDomainIncludes.contains(
new File(mContext.getDatabasePath("foo").getParentFile(), "include2.txt")
.getCanonicalPath()));
assertTrue("Invalid path parsed for <include/>",
databaseDomainIncludes.contains(
new File(
mContext.getDatabasePath("foo").getParentFile(),
"include2.txt-journal")
.getCanonicalPath()));
Set<String> sharedPrefDomainIncludes = includeMap.get(FullBackup.SHAREDPREFS_TREE_TOKEN);
assertEquals("Didn't find expected sharedpref domain include.",
1, sharedPrefDomainIncludes.size());
assertEquals("Invalid path parsed for <include/>",
new File(mContext.getSharedPrefsFile("foo").getParentFile(), "include3.txt")
.getCanonicalPath(),
sharedPrefDomainIncludes.iterator().next());
assertEquals("Unexpected number of <exclude/>s", 4, excludesSet.size());
// Sets are annoying to iterate over b/c order isn't enforced - convert to an array and
// sort lexicographically.
List<String> arrayedSet = new ArrayList<String>(excludesSet);
Collections.sort(arrayedSet);
assertEquals("Invalid path parsed for <exclude/>",
new File(mContext.getDatabasePath("foo").getParentFile(), "exclude2.txt")
.getCanonicalPath(),
arrayedSet.get(0));
assertEquals("Invalid path parsed for <exclude/>",
new File(mContext.getDatabasePath("foo").getParentFile(), "exclude2.txt-journal")
.getCanonicalPath(),
arrayedSet.get(1));
assertEquals("Invalid path parsed for <exclude/>",
new File(mContext.getFilesDir(), "exclude1.txt").getCanonicalPath(),
arrayedSet.get(2));
assertEquals("Invalid path parsed for <exclude/>",
new File(mContext.getSharedPrefsFile("foo").getParentFile(), "exclude3.txt")
.getCanonicalPath(),
arrayedSet.get(3));
}
public void testParseBackupSchemeFromXml_invalidXmlFails() throws Exception {
// Invalid root tag.
mXpp.setInput(new StringReader(
"<full-weird-tag>" +
"<exclude path=\"invalidRootTag.txt\" domain=\"file\"/>" +
"</ffull-weird-tag>" ));
try {
FullBackup.BackupScheme bs = FullBackup.getBackupSchemeForTest(mContext);
bs.parseBackupSchemeFromXmlLocked(mXpp, excludesSet, includeMap);
fail("Invalid root xml tag should throw an XmlPullParserException");
} catch (XmlPullParserException expected) {}
// Invalid exclude tag.
mXpp.setInput(new StringReader(
"<full-backup-content>" +
"<excluded path=\"invalidExcludeTag.txt\" domain=\"file\"/>" +
"</full-backup-conten>t" ));
try {
FullBackup.BackupScheme bs = FullBackup.getBackupSchemeForTest(mContext);
bs.parseBackupSchemeFromXmlLocked(mXpp, excludesSet, includeMap);
fail("Misspelled xml exclude tag should throw an XmlPullParserException");
} catch (XmlPullParserException expected) {}
// Just for good measure - invalid include tag.
mXpp.setInput(new StringReader(
"<full-backup-content>" +
"<yinclude path=\"invalidIncludeTag.txt\" domain=\"file\"/>" +
"</full-backup-conten>t" ));
try {
FullBackup.BackupScheme bs = FullBackup.getBackupSchemeForTest(mContext);
bs.parseBackupSchemeFromXmlLocked(mXpp, excludesSet, includeMap);
fail("Misspelled xml exclude tag should throw an XmlPullParserException");
} catch (XmlPullParserException expected) {}
}
public void testInvalidPath_doesNotBackup() throws Exception {
mXpp.setInput(new StringReader(
"<full-backup-content>" +
"<exclude path=\"..\" domain=\"file\"/>" + // Invalid use of ".." dir.
"</full-backup-content>" ));
FullBackup.BackupScheme bs = FullBackup.getBackupSchemeForTest(mContext);
bs.parseBackupSchemeFromXmlLocked(mXpp, excludesSet, includeMap);
assertEquals("Didn't throw away invalid \"..\" path.", 0, includeMap.size());
Set<String> fileDomainIncludes = includeMap.get(FullBackup.DATA_TREE_TOKEN);
assertNull("Didn't throw away invalid \"..\" path.", fileDomainIncludes);
}
public void testDoubleDotInPath_isIgnored() throws Exception {
mXpp.setInput(new StringReader(
"<full-backup-content>" +
"<include path=\"..\" domain=\"file\"/>" + // Invalid use of ".." dir.
"</full-backup-content>" ));
FullBackup.BackupScheme bs = FullBackup.getBackupSchemeForTest(mContext);
bs.parseBackupSchemeFromXmlLocked(mXpp, excludesSet, includeMap);
assertEquals("Didn't throw away invalid \"..\" path.", 0, includeMap.size());
Set<String> fileDomainIncludes = includeMap.get(FullBackup.DATA_TREE_TOKEN);
assertNull("Didn't throw away invalid \"..\" path.", fileDomainIncludes);
}
public void testDoubleSlashInPath_isIgnored() throws Exception {
mXpp.setInput(new StringReader(
"<full-backup-content>" +
"<exclude path=\"//hello.txt\" domain=\"file\"/>" + // Invalid use of "//"
"</full-backup-content>" ));
FullBackup.BackupScheme bs = FullBackup.getBackupSchemeForTest(mContext);
bs.parseBackupSchemeFromXmlLocked(mXpp, excludesSet, includeMap);
assertEquals("Didn't throw away invalid path containing \"//\".", 0, excludesSet.size());
}
}

View File

@@ -8,11 +8,11 @@ import android.os.Environment;
import android.os.ParcelFileDescriptor;
import android.os.storage.StorageManager;
import android.os.storage.StorageVolume;
import android.util.ArraySet;
import android.util.Slog;
import java.io.File;
import java.io.IOException;
import java.util.HashSet;
public class SharedStorageAgent extends FullBackupAgent {
static final String TAG = "SharedStorageAgent";
@@ -42,7 +42,7 @@ public class SharedStorageAgent extends FullBackupAgent {
if (DEBUG) Slog.i(TAG, "Backing up " + mVolumes.length + " shared volumes");
// Ignore all apps' getExternalFilesDir() content; it is backed up as part of
// each app-specific payload.
HashSet<String> externalFilesDirFilter = new HashSet<String>();
ArraySet<String> externalFilesDirFilter = new ArraySet();
final File externalAndroidRoot = new File(Environment.getExternalStorageDirectory(),
Environment.DIRECTORY_ANDROID);
externalFilesDirFilter.add(externalAndroidRoot.getCanonicalPath());
@@ -53,7 +53,9 @@ public class SharedStorageAgent extends FullBackupAgent {
// shared/N/path/to/file
// The restore will then extract to the given volume
String domain = FullBackup.SHARED_PREFIX + i;
fullBackupFileTree(null, domain, v.getPath(), externalFilesDirFilter, output);
fullBackupFileTree(null, domain, v.getPath(),
null /* manifestExcludes */,
externalFilesDirFilter /* systemExcludes */, output);
}
}
}

View File

@@ -160,7 +160,7 @@ import libcore.io.IoUtils;
public class BackupManagerService {
private static final String TAG = "BackupManagerService";
static final boolean DEBUG = true;
static final boolean DEBUG = false;
static final boolean MORE_DEBUG = false;
static final boolean DEBUG_SCHEDULING = MORE_DEBUG || true;