1 /*
2  * Copyright (C) 2011 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.android.hugebackup;
18 
19 import android.app.backup.BackupAgent;
20 import android.app.backup.BackupDataInput;
21 import android.app.backup.BackupDataOutput;
22 import android.os.ParcelFileDescriptor;
23 
24 import java.io.ByteArrayInputStream;
25 import java.io.ByteArrayOutputStream;
26 import java.io.DataInputStream;
27 import java.io.DataOutputStream;
28 import java.io.File;
29 import java.io.FileInputStream;
30 import java.io.FileOutputStream;
31 import java.io.IOException;
32 import java.io.RandomAccessFile;
33 
34 /**
35  * This is the backup/restore agent class for the BackupRestore sample
36  * application.  This particular agent illustrates using the backup and
37  * restore APIs directly, without taking advantage of any helper classes.
38  */
39 public class HugeAgent extends BackupAgent {
40     /**
41      * We put a simple version number into the state files so that we can
42      * tell properly how to read "old" versions if at some point we want
43      * to change what data we back up and how we store the state blob.
44      */
45     static final int AGENT_VERSION = 1;
46 
47     /**
48      * Pick an arbitrary string to use as the "key" under which the
49      * data is backed up.  This key identifies different data records
50      * within this one application's data set.  Since we only maintain
51      * one piece of data we don't need to distinguish, so we just pick
52      * some arbitrary tag to use.
53      */
54     static final String APP_DATA_KEY = "alldata";
55     static final String HUGE_DATA_KEY = "colossus";
56 
57     /** The app's current data, read from the live disk file */
58     boolean mAddMayo;
59     boolean mAddTomato;
60     int mFilling;
61 
62     /** The location of the application's persistent data file */
63     File mDataFile;
64 
65     /** For convenience, we set up the File object for the app's data on creation */
66     @Override
onCreate()67     public void onCreate() {
68         mDataFile = new File(getFilesDir(), HugeBackupActivity.DATA_FILE_NAME);
69     }
70 
71     /**
72      * The set of data backed up by this application is very small: just
73      * two booleans and an integer.  With such a simple dataset, it's
74      * easiest to simply store a copy of the backed-up data as the state
75      * blob describing the last dataset backed up.  The state file
76      * contents can be anything; it is private to the agent class, and
77      * is never stored off-device.
78      *
79      * <p>One thing that an application may wish to do is tag the state
80      * blob contents with a version number.  This is so that if the
81      * application is upgraded, the next time it attempts to do a backup,
82      * it can detect that the last backup operation was performed by an
83      * older version of the agent, and might therefore require different
84      * handling.
85      */
86     @Override
onBackup(ParcelFileDescriptor oldState, BackupDataOutput data, ParcelFileDescriptor newState)87     public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
88             ParcelFileDescriptor newState) throws IOException {
89         // First, get the current data from the application's file.  This
90         // may throw an IOException, but in that case something has gone
91         // badly wrong with the app's data on disk, and we do not want
92         // to back up garbage data.  If we just let the exception go, the
93         // Backup Manager will handle it and simply skip the current
94         // backup operation.
95         synchronized (HugeBackupActivity.sDataLock) {
96             RandomAccessFile file = new RandomAccessFile(mDataFile, "r");
97             mFilling = file.readInt();
98             mAddMayo = file.readBoolean();
99             mAddTomato = file.readBoolean();
100         }
101 
102         // If the new state file descriptor is null, this is the first time
103         // a backup is being performed, so we know we have to write the
104         // data.  If there <em>is</em> a previous state blob, we want to
105         // double check whether the current data is actually different from
106         // our last backup, so that we can avoid transmitting redundant
107         // data to the storage backend.
108         boolean doBackup = (oldState == null);
109         if (!doBackup) {
110             doBackup = compareStateFile(oldState);
111         }
112 
113         // If we decided that we do in fact need to write our dataset, go
114         // ahead and do that.  The way this agent backs up the data is to
115         // flatten it into a single buffer, then write that to the backup
116         // transport under the single key string.
117         if (doBackup) {
118             ByteArrayOutputStream bufStream = new ByteArrayOutputStream();
119 
120             // We use a DataOutputStream to write structured data into
121             // the buffering stream
122             DataOutputStream outWriter = new DataOutputStream(bufStream);
123             outWriter.writeInt(mFilling);
124             outWriter.writeBoolean(mAddMayo);
125             outWriter.writeBoolean(mAddTomato);
126 
127             // Okay, we've flattened the data for transmission.  Pull it
128             // out of the buffering stream object and send it off.
129             byte[] buffer = bufStream.toByteArray();
130             int len = buffer.length;
131             data.writeEntityHeader(APP_DATA_KEY, len);
132             data.writeEntityData(buffer, len);
133 
134             // ***** pathological behavior *****
135             // Now, in order to incur deliberate too-much-data failures,
136             // try to back up 20 MB of data besides what we already pushed.
137             final int MEGABYTE = 1024*1024;
138             final int NUM_MEGS = 20;
139             buffer = new byte[MEGABYTE];
140             data.writeEntityHeader(HUGE_DATA_KEY, NUM_MEGS * MEGABYTE);
141             for (int i = 0; i < NUM_MEGS; i++) {
142                 data.writeEntityData(buffer, MEGABYTE);
143             }
144         }
145 
146         // Finally, in all cases, we need to write the new state blob
147         writeStateFile(newState);
148     }
149 
150     /**
151      * Helper routine - read a previous state file and decide whether to
152      * perform a backup based on its contents.
153      *
154      * @return <code>true</code> if the application's data has changed since
155      *   the last backup operation; <code>false</code> otherwise.
156      */
compareStateFile(ParcelFileDescriptor oldState)157     boolean compareStateFile(ParcelFileDescriptor oldState) {
158         FileInputStream instream = new FileInputStream(oldState.getFileDescriptor());
159         DataInputStream in = new DataInputStream(instream);
160 
161         try {
162             int stateVersion = in.readInt();
163             if (stateVersion > AGENT_VERSION) {
164                 // Whoops; the last version of the app that backed up
165                 // data on this device was <em>newer</em> than the current
166                 // version -- the user has downgraded.  That's problematic.
167                 // In this implementation, we recover by simply rewriting
168                 // the backup.
169                 return true;
170             }
171 
172             // The state data we store is just a mirror of the app's data;
173             // read it from the state file then return 'true' if any of
174             // it differs from the current data.
175             int lastFilling = in.readInt();
176             boolean lastMayo = in.readBoolean();
177             boolean lastTomato = in.readBoolean();
178 
179             return (lastFilling != mFilling)
180                     || (lastTomato != mAddTomato)
181                     || (lastMayo != mAddMayo);
182         } catch (IOException e) {
183             // If something went wrong reading the state file, be safe
184             // and back up the data again.
185             return true;
186         }
187     }
188 
189     /**
190      * Write out the new state file:  the version number, followed by the
191      * three bits of data as we sent them off to the backup transport.
192      */
writeStateFile(ParcelFileDescriptor stateFile)193     void writeStateFile(ParcelFileDescriptor stateFile) throws IOException {
194         FileOutputStream outstream = new FileOutputStream(stateFile.getFileDescriptor());
195         DataOutputStream out = new DataOutputStream(outstream);
196 
197         out.writeInt(AGENT_VERSION);
198         out.writeInt(mFilling);
199         out.writeBoolean(mAddMayo);
200         out.writeBoolean(mAddTomato);
201     }
202 
203     /**
204      * This application does not do any "live" restores of its own data,
205      * so the only time a restore will happen is when the application is
206      * installed.  This means that the activity itself is not going to
207      * be running while we change its data out from under it.  That, in
208      * turn, means that there is no need to send out any sort of notification
209      * of the new data:  we only need to read the data from the stream
210      * provided here, build the application's new data file, and then
211      * write our new backup state blob that will be consulted at the next
212      * backup operation.
213      *
214      * <p>We don't bother checking the versionCode of the app who originated
215      * the data because we have never revised the backup data format.  If
216      * we had, the 'appVersionCode' parameter would tell us how we should
217      * interpret the data we're about to read.
218      */
219     @Override
onRestore(BackupDataInput data, int appVersionCode, ParcelFileDescriptor newState)220     public void onRestore(BackupDataInput data, int appVersionCode,
221             ParcelFileDescriptor newState) throws IOException {
222         // We should only see one entity in the data stream, but the safest
223         // way to consume it is using a while() loop
224         while (data.readNextHeader()) {
225             String key = data.getKey();
226             int dataSize = data.getDataSize();
227 
228             if (APP_DATA_KEY.equals(key)) {
229                 // It's our saved data, a flattened chunk of data all in
230                 // one buffer.  Use some handy structured I/O classes to
231                 // extract it.
232                 byte[] dataBuf = new byte[dataSize];
233                 data.readEntityData(dataBuf, 0, dataSize);
234                 ByteArrayInputStream baStream = new ByteArrayInputStream(dataBuf);
235                 DataInputStream in = new DataInputStream(baStream);
236 
237                 mFilling = in.readInt();
238                 mAddMayo = in.readBoolean();
239                 mAddTomato = in.readBoolean();
240 
241                 // Now we are ready to construct the app's data file based
242                 // on the data we are restoring from.
243                 synchronized (HugeBackupActivity.sDataLock) {
244                     RandomAccessFile file = new RandomAccessFile(mDataFile, "rw");
245                     file.setLength(0L);
246                     file.writeInt(mFilling);
247                     file.writeBoolean(mAddMayo);
248                     file.writeBoolean(mAddTomato);
249                 }
250             } else {
251                 // Curious!  This entity is data under a key we do not
252                 // understand how to process.  Just skip it.
253                 data.skipEntityData();
254             }
255         }
256 
257         // The last thing to do is write the state blob that describes the
258         // app's data as restored from backup.
259         writeStateFile(newState);
260     }
261 }
262