mobile/android/base/db/LocalBrowserDB.java

Wed, 31 Dec 2014 07:22:50 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 07:22:50 +0100
branch
TOR_BUG_3246
changeset 4
fc2d59ddac77
permissions
-rw-r--r--

Correct previous dual key logic pending first delivery installment.

     1 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
     2  * This Source Code Form is subject to the terms of the Mozilla Public
     3  * License, v. 2.0. If a copy of the MPL was not distributed with this
     4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     6 package org.mozilla.gecko.db;
     8 import java.io.ByteArrayOutputStream;
     9 import java.util.Collection;
    10 import java.util.HashMap;
    11 import java.util.List;
    13 import org.mozilla.gecko.AboutPages;
    14 import org.mozilla.gecko.db.BrowserContract.Bookmarks;
    15 import org.mozilla.gecko.db.BrowserContract.Combined;
    16 import org.mozilla.gecko.db.BrowserContract.ExpirePriority;
    17 import org.mozilla.gecko.db.BrowserContract.FaviconColumns;
    18 import org.mozilla.gecko.db.BrowserContract.Favicons;
    19 import org.mozilla.gecko.db.BrowserContract.History;
    20 import org.mozilla.gecko.db.BrowserContract.ReadingListItems;
    21 import org.mozilla.gecko.db.BrowserContract.SyncColumns;
    22 import org.mozilla.gecko.db.BrowserContract.Thumbnails;
    23 import org.mozilla.gecko.db.BrowserContract.URLColumns;
    24 import org.mozilla.gecko.favicons.decoders.FaviconDecoder;
    25 import org.mozilla.gecko.favicons.decoders.LoadFaviconResult;
    27 import android.content.ContentProviderOperation;
    28 import android.content.ContentResolver;
    29 import android.content.ContentValues;
    30 import android.database.ContentObserver;
    31 import android.database.Cursor;
    32 import android.database.CursorWrapper;
    33 import android.graphics.Bitmap;
    34 import android.graphics.drawable.BitmapDrawable;
    35 import android.net.Uri;
    36 import android.provider.Browser;
    37 import android.text.TextUtils;
    38 import android.util.Log;
    40 public class LocalBrowserDB implements BrowserDB.BrowserDBIface {
    41     // Calculate these once, at initialization. isLoggable is too expensive to
    42     // have in-line in each log call.
    43     private static final String LOGTAG = "GeckoLocalBrowserDB";
    44     private static boolean logDebug = Log.isLoggable(LOGTAG, Log.DEBUG);
    45     protected static void debug(String message) {
    46         if (logDebug) {
    47             Log.d(LOGTAG, message);
    48         }
    49     }
    51     private final String mProfile;
    53     // Map of folder GUIDs to IDs. Used for caching.
    54     private HashMap<String, Long> mFolderIdMap;
    56     // Use wrapped Boolean so that we can have a null state
    57     private Boolean mDesktopBookmarksExist;
    59     private final Uri mBookmarksUriWithProfile;
    60     private final Uri mParentsUriWithProfile;
    61     private final Uri mFlagsUriWithProfile;
    62     private final Uri mHistoryUriWithProfile;
    63     private final Uri mHistoryExpireUriWithProfile;
    64     private final Uri mCombinedUriWithProfile;
    65     private final Uri mDeletedHistoryUriWithProfile;
    66     private final Uri mUpdateHistoryUriWithProfile;
    67     private final Uri mFaviconsUriWithProfile;
    68     private final Uri mThumbnailsUriWithProfile;
    69     private final Uri mReadingListUriWithProfile;
    71     private static final String[] DEFAULT_BOOKMARK_COLUMNS =
    72             new String[] { Bookmarks._ID,
    73                            Bookmarks.GUID,
    74                            Bookmarks.URL,
    75                            Bookmarks.TITLE,
    76                            Bookmarks.TYPE,
    77                            Bookmarks.PARENT };
    79     public LocalBrowserDB(String profile) {
    80         mProfile = profile;
    81         mFolderIdMap = new HashMap<String, Long>();
    82         mDesktopBookmarksExist = null;
    84         mBookmarksUriWithProfile = appendProfile(Bookmarks.CONTENT_URI);
    85         mParentsUriWithProfile = appendProfile(Bookmarks.PARENTS_CONTENT_URI);
    86         mFlagsUriWithProfile = appendProfile(Bookmarks.FLAGS_URI);
    87         mHistoryUriWithProfile = appendProfile(History.CONTENT_URI);
    88         mHistoryExpireUriWithProfile = appendProfile(History.CONTENT_OLD_URI);
    89         mCombinedUriWithProfile = appendProfile(Combined.CONTENT_URI);
    90         mFaviconsUriWithProfile = appendProfile(Favicons.CONTENT_URI);
    91         mThumbnailsUriWithProfile = appendProfile(Thumbnails.CONTENT_URI);
    92         mReadingListUriWithProfile = appendProfile(ReadingListItems.CONTENT_URI);
    94         mDeletedHistoryUriWithProfile = mHistoryUriWithProfile.buildUpon().
    95             appendQueryParameter(BrowserContract.PARAM_SHOW_DELETED, "1").build();
    97         mUpdateHistoryUriWithProfile = mHistoryUriWithProfile.buildUpon().
    98             appendQueryParameter(BrowserContract.PARAM_INCREMENT_VISITS, "true").
    99             appendQueryParameter(BrowserContract.PARAM_INSERT_IF_NEEDED, "true").build();
   100     }
   102     // Invalidate cached data
   103     @Override
   104     public void invalidateCachedState() {
   105         mDesktopBookmarksExist = null;
   106     }
   108     private Uri historyUriWithLimit(int limit) {
   109         return mHistoryUriWithProfile.buildUpon().appendQueryParameter(BrowserContract.PARAM_LIMIT,
   110                                                                        String.valueOf(limit)).build();
   111     }
   113     private Uri bookmarksUriWithLimit(int limit) {
   114         return mBookmarksUriWithProfile.buildUpon().appendQueryParameter(BrowserContract.PARAM_LIMIT,
   115                                                                          String.valueOf(limit)).build();
   116     }
   118     private Uri combinedUriWithLimit(int limit) {
   119         return mCombinedUriWithProfile.buildUpon().appendQueryParameter(BrowserContract.PARAM_LIMIT,
   120                 String.valueOf(limit)).build();
   121     }
   123     private Uri appendProfile(Uri uri) {
   124         return uri.buildUpon().appendQueryParameter(BrowserContract.PARAM_PROFILE, mProfile).build();
   125     }
   127     private Uri getAllBookmarksUri() {
   128         Uri.Builder uriBuilder = mBookmarksUriWithProfile.buildUpon()
   129             .appendQueryParameter(BrowserContract.PARAM_SHOW_DELETED, "1");
   130         return uriBuilder.build();
   131     }
   133     private Uri getAllHistoryUri() {
   134         Uri.Builder uriBuilder = mHistoryUriWithProfile.buildUpon()
   135             .appendQueryParameter(BrowserContract.PARAM_SHOW_DELETED, "1");
   136         return uriBuilder.build();
   137     }
   139     private Uri getAllFaviconsUri() {
   140         Uri.Builder uriBuilder = mFaviconsUriWithProfile.buildUpon()
   141             .appendQueryParameter(BrowserContract.PARAM_SHOW_DELETED, "1");
   142         return uriBuilder.build();
   143     }
   145     private Cursor filterAllSites(ContentResolver cr, String[] projection, CharSequence constraint,
   146             int limit, CharSequence urlFilter) {
   147         return filterAllSites(cr, projection, constraint, limit, urlFilter, "", null);
   148     }
   150     private Cursor filterAllSites(ContentResolver cr, String[] projection, CharSequence constraint,
   151             int limit, CharSequence urlFilter, String selection, String[] selectionArgs) {
   152         // The combined history/bookmarks selection queries for sites with a url or title containing
   153         // the constraint string(s), treating space-separated words as separate constraints
   154         if (!TextUtils.isEmpty(constraint)) {
   155           String[] constraintWords = constraint.toString().split(" ");
   156           // Only create a filter query with a maximum of 10 constraint words
   157           int constraintCount = Math.min(constraintWords.length, 10);
   158           for (int i = 0; i < constraintCount; i++) {
   159               selection = DBUtils.concatenateWhere(selection, "(" + Combined.URL + " LIKE ? OR " +
   160                                                                     Combined.TITLE + " LIKE ?)");
   161               String constraintWord =  "%" + constraintWords[i] + "%";
   162               selectionArgs = DBUtils.appendSelectionArgs(selectionArgs,
   163                   new String[] { constraintWord, constraintWord });
   164           }
   165         }
   167         if (urlFilter != null) {
   168             selection = DBUtils.concatenateWhere(selection, "(" + Combined.URL + " NOT LIKE ?)");
   169             selectionArgs = DBUtils.appendSelectionArgs(selectionArgs, new String[] { urlFilter.toString() });
   170         }
   172         // Our version of frecency is computed by scaling the number of visits by a multiplier
   173         // that approximates Gaussian decay, based on how long ago the entry was last visited.
   174         // Since we're limited by the math we can do with sqlite, we're calculating this
   175         // approximation using the Cauchy distribution: multiplier = 15^2 / (age^2 + 15^2).
   176         // Using 15 as our scale parameter, we get a constant 15^2 = 225. Following this math,
   177         // frecencyScore = numVisits * max(1, 100 * 225 / (age*age + 225)). (See bug 704977)
   178         // We also give bookmarks an extra bonus boost by adding 100 points to their frecency score.
   179         final String sortOrder = BrowserContract.getFrecencySortOrder(true, false);
   181         Cursor c = cr.query(combinedUriWithLimit(limit),
   182                             projection,
   183                             selection,
   184                             selectionArgs,
   185                             sortOrder);
   187         return new LocalDBCursor(c);
   188     }
   190     @Override
   191     public int getCount(ContentResolver cr, String database) {
   192         int count = 0;
   193         String[] columns = null;
   194         String constraint = null;
   195         Uri uri = null;
   196         if ("history".equals(database)) {
   197             uri = mHistoryUriWithProfile;
   198             columns = new String[] { History._ID };
   199             constraint = Combined.VISITS + " > 0";
   200         } else if ("bookmarks".equals(database)) {
   201             uri = mBookmarksUriWithProfile;
   202             columns = new String[] { Bookmarks._ID };
   203             // ignore folders, tags, keywords, separators, etc.
   204             constraint = Bookmarks.TYPE + " = " + Bookmarks.TYPE_BOOKMARK;
   205         } else if ("thumbnails".equals(database)) {
   206             uri = mThumbnailsUriWithProfile;
   207             columns = new String[] { Thumbnails._ID };
   208         } else if ("favicons".equals(database)) {
   209             uri = mFaviconsUriWithProfile;
   210             columns = new String[] { Favicons._ID };
   211         }
   212         if (uri != null) {
   213             Cursor cursor = null;
   215             try {
   216                 cursor = cr.query(uri, columns, constraint, null, null);
   217                 count = cursor.getCount();
   218             } finally {
   219                 if (cursor != null)
   220                     cursor.close();
   221             }
   222         }
   223         debug("Got count " + count + " for " + database);
   224         return count;
   225     }
   227     @Override
   228     public Cursor filter(ContentResolver cr, CharSequence constraint, int limit) {
   229         return filterAllSites(cr,
   230                               new String[] { Combined._ID,
   231                                              Combined.URL,
   232                                              Combined.TITLE,
   233                                              Combined.DISPLAY,
   234                                              Combined.BOOKMARK_ID,
   235                                              Combined.HISTORY_ID },
   236                               constraint,
   237                               limit,
   238                               null);
   239     }
   241     @Override
   242     public Cursor getTopSites(ContentResolver cr, int limit) {
   243         // Filter out bookmarks that don't have real parents (e.g. pinned sites or reading list items)
   244         String selection = DBUtils.concatenateWhere("", Combined.URL + " NOT IN (SELECT " +
   245                                              Bookmarks.URL + " FROM bookmarks WHERE " +
   246                                              DBUtils.qualifyColumn("bookmarks", Bookmarks.PARENT) + " < ? AND " +
   247                                              DBUtils.qualifyColumn("bookmarks", Bookmarks.IS_DELETED) + " == 0)");
   248         String[] selectionArgs = new String[] { String.valueOf(Bookmarks.FIXED_ROOT_ID) };
   250         return filterAllSites(cr,
   251                               new String[] { Combined._ID,
   252                                              Combined.URL,
   253                                              Combined.TITLE,
   254                                              Combined.DISPLAY,
   255                                              Combined.BOOKMARK_ID,
   256                                              Combined.HISTORY_ID },
   257                               "",
   258                               limit,
   259                               AboutPages.URL_FILTER,
   260                               selection,
   261                               selectionArgs);
   262     }
   264     @Override
   265     public void updateVisitedHistory(ContentResolver cr, String uri) {
   266         ContentValues values = new ContentValues();
   268         values.put(History.URL, uri);
   269         values.put(History.DATE_LAST_VISITED, System.currentTimeMillis());
   270         values.put(History.IS_DELETED, 0);
   272         // This will insert a new history entry if one for this URL
   273         // doesn't already exist
   274         cr.update(mUpdateHistoryUriWithProfile,
   275                   values,
   276                   History.URL + " = ?",
   277                   new String[] { uri });
   278     }
   280     @Override
   281     public void updateHistoryTitle(ContentResolver cr, String uri, String title) {
   282         ContentValues values = new ContentValues();
   283         values.put(History.TITLE, title);
   285         cr.update(mHistoryUriWithProfile,
   286                   values,
   287                   History.URL + " = ?",
   288                   new String[] { uri });
   289     }
   291     @Override
   292     public void updateHistoryEntry(ContentResolver cr, String uri, String title,
   293                                    long date, int visits) {
   294         int oldVisits = 0;
   295         Cursor cursor = null;
   296         try {
   297             cursor = cr.query(mHistoryUriWithProfile,
   298                               new String[] { History.VISITS },
   299                               History.URL + " = ?",
   300                               new String[] { uri },
   301                               null);
   303             if (cursor.moveToFirst()) {
   304                 oldVisits = cursor.getInt(0);
   305             }
   306         } finally {
   307             if (cursor != null)
   308                 cursor.close();
   309         }
   311         ContentValues values = new ContentValues();
   312         values.put(History.DATE_LAST_VISITED, date);
   313         values.put(History.VISITS, oldVisits + visits);
   314         if (title != null) {
   315             values.put(History.TITLE, title);
   316         }
   318         cr.update(mHistoryUriWithProfile,
   319                   values,
   320                   History.URL + " = ?",
   321                   new String[] { uri });
   322     }
   324     @Override
   325     public Cursor getAllVisitedHistory(ContentResolver cr) {
   326         Cursor c = cr.query(mHistoryUriWithProfile,
   327                             new String[] { History.URL },
   328                             History.VISITS + " > 0",
   329                             null,
   330                             null);
   332         return new LocalDBCursor(c);
   333     }
   335     @Override
   336     public Cursor getRecentHistory(ContentResolver cr, int limit) {
   337         Cursor c = cr.query(combinedUriWithLimit(limit),
   338                             new String[] { Combined._ID,
   339                                            Combined.BOOKMARK_ID,
   340                                            Combined.HISTORY_ID,
   341                                            Combined.URL,
   342                                            Combined.TITLE,
   343                                            Combined.DISPLAY,
   344                                            Combined.DATE_LAST_VISITED,
   345                                            Combined.VISITS },
   346                             History.DATE_LAST_VISITED + " > 0",
   347                             null,
   348                             History.DATE_LAST_VISITED + " DESC");
   350         return new LocalDBCursor(c);
   351     }
   353     @Override
   354     public void expireHistory(ContentResolver cr, ExpirePriority priority) {
   355         Uri url = mHistoryExpireUriWithProfile;
   356         url = url.buildUpon().appendQueryParameter(BrowserContract.PARAM_EXPIRE_PRIORITY, priority.toString()).build();
   357         cr.delete(url, null, null);
   358     }
   360     @Override
   361     public void removeHistoryEntry(ContentResolver cr, int id) {
   362         cr.delete(mHistoryUriWithProfile,
   363                   History._ID + " = ?",
   364                   new String[] { String.valueOf(id) });
   365     }
   367     @Override
   368     public void removeHistoryEntry(ContentResolver cr, String url) {
   369         int deleted = cr.delete(mHistoryUriWithProfile,
   370                   History.URL + " = ?",
   371                   new String[] { url });
   372     }
   374     @Override
   375     public void clearHistory(ContentResolver cr) {
   376         cr.delete(mHistoryUriWithProfile, null, null);
   377     }
   379     @Override
   380     public Cursor getBookmarksInFolder(ContentResolver cr, long folderId) {
   381         Cursor c = null;
   382         boolean addDesktopFolder = false;
   384         // We always want to show mobile bookmarks in the root view.
   385         if (folderId == Bookmarks.FIXED_ROOT_ID) {
   386             folderId = getFolderIdFromGuid(cr, Bookmarks.MOBILE_FOLDER_GUID);
   388             // We'll add a fake "Desktop Bookmarks" folder to the root view if desktop 
   389             // bookmarks exist, so that the user can still access non-mobile bookmarks.
   390             addDesktopFolder = desktopBookmarksExist(cr);
   391         }
   393         if (folderId == Bookmarks.FAKE_DESKTOP_FOLDER_ID) {
   394             // Since the "Desktop Bookmarks" folder doesn't actually exist, we
   395             // just fake it by querying specifically certain known desktop folders.
   396             c = cr.query(mBookmarksUriWithProfile,
   397                          DEFAULT_BOOKMARK_COLUMNS,
   398                          Bookmarks.GUID + " = ? OR " +
   399                          Bookmarks.GUID + " = ? OR " +
   400                          Bookmarks.GUID + " = ?",
   401                          new String[] { Bookmarks.TOOLBAR_FOLDER_GUID,
   402                                         Bookmarks.MENU_FOLDER_GUID,
   403                                         Bookmarks.UNFILED_FOLDER_GUID },
   404                          null);
   405         } else {
   406             // Right now, we only support showing folder and bookmark type of
   407             // entries. We should add support for other types though (bug 737024)
   408             c = cr.query(mBookmarksUriWithProfile,
   409                          DEFAULT_BOOKMARK_COLUMNS,
   410                          Bookmarks.PARENT + " = ? AND " +
   411                          "(" + Bookmarks.TYPE + " = ? OR " +
   412                             "(" + Bookmarks.TYPE + " = ? AND " + Bookmarks.URL + " IS NOT NULL))",
   413                          new String[] { String.valueOf(folderId),
   414                                         String.valueOf(Bookmarks.TYPE_FOLDER),
   415                                         String.valueOf(Bookmarks.TYPE_BOOKMARK) },
   416                          null);
   417         }
   419         if (addDesktopFolder) {
   420             // Wrap cursor to add fake desktop bookmarks and reading list folders
   421             c = new SpecialFoldersCursorWrapper(c, addDesktopFolder);
   422         }
   424         return new LocalDBCursor(c);
   425     }
   427     @Override
   428     public Cursor getReadingList(ContentResolver cr) {
   429         return cr.query(mReadingListUriWithProfile,
   430                         ReadingListItems.DEFAULT_PROJECTION,
   431                         null,
   432                         null,
   433                         null);
   434     }
   437     // Returns true if any desktop bookmarks exist, which will be true if the user
   438     // has set up sync at one point, or done a profile migration from XUL fennec.
   439     private boolean desktopBookmarksExist(ContentResolver cr) {
   440         if (mDesktopBookmarksExist != null)
   441             return mDesktopBookmarksExist;
   443         Cursor c = null;
   444         int count = 0;
   445         try {
   446             // Check to see if there are any bookmarks in one of our three
   447             // fixed "Desktop Boomarks" folders.
   448             c = cr.query(bookmarksUriWithLimit(1),
   449                          new String[] { Bookmarks._ID },
   450                          Bookmarks.PARENT + " = ? OR " +
   451                          Bookmarks.PARENT + " = ? OR " +
   452                          Bookmarks.PARENT + " = ?",
   453                          new String[] { String.valueOf(getFolderIdFromGuid(cr, Bookmarks.TOOLBAR_FOLDER_GUID)),
   454                                         String.valueOf(getFolderIdFromGuid(cr, Bookmarks.MENU_FOLDER_GUID)),
   455                                         String.valueOf(getFolderIdFromGuid(cr, Bookmarks.UNFILED_FOLDER_GUID)) },
   456                          null);
   457             count = c.getCount();
   458         } finally {
   459             if (c != null)
   460                 c.close();
   461         }
   463         // Cache result for future queries
   464         mDesktopBookmarksExist = (count > 0);
   465         return mDesktopBookmarksExist;
   466     }
   468     @Override
   469     public int getReadingListCount(ContentResolver cr) {
   470         Cursor c = null;
   471         try {
   472             c = cr.query(mReadingListUriWithProfile,
   473                          new String[] { ReadingListItems._ID },
   474                          null,
   475                          null,
   476                          null);
   477             return c.getCount();
   478         } finally {
   479             if (c != null) {
   480                 c.close();
   481             }
   482         }
   483     }
   485     @Override
   486     public boolean isBookmark(ContentResolver cr, String uri) {
   487         // This method is about normal bookmarks, not the Reading List.
   488         Cursor c = null;
   489         try {
   490             c = cr.query(bookmarksUriWithLimit(1),
   491                          new String[] { Bookmarks._ID },
   492                          Bookmarks.URL + " = ? AND " +
   493                                  Bookmarks.PARENT + " != ? AND " +
   494                                  Bookmarks.PARENT + " != ?",
   495                          new String[] { uri,
   496                                  String.valueOf(Bookmarks.FIXED_READING_LIST_ID),
   497                                  String.valueOf(Bookmarks.FIXED_PINNED_LIST_ID) },
   498                          Bookmarks.URL);
   499             return c.getCount() > 0;
   500         } catch (NullPointerException e) {
   501             Log.e(LOGTAG, "NullPointerException in isBookmark");
   502         } finally {
   503             if (c != null)
   504                 c.close();
   505         }
   507         return false;
   508     }
   510     @Override
   511     public boolean isReadingListItem(ContentResolver cr, String uri) {
   512         Cursor c = null;
   513         try {
   514             c = cr.query(mReadingListUriWithProfile,
   515                          new String[] { ReadingListItems._ID },
   516                          ReadingListItems.URL + " = ? ",
   517                          new String[] { uri },
   518                          null);
   519             return c.getCount() > 0;
   520         } catch (NullPointerException e) {
   521             Log.e(LOGTAG, "NullPointerException in isReadingListItem");
   522         } finally {
   523             if (c != null)
   524                 c.close();
   525         }
   527         return false;
   528     }
   530     /**
   531      * For a given URI, we want to return a number of things:
   532      *
   533      * * Is this URI the URI of a bookmark?
   534      * * ... a reading list item?
   535      *
   536      * This will expand as necessary to eliminate multiple consecutive queries.
   537      */
   538     @Override
   539     public int getItemFlags(ContentResolver cr, String uri) {
   540         final Cursor c = cr.query(mFlagsUriWithProfile,
   541                                   null,
   542                                   null,
   543                                   new String[] { uri },
   544                                   null);
   545         if (c == null) {
   546             return 0;
   547         }
   549         try {
   550             // This should never fail: it returns a single `flags` row.
   551             c.moveToFirst();
   552             return Bookmarks.FLAG_SUCCESS | c.getInt(0);
   553         } finally {
   554             c.close();
   555         }
   556     }
   558     @Override
   559     public String getUrlForKeyword(ContentResolver cr, String keyword) {
   560         Cursor c = null;
   561         try {
   562             c = cr.query(mBookmarksUriWithProfile,
   563                          new String[] { Bookmarks.URL },
   564                          Bookmarks.KEYWORD + " = ?",
   565                          new String[] { keyword },
   566                          null);
   568             if (c.moveToFirst())
   569                 return c.getString(c.getColumnIndexOrThrow(Bookmarks.URL));
   570         } finally {
   571             if (c != null)
   572                 c.close();
   573         }
   575         return null;
   576     }
   578     private synchronized long getFolderIdFromGuid(ContentResolver cr, String guid) {
   579         if (mFolderIdMap.containsKey(guid))
   580             return mFolderIdMap.get(guid);
   582         long folderId = -1;
   583         Cursor c = null;
   585         try {
   586             c = cr.query(mBookmarksUriWithProfile,
   587                          new String[] { Bookmarks._ID },
   588                          Bookmarks.GUID + " = ?",
   589                          new String[] { guid },
   590                          null);
   592             if (c.moveToFirst())
   593                 folderId = c.getLong(c.getColumnIndexOrThrow(Bookmarks._ID));
   594         } finally {
   595             if (c != null)
   596                 c.close();
   597         }
   599         mFolderIdMap.put(guid, folderId);
   600         return folderId;
   601     }
   603     /**
   604      * Find parents of records that match the provided criteria, and bump their
   605      * modified timestamp.
   606      */
   607     protected void bumpParents(ContentResolver cr, String param, String value) {
   608         ContentValues values = new ContentValues();
   609         values.put(Bookmarks.DATE_MODIFIED, System.currentTimeMillis());
   611         String where  = param + " = ?";
   612         String[] args = new String[] { value };
   613         int updated  = cr.update(mParentsUriWithProfile, values, where, args);
   614         debug("Updated " + updated + " rows to new modified time.");
   615     }
   617     private void addBookmarkItem(ContentResolver cr, String title, String uri, long folderId) {
   618         final long now = System.currentTimeMillis();
   619         ContentValues values = new ContentValues();
   620         values.put(Browser.BookmarkColumns.TITLE, title);
   621         values.put(Bookmarks.URL, uri);
   622         values.put(Bookmarks.PARENT, folderId);
   623         values.put(Bookmarks.DATE_MODIFIED, now);
   625         // Get the page's favicon ID from the history table
   626         Cursor c = null;
   627         try {
   628             c = cr.query(mHistoryUriWithProfile,
   629                          new String[] { History.FAVICON_ID },
   630                          History.URL + " = ?",
   631                          new String[] { uri },
   632                          null);
   634             if (c.moveToFirst()) {
   635                 int columnIndex = c.getColumnIndexOrThrow(History.FAVICON_ID);
   636                 if (!c.isNull(columnIndex))
   637                     values.put(Bookmarks.FAVICON_ID, c.getLong(columnIndex));
   638             }
   639         } finally {
   640             if (c != null)
   641                 c.close();
   642         }
   644         // Restore deleted record if possible
   645         values.put(Bookmarks.IS_DELETED, 0);
   647         final Uri bookmarksWithInsert = mBookmarksUriWithProfile.buildUpon()
   648                                           .appendQueryParameter(BrowserContract.PARAM_INSERT_IF_NEEDED, "true")
   649                                           .build();
   650         cr.update(bookmarksWithInsert,
   651                   values,
   652                   Bookmarks.URL + " = ? AND " +
   653                   Bookmarks.PARENT + " = " + folderId,
   654                   new String[] { uri });
   656         // Bump parent modified time using its ID.
   657         debug("Bumping parent modified time for addition to: " + folderId);
   658         final String where  = Bookmarks._ID + " = ?";
   659         final String[] args = new String[] { String.valueOf(folderId) };
   661         ContentValues bumped = new ContentValues();
   662         bumped.put(Bookmarks.DATE_MODIFIED, now);
   664         final int updated = cr.update(mBookmarksUriWithProfile, bumped, where, args);
   665         debug("Updated " + updated + " rows to new modified time.");
   666     }
   668     @Override
   669     public void addBookmark(ContentResolver cr, String title, String uri) {
   670         long folderId = getFolderIdFromGuid(cr, Bookmarks.MOBILE_FOLDER_GUID);
   671         addBookmarkItem(cr, title, uri, folderId);
   672     }
   674     @Override
   675     public void removeBookmark(ContentResolver cr, int id) {
   676         Uri contentUri = mBookmarksUriWithProfile;
   678         // Do this now so that the item still exists!
   679         final String idString = String.valueOf(id);
   680         bumpParents(cr, Bookmarks._ID, idString);
   682         final String[] idArgs = new String[] { idString };
   683         final String idEquals = Bookmarks._ID + " = ?";
   684         cr.delete(contentUri, idEquals, idArgs);
   685     }
   687     @Override
   688     public void removeBookmarksWithURL(ContentResolver cr, String uri) {
   689         Uri contentUri = mBookmarksUriWithProfile;
   691         // Do this now so that the items still exist!
   692         bumpParents(cr, Bookmarks.URL, uri);
   694         // Toggling bookmark on an URL should not affect the items in the reading list or pinned sites.
   695         final String[] urlArgs = new String[] { uri, String.valueOf(Bookmarks.FIXED_READING_LIST_ID), String.valueOf(Bookmarks.FIXED_PINNED_LIST_ID) };
   696         final String urlEquals = Bookmarks.URL + " = ? AND " + Bookmarks.PARENT + " != ? AND " + Bookmarks.PARENT + " != ? ";
   698         cr.delete(contentUri, urlEquals, urlArgs);
   699     }
   701     @Override
   702     public void addReadingListItem(ContentResolver cr, ContentValues values) {
   703         // Check that required fields are present.
   704         for (String field: ReadingListItems.REQUIRED_FIELDS) {
   705             if (!values.containsKey(field)) {
   706                 throw new IllegalArgumentException("Missing required field for reading list item: " + field);
   707             }
   708         }
   710         // Clear delete flag if necessary
   711         values.put(ReadingListItems.IS_DELETED, 0);
   713         // Restore deleted record if possible
   714         final Uri insertUri = mReadingListUriWithProfile
   715                               .buildUpon()
   716                               .appendQueryParameter(BrowserContract.PARAM_INSERT_IF_NEEDED, "true")
   717                               .build();
   719         final int updated = cr.update(insertUri,
   720                                       values,
   721                                       ReadingListItems.URL + " = ? ",
   722                                       new String[] { values.getAsString(ReadingListItems.URL) });
   724         debug("Updated " + updated + " rows to new modified time.");
   725     }
   727     @Override
   728     public void removeReadingListItemWithURL(ContentResolver cr, String uri) {
   729         cr.delete(mReadingListUriWithProfile, ReadingListItems.URL + " = ? ", new String[] { uri });
   730     }
   732     @Override
   733     public void removeReadingListItem(ContentResolver cr, int id) {
   734         cr.delete(mReadingListUriWithProfile, ReadingListItems._ID + " = ? ", new String[] { String.valueOf(id) });
   735     }
   737     @Override
   738     public void registerBookmarkObserver(ContentResolver cr, ContentObserver observer) {
   739         cr.registerContentObserver(mBookmarksUriWithProfile, false, observer);
   740     }
   742     @Override
   743     public void registerHistoryObserver(ContentResolver cr, ContentObserver observer) {
   744         cr.registerContentObserver(mHistoryUriWithProfile, false, observer);
   745     }
   747     @Override
   748     public void updateBookmark(ContentResolver cr, int id, String uri, String title, String keyword) {
   749         ContentValues values = new ContentValues();
   750         values.put(Browser.BookmarkColumns.TITLE, title);
   751         values.put(Bookmarks.URL, uri);
   752         values.put(Bookmarks.KEYWORD, keyword);
   753         values.put(Bookmarks.DATE_MODIFIED, System.currentTimeMillis());
   755         cr.update(mBookmarksUriWithProfile,
   756                   values,
   757                   Bookmarks._ID + " = ?",
   758                   new String[] { String.valueOf(id) });
   759     }
   761     /**
   762      * Get the favicon from the database, if any, associated with the given favicon URL. (That is,
   763      * the URL of the actual favicon image, not the URL of the page with which the favicon is associated.)
   764      * @param cr The ContentResolver to use.
   765      * @param faviconURL The URL of the favicon to fetch from the database.
   766      * @return The decoded Bitmap from the database, if any. null if none is stored.
   767      */
   768     @Override
   769     public LoadFaviconResult getFaviconForUrl(ContentResolver cr, String faviconURL) {
   770         Cursor c = null;
   771         byte[] b = null;
   773         try {
   774             c = cr.query(mFaviconsUriWithProfile,
   775                          new String[] { Favicons.DATA },
   776                          Favicons.URL + " = ? AND " + Favicons.DATA + " IS NOT NULL",
   777                          new String[] { faviconURL },
   778                          null);
   780             if (!c.moveToFirst()) {
   781                 return null;
   782             }
   784             final int faviconIndex = c.getColumnIndexOrThrow(Favicons.DATA);
   785             b = c.getBlob(faviconIndex);
   786         } finally {
   787             if (c != null) {
   788                 c.close();
   789             }
   790         }
   792         if (b == null) {
   793             return null;
   794         }
   796         return FaviconDecoder.decodeFavicon(b);
   797     }
   799     @Override
   800     public String getFaviconUrlForHistoryUrl(ContentResolver cr, String uri) {
   801         Cursor c = null;
   803         try {
   804             c = cr.query(mHistoryUriWithProfile,
   805                          new String[] { History.FAVICON_URL },
   806                          Combined.URL + " = ?",
   807                          new String[] { uri },
   808                          null);
   810             if (c.moveToFirst())
   811                 return c.getString(c.getColumnIndexOrThrow(History.FAVICON_URL));
   812         } finally {
   813             if (c != null)
   814                 c.close();
   815         }
   817         return null;
   818     }
   820     @Override
   821     public void updateFaviconForUrl(ContentResolver cr, String pageUri,
   822             byte[] encodedFavicon, String faviconUri) {
   823         ContentValues values = new ContentValues();
   824         values.put(Favicons.URL, faviconUri);
   825         values.put(Favicons.PAGE_URL, pageUri);
   826         values.put(Favicons.DATA, encodedFavicon);
   828         // Update or insert
   829         Uri faviconsUri = getAllFaviconsUri().buildUpon().
   830                 appendQueryParameter(BrowserContract.PARAM_INSERT_IF_NEEDED, "true").build();
   832         cr.update(faviconsUri,
   833                   values,
   834                   Favicons.URL + " = ?",
   835                   new String[] { faviconUri });
   836     }
   838     @Override
   839     public void updateThumbnailForUrl(ContentResolver cr, String uri,
   840             BitmapDrawable thumbnail) {
   841         Bitmap bitmap = thumbnail.getBitmap();
   843         byte[] data = null;
   844         ByteArrayOutputStream stream = new ByteArrayOutputStream();
   845         if (bitmap.compress(Bitmap.CompressFormat.PNG, 0, stream)) {
   846             data = stream.toByteArray();
   847         } else {
   848             Log.w(LOGTAG, "Favicon compression failed.");
   849         }
   851         ContentValues values = new ContentValues();
   852         values.put(Thumbnails.DATA, data);
   853         values.put(Thumbnails.URL, uri);
   855         Uri thumbnailsUri = mThumbnailsUriWithProfile.buildUpon().
   856                 appendQueryParameter(BrowserContract.PARAM_INSERT_IF_NEEDED, "true").build();
   857         cr.update(thumbnailsUri,
   858                   values,
   859                   Thumbnails.URL + " = ?",
   860                   new String[] { uri });
   861     }
   863     @Override
   864     public byte[] getThumbnailForUrl(ContentResolver cr, String uri) {
   865         Cursor c = null;
   866         byte[] b = null;
   867         try {
   868             c = cr.query(mThumbnailsUriWithProfile,
   869                          new String[]{ Thumbnails.DATA },
   870                          Thumbnails.URL + " = ? AND " + Thumbnails.DATA + " IS NOT NULL",
   871                          new String[]{ uri },
   872                          null);
   874             if (!c.moveToFirst()) {
   875                 return null;
   876             }
   878             int thumbnailIndex = c.getColumnIndexOrThrow(Thumbnails.DATA);
   879             b = c.getBlob(thumbnailIndex);
   880         } finally {
   881             if (c != null) {
   882                 c.close();
   883             }
   884         }
   886         return b;
   887     }
   889     /**
   890      * Query for non-null thumbnails matching the provided <code>urls</code>.
   891      * The returned cursor will have no more than, but possibly fewer than,
   892      * the requested number of thumbnails.
   893      *
   894      * Returns null if the provided list of URLs is empty or null.
   895      */
   896     @Override
   897     public Cursor getThumbnailsForUrls(ContentResolver cr, List<String> urls) {
   898         if (urls == null) {
   899             return null;
   900         }
   902         int urlCount = urls.size();
   903         if (urlCount == 0) {
   904             return null;
   905         }
   907         // Don't match against null thumbnails.
   908         StringBuilder selection = new StringBuilder(
   909                 Thumbnails.DATA + " IS NOT NULL AND " +
   910                 Thumbnails.URL + " IN ("
   911         );
   913         // Compute a (?, ?, ?) sequence to match the provided URLs.
   914         int i = 1;
   915         while (i++ < urlCount) {
   916             selection.append("?, ");
   917         }
   918         selection.append("?)");
   920         String[] selectionArgs = urls.toArray(new String[urlCount]);
   922         return cr.query(mThumbnailsUriWithProfile,
   923                         new String[] { Thumbnails.URL, Thumbnails.DATA },
   924                         selection.toString(),
   925                         selectionArgs,
   926                         null);
   927     }
   929     @Override
   930     public void removeThumbnails(ContentResolver cr) {
   931         cr.delete(mThumbnailsUriWithProfile, null, null);
   932     }
   934     // Utility function for updating existing history using batch operations
   935     public void updateHistoryInBatch(ContentResolver cr,
   936                                      Collection<ContentProviderOperation> operations,
   937                                      String url, String title,
   938                                      long date, int visits) {
   939         Cursor cursor = null;
   941         try {
   942             final String[] projection = new String[] {
   943                 History._ID,
   944                 History.VISITS,
   945                 History.DATE_LAST_VISITED
   946             };
   948             // We need to get the old visit count.
   949             cursor = cr.query(getAllHistoryUri(),
   950                               projection,
   951                               History.URL + " = ?",
   952                               new String[] { url },
   953                               null);
   955             ContentValues values = new ContentValues();
   957             // Restore deleted record if possible
   958             values.put(History.IS_DELETED, 0);
   960             if (cursor.moveToFirst()) {
   961                 int visitsCol = cursor.getColumnIndexOrThrow(History.VISITS);
   962                 int dateCol = cursor.getColumnIndexOrThrow(History.DATE_LAST_VISITED);
   963                 int oldVisits = cursor.getInt(visitsCol);
   964                 long oldDate = cursor.getLong(dateCol);
   965                 values.put(History.VISITS, oldVisits + visits);
   966                 // Only update last visited if newer.
   967                 if (date > oldDate) {
   968                     values.put(History.DATE_LAST_VISITED, date);
   969                 }
   970             } else {
   971                 values.put(History.VISITS, visits);
   972                 values.put(History.DATE_LAST_VISITED, date);
   973             }
   974             if (title != null) {
   975                 values.put(History.TITLE, title);
   976             }
   977             values.put(History.URL, url);
   979             Uri historyUri = getAllHistoryUri().buildUpon().
   980                 appendQueryParameter(BrowserContract.PARAM_INSERT_IF_NEEDED, "true").build();
   982             // Update or insert
   983             ContentProviderOperation.Builder builder =
   984                 ContentProviderOperation.newUpdate(historyUri);
   985             builder.withSelection(History.URL + " = ?", new String[] { url });
   986             builder.withValues(values);
   988             // Queue the operation
   989             operations.add(builder.build());
   990         } finally {
   991             if (cursor != null)
   992                 cursor.close();
   993         }
   994     }
   996     public void updateBookmarkInBatch(ContentResolver cr,
   997                                       Collection<ContentProviderOperation> operations,
   998                                       String url, String title, String guid,
   999                                       long parent, long added,
  1000                                       long modified, long position,
  1001                                       String keyword, int type) {
  1002         ContentValues values = new ContentValues();
  1003         if (title == null && url != null) {
  1004             title = url;
  1006         if (title != null) {
  1007             values.put(Bookmarks.TITLE, title);
  1009         if (url != null) {
  1010             values.put(Bookmarks.URL, url);
  1012         if (guid != null) {
  1013             values.put(SyncColumns.GUID, guid);
  1015         if (keyword != null) {
  1016             values.put(Bookmarks.KEYWORD, keyword);
  1018         if (added > 0) {
  1019             values.put(SyncColumns.DATE_CREATED, added);
  1021         if (modified > 0) {
  1022             values.put(SyncColumns.DATE_MODIFIED, modified);
  1024         values.put(Bookmarks.POSITION, position);
  1025         // Restore deleted record if possible
  1026         values.put(Bookmarks.IS_DELETED, 0);
  1028         // This assumes no "real" folder has a negative ID. Only
  1029         // things like the reading list folder do.
  1030         if (parent < 0) {
  1031             parent = getFolderIdFromGuid(cr, Bookmarks.MOBILE_FOLDER_GUID);
  1033         values.put(Bookmarks.PARENT, parent);
  1034         values.put(Bookmarks.TYPE, type);
  1036         Uri bookmarkUri = getAllBookmarksUri().buildUpon().
  1037             appendQueryParameter(BrowserContract.PARAM_INSERT_IF_NEEDED, "true").build();
  1038         // Update or insert
  1039         ContentProviderOperation.Builder builder =
  1040             ContentProviderOperation.newUpdate(bookmarkUri);
  1041         if (url != null) {
  1042             // Bookmarks are defined by their URL and Folder.
  1043             builder.withSelection(Bookmarks.URL + " = ? AND "
  1044                                   + Bookmarks.PARENT + " = ? AND "
  1045                                   + Bookmarks.PARENT + " != ?",
  1046                                   new String[] { url,
  1047                                                  Long.toString(parent),
  1048                                                  String.valueOf(Bookmarks.FIXED_READING_LIST_ID)
  1049                                   });
  1050         } else if (title != null) {
  1051             // Or their title and parent folder. (Folders!)
  1052             builder.withSelection(Bookmarks.TITLE + " = ? AND "
  1053                                   + Bookmarks.PARENT + " = ? AND "
  1054                                   + Bookmarks.PARENT + " != ?",
  1055                                   new String[] { title,
  1056                                                  Long.toString(parent),
  1057                                                  String.valueOf(Bookmarks.FIXED_READING_LIST_ID)
  1058                                   });
  1059         } else if (type == Bookmarks.TYPE_SEPARATOR) {
  1060             // Or their their position (seperators)
  1061             builder.withSelection(Bookmarks.POSITION + " = ? AND "
  1062                                   + Bookmarks.PARENT + " = ? AND "
  1063                                   + Bookmarks.PARENT + " != ?",
  1064                                   new String[] { Long.toString(position),
  1065                                                  Long.toString(parent),
  1066                                                  String.valueOf(Bookmarks.FIXED_READING_LIST_ID)
  1067                                   });
  1068         } else {
  1069             Log.e(LOGTAG, "Bookmark entry without url or title and not a seperator, not added.");
  1071         builder.withValues(values);
  1073         // Queue the operation
  1074         operations.add(builder.build());
  1077     public void updateFaviconInBatch(ContentResolver cr,
  1078                                      Collection<ContentProviderOperation> operations,
  1079                                      String url, String faviconUrl,
  1080                                      String faviconGuid, byte[] data) {
  1081         ContentValues values = new ContentValues();
  1082         values.put(Favicons.DATA, data);
  1083         values.put(Favicons.PAGE_URL, url);
  1084         if (faviconUrl != null) {
  1085             values.put(Favicons.URL, faviconUrl);
  1088         // Update or insert
  1089         Uri faviconsUri = getAllFaviconsUri().buildUpon().
  1090             appendQueryParameter(BrowserContract.PARAM_INSERT_IF_NEEDED, "true").build();
  1091         // Update or insert
  1092         ContentProviderOperation.Builder builder =
  1093             ContentProviderOperation.newUpdate(faviconsUri);
  1094         builder.withValues(values);
  1095         builder.withSelection(Favicons.PAGE_URL + " = ?", new String[] { url });
  1096         // Queue the operation
  1097         operations.add(builder.build());
  1100     // This wrapper adds a fake "Desktop Bookmarks" folder entry to the
  1101     // beginning of the cursor's data set.
  1102     private class SpecialFoldersCursorWrapper extends CursorWrapper {
  1103         private int mIndexOffset;
  1105         private int mDesktopBookmarksIndex = -1;
  1107         private boolean mAtDesktopBookmarksPosition = false;
  1109         public SpecialFoldersCursorWrapper(Cursor c, boolean showDesktopBookmarks) {
  1110             super(c);
  1112             mIndexOffset = 0;
  1114             if (showDesktopBookmarks) {
  1115                 mDesktopBookmarksIndex = mIndexOffset;
  1116                 mIndexOffset++;
  1120         @Override
  1121         public int getCount() {
  1122             return super.getCount() + mIndexOffset;
  1125         @Override
  1126         public boolean moveToPosition(int position) {
  1127             mAtDesktopBookmarksPosition = (mDesktopBookmarksIndex == position);
  1129             if (mAtDesktopBookmarksPosition)
  1130                 return true;
  1132             return super.moveToPosition(position - mIndexOffset);
  1135         @Override
  1136         public long getLong(int columnIndex) {
  1137             if (!mAtDesktopBookmarksPosition)
  1138                 return super.getLong(columnIndex);
  1140             if (columnIndex == getColumnIndex(Bookmarks.PARENT)) {
  1141                 return Bookmarks.FIXED_ROOT_ID;
  1144             return -1;
  1147         @Override
  1148         public int getInt(int columnIndex) {
  1149             if (!mAtDesktopBookmarksPosition)
  1150                 return super.getInt(columnIndex);
  1152             if (columnIndex == getColumnIndex(Bookmarks._ID) && mAtDesktopBookmarksPosition)
  1153                     return Bookmarks.FAKE_DESKTOP_FOLDER_ID;
  1155             if (columnIndex == getColumnIndex(Bookmarks.TYPE))
  1156                 return Bookmarks.TYPE_FOLDER;
  1158             return -1;
  1161         @Override
  1162         public String getString(int columnIndex) {
  1163             if (!mAtDesktopBookmarksPosition)
  1164                 return super.getString(columnIndex);
  1166             if (columnIndex == getColumnIndex(Bookmarks.GUID) && mAtDesktopBookmarksPosition)
  1167                     return Bookmarks.FAKE_DESKTOP_FOLDER_GUID;
  1169             return "";
  1173     private static class LocalDBCursor extends CursorWrapper {
  1174         public LocalDBCursor(Cursor c) {
  1175             super(c);
  1178         private String translateColumnName(String columnName) {
  1179             if (columnName.equals(BrowserDB.URLColumns.URL)) {
  1180                 columnName = URLColumns.URL;
  1181             } else if (columnName.equals(BrowserDB.URLColumns.TITLE)) {
  1182                 columnName = URLColumns.TITLE;
  1183             } else if (columnName.equals(BrowserDB.URLColumns.FAVICON)) {
  1184                 columnName = FaviconColumns.FAVICON;
  1185             } else if (columnName.equals(BrowserDB.URLColumns.DATE_LAST_VISITED)) {
  1186                 columnName = History.DATE_LAST_VISITED;
  1187             } else if (columnName.equals(BrowserDB.URLColumns.VISITS)) {
  1188                 columnName = History.VISITS;
  1191             return columnName;
  1194         @Override
  1195         public int getColumnIndex(String columnName) {
  1196             return super.getColumnIndex(translateColumnName(columnName));
  1199         @Override
  1200         public int getColumnIndexOrThrow(String columnName) {
  1201             return super.getColumnIndexOrThrow(translateColumnName(columnName));
  1206     @Override
  1207     public void pinSite(ContentResolver cr, String url, String title, int position) {
  1208         ContentValues values = new ContentValues();
  1209         final long now = System.currentTimeMillis();
  1210         values.put(Bookmarks.TITLE, title);
  1211         values.put(Bookmarks.URL, url);
  1212         values.put(Bookmarks.PARENT, Bookmarks.FIXED_PINNED_LIST_ID);
  1213         values.put(Bookmarks.DATE_MODIFIED, now);
  1214         values.put(Bookmarks.POSITION, position);
  1215         values.put(Bookmarks.IS_DELETED, 0);
  1217         // We do an update-and-replace here without deleting any existing pins for the given URL.
  1218         // That means if the user pins a URL, then edits another thumbnail to use the same URL,
  1219         // we'll end up with two pins for that site. This is the intended behavior, which
  1220         // incidentally saves us a delete query.
  1221         Uri uri = mBookmarksUriWithProfile.buildUpon()
  1222                 .appendQueryParameter(BrowserContract.PARAM_INSERT_IF_NEEDED, "true").build();
  1223         cr.update(uri,
  1224                   values,
  1225                   Bookmarks.POSITION + " = ? AND " +
  1226                   Bookmarks.PARENT + " = ?",
  1227                   new String[] { Integer.toString(position),
  1228                                  String.valueOf(Bookmarks.FIXED_PINNED_LIST_ID) });
  1231     @Override
  1232     public Cursor getPinnedSites(ContentResolver cr, int limit) {
  1233         return cr.query(bookmarksUriWithLimit(limit),
  1234                         new String[] { Bookmarks._ID,
  1235                                        Bookmarks.URL,
  1236                                        Bookmarks.TITLE,
  1237                                        Bookmarks.POSITION },
  1238                         Bookmarks.PARENT + " == ?",
  1239                         new String[] { String.valueOf(Bookmarks.FIXED_PINNED_LIST_ID) },
  1240                         Bookmarks.POSITION + " ASC");
  1243     @Override
  1244     public void unpinSite(ContentResolver cr, int position) {
  1245         cr.delete(mBookmarksUriWithProfile,
  1246                   Bookmarks.PARENT + " == ? AND " + Bookmarks.POSITION + " = ?",
  1247                   new String[] {
  1248                       String.valueOf(Bookmarks.FIXED_PINNED_LIST_ID),
  1249                       Integer.toString(position)
  1250                   });
  1253     @Override
  1254     public void unpinAllSites(ContentResolver cr) {
  1255         cr.delete(mBookmarksUriWithProfile,
  1256                   Bookmarks.PARENT + " == ?",
  1257                   new String[] {
  1258                       String.valueOf(Bookmarks.FIXED_PINNED_LIST_ID)
  1259                   });
  1262     @Override
  1263     public boolean isVisited(ContentResolver cr, String uri) {
  1264         int count = 0;
  1265         Cursor c = null;
  1267         try {
  1268             c = cr.query(historyUriWithLimit(1),
  1269                          new String[] { History._ID },
  1270                          History.URL + " = ?",
  1271                          new String[] { uri },
  1272                          History.URL);
  1273             count = c.getCount();
  1274         } catch (NullPointerException e) {
  1275             Log.e(LOGTAG, "NullPointerException in isVisited");
  1276         } finally {
  1277             if (c != null)
  1278                 c.close();
  1281         return (count > 0);
  1284     public Cursor getBookmarkForUrl(ContentResolver cr, String url) {
  1285         Cursor c = cr.query(bookmarksUriWithLimit(1),
  1286                             new String[] { Bookmarks._ID,
  1287                                            Bookmarks.URL,
  1288                                            Bookmarks.TITLE,
  1289                                            Bookmarks.KEYWORD },
  1290                             Bookmarks.URL + " = ?",
  1291                             new String[] { url },
  1292                             null);
  1294         if (c != null && c.getCount() == 0) {
  1295             c.close();
  1296             c = null;
  1299         return c;

mercurial