/* * Copyright (C) 2010 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.example.android.backuprestore; import java.io.ByteArrayOutputStream; import java.io.ByteArrayInputStream; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.RandomAccessFile; import android.app.backup.BackupAgent; import android.app.backup.BackupDataInput; import android.app.backup.BackupDataOutput; import android.os.ParcelFileDescriptor; /** * This agent implementation is similar to the {@link ExampleAgent} one, but * stores each distinct piece of application data in a separate record within * the backup data set. These records are updated independently: if the user * changes the state of one of the UI's checkboxes, for example, only that * datum's backup record is updated, not the entire data file. */ public class MultiRecordExampleAgent extends BackupAgent { // Key strings for each record in the backup set static final String FILLING_KEY = "filling"; static final String MAYO_KEY = "mayo"; static final String TOMATO_KEY = "tomato"; // Current live data, read from the application's data file int mFilling; boolean mAddMayo; boolean mAddTomato; /** The location of the application's persistent data file */ File mDataFile; @Override public void onCreate() { // Cache a File for the app's data mDataFile = new File(getFilesDir(), BackupRestoreActivity.DATA_FILE_NAME); } @Override public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data, ParcelFileDescriptor newState) throws IOException { // First, get the current data from the application's file. This // may throw an IOException, but in that case something has gone // badly wrong with the app's data on disk, and we do not want // to back up garbage data. If we just let the exception go, the // Backup Manager will handle it and simply skip the current // backup operation. synchronized (BackupRestoreActivity.sDataLock) { RandomAccessFile file = new RandomAccessFile(mDataFile, "r"); mFilling = file.readInt(); mAddMayo = file.readBoolean(); mAddTomato = file.readBoolean(); } // If this is the first backup ever, we have to back up everything boolean forceBackup = (oldState == null); // Now read the state as of the previous backup pass, if any int lastFilling = 0; boolean lastMayo = false; boolean lastTomato = false; if (!forceBackup) { FileInputStream instream = new FileInputStream(oldState.getFileDescriptor()); DataInputStream in = new DataInputStream(instream); try { // Read the state as of the last backup lastFilling = in.readInt(); lastMayo = in.readBoolean(); lastTomato = in.readBoolean(); } catch (IOException e) { // If something went wrong reading the state file, be safe and // force a backup of all the data again. forceBackup = true; } } // Okay, now check each datum to see whether we need to back up a new value. We'll // reuse the bytearray buffering stream for each datum. We also use a little // helper routine to avoid some code duplication when writing the two boolean // records. ByteArrayOutputStream bufStream = new ByteArrayOutputStream(); DataOutputStream out = new DataOutputStream(bufStream); if (forceBackup || (mFilling != lastFilling)) { // bufStream.reset(); // not necessary the first time, but good to remember out.writeInt(mFilling); writeBackupEntity(data, bufStream, FILLING_KEY); } if (forceBackup || (mAddMayo != lastMayo)) { bufStream.reset(); out.writeBoolean(mAddMayo); writeBackupEntity(data, bufStream, MAYO_KEY); } if (forceBackup || (mAddTomato != lastTomato)) { bufStream.reset(); out.writeBoolean(mAddTomato); writeBackupEntity(data, bufStream, TOMATO_KEY); } // Finally, write the state file that describes our data as of this backup pass writeStateFile(newState); } /** * Write out the new state file: the version number, followed by the * three bits of data as we sent them off to the backup transport. */ void writeStateFile(ParcelFileDescriptor stateFile) throws IOException { FileOutputStream outstream = new FileOutputStream(stateFile.getFileDescriptor()); DataOutputStream out = new DataOutputStream(outstream); out.writeInt(mFilling); out.writeBoolean(mAddMayo); out.writeBoolean(mAddTomato); } // Helper: write the boolean 'value' as a backup record under the given 'key', // reusing the given buffering stream & data writer objects to do so. void writeBackupEntity(BackupDataOutput data, ByteArrayOutputStream bufStream, String key) throws IOException { byte[] buf = bufStream.toByteArray(); data.writeEntityHeader(key, buf.length); data.writeEntityData(buf, buf.length); } /** * On restore, we pull the various bits of data out of the restore stream, * then reconstruct the application's data file inside the shared lock. A * restore data set will always be the full set of records supplied by the * application's backup operations. */ @Override public void onRestore(BackupDataInput data, int appVersionCode, ParcelFileDescriptor newState) throws IOException { // Consume the restore data set, remembering each bit of application state // that we see along the way while (data.readNextHeader()) { String key = data.getKey(); int dataSize = data.getDataSize(); // In this implementation, we trust that we won't see any record keys // that we don't understand. Since we expect to handle them all, we // go ahead and extract the data for each record before deciding how // it will be handled. byte[] dataBuf = new byte[dataSize]; data.readEntityData(dataBuf, 0, dataSize); ByteArrayInputStream instream = new ByteArrayInputStream(dataBuf); DataInputStream in = new DataInputStream(instream); if (FILLING_KEY.equals(key)) { mFilling = in.readInt(); } else if (MAYO_KEY.equals(key)) { mAddMayo = in.readBoolean(); } else if (TOMATO_KEY.equals(key)) { mAddTomato = in.readBoolean(); } } // Now we're ready to write out a full new dataset for the application. Note that // the restore process is intended to *replace* any existing or default data, so // we can just go ahead and overwrite it all. synchronized (BackupRestoreActivity.sDataLock) { RandomAccessFile file = new RandomAccessFile(mDataFile, "rw"); file.setLength(0L); file.writeInt(mFilling); file.writeBoolean(mAddMayo); file.writeBoolean(mAddTomato); } // Finally, write the state file that describes our data as of this restore pass. writeStateFile(newState); } }