LocalTransport.java revision efe52647f6b41993be43a5f47d1178bb0468cec8
19bbc21a773cbdfbef2876a75c32bda5839647751Christopher Tatepackage com.android.internal.backup;
29bbc21a773cbdfbef2876a75c32bda5839647751Christopher Tate
32fdd428e0f18384160f7c38ce3a2cd9ba7e7b2c2Christopher Tateimport android.backup.BackupDataInput;
48e55eac96d768a4de68a091f57487deadf6d0a87Christopher Tateimport android.backup.BackupDataOutput;
59bbc21a773cbdfbef2876a75c32bda5839647751Christopher Tateimport android.backup.RestoreSet;
69bbc21a773cbdfbef2876a75c32bda5839647751Christopher Tateimport android.content.Context;
79bbc21a773cbdfbef2876a75c32bda5839647751Christopher Tateimport android.content.pm.PackageInfo;
89bbc21a773cbdfbef2876a75c32bda5839647751Christopher Tateimport android.content.pm.PackageManager;
99bbc21a773cbdfbef2876a75c32bda5839647751Christopher Tateimport android.content.pm.PackageManager.NameNotFoundException;
109bbc21a773cbdfbef2876a75c32bda5839647751Christopher Tateimport android.os.Environment;
119bbc21a773cbdfbef2876a75c32bda5839647751Christopher Tateimport android.os.ParcelFileDescriptor;
129bbc21a773cbdfbef2876a75c32bda5839647751Christopher Tateimport android.os.RemoteException;
139bbc21a773cbdfbef2876a75c32bda5839647751Christopher Tateimport android.util.Log;
149bbc21a773cbdfbef2876a75c32bda5839647751Christopher Tate
15e9190a2750e1fb67e300d2c128227cc9b7339efeChristopher Tateimport org.bouncycastle.util.encoders.Base64;
16e9190a2750e1fb67e300d2c128227cc9b7339efeChristopher Tate
179bbc21a773cbdfbef2876a75c32bda5839647751Christopher Tateimport java.io.File;
189bbc21a773cbdfbef2876a75c32bda5839647751Christopher Tateimport java.io.FileFilter;
199bbc21a773cbdfbef2876a75c32bda5839647751Christopher Tateimport java.io.FileInputStream;
209bbc21a773cbdfbef2876a75c32bda5839647751Christopher Tateimport java.io.FileOutputStream;
219bbc21a773cbdfbef2876a75c32bda5839647751Christopher Tateimport java.io.IOException;
229bbc21a773cbdfbef2876a75c32bda5839647751Christopher Tateimport java.util.ArrayList;
239bbc21a773cbdfbef2876a75c32bda5839647751Christopher Tate
249bbc21a773cbdfbef2876a75c32bda5839647751Christopher Tate/**
259bbc21a773cbdfbef2876a75c32bda5839647751Christopher Tate * Backup transport for stashing stuff into a known location on disk, and
269bbc21a773cbdfbef2876a75c32bda5839647751Christopher Tate * later restoring from there.  For testing only.
279bbc21a773cbdfbef2876a75c32bda5839647751Christopher Tate */
289bbc21a773cbdfbef2876a75c32bda5839647751Christopher Tate
299bbc21a773cbdfbef2876a75c32bda5839647751Christopher Tatepublic class LocalTransport extends IBackupTransport.Stub {
309bbc21a773cbdfbef2876a75c32bda5839647751Christopher Tate    private static final String TAG = "LocalTransport";
312fdd428e0f18384160f7c38ce3a2cd9ba7e7b2c2Christopher Tate    private static final boolean DEBUG = true;
329bbc21a773cbdfbef2876a75c32bda5839647751Christopher Tate
339bbc21a773cbdfbef2876a75c32bda5839647751Christopher Tate    private Context mContext;
349bbc21a773cbdfbef2876a75c32bda5839647751Christopher Tate    private PackageManager mPackageManager;
359bbc21a773cbdfbef2876a75c32bda5839647751Christopher Tate    private File mDataDir = new File(Environment.getDownloadCacheDirectory(), "backup");
36efe52647f6b41993be43a5f47d1178bb0468cec8Dan Egnor    private PackageInfo[] mRestorePackages = null;
37efe52647f6b41993be43a5f47d1178bb0468cec8Dan Egnor    private int mRestorePackage = -1;  // Index into mRestorePackages
389bbc21a773cbdfbef2876a75c32bda5839647751Christopher Tate
399bbc21a773cbdfbef2876a75c32bda5839647751Christopher Tate
409bbc21a773cbdfbef2876a75c32bda5839647751Christopher Tate    public LocalTransport(Context context) {
412fdd428e0f18384160f7c38ce3a2cd9ba7e7b2c2Christopher Tate        if (DEBUG) Log.v(TAG, "Transport constructed");
429bbc21a773cbdfbef2876a75c32bda5839647751Christopher Tate        mContext = context;
439bbc21a773cbdfbef2876a75c32bda5839647751Christopher Tate        mPackageManager = context.getPackageManager();
449bbc21a773cbdfbef2876a75c32bda5839647751Christopher Tate    }
459bbc21a773cbdfbef2876a75c32bda5839647751Christopher Tate
469bbc21a773cbdfbef2876a75c32bda5839647751Christopher Tate    public long requestBackupTime() throws RemoteException {
479bbc21a773cbdfbef2876a75c32bda5839647751Christopher Tate        // any time is a good time for local backup
489bbc21a773cbdfbef2876a75c32bda5839647751Christopher Tate        return 0;
499bbc21a773cbdfbef2876a75c32bda5839647751Christopher Tate    }
509bbc21a773cbdfbef2876a75c32bda5839647751Christopher Tate
51efe52647f6b41993be43a5f47d1178bb0468cec8Dan Egnor    public boolean performBackup(PackageInfo packageInfo, ParcelFileDescriptor data)
529bbc21a773cbdfbef2876a75c32bda5839647751Christopher Tate            throws RemoteException {
532fdd428e0f18384160f7c38ce3a2cd9ba7e7b2c2Christopher Tate        if (DEBUG) Log.v(TAG, "performBackup() pkg=" + packageInfo.packageName);
549bbc21a773cbdfbef2876a75c32bda5839647751Christopher Tate
552fdd428e0f18384160f7c38ce3a2cd9ba7e7b2c2Christopher Tate        File packageDir = new File(mDataDir, packageInfo.packageName);
562fdd428e0f18384160f7c38ce3a2cd9ba7e7b2c2Christopher Tate        packageDir.mkdirs();
579bbc21a773cbdfbef2876a75c32bda5839647751Christopher Tate
582fdd428e0f18384160f7c38ce3a2cd9ba7e7b2c2Christopher Tate        // Each 'record' in the restore set is kept in its own file, named by
592fdd428e0f18384160f7c38ce3a2cd9ba7e7b2c2Christopher Tate        // the record key.  Wind through the data file, extracting individual
602fdd428e0f18384160f7c38ce3a2cd9ba7e7b2c2Christopher Tate        // record operations and building a set of all the updates to apply
612fdd428e0f18384160f7c38ce3a2cd9ba7e7b2c2Christopher Tate        // in this update.
622fdd428e0f18384160f7c38ce3a2cd9ba7e7b2c2Christopher Tate        BackupDataInput changeSet = new BackupDataInput(data.getFileDescriptor());
632fdd428e0f18384160f7c38ce3a2cd9ba7e7b2c2Christopher Tate        try {
642fdd428e0f18384160f7c38ce3a2cd9ba7e7b2c2Christopher Tate            int bufSize = 512;
652fdd428e0f18384160f7c38ce3a2cd9ba7e7b2c2Christopher Tate            byte[] buf = new byte[bufSize];
662fdd428e0f18384160f7c38ce3a2cd9ba7e7b2c2Christopher Tate            while (changeSet.readNextHeader()) {
672fdd428e0f18384160f7c38ce3a2cd9ba7e7b2c2Christopher Tate                String key = changeSet.getKey();
685d605dc56b036232e885f6ec36b888b729673060Joe Onorato                String base64Key = new String(Base64.encode(key.getBytes()));
695d605dc56b036232e885f6ec36b888b729673060Joe Onorato                File entityFile = new File(packageDir, base64Key);
705d605dc56b036232e885f6ec36b888b729673060Joe Onorato
712fdd428e0f18384160f7c38ce3a2cd9ba7e7b2c2Christopher Tate                int dataSize = changeSet.getDataSize();
72e9190a2750e1fb67e300d2c128227cc9b7339efeChristopher Tate
73e9190a2750e1fb67e300d2c128227cc9b7339efeChristopher Tate                if (DEBUG) Log.v(TAG, "Got change set key=" + key + " size=" + dataSize
74e9190a2750e1fb67e300d2c128227cc9b7339efeChristopher Tate                        + " key64=" + base64Key);
752fdd428e0f18384160f7c38ce3a2cd9ba7e7b2c2Christopher Tate
765d605dc56b036232e885f6ec36b888b729673060Joe Onorato                if (dataSize >= 0) {
775d605dc56b036232e885f6ec36b888b729673060Joe Onorato                    FileOutputStream entity = new FileOutputStream(entityFile);
785d605dc56b036232e885f6ec36b888b729673060Joe Onorato
795d605dc56b036232e885f6ec36b888b729673060Joe Onorato                    if (dataSize > bufSize) {
805d605dc56b036232e885f6ec36b888b729673060Joe Onorato                        bufSize = dataSize;
815d605dc56b036232e885f6ec36b888b729673060Joe Onorato                        buf = new byte[bufSize];
825d605dc56b036232e885f6ec36b888b729673060Joe Onorato                    }
835d605dc56b036232e885f6ec36b888b729673060Joe Onorato                    changeSet.readEntityData(buf, 0, dataSize);
845d605dc56b036232e885f6ec36b888b729673060Joe Onorato                    if (DEBUG) Log.v(TAG, "  data size " + dataSize);
855d605dc56b036232e885f6ec36b888b729673060Joe Onorato
865d605dc56b036232e885f6ec36b888b729673060Joe Onorato                    try {
875d605dc56b036232e885f6ec36b888b729673060Joe Onorato                        entity.write(buf, 0, dataSize);
885d605dc56b036232e885f6ec36b888b729673060Joe Onorato                    } catch (IOException e) {
89efe52647f6b41993be43a5f47d1178bb0468cec8Dan Egnor                        Log.e(TAG, "Unable to update key file " + entityFile.getAbsolutePath());
90efe52647f6b41993be43a5f47d1178bb0468cec8Dan Egnor                        return false;
915d605dc56b036232e885f6ec36b888b729673060Joe Onorato                    } finally {
925d605dc56b036232e885f6ec36b888b729673060Joe Onorato                        entity.close();
935d605dc56b036232e885f6ec36b888b729673060Joe Onorato                    }
945d605dc56b036232e885f6ec36b888b729673060Joe Onorato                } else {
955d605dc56b036232e885f6ec36b888b729673060Joe Onorato                    entityFile.delete();
962fdd428e0f18384160f7c38ce3a2cd9ba7e7b2c2Christopher Tate                }
972fdd428e0f18384160f7c38ce3a2cd9ba7e7b2c2Christopher Tate            }
98efe52647f6b41993be43a5f47d1178bb0468cec8Dan Egnor            return true;
992fdd428e0f18384160f7c38ce3a2cd9ba7e7b2c2Christopher Tate        } catch (IOException e) {
1002fdd428e0f18384160f7c38ce3a2cd9ba7e7b2c2Christopher Tate            // oops, something went wrong.  abort the operation and return error.
101efe52647f6b41993be43a5f47d1178bb0468cec8Dan Egnor            Log.v(TAG, "Exception reading backup input:", e);
102efe52647f6b41993be43a5f47d1178bb0468cec8Dan Egnor            return false;
1032fdd428e0f18384160f7c38ce3a2cd9ba7e7b2c2Christopher Tate        }
104efe52647f6b41993be43a5f47d1178bb0468cec8Dan Egnor    }
1059bbc21a773cbdfbef2876a75c32bda5839647751Christopher Tate
106efe52647f6b41993be43a5f47d1178bb0468cec8Dan Egnor    public boolean finishBackup() throws RemoteException {
107efe52647f6b41993be43a5f47d1178bb0468cec8Dan Egnor        if (DEBUG) Log.v(TAG, "finishBackup()");
108efe52647f6b41993be43a5f47d1178bb0468cec8Dan Egnor        return true;
1099bbc21a773cbdfbef2876a75c32bda5839647751Christopher Tate    }
1109bbc21a773cbdfbef2876a75c32bda5839647751Christopher Tate
1119bbc21a773cbdfbef2876a75c32bda5839647751Christopher Tate    // Restore handling
1129bbc21a773cbdfbef2876a75c32bda5839647751Christopher Tate    public RestoreSet[] getAvailableRestoreSets() throws android.os.RemoteException {
1139bbc21a773cbdfbef2876a75c32bda5839647751Christopher Tate        // one hardcoded restore set
114f68eb500f99361541049e09eb7f9ddd6f4ef4efaChristopher Tate        RestoreSet set = new RestoreSet("Local disk image", "flash", 0);
115f68eb500f99361541049e09eb7f9ddd6f4ef4efaChristopher Tate        RestoreSet[] array = { set };
116f68eb500f99361541049e09eb7f9ddd6f4ef4efaChristopher Tate        return array;
1179bbc21a773cbdfbef2876a75c32bda5839647751Christopher Tate    }
1189bbc21a773cbdfbef2876a75c32bda5839647751Christopher Tate
119efe52647f6b41993be43a5f47d1178bb0468cec8Dan Egnor    public boolean startRestore(long token, PackageInfo[] packages) {
120efe52647f6b41993be43a5f47d1178bb0468cec8Dan Egnor        if (DEBUG) Log.v(TAG, "start restore " + token);
121efe52647f6b41993be43a5f47d1178bb0468cec8Dan Egnor        mRestorePackages = packages;
122efe52647f6b41993be43a5f47d1178bb0468cec8Dan Egnor        mRestorePackage = -1;
123efe52647f6b41993be43a5f47d1178bb0468cec8Dan Egnor        return true;
124efe52647f6b41993be43a5f47d1178bb0468cec8Dan Egnor    }
1259bbc21a773cbdfbef2876a75c32bda5839647751Christopher Tate
126efe52647f6b41993be43a5f47d1178bb0468cec8Dan Egnor    public String nextRestorePackage() {
127efe52647f6b41993be43a5f47d1178bb0468cec8Dan Egnor        if (mRestorePackages == null) throw new IllegalStateException("startRestore not called");
128efe52647f6b41993be43a5f47d1178bb0468cec8Dan Egnor        while (++mRestorePackage < mRestorePackages.length) {
129efe52647f6b41993be43a5f47d1178bb0468cec8Dan Egnor            String name = mRestorePackages[mRestorePackage].packageName;
130efe52647f6b41993be43a5f47d1178bb0468cec8Dan Egnor            if (new File(mDataDir, name).isDirectory()) {
131efe52647f6b41993be43a5f47d1178bb0468cec8Dan Egnor                if (DEBUG) Log.v(TAG, "  nextRestorePackage() = " + name);
132efe52647f6b41993be43a5f47d1178bb0468cec8Dan Egnor                return name;
1332fdd428e0f18384160f7c38ce3a2cd9ba7e7b2c2Christopher Tate            }
1349bbc21a773cbdfbef2876a75c32bda5839647751Christopher Tate        }
1359bbc21a773cbdfbef2876a75c32bda5839647751Christopher Tate
136efe52647f6b41993be43a5f47d1178bb0468cec8Dan Egnor        if (DEBUG) Log.v(TAG, "  no more packages to restore");
137efe52647f6b41993be43a5f47d1178bb0468cec8Dan Egnor        return "";
1389bbc21a773cbdfbef2876a75c32bda5839647751Christopher Tate    }
1399bbc21a773cbdfbef2876a75c32bda5839647751Christopher Tate
140efe52647f6b41993be43a5f47d1178bb0468cec8Dan Egnor    public boolean getRestoreData(ParcelFileDescriptor outFd) {
141efe52647f6b41993be43a5f47d1178bb0468cec8Dan Egnor        if (mRestorePackages == null) throw new IllegalStateException("startRestore not called");
142efe52647f6b41993be43a5f47d1178bb0468cec8Dan Egnor        if (mRestorePackage < 0) throw new IllegalStateException("nextRestorePackage not called");
143efe52647f6b41993be43a5f47d1178bb0468cec8Dan Egnor        File packageDir = new File(mDataDir, mRestorePackages[mRestorePackage].packageName);
1449bbc21a773cbdfbef2876a75c32bda5839647751Christopher Tate
1452fdd428e0f18384160f7c38ce3a2cd9ba7e7b2c2Christopher Tate        // The restore set is the concatenation of the individual record blobs,
1462fdd428e0f18384160f7c38ce3a2cd9ba7e7b2c2Christopher Tate        // each of which is a file in the package's directory
1472fdd428e0f18384160f7c38ce3a2cd9ba7e7b2c2Christopher Tate        File[] blobs = packageDir.listFiles();
148efe52647f6b41993be43a5f47d1178bb0468cec8Dan Egnor        if (blobs == null) {
149efe52647f6b41993be43a5f47d1178bb0468cec8Dan Egnor            Log.e(TAG, "Error listing directory: " + packageDir);
150efe52647f6b41993be43a5f47d1178bb0468cec8Dan Egnor            return false;  // nextRestorePackage() ensures the dir exists, so this is an error
151efe52647f6b41993be43a5f47d1178bb0468cec8Dan Egnor        }
152efe52647f6b41993be43a5f47d1178bb0468cec8Dan Egnor
153efe52647f6b41993be43a5f47d1178bb0468cec8Dan Egnor        // We expect at least some data if the directory exists in the first place
154efe52647f6b41993be43a5f47d1178bb0468cec8Dan Egnor        if (DEBUG) Log.v(TAG, "  getRestoreData() found " + blobs.length + " key files");
155efe52647f6b41993be43a5f47d1178bb0468cec8Dan Egnor        BackupDataOutput out = new BackupDataOutput(outFd.getFileDescriptor());
156efe52647f6b41993be43a5f47d1178bb0468cec8Dan Egnor        try {
157efe52647f6b41993be43a5f47d1178bb0468cec8Dan Egnor            for (File f : blobs) {
158efe52647f6b41993be43a5f47d1178bb0468cec8Dan Egnor                FileInputStream in = new FileInputStream(f);
159efe52647f6b41993be43a5f47d1178bb0468cec8Dan Egnor                try {
160efe52647f6b41993be43a5f47d1178bb0468cec8Dan Egnor                    int size = (int) f.length();
161efe52647f6b41993be43a5f47d1178bb0468cec8Dan Egnor                    byte[] buf = new byte[size];
162efe52647f6b41993be43a5f47d1178bb0468cec8Dan Egnor                    in.read(buf);
163efe52647f6b41993be43a5f47d1178bb0468cec8Dan Egnor                    String key = new String(Base64.decode(f.getName()));
164efe52647f6b41993be43a5f47d1178bb0468cec8Dan Egnor                    if (DEBUG) Log.v(TAG, "    ... key=" + key + " size=" + size);
165efe52647f6b41993be43a5f47d1178bb0468cec8Dan Egnor                    out.writeEntityHeader(key, size);
166efe52647f6b41993be43a5f47d1178bb0468cec8Dan Egnor                    out.writeEntityData(buf, size);
167efe52647f6b41993be43a5f47d1178bb0468cec8Dan Egnor                } finally {
168efe52647f6b41993be43a5f47d1178bb0468cec8Dan Egnor                    in.close();
1698e55eac96d768a4de68a091f57487deadf6d0a87Christopher Tate                }
1702fdd428e0f18384160f7c38ce3a2cd9ba7e7b2c2Christopher Tate            }
171efe52647f6b41993be43a5f47d1178bb0468cec8Dan Egnor            return true;
172efe52647f6b41993be43a5f47d1178bb0468cec8Dan Egnor        } catch (IOException e) {
173efe52647f6b41993be43a5f47d1178bb0468cec8Dan Egnor            Log.e(TAG, "Unable to read backup records", e);
174efe52647f6b41993be43a5f47d1178bb0468cec8Dan Egnor            return false;
1752fdd428e0f18384160f7c38ce3a2cd9ba7e7b2c2Christopher Tate        }
1769bbc21a773cbdfbef2876a75c32bda5839647751Christopher Tate    }
1773a31a93b8a195ae2d0180e6dfbf292da2e581f50Christopher Tate
178efe52647f6b41993be43a5f47d1178bb0468cec8Dan Egnor    public void finishRestore() {
179efe52647f6b41993be43a5f47d1178bb0468cec8Dan Egnor        if (DEBUG) Log.v(TAG, "finishRestore()");
1803a31a93b8a195ae2d0180e6dfbf292da2e581f50Christopher Tate    }
1819bbc21a773cbdfbef2876a75c32bda5839647751Christopher Tate}
182