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