blob: 2facce2ed190188ddba5527f167754eb1fad7f8c [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
Dan Egnorefe52642009-06-24 00:16:33 -070059 public boolean performBackup(PackageInfo packageInfo, ParcelFileDescriptor data)
Christopher Tate9bbc21a2009-06-10 20:23:25 -070060 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 Tate9bbc21a2009-06-10 20:23:25 -070065
Christopher Tate2fdd4282009-06-12 15:20:04 -070066 // Each 'record' in the restore set is kept in its own file, named by
67 // the record key. Wind through the data file, extracting individual
68 // record operations and building a set of all the updates to apply
69 // in this update.
70 BackupDataInput changeSet = new BackupDataInput(data.getFileDescriptor());
71 try {
72 int bufSize = 512;
73 byte[] buf = new byte[bufSize];
74 while (changeSet.readNextHeader()) {
75 String key = changeSet.getKey();
Joe Onorato5d605dc2009-06-18 18:23:43 -070076 String base64Key = new String(Base64.encode(key.getBytes()));
77 File entityFile = new File(packageDir, base64Key);
78
Christopher Tate2fdd4282009-06-12 15:20:04 -070079 int dataSize = changeSet.getDataSize();
Christopher Tatee9190a22009-06-17 17:52:05 -070080
Christopher Tatee9190a22009-06-17 17:52:05 -070081 if (DEBUG) Log.v(TAG, "Got change set key=" + key + " size=" + dataSize
82 + " key64=" + base64Key);
Christopher Tate9bbc21a2009-06-10 20:23:25 -070083
Joe Onorato5d605dc2009-06-18 18:23:43 -070084 if (dataSize >= 0) {
85 FileOutputStream entity = new FileOutputStream(entityFile);
86
87 if (dataSize > bufSize) {
88 bufSize = dataSize;
89 buf = new byte[bufSize];
90 }
91 changeSet.readEntityData(buf, 0, dataSize);
92 if (DEBUG) Log.v(TAG, " data size " + dataSize);
93
94 try {
95 entity.write(buf, 0, dataSize);
96 } catch (IOException e) {
Dan Egnorefe52642009-06-24 00:16:33 -070097 Log.e(TAG, "Unable to update key file " + entityFile.getAbsolutePath());
98 return false;
Joe Onorato5d605dc2009-06-18 18:23:43 -070099 } finally {
100 entity.close();
101 }
102 } else {
103 entityFile.delete();
Christopher Tate2fdd4282009-06-12 15:20:04 -0700104 }
105 }
Dan Egnorefe52642009-06-24 00:16:33 -0700106 return true;
Christopher Tate2fdd4282009-06-12 15:20:04 -0700107 } catch (IOException e) {
108 // oops, something went wrong. abort the operation and return error.
Dan Egnorefe52642009-06-24 00:16:33 -0700109 Log.v(TAG, "Exception reading backup input:", e);
110 return false;
Christopher Tate2fdd4282009-06-12 15:20:04 -0700111 }
Dan Egnorefe52642009-06-24 00:16:33 -0700112 }
Christopher Tate9bbc21a2009-06-10 20:23:25 -0700113
Christopher Tateee0e78a2009-07-02 11:17:03 -0700114 public boolean clearBackupData(PackageInfo packageInfo) {
115 if (DEBUG) Log.v(TAG, "clearBackupData() pkg=" + packageInfo.packageName);
116
117 File packageDir = new File(mDataDir, packageInfo.packageName);
118 for (File f : packageDir.listFiles()) {
119 f.delete();
120 }
121 packageDir.delete();
122 return true;
123 }
124
Dan Egnorefe52642009-06-24 00:16:33 -0700125 public boolean finishBackup() throws RemoteException {
126 if (DEBUG) Log.v(TAG, "finishBackup()");
127 return true;
Christopher Tate9bbc21a2009-06-10 20:23:25 -0700128 }
129
130 // Restore handling
131 public RestoreSet[] getAvailableRestoreSets() throws android.os.RemoteException {
132 // one hardcoded restore set
Christopher Tatef68eb502009-06-16 11:02:01 -0700133 RestoreSet set = new RestoreSet("Local disk image", "flash", 0);
134 RestoreSet[] array = { set };
135 return array;
Christopher Tate9bbc21a2009-06-10 20:23:25 -0700136 }
137
Dan Egnorefe52642009-06-24 00:16:33 -0700138 public boolean startRestore(long token, PackageInfo[] packages) {
139 if (DEBUG) Log.v(TAG, "start restore " + token);
140 mRestorePackages = packages;
141 mRestorePackage = -1;
142 return true;
Christopher Tate9bbc21a2009-06-10 20:23:25 -0700143 }
144
Dan Egnorefe52642009-06-24 00:16:33 -0700145 public String nextRestorePackage() {
146 if (mRestorePackages == null) throw new IllegalStateException("startRestore not called");
147 while (++mRestorePackage < mRestorePackages.length) {
148 String name = mRestorePackages[mRestorePackage].packageName;
149 if (new File(mDataDir, name).isDirectory()) {
150 if (DEBUG) Log.v(TAG, " nextRestorePackage() = " + name);
151 return name;
152 }
153 }
Christopher Tate9bbc21a2009-06-10 20:23:25 -0700154
Dan Egnorefe52642009-06-24 00:16:33 -0700155 if (DEBUG) Log.v(TAG, " no more packages to restore");
156 return "";
157 }
158
159 public boolean getRestoreData(ParcelFileDescriptor outFd) {
160 if (mRestorePackages == null) throw new IllegalStateException("startRestore not called");
161 if (mRestorePackage < 0) throw new IllegalStateException("nextRestorePackage not called");
162 File packageDir = new File(mDataDir, mRestorePackages[mRestorePackage].packageName);
Christopher Tate9bbc21a2009-06-10 20:23:25 -0700163
Christopher Tate2fdd4282009-06-12 15:20:04 -0700164 // The restore set is the concatenation of the individual record blobs,
165 // each of which is a file in the package's directory
166 File[] blobs = packageDir.listFiles();
Dan Egnorefe52642009-06-24 00:16:33 -0700167 if (blobs == null) {
168 Log.e(TAG, "Error listing directory: " + packageDir);
169 return false; // nextRestorePackage() ensures the dir exists, so this is an error
Christopher Tate2fdd4282009-06-12 15:20:04 -0700170 }
Dan Egnorefe52642009-06-24 00:16:33 -0700171
172 // We expect at least some data if the directory exists in the first place
173 if (DEBUG) Log.v(TAG, " getRestoreData() found " + blobs.length + " key files");
174 BackupDataOutput out = new BackupDataOutput(outFd.getFileDescriptor());
175 try {
176 for (File f : blobs) {
177 FileInputStream in = new FileInputStream(f);
178 try {
179 int size = (int) f.length();
180 byte[] buf = new byte[size];
181 in.read(buf);
182 String key = new String(Base64.decode(f.getName()));
183 if (DEBUG) Log.v(TAG, " ... key=" + key + " size=" + size);
184 out.writeEntityHeader(key, size);
185 out.writeEntityData(buf, size);
186 } finally {
187 in.close();
188 }
189 }
190 return true;
191 } catch (IOException e) {
192 Log.e(TAG, "Unable to read backup records", e);
193 return false;
194 }
Christopher Tate9bbc21a2009-06-10 20:23:25 -0700195 }
Christopher Tate3a31a932009-06-22 15:10:30 -0700196
Dan Egnorefe52642009-06-24 00:16:33 -0700197 public void finishRestore() {
198 if (DEBUG) Log.v(TAG, "finishRestore()");
Christopher Tate3a31a932009-06-22 15:10:30 -0700199 }
Christopher Tate9bbc21a2009-06-10 20:23:25 -0700200}