BrowserProvider2.java revision 64144e49208df145c8362dfa037335c7b9210167
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.Bookmarks;
35import android.provider.BrowserContract.ChromeSyncColumns;
36import android.provider.BrowserContract.History;
37import android.provider.BrowserContract.Searches;
38import android.provider.BrowserContract.SyncState;
39import android.provider.ContactsContract.RawContacts;
40import android.provider.SyncStateContract;
41import android.text.TextUtils;
42
43import java.util.HashMap;
44
45public class BrowserProvider2 extends SQLiteContentProvider {
46
47    static final Uri LEGACY_BROWSER_AUTHORITY_URI = Uri.parse("browser");
48
49    static final String TABLE_BOOKMARKS = "bookmarks";
50    static final String TABLE_HISTORY = "history";
51    static final String TABLE_SEARCHES = "searches";
52    static final String TABLE_SYNC_STATE = "syncstate";
53
54    static final String DEFAULT_HISTORY_SORT = History.DATE_LAST_VISITED + " DESC";
55
56    static final int BOOKMARKS = 1000;
57    static final int BOOKMARKS_ID = 1001;
58    static final int BOOKMARKS_FOLDER = 1002;
59    static final int BOOKMARKS_FOLDER_ID = 1003;
60
61    static final int HISTORY = 2000;
62    static final int HISTORY_ID = 2001;
63
64    static final int SEARCHES = 3000;
65    static final int SEARCHES_ID = 3001;
66
67    static final int SYNCSTATE = 4000;
68    static final int SYNCSTATE_ID = 4001;
69
70    static final long FIXED_ID_CHROME_ROOT = 1;
71    static final long FIXED_ID_BOOKMARKS = 2;
72    static final long FIXED_ID_BOOKMARKS_BAR = 3;
73    static final long FIXED_ID_OTHER_BOOKMARKS = 4;
74
75    static final String DEFAULT_BOOKMARKS_SORT_ORDER = "position ASC, _id ASC";
76
77    static final UriMatcher URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
78
79    static final HashMap<String, String> BOOKMARKS_PROJECTION_MAP = new HashMap<String, String>();
80    static final HashMap<String, String> OTHER_BOOKMARKS_PROJECTION_MAP = new HashMap<String, String>();
81    static final HashMap<String, String> HISTORY_PROJECTION_MAP = new HashMap<String, String>();
82    static final HashMap<String, String> SEARCHES_PROJECTION_MAP = new HashMap<String, String>();
83    static final HashMap<String, String> SYNC_STATE_PROJECTION_MAP = new HashMap<String, String>();
84
85    static {
86        final UriMatcher matcher = URI_MATCHER;
87        matcher.addURI(BrowserContract.AUTHORITY, "bookmarks", BOOKMARKS);
88        matcher.addURI(BrowserContract.AUTHORITY, "bookmarks/#", BOOKMARKS_ID);
89        matcher.addURI(BrowserContract.AUTHORITY, "bookmarks/folder", BOOKMARKS_FOLDER);
90        matcher.addURI(BrowserContract.AUTHORITY, "bookmarks/folder/#", BOOKMARKS_FOLDER_ID);
91        matcher.addURI(BrowserContract.AUTHORITY, "history", HISTORY);
92        matcher.addURI(BrowserContract.AUTHORITY, "history/#", HISTORY_ID);
93        matcher.addURI(BrowserContract.AUTHORITY, "searches", SEARCHES);
94        matcher.addURI(BrowserContract.AUTHORITY, "searches/#", SEARCHES_ID);
95        matcher.addURI(BrowserContract.AUTHORITY, "syncstate", SYNCSTATE);
96        matcher.addURI(BrowserContract.AUTHORITY, "syncstate/#", SYNCSTATE_ID);
97
98        // Bookmarks
99        HashMap<String, String> map = BOOKMARKS_PROJECTION_MAP;
100        map.put(Bookmarks._ID, qualifyColumn(TABLE_BOOKMARKS, Bookmarks._ID));
101        map.put(Bookmarks.TITLE, Bookmarks.TITLE);
102        map.put(Bookmarks.URL, Bookmarks.URL);
103        map.put(Bookmarks.FAVICON, Bookmarks.FAVICON);
104        map.put(Bookmarks.THUMBNAIL, Bookmarks.THUMBNAIL);
105        map.put(Bookmarks.TOUCH_ICON, Bookmarks.TOUCH_ICON);
106        map.put(Bookmarks.IS_FOLDER, Bookmarks.IS_FOLDER);
107        map.put(Bookmarks.PARENT, Bookmarks.PARENT);
108        map.put(Bookmarks.POSITION, Bookmarks.POSITION);
109        map.put(Bookmarks.IS_DELETED, Bookmarks.IS_DELETED);
110        map.put(Bookmarks.ACCOUNT_NAME, Bookmarks.ACCOUNT_NAME);
111        map.put(Bookmarks.ACCOUNT_TYPE, Bookmarks.ACCOUNT_TYPE);
112        map.put(Bookmarks.SOURCE_ID, Bookmarks.SOURCE_ID);
113        map.put(Bookmarks.VERSION, Bookmarks.VERSION);
114        map.put(Bookmarks.DIRTY, Bookmarks.DIRTY);
115        map.put(Bookmarks.SYNC1, Bookmarks.SYNC1);
116        map.put(Bookmarks.SYNC2, Bookmarks.SYNC2);
117        map.put(Bookmarks.SYNC3, Bookmarks.SYNC3);
118        map.put(Bookmarks.SYNC4, Bookmarks.SYNC4);
119        map.put(Bookmarks.SYNC5, Bookmarks.SYNC5);
120
121        // Other bookmarks
122        OTHER_BOOKMARKS_PROJECTION_MAP.putAll(BOOKMARKS_PROJECTION_MAP);
123        OTHER_BOOKMARKS_PROJECTION_MAP.put(Bookmarks.POSITION,
124                Long.toString(Long.MAX_VALUE) + " AS " + Bookmarks.POSITION);
125
126        // History
127        map = HISTORY_PROJECTION_MAP;
128        map.put(History._ID, qualifyColumn(TABLE_HISTORY, History._ID));
129        map.put(History.TITLE, Bookmarks.TITLE);
130        map.put(History.URL, Bookmarks.URL);
131        map.put(History.FAVICON, Bookmarks.FAVICON);
132        map.put(History.THUMBNAIL, Bookmarks.THUMBNAIL);
133        map.put(History.TOUCH_ICON, Bookmarks.TOUCH_ICON);
134        map.put(History.DATE_CREATED, History.DATE_CREATED);
135        map.put(History.DATE_LAST_VISITED, History.DATE_LAST_VISITED);
136        map.put(History.VISITS, History.VISITS);
137        map.put(History.USER_ENTERED, History.USER_ENTERED);
138
139        // Sync state
140        map = SYNC_STATE_PROJECTION_MAP;
141        map.put(SyncState._ID, SyncState._ID);
142        map.put(SyncState.ACCOUNT_NAME, SyncState.ACCOUNT_NAME);
143        map.put(SyncState.ACCOUNT_TYPE, SyncState.ACCOUNT_TYPE);
144        map.put(SyncState.DATA, SyncState.DATA);
145    }
146
147    static final String qualifyColumn(String table, String column) {
148        return table + "." + column + " AS " + column;
149    }
150
151    DatabaseHelper mOpenHelper;
152    SyncStateContentProviderHelper mSyncHelper = new SyncStateContentProviderHelper();
153
154    final class DatabaseHelper extends SQLiteOpenHelper {
155        static final String DATABASE_NAME = "browser2.db";
156        static final int DATABASE_VERSION = 15;
157        public DatabaseHelper(Context context) {
158            super(context, DATABASE_NAME, null, DATABASE_VERSION);
159        }
160
161        @Override
162        public void onCreate(SQLiteDatabase db) {
163            db.execSQL("CREATE TABLE " + TABLE_BOOKMARKS + "(" +
164                    Bookmarks._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
165                    Bookmarks.TITLE + " TEXT," +
166                    Bookmarks.URL + " TEXT," +
167                    Bookmarks.FAVICON + " BLOB," +
168                    Bookmarks.THUMBNAIL + " BLOB," +
169                    Bookmarks.TOUCH_ICON + " BLOB," +
170                    Bookmarks.IS_FOLDER + " INTEGER NOT NULL DEFAULT 0," +
171                    Bookmarks.PARENT + " INTEGER NOT NULL DEFAULT 0," +
172                    Bookmarks.POSITION + " INTEGER NOT NULL," +
173                    Bookmarks.INSERT_AFTER + " INTEGER," +
174                    Bookmarks.IS_DELETED + " INTEGER NOT NULL DEFAULT 0," +
175                    Bookmarks.ACCOUNT_NAME + " TEXT," +
176                    Bookmarks.ACCOUNT_TYPE + " TEXT," +
177                    Bookmarks.SOURCE_ID + " TEXT," +
178                    Bookmarks.VERSION + " INTEGER NOT NULL DEFAULT 1," +
179                    Bookmarks.DIRTY + " INTEGER NOT NULL DEFAULT 0," +
180                    Bookmarks.SYNC1 + " TEXT," +
181                    Bookmarks.SYNC2 + " TEXT," +
182                    Bookmarks.SYNC3 + " TEXT," +
183                    Bookmarks.SYNC4 + " TEXT," +
184                    Bookmarks.SYNC5 + " TEXT" +
185                    ");");
186
187            // TODO indices
188
189            createDefaultBookmarks(db);
190
191            db.execSQL("CREATE TABLE " + TABLE_HISTORY + "(" +
192                    History._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
193                    History.TITLE + " TEXT," +
194                    History.URL + " TEXT NOT NULL," +
195                    History.FAVICON + " BLOB," +
196                    History.THUMBNAIL + " BLOB," +
197                    History.TOUCH_ICON + " BLOB," +
198                    History.DATE_CREATED + " INTEGER," +
199                    History.DATE_LAST_VISITED + " INTEGER," +
200                    History.VISITS + " INTEGER NOT NULL DEFAULT 0," +
201                    History.USER_ENTERED + " INTEGER" +
202                    ");");
203
204            db.execSQL("CREATE TABLE " + TABLE_SEARCHES + " (" +
205                    Searches._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
206                    Searches.SEARCH + " TEXT," +
207                    Searches.DATE + " LONG" +
208                    ");");
209
210            mSyncHelper.createDatabase(db);
211        }
212
213        @Override
214        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
215            // TODO write upgrade logic
216            db.execSQL("DROP TABLE IF EXISTS " + TABLE_BOOKMARKS);
217            db.execSQL("DROP TABLE IF EXISTS " + TABLE_HISTORY);
218            db.execSQL("DROP TABLE IF EXISTS " + TABLE_SEARCHES);
219            onCreate(db);
220        }
221
222        @Override
223        public void onOpen(SQLiteDatabase db) {
224            mSyncHelper.onDatabaseOpened(db);
225        }
226
227        private void createDefaultBookmarks(SQLiteDatabase db) {
228            ContentValues values = new ContentValues();
229            // TODO figure out how to deal with localization for the defaults
230
231            // Chrome sync root folder
232            values.put(Bookmarks._ID, FIXED_ID_CHROME_ROOT);
233            values.put(ChromeSyncColumns.SERVER_UNIQUE, ChromeSyncColumns.FOLDER_NAME_ROOT);
234            values.put(Bookmarks.TITLE, "Google Chrome");
235            values.put(Bookmarks.PARENT, 0);
236            values.put(Bookmarks.POSITION, 0);
237            values.put(Bookmarks.IS_FOLDER, true);
238            values.put(Bookmarks.DIRTY, true);
239            db.insertOrThrow(TABLE_BOOKMARKS, null, values);
240
241            // Bookmarks folder
242            values.put(Bookmarks._ID, FIXED_ID_BOOKMARKS);
243            values.put(ChromeSyncColumns.SERVER_UNIQUE, ChromeSyncColumns.FOLDER_NAME_BOOKMARKS);
244            values.put(Bookmarks.TITLE, "Bookmarks");
245            values.put(Bookmarks.PARENT, FIXED_ID_CHROME_ROOT);
246            values.put(Bookmarks.POSITION, 0);
247            values.put(Bookmarks.IS_FOLDER, true);
248            values.put(Bookmarks.DIRTY, true);
249            db.insertOrThrow(TABLE_BOOKMARKS, null, values);
250
251            // Bookmarks Bar folder
252            values.clear();
253            values.put(Bookmarks._ID, FIXED_ID_BOOKMARKS_BAR);
254            values.put(ChromeSyncColumns.SERVER_UNIQUE,
255                    ChromeSyncColumns.FOLDER_NAME_BOOKMARKS_BAR);
256            values.put(Bookmarks.TITLE, "Bookmarks Bar");
257            values.put(Bookmarks.PARENT, FIXED_ID_BOOKMARKS);
258            values.put(Bookmarks.POSITION, 0);
259            values.put(Bookmarks.IS_FOLDER, true);
260            values.put(Bookmarks.DIRTY, true);
261            db.insertOrThrow(TABLE_BOOKMARKS, null, values);
262
263            // Other Bookmarks folder
264            values.clear();
265            values.put(Bookmarks._ID, FIXED_ID_OTHER_BOOKMARKS);
266            values.put(ChromeSyncColumns.SERVER_UNIQUE,
267                    ChromeSyncColumns.FOLDER_NAME_OTHER_BOOKMARKS);
268            values.put(Bookmarks.TITLE, "Other Bookmarks");
269            values.put(Bookmarks.PARENT, FIXED_ID_BOOKMARKS);
270            values.put(Bookmarks.POSITION, 1000);
271            values.put(Bookmarks.IS_FOLDER, true);
272            values.put(Bookmarks.DIRTY, true);
273            db.insertOrThrow(TABLE_BOOKMARKS, null, values);
274
275            addDefaultBookmarks(db, FIXED_ID_BOOKMARKS_BAR);
276
277            // TODO remove this testing code
278            db.execSQL("INSERT INTO bookmarks (" +
279                    Bookmarks.TITLE + ", " +
280                    Bookmarks.URL + ", " +
281                    Bookmarks.IS_FOLDER + "," +
282                    Bookmarks.PARENT + "," +
283                    Bookmarks.POSITION +
284                ") VALUES (" +
285                    "'Google Reader', " +
286                    "'http://reader.google.com', " +
287                    "0," +
288                    Long.toString(FIXED_ID_OTHER_BOOKMARKS) + "," +
289                    0 +
290                    ");");
291        }
292
293        private void addDefaultBookmarks(SQLiteDatabase db, long parentId) {
294            final CharSequence[] bookmarks = getContext().getResources().getTextArray(
295                    R.array.bookmarks);
296            int size = bookmarks.length;
297            try {
298                for (int i = 0; i < size; i = i + 2) {
299                    CharSequence bookmarkDestination = replaceSystemPropertyInString(getContext(),
300                            bookmarks[i + 1]);
301                    db.execSQL("INSERT INTO bookmarks (" +
302                            Bookmarks.TITLE + ", " +
303                            Bookmarks.URL + ", " +
304                            Bookmarks.IS_FOLDER + "," +
305                            Bookmarks.PARENT + "," +
306                            Bookmarks.POSITION +
307                        ") VALUES (" +
308                            "'" + bookmarks[i] + "', " +
309                            "'" + bookmarkDestination + "', " +
310                            "0," +
311                            Long.toString(parentId) + "," +
312                            Integer.toString(i) +
313                            ");");
314                }
315            } catch (ArrayIndexOutOfBoundsException e) {
316            }
317        }
318
319        // XXX: This is a major hack to remove our dependency on gsf constants and
320        // its content provider. http://b/issue?id=2425179
321        private String getClientId(ContentResolver cr) {
322            String ret = "android-google";
323            Cursor c = null;
324            try {
325                c = cr.query(Uri.parse("content://com.google.settings/partner"),
326                        new String[] { "value" }, "name='client_id'", null, null);
327                if (c != null && c.moveToNext()) {
328                    ret = c.getString(0);
329                }
330            } catch (RuntimeException ex) {
331                // fall through to return the default
332            } finally {
333                if (c != null) {
334                    c.close();
335                }
336            }
337            return ret;
338        }
339
340        private CharSequence replaceSystemPropertyInString(Context context, CharSequence srcString) {
341            StringBuffer sb = new StringBuffer();
342            int lastCharLoc = 0;
343
344            final String client_id = getClientId(context.getContentResolver());
345
346            for (int i = 0; i < srcString.length(); ++i) {
347                char c = srcString.charAt(i);
348                if (c == '{') {
349                    sb.append(srcString.subSequence(lastCharLoc, i));
350                    lastCharLoc = i;
351              inner:
352                    for (int j = i; j < srcString.length(); ++j) {
353                        char k = srcString.charAt(j);
354                        if (k == '}') {
355                            String propertyKeyValue = srcString.subSequence(i + 1, j).toString();
356                            if (propertyKeyValue.equals("CLIENT_ID")) {
357                                sb.append(client_id);
358                            } else {
359                                sb.append("unknown");
360                            }
361                            lastCharLoc = j + 1;
362                            i = j;
363                            break inner;
364                        }
365                    }
366                }
367            }
368            if (srcString.length() - lastCharLoc > 0) {
369                // Put on the tail, if there is one
370                sb.append(srcString.subSequence(lastCharLoc, srcString.length()));
371            }
372            return sb;
373        }
374    }
375
376    @Override
377    public SQLiteOpenHelper getDatabaseHelper(Context context) {
378        synchronized (this) {
379            if (mOpenHelper == null) {
380                mOpenHelper = new DatabaseHelper(context);
381            }
382            return mOpenHelper;
383        }
384    }
385
386    @Override
387    public boolean isCallerSyncAdapter(Uri uri) {
388        return uri.getBooleanQueryParameter(BrowserContract.CALLER_IS_SYNCADAPTER, false);
389    }
390
391    @Override
392    public void notifyChange(boolean callerIsSyncAdapter) {
393        ContentResolver resolver = getContext().getContentResolver();
394        resolver.notifyChange(BrowserContract.AUTHORITY_URI, null, !callerIsSyncAdapter);
395        resolver.notifyChange(LEGACY_BROWSER_AUTHORITY_URI, null, !callerIsSyncAdapter);
396    }
397
398    @Override
399    public String getType(Uri uri) {
400        final int match = URI_MATCHER.match(uri);
401        switch (match) {
402            case BOOKMARKS:
403                return Bookmarks.CONTENT_TYPE;
404            case BOOKMARKS_ID:
405                return Bookmarks.CONTENT_ITEM_TYPE;
406            case HISTORY:
407                return History.CONTENT_TYPE;
408            case HISTORY_ID:
409                return History.CONTENT_ITEM_TYPE;
410            case SEARCHES:
411                return Searches.CONTENT_TYPE;
412            case SEARCHES_ID:
413                return Searches.CONTENT_ITEM_TYPE;
414//            case SUGGEST:
415//                return SearchManager.SUGGEST_MIME_TYPE;
416        }
417        return null;
418    }
419
420    @Override
421    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
422            String sortOrder) {
423        SQLiteDatabase db = mOpenHelper.getReadableDatabase();
424        final int match = URI_MATCHER.match(uri);
425        SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
426        switch (match) {
427            case BOOKMARKS_FOLDER_ID:
428            case BOOKMARKS_ID:
429            case BOOKMARKS: {
430                // Only show deleted bookmarks if requested to do so
431                if (!uri.getBooleanQueryParameter(Bookmarks.QUERY_PARAMETER_SHOW_DELETED, false)) {
432                    selection = DatabaseUtils.concatenateWhere(
433                            Bookmarks.IS_DELETED + "=0", selection);
434                }
435
436                if (match == BOOKMARKS_ID) {
437                    // Tack on the ID of the specific bookmark requested
438                    selection = DatabaseUtils.concatenateWhere(
439                            TABLE_BOOKMARKS + "." + Bookmarks._ID + "=?", selection);
440                    selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs,
441                            new String[] { Long.toString(ContentUris.parseId(uri)) });
442                } else if (match == BOOKMARKS_FOLDER_ID) {
443                    // Tack on the ID of the specific folder requested
444                    selection = DatabaseUtils.concatenateWhere(
445                            TABLE_BOOKMARKS + "." + Bookmarks.PARENT + "=?", selection);
446                    selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs,
447                            new String[] { Long.toString(ContentUris.parseId(uri)) });
448                }
449
450                if (TextUtils.isEmpty(sortOrder)) {
451                    sortOrder = DEFAULT_BOOKMARKS_SORT_ORDER;
452                }
453
454                qb.setProjectionMap(BOOKMARKS_PROJECTION_MAP);
455                qb.setTables(TABLE_BOOKMARKS);
456                break;
457            }
458
459            case BOOKMARKS_FOLDER: {
460                // Don't allow selections to be applied to the default folder
461                if (!TextUtils.isEmpty(selection) || selectionArgs != null) {
462                    throw new UnsupportedOperationException(
463                            "selections aren't supported on this URI");
464                }
465
466                qb.setTables(TABLE_BOOKMARKS);
467                qb.setProjectionMap(BOOKMARKS_PROJECTION_MAP);
468                String bookmarksBarQuery = qb.buildQuery(projection,
469                        Bookmarks.PARENT + "=?",
470                        null, null, null, null, null);
471
472                qb.setProjectionMap(OTHER_BOOKMARKS_PROJECTION_MAP);
473                String otherBookmarksQuery = qb.buildQuery(projection,
474                        Bookmarks._ID + "=?",
475                        null, null, null, null, null);
476
477                String query = qb.buildUnionQuery(
478                        new String[] { bookmarksBarQuery, otherBookmarksQuery },
479                        DEFAULT_BOOKMARKS_SORT_ORDER, null);
480
481                return db.rawQuery(query, new String[] {
482                        Long.toString(FIXED_ID_BOOKMARKS_BAR),
483                        Long.toString(FIXED_ID_OTHER_BOOKMARKS)});
484            }
485
486            case HISTORY_ID: {
487                selection = DatabaseUtils.concatenateWhere(
488                        TABLE_HISTORY + "._id=?", selection);
489                selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs,
490                        new String[] { Long.toString(ContentUris.parseId(uri)) });
491                // fall through
492            }
493            case HISTORY: {
494                if (sortOrder == null) {
495                    sortOrder = DEFAULT_HISTORY_SORT;
496                }
497                qb.setProjectionMap(HISTORY_PROJECTION_MAP);
498                qb.setTables(TABLE_HISTORY);
499                break;
500            }
501
502            case SYNCSTATE: {
503                return mSyncHelper.query(db, projection, selection, selectionArgs, sortOrder);
504            }
505
506            case SYNCSTATE_ID: {
507                selection = appendAccountToSelection(uri, selection);
508                String selectionWithId =
509                        (SyncStateContract.Columns._ID + "=" + ContentUris.parseId(uri) + " ")
510                        + (selection == null ? "" : " AND (" + selection + ")");
511                return mSyncHelper.query(db, projection, selectionWithId, selectionArgs, sortOrder);
512            }
513
514            default: {
515                throw new UnsupportedOperationException("Unknown URL " + uri.toString());
516            }
517        }
518
519        Cursor cursor = qb.query(db, projection, selection, selectionArgs, null, null, sortOrder);
520        cursor.setNotificationUri(getContext().getContentResolver(), BrowserContract.AUTHORITY_URI);
521        return cursor;
522    }
523
524    @Override
525    public int deleteInTransaction(Uri uri, String selection, String[] selectionArgs,
526            boolean callerIsSyncAdapter) {
527        final int match = URI_MATCHER.match(uri);
528        final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
529        switch (match) {
530            case BOOKMARKS_ID:
531            case BOOKMARKS: {
532                //TODO cascade deletes down from folders
533                if (!callerIsSyncAdapter) {
534                    // If the caller isn't a sync adapter just go through and update all the
535                    // bookmarks to have the deleted flag set.
536                    ContentValues values = new ContentValues();
537                    values.put(Bookmarks.IS_DELETED, 1);
538                    return updateInTransaction(uri, values, selection, selectionArgs,
539                            callerIsSyncAdapter);
540                } else {
541                    // Sync adapters are allowed to actually delete things
542                    if (match == BOOKMARKS_ID) {
543                        selection = DatabaseUtils.concatenateWhere(selection,
544                                TABLE_BOOKMARKS + "._id=?");
545                        selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs,
546                                new String[] { Long.toString(ContentUris.parseId(uri)) });
547                    }
548                    return db.delete(TABLE_BOOKMARKS, selection, selectionArgs);
549                }
550            }
551
552            case HISTORY_ID: {
553                selection = DatabaseUtils.concatenateWhere(selection, TABLE_HISTORY + "._id=?");
554                selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs,
555                        new String[] { Long.toString(ContentUris.parseId(uri)) });
556                // fall through
557            }
558            case HISTORY: {
559                return db.delete(TABLE_HISTORY, selection, selectionArgs);
560            }
561
562            case SEARCHES_ID: {
563                selection = DatabaseUtils.concatenateWhere(selection, TABLE_SEARCHES + "._id=?");
564                selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs,
565                        new String[] { Long.toString(ContentUris.parseId(uri)) });
566                // fall through
567            }
568            case SEARCHES: {
569                return db.delete(TABLE_SEARCHES, selection, selectionArgs);
570            }
571
572            case SYNCSTATE: {
573                return mSyncHelper.delete(db, selection, selectionArgs);
574            }
575            case SYNCSTATE_ID: {
576                String selectionWithId =
577                        (SyncStateContract.Columns._ID + "=" + ContentUris.parseId(uri) + " ")
578                        + (selection == null ? "" : " AND (" + selection + ")");
579                return mSyncHelper.delete(db, selectionWithId, selectionArgs);
580            }
581        }
582        throw new UnsupportedOperationException("Unknown update URI " + uri);
583    }
584
585    @Override
586    public Uri insertInTransaction(Uri uri, ContentValues values, boolean callerIsSyncAdapter) {
587        final int match = URI_MATCHER.match(uri);
588        final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
589        long id = -1;
590        switch (match) {
591            case BOOKMARKS: {
592                // Mark rows dirty if they're not coming from a sync adapater
593                if (!callerIsSyncAdapter) {
594                    values.put(Bookmarks.DIRTY, 1);
595                }
596
597                // If no parent is set default to the "Bookmarks Bar" folder
598                if (!values.containsKey(Bookmarks.PARENT)) {
599                    values.put(Bookmarks.PARENT, FIXED_ID_BOOKMARKS_BAR);
600                }
601
602                // If no position is requested put the bookmark at the beginning of the list
603                if (!values.containsKey(Bookmarks.POSITION)) {
604                    values.put(Bookmarks.POSITION, Long.toString(Long.MIN_VALUE));
605                }
606
607                id = db.insertOrThrow(TABLE_BOOKMARKS, Bookmarks.DIRTY, values);
608                break;
609            }
610
611            case HISTORY: {
612                id = db.insertOrThrow(TABLE_HISTORY, History.VISITS, values);
613                break;
614            }
615
616            case SEARCHES: {
617                id = db.insertOrThrow(TABLE_SEARCHES, Searches.SEARCH, values);
618                break;
619            }
620
621            case SYNCSTATE: {
622                id = mSyncHelper.insert(mDb, values);
623                break;
624            }
625
626            default: {
627                throw new UnsupportedOperationException("Unknown insert URI " + uri);
628            }
629        }
630
631        if (id >= 0) {
632            return ContentUris.withAppendedId(uri, id);
633        } else {
634            return null;
635        }
636    }
637
638    @Override
639    public int updateInTransaction(Uri uri, ContentValues values, String selection,
640            String[] selectionArgs, boolean callerIsSyncAdapter) {
641        final int match = URI_MATCHER.match(uri);
642        final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
643        switch (match) {
644            case BOOKMARKS_ID: {
645                // Mark the bookmark dirty if the caller isn't a sync adapter
646                if (!callerIsSyncAdapter) {
647                    values = new ContentValues(values);
648                    values.put(Bookmarks.DIRTY, 1);
649                }
650                selection = DatabaseUtils.concatenateWhere(selection,
651                        TABLE_BOOKMARKS + "._id=?");
652                selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs,
653                        new String[] { Long.toString(ContentUris.parseId(uri)) });
654                return db.update(TABLE_BOOKMARKS, values, selection, selectionArgs);
655            }
656
657            case BOOKMARKS: {
658                if (!callerIsSyncAdapter) {
659                    values = new ContentValues(values);
660                    values.put(Bookmarks.DIRTY, 1);
661                }
662                return updateBookmarksInTransaction(values, selection, selectionArgs);
663            }
664
665            case HISTORY_ID: {
666                selection = DatabaseUtils.concatenateWhere(selection, TABLE_HISTORY + "._id=?");
667                selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs,
668                        new String[] { Long.toString(ContentUris.parseId(uri)) });
669                // fall through
670            }
671            case HISTORY: {
672                return db.update(TABLE_HISTORY, values, selection, selectionArgs);
673            }
674
675            case SEARCHES_ID: {
676                selection = DatabaseUtils.concatenateWhere(selection, TABLE_SEARCHES + "._id=?");
677                selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs,
678                        new String[] { Long.toString(ContentUris.parseId(uri)) });
679                // fall through
680            }
681            case SEARCHES: {
682                return db.update(TABLE_SEARCHES, values, selection, selectionArgs);
683            }
684
685            case SYNCSTATE: {
686                return mSyncHelper.update(mDb, values,
687                        appendAccountToSelection(uri, selection), selectionArgs);
688            }
689
690            case SYNCSTATE_ID: {
691                selection = appendAccountToSelection(uri, selection);
692                String selectionWithId =
693                        (SyncStateContract.Columns._ID + "=" + ContentUris.parseId(uri) + " ")
694                        + (selection == null ? "" : " AND (" + selection + ")");
695                return mSyncHelper.update(mDb, values,
696                        selectionWithId, selectionArgs);
697            }
698        }
699        throw new UnsupportedOperationException("Unknown update URI " + uri);
700    }
701
702    /**
703     * Does a query to find the matching bookmarks and updates each one with the provided values.
704     */
705    private int updateBookmarksInTransaction(ContentValues values, String selection,
706            String[] selectionArgs) {
707        int count = 0;
708        final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
709        Cursor cursor = query(Bookmarks.CONTENT_URI, new String[] { Bookmarks._ID },
710                selection, selectionArgs, null);
711        try {
712            String[] args = new String[1];
713            while (cursor.moveToNext()) {
714                args[0] = cursor.getString(0);
715                count += db.update(TABLE_BOOKMARKS, values, "_id=?", args);
716            }
717        } finally {
718            if (cursor != null) cursor.close();
719        }
720        return count;
721    }
722
723    private String appendAccountToSelection(Uri uri, String selection) {
724        final String accountName = uri.getQueryParameter(RawContacts.ACCOUNT_NAME);
725        final String accountType = uri.getQueryParameter(RawContacts.ACCOUNT_TYPE);
726
727        final boolean partialUri = TextUtils.isEmpty(accountName) ^ TextUtils.isEmpty(accountType);
728        if (partialUri) {
729            // Throw when either account is incomplete
730            throw new IllegalArgumentException(
731                    "Must specify both or neither of ACCOUNT_NAME and ACCOUNT_TYPE for " + uri);
732        }
733
734        // Accounts are valid by only checking one parameter, since we've
735        // already ruled out partial accounts.
736        final boolean validAccount = !TextUtils.isEmpty(accountName);
737        if (validAccount) {
738            StringBuilder selectionSb = new StringBuilder(RawContacts.ACCOUNT_NAME + "="
739                    + DatabaseUtils.sqlEscapeString(accountName) + " AND "
740                    + RawContacts.ACCOUNT_TYPE + "="
741                    + DatabaseUtils.sqlEscapeString(accountType));
742            if (!TextUtils.isEmpty(selection)) {
743                selectionSb.append(" AND (");
744                selectionSb.append(selection);
745                selectionSb.append(')');
746            }
747            return selectionSb.toString();
748        } else {
749            return selection;
750        }
751    }
752}
753