Backup restore : BackupManager « Core Class « Android






Backup restore

   


//src\com\example\android\backuprestore\BackupRestoreActivity.java
/*
 * 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 android.app.Activity;
import android.app.backup.BackupManager;
import android.app.backup.RestoreObserver;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.RadioGroup;

import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;

/**
 * This example is intended to demonstrate a few approaches that an Android
 * application developer can take when implementing a
 * {@link android.app.backup.BackupAgent BackupAgent}.  This feature, added
 * to the Android platform with API version 8, allows the application to
 * back up its data to a device-provided storage location, transparently to
 * the user.  If the application is uninstalled and then reinstalled, or if
 * the user starts using a new Android device, the backed-up information
 * can be provided automatically when the application is reinstalled.
 *
 * <p>Participating in the backup/restore mechanism is simple.  The application
 * provides a class that extends {@link android.app.backup.BackupAgent}, and
 * overrides the two core callback methods
 * {@link android.app.backup.BackupAgent#onBackup(android.os.ParcelFileDescriptor, android.app.backup.BackupDataOutput, android.os.ParcelFileDescriptor) onBackup()}
 * and
 * {@link android.app.backup.BackupAgent#onRestore(android.app.backup.BackupDataInput, int, android.os.ParcelFileDescriptor) onRestore()}.
 * It also publishes the agent class to the operating system by naming the class
 * with the <code>android:backupAgent</code> attribute of the
 * <code>&lt;application&gt;</code> tag in the application's manifest.
 * When a backup or restore operation is performed, the application's agent class
 * is instantiated within the application's execution context and the corresponding
 * method invoked.  Please see the documentation on the
 * {@link android.app.backup.BackupAgent BackupAgent} class for details about the
 * data interchange between the agent and the backup mechanism.
 *
 * <p>This example application maintains a few pieces of simple data, and provides
 * three different sample agent implementations, each illustrating an alternative
 * approach.  The three sample agent classes are:
 *
 * <p><ol type="1">
 * <li>{@link ExampleAgent} - this agent backs up the application's data in a single
 *     record.  It illustrates the direct "by hand" processes of saving backup state for
 *     future reference, sending data to the backup transport, and reading it from a restore
 *     dataset.</li>
 * <li>{@link FileHelperExampleAgent} - this agent takes advantage of the suite of
 *     helper classes provided along with the core BackupAgent API.  By extending
 *     {@link android.app.backup.BackupHelperAgent} and using the targeted
 *     {link android.app.backup.FileBackupHelper FileBackupHelper} class, it achieves
 *     the same result as {@link ExampleAgent} - backing up the application's saved
 *     data file in a single chunk, and restoring it upon request -- in only a few lines
 *     of code.</li>
 * <li>{@link MultiRecordExampleAgent} - this agent stores each separate bit of data
 *     managed by the UI in separate records within the backup dataset.  It illustrates
 *     how an application's backup agent can do selective updates of only what information
 *     has changed since the last backup.</li></ol>
 *
 * <p>You can build the application to use any of these agent implementations simply by
 * changing the class name supplied in the <code>android:backupAgent</code> manifest
 * attribute to indicate the agent you wish to use.  <strong>Note:</strong> the backed-up
 * data and backup-state tracking of these agents are not compatible!  If you change which
 * agent the application uses, you should also wipe the backup state associated with
 * the application on your handset.  The 'bmgr' shell application on the device can
 * do this; simply run the following command from your desktop computer while attached
 * to the device via adb:
 *
 * <p><code>adb shell bmgr wipe com.example.android.backuprestore</code>
 *
 * <p>You can then install the new version of the application, and its next backup pass
 * will start over from scratch with the new agent.
 */
public class BackupRestoreActivity extends Activity {
    static final String TAG = "BRActivity";

    /**
     * We serialize access to our persistent data through a global static
     * object.  This ensures that in the unlikely event of the our backup/restore
     * agent running to perform a backup while our UI is updating the file, the
     * agent will not accidentally read partially-written data.
     *
     * <p>Curious but true: a zero-length array is slightly lighter-weight than
     * merely allocating an Object, and can still be synchronized on.
     */
    static final Object[] sDataLock = new Object[0];

    /** Also supply a global standard file name for everyone to use */
    static final String DATA_FILE_NAME = "saved_data";

    /** The various bits of UI that the user can manipulate */
    RadioGroup mFillingGroup;
    CheckBox mAddMayoCheckbox;
    CheckBox mAddTomatoCheckbox;

    /** Cache a reference to our persistent data file */
    File mDataFile;

    /** Also cache a reference to the Backup Manager */
    BackupManager mBackupManager;

    /** Set up the activity and populate its UI from the persistent data. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        /** Establish the activity's UI */
        setContentView(R.layout.backup_restore);

        /** Once the UI has been inflated, cache the controls for later */
        mFillingGroup = (RadioGroup) findViewById(R.id.filling_group);
        mAddMayoCheckbox = (CheckBox) findViewById(R.id.mayo);
        mAddTomatoCheckbox = (CheckBox) findViewById(R.id.tomato);

        /** Set up our file bookkeeping */
        mDataFile = new File(getFilesDir(), BackupRestoreActivity.DATA_FILE_NAME);

        /** It is handy to keep a BackupManager cached */
        mBackupManager = new BackupManager(this);

        /**
         * Finally, build the UI from the persistent store
         */
        populateUI();
    }

    /**
     * Configure the UI based on our persistent data, creating the
     * data file and establishing defaults if necessary.
     */
    void populateUI() {
        RandomAccessFile file;

        // Default values in case there's no data file yet
        int whichFilling = R.id.pastrami;
        boolean addMayo = false;
        boolean addTomato = false;

        /** Hold the data-access lock around access to the file */
        synchronized (BackupRestoreActivity.sDataLock) {
            boolean exists = mDataFile.exists();
            try {
                file = new RandomAccessFile(mDataFile, "rw");
                if (exists) {
                    Log.v(TAG, "datafile exists");
                    whichFilling = file.readInt();
                    addMayo = file.readBoolean();
                    addTomato = file.readBoolean();
                    Log.v(TAG, "  mayo=" + addMayo
                            + " tomato=" + addTomato
                            + " filling=" + whichFilling);
                } else {
                    // The default values were configured above: write them
                    // to the newly-created file.
                    Log.v(TAG, "creating default datafile");
                    writeDataToFileLocked(file,
                            addMayo, addTomato, whichFilling);

                    // We also need to perform an initial backup; ask for one
                    mBackupManager.dataChanged();
                }
            } catch (IOException ioe) {
                
            }
        }

        /** Now that we've processed the file, build the UI outside the lock */
        mFillingGroup.check(whichFilling);
        mAddMayoCheckbox.setChecked(addMayo);
        mAddTomatoCheckbox.setChecked(addTomato);

        /**
         * We also want to record the new state when the user makes changes,
         * so install simple observers that do this
         */
        mFillingGroup.setOnCheckedChangeListener(
                new RadioGroup.OnCheckedChangeListener() {
                    public void onCheckedChanged(RadioGroup group,
                            int checkedId) {
                        // As with the checkbox listeners, rewrite the
                        // entire state file
                        Log.v(TAG, "New radio item selected: " + checkedId);
                        recordNewUIState();
                    }
                });

        CompoundButton.OnCheckedChangeListener checkListener
                = new CompoundButton.OnCheckedChangeListener() {
            public void onCheckedChanged(CompoundButton buttonView,
                    boolean isChecked) {
                // Whichever one is altered, we rewrite the entire UI state
                Log.v(TAG, "Checkbox toggled: " + buttonView);
                recordNewUIState();
            }
        };
        mAddMayoCheckbox.setOnCheckedChangeListener(checkListener);
        mAddTomatoCheckbox.setOnCheckedChangeListener(checkListener);
    }

    /**
     * Handy helper routine to write the UI data to a file.
     */
    void writeDataToFileLocked(RandomAccessFile file,
            boolean addMayo, boolean addTomato, int whichFilling)
        throws IOException {
            file.setLength(0L);
            file.writeInt(whichFilling);
            file.writeBoolean(addMayo);
            file.writeBoolean(addTomato);
            Log.v(TAG, "NEW STATE: mayo=" + addMayo
                    + " tomato=" + addTomato
                    + " filling=" + whichFilling);
    }

    /**
     * Another helper; this one reads the current UI state and writes that
     * to the persistent store, then tells the backup manager that we need
     * a backup.
     */
    void recordNewUIState() {
        boolean addMayo = mAddMayoCheckbox.isChecked();
        boolean addTomato = mAddTomatoCheckbox.isChecked();
        int whichFilling = mFillingGroup.getCheckedRadioButtonId();
        try {
            synchronized (BackupRestoreActivity.sDataLock) {
                RandomAccessFile file = new RandomAccessFile(mDataFile, "rw");
                writeDataToFileLocked(file, addMayo, addTomato, whichFilling);
            }
        } catch (IOException e) {
            Log.e(TAG, "Unable to record new UI state");
        }

        mBackupManager.dataChanged();
    }

    /**
     * Click handler, designated in the layout, that runs a restore of the app's
     * most recent data when the button is pressed.
     */
    public void onRestoreButtonClick(View v) {
        Log.v(TAG, "Requesting restore of our most recent data");
        mBackupManager.requestRestore(
                new RestoreObserver() {
                    public void restoreFinished(int error) {
                        /** Done with the restore!  Now draw the new state of our data */
                        Log.v(TAG, "Restore finished, error = " + error);
                        populateUI();
                    }
                }
        );
    }
}



//src\com\example\android\backuprestore\ExampleAgent.java
/*
 * 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 android.app.backup.BackupAgent;
import android.app.backup.BackupDataInput;
import android.app.backup.BackupDataOutput;
import android.os.ParcelFileDescriptor;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
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;

/**
 * This is the backup/restore agent class for the BackupRestore sample
 * application.  This particular agent illustrates using the backup and
 * restore APIs directly, without taking advantage of any helper classes.
 */
public class ExampleAgent extends BackupAgent {
    /**
     * We put a simple version number into the state files so that we can
     * tell properly how to read "old" versions if at some point we want
     * to change what data we back up and how we store the state blob.
     */
    static final int AGENT_VERSION = 1;

    /**
     * Pick an arbitrary string to use as the "key" under which the
     * data is backed up.  This key identifies different data records
     * within this one application's data set.  Since we only maintain
     * one piece of data we don't need to distinguish, so we just pick
     * some arbitrary tag to use. 
     */
    static final String APP_DATA_KEY = "alldata";

    /** The app's current data, read from the live disk file */
    boolean mAddMayo;
    boolean mAddTomato;
    int mFilling;

    /** The location of the application's persistent data file */
    File mDataFile;

    /** For convenience, we set up the File object for the app's data on creation */
    @Override
    public void onCreate() {
        mDataFile = new File(getFilesDir(), BackupRestoreActivity.DATA_FILE_NAME);
    }

    /**
     * The set of data backed up by this application is very small: just
     * two booleans and an integer.  With such a simple dataset, it's
     * easiest to simply store a copy of the backed-up data as the state
     * blob describing the last dataset backed up.  The state file
     * contents can be anything; it is private to the agent class, and
     * is never stored off-device.
     *
     * <p>One thing that an application may wish to do is tag the state
     * blob contents with a version number.  This is so that if the
     * application is upgraded, the next time it attempts to do a backup,
     * it can detect that the last backup operation was performed by an
     * older version of the agent, and might therefore require different
     * handling.
     */
    @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 the new state file descriptor is null, this is the first time
        // a backup is being performed, so we know we have to write the
        // data.  If there <em>is</em> a previous state blob, we want to
        // double check whether the current data is actually different from
        // our last backup, so that we can avoid transmitting redundant
        // data to the storage backend.
        boolean doBackup = (oldState == null);
        if (!doBackup) {
            doBackup = compareStateFile(oldState);
        }

        // If we decided that we do in fact need to write our dataset, go
        // ahead and do that.  The way this agent backs up the data is to
        // flatten it into a single buffer, then write that to the backup
        // transport under the single key string.
        if (doBackup) {
            ByteArrayOutputStream bufStream = new ByteArrayOutputStream();

            // We use a DataOutputStream to write structured data into
            // the buffering stream
            DataOutputStream outWriter = new DataOutputStream(bufStream);
            outWriter.writeInt(mFilling);
            outWriter.writeBoolean(mAddMayo);
            outWriter.writeBoolean(mAddTomato);

            // Okay, we've flattened the data for transmission.  Pull it
            // out of the buffering stream object and send it off.
            byte[] buffer = bufStream.toByteArray();
            int len = buffer.length;
            data.writeEntityHeader(APP_DATA_KEY, len);
            data.writeEntityData(buffer, len);
        }

        // Finally, in all cases, we need to write the new state blob
        writeStateFile(newState);
    }

    /**
     * Helper routine - read a previous state file and decide whether to
     * perform a backup based on its contents.
     *
     * @return <code>true</code> if the application's data has changed since
     *   the last backup operation; <code>false</code> otherwise.
     */
    boolean compareStateFile(ParcelFileDescriptor oldState) {
        FileInputStream instream = new FileInputStream(oldState.getFileDescriptor());
        DataInputStream in = new DataInputStream(instream);

        try {
            int stateVersion = in.readInt();
            if (stateVersion > AGENT_VERSION) {
                // Whoops; the last version of the app that backed up
                // data on this device was <em>newer</em> than the current
                // version -- the user has downgraded.  That's problematic.
                // In this implementation, we recover by simply rewriting
                // the backup.
                return true;
            }

            // The state data we store is just a mirror of the app's data;
            // read it from the state file then return 'true' if any of
            // it differs from the current data.
            int lastFilling = in.readInt();
            boolean lastMayo = in.readBoolean();
            boolean lastTomato = in.readBoolean();

            return (lastFilling != mFilling)
                    || (lastTomato != mAddTomato)
                    || (lastMayo != mAddMayo);
        } catch (IOException e) {
            // If something went wrong reading the state file, be safe
            // and back up the data again.
            return true;
        }
    }

    /**
     * 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(AGENT_VERSION);
        out.writeInt(mFilling);
        out.writeBoolean(mAddMayo);
        out.writeBoolean(mAddTomato);
    }

    /**
     * This application does not do any "live" restores of its own data,
     * so the only time a restore will happen is when the application is
     * installed.  This means that the activity itself is not going to
     * be running while we change its data out from under it.  That, in
     * turn, means that there is no need to send out any sort of notification
     * of the new data:  we only need to read the data from the stream
     * provided here, build the application's new data file, and then
     * write our new backup state blob that will be consulted at the next
     * backup operation.
     * 
     * <p>We don't bother checking the versionCode of the app who originated
     * the data because we have never revised the backup data format.  If
     * we had, the 'appVersionCode' parameter would tell us how we should
     * interpret the data we're about to read.
     */
    @Override
    public void onRestore(BackupDataInput data, int appVersionCode,
            ParcelFileDescriptor newState) throws IOException {
        // We should only see one entity in the data stream, but the safest
        // way to consume it is using a while() loop
        while (data.readNextHeader()) {
            String key = data.getKey();
            int dataSize = data.getDataSize();

            if (APP_DATA_KEY.equals(key)) {
                // It's our saved data, a flattened chunk of data all in
                // one buffer.  Use some handy structured I/O classes to
                // extract it.
                byte[] dataBuf = new byte[dataSize];
                data.readEntityData(dataBuf, 0, dataSize);
                ByteArrayInputStream baStream = new ByteArrayInputStream(dataBuf);
                DataInputStream in = new DataInputStream(baStream);

                mFilling = in.readInt();
                mAddMayo = in.readBoolean();
                mAddTomato = in.readBoolean();

                // Now we are ready to construct the app's data file based
                // on the data we are restoring from.
                synchronized (BackupRestoreActivity.sDataLock) {
                    RandomAccessFile file = new RandomAccessFile(mDataFile, "rw");
                    file.setLength(0L);
                    file.writeInt(mFilling);
                    file.writeBoolean(mAddMayo);
                    file.writeBoolean(mAddTomato);
                }
            } else {
                // Curious!  This entity is data under a key we do not
                // understand how to process.  Just skip it.
                data.skipEntityData();
            }
        }

        // The last thing to do is write the state blob that describes the
        // app's data as restored from backup.
        writeStateFile(newState);
    }
}



//src\com\example\android\backuprestore\FileHelperExampleAgent.java
/*
 * 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.IOException;

import android.app.backup.BackupAgentHelper;
import android.app.backup.BackupDataInput;
import android.app.backup.BackupDataOutput;
import android.app.backup.FileBackupHelper;
import android.os.ParcelFileDescriptor;

/**
 * This agent backs up the application's data using the BackupAgentHelper
 * infrastructure.  In this application's case, the backup data is merely
 * a duplicate of the stored data file; that makes it a perfect candidate
 * for backing up using the {@link android.app.backup.FileBackupHelper} class
 * provided by the Android operating system.
 *
 * <p>"Backup helpers" are a general mechanism that an agent implementation
 * uses by extending {@link BackupAgentHelper} rather than the basic
 * {@link BackupAgent} class.
 *
 * <p>By itself, the FileBackupHelper is properly handling the backup and
 * restore of the datafile that we've configured it with, but it does
 * not know about the potential need to use locking around its access
 * to it.  However, it is straightforward to override
 * {@link #onBackup()} and {@link #onRestore()} to supply the necessary locking
 * around the helper's operation.
 */
public class FileHelperExampleAgent extends BackupAgentHelper {
    /**
     * The "key" string passed when adding a helper is a token used to
     * disambiguate between entities supplied by multiple different helper
     * objects.  They only need to be unique among the helpers within this
     * one agent class, not globally unique.
     */
    static final String FILE_HELPER_KEY = "the_file";

    /**
     * The {@link android.app.backup.FileBackupHelper FileBackupHelper} class
     * does nearly all of the work for our use case:  backup and restore of a
     * file stored within our application's getFilesDir() location.  It will
     * also handle files stored at any subpath within that location.  All we
     * need to do is a bit of one-time configuration: installing the helper
     * when this agent object is created.
     */
    @Override
    public void onCreate() {
        // All we need to do when working within the BackupAgentHelper mechanism
        // is to install the helper that will process and back up the files we
        // care about.  In this case, it's just one file.
        FileBackupHelper helper = new FileBackupHelper(this, BackupRestoreActivity.DATA_FILE_NAME);
        addHelper(FILE_HELPER_KEY, helper);
    }

    /**
     * We want to ensure that the UI is not trying to rewrite the data file
     * while we're reading it for backup, so we override this method to
     * supply the necessary locking.
     */
    @Override
    public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
             ParcelFileDescriptor newState) throws IOException {
        // Hold the lock while the FileBackupHelper performs the backup operation
        synchronized (BackupRestoreActivity.sDataLock) {
            super.onBackup(oldState, data, newState);
        }
    }

    /**
     * Adding locking around the file rewrite that happens during restore is
     * similarly straightforward.
     */
    @Override
    public void onRestore(BackupDataInput data, int appVersionCode,
            ParcelFileDescriptor newState) throws IOException {
        // Hold the lock while the FileBackupHelper restores the file from
        // the data provided here.
        synchronized (BackupRestoreActivity.sDataLock) {
            super.onRestore(data, appVersionCode, newState);
        }
    }
}



//src\com\example\android\backuprestore\MultiRecordExampleAgent.java
/*
 * 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);
    }
}



//res\layout\backup_restore.xml
<?xml version="1.0" encoding="utf-8"?>
<!-- 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.
-->

<!-- Layout description of the BackupRestore sample's main activity -->

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <ScrollView
        android:orientation="vertical"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:layout_weight="1">

        <LinearLayout
            android:orientation="vertical"
            android:layout_width="match_parent"
            android:layout_height="wrap_content">

            <TextView android:text="@string/filling_text"
                android:textSize="20dp"
                android:layout_marginTop="20dp"
                android:layout_marginBottom="10dp"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"/>

            <RadioGroup android:id="@+id/filling_group"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginLeft="20dp"
                android:orientation="vertical">

                <RadioButton android:id="@+id/bacon"
                    android:text="@string/bacon_label"/>
                <RadioButton android:id="@+id/pastrami"
                    android:text="@string/pastrami_label"/>
                <RadioButton android:id="@+id/hummus"
                    android:text="@string/hummus_label"/>

            </RadioGroup>

            <TextView android:text="@string/extras_text"
                android:textSize="20dp"
                android:layout_marginTop="20dp"
                android:layout_marginBottom="10dp"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"/>

            <CheckBox android:id="@+id/mayo"
                android:text="@string/mayo_text"
                android:layout_marginLeft="20dp"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"/>

            <CheckBox android:id="@+id/tomato"
                android:text="@string/tomato_text"
                android:layout_marginLeft="20dp"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"/>

        </LinearLayout>

    </ScrollView>

    <Button android:id="@+id/restore_button"
        android:text="@string/restore_text"
        android:onClick="onRestoreButtonClick"
        android:layout_height="wrap_content"
        android:layout_width="wrap_content"
        android:layout_weight="0" />

</LinearLayout>




//res\values\strings.xml
<?xml version="1.0" encoding="utf-8"?>
<!-- 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.
-->

<resources>
  <string name="filling_text">Choose a sandwich filling:</string>
  <string name="bacon_label">Bacon</string>
  <string name="pastrami_label">Pastrami</string>
  <string name="hummus_label">Hummus</string>

  <string name="extras_text">Extras:</string>
  <string name="mayo_text">Mayonnaise\?</string>
  <string name="tomato_text">Tomato\?</string>

  <string name="restore_text">Restore last data</string>
</resources>

   
    
    
  








Related examples in the same category