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