BrowserProvider2.java revision 6e073f306f7da0f985646e460fd47f47187ab144
1/*
2 * Copyright (C) 2010 he 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.provider;
18
19import com.android.browser.R;
20import com.android.internal.content.SyncStateContentProviderHelper;
21
22import android.content.ContentResolver;
23import android.content.ContentUris;
24import android.content.ContentValues;
25import android.content.Context;
26import android.content.UriMatcher;
27import android.database.Cursor;
28import android.database.DatabaseUtils;
29import android.database.sqlite.SQLiteDatabase;
30import android.database.sqlite.SQLiteOpenHelper;
31import android.database.sqlite.SQLiteQueryBuilder;
32import android.net.Uri;
33import android.provider.BrowserContract;
34import android.provider.BrowserContract.Accounts;
35import android.provider.BrowserContract.Bookmarks;
36import android.provider.BrowserContract.ChromeSyncColumns;
37import android.provider.BrowserContract.Combined;
38import android.provider.BrowserContract.History;
39import android.provider.BrowserContract.Images;
40import android.provider.BrowserContract.Searches;
41import android.provider.BrowserContract.SyncState;
42import android.provider.ContactsContract.RawContacts;
43import android.provider.SyncStateContract;
44import android.text.TextUtils;
45
46import java.util.HashMap;
47
48public class BrowserProvider2 extends SQLiteContentProvider {
49
50    static final String LEGACY_AUTHORITY = "browser";
51    static final Uri LEGACY_AUTHORITY_URI = new Uri.Builder().authority(LEGACY_AUTHORITY).build();
52
53    static final String TABLE_BOOKMARKS = "bookmarks";
54    static final String TABLE_HISTORY = "history";
55    static final String TABLE_IMAGES = "images";
56    static final String TABLE_SEARCHES = "searches";
57    static final String TABLE_SYNC_STATE = "syncstate";
58    static final String VIEW_COMBINED = "combined";
59
60    static final String TABLE_BOOKMARKS_JOIN_IMAGES = "bookmarks LEFT OUTER JOIN images " +
61            "ON bookmarks.url = images." + Images.URL;
62    static final String TABLE_HISTORY_JOIN_IMAGES = "history LEFT OUTER JOIN images " +
63            "ON history.url = images." + Images.URL;
64
65    static final String DEFAULT_SORT_HISTORY = History.DATE_LAST_VISITED + " DESC";
66
67    static final int BOOKMARKS = 1000;
68    static final int BOOKMARKS_ID = 1001;
69    static final int BOOKMARKS_FOLDER = 1002;
70    static final int BOOKMARKS_FOLDER_ID = 1003;
71
72    static final int HISTORY = 2000;
73    static final int HISTORY_ID = 2001;
74
75    static final int SEARCHES = 3000;
76    static final int SEARCHES_ID = 3001;
77
78    static final int SYNCSTATE = 4000;
79    static final int SYNCSTATE_ID = 4001;
80
81    static final int IMAGES = 5000;
82
83    static final int COMBINED = 6000;
84    static final int COMBINED_ID = 6001;
85
86    static final int ACCOUNTS = 7000;
87
88    static final long FIXED_ID_CHROME_ROOT = 1;
89    static final long FIXED_ID_BOOKMARKS = 2;
90    static final long FIXED_ID_BOOKMARKS_BAR = 3;
91    static final long FIXED_ID_OTHER_BOOKMARKS = 4;
92
93    static final String DEFAULT_BOOKMARKS_SORT_ORDER = "position ASC, _id ASC";
94
95    static final UriMatcher URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
96
97    static final HashMap<String, String> ACCOUNTS_PROJECTION_MAP = new HashMap<String, String>();
98    static final HashMap<String, String> BOOKMARKS_PROJECTION_MAP = new HashMap<String, String>();
99    static final HashMap<String, String> OTHER_BOOKMARKS_PROJECTION_MAP =
100            new HashMap<String, String>();
101    static final HashMap<String, String> HISTORY_PROJECTION_MAP = new HashMap<String, String>();
102    static final HashMap<String, String> SYNC_STATE_PROJECTION_MAP = new HashMap<String, String>();
103    static final HashMap<String, String> IMAGES_PROJECTION_MAP = new HashMap<String, String>();
104    static final HashMap<String, String> COMBINED_PROJECTION_MAP = new HashMap<String, String>();
105
106    static {
107        final UriMatcher matcher = URI_MATCHER;
108        final String authority = BrowserContract.AUTHORITY;
109        matcher.addURI(authority, "accounts", ACCOUNTS);
110        matcher.addURI(authority, "bookmarks", BOOKMARKS);
111        matcher.addURI(authority, "bookmarks/#", BOOKMARKS_ID);
112        matcher.addURI(authority, "bookmarks/folder", BOOKMARKS_FOLDER);
113        matcher.addURI(authority, "bookmarks/folder/#", BOOKMARKS_FOLDER_ID);
114        matcher.addURI(authority, "history", HISTORY);
115        matcher.addURI(authority, "history/#", HISTORY_ID);
116        matcher.addURI(authority, "searches", SEARCHES);
117        matcher.addURI(authority, "searches/#", SEARCHES_ID);
118        matcher.addURI(authority, "syncstate", SYNCSTATE);
119        matcher.addURI(authority, "syncstate/#", SYNCSTATE_ID);
120        matcher.addURI(authority, "images", IMAGES);
121        matcher.addURI(authority, "combined", COMBINED);
122        matcher.addURI(authority, "combined/#", COMBINED_ID);
123
124        // Projection maps
125        HashMap<String, String> map;
126
127        // Accounts
128        map = ACCOUNTS_PROJECTION_MAP;
129        map.put(Accounts.ACCOUNT_TYPE, Accounts.ACCOUNT_TYPE);
130        map.put(Accounts.ACCOUNT_NAME, Accounts.ACCOUNT_NAME);
131
132        // Bookmarks
133        map = BOOKMARKS_PROJECTION_MAP;
134        map.put(Bookmarks._ID, qualifyColumn(TABLE_BOOKMARKS, Bookmarks._ID));
135        map.put(Bookmarks.TITLE, Bookmarks.TITLE);
136        map.put(Bookmarks.URL, Bookmarks.URL);
137        map.put(Bookmarks.FAVICON, Bookmarks.FAVICON);
138        map.put(Bookmarks.THUMBNAIL, Bookmarks.THUMBNAIL);
139        map.put(Bookmarks.TOUCH_ICON, Bookmarks.TOUCH_ICON);
140        map.put(Bookmarks.IS_FOLDER, Bookmarks.IS_FOLDER);
141        map.put(Bookmarks.PARENT, Bookmarks.PARENT);
142        map.put(Bookmarks.POSITION, Bookmarks.POSITION);
143        map.put(Bookmarks.INSERT_AFTER, Bookmarks.INSERT_AFTER);
144        map.put(Bookmarks.IS_DELETED, Bookmarks.IS_DELETED);
145        map.put(Bookmarks.ACCOUNT_NAME, Bookmarks.ACCOUNT_NAME);
146        map.put(Bookmarks.ACCOUNT_TYPE, Bookmarks.ACCOUNT_TYPE);
147        map.put(Bookmarks.SOURCE_ID, Bookmarks.SOURCE_ID);
148        map.put(Bookmarks.VERSION, Bookmarks.VERSION);
149        map.put(Bookmarks.DATE_CREATED, Bookmarks.DATE_CREATED);
150        map.put(Bookmarks.DATE_MODIFIED, Bookmarks.DATE_MODIFIED);
151        map.put(Bookmarks.DIRTY, Bookmarks.DIRTY);
152        map.put(Bookmarks.SYNC1, Bookmarks.SYNC1);
153        map.put(Bookmarks.SYNC2, Bookmarks.SYNC2);
154        map.put(Bookmarks.SYNC3, Bookmarks.SYNC3);
155        map.put(Bookmarks.SYNC4, Bookmarks.SYNC4);
156        map.put(Bookmarks.SYNC5, Bookmarks.SYNC5);
157        map.put(Bookmarks.PARENT_SOURCE_ID, "(SELECT " + Bookmarks.SOURCE_ID +
158                " FROM " + TABLE_BOOKMARKS + " A WHERE " +
159                "A." + Bookmarks._ID + "=" + TABLE_BOOKMARKS + "." + Bookmarks.PARENT +
160                ") AS " + Bookmarks.PARENT_SOURCE_ID);
161        map.put(Bookmarks.INSERT_AFTER_SOURCE_ID, "(SELECT " + Bookmarks.SOURCE_ID +
162                " FROM " + TABLE_BOOKMARKS + " A WHERE " +
163                "A." + Bookmarks._ID + "=" + TABLE_BOOKMARKS + "." + Bookmarks.INSERT_AFTER +
164                ") AS " + Bookmarks.INSERT_AFTER_SOURCE_ID);
165
166        // Other bookmarks
167        OTHER_BOOKMARKS_PROJECTION_MAP.putAll(BOOKMARKS_PROJECTION_MAP);
168        OTHER_BOOKMARKS_PROJECTION_MAP.put(Bookmarks.POSITION,
169                Long.toString(Long.MAX_VALUE) + " AS " + Bookmarks.POSITION);
170
171        // History
172        map = HISTORY_PROJECTION_MAP;
173        map.put(History._ID, qualifyColumn(TABLE_HISTORY, History._ID));
174        map.put(History.TITLE, History.TITLE);
175        map.put(History.URL, History.URL);
176        map.put(History.FAVICON, History.FAVICON);
177        map.put(History.THUMBNAIL, History.THUMBNAIL);
178        map.put(History.TOUCH_ICON, History.TOUCH_ICON);
179        map.put(History.DATE_CREATED, History.DATE_CREATED);
180        map.put(History.DATE_LAST_VISITED, History.DATE_LAST_VISITED);
181        map.put(History.VISITS, History.VISITS);
182        map.put(History.USER_ENTERED, History.USER_ENTERED);
183
184        // Sync state
185        map = SYNC_STATE_PROJECTION_MAP;
186        map.put(SyncState._ID, SyncState._ID);
187        map.put(SyncState.ACCOUNT_NAME, SyncState.ACCOUNT_NAME);
188        map.put(SyncState.ACCOUNT_TYPE, SyncState.ACCOUNT_TYPE);
189        map.put(SyncState.DATA, SyncState.DATA);
190
191        // Images
192        map = IMAGES_PROJECTION_MAP;
193        map.put(Images.URL, Images.URL);
194        map.put(Images.FAVICON, Images.FAVICON);
195        map.put(Images.THUMBNAIL, Images.THUMBNAIL);
196        map.put(Images.TOUCH_ICON, Images.TOUCH_ICON);
197
198        // Combined history half
199        map = COMBINED_PROJECTION_MAP;
200        map.put(Combined._ID, Combined._ID);
201        map.put(Combined.TITLE, Combined.TITLE);
202        map.put(Combined.URL, Combined.URL);
203        map.put(Combined.DATE_CREATED, Combined.DATE_CREATED);
204        map.put(Combined.DATE_LAST_VISITED, Combined.DATE_LAST_VISITED);
205        map.put(Combined.IS_BOOKMARK, Combined.IS_BOOKMARK);
206        map.put(Combined.VISITS, Combined.VISITS);
207        map.put(Combined.FAVICON, Combined.FAVICON);
208        map.put(Combined.THUMBNAIL, Combined.THUMBNAIL);
209        map.put(Combined.TOUCH_ICON, Combined.TOUCH_ICON);
210        map.put(Combined.USER_ENTERED, Combined.USER_ENTERED);
211    }
212
213    static final String bookmarkOrHistoryColumn(String column) {
214        return "CASE WHEN bookmarks." + column + " IS NOT NULL THEN " +
215                "bookmarks." + column + " ELSE history." + column + " END AS " + column;
216    }
217
218    static final String qualifyColumn(String table, String column) {
219        return table + "." + column + " AS " + column;
220    }
221
222    DatabaseHelper mOpenHelper;
223    SyncStateContentProviderHelper mSyncHelper = new SyncStateContentProviderHelper();
224
225    final class DatabaseHelper extends SQLiteOpenHelper {
226        static final String DATABASE_NAME = "browser2.db";
227        static final int DATABASE_VERSION = 22;
228        public DatabaseHelper(Context context) {
229            super(context, DATABASE_NAME, null, DATABASE_VERSION);
230        }
231
232        @Override
233        public void onCreate(SQLiteDatabase db) {
234            db.execSQL("CREATE TABLE " + TABLE_BOOKMARKS + "(" +
235                    Bookmarks._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
236                    Bookmarks.TITLE + " TEXT," +
237                    Bookmarks.URL + " TEXT," +
238                    Bookmarks.IS_FOLDER + " INTEGER NOT NULL DEFAULT 0," +
239                    Bookmarks.PARENT + " INTEGER," +
240                    Bookmarks.POSITION + " INTEGER NOT NULL," +
241                    Bookmarks.INSERT_AFTER + " INTEGER," +
242                    Bookmarks.IS_DELETED + " INTEGER NOT NULL DEFAULT 0," +
243                    Bookmarks.ACCOUNT_NAME + " TEXT," +
244                    Bookmarks.ACCOUNT_TYPE + " TEXT," +
245                    Bookmarks.SOURCE_ID + " TEXT," +
246                    Bookmarks.VERSION + " INTEGER NOT NULL DEFAULT 1," +
247                    Bookmarks.DATE_CREATED + " INTEGER," +
248                    Bookmarks.DATE_MODIFIED + " INTEGER," +
249                    Bookmarks.DIRTY + " INTEGER NOT NULL DEFAULT 0," +
250                    Bookmarks.SYNC1 + " TEXT," +
251                    Bookmarks.SYNC2 + " TEXT," +
252                    Bookmarks.SYNC3 + " TEXT," +
253                    Bookmarks.SYNC4 + " TEXT," +
254                    Bookmarks.SYNC5 + " TEXT" +
255                    ");");
256
257            // TODO indices
258
259            createDefaultBookmarks(db);
260
261            db.execSQL("CREATE TABLE " + TABLE_HISTORY + "(" +
262                    History._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
263                    History.TITLE + " TEXT," +
264                    History.URL + " TEXT NOT NULL," +
265                    History.DATE_CREATED + " INTEGER," +
266                    History.DATE_LAST_VISITED + " INTEGER," +
267                    History.VISITS + " INTEGER NOT NULL DEFAULT 0," +
268                    History.USER_ENTERED + " INTEGER" +
269                    ");");
270
271            db.execSQL("CREATE TABLE " + TABLE_IMAGES + " (" +
272                    Images.URL + " TEXT UNIQUE NOT NULL," +
273                    Images.FAVICON + " BLOB," +
274                    Images.THUMBNAIL + " BLOB," +
275                    Images.TOUCH_ICON + " BLOB" +
276                    ");");
277            db.execSQL("CREATE INDEX imagesUrlIndex ON " + TABLE_IMAGES +
278                    "(" + Images.URL + ")");
279
280            db.execSQL("CREATE TABLE " + TABLE_SEARCHES + " (" +
281                    Searches._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
282                    Searches.SEARCH + " TEXT," +
283                    Searches.DATE + " LONG" +
284                    ");");
285
286            db.execSQL("CREATE VIEW " + VIEW_COMBINED + " AS " +
287                "SELECT " +
288                    bookmarkOrHistoryColumn(Combined._ID) + ", " +
289                    bookmarkOrHistoryColumn(Combined.TITLE) + ", " +
290                    qualifyColumn(TABLE_HISTORY, Combined.URL) + ", " +
291                    qualifyColumn(TABLE_HISTORY, Combined.DATE_CREATED) + ", " +
292                    Combined.DATE_LAST_VISITED + ", " +
293                    "CASE WHEN bookmarks._id IS NOT NULL THEN 1 ELSE 0 END AS " + Combined.IS_BOOKMARK + ", " +
294                    Combined.VISITS + ", " +
295                    Combined.FAVICON + ", " +
296                    Combined.THUMBNAIL + ", " +
297                    Combined.TOUCH_ICON + ", " +
298                    "NULL AS " + Combined.USER_ENTERED + " "+
299                "FROM history LEFT OUTER JOIN bookmarks ON history.url = bookmarks.url LEFT OUTER JOIN images ON history.url = images.url_key " +
300
301                "UNION ALL " +
302
303                "SELECT " +
304                    Combined._ID + ", " +
305                    Combined.TITLE + ", " +
306                    Combined.URL + ", " +
307                    Combined.DATE_CREATED + ", " +
308                    "NULL AS " + Combined.DATE_LAST_VISITED + ", "+
309                    "1 AS " + Combined.IS_BOOKMARK + ", " +
310                    "0 AS " + Combined.VISITS + ", "+
311                    Combined.FAVICON + ", " +
312                    Combined.THUMBNAIL + ", " +
313                    Combined.TOUCH_ICON + ", " +
314                    "NULL AS " + Combined.USER_ENTERED + " "+
315                "FROM bookmarks LEFT OUTER JOIN images ON bookmarks.url = images.url_key WHERE url NOT IN (SELECT url FROM history)");
316
317            mSyncHelper.createDatabase(db);
318        }
319
320        @Override
321        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
322            // TODO write upgrade logic
323            db.execSQL("DROP TABLE IF EXISTS " + TABLE_BOOKMARKS);
324            db.execSQL("DROP TABLE IF EXISTS " + TABLE_HISTORY);
325            db.execSQL("DROP TABLE IF EXISTS " + TABLE_SEARCHES);
326            db.execSQL("DROP TABLE IF EXISTS " + TABLE_IMAGES);
327            db.execSQL("DROP VIEW IF EXISTS " + VIEW_COMBINED);
328            onCreate(db);
329        }
330
331        @Override
332        public void onOpen(SQLiteDatabase db) {
333            mSyncHelper.onDatabaseOpened(db);
334        }
335
336
337        private void createDefaultBookmarks(SQLiteDatabase db) {
338            ContentValues values = new ContentValues();
339            // TODO figure out how to deal with localization for the defaults
340
341            // Chrome sync root folder
342            values.put(Bookmarks._ID, FIXED_ID_CHROME_ROOT);
343            values.put(ChromeSyncColumns.SERVER_UNIQUE, ChromeSyncColumns.FOLDER_NAME_ROOT);
344            values.put(Bookmarks.TITLE, "Google Chrome");
345            values.put(Bookmarks.PARENT, 0);
346            values.put(Bookmarks.POSITION, 0);
347            values.put(Bookmarks.IS_FOLDER, true);
348            values.put(Bookmarks.DIRTY, true);
349            db.insertOrThrow(TABLE_BOOKMARKS, null, values);
350
351            // Bookmarks folder
352            values.put(Bookmarks._ID, FIXED_ID_BOOKMARKS);
353            values.put(ChromeSyncColumns.SERVER_UNIQUE, ChromeSyncColumns.FOLDER_NAME_BOOKMARKS);
354            values.put(Bookmarks.TITLE, "Bookmarks");
355            values.put(Bookmarks.PARENT, FIXED_ID_CHROME_ROOT);
356            values.put(Bookmarks.POSITION, 0);
357            values.put(Bookmarks.IS_FOLDER, true);
358            values.put(Bookmarks.DIRTY, true);
359            db.insertOrThrow(TABLE_BOOKMARKS, null, values);
360
361            // Bookmarks Bar folder
362            values.clear();
363            values.put(Bookmarks._ID, FIXED_ID_BOOKMARKS_BAR);
364            values.put(ChromeSyncColumns.SERVER_UNIQUE,
365                    ChromeSyncColumns.FOLDER_NAME_BOOKMARKS_BAR);
366            values.put(Bookmarks.TITLE, "Bookmarks Bar");
367            values.put(Bookmarks.PARENT, FIXED_ID_BOOKMARKS);
368            values.put(Bookmarks.POSITION, 0);
369            values.put(Bookmarks.IS_FOLDER, true);
370            values.put(Bookmarks.DIRTY, true);
371            db.insertOrThrow(TABLE_BOOKMARKS, null, values);
372
373            // Other Bookmarks folder
374            values.clear();
375            values.put(Bookmarks._ID, FIXED_ID_OTHER_BOOKMARKS);
376            values.put(ChromeSyncColumns.SERVER_UNIQUE,
377                    ChromeSyncColumns.FOLDER_NAME_OTHER_BOOKMARKS);
378            values.put(Bookmarks.TITLE, "Other Bookmarks");
379            values.put(Bookmarks.PARENT, FIXED_ID_BOOKMARKS);
380            values.put(Bookmarks.POSITION, 1000);
381            values.put(Bookmarks.IS_FOLDER, true);
382            values.put(Bookmarks.DIRTY, true);
383            db.insertOrThrow(TABLE_BOOKMARKS, null, values);
384
385            addDefaultBookmarks(db, FIXED_ID_BOOKMARKS_BAR);
386
387            // TODO remove this testing code
388            db.execSQL("INSERT INTO bookmarks (" +
389                    Bookmarks.TITLE + ", " +
390                    Bookmarks.URL + ", " +
391                    Bookmarks.IS_FOLDER + "," +
392                    Bookmarks.PARENT + "," +
393                    Bookmarks.POSITION +
394                ") VALUES (" +
395                    "'Google Reader', " +
396                    "'http://reader.google.com', " +
397                    "0," +
398                    Long.toString(FIXED_ID_OTHER_BOOKMARKS) + "," +
399                    0 +
400                    ");");
401        }
402
403        private void addDefaultBookmarks(SQLiteDatabase db, long parentId) {
404            final CharSequence[] bookmarks = getContext().getResources().getTextArray(
405                    R.array.bookmarks);
406            int size = bookmarks.length;
407            try {
408                String parent = Long.toString(parentId);
409                String now = Long.toString(System.currentTimeMillis());
410                for (int i = 0; i < size; i = i + 2) {
411                    CharSequence bookmarkDestination = replaceSystemPropertyInString(getContext(),
412                            bookmarks[i + 1]);
413                    db.execSQL("INSERT INTO bookmarks (" +
414                            Bookmarks.TITLE + ", " +
415                            Bookmarks.URL + ", " +
416                            Bookmarks.IS_FOLDER + "," +
417                            Bookmarks.PARENT + "," +
418                            Bookmarks.POSITION + "," +
419                            Bookmarks.DATE_CREATED +
420                        ") VALUES (" +
421                            "'" + bookmarks[i] + "', " +
422                            "'" + bookmarkDestination + "', " +
423                            "0," +
424                            parent + "," +
425                            Integer.toString(i) + "," +
426                            now +
427                            ");");
428                }
429            } catch (ArrayIndexOutOfBoundsException e) {
430            }
431        }
432
433        // XXX: This is a major hack to remove our dependency on gsf constants and
434        // its content provider. http://b/issue?id=2425179
435        private String getClientId(ContentResolver cr) {
436            String ret = "android-google";
437            Cursor c = null;
438            try {
439                c = cr.query(Uri.parse("content://com.google.settings/partner"),
440                        new String[] { "value" }, "name='client_id'", null, null);
441                if (c != null && c.moveToNext()) {
442                    ret = c.getString(0);
443                }
444            } catch (RuntimeException ex) {
445                // fall through to return the default
446            } finally {
447                if (c != null) {
448                    c.close();
449                }
450            }
451            return ret;
452        }
453
454        private CharSequence replaceSystemPropertyInString(Context context, CharSequence srcString) {
455            StringBuffer sb = new StringBuffer();
456            int lastCharLoc = 0;
457
458            final String client_id = getClientId(context.getContentResolver());
459
460            for (int i = 0; i < srcString.length(); ++i) {
461                char c = srcString.charAt(i);
462                if (c == '{') {
463                    sb.append(srcString.subSequence(lastCharLoc, i));
464                    lastCharLoc = i;
465              inner:
466                    for (int j = i; j < srcString.length(); ++j) {
467                        char k = srcString.charAt(j);
468                        if (k == '}') {
469                            String propertyKeyValue = srcString.subSequence(i + 1, j).toString();
470                            if (propertyKeyValue.equals("CLIENT_ID")) {
471                                sb.append(client_id);
472                            } else {
473                                sb.append("unknown");
474                            }
475                            lastCharLoc = j + 1;
476                            i = j;
477                            break inner;
478                        }
479                    }
480                }
481            }
482            if (srcString.length() - lastCharLoc > 0) {
483                // Put on the tail, if there is one
484                sb.append(srcString.subSequence(lastCharLoc, srcString.length()));
485            }
486            return sb;
487        }
488    }
489
490    @Override
491    public SQLiteOpenHelper getDatabaseHelper(Context context) {
492        synchronized (this) {
493            if (mOpenHelper == null) {
494                mOpenHelper = new DatabaseHelper(context);
495            }
496            return mOpenHelper;
497        }
498    }
499
500    @Override
501    public boolean isCallerSyncAdapter(Uri uri) {
502        return uri.getBooleanQueryParameter(BrowserContract.CALLER_IS_SYNCADAPTER, false);
503    }
504
505    @Override
506    public void notifyChange(boolean callerIsSyncAdapter) {
507        ContentResolver resolver = getContext().getContentResolver();
508        resolver.notifyChange(BrowserContract.AUTHORITY_URI, null, !callerIsSyncAdapter);
509        resolver.notifyChange(LEGACY_AUTHORITY_URI, null, !callerIsSyncAdapter);
510    }
511
512    @Override
513    public String getType(Uri uri) {
514        final int match = URI_MATCHER.match(uri);
515        switch (match) {
516            case BOOKMARKS:
517                return Bookmarks.CONTENT_TYPE;
518            case BOOKMARKS_ID:
519                return Bookmarks.CONTENT_ITEM_TYPE;
520            case HISTORY:
521                return History.CONTENT_TYPE;
522            case HISTORY_ID:
523                return History.CONTENT_ITEM_TYPE;
524            case SEARCHES:
525                return Searches.CONTENT_TYPE;
526            case SEARCHES_ID:
527                return Searches.CONTENT_ITEM_TYPE;
528//            case SUGGEST:
529//                return SearchManager.SUGGEST_MIME_TYPE;
530        }
531        return null;
532    }
533
534    @Override
535    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
536            String sortOrder) {
537        SQLiteDatabase db = mOpenHelper.getReadableDatabase();
538        final int match = URI_MATCHER.match(uri);
539        SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
540        String limit = uri.getQueryParameter(BrowserContract.PARAM_LIMIT);
541        switch (match) {
542            case ACCOUNTS: {
543                qb.setTables(TABLE_BOOKMARKS);
544                qb.setProjectionMap(ACCOUNTS_PROJECTION_MAP);
545                qb.setDistinct(true);
546                break;
547            }
548
549            case BOOKMARKS_FOLDER_ID:
550            case BOOKMARKS_ID:
551            case BOOKMARKS: {
552                // Only show deleted bookmarks if requested to do so
553                if (!uri.getBooleanQueryParameter(Bookmarks.QUERY_PARAMETER_SHOW_DELETED, false)) {
554                    selection = DatabaseUtils.concatenateWhere(
555                            Bookmarks.IS_DELETED + "=0", selection);
556                }
557
558                if (match == BOOKMARKS_ID) {
559                    // Tack on the ID of the specific bookmark requested
560                    selection = DatabaseUtils.concatenateWhere(selection,
561                            TABLE_BOOKMARKS + "." + Bookmarks._ID + "=?");
562                    selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs,
563                            new String[] { Long.toString(ContentUris.parseId(uri)) });
564                } else if (match == BOOKMARKS_FOLDER_ID) {
565                    // Tack on the ID of the specific folder requested
566                    selection = DatabaseUtils.concatenateWhere(selection,
567                            TABLE_BOOKMARKS + "." + Bookmarks.PARENT + "=?");
568                    selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs,
569                            new String[] { Long.toString(ContentUris.parseId(uri)) });
570                }
571
572                // Look for account info
573                String accountType = uri.getQueryParameter(Bookmarks.PARAM_ACCOUNT_TYPE);
574                String accountName = uri.getQueryParameter(Bookmarks.PARAM_ACCOUNT_NAME);
575                if (!TextUtils.isEmpty(accountType) && !TextUtils.isEmpty(accountName)) {
576                    selection = DatabaseUtils.concatenateWhere(selection,
577                            Bookmarks.ACCOUNT_TYPE + "=? AND " + Bookmarks.ACCOUNT_NAME + "=? ");
578                    selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs,
579                            new String[] { accountType, accountName });
580                }
581
582                // Set a default sort order if one isn't specified
583                if (TextUtils.isEmpty(sortOrder)) {
584                    sortOrder = DEFAULT_BOOKMARKS_SORT_ORDER;
585                }
586
587                qb.setProjectionMap(BOOKMARKS_PROJECTION_MAP);
588                qb.setTables(TABLE_BOOKMARKS_JOIN_IMAGES);
589                break;
590            }
591
592            case BOOKMARKS_FOLDER: {
593                // Don't allow selections to be applied to the default folder
594                if (!TextUtils.isEmpty(selection) || selectionArgs != null) {
595                    throw new UnsupportedOperationException(
596                            "selections aren't supported on this URI");
597                }
598
599                // Look for an account
600                boolean useAccount = false;
601                String accountType = uri.getQueryParameter(Bookmarks.PARAM_ACCOUNT_TYPE);
602                String accountName = uri.getQueryParameter(Bookmarks.PARAM_ACCOUNT_NAME);
603                if (!TextUtils.isEmpty(accountType) && !TextUtils.isEmpty(accountName)) {
604                    useAccount = true;
605                }
606
607                qb.setTables(TABLE_BOOKMARKS_JOIN_IMAGES);
608                String bookmarksBarQuery;
609                String otherBookmarksQuery;
610                String[] args;
611                if (!useAccount) {
612                    qb.setProjectionMap(BOOKMARKS_PROJECTION_MAP);
613                    bookmarksBarQuery = qb.buildQuery(projection,
614                            Bookmarks.PARENT + "=? AND " + Bookmarks.IS_DELETED + "=0",
615                            null, null, null, null, null);
616
617                    qb.setProjectionMap(OTHER_BOOKMARKS_PROJECTION_MAP);
618                    otherBookmarksQuery = qb.buildQuery(projection,
619                            Bookmarks._ID + "=?",
620                            null, null, null, null, null);
621
622                    args = new String[] { Long.toString(FIXED_ID_BOOKMARKS_BAR),
623                            Long.toString(FIXED_ID_OTHER_BOOKMARKS) };
624                } else {
625                    qb.setProjectionMap(BOOKMARKS_PROJECTION_MAP);
626                    bookmarksBarQuery = qb.buildQuery(projection,
627                            Bookmarks.ACCOUNT_TYPE + "=? AND " + Bookmarks.ACCOUNT_NAME + "=? " +
628                                    "AND parent = " +
629                                        "(SELECT _id FROM " + TABLE_BOOKMARKS + " WHERE " +
630                                        ChromeSyncColumns.SERVER_UNIQUE + "=" +
631                                        "'" + ChromeSyncColumns.FOLDER_NAME_BOOKMARKS_BAR + "' " +
632                                        "AND account_type = ? AND account_name = ?) " +
633                                    "AND " + Bookmarks.IS_DELETED + "=0",
634                            null, null, null, null, null);
635
636                    qb.setProjectionMap(OTHER_BOOKMARKS_PROJECTION_MAP);
637                    otherBookmarksQuery = qb.buildQuery(projection,
638                            Bookmarks.ACCOUNT_TYPE + "=? AND " + Bookmarks.ACCOUNT_NAME + "=?" +
639                                    " AND " + ChromeSyncColumns.SERVER_UNIQUE + "=?",
640                            null, null, null, null, null);
641
642                    args = new String[] {
643                            accountType, accountName, accountType, accountName,
644                            accountType, accountName, ChromeSyncColumns.FOLDER_NAME_OTHER_BOOKMARKS,
645                            };
646                }
647
648                String query = qb.buildUnionQuery(
649                        new String[] { bookmarksBarQuery, otherBookmarksQuery },
650                        DEFAULT_BOOKMARKS_SORT_ORDER, limit);
651                return db.rawQuery(query, args);
652            }
653
654            case HISTORY_ID: {
655                selection = DatabaseUtils.concatenateWhere(selection, TABLE_HISTORY + "._id=?");
656                selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs,
657                        new String[] { Long.toString(ContentUris.parseId(uri)) });
658                // fall through
659            }
660            case HISTORY: {
661                if (sortOrder == null) {
662                    sortOrder = DEFAULT_SORT_HISTORY;
663                }
664                qb.setProjectionMap(HISTORY_PROJECTION_MAP);
665                qb.setTables(TABLE_HISTORY_JOIN_IMAGES);
666                break;
667            }
668
669            case SYNCSTATE: {
670                return mSyncHelper.query(db, projection, selection, selectionArgs, sortOrder);
671            }
672
673            case SYNCSTATE_ID: {
674                selection = appendAccountToSelection(uri, selection);
675                String selectionWithId =
676                        (SyncStateContract.Columns._ID + "=" + ContentUris.parseId(uri) + " ")
677                        + (selection == null ? "" : " AND (" + selection + ")");
678                return mSyncHelper.query(db, projection, selectionWithId, selectionArgs, sortOrder);
679            }
680
681            case IMAGES: {
682                qb.setTables(TABLE_IMAGES);
683                qb.setProjectionMap(IMAGES_PROJECTION_MAP);
684                break;
685            }
686
687            case COMBINED_ID: {
688                selection = DatabaseUtils.concatenateWhere(selection, VIEW_COMBINED + "._id=?");
689                selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs,
690                        new String[] { Long.toString(ContentUris.parseId(uri)) });
691                // fall through
692            }
693            case COMBINED: {
694                qb.setTables(VIEW_COMBINED);
695                qb.setProjectionMap(COMBINED_PROJECTION_MAP);
696                break;
697            }
698
699            default: {
700                throw new UnsupportedOperationException("Unknown URL " + uri.toString());
701            }
702        }
703
704        Cursor cursor = qb.query(db, projection, selection, selectionArgs, null, null, sortOrder,
705                limit);
706        cursor.setNotificationUri(getContext().getContentResolver(), BrowserContract.AUTHORITY_URI);
707        return cursor;
708    }
709
710    @Override
711    public int deleteInTransaction(Uri uri, String selection, String[] selectionArgs,
712            boolean callerIsSyncAdapter) {
713        final int match = URI_MATCHER.match(uri);
714        final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
715        switch (match) {
716            case BOOKMARKS_ID:
717            case BOOKMARKS: {
718                //TODO cascade deletes down from folders
719                if (!callerIsSyncAdapter) {
720                    // If the caller isn't a sync adapter just go through and update all the
721                    // bookmarks to have the deleted flag set.
722                    ContentValues values = new ContentValues();
723                    values.put(Bookmarks.DATE_MODIFIED, System.currentTimeMillis());
724                    values.put(Bookmarks.IS_DELETED, 1);
725                    return updateInTransaction(uri, values, selection, selectionArgs,
726                            callerIsSyncAdapter);
727                } else {
728                    // Sync adapters are allowed to actually delete things
729                    if (match == BOOKMARKS_ID) {
730                        selection = DatabaseUtils.concatenateWhere(selection,
731                                TABLE_BOOKMARKS + "._id=?");
732                        selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs,
733                                new String[] { Long.toString(ContentUris.parseId(uri)) });
734                    }
735                    return db.delete(TABLE_BOOKMARKS, selection, selectionArgs);
736                }
737            }
738
739            case HISTORY_ID: {
740                selection = DatabaseUtils.concatenateWhere(selection, TABLE_HISTORY + "._id=?");
741                selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs,
742                        new String[] { Long.toString(ContentUris.parseId(uri)) });
743                // fall through
744            }
745            case HISTORY: {
746                return db.delete(TABLE_HISTORY, selection, selectionArgs);
747            }
748
749            case SEARCHES_ID: {
750                selection = DatabaseUtils.concatenateWhere(selection, TABLE_SEARCHES + "._id=?");
751                selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs,
752                        new String[] { Long.toString(ContentUris.parseId(uri)) });
753                // fall through
754            }
755            case SEARCHES: {
756                return db.delete(TABLE_SEARCHES, selection, selectionArgs);
757            }
758
759            case SYNCSTATE: {
760                return mSyncHelper.delete(db, selection, selectionArgs);
761            }
762            case SYNCSTATE_ID: {
763                String selectionWithId =
764                        (SyncStateContract.Columns._ID + "=" + ContentUris.parseId(uri) + " ")
765                        + (selection == null ? "" : " AND (" + selection + ")");
766                return mSyncHelper.delete(db, selectionWithId, selectionArgs);
767            }
768        }
769        throw new UnsupportedOperationException("Unknown update URI " + uri);
770    }
771
772    @Override
773    public Uri insertInTransaction(Uri uri, ContentValues values, boolean callerIsSyncAdapter) {
774        final int match = URI_MATCHER.match(uri);
775        final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
776        long id = -1;
777        switch (match) {
778            case BOOKMARKS: {
779                // Mark rows dirty if they're not coming from a sync adapater
780                if (!callerIsSyncAdapter) {
781                    long now = System.currentTimeMillis();
782                    values.put(Bookmarks.DATE_CREATED, now);
783                    values.put(Bookmarks.DATE_MODIFIED, now);
784                    values.put(Bookmarks.DIRTY, 1);
785
786                    // If no parent is set default to the "Bookmarks Bar" folder
787                    if (!values.containsKey(Bookmarks.PARENT)) {
788                        values.put(Bookmarks.PARENT, FIXED_ID_BOOKMARKS_BAR);
789                    }
790                }
791
792                // If no position is requested put the bookmark at the beginning of the list
793                if (!values.containsKey(Bookmarks.POSITION)) {
794                    values.put(Bookmarks.POSITION, Long.toString(Long.MIN_VALUE));
795                }
796
797                // Extract out the image values so they can be inserted into the images table
798                String url = values.getAsString(Bookmarks.URL);
799                ContentValues imageValues = extractImageValues(values, url);
800                boolean isFolder = (values.getAsInteger(Bookmarks.IS_FOLDER) != 0);
801                if (!isFolder && imageValues != null && !TextUtils.isEmpty(url)) {
802                    db.insertOrThrow(TABLE_IMAGES, Images.FAVICON, imageValues);
803                }
804
805                id = db.insertOrThrow(TABLE_BOOKMARKS, Bookmarks.DIRTY, values);
806                break;
807            }
808
809            case HISTORY: {
810                // If no created time is specified set it to now
811                if (!values.containsKey(History.DATE_CREATED)) {
812                    values.put(History.DATE_CREATED, System.currentTimeMillis());
813                }
814
815                // Extract out the image values so they can be inserted into the images table
816                ContentValues imageValues = extractImageValues(values,
817                        values.getAsString(History.URL));
818                if (imageValues != null) {
819                    db.insertOrThrow(TABLE_IMAGES, Images.FAVICON, imageValues);
820                }
821
822                id = db.insertOrThrow(TABLE_HISTORY, History.VISITS, values);
823                break;
824            }
825
826            case SEARCHES: {
827                id = insertSearchesInTransaction(db, values);
828                break;
829            }
830
831            case SYNCSTATE: {
832                id = mSyncHelper.insert(db, values);
833                break;
834            }
835
836            default: {
837                throw new UnsupportedOperationException("Unknown insert URI " + uri);
838            }
839        }
840
841        if (id >= 0) {
842            return ContentUris.withAppendedId(uri, id);
843        } else {
844            return null;
845        }
846    }
847
848    /**
849     * Searches are unique, so perform an UPSERT manually since SQLite doesn't support them.
850     */
851    private long insertSearchesInTransaction(SQLiteDatabase db, ContentValues values) {
852        String search = values.getAsString(Searches.SEARCH);
853        if (TextUtils.isEmpty(search)) {
854            throw new IllegalArgumentException("Must include the SEARCH field");
855        }
856        Cursor cursor = null;
857        try {
858            cursor = db.query(TABLE_SEARCHES, new String[] { Searches._ID },
859                    Searches.SEARCH + "=?", new String[] { search }, null, null, null);
860            if (cursor.moveToNext()) {
861                long id = cursor.getLong(0);
862                db.update(TABLE_SEARCHES, values, Searches._ID + "=?",
863                        new String[] { Long.toString(id) });
864                return id;
865            } else {
866                return db.insertOrThrow(TABLE_SEARCHES, Searches.SEARCH, values);
867            }
868        } finally {
869            if (cursor != null) cursor.close();
870        }
871    }
872
873    @Override
874    public int updateInTransaction(Uri uri, ContentValues values, String selection,
875            String[] selectionArgs, boolean callerIsSyncAdapter) {
876        final int match = URI_MATCHER.match(uri);
877        final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
878        switch (match) {
879            case BOOKMARKS_ID: {
880                selection = DatabaseUtils.concatenateWhere(selection,
881                        TABLE_BOOKMARKS + "._id=?");
882                selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs,
883                        new String[] { Long.toString(ContentUris.parseId(uri)) });
884                // fall through
885            }
886            case BOOKMARKS: {
887                return updateBookmarksInTransaction(values, selection, selectionArgs,
888                        callerIsSyncAdapter);
889            }
890
891            case HISTORY_ID: {
892                selection = DatabaseUtils.concatenateWhere(selection, TABLE_HISTORY + "._id=?");
893                selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs,
894                        new String[] { Long.toString(ContentUris.parseId(uri)) });
895                // fall through
896            }
897            case HISTORY: {
898                return updateHistoryInTransaction(values, selection, selectionArgs);
899            }
900
901            case SYNCSTATE: {
902                return mSyncHelper.update(mDb, values,
903                        appendAccountToSelection(uri, selection), selectionArgs);
904            }
905
906            case SYNCSTATE_ID: {
907                selection = appendAccountToSelection(uri, selection);
908                String selectionWithId =
909                        (SyncStateContract.Columns._ID + "=" + ContentUris.parseId(uri) + " ")
910                        + (selection == null ? "" : " AND (" + selection + ")");
911                return mSyncHelper.update(mDb, values,
912                        selectionWithId, selectionArgs);
913            }
914
915            case IMAGES: {
916                String url = values.getAsString(Images.URL);
917                if (TextUtils.isEmpty(url)) {
918                    throw new IllegalArgumentException("Images.URL is required");
919                }
920                int count = db.update(TABLE_IMAGES, values, Images.URL + "=?",
921                        new String[] { url });
922                if (count == 0) {
923                    db.insertOrThrow(TABLE_IMAGES, Images.FAVICON, values);
924                    count = 1;
925                }
926                return count;
927            }
928        }
929        throw new UnsupportedOperationException("Unknown update URI " + uri);
930    }
931
932    /**
933     * Does a query to find the matching bookmarks and updates each one with the provided values.
934     */
935    int updateBookmarksInTransaction(ContentValues values, String selection,
936            String[] selectionArgs, boolean callerIsSyncAdapter) {
937        int count = 0;
938        final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
939        Cursor cursor = query(Bookmarks.CONTENT_URI,
940                new String[] { Bookmarks._ID, Bookmarks.VERSION, Bookmarks.URL },
941                selection, selectionArgs, null);
942        try {
943            String[] args = new String[1];
944            // Mark the bookmark dirty if the caller isn't a sync adapter
945            if (!callerIsSyncAdapter) {
946                values.put(Bookmarks.DATE_MODIFIED, System.currentTimeMillis());
947                values.put(Bookmarks.DIRTY, 1);
948            }
949
950            boolean updatingUrl = values.containsKey(Bookmarks.URL);
951            String url = null;
952            if (updatingUrl) {
953                url = values.getAsString(Bookmarks.URL);
954            }
955            ContentValues imageValues = extractImageValues(values, url);
956
957            while (cursor.moveToNext()) {
958                args[0] = cursor.getString(0);
959                if (!callerIsSyncAdapter) {
960                    // increase the local version for non-sync changes
961                    values.put(Bookmarks.VERSION, cursor.getLong(1) + 1);
962                }
963                count += db.update(TABLE_BOOKMARKS, values, "_id=?", args);
964
965                // Update the images over in their table
966                if (imageValues != null) {
967                    if (!updatingUrl) {
968                        url = cursor.getString(2);
969                        imageValues.put(Images.URL, url);
970                    }
971
972                    if (!TextUtils.isEmpty(url)) {
973                        args[0] = url;
974                        if (db.update(TABLE_IMAGES, imageValues, Images.URL + "=?", args) == 0) {
975                            db.insert(TABLE_IMAGES, Images.FAVICON, imageValues);
976                        }
977                    }
978                }
979            }
980        } finally {
981            if (cursor != null) cursor.close();
982        }
983        return count;
984    }
985
986    /**
987     * Does a query to find the matching bookmarks and updates each one with the provided values.
988     */
989    int updateHistoryInTransaction(ContentValues values, String selection, String[] selectionArgs) {
990        int count = 0;
991        final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
992        Cursor cursor = query(History.CONTENT_URI,
993                new String[] { History._ID, History.URL },
994                selection, selectionArgs, null);
995        try {
996            String[] args = new String[1];
997
998            boolean updatingUrl = values.containsKey(History.URL);
999            String url = null;
1000            if (updatingUrl) {
1001                url = values.getAsString(History.URL);
1002            }
1003            ContentValues imageValues = extractImageValues(values, url);
1004
1005            while (cursor.moveToNext()) {
1006                args[0] = cursor.getString(0);
1007                count += db.update(TABLE_HISTORY, values, "_id=?", args);
1008
1009                // Update the images over in their table
1010                if (imageValues != null) {
1011                    if (!updatingUrl) {
1012                        url = cursor.getString(1);
1013                        imageValues.put(Images.URL, url);
1014                    }
1015                    args[0] = url;
1016                    if (db.update(TABLE_IMAGES, imageValues, Images.URL + "=?", args) == 0) {
1017                        db.insert(TABLE_IMAGES, Images.FAVICON, imageValues);
1018                    }
1019                }
1020            }
1021        } finally {
1022            if (cursor != null) cursor.close();
1023        }
1024        return count;
1025    }
1026
1027    String appendAccountToSelection(Uri uri, String selection) {
1028        final String accountName = uri.getQueryParameter(RawContacts.ACCOUNT_NAME);
1029        final String accountType = uri.getQueryParameter(RawContacts.ACCOUNT_TYPE);
1030
1031        final boolean partialUri = TextUtils.isEmpty(accountName) ^ TextUtils.isEmpty(accountType);
1032        if (partialUri) {
1033            // Throw when either account is incomplete
1034            throw new IllegalArgumentException(
1035                    "Must specify both or neither of ACCOUNT_NAME and ACCOUNT_TYPE for " + uri);
1036        }
1037
1038        // Accounts are valid by only checking one parameter, since we've
1039        // already ruled out partial accounts.
1040        final boolean validAccount = !TextUtils.isEmpty(accountName);
1041        if (validAccount) {
1042            StringBuilder selectionSb = new StringBuilder(RawContacts.ACCOUNT_NAME + "="
1043                    + DatabaseUtils.sqlEscapeString(accountName) + " AND "
1044                    + RawContacts.ACCOUNT_TYPE + "="
1045                    + DatabaseUtils.sqlEscapeString(accountType));
1046            if (!TextUtils.isEmpty(selection)) {
1047                selectionSb.append(" AND (");
1048                selectionSb.append(selection);
1049                selectionSb.append(')');
1050            }
1051            return selectionSb.toString();
1052        } else {
1053            return selection;
1054        }
1055    }
1056
1057    ContentValues extractImageValues(ContentValues values, String url) {
1058        ContentValues imageValues = null;
1059        // favicon
1060        if (values.containsKey(Bookmarks.FAVICON)) {
1061            imageValues = new ContentValues();
1062            imageValues.put(Images.FAVICON, values.getAsByteArray(Bookmarks.FAVICON));
1063            values.remove(Bookmarks.FAVICON);
1064        }
1065
1066        // thumbnail
1067        if (values.containsKey(Bookmarks.THUMBNAIL)) {
1068            if (imageValues == null) {
1069                imageValues = new ContentValues();
1070            }
1071            imageValues.put(Images.THUMBNAIL, values.getAsByteArray(Bookmarks.THUMBNAIL));
1072            values.remove(Bookmarks.THUMBNAIL);
1073        }
1074
1075        // touch icon
1076        if (values.containsKey(Bookmarks.TOUCH_ICON)) {
1077            if (imageValues == null) {
1078                imageValues = new ContentValues();
1079            }
1080            imageValues.put(Images.TOUCH_ICON, values.getAsByteArray(Bookmarks.TOUCH_ICON));
1081            values.remove(Bookmarks.TOUCH_ICON);
1082        }
1083
1084        if (imageValues != null) {
1085            imageValues.put(Images.URL,  url);
1086        }
1087        return imageValues;
1088    }
1089}
1090