blob: 981ea82028a6fa79922ba987ef12b7d41db42e36 [file] [log] [blame]
Christopher Tate9bbc21a2009-06-10 20:23:25 -07001package com.android.internal.backup;
2
Christopher Tate2fdd4282009-06-12 15:20:04 -07003import android.backup.BackupDataInput;
Christopher Tate8e55eac92009-06-15 12:31:20 -07004import android.backup.BackupDataOutput;
Christopher Tate9bbc21a2009-06-10 20:23:25 -07005import 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
Christopher Tatee9190a22009-06-17 17:52:05 -070015import org.bouncycastle.util.encoders.Base64;
16
Christopher Tate9bbc21a2009-06-10 20:23:25 -070017import 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";
Christopher Tate2fdd4282009-06-12 15:20:04 -070031 private static final boolean DEBUG = true;
Christopher Tate9bbc21a2009-06-10 20:23:25 -070032
Christopher Tate5cb400b2009-06-25 16:03:14 -070033 private static final String TRANSPORT_DIR_NAME
34 = "com.android.internal.backup.LocalTransport";
35
Christopher Tate9bbc21a2009-06-10 20:23:25 -070036 private Context mContext;
37 private PackageManager mPackageManager;
38 private File mDataDir = new File(Environment.getDownloadCacheDirectory(), "backup");
Dan Egnorefe52642009-06-24 00:16:33 -070039 private PackageInfo[] mRestorePackages = null;
40 private int mRestorePackage = -1; // Index into mRestorePackages
Christopher Tate9bbc21a2009-06-10 20:23:25 -070041
42
43 public LocalTransport(Context context) {
Christopher Tate2fdd4282009-06-12 15:20:04 -070044 if (DEBUG) Log.v(TAG, "Transport constructed");
Christopher Tate9bbc21a2009-06-10 20:23:25 -070045 mContext = context;
46 mPackageManager = context.getPackageManager();
47 }
48
Christopher Tate5cb400b2009-06-25 16:03:14 -070049
50 public String transportDirName() throws RemoteException {
51 return TRANSPORT_DIR_NAME;
52 }
53
Christopher Tate9bbc21a2009-06-10 20:23:25 -070054 public long requestBackupTime() throws RemoteException {
55 // any time is a good time for local backup
56 return 0;
57 }
58
Christopher Tate25a747f2009-09-20 12:43:58 -070059 public boolean performBackup(PackageInfo packageInfo, ParcelFileDescriptor data,
60 boolean wipeAllFirst) throws RemoteException {
Christopher Tate2fdd4282009-06-12 15:20:04 -070061 if (DEBUG) Log.v(TAG, "performBackup() pkg=" + packageInfo.packageName);
Christopher Tate2fdd4282009-06-12 15:20:04 -070062
Christopher Tate9bbc21a2009-06-10 20:23:25 -070063 File packageDir = new File(mDataDir, packageInfo.packageName);
Christopher Tate2fdd4282009-06-12 15:20:04 -070064 packageDir.mkdirs();
Christopher Tate25a747f2009-09-20 12:43:58 -070065 if (wipeAllFirst) {
66 if (DEBUG) Log.v(TAG, "wiping all data first");
67 deleteContents(mDataDir);
68 }
Christopher Tate9bbc21a2009-06-10 20:23:25 -070069
Christopher Tate2fdd4282009-06-12 15:20:04 -070070 // Each 'record' in the restore set is kept in its own file, named by
71 // the record key. Wind through the data file, extracting individual
72 // record operations and building a set of all the updates to apply
73 // in this update.
74 BackupDataInput changeSet = new BackupDataInput(data.getFileDescriptor());
75 try {
76 int bufSize = 512;
77 byte[] buf = new byte[bufSize];
78 while (changeSet.readNextHeader()) {
79 String key = changeSet.getKey();
Joe Onorato5d605dc2009-06-18 18:23:43 -070080 String base64Key = new String(Base64.encode(key.getBytes()));
81 File entityFile = new File(packageDir, base64Key);
82
Christopher Tate2fdd4282009-06-12 15:20:04 -070083 int dataSize = changeSet.getDataSize();
Christopher Tatee9190a22009-06-17 17:52:05 -070084
Christopher Tatee9190a22009-06-17 17:52:05 -070085 if (DEBUG) Log.v(TAG, "Got change set key=" + key + " size=" + dataSize
86 + " key64=" + base64Key);
Christopher Tate9bbc21a2009-06-10 20:23:25 -070087
Joe Onorato5d605dc2009-06-18 18:23:43 -070088 if (dataSize >= 0) {
89 FileOutputStream entity = new FileOutputStream(entityFile);
90
91 if (dataSize > bufSize) {
92 bufSize = dataSize;
93 buf = new byte[bufSize];
94 }
95 changeSet.readEntityData(buf, 0, dataSize);
96 if (DEBUG) Log.v(TAG, " data size " + dataSize);
97
98 try {
99 entity.write(buf, 0, dataSize);
100 } catch (IOException e) {
Dan Egnorefe52642009-06-24 00:16:33 -0700101 Log.e(TAG, "Unable to update key file " + entityFile.getAbsolutePath());
102 return false;
Joe Onorato5d605dc2009-06-18 18:23:43 -0700103 } finally {
104 entity.close();
105 }
106 } else {
107 entityFile.delete();
Christopher Tate2fdd4282009-06-12 15:20:04 -0700108 }
109 }
Dan Egnorefe52642009-06-24 00:16:33 -0700110 return true;
Christopher Tate2fdd4282009-06-12 15:20:04 -0700111 } catch (IOException e) {
112 // oops, something went wrong. abort the operation and return error.
Dan Egnorefe52642009-06-24 00:16:33 -0700113 Log.v(TAG, "Exception reading backup input:", e);
114 return false;
Christopher Tate2fdd4282009-06-12 15:20:04 -0700115 }
Dan Egnorefe52642009-06-24 00:16:33 -0700116 }
Christopher Tate9bbc21a2009-06-10 20:23:25 -0700117
Christopher Tate25a747f2009-09-20 12:43:58 -0700118 // Deletes the contents but not the given directory
119 private void deleteContents(File dirname) {
120 File[] contents = dirname.listFiles();
121 if (contents != null) {
122 for (File f : contents) {
123 if (f.isDirectory()) {
124 // delete the directory's contents then fall through
125 // and delete the directory itself.
126 deleteContents(f);
127 }
128 f.delete();
129 }
130 }
131 }
132
Christopher Tateee0e78a2009-07-02 11:17:03 -0700133 public boolean clearBackupData(PackageInfo packageInfo) {
134 if (DEBUG) Log.v(TAG, "clearBackupData() pkg=" + packageInfo.packageName);
135
136 File packageDir = new File(mDataDir, packageInfo.packageName);
137 for (File f : packageDir.listFiles()) {
138 f.delete();
139 }
140 packageDir.delete();
141 return true;
142 }
143
Dan Egnorefe52642009-06-24 00:16:33 -0700144 public boolean finishBackup() throws RemoteException {
145 if (DEBUG) Log.v(TAG, "finishBackup()");
146 return true;
Christopher Tate9bbc21a2009-06-10 20:23:25 -0700147 }
148
149 // Restore handling
150 public RestoreSet[] getAvailableRestoreSets() throws android.os.RemoteException {
151 // one hardcoded restore set
Christopher Tatef68eb502009-06-16 11:02:01 -0700152 RestoreSet set = new RestoreSet("Local disk image", "flash", 0);
153 RestoreSet[] array = { set };
154 return array;
Christopher Tate9bbc21a2009-06-10 20:23:25 -0700155 }
156
Dan Egnorefe52642009-06-24 00:16:33 -0700157 public boolean startRestore(long token, PackageInfo[] packages) {
158 if (DEBUG) Log.v(TAG, "start restore " + token);
159 mRestorePackages = packages;
160 mRestorePackage = -1;
161 return true;
Christopher Tate9bbc21a2009-06-10 20:23:25 -0700162 }
163
Dan Egnorefe52642009-06-24 00:16:33 -0700164 public String nextRestorePackage() {
165 if (mRestorePackages == null) throw new IllegalStateException("startRestore not called");
166 while (++mRestorePackage < mRestorePackages.length) {
167 String name = mRestorePackages[mRestorePackage].packageName;
168 if (new File(mDataDir, name).isDirectory()) {
169 if (DEBUG) Log.v(TAG, " nextRestorePackage() = " + name);
170 return name;
171 }
172 }
Christopher Tate9bbc21a2009-06-10 20:23:25 -0700173
Dan Egnorefe52642009-06-24 00:16:33 -0700174 if (DEBUG) Log.v(TAG, " no more packages to restore");
175 return "";
176 }
177
178 public boolean getRestoreData(ParcelFileDescriptor outFd) {
179 if (mRestorePackages == null) throw new IllegalStateException("startRestore not called");
180 if (mRestorePackage < 0) throw new IllegalStateException("nextRestorePackage not called");
181 File packageDir = new File(mDataDir, mRestorePackages[mRestorePackage].packageName);
Christopher Tate9bbc21a2009-06-10 20:23:25 -0700182
Christopher Tate2fdd4282009-06-12 15:20:04 -0700183 // The restore set is the concatenation of the individual record blobs,
184 // each of which is a file in the package's directory
185 File[] blobs = packageDir.listFiles();
Dan Egnorefe52642009-06-24 00:16:33 -0700186 if (blobs == null) {
187 Log.e(TAG, "Error listing directory: " + packageDir);
188 return false; // nextRestorePackage() ensures the dir exists, so this is an error
Christopher Tate2fdd4282009-06-12 15:20:04 -0700189 }
Dan Egnorefe52642009-06-24 00:16:33 -0700190
191 // We expect at least some data if the directory exists in the first place
192 if (DEBUG) Log.v(TAG, " getRestoreData() found " + blobs.length + " key files");
193 BackupDataOutput out = new BackupDataOutput(outFd.getFileDescriptor());
194 try {
195 for (File f : blobs) {
196 FileInputStream in = new FileInputStream(f);
197 try {
198 int size = (int) f.length();
199 byte[] buf = new byte[size];
200 in.read(buf);
201 String key = new String(Base64.decode(f.getName()));
202 if (DEBUG) Log.v(TAG, " ... key=" + key + " size=" + size);
203 out.writeEntityHeader(key, size);
204 out.writeEntityData(buf, size);
205 } finally {
206 in.close();
207 }
208 }
209 return true;
210 } catch (IOException e) {
211 Log.e(TAG, "Unable to read backup records", e);
212 return false;
213 }
Christopher Tate9bbc21a2009-06-10 20:23:25 -0700214 }
Christopher Tate3a31a932009-06-22 15:10:30 -0700215
Dan Egnorefe52642009-06-24 00:16:33 -0700216 public void finishRestore() {
217 if (DEBUG) Log.v(TAG, "finishRestore()");
Christopher Tate3a31a932009-06-22 15:10:30 -0700218 }
Christopher Tate9bbc21a2009-06-10 20:23:25 -0700219}