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 com.android.browser; 18 19 import android.app.backup.BackupAgent; 20 import android.app.backup.BackupDataInput; 21 import android.app.backup.BackupDataOutput; 22 import android.content.ContentValues; 23 import android.database.Cursor; 24 import android.os.ParcelFileDescriptor; 25 import android.provider.BrowserContract; 26 import android.provider.BrowserContract.Bookmarks; 27 import android.util.Log; 28 29 import java.io.DataInputStream; 30 import java.io.DataOutputStream; 31 import java.io.EOFException; 32 import java.io.File; 33 import java.io.FileInputStream; 34 import java.io.FileOutputStream; 35 import java.io.IOException; 36 import java.util.ArrayList; 37 import java.util.zip.CRC32; 38 39 /** 40 * Settings backup agent for the Android browser. Currently the only thing 41 * stored is the set of bookmarks. It's okay if I/O exceptions are thrown 42 * out of the agent; the calling code handles it and the backup operation 43 * simply fails. 44 * 45 * @hide 46 */ 47 public class BrowserBackupAgent extends BackupAgent { 48 static final String TAG = "BrowserBackupAgent"; 49 static final boolean DEBUG = false; 50 51 static final String BOOKMARK_KEY = "_bookmarks_"; 52 /** this version num MUST be incremented if the flattened-file schema ever changes */ 53 static final int BACKUP_AGENT_VERSION = 0; 54 55 /** 56 * This simply preserves the existing state as we now prefer Chrome Sync 57 * to handle bookmark backup. 58 */ 59 @Override 60 public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data, 61 ParcelFileDescriptor newState) throws IOException { 62 long savedFileSize = -1; 63 long savedCrc = -1; 64 int savedVersion = -1; 65 66 // Extract the previous bookmark file size & CRC from the saved state 67 DataInputStream in = new DataInputStream( 68 new FileInputStream(oldState.getFileDescriptor())); 69 try { 70 savedFileSize = in.readLong(); 71 savedCrc = in.readLong(); 72 savedVersion = in.readInt(); 73 } catch (EOFException e) { 74 // It means we had no previous state; that's fine 75 return; 76 } finally { 77 if (in != null) { 78 in.close(); 79 } 80 } 81 // Write the existing state 82 writeBackupState(savedFileSize, savedCrc, newState); 83 } 84 85 /** 86 * Restore from backup -- reads in the flattened bookmark file as supplied from 87 * the backup service, parses that out, and rebuilds the bookmarks table in the 88 * browser database from it. 89 */ 90 @Override 91 public void onRestore(BackupDataInput data, int appVersionCode, 92 ParcelFileDescriptor newState) throws IOException { 93 long crc = -1; 94 File tmpfile = File.createTempFile("rst", null, getFilesDir()); 95 try { 96 while (data.readNextHeader()) { 97 if (BOOKMARK_KEY.equals(data.getKey())) { 98 // Read the flattened bookmark data into a temp file 99 crc = copyBackupToFile(data, tmpfile, data.getDataSize()); 100 101 FileInputStream infstream = new FileInputStream(tmpfile); 102 DataInputStream in = new DataInputStream(infstream); 103 104 try { 105 int count = in.readInt(); 106 ArrayList<Bookmark> bookmarks = new ArrayList<Bookmark>(count); 107 108 // Read all the bookmarks, then process later -- if we can't read 109 // all the data successfully, we don't touch the bookmarks table 110 for (int i = 0; i < count; i++) { 111 Bookmark mark = new Bookmark(); 112 mark.url = in.readUTF(); 113 mark.visits = in.readInt(); 114 mark.date = in.readLong(); 115 mark.created = in.readLong(); 116 mark.title = in.readUTF(); 117 bookmarks.add(mark); 118 } 119 120 // Okay, we have all the bookmarks -- now see if we need to add 121 // them to the browser's database 122 int N = bookmarks.size(); 123 int nUnique = 0; 124 if (DEBUG) Log.v(TAG, "Restoring " + N + " bookmarks"); 125 String[] urlCol = new String[] { Bookmarks.URL }; 126 for (int i = 0; i < N; i++) { 127 Bookmark mark = bookmarks.get(i); 128 129 // Does this URL exist in the bookmark table? 130 Cursor cursor = getContentResolver().query( 131 Bookmarks.CONTENT_URI, urlCol, 132 Bookmarks.URL + " == ?", 133 new String[] { mark.url }, null); 134 // if not, insert it 135 if (cursor.getCount() <= 0) { 136 if (DEBUG) Log.v(TAG, "Did not see url: " + mark.url); 137 addBookmark(mark); 138 nUnique++; 139 } else { 140 if (DEBUG) Log.v(TAG, "Skipping extant url: " + mark.url); 141 } 142 cursor.close(); 143 } 144 Log.i(TAG, "Restored " + nUnique + " of " + N + " bookmarks"); 145 } catch (IOException ioe) { 146 Log.w(TAG, "Bad backup data; not restoring"); 147 crc = -1; 148 } finally { 149 if (in != null) { 150 in.close(); 151 } 152 } 153 } 154 155 // Last, write the state we just restored from so we can discern 156 // changes whenever we get invoked for backup in the future 157 writeBackupState(tmpfile.length(), crc, newState); 158 } 159 } finally { 160 // Whatever happens, delete the temp file 161 tmpfile.delete(); 162 } 163 } 164 165 void addBookmark(Bookmark mark) { 166 ContentValues values = new ContentValues(); 167 values.put(Bookmarks.TITLE, mark.title); 168 values.put(Bookmarks.URL, mark.url); 169 values.put(Bookmarks.IS_FOLDER, 0); 170 values.put(Bookmarks.DATE_CREATED, mark.created); 171 values.put(Bookmarks.DATE_MODIFIED, mark.date); 172 getContentResolver().insert(Bookmarks.CONTENT_URI, values); 173 } 174 175 static class Bookmark { 176 public String url; 177 public int visits; 178 public long date; 179 public long created; 180 public String title; 181 } 182 /* 183 * Utility functions 184 */ 185 186 // Read the given file from backup to a file, calculating a CRC32 along the way 187 private long copyBackupToFile(BackupDataInput data, File file, int toRead) 188 throws IOException { 189 final int CHUNK = 8192; 190 byte[] buf = new byte[CHUNK]; 191 CRC32 crc = new CRC32(); 192 FileOutputStream out = new FileOutputStream(file); 193 194 try { 195 while (toRead > 0) { 196 int numRead = data.readEntityData(buf, 0, CHUNK); 197 crc.update(buf, 0, numRead); 198 out.write(buf, 0, numRead); 199 toRead -= numRead; 200 } 201 } finally { 202 if (out != null) { 203 out.close(); 204 } 205 } 206 return crc.getValue(); 207 } 208 209 // Write the given metrics to the new state file 210 private void writeBackupState(long fileSize, long crc, ParcelFileDescriptor stateFile) 211 throws IOException { 212 DataOutputStream out = new DataOutputStream( 213 new FileOutputStream(stateFile.getFileDescriptor())); 214 try { 215 out.writeLong(fileSize); 216 out.writeLong(crc); 217 out.writeInt(BACKUP_AGENT_VERSION); 218 } finally { 219 if (out != null) { 220 out.close(); 221 } 222 } 223 } 224 } 225