LocalTransport.java revision efe52647f6b41993be43a5f47d1178bb0468cec8
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 Context mContext; 34 private PackageManager mPackageManager; 35 private File mDataDir = new File(Environment.getDownloadCacheDirectory(), "backup"); 36 private PackageInfo[] mRestorePackages = null; 37 private int mRestorePackage = -1; // Index into mRestorePackages 38 39 40 public LocalTransport(Context context) { 41 if (DEBUG) Log.v(TAG, "Transport constructed"); 42 mContext = context; 43 mPackageManager = context.getPackageManager(); 44 } 45 46 public long requestBackupTime() throws RemoteException { 47 // any time is a good time for local backup 48 return 0; 49 } 50 51 public boolean performBackup(PackageInfo packageInfo, ParcelFileDescriptor data) 52 throws RemoteException { 53 if (DEBUG) Log.v(TAG, "performBackup() pkg=" + packageInfo.packageName); 54 55 File packageDir = new File(mDataDir, packageInfo.packageName); 56 packageDir.mkdirs(); 57 58 // Each 'record' in the restore set is kept in its own file, named by 59 // the record key. Wind through the data file, extracting individual 60 // record operations and building a set of all the updates to apply 61 // in this update. 62 BackupDataInput changeSet = new BackupDataInput(data.getFileDescriptor()); 63 try { 64 int bufSize = 512; 65 byte[] buf = new byte[bufSize]; 66 while (changeSet.readNextHeader()) { 67 String key = changeSet.getKey(); 68 String base64Key = new String(Base64.encode(key.getBytes())); 69 File entityFile = new File(packageDir, base64Key); 70 71 int dataSize = changeSet.getDataSize(); 72 73 if (DEBUG) Log.v(TAG, "Got change set key=" + key + " size=" + dataSize 74 + " key64=" + base64Key); 75 76 if (dataSize >= 0) { 77 FileOutputStream entity = new FileOutputStream(entityFile); 78 79 if (dataSize > bufSize) { 80 bufSize = dataSize; 81 buf = new byte[bufSize]; 82 } 83 changeSet.readEntityData(buf, 0, dataSize); 84 if (DEBUG) Log.v(TAG, " data size " + dataSize); 85 86 try { 87 entity.write(buf, 0, dataSize); 88 } catch (IOException e) { 89 Log.e(TAG, "Unable to update key file " + entityFile.getAbsolutePath()); 90 return false; 91 } finally { 92 entity.close(); 93 } 94 } else { 95 entityFile.delete(); 96 } 97 } 98 return true; 99 } catch (IOException e) { 100 // oops, something went wrong. abort the operation and return error. 101 Log.v(TAG, "Exception reading backup input:", e); 102 return false; 103 } 104 } 105 106 public boolean finishBackup() throws RemoteException { 107 if (DEBUG) Log.v(TAG, "finishBackup()"); 108 return true; 109 } 110 111 // Restore handling 112 public RestoreSet[] getAvailableRestoreSets() throws android.os.RemoteException { 113 // one hardcoded restore set 114 RestoreSet set = new RestoreSet("Local disk image", "flash", 0); 115 RestoreSet[] array = { set }; 116 return array; 117 } 118 119 public boolean startRestore(long token, PackageInfo[] packages) { 120 if (DEBUG) Log.v(TAG, "start restore " + token); 121 mRestorePackages = packages; 122 mRestorePackage = -1; 123 return true; 124 } 125 126 public String nextRestorePackage() { 127 if (mRestorePackages == null) throw new IllegalStateException("startRestore not called"); 128 while (++mRestorePackage < mRestorePackages.length) { 129 String name = mRestorePackages[mRestorePackage].packageName; 130 if (new File(mDataDir, name).isDirectory()) { 131 if (DEBUG) Log.v(TAG, " nextRestorePackage() = " + name); 132 return name; 133 } 134 } 135 136 if (DEBUG) Log.v(TAG, " no more packages to restore"); 137 return ""; 138 } 139 140 public boolean getRestoreData(ParcelFileDescriptor outFd) { 141 if (mRestorePackages == null) throw new IllegalStateException("startRestore not called"); 142 if (mRestorePackage < 0) throw new IllegalStateException("nextRestorePackage not called"); 143 File packageDir = new File(mDataDir, mRestorePackages[mRestorePackage].packageName); 144 145 // The restore set is the concatenation of the individual record blobs, 146 // each of which is a file in the package's directory 147 File[] blobs = packageDir.listFiles(); 148 if (blobs == null) { 149 Log.e(TAG, "Error listing directory: " + packageDir); 150 return false; // nextRestorePackage() ensures the dir exists, so this is an error 151 } 152 153 // We expect at least some data if the directory exists in the first place 154 if (DEBUG) Log.v(TAG, " getRestoreData() found " + blobs.length + " key files"); 155 BackupDataOutput out = new BackupDataOutput(outFd.getFileDescriptor()); 156 try { 157 for (File f : blobs) { 158 FileInputStream in = new FileInputStream(f); 159 try { 160 int size = (int) f.length(); 161 byte[] buf = new byte[size]; 162 in.read(buf); 163 String key = new String(Base64.decode(f.getName())); 164 if (DEBUG) Log.v(TAG, " ... key=" + key + " size=" + size); 165 out.writeEntityHeader(key, size); 166 out.writeEntityData(buf, size); 167 } finally { 168 in.close(); 169 } 170 } 171 return true; 172 } catch (IOException e) { 173 Log.e(TAG, "Unable to read backup records", e); 174 return false; 175 } 176 } 177 178 public void finishRestore() { 179 if (DEBUG) Log.v(TAG, "finishRestore()"); 180 } 181} 182