LocalTransport.java revision 50c6df04cf17a99c3959c306a4e0e10da6d85c46
1package com.android.internal.backup; 2 3import android.backup.BackupDataInput; 4import android.backup.BackupDataOutput; 5import android.backup.RestoreSet; 6import android.content.Context; 7import android.content.pm.PackageInfo; 8import android.content.pm.PackageManager; 9import android.content.pm.PackageManager.NameNotFoundException; 10import android.os.Environment; 11import android.os.ParcelFileDescriptor; 12import android.os.RemoteException; 13import android.util.Log; 14 15import org.bouncycastle.util.encoders.Base64; 16 17import java.io.File; 18import java.io.FileFilter; 19import java.io.FileInputStream; 20import java.io.FileOutputStream; 21import java.io.IOException; 22import java.util.ArrayList; 23 24/** 25 * Backup transport for stashing stuff into a known location on disk, and 26 * later restoring from there. For testing only. 27 */ 28 29public class LocalTransport extends IBackupTransport.Stub { 30 private static final String TAG = "LocalTransport"; 31 private static final boolean DEBUG = true; 32 33 private static final String TRANSPORT_DIR_NAME 34 = "com.android.internal.backup.LocalTransport"; 35 36 // The single hardcoded restore set always has the same (nonzero!) token 37 private static final long RESTORE_TOKEN = 1; 38 39 private Context mContext; 40 private PackageManager mPackageManager; 41 private File mDataDir = new File(Environment.getDownloadCacheDirectory(), "backup"); 42 private PackageInfo[] mRestorePackages = null; 43 private int mRestorePackage = -1; // Index into mRestorePackages 44 45 46 public LocalTransport(Context context) { 47 mContext = context; 48 mPackageManager = context.getPackageManager(); 49 } 50 51 52 public String transportDirName() { 53 return TRANSPORT_DIR_NAME; 54 } 55 56 public long requestBackupTime() { 57 // any time is a good time for local backup 58 return 0; 59 } 60 61 public int initializeDevice() { 62 if (DEBUG) Log.v(TAG, "wiping all data"); 63 deleteContents(mDataDir); 64 return BackupConstants.TRANSPORT_OK; 65 } 66 67 public int performBackup(PackageInfo packageInfo, ParcelFileDescriptor data) { 68 if (DEBUG) Log.v(TAG, "performBackup() pkg=" + packageInfo.packageName); 69 70 File packageDir = new File(mDataDir, packageInfo.packageName); 71 packageDir.mkdirs(); 72 73 // Each 'record' in the restore set is kept in its own file, named by 74 // the record key. Wind through the data file, extracting individual 75 // record operations and building a set of all the updates to apply 76 // in this update. 77 BackupDataInput changeSet = new BackupDataInput(data.getFileDescriptor()); 78 try { 79 int bufSize = 512; 80 byte[] buf = new byte[bufSize]; 81 while (changeSet.readNextHeader()) { 82 String key = changeSet.getKey(); 83 String base64Key = new String(Base64.encode(key.getBytes())); 84 File entityFile = new File(packageDir, base64Key); 85 86 int dataSize = changeSet.getDataSize(); 87 88 if (DEBUG) Log.v(TAG, "Got change set key=" + key + " size=" + dataSize 89 + " key64=" + base64Key); 90 91 if (dataSize >= 0) { 92 FileOutputStream entity = new FileOutputStream(entityFile); 93 94 if (dataSize > bufSize) { 95 bufSize = dataSize; 96 buf = new byte[bufSize]; 97 } 98 changeSet.readEntityData(buf, 0, dataSize); 99 if (DEBUG) Log.v(TAG, " data size " + dataSize); 100 101 try { 102 entity.write(buf, 0, dataSize); 103 } catch (IOException e) { 104 Log.e(TAG, "Unable to update key file " + entityFile.getAbsolutePath()); 105 return BackupConstants.TRANSPORT_ERROR; 106 } finally { 107 entity.close(); 108 } 109 } else { 110 entityFile.delete(); 111 } 112 } 113 return BackupConstants.TRANSPORT_OK; 114 } catch (IOException e) { 115 // oops, something went wrong. abort the operation and return error. 116 Log.v(TAG, "Exception reading backup input:", e); 117 return BackupConstants.TRANSPORT_ERROR; 118 } 119 } 120 121 // Deletes the contents but not the given directory 122 private void deleteContents(File dirname) { 123 File[] contents = dirname.listFiles(); 124 if (contents != null) { 125 for (File f : contents) { 126 if (f.isDirectory()) { 127 // delete the directory's contents then fall through 128 // and delete the directory itself. 129 deleteContents(f); 130 } 131 f.delete(); 132 } 133 } 134 } 135 136 public int clearBackupData(PackageInfo packageInfo) { 137 if (DEBUG) Log.v(TAG, "clearBackupData() pkg=" + packageInfo.packageName); 138 139 File packageDir = new File(mDataDir, packageInfo.packageName); 140 for (File f : packageDir.listFiles()) { 141 f.delete(); 142 } 143 packageDir.delete(); 144 return BackupConstants.TRANSPORT_OK; 145 } 146 147 public int finishBackup() { 148 if (DEBUG) Log.v(TAG, "finishBackup()"); 149 return BackupConstants.TRANSPORT_OK; 150 } 151 152 // Restore handling 153 public RestoreSet[] getAvailableRestoreSets() throws android.os.RemoteException { 154 // one hardcoded restore set 155 RestoreSet set = new RestoreSet("Local disk image", "flash", RESTORE_TOKEN); 156 RestoreSet[] array = { set }; 157 return array; 158 } 159 160 public long getCurrentRestoreSet() { 161 // The hardcoded restore set always has the same token 162 return RESTORE_TOKEN; 163 } 164 165 public int startRestore(long token, PackageInfo[] packages) { 166 if (DEBUG) Log.v(TAG, "start restore " + token); 167 mRestorePackages = packages; 168 mRestorePackage = -1; 169 return BackupConstants.TRANSPORT_OK; 170 } 171 172 public String nextRestorePackage() { 173 if (mRestorePackages == null) throw new IllegalStateException("startRestore not called"); 174 while (++mRestorePackage < mRestorePackages.length) { 175 String name = mRestorePackages[mRestorePackage].packageName; 176 if (new File(mDataDir, name).isDirectory()) { 177 if (DEBUG) Log.v(TAG, " nextRestorePackage() = " + name); 178 return name; 179 } 180 } 181 182 if (DEBUG) Log.v(TAG, " no more packages to restore"); 183 return ""; 184 } 185 186 public int getRestoreData(ParcelFileDescriptor outFd) { 187 if (mRestorePackages == null) throw new IllegalStateException("startRestore not called"); 188 if (mRestorePackage < 0) throw new IllegalStateException("nextRestorePackage not called"); 189 File packageDir = new File(mDataDir, mRestorePackages[mRestorePackage].packageName); 190 191 // The restore set is the concatenation of the individual record blobs, 192 // each of which is a file in the package's directory 193 File[] blobs = packageDir.listFiles(); 194 if (blobs == null) { // nextRestorePackage() ensures the dir exists, so this is an error 195 Log.e(TAG, "Error listing directory: " + packageDir); 196 return BackupConstants.TRANSPORT_ERROR; 197 } 198 199 // We expect at least some data if the directory exists in the first place 200 if (DEBUG) Log.v(TAG, " getRestoreData() found " + blobs.length + " key files"); 201 BackupDataOutput out = new BackupDataOutput(outFd.getFileDescriptor()); 202 try { 203 for (File f : blobs) { 204 FileInputStream in = new FileInputStream(f); 205 try { 206 int size = (int) f.length(); 207 byte[] buf = new byte[size]; 208 in.read(buf); 209 String key = new String(Base64.decode(f.getName())); 210 if (DEBUG) Log.v(TAG, " ... key=" + key + " size=" + size); 211 out.writeEntityHeader(key, size); 212 out.writeEntityData(buf, size); 213 } finally { 214 in.close(); 215 } 216 } 217 return BackupConstants.TRANSPORT_OK; 218 } catch (IOException e) { 219 Log.e(TAG, "Unable to read backup records", e); 220 return BackupConstants.TRANSPORT_ERROR; 221 } 222 } 223 224 public void finishRestore() { 225 if (DEBUG) Log.v(TAG, "finishRestore()"); 226 } 227} 228