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