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