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