14a627c71ff53a4fca1f961f4b1dcc0461df18a06Christopher Tate/*
24a627c71ff53a4fca1f961f4b1dcc0461df18a06Christopher Tate * Copyright (C) 2011 The Android Open Source Project
34a627c71ff53a4fca1f961f4b1dcc0461df18a06Christopher Tate *
44a627c71ff53a4fca1f961f4b1dcc0461df18a06Christopher Tate * Licensed under the Apache License, Version 2.0 (the "License");
54a627c71ff53a4fca1f961f4b1dcc0461df18a06Christopher Tate * you may not use this file except in compliance with the License.
64a627c71ff53a4fca1f961f4b1dcc0461df18a06Christopher Tate * You may obtain a copy of the License at
74a627c71ff53a4fca1f961f4b1dcc0461df18a06Christopher Tate *
84a627c71ff53a4fca1f961f4b1dcc0461df18a06Christopher Tate *      http://www.apache.org/licenses/LICENSE-2.0
94a627c71ff53a4fca1f961f4b1dcc0461df18a06Christopher Tate *
104a627c71ff53a4fca1f961f4b1dcc0461df18a06Christopher Tate * Unless required by applicable law or agreed to in writing, software
114a627c71ff53a4fca1f961f4b1dcc0461df18a06Christopher Tate * distributed under the License is distributed on an "AS IS" BASIS,
124a627c71ff53a4fca1f961f4b1dcc0461df18a06Christopher Tate * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
134a627c71ff53a4fca1f961f4b1dcc0461df18a06Christopher Tate * See the License for the specific language governing permissions and
144a627c71ff53a4fca1f961f4b1dcc0461df18a06Christopher Tate * limitations under the License.
154a627c71ff53a4fca1f961f4b1dcc0461df18a06Christopher Tate */
164a627c71ff53a4fca1f961f4b1dcc0461df18a06Christopher Tate
174a627c71ff53a4fca1f961f4b1dcc0461df18a06Christopher Tatepackage android.app.backup;
184a627c71ff53a4fca1f961f4b1dcc0461df18a06Christopher Tate
19303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williamsimport android.content.Context;
20303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williamsimport android.content.pm.PackageManager;
21303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williamsimport android.content.res.XmlResourceParser;
228a372a0a280127743ce9a7ce4b6198c7a02d2a4fJeff Sharkeyimport android.os.ParcelFileDescriptor;
23303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williamsimport android.os.Process;
2460af594c3e5834fb710032c2294e36784cdd506fChristopher Tateimport android.os.storage.StorageManager;
2560af594c3e5834fb710032c2294e36784cdd506fChristopher Tateimport android.os.storage.StorageVolume;
2634385d352da19805ae948215e2edbeedd16b7941Elliott Hughesimport android.system.ErrnoException;
2734385d352da19805ae948215e2edbeedd16b7941Elliott Hughesimport android.system.Os;
28303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williamsimport android.text.TextUtils;
29303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williamsimport android.util.ArrayMap;
30303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williamsimport android.util.ArraySet;
3175a99709accef8cf221fd436d646727e7c8dd1f1Christopher Tateimport android.util.Log;
3275a99709accef8cf221fd436d646727e7c8dd1f1Christopher Tate
33303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williamsimport com.android.internal.annotations.VisibleForTesting;
34303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams
35303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williamsimport org.xmlpull.v1.XmlPullParser;
368a372a0a280127743ce9a7ce4b6198c7a02d2a4fJeff Sharkeyimport org.xmlpull.v1.XmlPullParserException;
37303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams
3875a99709accef8cf221fd436d646727e7c8dd1f1Christopher Tateimport java.io.File;
3975a99709accef8cf221fd436d646727e7c8dd1f1Christopher Tateimport java.io.FileInputStream;
4075a99709accef8cf221fd436d646727e7c8dd1f1Christopher Tateimport java.io.FileOutputStream;
4175a99709accef8cf221fd436d646727e7c8dd1f1Christopher Tateimport java.io.IOException;
42303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williamsimport java.util.Map;
43303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williamsimport java.util.Set;
4475a99709accef8cf221fd436d646727e7c8dd1f1Christopher Tate
454a627c71ff53a4fca1f961f4b1dcc0461df18a06Christopher Tate/**
464a627c71ff53a4fca1f961f4b1dcc0461df18a06Christopher Tate * Global constant definitions et cetera related to the full-backup-to-fd
4779ec80db70d788f35aa13346e4684ecbd401bd84Christopher Tate * binary format.  Nothing in this namespace is part of any API; it's all
4879ec80db70d788f35aa13346e4684ecbd401bd84Christopher Tate * hidden details of the current implementation gathered into one location.
494a627c71ff53a4fca1f961f4b1dcc0461df18a06Christopher Tate *
504a627c71ff53a4fca1f961f4b1dcc0461df18a06Christopher Tate * @hide
514a627c71ff53a4fca1f961f4b1dcc0461df18a06Christopher Tate */
524a627c71ff53a4fca1f961f4b1dcc0461df18a06Christopher Tatepublic class FullBackup {
5375a99709accef8cf221fd436d646727e7c8dd1f1Christopher Tate    static final String TAG = "FullBackup";
54303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams    /** Enable this log tag to get verbose information while parsing the client xml. */
55303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams    static final String TAG_XML_PARSER = "BackupXmlParserLogging";
5675a99709accef8cf221fd436d646727e7c8dd1f1Christopher Tate
5775a99709accef8cf221fd436d646727e7c8dd1f1Christopher Tate    public static final String APK_TREE_TOKEN = "a";
5875a99709accef8cf221fd436d646727e7c8dd1f1Christopher Tate    public static final String OBB_TREE_TOKEN = "obb";
592c1ba9a961d4f96c26df260ee437655ad9e7c03eJeff Sharkey
6075a99709accef8cf221fd436d646727e7c8dd1f1Christopher Tate    public static final String ROOT_TREE_TOKEN = "r";
612c1ba9a961d4f96c26df260ee437655ad9e7c03eJeff Sharkey    public static final String FILES_TREE_TOKEN = "f";
62a7835b6b6b00923b608a6bc3194e7840f67de7a8Christopher Tate    public static final String NO_BACKUP_TREE_TOKEN = "nb";
6375a99709accef8cf221fd436d646727e7c8dd1f1Christopher Tate    public static final String DATABASE_TREE_TOKEN = "db";
6475a99709accef8cf221fd436d646727e7c8dd1f1Christopher Tate    public static final String SHAREDPREFS_TREE_TOKEN = "sp";
6575a99709accef8cf221fd436d646727e7c8dd1f1Christopher Tate    public static final String CACHE_TREE_TOKEN = "c";
662c1ba9a961d4f96c26df260ee437655ad9e7c03eJeff Sharkey
672c1ba9a961d4f96c26df260ee437655ad9e7c03eJeff Sharkey    public static final String DEVICE_ROOT_TREE_TOKEN = "d_r";
682c1ba9a961d4f96c26df260ee437655ad9e7c03eJeff Sharkey    public static final String DEVICE_FILES_TREE_TOKEN = "d_f";
692c1ba9a961d4f96c26df260ee437655ad9e7c03eJeff Sharkey    public static final String DEVICE_NO_BACKUP_TREE_TOKEN = "d_nb";
702c1ba9a961d4f96c26df260ee437655ad9e7c03eJeff Sharkey    public static final String DEVICE_DATABASE_TREE_TOKEN = "d_db";
712c1ba9a961d4f96c26df260ee437655ad9e7c03eJeff Sharkey    public static final String DEVICE_SHAREDPREFS_TREE_TOKEN = "d_sp";
722c1ba9a961d4f96c26df260ee437655ad9e7c03eJeff Sharkey    public static final String DEVICE_CACHE_TREE_TOKEN = "d_c";
732c1ba9a961d4f96c26df260ee437655ad9e7c03eJeff Sharkey
742c1ba9a961d4f96c26df260ee437655ad9e7c03eJeff Sharkey    public static final String MANAGED_EXTERNAL_TREE_TOKEN = "ef";
7575a99709accef8cf221fd436d646727e7c8dd1f1Christopher Tate    public static final String SHARED_STORAGE_TOKEN = "shared";
7675a99709accef8cf221fd436d646727e7c8dd1f1Christopher Tate
7775a99709accef8cf221fd436d646727e7c8dd1f1Christopher Tate    public static final String APPS_PREFIX = "apps/";
78b0628bfd5aac480a0d412ac96b8af1d97ac01c30Christopher Tate    public static final String SHARED_PREFIX = SHARED_STORAGE_TOKEN + "/";
7975a99709accef8cf221fd436d646727e7c8dd1f1Christopher Tate
8075a99709accef8cf221fd436d646727e7c8dd1f1Christopher Tate    public static final String FULL_BACKUP_INTENT_ACTION = "fullback";
8175a99709accef8cf221fd436d646727e7c8dd1f1Christopher Tate    public static final String FULL_RESTORE_INTENT_ACTION = "fullrest";
8275a99709accef8cf221fd436d646727e7c8dd1f1Christopher Tate    public static final String CONF_TOKEN_INTENT_EXTRA = "conftoken";
8375a99709accef8cf221fd436d646727e7c8dd1f1Christopher Tate
8479ec80db70d788f35aa13346e4684ecbd401bd84Christopher Tate    /**
8579ec80db70d788f35aa13346e4684ecbd401bd84Christopher Tate     * @hide
8679ec80db70d788f35aa13346e4684ecbd401bd84Christopher Tate     */
874a627c71ff53a4fca1f961f4b1dcc0461df18a06Christopher Tate    static public native int backupToTar(String packageName, String domain,
8811ae768cf1b8348e761ad9c09e98788da1e591b1Christopher Tate            String linkdomain, String rootpath, String path, FullBackupDataOutput output);
8975a99709accef8cf221fd436d646727e7c8dd1f1Christopher Tate
90303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams    private static final Map<String, BackupScheme> kPackageBackupSchemeMap =
91303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams            new ArrayMap<String, BackupScheme>();
92303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams
93303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams    static synchronized BackupScheme getBackupScheme(Context context) {
94303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams        BackupScheme backupSchemeForPackage =
95303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                kPackageBackupSchemeMap.get(context.getPackageName());
96303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams        if (backupSchemeForPackage == null) {
97303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams            backupSchemeForPackage = new BackupScheme(context);
98303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams            kPackageBackupSchemeMap.put(context.getPackageName(), backupSchemeForPackage);
99303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams        }
100303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams        return backupSchemeForPackage;
101303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams    }
102303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams
103303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams    public static BackupScheme getBackupSchemeForTest(Context context) {
104303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams        BackupScheme testing = new BackupScheme(context);
105303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams        testing.mExcludes = new ArraySet();
106303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams        testing.mIncludes = new ArrayMap();
107303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams        return testing;
108303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams    }
109303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams
110303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams
11179ec80db70d788f35aa13346e4684ecbd401bd84Christopher Tate    /**
11279ec80db70d788f35aa13346e4684ecbd401bd84Christopher Tate     * Copy data from a socket to the given File location on permanent storage.  The
113f6d6fa8cbc0251da1900e858bb0379cda5014b6fChristopher Tate     * modification time and access mode of the resulting file will be set if desired,
114f6d6fa8cbc0251da1900e858bb0379cda5014b6fChristopher Tate     * although group/all rwx modes will be stripped: the restored file will not be
115f6d6fa8cbc0251da1900e858bb0379cda5014b6fChristopher Tate     * accessible from outside the target application even if the original file was.
11679ec80db70d788f35aa13346e4684ecbd401bd84Christopher Tate     * If the {@code type} parameter indicates that the result should be a directory,
11779ec80db70d788f35aa13346e4684ecbd401bd84Christopher Tate     * the socket parameter may be {@code null}; even if it is valid, no data will be
11879ec80db70d788f35aa13346e4684ecbd401bd84Christopher Tate     * read from it in this case.
11979ec80db70d788f35aa13346e4684ecbd401bd84Christopher Tate     * <p>
12079ec80db70d788f35aa13346e4684ecbd401bd84Christopher Tate     * If the {@code mode} argument is negative, then the resulting output file will not
12179ec80db70d788f35aa13346e4684ecbd401bd84Christopher Tate     * have its access mode or last modification time reset as part of this operation.
12279ec80db70d788f35aa13346e4684ecbd401bd84Christopher Tate     *
12379ec80db70d788f35aa13346e4684ecbd401bd84Christopher Tate     * @param data Socket supplying the data to be copied to the output file.  If the
12479ec80db70d788f35aa13346e4684ecbd401bd84Christopher Tate     *    output is a directory, this may be {@code null}.
12579ec80db70d788f35aa13346e4684ecbd401bd84Christopher Tate     * @param size Number of bytes of data to copy from the socket to the file.  At least
12679ec80db70d788f35aa13346e4684ecbd401bd84Christopher Tate     *    this much data must be available through the {@code data} parameter.
12779ec80db70d788f35aa13346e4684ecbd401bd84Christopher Tate     * @param type Must be either {@link BackupAgent#TYPE_FILE} for ordinary file data
12879ec80db70d788f35aa13346e4684ecbd401bd84Christopher Tate     *    or {@link BackupAgent#TYPE_DIRECTORY} for a directory.
12979ec80db70d788f35aa13346e4684ecbd401bd84Christopher Tate     * @param mode Unix-style file mode (as used by the chmod(2) syscall) to be set on
130f6d6fa8cbc0251da1900e858bb0379cda5014b6fChristopher Tate     *    the output file or directory.  group/all rwx modes are stripped even if set
131f6d6fa8cbc0251da1900e858bb0379cda5014b6fChristopher Tate     *    in this parameter.  If this parameter is negative then neither
132f6d6fa8cbc0251da1900e858bb0379cda5014b6fChristopher Tate     *    the mode nor the mtime values will be applied to the restored file.
13379ec80db70d788f35aa13346e4684ecbd401bd84Christopher Tate     * @param mtime A timestamp in the standard Unix epoch that will be imposed as the
13479ec80db70d788f35aa13346e4684ecbd401bd84Christopher Tate     *    last modification time of the output file.  if the {@code mode} parameter is
13579ec80db70d788f35aa13346e4684ecbd401bd84Christopher Tate     *    negative then this parameter will be ignored.
13679ec80db70d788f35aa13346e4684ecbd401bd84Christopher Tate     * @param outFile Location within the filesystem to place the data.  This must point
13746cc43c6fa7623820d4ae9149496cf96bb15f8a3Christopher Tate     *    to a location that is writeable by the caller, preferably using an absolute path.
13879ec80db70d788f35aa13346e4684ecbd401bd84Christopher Tate     * @throws IOException
13979ec80db70d788f35aa13346e4684ecbd401bd84Christopher Tate     */
14079ec80db70d788f35aa13346e4684ecbd401bd84Christopher Tate    static public void restoreFile(ParcelFileDescriptor data,
14179ec80db70d788f35aa13346e4684ecbd401bd84Christopher Tate            long size, int type, long mode, long mtime, File outFile) throws IOException {
14279ec80db70d788f35aa13346e4684ecbd401bd84Christopher Tate        if (type == BackupAgent.TYPE_DIRECTORY) {
14375a99709accef8cf221fd436d646727e7c8dd1f1Christopher Tate            // Canonically a directory has no associated content, so we don't need to read
14475a99709accef8cf221fd436d646727e7c8dd1f1Christopher Tate            // anything from the pipe in this case.  Just create the directory here and
14575a99709accef8cf221fd436d646727e7c8dd1f1Christopher Tate            // drop down to the final metadata adjustment.
14675a99709accef8cf221fd436d646727e7c8dd1f1Christopher Tate            if (outFile != null) outFile.mkdirs();
14775a99709accef8cf221fd436d646727e7c8dd1f1Christopher Tate        } else {
14875a99709accef8cf221fd436d646727e7c8dd1f1Christopher Tate            FileOutputStream out = null;
14975a99709accef8cf221fd436d646727e7c8dd1f1Christopher Tate
15075a99709accef8cf221fd436d646727e7c8dd1f1Christopher Tate            // Pull the data from the pipe, copying it to the output file, until we're done
15175a99709accef8cf221fd436d646727e7c8dd1f1Christopher Tate            try {
15275a99709accef8cf221fd436d646727e7c8dd1f1Christopher Tate                if (outFile != null) {
15375a99709accef8cf221fd436d646727e7c8dd1f1Christopher Tate                    File parent = outFile.getParentFile();
15475a99709accef8cf221fd436d646727e7c8dd1f1Christopher Tate                    if (!parent.exists()) {
15575a99709accef8cf221fd436d646727e7c8dd1f1Christopher Tate                        // in practice this will only be for the default semantic directories,
15675a99709accef8cf221fd436d646727e7c8dd1f1Christopher Tate                        // and using the default mode for those is appropriate.
157303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                        // This can also happen for the case where a parent directory has been
158303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                        // excluded, but a file within that directory has been included.
15975a99709accef8cf221fd436d646727e7c8dd1f1Christopher Tate                        parent.mkdirs();
16075a99709accef8cf221fd436d646727e7c8dd1f1Christopher Tate                    }
16175a99709accef8cf221fd436d646727e7c8dd1f1Christopher Tate                    out = new FileOutputStream(outFile);
16275a99709accef8cf221fd436d646727e7c8dd1f1Christopher Tate                }
16375a99709accef8cf221fd436d646727e7c8dd1f1Christopher Tate            } catch (IOException e) {
16475a99709accef8cf221fd436d646727e7c8dd1f1Christopher Tate                Log.e(TAG, "Unable to create/open file " + outFile.getPath(), e);
16575a99709accef8cf221fd436d646727e7c8dd1f1Christopher Tate            }
16675a99709accef8cf221fd436d646727e7c8dd1f1Christopher Tate
16775a99709accef8cf221fd436d646727e7c8dd1f1Christopher Tate            byte[] buffer = new byte[32 * 1024];
16875a99709accef8cf221fd436d646727e7c8dd1f1Christopher Tate            final long origSize = size;
16975a99709accef8cf221fd436d646727e7c8dd1f1Christopher Tate            FileInputStream in = new FileInputStream(data.getFileDescriptor());
17075a99709accef8cf221fd436d646727e7c8dd1f1Christopher Tate            while (size > 0) {
17175a99709accef8cf221fd436d646727e7c8dd1f1Christopher Tate                int toRead = (size > buffer.length) ? buffer.length : (int)size;
17275a99709accef8cf221fd436d646727e7c8dd1f1Christopher Tate                int got = in.read(buffer, 0, toRead);
17375a99709accef8cf221fd436d646727e7c8dd1f1Christopher Tate                if (got <= 0) {
17475a99709accef8cf221fd436d646727e7c8dd1f1Christopher Tate                    Log.w(TAG, "Incomplete read: expected " + size + " but got "
17575a99709accef8cf221fd436d646727e7c8dd1f1Christopher Tate                            + (origSize - size));
17675a99709accef8cf221fd436d646727e7c8dd1f1Christopher Tate                    break;
17775a99709accef8cf221fd436d646727e7c8dd1f1Christopher Tate                }
17875a99709accef8cf221fd436d646727e7c8dd1f1Christopher Tate                if (out != null) {
17975a99709accef8cf221fd436d646727e7c8dd1f1Christopher Tate                    try {
18075a99709accef8cf221fd436d646727e7c8dd1f1Christopher Tate                        out.write(buffer, 0, got);
18175a99709accef8cf221fd436d646727e7c8dd1f1Christopher Tate                    } catch (IOException e) {
18275a99709accef8cf221fd436d646727e7c8dd1f1Christopher Tate                        // Problem writing to the file.  Quit copying data and delete
18375a99709accef8cf221fd436d646727e7c8dd1f1Christopher Tate                        // the file, but of course keep consuming the input stream.
18475a99709accef8cf221fd436d646727e7c8dd1f1Christopher Tate                        Log.e(TAG, "Unable to write to file " + outFile.getPath(), e);
18575a99709accef8cf221fd436d646727e7c8dd1f1Christopher Tate                        out.close();
18675a99709accef8cf221fd436d646727e7c8dd1f1Christopher Tate                        out = null;
18775a99709accef8cf221fd436d646727e7c8dd1f1Christopher Tate                        outFile.delete();
18875a99709accef8cf221fd436d646727e7c8dd1f1Christopher Tate                    }
18975a99709accef8cf221fd436d646727e7c8dd1f1Christopher Tate                }
19075a99709accef8cf221fd436d646727e7c8dd1f1Christopher Tate                size -= got;
19175a99709accef8cf221fd436d646727e7c8dd1f1Christopher Tate            }
19275a99709accef8cf221fd436d646727e7c8dd1f1Christopher Tate            if (out != null) out.close();
19375a99709accef8cf221fd436d646727e7c8dd1f1Christopher Tate        }
19475a99709accef8cf221fd436d646727e7c8dd1f1Christopher Tate
19575a99709accef8cf221fd436d646727e7c8dd1f1Christopher Tate        // Now twiddle the state to match the backup, assuming all went well
19679ec80db70d788f35aa13346e4684ecbd401bd84Christopher Tate        if (mode >= 0 && outFile != null) {
19775a99709accef8cf221fd436d646727e7c8dd1f1Christopher Tate            try {
198f6d6fa8cbc0251da1900e858bb0379cda5014b6fChristopher Tate                // explicitly prevent emplacement of files accessible by outside apps
199f6d6fa8cbc0251da1900e858bb0379cda5014b6fChristopher Tate                mode &= 0700;
20034385d352da19805ae948215e2edbeedd16b7941Elliott Hughes                Os.chmod(outFile.getPath(), (int)mode);
20175a99709accef8cf221fd436d646727e7c8dd1f1Christopher Tate            } catch (ErrnoException e) {
20275a99709accef8cf221fd436d646727e7c8dd1f1Christopher Tate                e.rethrowAsIOException();
20375a99709accef8cf221fd436d646727e7c8dd1f1Christopher Tate            }
20475a99709accef8cf221fd436d646727e7c8dd1f1Christopher Tate            outFile.setLastModified(mtime);
20575a99709accef8cf221fd436d646727e7c8dd1f1Christopher Tate        }
20675a99709accef8cf221fd436d646727e7c8dd1f1Christopher Tate    }
207303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams
208303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams    @VisibleForTesting
209303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams    public static class BackupScheme {
210303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams        private final File FILES_DIR;
211303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams        private final File DATABASE_DIR;
212303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams        private final File ROOT_DIR;
213303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams        private final File SHAREDPREF_DIR;
214303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams        private final File CACHE_DIR;
215303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams        private final File NOBACKUP_DIR;
216303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams
2172c1ba9a961d4f96c26df260ee437655ad9e7c03eJeff Sharkey        private final File DEVICE_FILES_DIR;
2182c1ba9a961d4f96c26df260ee437655ad9e7c03eJeff Sharkey        private final File DEVICE_DATABASE_DIR;
2192c1ba9a961d4f96c26df260ee437655ad9e7c03eJeff Sharkey        private final File DEVICE_ROOT_DIR;
2202c1ba9a961d4f96c26df260ee437655ad9e7c03eJeff Sharkey        private final File DEVICE_SHAREDPREF_DIR;
2212c1ba9a961d4f96c26df260ee437655ad9e7c03eJeff Sharkey        private final File DEVICE_CACHE_DIR;
2222c1ba9a961d4f96c26df260ee437655ad9e7c03eJeff Sharkey        private final File DEVICE_NOBACKUP_DIR;
2232c1ba9a961d4f96c26df260ee437655ad9e7c03eJeff Sharkey
2242c1ba9a961d4f96c26df260ee437655ad9e7c03eJeff Sharkey        private final File EXTERNAL_DIR;
2252c1ba9a961d4f96c26df260ee437655ad9e7c03eJeff Sharkey
226303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams        final int mFullBackupContent;
227303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams        final PackageManager mPackageManager;
22860af594c3e5834fb710032c2294e36784cdd506fChristopher Tate        final StorageManager mStorageManager;
229303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams        final String mPackageName;
230303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams
23160af594c3e5834fb710032c2294e36784cdd506fChristopher Tate        // lazy initialized, only when needed
23260af594c3e5834fb710032c2294e36784cdd506fChristopher Tate        private StorageVolume[] mVolumes = null;
23360af594c3e5834fb710032c2294e36784cdd506fChristopher Tate
234303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams        /**
235303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams         * Parse out the semantic domains into the correct physical location.
236303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams         */
237303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams        String tokenToDirectoryPath(String domainToken) {
238303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams            try {
2392c1ba9a961d4f96c26df260ee437655ad9e7c03eJeff Sharkey                if (domainToken.equals(FullBackup.FILES_TREE_TOKEN)) {
240303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                    return FILES_DIR.getCanonicalPath();
241303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                } else if (domainToken.equals(FullBackup.DATABASE_TREE_TOKEN)) {
242303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                    return DATABASE_DIR.getCanonicalPath();
243303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                } else if (domainToken.equals(FullBackup.ROOT_TREE_TOKEN)) {
244303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                    return ROOT_DIR.getCanonicalPath();
245303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                } else if (domainToken.equals(FullBackup.SHAREDPREFS_TREE_TOKEN)) {
246303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                    return SHAREDPREF_DIR.getCanonicalPath();
247303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                } else if (domainToken.equals(FullBackup.CACHE_TREE_TOKEN)) {
248303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                    return CACHE_DIR.getCanonicalPath();
2492c1ba9a961d4f96c26df260ee437655ad9e7c03eJeff Sharkey                } else if (domainToken.equals(FullBackup.NO_BACKUP_TREE_TOKEN)) {
2502c1ba9a961d4f96c26df260ee437655ad9e7c03eJeff Sharkey                    return NOBACKUP_DIR.getCanonicalPath();
2512c1ba9a961d4f96c26df260ee437655ad9e7c03eJeff Sharkey                } else if (domainToken.equals(FullBackup.DEVICE_FILES_TREE_TOKEN)) {
2522c1ba9a961d4f96c26df260ee437655ad9e7c03eJeff Sharkey                    return DEVICE_FILES_DIR.getCanonicalPath();
2532c1ba9a961d4f96c26df260ee437655ad9e7c03eJeff Sharkey                } else if (domainToken.equals(FullBackup.DEVICE_DATABASE_TREE_TOKEN)) {
2542c1ba9a961d4f96c26df260ee437655ad9e7c03eJeff Sharkey                    return DEVICE_DATABASE_DIR.getCanonicalPath();
2552c1ba9a961d4f96c26df260ee437655ad9e7c03eJeff Sharkey                } else if (domainToken.equals(FullBackup.DEVICE_ROOT_TREE_TOKEN)) {
2562c1ba9a961d4f96c26df260ee437655ad9e7c03eJeff Sharkey                    return DEVICE_ROOT_DIR.getCanonicalPath();
2572c1ba9a961d4f96c26df260ee437655ad9e7c03eJeff Sharkey                } else if (domainToken.equals(FullBackup.DEVICE_SHAREDPREFS_TREE_TOKEN)) {
2582c1ba9a961d4f96c26df260ee437655ad9e7c03eJeff Sharkey                    return DEVICE_SHAREDPREF_DIR.getCanonicalPath();
2592c1ba9a961d4f96c26df260ee437655ad9e7c03eJeff Sharkey                } else if (domainToken.equals(FullBackup.DEVICE_CACHE_TREE_TOKEN)) {
2602c1ba9a961d4f96c26df260ee437655ad9e7c03eJeff Sharkey                    return DEVICE_CACHE_DIR.getCanonicalPath();
2612c1ba9a961d4f96c26df260ee437655ad9e7c03eJeff Sharkey                } else if (domainToken.equals(FullBackup.DEVICE_NO_BACKUP_TREE_TOKEN)) {
2622c1ba9a961d4f96c26df260ee437655ad9e7c03eJeff Sharkey                    return DEVICE_NOBACKUP_DIR.getCanonicalPath();
263303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                } else if (domainToken.equals(FullBackup.MANAGED_EXTERNAL_TREE_TOKEN)) {
264303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                    if (EXTERNAL_DIR != null) {
265303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                        return EXTERNAL_DIR.getCanonicalPath();
266303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                    } else {
267303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                        return null;
268303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                    }
26960af594c3e5834fb710032c2294e36784cdd506fChristopher Tate                } else if (domainToken.startsWith(FullBackup.SHARED_PREFIX)) {
27060af594c3e5834fb710032c2294e36784cdd506fChristopher Tate                    return sharedDomainToPath(domainToken);
271303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                }
272303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                // Not a supported location
273303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                Log.i(TAG, "Unrecognized domain " + domainToken);
274303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                return null;
27560af594c3e5834fb710032c2294e36784cdd506fChristopher Tate            } catch (Exception e) {
276303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                Log.i(TAG, "Error reading directory for domain: " + domainToken);
277303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                return null;
278303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams            }
279303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams
280303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams        }
28160af594c3e5834fb710032c2294e36784cdd506fChristopher Tate
28260af594c3e5834fb710032c2294e36784cdd506fChristopher Tate        private String sharedDomainToPath(String domain) throws IOException {
28360af594c3e5834fb710032c2294e36784cdd506fChristopher Tate            // already known to start with SHARED_PREFIX, so we just look after that
28460af594c3e5834fb710032c2294e36784cdd506fChristopher Tate            final String volume = domain.substring(FullBackup.SHARED_PREFIX.length());
28560af594c3e5834fb710032c2294e36784cdd506fChristopher Tate            final StorageVolume[] volumes = getVolumeList();
28660af594c3e5834fb710032c2294e36784cdd506fChristopher Tate            final int volNum = Integer.parseInt(volume);
28760af594c3e5834fb710032c2294e36784cdd506fChristopher Tate            if (volNum < mVolumes.length) {
28860af594c3e5834fb710032c2294e36784cdd506fChristopher Tate                return volumes[volNum].getPathFile().getCanonicalPath();
28960af594c3e5834fb710032c2294e36784cdd506fChristopher Tate            }
29060af594c3e5834fb710032c2294e36784cdd506fChristopher Tate            return null;
29160af594c3e5834fb710032c2294e36784cdd506fChristopher Tate        }
29260af594c3e5834fb710032c2294e36784cdd506fChristopher Tate
29360af594c3e5834fb710032c2294e36784cdd506fChristopher Tate        private StorageVolume[] getVolumeList() {
29460af594c3e5834fb710032c2294e36784cdd506fChristopher Tate            if (mStorageManager != null) {
29560af594c3e5834fb710032c2294e36784cdd506fChristopher Tate                if (mVolumes == null) {
29660af594c3e5834fb710032c2294e36784cdd506fChristopher Tate                    mVolumes = mStorageManager.getVolumeList();
29760af594c3e5834fb710032c2294e36784cdd506fChristopher Tate                }
29860af594c3e5834fb710032c2294e36784cdd506fChristopher Tate            } else {
29960af594c3e5834fb710032c2294e36784cdd506fChristopher Tate                Log.e(TAG, "Unable to access Storage Manager");
30060af594c3e5834fb710032c2294e36784cdd506fChristopher Tate            }
30160af594c3e5834fb710032c2294e36784cdd506fChristopher Tate            return mVolumes;
30260af594c3e5834fb710032c2294e36784cdd506fChristopher Tate        }
30360af594c3e5834fb710032c2294e36784cdd506fChristopher Tate
304303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams        /**
305303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams        * A map of domain -> list of canonical file names in that domain that are to be included.
306303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams        * We keep track of the domain so that we can go through the file system in order later on.
307303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams        */
308303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams        Map<String, Set<String>> mIncludes;
309303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams        /**e
310303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams         * List that will be populated with the canonical names of each file or directory that is
311303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams         * to be excluded.
312303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams         */
313303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams        ArraySet<String> mExcludes;
314303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams
315303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams        BackupScheme(Context context) {
316303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams            mFullBackupContent = context.getApplicationInfo().fullBackupContent;
31760af594c3e5834fb710032c2294e36784cdd506fChristopher Tate            mStorageManager = (StorageManager) context.getSystemService(Context.STORAGE_SERVICE);
318303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams            mPackageManager = context.getPackageManager();
319303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams            mPackageName = context.getPackageName();
3202c1ba9a961d4f96c26df260ee437655ad9e7c03eJeff Sharkey
3212c1ba9a961d4f96c26df260ee437655ad9e7c03eJeff Sharkey            // System apps have control over where their default storage context
3222c1ba9a961d4f96c26df260ee437655ad9e7c03eJeff Sharkey            // is pointed, so we're always explicit when building paths.
3238a372a0a280127743ce9a7ce4b6198c7a02d2a4fJeff Sharkey            final Context ceContext = context.createCredentialProtectedStorageContext();
3242c1ba9a961d4f96c26df260ee437655ad9e7c03eJeff Sharkey            FILES_DIR = ceContext.getFilesDir();
3252c1ba9a961d4f96c26df260ee437655ad9e7c03eJeff Sharkey            DATABASE_DIR = ceContext.getDatabasePath("foo").getParentFile();
3262c1ba9a961d4f96c26df260ee437655ad9e7c03eJeff Sharkey            ROOT_DIR = ceContext.getDataDir();
3272c1ba9a961d4f96c26df260ee437655ad9e7c03eJeff Sharkey            SHAREDPREF_DIR = ceContext.getSharedPreferencesPath("foo").getParentFile();
3282c1ba9a961d4f96c26df260ee437655ad9e7c03eJeff Sharkey            CACHE_DIR = ceContext.getCacheDir();
3292c1ba9a961d4f96c26df260ee437655ad9e7c03eJeff Sharkey            NOBACKUP_DIR = ceContext.getNoBackupFilesDir();
3302c1ba9a961d4f96c26df260ee437655ad9e7c03eJeff Sharkey
3318a372a0a280127743ce9a7ce4b6198c7a02d2a4fJeff Sharkey            final Context deContext = context.createDeviceProtectedStorageContext();
3322c1ba9a961d4f96c26df260ee437655ad9e7c03eJeff Sharkey            DEVICE_FILES_DIR = deContext.getFilesDir();
3332c1ba9a961d4f96c26df260ee437655ad9e7c03eJeff Sharkey            DEVICE_DATABASE_DIR = deContext.getDatabasePath("foo").getParentFile();
3342c1ba9a961d4f96c26df260ee437655ad9e7c03eJeff Sharkey            DEVICE_ROOT_DIR = deContext.getDataDir();
3352c1ba9a961d4f96c26df260ee437655ad9e7c03eJeff Sharkey            DEVICE_SHAREDPREF_DIR = deContext.getSharedPreferencesPath("foo").getParentFile();
3362c1ba9a961d4f96c26df260ee437655ad9e7c03eJeff Sharkey            DEVICE_CACHE_DIR = deContext.getCacheDir();
3372c1ba9a961d4f96c26df260ee437655ad9e7c03eJeff Sharkey            DEVICE_NOBACKUP_DIR = deContext.getNoBackupFilesDir();
3382c1ba9a961d4f96c26df260ee437655ad9e7c03eJeff Sharkey
339303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams            if (android.os.Process.myUid() != Process.SYSTEM_UID) {
340303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                EXTERNAL_DIR = context.getExternalFilesDir(null);
341303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams            } else {
342303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                EXTERNAL_DIR = null;
343303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams            }
344303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams        }
345303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams
346303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams        boolean isFullBackupContentEnabled() {
347303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams            if (mFullBackupContent < 0) {
348303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                // android:fullBackupContent="false", bail.
349303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                if (Log.isLoggable(FullBackup.TAG_XML_PARSER, Log.VERBOSE)) {
350303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                    Log.v(FullBackup.TAG_XML_PARSER, "android:fullBackupContent - \"false\"");
351303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                }
352303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                return false;
353303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams            }
354303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams            return true;
355303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams        }
356303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams
357303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams        /**
358303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams         * @return A mapping of domain -> canonical paths within that domain. Each of these paths
359303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams         * specifies a file that the client has explicitly included in their backup set. If this
360303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams         * map is empty we will back up the entire data directory (including managed external
361303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams         * storage).
362303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams         */
363303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams        public synchronized Map<String, Set<String>> maybeParseAndGetCanonicalIncludePaths()
364303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                throws IOException, XmlPullParserException {
365303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams            if (mIncludes == null) {
366303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                maybeParseBackupSchemeLocked();
367303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams            }
368303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams            return mIncludes;
369303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams        }
370303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams
371303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams        /**
372303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams         * @return A set of canonical paths that are to be excluded from the backup/restore set.
373303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams         */
374303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams        public synchronized ArraySet<String> maybeParseAndGetCanonicalExcludePaths()
375303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                throws IOException, XmlPullParserException {
376303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams            if (mExcludes == null) {
377303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                maybeParseBackupSchemeLocked();
378303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams            }
379303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams            return mExcludes;
380303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams        }
381303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams
382303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams        private void maybeParseBackupSchemeLocked() throws IOException, XmlPullParserException {
383303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams            // This not being null is how we know that we've tried to parse the xml already.
384303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams            mIncludes = new ArrayMap<String, Set<String>>();
385303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams            mExcludes = new ArraySet<String>();
386303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams
387303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams            if (mFullBackupContent == 0) {
388303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                // android:fullBackupContent="true" which means that we'll do everything.
389303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                if (Log.isLoggable(FullBackup.TAG_XML_PARSER, Log.VERBOSE)) {
390303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                    Log.v(FullBackup.TAG_XML_PARSER, "android:fullBackupContent - \"true\"");
391303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                }
392303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams            } else {
393303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                // android:fullBackupContent="@xml/some_resource".
394303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                if (Log.isLoggable(FullBackup.TAG_XML_PARSER, Log.VERBOSE)) {
395303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                    Log.v(FullBackup.TAG_XML_PARSER,
396303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                            "android:fullBackupContent - found xml resource");
397303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                }
398303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                XmlResourceParser parser = null;
399303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                try {
400303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                    parser = mPackageManager
401303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                            .getResourcesForApplication(mPackageName)
402303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                            .getXml(mFullBackupContent);
403303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                    parseBackupSchemeFromXmlLocked(parser, mExcludes, mIncludes);
404303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                } catch (PackageManager.NameNotFoundException e) {
405303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                    // Throw it as an IOException
406303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                    throw new IOException(e);
407303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                } finally {
408303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                    if (parser != null) {
409303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                        parser.close();
410303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                    }
411303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                }
412303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams            }
413303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams        }
414303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams
415303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams        @VisibleForTesting
416303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams        public void parseBackupSchemeFromXmlLocked(XmlPullParser parser,
417303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                                                   Set<String> excludes,
418303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                                                   Map<String, Set<String>> includes)
419303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                throws IOException, XmlPullParserException {
420303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams            int event = parser.getEventType(); // START_DOCUMENT
421303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams            while (event != XmlPullParser.START_TAG) {
422303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                event = parser.next();
423303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams            }
424303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams
425303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams            if (!"full-backup-content".equals(parser.getName())) {
426303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                throw new XmlPullParserException("Xml file didn't start with correct tag" +
427303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                        " (<full-backup-content>). Found \"" + parser.getName() + "\"");
428303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams            }
429303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams
430303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams            if (Log.isLoggable(TAG_XML_PARSER, Log.VERBOSE)) {
431303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                Log.v(TAG_XML_PARSER, "\n");
432303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                Log.v(TAG_XML_PARSER, "====================================================");
433303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                Log.v(TAG_XML_PARSER, "Found valid fullBackupContent; parsing xml resource.");
434303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                Log.v(TAG_XML_PARSER, "====================================================");
435303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                Log.v(TAG_XML_PARSER, "");
436303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams            }
437303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams
438303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams            while ((event = parser.next()) != XmlPullParser.END_DOCUMENT) {
439303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                switch (event) {
440303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                    case XmlPullParser.START_TAG:
441303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                        validateInnerTagContents(parser);
442303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                        final String domainFromXml = parser.getAttributeValue(null, "domain");
443303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                        final File domainDirectory =
444303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                                getDirectoryForCriteriaDomain(domainFromXml);
445303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                        if (domainDirectory == null) {
446303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                            if (Log.isLoggable(TAG_XML_PARSER, Log.VERBOSE)) {
447303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                                Log.v(TAG_XML_PARSER, "...parsing \"" + parser.getName() + "\": "
448303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                                        + "domain=\"" + domainFromXml + "\" invalid; skipping");
449303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                            }
450303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                            break;
451303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                        }
452303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                        final File canonicalFile =
453303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                                extractCanonicalFile(domainDirectory,
454303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                                        parser.getAttributeValue(null, "path"));
455303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                        if (canonicalFile == null) {
456303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                            break;
457303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                        }
458303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams
459303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                        Set<String> activeSet = parseCurrentTagForDomain(
460303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                                parser, excludes, includes, domainFromXml);
461303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                        activeSet.add(canonicalFile.getCanonicalPath());
462303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                        if (Log.isLoggable(TAG_XML_PARSER, Log.VERBOSE)) {
463303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                            Log.v(TAG_XML_PARSER, "...parsed " + canonicalFile.getCanonicalPath()
464303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                                    + " for domain \"" + domainFromXml + "\"");
465303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                        }
466303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams
467303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                        // Special case journal files (not dirs) for sqlite database. frowny-face.
468303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                        // Note that for a restore, the file is never a directory (b/c it doesn't
469303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                        // exist). We have no way of knowing a priori whether or not to expect a
470303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                        // dir, so we add the -journal anyway to be safe.
471303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                        if ("database".equals(domainFromXml) && !canonicalFile.isDirectory()) {
472303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                            final String canonicalJournalPath =
473303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                                    canonicalFile.getCanonicalPath() + "-journal";
474303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                            activeSet.add(canonicalJournalPath);
475303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                            if (Log.isLoggable(TAG_XML_PARSER, Log.VERBOSE)) {
476303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                                Log.v(TAG_XML_PARSER, "...automatically generated "
47780e8db3d9cbdd6f42ca83b535abd26d761dc26eeDmitry Polukhin                                        + canonicalJournalPath + ". Ignore if nonexistent.");
47880e8db3d9cbdd6f42ca83b535abd26d761dc26eeDmitry Polukhin                            }
4792c1ba9a961d4f96c26df260ee437655ad9e7c03eJeff Sharkey                            final String canonicalWalPath =
4802c1ba9a961d4f96c26df260ee437655ad9e7c03eJeff Sharkey                                    canonicalFile.getCanonicalPath() + "-wal";
4812c1ba9a961d4f96c26df260ee437655ad9e7c03eJeff Sharkey                            activeSet.add(canonicalWalPath);
4822c1ba9a961d4f96c26df260ee437655ad9e7c03eJeff Sharkey                            if (Log.isLoggable(TAG_XML_PARSER, Log.VERBOSE)) {
4832c1ba9a961d4f96c26df260ee437655ad9e7c03eJeff Sharkey                                Log.v(TAG_XML_PARSER, "...automatically generated "
4842c1ba9a961d4f96c26df260ee437655ad9e7c03eJeff Sharkey                                        + canonicalWalPath + ". Ignore if nonexistent.");
4852c1ba9a961d4f96c26df260ee437655ad9e7c03eJeff Sharkey                            }
48680e8db3d9cbdd6f42ca83b535abd26d761dc26eeDmitry Polukhin                        }
48780e8db3d9cbdd6f42ca83b535abd26d761dc26eeDmitry Polukhin
48880e8db3d9cbdd6f42ca83b535abd26d761dc26eeDmitry Polukhin                        // Special case for sharedpref files (not dirs) also add ".xml" suffix file.
48980e8db3d9cbdd6f42ca83b535abd26d761dc26eeDmitry Polukhin                        if ("sharedpref".equals(domainFromXml) && !canonicalFile.isDirectory() &&
49080e8db3d9cbdd6f42ca83b535abd26d761dc26eeDmitry Polukhin                            !canonicalFile.getCanonicalPath().endsWith(".xml")) {
49180e8db3d9cbdd6f42ca83b535abd26d761dc26eeDmitry Polukhin                            final String canonicalXmlPath =
49280e8db3d9cbdd6f42ca83b535abd26d761dc26eeDmitry Polukhin                                    canonicalFile.getCanonicalPath() + ".xml";
49380e8db3d9cbdd6f42ca83b535abd26d761dc26eeDmitry Polukhin                            activeSet.add(canonicalXmlPath);
49480e8db3d9cbdd6f42ca83b535abd26d761dc26eeDmitry Polukhin                            if (Log.isLoggable(TAG_XML_PARSER, Log.VERBOSE)) {
49580e8db3d9cbdd6f42ca83b535abd26d761dc26eeDmitry Polukhin                                Log.v(TAG_XML_PARSER, "...automatically generated "
49680e8db3d9cbdd6f42ca83b535abd26d761dc26eeDmitry Polukhin                                        + canonicalXmlPath + ". Ignore if nonexistent.");
497303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                            }
498303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                        }
499303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                }
500303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams            }
501303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams            if (Log.isLoggable(TAG_XML_PARSER, Log.VERBOSE)) {
502303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                Log.v(TAG_XML_PARSER, "\n");
503303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                Log.v(TAG_XML_PARSER, "Xml resource parsing complete.");
504303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                Log.v(TAG_XML_PARSER, "Final tally.");
505303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                Log.v(TAG_XML_PARSER, "Includes:");
506303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                if (includes.isEmpty()) {
507303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                    Log.v(TAG_XML_PARSER, "  ...nothing specified (This means the entirety of app"
508303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                            + " data minus excludes)");
509303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                } else {
510303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                    for (Map.Entry<String, Set<String>> entry : includes.entrySet()) {
511303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                        Log.v(TAG_XML_PARSER, "  domain=" + entry.getKey());
512303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                        for (String includeData : entry.getValue()) {
513303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                            Log.v(TAG_XML_PARSER, "  " + includeData);
514303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                        }
515303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                    }
516303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                }
517303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams
518303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                Log.v(TAG_XML_PARSER, "Excludes:");
519303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                if (excludes.isEmpty()) {
520303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                    Log.v(TAG_XML_PARSER, "  ...nothing to exclude.");
521303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                } else {
522303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                    for (String excludeData : excludes) {
523303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                        Log.v(TAG_XML_PARSER, "  " + excludeData);
524303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                    }
525303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                }
526303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams
527303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                Log.v(TAG_XML_PARSER, "  ");
528303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                Log.v(TAG_XML_PARSER, "====================================================");
529303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                Log.v(TAG_XML_PARSER, "\n");
530303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams            }
531303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams        }
532303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams
533303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams        private Set<String> parseCurrentTagForDomain(XmlPullParser parser,
534303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                                                     Set<String> excludes,
535303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                                                     Map<String, Set<String>> includes,
536303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                                                     String domain)
537303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                throws XmlPullParserException {
538303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams            if ("include".equals(parser.getName())) {
539303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                final String domainToken = getTokenForXmlDomain(domain);
540303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                Set<String> includeSet = includes.get(domainToken);
541303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                if (includeSet == null) {
542303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                    includeSet = new ArraySet<String>();
543303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                    includes.put(domainToken, includeSet);
544303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                }
545303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                return includeSet;
546303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams            } else if ("exclude".equals(parser.getName())) {
547303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                return excludes;
548303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams            } else {
549303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                // Unrecognised tag => hard failure.
550303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                if (Log.isLoggable(TAG_XML_PARSER, Log.VERBOSE)) {
551303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                    Log.v(TAG_XML_PARSER, "Invalid tag found in xml \""
552303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                            + parser.getName() + "\"; aborting operation.");
553303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                }
554303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                throw new XmlPullParserException("Unrecognised tag in backup" +
555303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                        " criteria xml (" + parser.getName() + ")");
556303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams            }
557303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams        }
558303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams
559303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams        /**
560303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams         * Map xml specified domain (human-readable, what clients put in their manifest's xml) to
561303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams         * BackupAgent internal data token.
562303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams         * @return null if the xml domain was invalid.
563303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams         */
564303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams        private String getTokenForXmlDomain(String xmlDomain) {
565303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams            if ("root".equals(xmlDomain)) {
566303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                return FullBackup.ROOT_TREE_TOKEN;
567303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams            } else if ("file".equals(xmlDomain)) {
5682c1ba9a961d4f96c26df260ee437655ad9e7c03eJeff Sharkey                return FullBackup.FILES_TREE_TOKEN;
569303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams            } else if ("database".equals(xmlDomain)) {
570303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                return FullBackup.DATABASE_TREE_TOKEN;
571303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams            } else if ("sharedpref".equals(xmlDomain)) {
572303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                return FullBackup.SHAREDPREFS_TREE_TOKEN;
5732c1ba9a961d4f96c26df260ee437655ad9e7c03eJeff Sharkey            } else if ("device_root".equals(xmlDomain)) {
5742c1ba9a961d4f96c26df260ee437655ad9e7c03eJeff Sharkey                return FullBackup.DEVICE_ROOT_TREE_TOKEN;
5752c1ba9a961d4f96c26df260ee437655ad9e7c03eJeff Sharkey            } else if ("device_file".equals(xmlDomain)) {
5762c1ba9a961d4f96c26df260ee437655ad9e7c03eJeff Sharkey                return FullBackup.DEVICE_FILES_TREE_TOKEN;
5772c1ba9a961d4f96c26df260ee437655ad9e7c03eJeff Sharkey            } else if ("device_database".equals(xmlDomain)) {
5782c1ba9a961d4f96c26df260ee437655ad9e7c03eJeff Sharkey                return FullBackup.DEVICE_DATABASE_TREE_TOKEN;
5792c1ba9a961d4f96c26df260ee437655ad9e7c03eJeff Sharkey            } else if ("device_sharedpref".equals(xmlDomain)) {
5802c1ba9a961d4f96c26df260ee437655ad9e7c03eJeff Sharkey                return FullBackup.DEVICE_SHAREDPREFS_TREE_TOKEN;
581303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams            } else if ("external".equals(xmlDomain)) {
582303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                return FullBackup.MANAGED_EXTERNAL_TREE_TOKEN;
583303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams            } else {
584303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                return null;
585303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams            }
586303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams        }
587303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams
588303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams        /**
589303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams         *
590303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams         * @param domain Directory where the specified file should exist. Not null.
591303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams         * @param filePathFromXml parsed from xml. Not sanitised before calling this function so may be
592303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams         *                        null.
593303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams         * @return The canonical path of the file specified or null if no such file exists.
594303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams         */
595303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams        private File extractCanonicalFile(File domain, String filePathFromXml) {
596303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams            if (filePathFromXml == null) {
597303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                // Allow things like <include domain="sharedpref"/>
598303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                filePathFromXml = "";
599303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams            }
600303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams            if (filePathFromXml.contains("..")) {
601303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                if (Log.isLoggable(TAG_XML_PARSER, Log.VERBOSE)) {
602303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                    Log.v(TAG_XML_PARSER, "...resolved \"" + domain.getPath() + " " + filePathFromXml
603303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                            + "\", but the \"..\" path is not permitted; skipping.");
604303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                }
605303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                return null;
606303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams            }
607303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams            if (filePathFromXml.contains("//")) {
608303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                if (Log.isLoggable(TAG_XML_PARSER, Log.VERBOSE)) {
609303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                    Log.v(TAG_XML_PARSER, "...resolved \"" + domain.getPath() + " " + filePathFromXml
610303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                            + "\", which contains the invalid \"//\" sequence; skipping.");
611303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                }
612303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                return null;
613303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams            }
614303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams            return new File(domain, filePathFromXml);
615303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams        }
616303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams
617303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams        /**
618303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams         * @param domain parsed from xml. Not sanitised before calling this function so may be null.
619303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams         * @return The directory relevant to the domain specified.
620303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams         */
621303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams        private File getDirectoryForCriteriaDomain(String domain) {
622303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams            if (TextUtils.isEmpty(domain)) {
623303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                return null;
624303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams            }
625303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams            if ("file".equals(domain)) {
626303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                return FILES_DIR;
627303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams            } else if ("database".equals(domain)) {
628303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                return DATABASE_DIR;
629303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams            } else if ("root".equals(domain)) {
630303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                return ROOT_DIR;
631303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams            } else if ("sharedpref".equals(domain)) {
632303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                return SHAREDPREF_DIR;
6332c1ba9a961d4f96c26df260ee437655ad9e7c03eJeff Sharkey            } else if ("device_file".equals(domain)) {
6342c1ba9a961d4f96c26df260ee437655ad9e7c03eJeff Sharkey                return DEVICE_FILES_DIR;
6352c1ba9a961d4f96c26df260ee437655ad9e7c03eJeff Sharkey            } else if ("device_database".equals(domain)) {
6362c1ba9a961d4f96c26df260ee437655ad9e7c03eJeff Sharkey                return DEVICE_DATABASE_DIR;
6372c1ba9a961d4f96c26df260ee437655ad9e7c03eJeff Sharkey            } else if ("device_root".equals(domain)) {
6382c1ba9a961d4f96c26df260ee437655ad9e7c03eJeff Sharkey                return DEVICE_ROOT_DIR;
6392c1ba9a961d4f96c26df260ee437655ad9e7c03eJeff Sharkey            } else if ("device_sharedpref".equals(domain)) {
6402c1ba9a961d4f96c26df260ee437655ad9e7c03eJeff Sharkey                return DEVICE_SHAREDPREF_DIR;
641303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams            } else if ("external".equals(domain)) {
642303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                return EXTERNAL_DIR;
643303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams            } else {
644303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                return null;
645303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams            }
646303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams        }
647303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams
648303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams        /**
649303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams         * Let's be strict about the type of xml the client can write. If we see anything untoward,
650303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams         * throw an XmlPullParserException.
651303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams         */
652303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams        private void validateInnerTagContents(XmlPullParser parser)
653303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                throws XmlPullParserException {
654303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams            if (parser.getAttributeCount() > 2) {
655303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                throw new XmlPullParserException("At most 2 tag attributes allowed for \""
656303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                        + parser.getName() + "\" tag (\"domain\" & \"path\".");
657303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams            }
658303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams            if (!"include".equals(parser.getName()) && !"exclude".equals(parser.getName())) {
659303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                throw new XmlPullParserException("A valid tag is one of \"<include/>\" or" +
660303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams                        " \"<exclude/>. You provided \"" + parser.getName() + "\"");
661303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams            }
662303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams        }
663303650c9cdb7cec88e7ec20747b161d9fff10719Matthew Williams    }
6644a627c71ff53a4fca1f961f4b1dcc0461df18a06Christopher Tate}
665