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