1b0628bfd5aac480a0d412ac96b8af1d97ac01c30Christopher Tatepackage com.android.sharedstoragebackup;
2b0628bfd5aac480a0d412ac96b8af1d97ac01c30Christopher Tate
3b0628bfd5aac480a0d412ac96b8af1d97ac01c30Christopher Tateimport android.app.backup.FullBackupAgent;
479ec80db70d788f35aa13346e4684ecbd401bd84Christopher Tateimport android.app.backup.FullBackup;
579ec80db70d788f35aa13346e4684ecbd401bd84Christopher Tateimport android.app.backup.FullBackupDataOutput;
6b0628bfd5aac480a0d412ac96b8af1d97ac01c30Christopher Tateimport android.content.Context;
7416c39e8d48048fa4a997c09460c262cca871fc4Christopher Tateimport android.os.Environment;
8b0628bfd5aac480a0d412ac96b8af1d97ac01c30Christopher Tateimport android.os.ParcelFileDescriptor;
9b0628bfd5aac480a0d412ac96b8af1d97ac01c30Christopher Tateimport android.os.storage.StorageManager;
10b0628bfd5aac480a0d412ac96b8af1d97ac01c30Christopher Tateimport android.os.storage.StorageVolume;
11b0628bfd5aac480a0d412ac96b8af1d97ac01c30Christopher Tateimport android.util.Slog;
12b0628bfd5aac480a0d412ac96b8af1d97ac01c30Christopher Tate
13b0628bfd5aac480a0d412ac96b8af1d97ac01c30Christopher Tateimport java.io.File;
14b0628bfd5aac480a0d412ac96b8af1d97ac01c30Christopher Tateimport java.io.IOException;
15416c39e8d48048fa4a997c09460c262cca871fc4Christopher Tateimport java.util.HashSet;
16b0628bfd5aac480a0d412ac96b8af1d97ac01c30Christopher Tate
17b0628bfd5aac480a0d412ac96b8af1d97ac01c30Christopher Tatepublic class SharedStorageAgent extends FullBackupAgent {
18b0628bfd5aac480a0d412ac96b8af1d97ac01c30Christopher Tate    static final String TAG = "SharedStorageAgent";
19b0628bfd5aac480a0d412ac96b8af1d97ac01c30Christopher Tate    static final boolean DEBUG = true;
20b0628bfd5aac480a0d412ac96b8af1d97ac01c30Christopher Tate
21b0628bfd5aac480a0d412ac96b8af1d97ac01c30Christopher Tate    StorageVolume[] mVolumes;
22b0628bfd5aac480a0d412ac96b8af1d97ac01c30Christopher Tate
23b0628bfd5aac480a0d412ac96b8af1d97ac01c30Christopher Tate    @Override
24b0628bfd5aac480a0d412ac96b8af1d97ac01c30Christopher Tate    public void onCreate() {
25b0628bfd5aac480a0d412ac96b8af1d97ac01c30Christopher Tate        StorageManager mgr = (StorageManager) getSystemService(Context.STORAGE_SERVICE);
26b0628bfd5aac480a0d412ac96b8af1d97ac01c30Christopher Tate        if (mgr != null) {
27b0628bfd5aac480a0d412ac96b8af1d97ac01c30Christopher Tate            mVolumes = mgr.getVolumeList();
28b0628bfd5aac480a0d412ac96b8af1d97ac01c30Christopher Tate        } else {
29b0628bfd5aac480a0d412ac96b8af1d97ac01c30Christopher Tate            Slog.e(TAG, "Unable to access Storage Manager");
30b0628bfd5aac480a0d412ac96b8af1d97ac01c30Christopher Tate        }
31b0628bfd5aac480a0d412ac96b8af1d97ac01c30Christopher Tate    }
32b0628bfd5aac480a0d412ac96b8af1d97ac01c30Christopher Tate
3379ec80db70d788f35aa13346e4684ecbd401bd84Christopher Tate    /**
3479ec80db70d788f35aa13346e4684ecbd401bd84Christopher Tate     * Full backup of the shared-storage filesystem
3579ec80db70d788f35aa13346e4684ecbd401bd84Christopher Tate     */
36b0628bfd5aac480a0d412ac96b8af1d97ac01c30Christopher Tate    @Override
3779ec80db70d788f35aa13346e4684ecbd401bd84Christopher Tate    public void onFullBackup(FullBackupDataOutput output) throws IOException {
38b0628bfd5aac480a0d412ac96b8af1d97ac01c30Christopher Tate        // If there are shared-storage volumes available, run the inherited directory-
39b0628bfd5aac480a0d412ac96b8af1d97ac01c30Christopher Tate        // hierarchy backup process on them.  By convention in the Storage Manager, the
40b0628bfd5aac480a0d412ac96b8af1d97ac01c30Christopher Tate        // "primary" shared storage volume is first in the list.
41b0628bfd5aac480a0d412ac96b8af1d97ac01c30Christopher Tate        if (mVolumes != null) {
42fb2ea43112bdf5a7dc121b59e2ef7e8b411bd019Christopher Tate            if (DEBUG) Slog.i(TAG, "Backing up " + mVolumes.length + " shared volumes");
43416c39e8d48048fa4a997c09460c262cca871fc4Christopher Tate            // Ignore all apps' getExternalFilesDir() content; it is backed up as part of
44416c39e8d48048fa4a997c09460c262cca871fc4Christopher Tate            // each app-specific payload.
45416c39e8d48048fa4a997c09460c262cca871fc4Christopher Tate            HashSet<String> externalFilesDirFilter = new HashSet<String>();
46416c39e8d48048fa4a997c09460c262cca871fc4Christopher Tate            final File externalAndroidRoot = new File(Environment.getExternalStorageDirectory(),
47416c39e8d48048fa4a997c09460c262cca871fc4Christopher Tate                    Environment.DIRECTORY_ANDROID);
48416c39e8d48048fa4a997c09460c262cca871fc4Christopher Tate            externalFilesDirFilter.add(externalAndroidRoot.getCanonicalPath());
49416c39e8d48048fa4a997c09460c262cca871fc4Christopher Tate
50b0628bfd5aac480a0d412ac96b8af1d97ac01c30Christopher Tate            for (int i = 0; i < mVolumes.length; i++) {
51b0628bfd5aac480a0d412ac96b8af1d97ac01c30Christopher Tate                StorageVolume v = mVolumes[i];
52b0628bfd5aac480a0d412ac96b8af1d97ac01c30Christopher Tate                // Express the contents of volume N this way in the tar stream:
53b0628bfd5aac480a0d412ac96b8af1d97ac01c30Christopher Tate                //     shared/N/path/to/file
54b0628bfd5aac480a0d412ac96b8af1d97ac01c30Christopher Tate                // The restore will then extract to the given volume
55b0628bfd5aac480a0d412ac96b8af1d97ac01c30Christopher Tate                String domain = FullBackup.SHARED_PREFIX + i;
56416c39e8d48048fa4a997c09460c262cca871fc4Christopher Tate                fullBackupFileTree(null, domain, v.getPath(), externalFilesDirFilter, output);
57b0628bfd5aac480a0d412ac96b8af1d97ac01c30Christopher Tate            }
58b0628bfd5aac480a0d412ac96b8af1d97ac01c30Christopher Tate        }
59b0628bfd5aac480a0d412ac96b8af1d97ac01c30Christopher Tate    }
60b0628bfd5aac480a0d412ac96b8af1d97ac01c30Christopher Tate
61b0628bfd5aac480a0d412ac96b8af1d97ac01c30Christopher Tate    /**
62b0628bfd5aac480a0d412ac96b8af1d97ac01c30Christopher Tate     * Full restore of one file to shared storage
63b0628bfd5aac480a0d412ac96b8af1d97ac01c30Christopher Tate     */
64b0628bfd5aac480a0d412ac96b8af1d97ac01c30Christopher Tate    @Override
65b0628bfd5aac480a0d412ac96b8af1d97ac01c30Christopher Tate    public void onRestoreFile(ParcelFileDescriptor data, long size,
66b0628bfd5aac480a0d412ac96b8af1d97ac01c30Christopher Tate            int type, String domain, String relpath, long mode, long mtime)
67b0628bfd5aac480a0d412ac96b8af1d97ac01c30Christopher Tate            throws IOException {
68fb2ea43112bdf5a7dc121b59e2ef7e8b411bd019Christopher Tate        if (DEBUG) Slog.d(TAG, "Shared restore: [ " + domain + " : " + relpath + "]");
69b0628bfd5aac480a0d412ac96b8af1d97ac01c30Christopher Tate
70b0628bfd5aac480a0d412ac96b8af1d97ac01c30Christopher Tate        File outFile = null;
71b0628bfd5aac480a0d412ac96b8af1d97ac01c30Christopher Tate
72b0628bfd5aac480a0d412ac96b8af1d97ac01c30Christopher Tate        // The file path must be in the semantic form [number]/path/to/file...
73b0628bfd5aac480a0d412ac96b8af1d97ac01c30Christopher Tate        int slash = relpath.indexOf('/');
74b0628bfd5aac480a0d412ac96b8af1d97ac01c30Christopher Tate        if (slash > 0) {
75b0628bfd5aac480a0d412ac96b8af1d97ac01c30Christopher Tate            try {
76b0628bfd5aac480a0d412ac96b8af1d97ac01c30Christopher Tate                int i = Integer.parseInt(relpath.substring(0, slash));
77b0628bfd5aac480a0d412ac96b8af1d97ac01c30Christopher Tate                if (i <= mVolumes.length) {
78b0628bfd5aac480a0d412ac96b8af1d97ac01c30Christopher Tate                    outFile = new File(mVolumes[i].getPath(), relpath.substring(slash + 1));
79b0628bfd5aac480a0d412ac96b8af1d97ac01c30Christopher Tate                    if (DEBUG) Slog.i(TAG, " => " + outFile.getAbsolutePath());
80b0628bfd5aac480a0d412ac96b8af1d97ac01c30Christopher Tate                } else {
81b0628bfd5aac480a0d412ac96b8af1d97ac01c30Christopher Tate                    Slog.w(TAG, "Cannot restore data for unavailable volume " + i);
82b0628bfd5aac480a0d412ac96b8af1d97ac01c30Christopher Tate                }
83b0628bfd5aac480a0d412ac96b8af1d97ac01c30Christopher Tate            } catch (NumberFormatException e) {
84b0628bfd5aac480a0d412ac96b8af1d97ac01c30Christopher Tate                if (DEBUG) Slog.w(TAG, "Bad volume number token: " + relpath.substring(0, slash));
85b0628bfd5aac480a0d412ac96b8af1d97ac01c30Christopher Tate            }
86b0628bfd5aac480a0d412ac96b8af1d97ac01c30Christopher Tate        } else {
87b0628bfd5aac480a0d412ac96b8af1d97ac01c30Christopher Tate            if (DEBUG) Slog.i(TAG, "Can't find volume-number token");
88b0628bfd5aac480a0d412ac96b8af1d97ac01c30Christopher Tate        }
89b0628bfd5aac480a0d412ac96b8af1d97ac01c30Christopher Tate        if (outFile == null) {
90b0628bfd5aac480a0d412ac96b8af1d97ac01c30Christopher Tate            Slog.e(TAG, "Skipping data with malformed path " + relpath);
91b0628bfd5aac480a0d412ac96b8af1d97ac01c30Christopher Tate        }
92b0628bfd5aac480a0d412ac96b8af1d97ac01c30Christopher Tate
9379ec80db70d788f35aa13346e4684ecbd401bd84Christopher Tate        FullBackup.restoreFile(data, size, type, -1, mtime, outFile);
94b0628bfd5aac480a0d412ac96b8af1d97ac01c30Christopher Tate    }
95b0628bfd5aac480a0d412ac96b8af1d97ac01c30Christopher Tate}
96