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 final File[] fileset = packageDir.listFiles(); 170 if (fileset != null) { 171 for (File f : fileset) { 172 f.delete(); 173 } 174 packageDir.delete(); 175 } 176 return BackupConstants.TRANSPORT_OK; 177 } 178 finishBackup()179 public int finishBackup() { 180 if (DEBUG) Log.v(TAG, "finishBackup()"); 181 return BackupConstants.TRANSPORT_OK; 182 } 183 184 // Restore handling getAvailableRestoreSets()185 public RestoreSet[] getAvailableRestoreSets() throws android.os.RemoteException { 186 // one hardcoded restore set 187 RestoreSet set = new RestoreSet("Local disk image", "flash", RESTORE_TOKEN); 188 RestoreSet[] array = { set }; 189 return array; 190 } 191 getCurrentRestoreSet()192 public long getCurrentRestoreSet() { 193 // The hardcoded restore set always has the same token 194 return RESTORE_TOKEN; 195 } 196 startRestore(long token, PackageInfo[] packages)197 public int startRestore(long token, PackageInfo[] packages) { 198 if (DEBUG) Log.v(TAG, "start restore " + token); 199 mRestorePackages = packages; 200 mRestorePackage = -1; 201 return BackupConstants.TRANSPORT_OK; 202 } 203 nextRestorePackage()204 public String nextRestorePackage() { 205 if (mRestorePackages == null) throw new IllegalStateException("startRestore not called"); 206 while (++mRestorePackage < mRestorePackages.length) { 207 String name = mRestorePackages[mRestorePackage].packageName; 208 if (new File(mDataDir, name).isDirectory()) { 209 if (DEBUG) Log.v(TAG, " nextRestorePackage() = " + name); 210 return name; 211 } 212 } 213 214 if (DEBUG) Log.v(TAG, " no more packages to restore"); 215 return ""; 216 } 217 getRestoreData(ParcelFileDescriptor outFd)218 public int getRestoreData(ParcelFileDescriptor outFd) { 219 if (mRestorePackages == null) throw new IllegalStateException("startRestore not called"); 220 if (mRestorePackage < 0) throw new IllegalStateException("nextRestorePackage not called"); 221 File packageDir = new File(mDataDir, mRestorePackages[mRestorePackage].packageName); 222 223 // The restore set is the concatenation of the individual record blobs, 224 // each of which is a file in the package's directory 225 File[] blobs = packageDir.listFiles(); 226 if (blobs == null) { // nextRestorePackage() ensures the dir exists, so this is an error 227 Log.e(TAG, "Error listing directory: " + packageDir); 228 return BackupConstants.TRANSPORT_ERROR; 229 } 230 231 // We expect at least some data if the directory exists in the first place 232 if (DEBUG) Log.v(TAG, " getRestoreData() found " + blobs.length + " key files"); 233 BackupDataOutput out = new BackupDataOutput(outFd.getFileDescriptor()); 234 try { 235 for (File f : blobs) { 236 FileInputStream in = new FileInputStream(f); 237 try { 238 int size = (int) f.length(); 239 byte[] buf = new byte[size]; 240 in.read(buf); 241 String key = new String(Base64.decode(f.getName())); 242 if (DEBUG) Log.v(TAG, " ... key=" + key + " size=" + size); 243 out.writeEntityHeader(key, size); 244 out.writeEntityData(buf, size); 245 } finally { 246 in.close(); 247 } 248 } 249 return BackupConstants.TRANSPORT_OK; 250 } catch (IOException e) { 251 Log.e(TAG, "Unable to read backup records", e); 252 return BackupConstants.TRANSPORT_ERROR; 253 } 254 } 255 finishRestore()256 public void finishRestore() { 257 if (DEBUG) Log.v(TAG, "finishRestore()"); 258 } 259 } 260