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