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