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