LocalTransport.java revision bd0a81ff1c0e92e80e05e2f12bb1805c7d081e94
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    private Context mContext;
37    private PackageManager mPackageManager;
38    private File mDataDir = new File(Environment.getDownloadCacheDirectory(), "backup");
39    private PackageInfo[] mRestorePackages = null;
40    private int mRestorePackage = -1;  // Index into mRestorePackages
41
42
43    public LocalTransport(Context context) {
44        mContext = context;
45        mPackageManager = context.getPackageManager();
46    }
47
48
49    public String transportDirName() {
50        return TRANSPORT_DIR_NAME;
51    }
52
53    public long requestBackupTime() {
54        // any time is a good time for local backup
55        return 0;
56    }
57
58    public int initializeDevice() {
59        if (DEBUG) Log.v(TAG, "wiping all data");
60        deleteContents(mDataDir);
61        return BackupConstants.TRANSPORT_OK;
62    }
63
64    public int performBackup(PackageInfo packageInfo, ParcelFileDescriptor data) {
65        if (DEBUG) Log.v(TAG, "performBackup() pkg=" + packageInfo.packageName);
66
67        File packageDir = new File(mDataDir, packageInfo.packageName);
68        packageDir.mkdirs();
69
70        // 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();
80                String base64Key = new String(Base64.encode(key.getBytes()));
81                File entityFile = new File(packageDir, base64Key);
82
83                int dataSize = changeSet.getDataSize();
84
85                if (DEBUG) Log.v(TAG, "Got change set key=" + key + " size=" + dataSize
86                        + " key64=" + base64Key);
87
88                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) {
101                        Log.e(TAG, "Unable to update key file " + entityFile.getAbsolutePath());
102                        return BackupConstants.TRANSPORT_ERROR;
103                    } finally {
104                        entity.close();
105                    }
106                } else {
107                    entityFile.delete();
108                }
109            }
110            return BackupConstants.TRANSPORT_OK;
111        } catch (IOException e) {
112            // oops, something went wrong.  abort the operation and return error.
113            Log.v(TAG, "Exception reading backup input:", e);
114            return BackupConstants.TRANSPORT_ERROR;
115        }
116    }
117
118    // 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
133    public int 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 BackupConstants.TRANSPORT_OK;
142    }
143
144    public int finishBackup() {
145        if (DEBUG) Log.v(TAG, "finishBackup()");
146        return BackupConstants.TRANSPORT_OK;
147    }
148
149    // Restore handling
150    public RestoreSet[] getAvailableRestoreSets() throws android.os.RemoteException {
151        // one hardcoded restore set
152        RestoreSet set = new RestoreSet("Local disk image", "flash", 0);
153        RestoreSet[] array = { set };
154        return array;
155    }
156
157    public int startRestore(long token, PackageInfo[] packages) {
158        if (DEBUG) Log.v(TAG, "start restore " + token);
159        mRestorePackages = packages;
160        mRestorePackage = -1;
161        return BackupConstants.TRANSPORT_OK;
162    }
163
164    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        }
173
174        if (DEBUG) Log.v(TAG, "  no more packages to restore");
175        return "";
176    }
177
178    public int 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);
182
183        // 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();
186        if (blobs == null) {  // nextRestorePackage() ensures the dir exists, so this is an error
187            Log.e(TAG, "Error listing directory: " + packageDir);
188            return BackupConstants.TRANSPORT_ERROR;
189        }
190
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 BackupConstants.TRANSPORT_OK;
210        } catch (IOException e) {
211            Log.e(TAG, "Unable to read backup records", e);
212            return BackupConstants.TRANSPORT_ERROR;
213        }
214    }
215
216    public void finishRestore() {
217        if (DEBUG) Log.v(TAG, "finishRestore()");
218    }
219}
220