blob: b4363638329706dff05ed8a900d481527d93e2a8 [file] [log] [blame]
Kenny Root15a4d2f2010-03-11 18:20:12 -08001/*
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
Christopher Tate9bbc21a2009-06-10 20:23:25 -070017package com.android.internal.backup;
18
Christopher Tate45281862010-03-05 15:46:30 -080019import android.app.backup.BackupDataInput;
20import android.app.backup.BackupDataOutput;
21import android.app.backup.RestoreSet;
Christopher Tate9bbc21a2009-06-10 20:23:25 -070022import android.content.Context;
23import android.content.pm.PackageInfo;
24import android.content.pm.PackageManager;
25import android.content.pm.PackageManager.NameNotFoundException;
26import android.os.Environment;
27import android.os.ParcelFileDescriptor;
28import android.os.RemoteException;
29import android.util.Log;
30
Christopher Tatee9190a22009-06-17 17:52:05 -070031import org.bouncycastle.util.encoders.Base64;
32
Christopher Tate9bbc21a2009-06-10 20:23:25 -070033import java.io.File;
34import java.io.FileFilter;
35import java.io.FileInputStream;
36import java.io.FileOutputStream;
37import java.io.IOException;
38import java.util.ArrayList;
39
40/**
41 * Backup transport for stashing stuff into a known location on disk, and
42 * later restoring from there. For testing only.
43 */
44
45public class LocalTransport extends IBackupTransport.Stub {
46 private static final String TAG = "LocalTransport";
Christopher Tate2fdd4282009-06-12 15:20:04 -070047 private static final boolean DEBUG = true;
Christopher Tate9bbc21a2009-06-10 20:23:25 -070048
Christopher Tate5cb400b2009-06-25 16:03:14 -070049 private static final String TRANSPORT_DIR_NAME
50 = "com.android.internal.backup.LocalTransport";
51
Christopher Tate50c6df02010-01-29 12:48:20 -080052 // The single hardcoded restore set always has the same (nonzero!) token
53 private static final long RESTORE_TOKEN = 1;
54
Christopher Tate9bbc21a2009-06-10 20:23:25 -070055 private Context mContext;
56 private PackageManager mPackageManager;
57 private File mDataDir = new File(Environment.getDownloadCacheDirectory(), "backup");
Dan Egnorefe52642009-06-24 00:16:33 -070058 private PackageInfo[] mRestorePackages = null;
59 private int mRestorePackage = -1; // Index into mRestorePackages
Christopher Tate9bbc21a2009-06-10 20:23:25 -070060
61
62 public LocalTransport(Context context) {
63 mContext = context;
64 mPackageManager = context.getPackageManager();
65 }
66
Christopher Tate5cb400b2009-06-25 16:03:14 -070067
Dan Egnor01445162009-09-21 17:04:05 -070068 public String transportDirName() {
Christopher Tate5cb400b2009-06-25 16:03:14 -070069 return TRANSPORT_DIR_NAME;
70 }
71
Dan Egnor01445162009-09-21 17:04:05 -070072 public long requestBackupTime() {
Christopher Tate9bbc21a2009-06-10 20:23:25 -070073 // any time is a good time for local backup
74 return 0;
75 }
76
Dan Egnor01445162009-09-21 17:04:05 -070077 public int initializeDevice() {
78 if (DEBUG) Log.v(TAG, "wiping all data");
79 deleteContents(mDataDir);
80 return BackupConstants.TRANSPORT_OK;
81 }
82
83 public int performBackup(PackageInfo packageInfo, ParcelFileDescriptor data) {
Christopher Tate2fdd4282009-06-12 15:20:04 -070084 if (DEBUG) Log.v(TAG, "performBackup() pkg=" + packageInfo.packageName);
Christopher Tate2fdd4282009-06-12 15:20:04 -070085
Christopher Tate9bbc21a2009-06-10 20:23:25 -070086 File packageDir = new File(mDataDir, packageInfo.packageName);
Christopher Tate2fdd4282009-06-12 15:20:04 -070087 packageDir.mkdirs();
Christopher Tate9bbc21a2009-06-10 20:23:25 -070088
Christopher Tate2fdd4282009-06-12 15:20:04 -070089 // Each 'record' in the restore set is kept in its own file, named by
90 // the record key. Wind through the data file, extracting individual
91 // record operations and building a set of all the updates to apply
92 // in this update.
93 BackupDataInput changeSet = new BackupDataInput(data.getFileDescriptor());
94 try {
95 int bufSize = 512;
96 byte[] buf = new byte[bufSize];
97 while (changeSet.readNextHeader()) {
98 String key = changeSet.getKey();
Joe Onorato5d605dc2009-06-18 18:23:43 -070099 String base64Key = new String(Base64.encode(key.getBytes()));
100 File entityFile = new File(packageDir, base64Key);
101
Christopher Tate2fdd4282009-06-12 15:20:04 -0700102 int dataSize = changeSet.getDataSize();
Christopher Tatee9190a22009-06-17 17:52:05 -0700103
Christopher Tatee9190a22009-06-17 17:52:05 -0700104 if (DEBUG) Log.v(TAG, "Got change set key=" + key + " size=" + dataSize
105 + " key64=" + base64Key);
Christopher Tate9bbc21a2009-06-10 20:23:25 -0700106
Joe Onorato5d605dc2009-06-18 18:23:43 -0700107 if (dataSize >= 0) {
Dianne Hackborn1afd1c92010-03-18 22:47:17 -0700108 if (entityFile.exists()) {
109 entityFile.delete();
110 }
Joe Onorato5d605dc2009-06-18 18:23:43 -0700111 FileOutputStream entity = new FileOutputStream(entityFile);
112
113 if (dataSize > bufSize) {
114 bufSize = dataSize;
115 buf = new byte[bufSize];
116 }
117 changeSet.readEntityData(buf, 0, dataSize);
118 if (DEBUG) Log.v(TAG, " data size " + dataSize);
119
120 try {
121 entity.write(buf, 0, dataSize);
122 } catch (IOException e) {
Dan Egnorefe52642009-06-24 00:16:33 -0700123 Log.e(TAG, "Unable to update key file " + entityFile.getAbsolutePath());
Christopher Tated55e18a2009-09-21 10:12:59 -0700124 return BackupConstants.TRANSPORT_ERROR;
Joe Onorato5d605dc2009-06-18 18:23:43 -0700125 } finally {
126 entity.close();
127 }
128 } else {
129 entityFile.delete();
Christopher Tate2fdd4282009-06-12 15:20:04 -0700130 }
131 }
Christopher Tated55e18a2009-09-21 10:12:59 -0700132 return BackupConstants.TRANSPORT_OK;
Christopher Tate2fdd4282009-06-12 15:20:04 -0700133 } catch (IOException e) {
134 // oops, something went wrong. abort the operation and return error.
Dan Egnorefe52642009-06-24 00:16:33 -0700135 Log.v(TAG, "Exception reading backup input:", e);
Christopher Tated55e18a2009-09-21 10:12:59 -0700136 return BackupConstants.TRANSPORT_ERROR;
Christopher Tate2fdd4282009-06-12 15:20:04 -0700137 }
Dan Egnorefe52642009-06-24 00:16:33 -0700138 }
Christopher Tate9bbc21a2009-06-10 20:23:25 -0700139
Christopher Tate25a747f2009-09-20 12:43:58 -0700140 // Deletes the contents but not the given directory
141 private void deleteContents(File dirname) {
142 File[] contents = dirname.listFiles();
143 if (contents != null) {
144 for (File f : contents) {
145 if (f.isDirectory()) {
146 // delete the directory's contents then fall through
147 // and delete the directory itself.
148 deleteContents(f);
149 }
150 f.delete();
151 }
152 }
153 }
154
Dan Egnor01445162009-09-21 17:04:05 -0700155 public int clearBackupData(PackageInfo packageInfo) {
Christopher Tateee0e78a2009-07-02 11:17:03 -0700156 if (DEBUG) Log.v(TAG, "clearBackupData() pkg=" + packageInfo.packageName);
157
158 File packageDir = new File(mDataDir, packageInfo.packageName);
159 for (File f : packageDir.listFiles()) {
160 f.delete();
161 }
162 packageDir.delete();
Dan Egnor01445162009-09-21 17:04:05 -0700163 return BackupConstants.TRANSPORT_OK;
Christopher Tateee0e78a2009-07-02 11:17:03 -0700164 }
165
Dan Egnor01445162009-09-21 17:04:05 -0700166 public int finishBackup() {
Dan Egnorefe52642009-06-24 00:16:33 -0700167 if (DEBUG) Log.v(TAG, "finishBackup()");
Dan Egnor01445162009-09-21 17:04:05 -0700168 return BackupConstants.TRANSPORT_OK;
Christopher Tate9bbc21a2009-06-10 20:23:25 -0700169 }
170
171 // Restore handling
172 public RestoreSet[] getAvailableRestoreSets() throws android.os.RemoteException {
173 // one hardcoded restore set
Christopher Tate50c6df02010-01-29 12:48:20 -0800174 RestoreSet set = new RestoreSet("Local disk image", "flash", RESTORE_TOKEN);
Christopher Tatef68eb502009-06-16 11:02:01 -0700175 RestoreSet[] array = { set };
176 return array;
Christopher Tate9bbc21a2009-06-10 20:23:25 -0700177 }
178
Christopher Tate50c6df02010-01-29 12:48:20 -0800179 public long getCurrentRestoreSet() {
180 // The hardcoded restore set always has the same token
181 return RESTORE_TOKEN;
182 }
183
Dan Egnor01445162009-09-21 17:04:05 -0700184 public int startRestore(long token, PackageInfo[] packages) {
Dan Egnorefe52642009-06-24 00:16:33 -0700185 if (DEBUG) Log.v(TAG, "start restore " + token);
186 mRestorePackages = packages;
187 mRestorePackage = -1;
Dan Egnor01445162009-09-21 17:04:05 -0700188 return BackupConstants.TRANSPORT_OK;
Christopher Tate9bbc21a2009-06-10 20:23:25 -0700189 }
190
Dan Egnorefe52642009-06-24 00:16:33 -0700191 public String nextRestorePackage() {
192 if (mRestorePackages == null) throw new IllegalStateException("startRestore not called");
193 while (++mRestorePackage < mRestorePackages.length) {
194 String name = mRestorePackages[mRestorePackage].packageName;
195 if (new File(mDataDir, name).isDirectory()) {
196 if (DEBUG) Log.v(TAG, " nextRestorePackage() = " + name);
197 return name;
198 }
199 }
Christopher Tate9bbc21a2009-06-10 20:23:25 -0700200
Dan Egnorefe52642009-06-24 00:16:33 -0700201 if (DEBUG) Log.v(TAG, " no more packages to restore");
202 return "";
203 }
204
Dan Egnor01445162009-09-21 17:04:05 -0700205 public int getRestoreData(ParcelFileDescriptor outFd) {
Dan Egnorefe52642009-06-24 00:16:33 -0700206 if (mRestorePackages == null) throw new IllegalStateException("startRestore not called");
207 if (mRestorePackage < 0) throw new IllegalStateException("nextRestorePackage not called");
208 File packageDir = new File(mDataDir, mRestorePackages[mRestorePackage].packageName);
Christopher Tate9bbc21a2009-06-10 20:23:25 -0700209
Christopher Tate2fdd4282009-06-12 15:20:04 -0700210 // The restore set is the concatenation of the individual record blobs,
211 // each of which is a file in the package's directory
212 File[] blobs = packageDir.listFiles();
Dan Egnor01445162009-09-21 17:04:05 -0700213 if (blobs == null) { // nextRestorePackage() ensures the dir exists, so this is an error
Dan Egnorefe52642009-06-24 00:16:33 -0700214 Log.e(TAG, "Error listing directory: " + packageDir);
Dan Egnor01445162009-09-21 17:04:05 -0700215 return BackupConstants.TRANSPORT_ERROR;
Christopher Tate2fdd4282009-06-12 15:20:04 -0700216 }
Dan Egnorefe52642009-06-24 00:16:33 -0700217
218 // We expect at least some data if the directory exists in the first place
219 if (DEBUG) Log.v(TAG, " getRestoreData() found " + blobs.length + " key files");
220 BackupDataOutput out = new BackupDataOutput(outFd.getFileDescriptor());
221 try {
222 for (File f : blobs) {
223 FileInputStream in = new FileInputStream(f);
224 try {
225 int size = (int) f.length();
226 byte[] buf = new byte[size];
227 in.read(buf);
228 String key = new String(Base64.decode(f.getName()));
229 if (DEBUG) Log.v(TAG, " ... key=" + key + " size=" + size);
230 out.writeEntityHeader(key, size);
231 out.writeEntityData(buf, size);
232 } finally {
233 in.close();
234 }
235 }
Dan Egnor01445162009-09-21 17:04:05 -0700236 return BackupConstants.TRANSPORT_OK;
Dan Egnorefe52642009-06-24 00:16:33 -0700237 } catch (IOException e) {
238 Log.e(TAG, "Unable to read backup records", e);
Dan Egnor01445162009-09-21 17:04:05 -0700239 return BackupConstants.TRANSPORT_ERROR;
Dan Egnorefe52642009-06-24 00:16:33 -0700240 }
Christopher Tate9bbc21a2009-06-10 20:23:25 -0700241 }
Christopher Tate3a31a932009-06-22 15:10:30 -0700242
Dan Egnorefe52642009-06-24 00:16:33 -0700243 public void finishRestore() {
244 if (DEBUG) Log.v(TAG, "finishRestore()");
Christopher Tate3a31a932009-06-22 15:10:30 -0700245 }
Christopher Tate9bbc21a2009-06-10 20:23:25 -0700246}