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 BackupDataInput(FileDescriptor fd)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 finalize()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 */ readNextHeader()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 */ getKey()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 */ getDataSize()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 */ readEntityData(byte[] data, int offset, int size)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 */ skipEntityData()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 ctor(FileDescriptor fd)192 private native static long ctor(FileDescriptor fd); dtor(long mBackupReader)193 private native static void dtor(long mBackupReader); 194 readNextHeader_native(long mBackupReader, EntityHeader entity)195 private native int readNextHeader_native(long mBackupReader, EntityHeader entity); readEntityData_native(long mBackupReader, byte[] data, int offset, int size)196 private native int readEntityData_native(long mBackupReader, byte[] data, int offset, int size); skipEntityData_native(long mBackupReader)197 private native int skipEntityData_native(long mBackupReader); 198 } 199