blob: 6f6e829c8a7faab7ac11ef3239dfbced7a4c983c [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
Christopher Tateb6a65442010-03-05 15:47:48 -080021import android.app.backup.BackupAgent;
22import android.app.backup.BackupDataInput;
23import android.app.backup.BackupDataOutput;
Christopher Tatede6f1312009-07-07 13:11:41 -070024import 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 Tatef8b59982009-09-29 12:40:25 -070049 static final String TAG = "BrowserBackupAgent";
50 static final boolean DEBUG = false;
Christopher Tate9c0dd8c2009-07-10 17:51:48 -070051
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();
Christopher Tatef8b59982009-09-29 12:40:25 -0700150 int nUnique = 0;
Christopher Tate9c0dd8c2009-07-10 17:51:48 -0700151 if (DEBUG) Log.v(TAG, "Restoring " + N + " bookmarks");
152 String[] urlCol = new String[] { BookmarkColumns.URL };
153 for (int i = 0; i < N; i++) {
154 Bookmark mark = bookmarks.get(i);
155
156 // Does this URL exist in the bookmark table?
157 Cursor cursor = getContentResolver().query(Browser.BOOKMARKS_URI,
158 urlCol, BookmarkColumns.URL + " == '" + mark.url + "' AND " +
159 BookmarkColumns.BOOKMARK + " == 1 ", null, null);
160 // if not, insert it
161 if (cursor.getCount() <= 0) {
Christopher Tatef8b59982009-09-29 12:40:25 -0700162 if (DEBUG) Log.v(TAG, "Did not see url: " + mark.url);
Christopher Tate9c0dd8c2009-07-10 17:51:48 -0700163 // Right now we do not reconstruct the db entry in its
164 // entirety; we just add a new bookmark with the same data
165 Bookmarks.addBookmark(null, getContentResolver(),
Ben Murdochaac7aa62009-09-17 16:57:40 +0100166 mark.url, mark.title, null, false);
Christopher Tatef8b59982009-09-29 12:40:25 -0700167 nUnique++;
Christopher Tate9c0dd8c2009-07-10 17:51:48 -0700168 } else {
Christopher Tatef8b59982009-09-29 12:40:25 -0700169 if (DEBUG) Log.v(TAG, "Skipping extant url: " + mark.url);
Christopher Tate9c0dd8c2009-07-10 17:51:48 -0700170 }
171 cursor.close();
172 }
Christopher Tatef8b59982009-09-29 12:40:25 -0700173 Log.i(TAG, "Restored " + nUnique + " of " + N + " bookmarks");
Christopher Tate9c0dd8c2009-07-10 17:51:48 -0700174 } catch (IOException ioe) {
175 Log.w(TAG, "Bad backup data; not restoring");
176 crc = -1;
177 }
Christopher Tatede6f1312009-07-07 13:11:41 -0700178 }
179
180 // Last, write the state we just restored from so we can discern
181 // changes whenever we get invoked for backup in the future
182 writeBackupState(tmpfile.length(), crc, newState);
183 }
184 } finally {
185 // Whatever happens, delete the temp file
186 tmpfile.delete();
187 }
188 }
189
Christopher Tate9c0dd8c2009-07-10 17:51:48 -0700190 class Bookmark {
191 public String url;
192 public int visits;
193 public long date;
194 public long created;
195 public String title;
196 }
Christopher Tatede6f1312009-07-07 13:11:41 -0700197 /*
198 * Utility functions
199 */
200
Christopher Tate9c0dd8c2009-07-10 17:51:48 -0700201 // Flatten the bookmarks table into the given file, calculating its CRC in the process
202 private long buildBookmarkFile(FileOutputStream outfstream) throws IOException {
203 CRC32 crc = new CRC32();
204 ByteArrayOutputStream bufstream = new ByteArrayOutputStream(512);
205 DataOutputStream bout = new DataOutputStream(bufstream);
206
207 Cursor cursor = getContentResolver().query(Browser.BOOKMARKS_URI,
208 new String[] { BookmarkColumns.URL, BookmarkColumns.VISITS,
209 BookmarkColumns.DATE, BookmarkColumns.CREATED,
210 BookmarkColumns.TITLE },
211 BookmarkColumns.BOOKMARK + " == 1 ", null, null);
212
213 // The first thing in the file is the row count...
214 int count = cursor.getCount();
215 if (DEBUG) Log.v(TAG, "Backing up " + count + " bookmarks");
216 bout.writeInt(count);
217 byte[] record = bufstream.toByteArray();
218 crc.update(record);
219 outfstream.write(record);
220
221 // ... followed by the data for each row
222 for (int i = 0; i < count; i++) {
223 cursor.moveToNext();
224
225 String url = cursor.getString(0);
226 int visits = cursor.getInt(1);
227 long date = cursor.getLong(2);
228 long created = cursor.getLong(3);
229 String title = cursor.getString(4);
230
231 // construct the flattened record in a byte array
232 bufstream.reset();
233 bout.writeUTF(url);
234 bout.writeInt(visits);
235 bout.writeLong(date);
236 bout.writeLong(created);
237 bout.writeUTF(title);
238
239 // Update the CRC and write the record to the temp file
240 record = bufstream.toByteArray();
241 crc.update(record);
242 outfstream.write(record);
243
244 if (DEBUG) Log.v(TAG, " wrote url " + url);
245 }
246
247 cursor.close();
248 return crc.getValue();
249 }
250
Christopher Tatede6f1312009-07-07 13:11:41 -0700251 // Write the file to backup as a single record under the given key
252 private void copyFileToBackup(String key, File file, BackupDataOutput data)
253 throws IOException {
254 final int CHUNK = 8192;
255 byte[] buf = new byte[CHUNK];
256
257 int toCopy = (int) file.length();
258 data.writeEntityHeader(key, toCopy);
259
260 FileInputStream in = new FileInputStream(file);
261 int nRead;
262 while (toCopy > 0) {
263 nRead = in.read(buf, 0, CHUNK);
264 data.writeEntityData(buf, nRead);
265 toCopy -= nRead;
266 }
267 in.close();
268 }
269
270 // Read the given file from backup to a file, calculating a CRC32 along the way
271 private long copyBackupToFile(BackupDataInput data, File file, int toRead)
272 throws IOException {
273 final int CHUNK = 8192;
274 byte[] buf = new byte[CHUNK];
275 CRC32 crc = new CRC32();
Christopher Tate9c0dd8c2009-07-10 17:51:48 -0700276 FileOutputStream out = new FileOutputStream(file);
Christopher Tatede6f1312009-07-07 13:11:41 -0700277
278 while (toRead > 0) {
279 int numRead = data.readEntityData(buf, 0, CHUNK);
280 crc.update(buf, 0, numRead);
Christopher Tate9c0dd8c2009-07-10 17:51:48 -0700281 out.write(buf, 0, numRead);
Christopher Tatede6f1312009-07-07 13:11:41 -0700282 toRead -= numRead;
283 }
284
Christopher Tate9c0dd8c2009-07-10 17:51:48 -0700285 out.close();
Christopher Tatede6f1312009-07-07 13:11:41 -0700286 return crc.getValue();
287 }
288
289 // Write the given metrics to the new state file
290 private void writeBackupState(long fileSize, long crc, ParcelFileDescriptor stateFile)
291 throws IOException {
292 DataOutputStream out = new DataOutputStream(
293 new FileOutputStream(stateFile.getFileDescriptor()));
294 out.writeLong(fileSize);
295 out.writeLong(crc);
Christopher Tate9c0dd8c2009-07-10 17:51:48 -0700296 out.writeInt(BACKUP_AGENT_VERSION);
Christopher Tatede6f1312009-07-07 13:11:41 -0700297 }
298}