Home | History | Annotate | Download | only in backuprestore
      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
     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
     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      */
    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      */
    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
    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