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