LocalTransport.java revision 50c6df04cf17a99c3959c306a4e0e10da6d85c46
1package com.android.internal.backup;
2
3import android.backup.BackupDataInput;
4import android.backup.BackupDataOutput;
5import 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
15import org.bouncycastle.util.encoders.Base64;
16
17import 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";
31    private static final boolean DEBUG = true;
32
33    private static final String TRANSPORT_DIR_NAME
34            = "com.android.internal.backup.LocalTransport";
35
36    // The single hardcoded restore set always has the same (nonzero!) token
37    private static final long RESTORE_TOKEN = 1;
38
39    private Context mContext;
40    private PackageManager mPackageManager;
41    private File mDataDir = new File(Environment.getDownloadCacheDirectory(), "backup");
42    private PackageInfo[] mRestorePackages = null;
43    private int mRestorePackage = -1;  // Index into mRestorePackages
44
45
46    public LocalTransport(Context context) {
47        mContext = context;
48        mPackageManager = context.getPackageManager();
49    }
50
51
52    public String transportDirName() {
53        return TRANSPORT_DIR_NAME;
54    }
55
56    public long requestBackupTime() {
57        // any time is a good time for local backup
58        return 0;
59    }
60
61    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) {
68        if (DEBUG) Log.v(TAG, "performBackup() pkg=" + packageInfo.packageName);
69
70        File packageDir = new File(mDataDir, packageInfo.packageName);
71        packageDir.mkdirs();
72
73        // 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();
83                String base64Key = new String(Base64.encode(key.getBytes()));
84                File entityFile = new File(packageDir, base64Key);
85
86                int dataSize = changeSet.getDataSize();
87
88                if (DEBUG) Log.v(TAG, "Got change set key=" + key + " size=" + dataSize
89                        + " key64=" + base64Key);
90
91                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) {
104                        Log.e(TAG, "Unable to update key file " + entityFile.getAbsolutePath());
105                        return BackupConstants.TRANSPORT_ERROR;
106                    } finally {
107                        entity.close();
108                    }
109                } else {
110                    entityFile.delete();
111                }
112            }
113            return BackupConstants.TRANSPORT_OK;
114        } catch (IOException e) {
115            // oops, something went wrong.  abort the operation and return error.
116            Log.v(TAG, "Exception reading backup input:", e);
117            return BackupConstants.TRANSPORT_ERROR;
118        }
119    }
120
121    // 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
136    public int clearBackupData(PackageInfo packageInfo) {
137        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();
144        return BackupConstants.TRANSPORT_OK;
145    }
146
147    public int finishBackup() {
148        if (DEBUG) Log.v(TAG, "finishBackup()");
149        return BackupConstants.TRANSPORT_OK;
150    }
151
152    // Restore handling
153    public RestoreSet[] getAvailableRestoreSets() throws android.os.RemoteException {
154        // one hardcoded restore set
155        RestoreSet set = new RestoreSet("Local disk image", "flash", RESTORE_TOKEN);
156        RestoreSet[] array = { set };
157        return array;
158    }
159
160    public long getCurrentRestoreSet() {
161        // The hardcoded restore set always has the same token
162        return RESTORE_TOKEN;
163    }
164
165    public int startRestore(long token, PackageInfo[] packages) {
166        if (DEBUG) Log.v(TAG, "start restore " + token);
167        mRestorePackages = packages;
168        mRestorePackage = -1;
169        return BackupConstants.TRANSPORT_OK;
170    }
171
172    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        }
181
182        if (DEBUG) Log.v(TAG, "  no more packages to restore");
183        return "";
184    }
185
186    public int getRestoreData(ParcelFileDescriptor outFd) {
187        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);
190
191        // 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();
194        if (blobs == null) {  // nextRestorePackage() ensures the dir exists, so this is an error
195            Log.e(TAG, "Error listing directory: " + packageDir);
196            return BackupConstants.TRANSPORT_ERROR;
197        }
198
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            }
217            return BackupConstants.TRANSPORT_OK;
218        } catch (IOException e) {
219            Log.e(TAG, "Unable to read backup records", e);
220            return BackupConstants.TRANSPORT_ERROR;
221        }
222    }
223
224    public void finishRestore() {
225        if (DEBUG) Log.v(TAG, "finishRestore()");
226    }
227}
228