LocalTransport.java revision ee0e78af5af3bf23dd928fe5e0ebeb39157eaf66
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() throws RemoteException {
51        return TRANSPORT_DIR_NAME;
52    }
53
54    public long requestBackupTime() throws RemoteException {
55        // any time is a good time for local backup
56        return 0;
57    }
58
59    public boolean performBackup(PackageInfo packageInfo, ParcelFileDescriptor data)
60            throws RemoteException {
61        if (DEBUG) Log.v(TAG, "performBackup() pkg=" + packageInfo.packageName);
62
63        File packageDir = new File(mDataDir, packageInfo.packageName);
64        packageDir.mkdirs();
65
66        // 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();
76                String base64Key = new String(Base64.encode(key.getBytes()));
77                File entityFile = new File(packageDir, base64Key);
78
79                int dataSize = changeSet.getDataSize();
80
81                if (DEBUG) Log.v(TAG, "Got change set key=" + key + " size=" + dataSize
82                        + " key64=" + base64Key);
83
84                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) {
97                        Log.e(TAG, "Unable to update key file " + entityFile.getAbsolutePath());
98                        return false;
99                    } finally {
100                        entity.close();
101                    }
102                } else {
103                    entityFile.delete();
104                }
105            }
106            return true;
107        } catch (IOException e) {
108            // oops, something went wrong.  abort the operation and return error.
109            Log.v(TAG, "Exception reading backup input:", e);
110            return false;
111        }
112    }
113
114    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
125    public boolean finishBackup() throws RemoteException {
126        if (DEBUG) Log.v(TAG, "finishBackup()");
127        return true;
128    }
129
130    // Restore handling
131    public RestoreSet[] getAvailableRestoreSets() throws android.os.RemoteException {
132        // one hardcoded restore set
133        RestoreSet set = new RestoreSet("Local disk image", "flash", 0);
134        RestoreSet[] array = { set };
135        return array;
136    }
137
138    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;
143    }
144
145    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        }
154
155        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);
163
164        // 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();
167        if (blobs == null) {
168            Log.e(TAG, "Error listing directory: " + packageDir);
169            return false;  // nextRestorePackage() ensures the dir exists, so this is an error
170        }
171
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        }
195    }
196
197    public void finishRestore() {
198        if (DEBUG) Log.v(TAG, "finishRestore()");
199    }
200}
201