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