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