blob: be83eb9574f7fdb118e20091ce33aba7a201148f [file] [log] [blame]
Christopher Tate9bbc21a2009-06-10 20:23:25 -07001package com.android.internal.backup;
2
Christopher Tate45281862010-03-05 15:46:30 -08003import android.app.backup.BackupDataInput;
4import android.app.backup.BackupDataOutput;
5import android.app.backup.RestoreSet;
Christopher Tate9bbc21a2009-06-10 20:23:25 -07006import 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 Tate50c6df02010-01-29 12:48:20 -080036 // The single hardcoded restore set always has the same (nonzero!) token
37 private static final long RESTORE_TOKEN = 1;
38
Christopher Tate9bbc21a2009-06-10 20:23:25 -070039 private Context mContext;
40 private PackageManager mPackageManager;
41 private File mDataDir = new File(Environment.getDownloadCacheDirectory(), "backup");
Dan Egnorefe52642009-06-24 00:16:33 -070042 private PackageInfo[] mRestorePackages = null;
43 private int mRestorePackage = -1; // Index into mRestorePackages
Christopher Tate9bbc21a2009-06-10 20:23:25 -070044
45
46 public LocalTransport(Context context) {
47 mContext = context;
48 mPackageManager = context.getPackageManager();
49 }
50
Christopher Tate5cb400b2009-06-25 16:03:14 -070051
Dan Egnor01445162009-09-21 17:04:05 -070052 public String transportDirName() {
Christopher Tate5cb400b2009-06-25 16:03:14 -070053 return TRANSPORT_DIR_NAME;
54 }
55
Dan Egnor01445162009-09-21 17:04:05 -070056 public long requestBackupTime() {
Christopher Tate9bbc21a2009-06-10 20:23:25 -070057 // any time is a good time for local backup
58 return 0;
59 }
60
Dan Egnor01445162009-09-21 17:04:05 -070061 public int initializeDevice() {
62 if (DEBUG) Log.v(TAG, "wiping all data");
63 deleteContents(mDataDir);
64 return BackupConstants.TRANSPORT_OK;
65 }
66
67 public int performBackup(PackageInfo packageInfo, ParcelFileDescriptor data) {
Christopher Tate2fdd4282009-06-12 15:20:04 -070068 if (DEBUG) Log.v(TAG, "performBackup() pkg=" + packageInfo.packageName);
Christopher Tate2fdd4282009-06-12 15:20:04 -070069
Christopher Tate9bbc21a2009-06-10 20:23:25 -070070 File packageDir = new File(mDataDir, packageInfo.packageName);
Christopher Tate2fdd4282009-06-12 15:20:04 -070071 packageDir.mkdirs();
Christopher Tate9bbc21a2009-06-10 20:23:25 -070072
Christopher Tate2fdd4282009-06-12 15:20:04 -070073 // Each 'record' in the restore set is kept in its own file, named by
74 // the record key. Wind through the data file, extracting individual
75 // record operations and building a set of all the updates to apply
76 // in this update.
77 BackupDataInput changeSet = new BackupDataInput(data.getFileDescriptor());
78 try {
79 int bufSize = 512;
80 byte[] buf = new byte[bufSize];
81 while (changeSet.readNextHeader()) {
82 String key = changeSet.getKey();
Joe Onorato5d605dc2009-06-18 18:23:43 -070083 String base64Key = new String(Base64.encode(key.getBytes()));
84 File entityFile = new File(packageDir, base64Key);
85
Christopher Tate2fdd4282009-06-12 15:20:04 -070086 int dataSize = changeSet.getDataSize();
Christopher Tatee9190a22009-06-17 17:52:05 -070087
Christopher Tatee9190a22009-06-17 17:52:05 -070088 if (DEBUG) Log.v(TAG, "Got change set key=" + key + " size=" + dataSize
89 + " key64=" + base64Key);
Christopher Tate9bbc21a2009-06-10 20:23:25 -070090
Joe Onorato5d605dc2009-06-18 18:23:43 -070091 if (dataSize >= 0) {
92 FileOutputStream entity = new FileOutputStream(entityFile);
93
94 if (dataSize > bufSize) {
95 bufSize = dataSize;
96 buf = new byte[bufSize];
97 }
98 changeSet.readEntityData(buf, 0, dataSize);
99 if (DEBUG) Log.v(TAG, " data size " + dataSize);
100
101 try {
102 entity.write(buf, 0, dataSize);
103 } catch (IOException e) {
Dan Egnorefe52642009-06-24 00:16:33 -0700104 Log.e(TAG, "Unable to update key file " + entityFile.getAbsolutePath());
Christopher Tated55e18a2009-09-21 10:12:59 -0700105 return BackupConstants.TRANSPORT_ERROR;
Joe Onorato5d605dc2009-06-18 18:23:43 -0700106 } finally {
107 entity.close();
108 }
109 } else {
110 entityFile.delete();
Christopher Tate2fdd4282009-06-12 15:20:04 -0700111 }
112 }
Christopher Tated55e18a2009-09-21 10:12:59 -0700113 return BackupConstants.TRANSPORT_OK;
Christopher Tate2fdd4282009-06-12 15:20:04 -0700114 } catch (IOException e) {
115 // oops, something went wrong. abort the operation and return error.
Dan Egnorefe52642009-06-24 00:16:33 -0700116 Log.v(TAG, "Exception reading backup input:", e);
Christopher Tated55e18a2009-09-21 10:12:59 -0700117 return BackupConstants.TRANSPORT_ERROR;
Christopher Tate2fdd4282009-06-12 15:20:04 -0700118 }
Dan Egnorefe52642009-06-24 00:16:33 -0700119 }
Christopher Tate9bbc21a2009-06-10 20:23:25 -0700120
Christopher Tate25a747f2009-09-20 12:43:58 -0700121 // Deletes the contents but not the given directory
122 private void deleteContents(File dirname) {
123 File[] contents = dirname.listFiles();
124 if (contents != null) {
125 for (File f : contents) {
126 if (f.isDirectory()) {
127 // delete the directory's contents then fall through
128 // and delete the directory itself.
129 deleteContents(f);
130 }
131 f.delete();
132 }
133 }
134 }
135
Dan Egnor01445162009-09-21 17:04:05 -0700136 public int clearBackupData(PackageInfo packageInfo) {
Christopher Tateee0e78a2009-07-02 11:17:03 -0700137 if (DEBUG) Log.v(TAG, "clearBackupData() pkg=" + packageInfo.packageName);
138
139 File packageDir = new File(mDataDir, packageInfo.packageName);
140 for (File f : packageDir.listFiles()) {
141 f.delete();
142 }
143 packageDir.delete();
Dan Egnor01445162009-09-21 17:04:05 -0700144 return BackupConstants.TRANSPORT_OK;
Christopher Tateee0e78a2009-07-02 11:17:03 -0700145 }
146
Dan Egnor01445162009-09-21 17:04:05 -0700147 public int finishBackup() {
Dan Egnorefe52642009-06-24 00:16:33 -0700148 if (DEBUG) Log.v(TAG, "finishBackup()");
Dan Egnor01445162009-09-21 17:04:05 -0700149 return BackupConstants.TRANSPORT_OK;
Christopher Tate9bbc21a2009-06-10 20:23:25 -0700150 }
151
152 // Restore handling
153 public RestoreSet[] getAvailableRestoreSets() throws android.os.RemoteException {
154 // one hardcoded restore set
Christopher Tate50c6df02010-01-29 12:48:20 -0800155 RestoreSet set = new RestoreSet("Local disk image", "flash", RESTORE_TOKEN);
Christopher Tatef68eb502009-06-16 11:02:01 -0700156 RestoreSet[] array = { set };
157 return array;
Christopher Tate9bbc21a2009-06-10 20:23:25 -0700158 }
159
Christopher Tate50c6df02010-01-29 12:48:20 -0800160 public long getCurrentRestoreSet() {
161 // The hardcoded restore set always has the same token
162 return RESTORE_TOKEN;
163 }
164
Dan Egnor01445162009-09-21 17:04:05 -0700165 public int startRestore(long token, PackageInfo[] packages) {
Dan Egnorefe52642009-06-24 00:16:33 -0700166 if (DEBUG) Log.v(TAG, "start restore " + token);
167 mRestorePackages = packages;
168 mRestorePackage = -1;
Dan Egnor01445162009-09-21 17:04:05 -0700169 return BackupConstants.TRANSPORT_OK;
Christopher Tate9bbc21a2009-06-10 20:23:25 -0700170 }
171
Dan Egnorefe52642009-06-24 00:16:33 -0700172 public String nextRestorePackage() {
173 if (mRestorePackages == null) throw new IllegalStateException("startRestore not called");
174 while (++mRestorePackage < mRestorePackages.length) {
175 String name = mRestorePackages[mRestorePackage].packageName;
176 if (new File(mDataDir, name).isDirectory()) {
177 if (DEBUG) Log.v(TAG, " nextRestorePackage() = " + name);
178 return name;
179 }
180 }
Christopher Tate9bbc21a2009-06-10 20:23:25 -0700181
Dan Egnorefe52642009-06-24 00:16:33 -0700182 if (DEBUG) Log.v(TAG, " no more packages to restore");
183 return "";
184 }
185
Dan Egnor01445162009-09-21 17:04:05 -0700186 public int getRestoreData(ParcelFileDescriptor outFd) {
Dan Egnorefe52642009-06-24 00:16:33 -0700187 if (mRestorePackages == null) throw new IllegalStateException("startRestore not called");
188 if (mRestorePackage < 0) throw new IllegalStateException("nextRestorePackage not called");
189 File packageDir = new File(mDataDir, mRestorePackages[mRestorePackage].packageName);
Christopher Tate9bbc21a2009-06-10 20:23:25 -0700190
Christopher Tate2fdd4282009-06-12 15:20:04 -0700191 // The restore set is the concatenation of the individual record blobs,
192 // each of which is a file in the package's directory
193 File[] blobs = packageDir.listFiles();
Dan Egnor01445162009-09-21 17:04:05 -0700194 if (blobs == null) { // nextRestorePackage() ensures the dir exists, so this is an error
Dan Egnorefe52642009-06-24 00:16:33 -0700195 Log.e(TAG, "Error listing directory: " + packageDir);
Dan Egnor01445162009-09-21 17:04:05 -0700196 return BackupConstants.TRANSPORT_ERROR;
Christopher Tate2fdd4282009-06-12 15:20:04 -0700197 }
Dan Egnorefe52642009-06-24 00:16:33 -0700198
199 // We expect at least some data if the directory exists in the first place
200 if (DEBUG) Log.v(TAG, " getRestoreData() found " + blobs.length + " key files");
201 BackupDataOutput out = new BackupDataOutput(outFd.getFileDescriptor());
202 try {
203 for (File f : blobs) {
204 FileInputStream in = new FileInputStream(f);
205 try {
206 int size = (int) f.length();
207 byte[] buf = new byte[size];
208 in.read(buf);
209 String key = new String(Base64.decode(f.getName()));
210 if (DEBUG) Log.v(TAG, " ... key=" + key + " size=" + size);
211 out.writeEntityHeader(key, size);
212 out.writeEntityData(buf, size);
213 } finally {
214 in.close();
215 }
216 }
Dan Egnor01445162009-09-21 17:04:05 -0700217 return BackupConstants.TRANSPORT_OK;
Dan Egnorefe52642009-06-24 00:16:33 -0700218 } catch (IOException e) {
219 Log.e(TAG, "Unable to read backup records", e);
Dan Egnor01445162009-09-21 17:04:05 -0700220 return BackupConstants.TRANSPORT_ERROR;
Dan Egnorefe52642009-06-24 00:16:33 -0700221 }
Christopher Tate9bbc21a2009-06-10 20:23:25 -0700222 }
Christopher Tate3a31a932009-06-22 15:10:30 -0700223
Dan Egnorefe52642009-06-24 00:16:33 -0700224 public void finishRestore() {
225 if (DEBUG) Log.v(TAG, "finishRestore()");
Christopher Tate3a31a932009-06-22 15:10:30 -0700226 }
Christopher Tate9bbc21a2009-06-10 20:23:25 -0700227}