1f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate/* 2f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate * Copyright (C) 2015 The Android Open Source Project 3f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate * 4f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate * Licensed under the Apache License, Version 2.0 (the "License"); 5f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate * you may not use this file except in compliance with the License. 6f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate * You may obtain a copy of the License at 7f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate * 8f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate * http://www.apache.org/licenses/LICENSE-2.0 9f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate * 10f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate * Unless required by applicable law or agreed to in writing, software 11f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate * distributed under the License is distributed on an "AS IS" BASIS, 12f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate * See the License for the specific language governing permissions and 14f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate * limitations under the License. 15f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate */ 16f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate 17f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tatepackage android.app.backup; 18f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate 19f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tateimport android.os.ParcelFileDescriptor; 20f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tateimport android.util.ArrayMap; 21f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tateimport android.util.Log; 22f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate 23f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tateimport java.io.ByteArrayInputStream; 24f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tateimport java.io.ByteArrayOutputStream; 25f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tateimport java.io.DataInputStream; 26f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tateimport java.io.DataOutputStream; 27f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tateimport java.io.EOFException; 28f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tateimport java.io.FileInputStream; 29f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tateimport java.io.FileOutputStream; 30f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tateimport java.io.IOException; 31f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tateimport java.util.zip.CRC32; 32f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tateimport java.util.zip.DeflaterOutputStream; 33f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tateimport java.util.zip.InflaterInputStream; 34f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate 35f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate/** 36f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate * Utility class for writing BackupHelpers whose underlying data is a 37f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate * fixed set of byte-array blobs. The helper manages diff detection 38f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate * and compression on the wire. 39f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate * 40f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate * @hide 41f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate */ 42f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tatepublic abstract class BlobBackupHelper implements BackupHelper { 43f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate private static final String TAG = "BlobBackupHelper"; 44e491d142b2489bef96c8dce65c04a7a9c445f6e1Christopher Tate private static final boolean DEBUG = false; 45f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate 46f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate private final int mCurrentBlobVersion; 47f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate private final String[] mKeys; 48f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate 49f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate public BlobBackupHelper(int currentBlobVersion, String... keys) { 50f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate mCurrentBlobVersion = currentBlobVersion; 51f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate mKeys = keys; 52f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate } 53f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate 54f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate // Client interface 55f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate 56f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate /** 57f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate * Generate and return the byte array containing the backup payload describing 58f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate * the current data state. During a backup operation this method is called once 59f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate * per key that was supplied to the helper's constructor. 60f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate * 61f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate * @return A byte array containing the data blob that the caller wishes to store, 62f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate * or {@code null} if the current state is empty or undefined. 63f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate */ 64f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate abstract protected byte[] getBackupPayload(String key); 65f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate 66f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate /** 67f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate * Given a byte array that was restored from backup, do whatever is appropriate 68f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate * to apply that described state in the live system. This method is called once 69f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate * per key/value payload that was delivered for restore. Typically data is delivered 70f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate * for restore in lexical order by key, <i>not</i> in the order in which the keys 71f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate * were supplied in the constructor. 72f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate * 73f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate * @param payload The byte array that was passed to {@link #getBackupPayload()} 74f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate * on the ancestral device. 75f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate */ 76f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate abstract protected void applyRestoredPayload(String key, byte[] payload); 77f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate 78f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate 79f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate // Internal implementation 80f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate 81f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate /* 82f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate * State on-disk format: 83f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate * [Int] : overall blob version number 84f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate * [Int=N] : number of keys represented in the state blob 85f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate * N* : 86f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate * [String] key 87f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate * [Long] blob checksum, calculated after compression 88f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate */ 89f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate @SuppressWarnings("resource") 90f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate private ArrayMap<String, Long> readOldState(ParcelFileDescriptor oldStateFd) { 91f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate final ArrayMap<String, Long> state = new ArrayMap<String, Long>(); 92f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate 93f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate FileInputStream fis = new FileInputStream(oldStateFd.getFileDescriptor()); 94e491d142b2489bef96c8dce65c04a7a9c445f6e1Christopher Tate DataInputStream in = new DataInputStream(fis); 95f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate 96f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate try { 97f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate int version = in.readInt(); 98f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate if (version <= mCurrentBlobVersion) { 99f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate final int numKeys = in.readInt(); 100e491d142b2489bef96c8dce65c04a7a9c445f6e1Christopher Tate if (DEBUG) { 101e491d142b2489bef96c8dce65c04a7a9c445f6e1Christopher Tate Log.i(TAG, " " + numKeys + " keys in state record"); 102e491d142b2489bef96c8dce65c04a7a9c445f6e1Christopher Tate } 103f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate for (int i = 0; i < numKeys; i++) { 104f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate String key = in.readUTF(); 105f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate long checksum = in.readLong(); 106e491d142b2489bef96c8dce65c04a7a9c445f6e1Christopher Tate if (DEBUG) { 107e491d142b2489bef96c8dce65c04a7a9c445f6e1Christopher Tate Log.i(TAG, " key '" + key + "' checksum is " + checksum); 108e491d142b2489bef96c8dce65c04a7a9c445f6e1Christopher Tate } 109f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate state.put(key, checksum); 110f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate } 111f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate } else { 112f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate Log.w(TAG, "Prior state from unrecognized version " + version); 113f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate } 114f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate } catch (EOFException e) { 115f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate // Empty file is expected on first backup, so carry on. If the state 116f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate // is truncated we just treat it the same way. 117e491d142b2489bef96c8dce65c04a7a9c445f6e1Christopher Tate if (DEBUG) { 118e491d142b2489bef96c8dce65c04a7a9c445f6e1Christopher Tate Log.i(TAG, "Hit EOF reading prior state"); 119e491d142b2489bef96c8dce65c04a7a9c445f6e1Christopher Tate } 120f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate state.clear(); 121f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate } catch (Exception e) { 122f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate Log.e(TAG, "Error examining prior backup state " + e.getMessage()); 123f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate state.clear(); 124f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate } 125f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate 126f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate return state; 127f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate } 128f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate 129f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate /** 130f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate * New overall state record 131f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate */ 132f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate private void writeBackupState(ArrayMap<String, Long> state, ParcelFileDescriptor stateFile) { 133f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate try { 134f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate FileOutputStream fos = new FileOutputStream(stateFile.getFileDescriptor()); 135f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate 136f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate // We explicitly don't close 'out' because we must not close the backing fd. 137f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate // The FileOutputStream will not close it implicitly. 138f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate @SuppressWarnings("resource") 139f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate DataOutputStream out = new DataOutputStream(fos); 140f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate 141f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate out.writeInt(mCurrentBlobVersion); 142f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate 143f7cb8a0b50968f51994279b0ae3fede0a586ba45Christopher Tate final int N = (state != null) ? state.size() : 0; 144f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate out.writeInt(N); 145f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate for (int i = 0; i < N; i++) { 146e491d142b2489bef96c8dce65c04a7a9c445f6e1Christopher Tate final String key = state.keyAt(i); 147e491d142b2489bef96c8dce65c04a7a9c445f6e1Christopher Tate final long checksum = state.valueAt(i).longValue(); 148e491d142b2489bef96c8dce65c04a7a9c445f6e1Christopher Tate if (DEBUG) { 149e491d142b2489bef96c8dce65c04a7a9c445f6e1Christopher Tate Log.i(TAG, " writing key " + key + " checksum = " + checksum); 150e491d142b2489bef96c8dce65c04a7a9c445f6e1Christopher Tate } 151e491d142b2489bef96c8dce65c04a7a9c445f6e1Christopher Tate out.writeUTF(key); 152e491d142b2489bef96c8dce65c04a7a9c445f6e1Christopher Tate out.writeLong(checksum); 153f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate } 154f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate } catch (IOException e) { 155f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate Log.e(TAG, "Unable to write updated state", e); 156f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate } 157f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate } 158f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate 159f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate // Also versions the deflated blob internally in case we need to revise it 160f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate private byte[] deflate(byte[] data) { 161f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate byte[] result = null; 162f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate if (data != null) { 163f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate try { 164f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate ByteArrayOutputStream sink = new ByteArrayOutputStream(); 165f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate DataOutputStream headerOut = new DataOutputStream(sink); 166f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate 167f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate // write the header directly to the sink ahead of the deflated payload 168f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate headerOut.writeInt(mCurrentBlobVersion); 169f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate 170f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate DeflaterOutputStream out = new DeflaterOutputStream(sink); 171f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate out.write(data); 172f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate out.close(); // finishes and commits the compression run 173f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate result = sink.toByteArray(); 174f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate if (DEBUG) { 175f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate Log.v(TAG, "Deflated " + data.length + " bytes to " + result.length); 176f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate } 177f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate } catch (IOException e) { 178f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate Log.w(TAG, "Unable to process payload: " + e.getMessage()); 179f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate } 180f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate } 181f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate return result; 182f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate } 183f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate 184f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate // Returns null if inflation failed 185f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate private byte[] inflate(byte[] compressedData) { 186f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate byte[] result = null; 187f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate if (compressedData != null) { 188f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate try { 189f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate ByteArrayInputStream source = new ByteArrayInputStream(compressedData); 190f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate DataInputStream headerIn = new DataInputStream(source); 191f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate int version = headerIn.readInt(); 192f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate if (version > mCurrentBlobVersion) { 193f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate Log.w(TAG, "Saved payload from unrecognized version " + version); 194f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate return null; 195f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate } 196f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate 197f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate InflaterInputStream in = new InflaterInputStream(source); 198f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate ByteArrayOutputStream inflated = new ByteArrayOutputStream(); 199f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate byte[] buffer = new byte[4096]; 200f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate int nRead; 201f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate while ((nRead = in.read(buffer)) > 0) { 202f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate inflated.write(buffer, 0, nRead); 203f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate } 204f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate in.close(); 205f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate inflated.flush(); 206f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate result = inflated.toByteArray(); 207f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate if (DEBUG) { 208f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate Log.v(TAG, "Inflated " + compressedData.length + " bytes to " + result.length); 209f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate } 210f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate } catch (IOException e) { 211f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate // result is still null here 212f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate Log.w(TAG, "Unable to process restored payload: " + e.getMessage()); 213f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate } 214f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate } 215f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate return result; 216f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate } 217f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate 218f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate private long checksum(byte[] buffer) { 219f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate if (buffer != null) { 220f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate try { 221f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate CRC32 crc = new CRC32(); 222f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate ByteArrayInputStream bis = new ByteArrayInputStream(buffer); 223f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate byte[] buf = new byte[4096]; 224f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate int nRead = 0; 225f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate while ((nRead = bis.read(buf)) >= 0) { 226f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate crc.update(buf, 0, nRead); 227f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate } 228f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate return crc.getValue(); 229f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate } catch (Exception e) { 230f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate // whoops; fall through with an explicitly bogus checksum 231f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate } 232f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate } 233f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate return -1; 234f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate } 235f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate 236f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate // BackupHelper interface 237f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate 238f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate @Override 239f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate public void performBackup(ParcelFileDescriptor oldStateFd, BackupDataOutput data, 240f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate ParcelFileDescriptor newStateFd) { 241e491d142b2489bef96c8dce65c04a7a9c445f6e1Christopher Tate if (DEBUG) { 242e491d142b2489bef96c8dce65c04a7a9c445f6e1Christopher Tate Log.i(TAG, "Performing backup for " + this.getClass().getName()); 243e491d142b2489bef96c8dce65c04a7a9c445f6e1Christopher Tate } 244f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate 245f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate final ArrayMap<String, Long> oldState = readOldState(oldStateFd); 246f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate final ArrayMap<String, Long> newState = new ArrayMap<String, Long>(); 247f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate 248f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate try { 249f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate for (String key : mKeys) { 250f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate final byte[] payload = deflate(getBackupPayload(key)); 251f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate final long checksum = checksum(payload); 252e491d142b2489bef96c8dce65c04a7a9c445f6e1Christopher Tate if (DEBUG) { 253e491d142b2489bef96c8dce65c04a7a9c445f6e1Christopher Tate Log.i(TAG, "Key " + key + " backup checksum is " + checksum); 254e491d142b2489bef96c8dce65c04a7a9c445f6e1Christopher Tate } 255f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate newState.put(key, checksum); 256f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate 257f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate Long oldChecksum = oldState.get(key); 258e491d142b2489bef96c8dce65c04a7a9c445f6e1Christopher Tate if (oldChecksum == null || checksum != oldChecksum.longValue()) { 259f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate if (DEBUG) { 260e491d142b2489bef96c8dce65c04a7a9c445f6e1Christopher Tate Log.i(TAG, "Checksum has changed from " + oldChecksum + " to " + checksum 261e491d142b2489bef96c8dce65c04a7a9c445f6e1Christopher Tate + " for key " + key + ", writing"); 262f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate } 263f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate if (payload != null) { 264f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate data.writeEntityHeader(key, payload.length); 265f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate data.writeEntityData(payload, payload.length); 266f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate } else { 267f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate // state's changed but there's no current payload => delete 268f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate data.writeEntityHeader(key, -1); 269f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate } 270f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate } else { 271f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate if (DEBUG) { 272f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate Log.i(TAG, "No change under key " + key + " => not writing"); 273f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate } 274f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate } 275f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate } 276f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate } catch (Exception e) { 277f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate Log.w(TAG, "Unable to record notification state: " + e.getMessage()); 278f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate newState.clear(); 279f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate } finally { 280e491d142b2489bef96c8dce65c04a7a9c445f6e1Christopher Tate // Always rewrite the state even if nothing changed 281f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate writeBackupState(newState, newStateFd); 282f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate } 283f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate } 284f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate 285f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate @Override 286f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate public void restoreEntity(BackupDataInputStream data) { 287f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate final String key = data.getKey(); 288f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate try { 289f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate // known key? 290f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate int which; 291f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate for (which = 0; which < mKeys.length; which++) { 292f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate if (key.equals(mKeys[which])) { 293f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate break; 294f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate } 295f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate } 296f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate if (which >= mKeys.length) { 297f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate Log.e(TAG, "Unrecognized key " + key + ", ignoring"); 298f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate return; 299f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate } 300f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate 301f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate byte[] compressed = new byte[data.size()]; 302f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate data.read(compressed); 303f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate byte[] payload = inflate(compressed); 304f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate applyRestoredPayload(key, payload); 305f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate } catch (Exception e) { 306f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate Log.e(TAG, "Exception restoring entity " + key + " : " + e.getMessage()); 307f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate } 308f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate } 309f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate 310f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate @Override 311f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate public void writeNewStateDescription(ParcelFileDescriptor newState) { 312f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate // Just ensure that we do a full backup the first time after a restore 313e491d142b2489bef96c8dce65c04a7a9c445f6e1Christopher Tate if (DEBUG) { 314e491d142b2489bef96c8dce65c04a7a9c445f6e1Christopher Tate Log.i(TAG, "Writing state description after restore"); 315e491d142b2489bef96c8dce65c04a7a9c445f6e1Christopher Tate } 316f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate writeBackupState(null, newState); 317f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate } 318f9767d680d63ac1771a17ccc5775cd337ff967b8Christopher Tate} 319