1 /* 2 * Copyright (C) 2010 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.example.android.backuprestore; 18 19 import java.io.ByteArrayOutputStream; 20 import java.io.ByteArrayInputStream; 21 import java.io.DataInputStream; 22 import java.io.DataOutputStream; 23 import java.io.File; 24 import java.io.FileInputStream; 25 import java.io.FileOutputStream; 26 import java.io.IOException; 27 import java.io.RandomAccessFile; 28 29 import android.app.backup.BackupAgent; 30 import android.app.backup.BackupDataInput; 31 import android.app.backup.BackupDataOutput; 32 import android.os.ParcelFileDescriptor; 33 34 /** 35 * This agent implementation is similar to the {@link ExampleAgent} one, but 36 * stores each distinct piece of application data in a separate record within 37 * the backup data set. These records are updated independently: if the user 38 * changes the state of one of the UI's checkboxes, for example, only that 39 * datum's backup record is updated, not the entire data file. 40 */ 41 public class MultiRecordExampleAgent extends BackupAgent { 42 // Key strings for each record in the backup set 43 static final String FILLING_KEY = "filling"; 44 static final String MAYO_KEY = "mayo"; 45 static final String TOMATO_KEY = "tomato"; 46 47 // Current live data, read from the application's data file 48 int mFilling; 49 boolean mAddMayo; 50 boolean mAddTomato; 51 52 /** The location of the application's persistent data file */ 53 File mDataFile; 54 55 @Override 56 public void onCreate() { 57 // Cache a File for the app's data 58 mDataFile = new File(getFilesDir(), BackupRestoreActivity.DATA_FILE_NAME); 59 } 60 61 @Override 62 public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data, 63 ParcelFileDescriptor newState) throws IOException { 64 // First, get the current data from the application's file. This 65 // may throw an IOException, but in that case something has gone 66 // badly wrong with the app's data on disk, and we do not want 67 // to back up garbage data. If we just let the exception go, the 68 // Backup Manager will handle it and simply skip the current 69 // backup operation. 70 synchronized (BackupRestoreActivity.sDataLock) { 71 RandomAccessFile file = new RandomAccessFile(mDataFile, "r"); 72 mFilling = file.readInt(); 73 mAddMayo = file.readBoolean(); 74 mAddTomato = file.readBoolean(); 75 } 76 77 // If this is the first backup ever, we have to back up everything 78 boolean forceBackup = (oldState == null); 79 80 // Now read the state as of the previous backup pass, if any 81 int lastFilling = 0; 82 boolean lastMayo = false; 83 boolean lastTomato = false; 84 85 if (!forceBackup) { 86 87 FileInputStream instream = new FileInputStream(oldState.getFileDescriptor()); 88 DataInputStream in = new DataInputStream(instream); 89 90 try { 91 // Read the state as of the last backup 92 lastFilling = in.readInt(); 93 lastMayo = in.readBoolean(); 94 lastTomato = in.readBoolean(); 95 } catch (IOException e) { 96 // If something went wrong reading the state file, be safe and 97 // force a backup of all the data again. 98 forceBackup = true; 99 } 100 } 101 102 // Okay, now check each datum to see whether we need to back up a new value. We'll 103 // reuse the bytearray buffering stream for each datum. We also use a little 104 // helper routine to avoid some code duplication when writing the two boolean 105 // records. 106 ByteArrayOutputStream bufStream = new ByteArrayOutputStream(); 107 DataOutputStream out = new DataOutputStream(bufStream); 108 109 if (forceBackup || (mFilling != lastFilling)) { 110 // bufStream.reset(); // not necessary the first time, but good to remember 111 out.writeInt(mFilling); 112 writeBackupEntity(data, bufStream, FILLING_KEY); 113 } 114 115 if (forceBackup || (mAddMayo != lastMayo)) { 116 bufStream.reset(); 117 out.writeBoolean(mAddMayo); 118 writeBackupEntity(data, bufStream, MAYO_KEY); 119 } 120 121 if (forceBackup || (mAddTomato != lastTomato)) { 122 bufStream.reset(); 123 out.writeBoolean(mAddTomato); 124 writeBackupEntity(data, bufStream, TOMATO_KEY); 125 } 126 127 // Finally, write the state file that describes our data as of this backup pass 128 writeStateFile(newState); 129 } 130 131 /** 132 * Write out the new state file: the version number, followed by the 133 * three bits of data as we sent them off to the backup transport. 134 */ 135 void writeStateFile(ParcelFileDescriptor stateFile) throws IOException { 136 FileOutputStream outstream = new FileOutputStream(stateFile.getFileDescriptor()); 137 DataOutputStream out = new DataOutputStream(outstream); 138 139 out.writeInt(mFilling); 140 out.writeBoolean(mAddMayo); 141 out.writeBoolean(mAddTomato); 142 } 143 144 // Helper: write the boolean 'value' as a backup record under the given 'key', 145 // reusing the given buffering stream & data writer objects to do so. 146 void writeBackupEntity(BackupDataOutput data, ByteArrayOutputStream bufStream, String key) 147 throws IOException { 148 byte[] buf = bufStream.toByteArray(); 149 data.writeEntityHeader(key, buf.length); 150 data.writeEntityData(buf, buf.length); 151 } 152 153 /** 154 * On restore, we pull the various bits of data out of the restore stream, 155 * then reconstruct the application's data file inside the shared lock. A 156 * restore data set will always be the full set of records supplied by the 157 * application's backup operations. 158 */ 159 @Override 160 public void onRestore(BackupDataInput data, int appVersionCode, 161 ParcelFileDescriptor newState) throws IOException { 162 163 // Consume the restore data set, remembering each bit of application state 164 // that we see along the way 165 while (data.readNextHeader()) { 166 String key = data.getKey(); 167 int dataSize = data.getDataSize(); 168 169 // In this implementation, we trust that we won't see any record keys 170 // that we don't understand. Since we expect to handle them all, we 171 // go ahead and extract the data for each record before deciding how 172 // it will be handled. 173 byte[] dataBuf = new byte[dataSize]; 174 data.readEntityData(dataBuf, 0, dataSize); 175 ByteArrayInputStream instream = new ByteArrayInputStream(dataBuf); 176 DataInputStream in = new DataInputStream(instream); 177 178 if (FILLING_KEY.equals(key)) { 179 mFilling = in.readInt(); 180 } else if (MAYO_KEY.equals(key)) { 181 mAddMayo = in.readBoolean(); 182 } else if (TOMATO_KEY.equals(key)) { 183 mAddTomato = in.readBoolean(); 184 } 185 } 186 187 // Now we're ready to write out a full new dataset for the application. Note that 188 // the restore process is intended to *replace* any existing or default data, so 189 // we can just go ahead and overwrite it all. 190 synchronized (BackupRestoreActivity.sDataLock) { 191 RandomAccessFile file = new RandomAccessFile(mDataFile, "rw"); 192 file.setLength(0L); 193 file.writeInt(mFilling); 194 file.writeBoolean(mAddMayo); 195 file.writeBoolean(mAddTomato); 196 } 197 198 // Finally, write the state file that describes our data as of this restore pass. 199 writeStateFile(newState); 200 } 201 } 202