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