11c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate/*
21c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate * Copyright (C) 2010 The Android Open Source Project
31c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate *
41c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate * Licensed under the Apache License, Version 2.0 (the "License");
51c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate * you may not use this file except in compliance with the License.
61c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate * You may obtain a copy of the License at
71c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate *
81c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate *      http://www.apache.org/licenses/LICENSE-2.0
91c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate *
101c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate * Unless required by applicable law or agreed to in writing, software
111c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate * distributed under the License is distributed on an "AS IS" BASIS,
121c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
131c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate * See the License for the specific language governing permissions and
141c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate * limitations under the License.
151c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate */
161c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate
171c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tatepackage com.example.android.backuprestore;
181c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate
191c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tateimport java.io.ByteArrayOutputStream;
201c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tateimport java.io.ByteArrayInputStream;
211c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tateimport java.io.DataInputStream;
221c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tateimport java.io.DataOutputStream;
231c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tateimport java.io.File;
241c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tateimport java.io.FileInputStream;
251c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tateimport java.io.FileOutputStream;
261c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tateimport java.io.IOException;
271c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tateimport java.io.RandomAccessFile;
281c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate
291c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tateimport android.app.backup.BackupAgent;
301c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tateimport android.app.backup.BackupDataInput;
311c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tateimport android.app.backup.BackupDataOutput;
321c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tateimport android.os.ParcelFileDescriptor;
331c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate
341c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate/**
351c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate * This agent implementation is similar to the {@link ExampleAgent} one, but
361c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate * stores each distinct piece of application data in a separate record within
371c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate * the backup data set.  These records are updated independently: if the user
381c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate * changes the state of one of the UI's checkboxes, for example, only that
391c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate * datum's backup record is updated, not the entire data file.
401c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate */
411c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tatepublic class MultiRecordExampleAgent extends BackupAgent {
421c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate    // Key strings for each record in the backup set
431c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate    static final String FILLING_KEY = "filling";
441c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate    static final String MAYO_KEY = "mayo";
451c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate    static final String TOMATO_KEY = "tomato";
461c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate
471c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate    // Current live data, read from the application's data file
481c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate    int mFilling;
491c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate    boolean mAddMayo;
501c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate    boolean mAddTomato;
511c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate
521c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate    /** The location of the application's persistent data file */
531c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate    File mDataFile;
541c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate
551c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate    @Override
561c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate    public void onCreate() {
571c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate        // Cache a File for the app's data
581c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate        mDataFile = new File(getFilesDir(), BackupRestoreActivity.DATA_FILE_NAME);
591c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate    }
601c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate
611c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate    @Override
621c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate    public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
631c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate            ParcelFileDescriptor newState) throws IOException {
641c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate        // First, get the current data from the application's file.  This
651c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate        // may throw an IOException, but in that case something has gone
661c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate        // badly wrong with the app's data on disk, and we do not want
671c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate        // to back up garbage data.  If we just let the exception go, the
681c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate        // Backup Manager will handle it and simply skip the current
691c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate        // backup operation.
701c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate        synchronized (BackupRestoreActivity.sDataLock) {
711c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate            RandomAccessFile file = new RandomAccessFile(mDataFile, "r");
721c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate            mFilling = file.readInt();
731c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate            mAddMayo = file.readBoolean();
741c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate            mAddTomato = file.readBoolean();
751c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate        }
761c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate
771c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate        // If this is the first backup ever, we have to back up everything
781c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate        boolean forceBackup = (oldState == null);
791c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate
801c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate        // Now read the state as of the previous backup pass, if any
811c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate        int lastFilling = 0;
821c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate        boolean lastMayo = false;
831c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate        boolean lastTomato = false;
841c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate
851c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate        if (!forceBackup) {
861c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate
871c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate            FileInputStream instream = new FileInputStream(oldState.getFileDescriptor());
881c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate            DataInputStream in = new DataInputStream(instream);
891c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate
901c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate            try {
911c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate                // Read the state as of the last backup
921c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate                lastFilling = in.readInt();
931c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate                lastMayo = in.readBoolean();
941c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate                lastTomato = in.readBoolean();
951c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate            } catch (IOException e) {
961c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate                // If something went wrong reading the state file, be safe and
971c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate                // force a backup of all the data again.
981c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate                forceBackup = true;
991c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate            }
1001c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate        }
1011c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate
1021c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate        // Okay, now check each datum to see whether we need to back up a new value.  We'll
1031c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate        // reuse the bytearray buffering stream for each datum.  We also use a little
1041c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate        // helper routine to avoid some code duplication when writing the two boolean
1051c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate        // records.
1061c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate        ByteArrayOutputStream bufStream = new ByteArrayOutputStream();
1071c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate        DataOutputStream out = new DataOutputStream(bufStream);
1081c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate
1091c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate        if (forceBackup || (mFilling != lastFilling)) {
1101c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate            // bufStream.reset();   // not necessary the first time, but good to remember
1111c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate            out.writeInt(mFilling);
1121c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate            writeBackupEntity(data, bufStream, FILLING_KEY);
1131c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate        }
1141c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate
1151c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate        if (forceBackup || (mAddMayo != lastMayo)) {
1161c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate            bufStream.reset();
1171c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate            out.writeBoolean(mAddMayo);
1181c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate            writeBackupEntity(data, bufStream, MAYO_KEY);
1191c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate        }
1201c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate
1211c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate        if (forceBackup || (mAddTomato != lastTomato)) {
1221c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate            bufStream.reset();
1231c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate            out.writeBoolean(mAddTomato);
1241c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate            writeBackupEntity(data, bufStream, TOMATO_KEY);
1251c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate        }
1261c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate
1271c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate        // Finally, write the state file that describes our data as of this backup pass
1281c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate        writeStateFile(newState);
1291c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate    }
1301c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate
1311c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate    /**
1321c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate     * Write out the new state file:  the version number, followed by the
1331c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate     * three bits of data as we sent them off to the backup transport.
1341c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate     */
1351c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate    void writeStateFile(ParcelFileDescriptor stateFile) throws IOException {
1361c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate        FileOutputStream outstream = new FileOutputStream(stateFile.getFileDescriptor());
1371c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate        DataOutputStream out = new DataOutputStream(outstream);
1381c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate
1391c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate        out.writeInt(mFilling);
1401c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate        out.writeBoolean(mAddMayo);
1411c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate        out.writeBoolean(mAddTomato);
1421c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate    }
1431c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate
1441c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate    // Helper: write the boolean 'value' as a backup record under the given 'key',
1451c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate    // reusing the given buffering stream & data writer objects to do so.
1461c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate    void writeBackupEntity(BackupDataOutput data, ByteArrayOutputStream bufStream, String key)
1471c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate            throws IOException {
1481c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate        byte[] buf = bufStream.toByteArray();
1491c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate        data.writeEntityHeader(key, buf.length);
1501c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate        data.writeEntityData(buf, buf.length);
1511c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate    }
1521c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate
1531c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate    /**
1541c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate     * On restore, we pull the various bits of data out of the restore stream,
1551c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate     * then reconstruct the application's data file inside the shared lock.  A
1561c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate     * restore data set will always be the full set of records supplied by the
1571c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate     * application's backup operations.
1581c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate     */
1591c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate    @Override
1601c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate    public void onRestore(BackupDataInput data, int appVersionCode,
1611c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate            ParcelFileDescriptor newState) throws IOException {
1621c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate
1631c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate        // Consume the restore data set, remembering each bit of application state
1641c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate        // that we see along the way
1651c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate        while (data.readNextHeader()) {
1661c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate            String key = data.getKey();
1671c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate            int dataSize = data.getDataSize();
1681c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate
1691c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate            // In this implementation, we trust that we won't see any record keys
1701c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate            // that we don't understand.  Since we expect to handle them all, we
1711c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate            // go ahead and extract the data for each record before deciding how
1721c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate            // it will be handled.
1731c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate            byte[] dataBuf = new byte[dataSize];
1741c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate            data.readEntityData(dataBuf, 0, dataSize);
1751c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate            ByteArrayInputStream instream = new ByteArrayInputStream(dataBuf);
1761c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate            DataInputStream in = new DataInputStream(instream);
1771c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate
1781c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate            if (FILLING_KEY.equals(key)) {
1791c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate                mFilling = in.readInt();
1801c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate            } else if (MAYO_KEY.equals(key)) {
1811c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate                mAddMayo = in.readBoolean();
1821c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate            } else if (TOMATO_KEY.equals(key)) {
1831c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate                mAddTomato = in.readBoolean();
1841c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate            }
1851c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate        }
1861c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate
1871c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate        // Now we're ready to write out a full new dataset for the application.  Note that
1881c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate        // the restore process is intended to *replace* any existing or default data, so
1891c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate        // we can just go ahead and overwrite it all.
1901c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate        synchronized (BackupRestoreActivity.sDataLock) {
1911c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate            RandomAccessFile file = new RandomAccessFile(mDataFile, "rw");
1921c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate            file.setLength(0L);
1931c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate            file.writeInt(mFilling);
1941c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate            file.writeBoolean(mAddMayo);
1951c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate            file.writeBoolean(mAddTomato);
1961c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate        }
1971c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate
1981c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate        // Finally, write the state file that describes our data as of this restore pass.
1991c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate        writeStateFile(newState);
2001c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate    }
2011c0a20aeeb089971a624c102435755ba9ae74aabChristopher Tate}
202