LocalTransport.java revision 0144516e19b9fd5415a56f8b41191187e2344bb0
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        if (DEBUG) Log.v(TAG, "Transport constructed");
45        mContext = context;
46        mPackageManager = context.getPackageManager();
47    }
48
49
50    public String transportDirName() {
51        return TRANSPORT_DIR_NAME;
52    }
53
54    public long requestBackupTime() {
55        // any time is a good time for local backup
56        return 0;
57    }
58
59    public int initializeDevice() {
60        if (DEBUG) Log.v(TAG, "wiping all data");
61        deleteContents(mDataDir);
62        return BackupConstants.TRANSPORT_OK;
63    }
64
65    public int performBackup(PackageInfo packageInfo, ParcelFileDescriptor data) {
66        if (DEBUG) Log.v(TAG, "performBackup() pkg=" + packageInfo.packageName);
67
68        File packageDir = new File(mDataDir, packageInfo.packageName);
69        packageDir.mkdirs();
70
71        // Each 'record' in the restore set is kept in its own file, named by
72        // the record key.  Wind through the data file, extracting individual
73        // record operations and building a set of all the updates to apply
74        // in this update.
75        BackupDataInput changeSet = new BackupDataInput(data.getFileDescriptor());
76        try {
77            int bufSize = 512;
78            byte[] buf = new byte[bufSize];
79            while (changeSet.readNextHeader()) {
80                String key = changeSet.getKey();
81                String base64Key = new String(Base64.encode(key.getBytes()));
82                File entityFile = new File(packageDir, base64Key);
83
84                int dataSize = changeSet.getDataSize();
85
86                if (DEBUG) Log.v(TAG, "Got change set key=" + key + " size=" + dataSize
87                        + " key64=" + base64Key);
88
89                if (dataSize >= 0) {
90                    FileOutputStream entity = new FileOutputStream(entityFile);
91
92                    if (dataSize > bufSize) {
93                        bufSize = dataSize;
94                        buf = new byte[bufSize];
95                    }
96                    changeSet.readEntityData(buf, 0, dataSize);
97                    if (DEBUG) Log.v(TAG, "  data size " + dataSize);
98
99                    try {
100                        entity.write(buf, 0, dataSize);
101                    } catch (IOException e) {
102                        Log.e(TAG, "Unable to update key file " + entityFile.getAbsolutePath());
103                        return BackupConstants.TRANSPORT_ERROR;
104                    } finally {
105                        entity.close();
106                    }
107                } else {
108                    entityFile.delete();
109                }
110            }
111            return BackupConstants.TRANSPORT_OK;
112        } catch (IOException e) {
113            // oops, something went wrong.  abort the operation and return error.
114            Log.v(TAG, "Exception reading backup input:", e);
115            return BackupConstants.TRANSPORT_ERROR;
116        }
117    }
118
119    // Deletes the contents but not the given directory
120    private void deleteContents(File dirname) {
121        File[] contents = dirname.listFiles();
122        if (contents != null) {
123            for (File f : contents) {
124                if (f.isDirectory()) {
125                    // delete the directory's contents then fall through
126                    // and delete the directory itself.
127                    deleteContents(f);
128                }
129                f.delete();
130            }
131        }
132    }
133
134    public int clearBackupData(PackageInfo packageInfo) {
135        if (DEBUG) Log.v(TAG, "clearBackupData() pkg=" + packageInfo.packageName);
136
137        File packageDir = new File(mDataDir, packageInfo.packageName);
138        for (File f : packageDir.listFiles()) {
139            f.delete();
140        }
141        packageDir.delete();
142        return BackupConstants.TRANSPORT_OK;
143    }
144
145    public int finishBackup() {
146        if (DEBUG) Log.v(TAG, "finishBackup()");
147        return BackupConstants.TRANSPORT_OK;
148    }
149
150    // Restore handling
151    public RestoreSet[] getAvailableRestoreSets() throws android.os.RemoteException {
152        // one hardcoded restore set
153        RestoreSet set = new RestoreSet("Local disk image", "flash", 0);
154        RestoreSet[] array = { set };
155        return array;
156    }
157
158    public int startRestore(long token, PackageInfo[] packages) {
159        if (DEBUG) Log.v(TAG, "start restore " + token);
160        mRestorePackages = packages;
161        mRestorePackage = -1;
162        return BackupConstants.TRANSPORT_OK;
163    }
164
165    public String nextRestorePackage() {
166        if (mRestorePackages == null) throw new IllegalStateException("startRestore not called");
167        while (++mRestorePackage < mRestorePackages.length) {
168            String name = mRestorePackages[mRestorePackage].packageName;
169            if (new File(mDataDir, name).isDirectory()) {
170                if (DEBUG) Log.v(TAG, "  nextRestorePackage() = " + name);
171                return name;
172            }
173        }
174
175        if (DEBUG) Log.v(TAG, "  no more packages to restore");
176        return "";
177    }
178
179    public int getRestoreData(ParcelFileDescriptor outFd) {
180        if (mRestorePackages == null) throw new IllegalStateException("startRestore not called");
181        if (mRestorePackage < 0) throw new IllegalStateException("nextRestorePackage not called");
182        File packageDir = new File(mDataDir, mRestorePackages[mRestorePackage].packageName);
183
184        // The restore set is the concatenation of the individual record blobs,
185        // each of which is a file in the package's directory
186        File[] blobs = packageDir.listFiles();
187        if (blobs == null) {  // nextRestorePackage() ensures the dir exists, so this is an error
188            Log.e(TAG, "Error listing directory: " + packageDir);
189            return BackupConstants.TRANSPORT_ERROR;
190        }
191
192        // We expect at least some data if the directory exists in the first place
193        if (DEBUG) Log.v(TAG, "  getRestoreData() found " + blobs.length + " key files");
194        BackupDataOutput out = new BackupDataOutput(outFd.getFileDescriptor());
195        try {
196            for (File f : blobs) {
197                FileInputStream in = new FileInputStream(f);
198                try {
199                    int size = (int) f.length();
200                    byte[] buf = new byte[size];
201                    in.read(buf);
202                    String key = new String(Base64.decode(f.getName()));
203                    if (DEBUG) Log.v(TAG, "    ... key=" + key + " size=" + size);
204                    out.writeEntityHeader(key, size);
205                    out.writeEntityData(buf, size);
206                } finally {
207                    in.close();
208                }
209            }
210            return BackupConstants.TRANSPORT_OK;
211        } catch (IOException e) {
212            Log.e(TAG, "Unable to read backup records", e);
213            return BackupConstants.TRANSPORT_ERROR;
214        }
215    }
216
217    public void finishRestore() {
218        if (DEBUG) Log.v(TAG, "finishRestore()");
219    }
220}
221