blob: c239b127eef385df286d85f8548dd10ba6e55b9b [file] [log] [blame]
Christopher Tatede6f1312009-07-07 13:11:41 -07001/*
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 com.android.browser;
18
19import java.io.IOException;
20
21import android.app.BackupAgent;
22import android.backup.BackupDataInput;
23import android.backup.BackupDataOutput;
24import android.database.Cursor;
25import android.os.ParcelFileDescriptor;
26import android.provider.Browser;
27import android.provider.Browser.BookmarkColumns;
Christopher Tate9c0dd8c2009-07-10 17:51:48 -070028import android.util.Log;
Christopher Tatede6f1312009-07-07 13:11:41 -070029
Christopher Tate9c0dd8c2009-07-10 17:51:48 -070030import java.io.ByteArrayOutputStream;
Christopher Tatede6f1312009-07-07 13:11:41 -070031import java.io.DataInputStream;
32import java.io.DataOutputStream;
33import java.io.EOFException;
34import java.io.File;
35import java.io.FileInputStream;
36import java.io.FileOutputStream;
Christopher Tate9c0dd8c2009-07-10 17:51:48 -070037import java.util.ArrayList;
Christopher Tatede6f1312009-07-07 13:11:41 -070038import java.util.zip.CRC32;
39
40/**
41 * Settings backup agent for the Android browser. Currently the only thing
42 * stored is the set of bookmarks. It's okay if I/O exceptions are thrown
43 * out of the agent; the calling code handles it and the backup operation
44 * simply fails.
Christopher Tate9c0dd8c2009-07-10 17:51:48 -070045 *
46 * @hide
Christopher Tatede6f1312009-07-07 13:11:41 -070047 */
48public class BrowserBackupAgent extends BackupAgent {
Christopher Tate9c0dd8c2009-07-10 17:51:48 -070049 static final String TAG = "BrowserBookmarkAgent";
50 static final boolean DEBUG = true;
51
Christopher Tatede6f1312009-07-07 13:11:41 -070052 static final String BOOKMARK_KEY = "_bookmarks_";
Christopher Tate9c0dd8c2009-07-10 17:51:48 -070053 /** this version num MUST be incremented if the flattened-file schema ever changes */
54 static final int BACKUP_AGENT_VERSION = 0;
Christopher Tatede6f1312009-07-07 13:11:41 -070055
56 /**
57 * In order to determine whether the bookmark set has changed since the
58 * last time we did a backup, we store the following bits of info in the
59 * state file after a backup:
60 *
61 * 1. the size of the flattened bookmark file
62 * 2. the CRC32 of that file
Christopher Tate9c0dd8c2009-07-10 17:51:48 -070063 * 3. the agent version number [relevant following an OTA]
Christopher Tatede6f1312009-07-07 13:11:41 -070064 *
65 * After we flatten the bookmarks file here in onBackup, we compare its
66 * metrics with the values from the saved state. If they match, it means
67 * the bookmarks didn't really change and we don't need to send the data.
68 * (If they don't match, of course, then they've changed and we do indeed
69 * send the new flattened file to be backed up.)
70 */
71 @Override
72 public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
73 ParcelFileDescriptor newState) throws IOException {
74 long savedFileSize = -1;
75 long savedCrc = -1;
Christopher Tate9c0dd8c2009-07-10 17:51:48 -070076 int savedVersion = -1;
Christopher Tatede6f1312009-07-07 13:11:41 -070077
78 // Extract the previous bookmark file size & CRC from the saved state
79 DataInputStream in = new DataInputStream(
80 new FileInputStream(oldState.getFileDescriptor()));
81 try {
82 savedFileSize = in.readLong();
83 savedCrc = in.readLong();
Christopher Tate9c0dd8c2009-07-10 17:51:48 -070084 savedVersion = in.readInt();
Christopher Tatede6f1312009-07-07 13:11:41 -070085 } catch (EOFException e) {
86 // It means we had no previous state; that's fine
87 }
88
Christopher Tate9c0dd8c2009-07-10 17:51:48 -070089 // Build a flattened representation of the bookmarks table
90 File tmpfile = File.createTempFile("bkp", null, getCacheDir());
Christopher Tatede6f1312009-07-07 13:11:41 -070091 try {
Christopher Tate9c0dd8c2009-07-10 17:51:48 -070092 FileOutputStream outfstream = new FileOutputStream(tmpfile);
93 long newCrc = buildBookmarkFile(outfstream);
94 outfstream.close();
Christopher Tateb4645a12009-07-10 13:36:58 -070095
Christopher Tate9c0dd8c2009-07-10 17:51:48 -070096 // Any changes since the last backup?
97 if ((savedVersion != BACKUP_AGENT_VERSION)
98 || (newCrc != savedCrc)
99 || (tmpfile.length() != savedFileSize)) {
Christopher Tatede6f1312009-07-07 13:11:41 -0700100 // Different checksum or different size, so we need to back it up
101 copyFileToBackup(BOOKMARK_KEY, tmpfile, data);
102 }
103
Christopher Tate9c0dd8c2009-07-10 17:51:48 -0700104 // Record our backup state and we're done
105 writeBackupState(tmpfile.length(), newCrc, newState);
Christopher Tatede6f1312009-07-07 13:11:41 -0700106 } finally {
107 // Make sure to tidy up when we're done
108 tmpfile.delete();
109 }
110 }
111
112 /**
113 * Restore from backup -- reads in the flattened bookmark file as supplied from
114 * the backup service, parses that out, and rebuilds the bookmarks table in the
115 * browser database from it.
116 */
117 @Override
118 public void onRestore(BackupDataInput data, int appVersionCode,
119 ParcelFileDescriptor newState) throws IOException {
120 long crc = -1;
Christopher Tate9c0dd8c2009-07-10 17:51:48 -0700121 File tmpfile = File.createTempFile("rst", null, getFilesDir());
Christopher Tatede6f1312009-07-07 13:11:41 -0700122 try {
123 while (data.readNextHeader()) {
124 if (BOOKMARK_KEY.equals(data.getKey())) {
125 // Read the flattened bookmark data into a temp file
126 crc = copyBackupToFile(data, tmpfile, data.getDataSize());
127
Christopher Tate9c0dd8c2009-07-10 17:51:48 -0700128 FileInputStream infstream = new FileInputStream(tmpfile);
129 DataInputStream in = new DataInputStream(infstream);
130
131 try {
132 int count = in.readInt();
133 ArrayList<Bookmark> bookmarks = new ArrayList<Bookmark>(count);
134
135 // Read all the bookmarks, then process later -- if we can't read
136 // all the data successfully, we don't touch the bookmarks table
137 for (int i = 0; i < count; i++) {
138 Bookmark mark = new Bookmark();
139 mark.url = in.readUTF();
140 mark.visits = in.readInt();
141 mark.date = in.readLong();
142 mark.created = in.readLong();
143 mark.title = in.readUTF();
144 bookmarks.add(mark);
145 }
146
147 // Okay, we have all the bookmarks -- now see if we need to add
148 // them to the browser's database
149 int N = bookmarks.size();
150 if (DEBUG) Log.v(TAG, "Restoring " + N + " bookmarks");
151 String[] urlCol = new String[] { BookmarkColumns.URL };
152 for (int i = 0; i < N; i++) {
153 Bookmark mark = bookmarks.get(i);
154
155 // Does this URL exist in the bookmark table?
156 Cursor cursor = getContentResolver().query(Browser.BOOKMARKS_URI,
157 urlCol, BookmarkColumns.URL + " == '" + mark.url + "' AND " +
158 BookmarkColumns.BOOKMARK + " == 1 ", null, null);
159 // if not, insert it
160 if (cursor.getCount() <= 0) {
161 Log.v(TAG, "Did not see url: " + mark.url);
162 // Right now we do not reconstruct the db entry in its
163 // entirety; we just add a new bookmark with the same data
164 Bookmarks.addBookmark(null, getContentResolver(),
165 mark.url, mark.title, false);
166 } else {
167 Log.v(TAG, "Skipping extant url: " + mark.url);
168 }
169 cursor.close();
170 }
171 } catch (IOException ioe) {
172 Log.w(TAG, "Bad backup data; not restoring");
173 crc = -1;
174 }
Christopher Tatede6f1312009-07-07 13:11:41 -0700175 }
176
177 // Last, write the state we just restored from so we can discern
178 // changes whenever we get invoked for backup in the future
179 writeBackupState(tmpfile.length(), crc, newState);
180 }
181 } finally {
182 // Whatever happens, delete the temp file
183 tmpfile.delete();
184 }
185 }
186
Christopher Tate9c0dd8c2009-07-10 17:51:48 -0700187 class Bookmark {
188 public String url;
189 public int visits;
190 public long date;
191 public long created;
192 public String title;
193 }
Christopher Tatede6f1312009-07-07 13:11:41 -0700194 /*
195 * Utility functions
196 */
197
Christopher Tate9c0dd8c2009-07-10 17:51:48 -0700198 // Flatten the bookmarks table into the given file, calculating its CRC in the process
199 private long buildBookmarkFile(FileOutputStream outfstream) throws IOException {
200 CRC32 crc = new CRC32();
201 ByteArrayOutputStream bufstream = new ByteArrayOutputStream(512);
202 DataOutputStream bout = new DataOutputStream(bufstream);
203
204 Cursor cursor = getContentResolver().query(Browser.BOOKMARKS_URI,
205 new String[] { BookmarkColumns.URL, BookmarkColumns.VISITS,
206 BookmarkColumns.DATE, BookmarkColumns.CREATED,
207 BookmarkColumns.TITLE },
208 BookmarkColumns.BOOKMARK + " == 1 ", null, null);
209
210 // The first thing in the file is the row count...
211 int count = cursor.getCount();
212 if (DEBUG) Log.v(TAG, "Backing up " + count + " bookmarks");
213 bout.writeInt(count);
214 byte[] record = bufstream.toByteArray();
215 crc.update(record);
216 outfstream.write(record);
217
218 // ... followed by the data for each row
219 for (int i = 0; i < count; i++) {
220 cursor.moveToNext();
221
222 String url = cursor.getString(0);
223 int visits = cursor.getInt(1);
224 long date = cursor.getLong(2);
225 long created = cursor.getLong(3);
226 String title = cursor.getString(4);
227
228 // construct the flattened record in a byte array
229 bufstream.reset();
230 bout.writeUTF(url);
231 bout.writeInt(visits);
232 bout.writeLong(date);
233 bout.writeLong(created);
234 bout.writeUTF(title);
235
236 // Update the CRC and write the record to the temp file
237 record = bufstream.toByteArray();
238 crc.update(record);
239 outfstream.write(record);
240
241 if (DEBUG) Log.v(TAG, " wrote url " + url);
242 }
243
244 cursor.close();
245 return crc.getValue();
246 }
247
Christopher Tatede6f1312009-07-07 13:11:41 -0700248 // Write the file to backup as a single record under the given key
249 private void copyFileToBackup(String key, File file, BackupDataOutput data)
250 throws IOException {
251 final int CHUNK = 8192;
252 byte[] buf = new byte[CHUNK];
253
254 int toCopy = (int) file.length();
255 data.writeEntityHeader(key, toCopy);
256
257 FileInputStream in = new FileInputStream(file);
258 int nRead;
259 while (toCopy > 0) {
260 nRead = in.read(buf, 0, CHUNK);
261 data.writeEntityData(buf, nRead);
262 toCopy -= nRead;
263 }
264 in.close();
265 }
266
267 // Read the given file from backup to a file, calculating a CRC32 along the way
268 private long copyBackupToFile(BackupDataInput data, File file, int toRead)
269 throws IOException {
270 final int CHUNK = 8192;
271 byte[] buf = new byte[CHUNK];
272 CRC32 crc = new CRC32();
Christopher Tate9c0dd8c2009-07-10 17:51:48 -0700273 FileOutputStream out = new FileOutputStream(file);
Christopher Tatede6f1312009-07-07 13:11:41 -0700274
275 while (toRead > 0) {
276 int numRead = data.readEntityData(buf, 0, CHUNK);
277 crc.update(buf, 0, numRead);
Christopher Tate9c0dd8c2009-07-10 17:51:48 -0700278 out.write(buf, 0, numRead);
Christopher Tatede6f1312009-07-07 13:11:41 -0700279 toRead -= numRead;
280 }
281
Christopher Tate9c0dd8c2009-07-10 17:51:48 -0700282 out.close();
Christopher Tatede6f1312009-07-07 13:11:41 -0700283 return crc.getValue();
284 }
285
286 // Write the given metrics to the new state file
287 private void writeBackupState(long fileSize, long crc, ParcelFileDescriptor stateFile)
288 throws IOException {
289 DataOutputStream out = new DataOutputStream(
290 new FileOutputStream(stateFile.getFileDescriptor()));
291 out.writeLong(fileSize);
292 out.writeLong(crc);
Christopher Tate9c0dd8c2009-07-10 17:51:48 -0700293 out.writeInt(BACKUP_AGENT_VERSION);
Christopher Tatede6f1312009-07-07 13:11:41 -0700294 }
295}