1 package com.android.sharedstoragebackup;
2 
3 import android.app.backup.FullBackupAgent;
4 import android.app.backup.FullBackup;
5 import android.app.backup.FullBackupDataOutput;
6 import android.content.Context;
7 import android.os.Environment;
8 import android.os.ParcelFileDescriptor;
9 import android.os.storage.StorageManager;
10 import android.os.storage.StorageVolume;
11 import android.util.ArraySet;
12 import android.util.Slog;
13 
14 import java.io.File;
15 import java.io.IOException;
16 
17 public class SharedStorageAgent extends FullBackupAgent {
18     static final String TAG = "SharedStorageAgent";
19     static final boolean DEBUG = true;
20 
21     StorageVolume[] mVolumes;
22 
23     @Override
onCreate()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
onFullBackup(FullBackupDataOutput output)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             ArraySet<String> externalFilesDirFilter = new ArraySet();
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(),
57                         null /* manifestExcludes */,
58                         externalFilesDirFilter /* systemExcludes */, output);
59             }
60         }
61     }
62 
63     /**
64      * Full restore of one file to shared storage
65      */
66     @Override
onRestoreFile(ParcelFileDescriptor data, long size, int type, String domain, String relpath, long mode, long mtime)67     public void onRestoreFile(ParcelFileDescriptor data, long size,
68             int type, String domain, String relpath, long mode, long mtime)
69             throws IOException {
70         if (DEBUG) Slog.d(TAG, "Shared restore: [ " + domain + " : " + relpath + "]");
71 
72         File outFile = null;
73 
74         // The file path must be in the semantic form [number]/path/to/file...
75         int slash = relpath.indexOf('/');
76         if (slash > 0) {
77             try {
78                 int i = Integer.parseInt(relpath.substring(0, slash));
79                 if (i <= mVolumes.length) {
80                     outFile = new File(mVolumes[i].getPath(), relpath.substring(slash + 1));
81                     if (DEBUG) Slog.i(TAG, " => " + outFile.getAbsolutePath());
82                 } else {
83                     Slog.w(TAG, "Cannot restore data for unavailable volume " + i);
84                 }
85             } catch (NumberFormatException e) {
86                 if (DEBUG) Slog.w(TAG, "Bad volume number token: " + relpath.substring(0, slash));
87             }
88         } else {
89             if (DEBUG) Slog.i(TAG, "Can't find volume-number token");
90         }
91         if (outFile == null) {
92             Slog.e(TAG, "Skipping data with malformed path " + relpath);
93         }
94 
95         FullBackup.restoreFile(data, size, type, -1, mtime, outFile);
96     }
97 }
98