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
17package com.android.browser;
18
19import android.app.backup.BackupAgent;
20import android.app.backup.BackupDataInput;
21import android.app.backup.BackupDataOutput;
22import android.content.ContentValues;
23import android.database.Cursor;
24import android.os.ParcelFileDescriptor;
25import android.provider.BrowserContract;
26import android.provider.BrowserContract.Bookmarks;
27import android.util.Log;
28
29import java.io.DataInputStream;
30import java.io.DataOutputStream;
31import java.io.EOFException;
32import java.io.File;
33import java.io.FileInputStream;
34import java.io.FileOutputStream;
35import java.io.IOException;
36import java.util.ArrayList;
37import 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 */
47public 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