Home | History | Annotate | Download | only in backup
      1 /*
      2  * Copyright (C) 2009 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 android.app.backup;
     18 
     19 import android.annotation.SystemApi;
     20 
     21 import java.io.FileDescriptor;
     22 import java.io.IOException;
     23 
     24 /**
     25  * Provides the structured interface through which a {@link BackupAgent} reads
     26  * information from the backup data set, via its
     27  * {@link BackupAgent#onRestore(BackupDataInput, int, android.os.ParcelFileDescriptor) onRestore()}
     28  * method.  The data is presented as a set of "entities," each
     29  * representing one named record as previously stored by the agent's
     30  * {@link BackupAgent#onBackup(ParcelFileDescriptor,BackupDataOutput,ParcelFileDescriptor)
     31  * onBackup()} implementation.  An entity is composed of a descriptive header plus a
     32  * byte array that holds the raw data saved in the remote backup.
     33  * <p>
     34  * The agent must consume every entity in the data stream, otherwise the
     35  * restored state of the application will be incomplete.
     36  * <h3>Example</h3>
     37  * <p>
     38  * A typical
     39  * {@link BackupAgent#onRestore(BackupDataInput,int,ParcelFileDescriptor)
     40  * onRestore()} implementation might be structured something like this:
     41  * <pre>
     42  * public void onRestore(BackupDataInput data, int appVersionCode,
     43  *                       ParcelFileDescriptor newState) {
     44  *     while (data.readNextHeader()) {
     45  *         String key = data.getKey();
     46  *         int dataSize = data.getDataSize();
     47  *
     48  *         if (key.equals(MY_BACKUP_KEY_ONE)) {
     49  *             // process this kind of record here
     50  *             byte[] buffer = new byte[dataSize];
     51  *             data.readEntityData(buffer, 0, dataSize); // reads the entire entity at once
     52  *
     53  *             // now 'buffer' holds the raw data and can be processed however
     54  *             // the agent wishes
     55  *             processBackupKeyOne(buffer);
     56  *         } else if (key.equals(MY_BACKUP_KEY_TO_IGNORE) {
     57  *             // a key we recognize but wish to discard
     58  *             data.skipEntityData();
     59  *         } // ... etc.
     60  *    }
     61  * }</pre>
     62  */
     63 public class BackupDataInput {
     64     long mBackupReader;
     65 
     66     private EntityHeader mHeader = new EntityHeader();
     67     private boolean mHeaderReady;
     68 
     69     private static class EntityHeader {
     70         String key;
     71         int dataSize;
     72     }
     73 
     74     /** @hide */
     75     @SystemApi
     76     public BackupDataInput(FileDescriptor fd) {
     77         if (fd == null) throw new NullPointerException();
     78         mBackupReader = ctor(fd);
     79         if (mBackupReader == 0) {
     80             throw new RuntimeException("Native initialization failed with fd=" + fd);
     81         }
     82     }
     83 
     84     /** @hide */
     85     @Override
     86     protected void finalize() throws Throwable {
     87         try {
     88             dtor(mBackupReader);
     89         } finally {
     90             super.finalize();
     91         }
     92     }
     93 
     94     /**
     95      * Extract the next entity header from the restore stream.  After this method
     96      * return success, the {@link #getKey()} and {@link #getDataSize()} methods can
     97      * be used to inspect the entity that is now available for processing.
     98      *
     99      * @return <code>true</code> when there is an entity ready for consumption from the
    100      *    restore stream, <code>false</code> if the restore stream has been fully consumed.
    101      * @throws IOException if an error occurred while reading the restore stream
    102      */
    103     public boolean readNextHeader() throws IOException {
    104         int result = readNextHeader_native(mBackupReader, mHeader);
    105         if (result == 0) {
    106             // read successfully
    107             mHeaderReady = true;
    108             return true;
    109         } else if (result > 0) {
    110             // done
    111             mHeaderReady = false;
    112             return false;
    113         } else {
    114             // error
    115             mHeaderReady = false;
    116             throw new IOException("failed: 0x" + Integer.toHexString(result));
    117         }
    118     }
    119 
    120     /**
    121      * Report the key associated with the current entity in the restore stream
    122      * @return the current entity's key string
    123      * @throws IllegalStateException if the next record header has not yet been read
    124      */
    125     public String getKey() {
    126         if (mHeaderReady) {
    127             return mHeader.key;
    128         } else {
    129             throw new IllegalStateException("Entity header not read");
    130         }
    131     }
    132 
    133     /**
    134      * Report the size in bytes of the data associated with the current entity in the
    135      * restore stream.
    136      *
    137      * @return The size of the record's raw data, in bytes
    138      * @throws IllegalStateException if the next record header has not yet been read
    139      */
    140     public int getDataSize() {
    141         if (mHeaderReady) {
    142             return mHeader.dataSize;
    143         } else {
    144             throw new IllegalStateException("Entity header not read");
    145         }
    146     }
    147 
    148     /**
    149      * Read a record's raw data from the restore stream.  The record's header must first
    150      * have been processed by the {@link #readNextHeader()} method.  Multiple calls to
    151      * this method may be made in order to process the data in chunks; not all of it
    152      * must be read in a single call.  Once all of the raw data for the current entity
    153      * has been read, further calls to this method will simply return zero.
    154      *
    155      * @param data An allocated byte array of at least 'size' bytes
    156      * @param offset Offset within the 'data' array at which the data will be placed
    157      *    when read from the stream
    158      * @param size The number of bytes to read in this pass
    159      * @return The number of bytes of data read.  Once all of the data for this entity
    160      *    has been read, further calls to this method will return zero.
    161      * @throws IOException if an error occurred when trying to read the restore data stream
    162      */
    163     public int readEntityData(byte[] data, int offset, int size) throws IOException {
    164         if (mHeaderReady) {
    165             int result = readEntityData_native(mBackupReader, data, offset, size);
    166             if (result >= 0) {
    167                 return result;
    168             } else {
    169                 throw new IOException("result=0x" + Integer.toHexString(result));
    170             }
    171         } else {
    172             throw new IllegalStateException("Entity header not read");
    173         }
    174     }
    175 
    176     /**
    177      * Consume the current entity's data without extracting it into a buffer
    178      * for further processing.  This allows a {@link android.app.backup.BackupAgent} to
    179      * efficiently discard obsolete or otherwise uninteresting records during the
    180      * restore operation.
    181      *
    182      * @throws IOException if an error occurred when trying to read the restore data stream
    183      */
    184     public void skipEntityData() throws IOException {
    185         if (mHeaderReady) {
    186             skipEntityData_native(mBackupReader);
    187         } else {
    188             throw new IllegalStateException("Entity header not read");
    189         }
    190     }
    191 
    192     private native static long ctor(FileDescriptor fd);
    193     private native static void dtor(long mBackupReader);
    194 
    195     private native int readNextHeader_native(long mBackupReader, EntityHeader entity);
    196     private native int readEntityData_native(long mBackupReader, byte[] data, int offset, int size);
    197     private native int skipEntityData_native(long mBackupReader);
    198 }
    199