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