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 android.annotation.SystemApi;
20
21import java.io.FileDescriptor;
22import 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 */
63public 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