1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/mobile/android/base/db/LocalBrowserDB.java Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,1301 @@ 1.4 +/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- 1.5 + * This Source Code Form is subject to the terms of the Mozilla Public 1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.8 + 1.9 +package org.mozilla.gecko.db; 1.10 + 1.11 +import java.io.ByteArrayOutputStream; 1.12 +import java.util.Collection; 1.13 +import java.util.HashMap; 1.14 +import java.util.List; 1.15 + 1.16 +import org.mozilla.gecko.AboutPages; 1.17 +import org.mozilla.gecko.db.BrowserContract.Bookmarks; 1.18 +import org.mozilla.gecko.db.BrowserContract.Combined; 1.19 +import org.mozilla.gecko.db.BrowserContract.ExpirePriority; 1.20 +import org.mozilla.gecko.db.BrowserContract.FaviconColumns; 1.21 +import org.mozilla.gecko.db.BrowserContract.Favicons; 1.22 +import org.mozilla.gecko.db.BrowserContract.History; 1.23 +import org.mozilla.gecko.db.BrowserContract.ReadingListItems; 1.24 +import org.mozilla.gecko.db.BrowserContract.SyncColumns; 1.25 +import org.mozilla.gecko.db.BrowserContract.Thumbnails; 1.26 +import org.mozilla.gecko.db.BrowserContract.URLColumns; 1.27 +import org.mozilla.gecko.favicons.decoders.FaviconDecoder; 1.28 +import org.mozilla.gecko.favicons.decoders.LoadFaviconResult; 1.29 + 1.30 +import android.content.ContentProviderOperation; 1.31 +import android.content.ContentResolver; 1.32 +import android.content.ContentValues; 1.33 +import android.database.ContentObserver; 1.34 +import android.database.Cursor; 1.35 +import android.database.CursorWrapper; 1.36 +import android.graphics.Bitmap; 1.37 +import android.graphics.drawable.BitmapDrawable; 1.38 +import android.net.Uri; 1.39 +import android.provider.Browser; 1.40 +import android.text.TextUtils; 1.41 +import android.util.Log; 1.42 + 1.43 +public class LocalBrowserDB implements BrowserDB.BrowserDBIface { 1.44 + // Calculate these once, at initialization. isLoggable is too expensive to 1.45 + // have in-line in each log call. 1.46 + private static final String LOGTAG = "GeckoLocalBrowserDB"; 1.47 + private static boolean logDebug = Log.isLoggable(LOGTAG, Log.DEBUG); 1.48 + protected static void debug(String message) { 1.49 + if (logDebug) { 1.50 + Log.d(LOGTAG, message); 1.51 + } 1.52 + } 1.53 + 1.54 + private final String mProfile; 1.55 + 1.56 + // Map of folder GUIDs to IDs. Used for caching. 1.57 + private HashMap<String, Long> mFolderIdMap; 1.58 + 1.59 + // Use wrapped Boolean so that we can have a null state 1.60 + private Boolean mDesktopBookmarksExist; 1.61 + 1.62 + private final Uri mBookmarksUriWithProfile; 1.63 + private final Uri mParentsUriWithProfile; 1.64 + private final Uri mFlagsUriWithProfile; 1.65 + private final Uri mHistoryUriWithProfile; 1.66 + private final Uri mHistoryExpireUriWithProfile; 1.67 + private final Uri mCombinedUriWithProfile; 1.68 + private final Uri mDeletedHistoryUriWithProfile; 1.69 + private final Uri mUpdateHistoryUriWithProfile; 1.70 + private final Uri mFaviconsUriWithProfile; 1.71 + private final Uri mThumbnailsUriWithProfile; 1.72 + private final Uri mReadingListUriWithProfile; 1.73 + 1.74 + private static final String[] DEFAULT_BOOKMARK_COLUMNS = 1.75 + new String[] { Bookmarks._ID, 1.76 + Bookmarks.GUID, 1.77 + Bookmarks.URL, 1.78 + Bookmarks.TITLE, 1.79 + Bookmarks.TYPE, 1.80 + Bookmarks.PARENT }; 1.81 + 1.82 + public LocalBrowserDB(String profile) { 1.83 + mProfile = profile; 1.84 + mFolderIdMap = new HashMap<String, Long>(); 1.85 + mDesktopBookmarksExist = null; 1.86 + 1.87 + mBookmarksUriWithProfile = appendProfile(Bookmarks.CONTENT_URI); 1.88 + mParentsUriWithProfile = appendProfile(Bookmarks.PARENTS_CONTENT_URI); 1.89 + mFlagsUriWithProfile = appendProfile(Bookmarks.FLAGS_URI); 1.90 + mHistoryUriWithProfile = appendProfile(History.CONTENT_URI); 1.91 + mHistoryExpireUriWithProfile = appendProfile(History.CONTENT_OLD_URI); 1.92 + mCombinedUriWithProfile = appendProfile(Combined.CONTENT_URI); 1.93 + mFaviconsUriWithProfile = appendProfile(Favicons.CONTENT_URI); 1.94 + mThumbnailsUriWithProfile = appendProfile(Thumbnails.CONTENT_URI); 1.95 + mReadingListUriWithProfile = appendProfile(ReadingListItems.CONTENT_URI); 1.96 + 1.97 + mDeletedHistoryUriWithProfile = mHistoryUriWithProfile.buildUpon(). 1.98 + appendQueryParameter(BrowserContract.PARAM_SHOW_DELETED, "1").build(); 1.99 + 1.100 + mUpdateHistoryUriWithProfile = mHistoryUriWithProfile.buildUpon(). 1.101 + appendQueryParameter(BrowserContract.PARAM_INCREMENT_VISITS, "true"). 1.102 + appendQueryParameter(BrowserContract.PARAM_INSERT_IF_NEEDED, "true").build(); 1.103 + } 1.104 + 1.105 + // Invalidate cached data 1.106 + @Override 1.107 + public void invalidateCachedState() { 1.108 + mDesktopBookmarksExist = null; 1.109 + } 1.110 + 1.111 + private Uri historyUriWithLimit(int limit) { 1.112 + return mHistoryUriWithProfile.buildUpon().appendQueryParameter(BrowserContract.PARAM_LIMIT, 1.113 + String.valueOf(limit)).build(); 1.114 + } 1.115 + 1.116 + private Uri bookmarksUriWithLimit(int limit) { 1.117 + return mBookmarksUriWithProfile.buildUpon().appendQueryParameter(BrowserContract.PARAM_LIMIT, 1.118 + String.valueOf(limit)).build(); 1.119 + } 1.120 + 1.121 + private Uri combinedUriWithLimit(int limit) { 1.122 + return mCombinedUriWithProfile.buildUpon().appendQueryParameter(BrowserContract.PARAM_LIMIT, 1.123 + String.valueOf(limit)).build(); 1.124 + } 1.125 + 1.126 + private Uri appendProfile(Uri uri) { 1.127 + return uri.buildUpon().appendQueryParameter(BrowserContract.PARAM_PROFILE, mProfile).build(); 1.128 + } 1.129 + 1.130 + private Uri getAllBookmarksUri() { 1.131 + Uri.Builder uriBuilder = mBookmarksUriWithProfile.buildUpon() 1.132 + .appendQueryParameter(BrowserContract.PARAM_SHOW_DELETED, "1"); 1.133 + return uriBuilder.build(); 1.134 + } 1.135 + 1.136 + private Uri getAllHistoryUri() { 1.137 + Uri.Builder uriBuilder = mHistoryUriWithProfile.buildUpon() 1.138 + .appendQueryParameter(BrowserContract.PARAM_SHOW_DELETED, "1"); 1.139 + return uriBuilder.build(); 1.140 + } 1.141 + 1.142 + private Uri getAllFaviconsUri() { 1.143 + Uri.Builder uriBuilder = mFaviconsUriWithProfile.buildUpon() 1.144 + .appendQueryParameter(BrowserContract.PARAM_SHOW_DELETED, "1"); 1.145 + return uriBuilder.build(); 1.146 + } 1.147 + 1.148 + private Cursor filterAllSites(ContentResolver cr, String[] projection, CharSequence constraint, 1.149 + int limit, CharSequence urlFilter) { 1.150 + return filterAllSites(cr, projection, constraint, limit, urlFilter, "", null); 1.151 + } 1.152 + 1.153 + private Cursor filterAllSites(ContentResolver cr, String[] projection, CharSequence constraint, 1.154 + int limit, CharSequence urlFilter, String selection, String[] selectionArgs) { 1.155 + // The combined history/bookmarks selection queries for sites with a url or title containing 1.156 + // the constraint string(s), treating space-separated words as separate constraints 1.157 + if (!TextUtils.isEmpty(constraint)) { 1.158 + String[] constraintWords = constraint.toString().split(" "); 1.159 + // Only create a filter query with a maximum of 10 constraint words 1.160 + int constraintCount = Math.min(constraintWords.length, 10); 1.161 + for (int i = 0; i < constraintCount; i++) { 1.162 + selection = DBUtils.concatenateWhere(selection, "(" + Combined.URL + " LIKE ? OR " + 1.163 + Combined.TITLE + " LIKE ?)"); 1.164 + String constraintWord = "%" + constraintWords[i] + "%"; 1.165 + selectionArgs = DBUtils.appendSelectionArgs(selectionArgs, 1.166 + new String[] { constraintWord, constraintWord }); 1.167 + } 1.168 + } 1.169 + 1.170 + if (urlFilter != null) { 1.171 + selection = DBUtils.concatenateWhere(selection, "(" + Combined.URL + " NOT LIKE ?)"); 1.172 + selectionArgs = DBUtils.appendSelectionArgs(selectionArgs, new String[] { urlFilter.toString() }); 1.173 + } 1.174 + 1.175 + // Our version of frecency is computed by scaling the number of visits by a multiplier 1.176 + // that approximates Gaussian decay, based on how long ago the entry was last visited. 1.177 + // Since we're limited by the math we can do with sqlite, we're calculating this 1.178 + // approximation using the Cauchy distribution: multiplier = 15^2 / (age^2 + 15^2). 1.179 + // Using 15 as our scale parameter, we get a constant 15^2 = 225. Following this math, 1.180 + // frecencyScore = numVisits * max(1, 100 * 225 / (age*age + 225)). (See bug 704977) 1.181 + // We also give bookmarks an extra bonus boost by adding 100 points to their frecency score. 1.182 + final String sortOrder = BrowserContract.getFrecencySortOrder(true, false); 1.183 + 1.184 + Cursor c = cr.query(combinedUriWithLimit(limit), 1.185 + projection, 1.186 + selection, 1.187 + selectionArgs, 1.188 + sortOrder); 1.189 + 1.190 + return new LocalDBCursor(c); 1.191 + } 1.192 + 1.193 + @Override 1.194 + public int getCount(ContentResolver cr, String database) { 1.195 + int count = 0; 1.196 + String[] columns = null; 1.197 + String constraint = null; 1.198 + Uri uri = null; 1.199 + if ("history".equals(database)) { 1.200 + uri = mHistoryUriWithProfile; 1.201 + columns = new String[] { History._ID }; 1.202 + constraint = Combined.VISITS + " > 0"; 1.203 + } else if ("bookmarks".equals(database)) { 1.204 + uri = mBookmarksUriWithProfile; 1.205 + columns = new String[] { Bookmarks._ID }; 1.206 + // ignore folders, tags, keywords, separators, etc. 1.207 + constraint = Bookmarks.TYPE + " = " + Bookmarks.TYPE_BOOKMARK; 1.208 + } else if ("thumbnails".equals(database)) { 1.209 + uri = mThumbnailsUriWithProfile; 1.210 + columns = new String[] { Thumbnails._ID }; 1.211 + } else if ("favicons".equals(database)) { 1.212 + uri = mFaviconsUriWithProfile; 1.213 + columns = new String[] { Favicons._ID }; 1.214 + } 1.215 + if (uri != null) { 1.216 + Cursor cursor = null; 1.217 + 1.218 + try { 1.219 + cursor = cr.query(uri, columns, constraint, null, null); 1.220 + count = cursor.getCount(); 1.221 + } finally { 1.222 + if (cursor != null) 1.223 + cursor.close(); 1.224 + } 1.225 + } 1.226 + debug("Got count " + count + " for " + database); 1.227 + return count; 1.228 + } 1.229 + 1.230 + @Override 1.231 + public Cursor filter(ContentResolver cr, CharSequence constraint, int limit) { 1.232 + return filterAllSites(cr, 1.233 + new String[] { Combined._ID, 1.234 + Combined.URL, 1.235 + Combined.TITLE, 1.236 + Combined.DISPLAY, 1.237 + Combined.BOOKMARK_ID, 1.238 + Combined.HISTORY_ID }, 1.239 + constraint, 1.240 + limit, 1.241 + null); 1.242 + } 1.243 + 1.244 + @Override 1.245 + public Cursor getTopSites(ContentResolver cr, int limit) { 1.246 + // Filter out bookmarks that don't have real parents (e.g. pinned sites or reading list items) 1.247 + String selection = DBUtils.concatenateWhere("", Combined.URL + " NOT IN (SELECT " + 1.248 + Bookmarks.URL + " FROM bookmarks WHERE " + 1.249 + DBUtils.qualifyColumn("bookmarks", Bookmarks.PARENT) + " < ? AND " + 1.250 + DBUtils.qualifyColumn("bookmarks", Bookmarks.IS_DELETED) + " == 0)"); 1.251 + String[] selectionArgs = new String[] { String.valueOf(Bookmarks.FIXED_ROOT_ID) }; 1.252 + 1.253 + return filterAllSites(cr, 1.254 + new String[] { Combined._ID, 1.255 + Combined.URL, 1.256 + Combined.TITLE, 1.257 + Combined.DISPLAY, 1.258 + Combined.BOOKMARK_ID, 1.259 + Combined.HISTORY_ID }, 1.260 + "", 1.261 + limit, 1.262 + AboutPages.URL_FILTER, 1.263 + selection, 1.264 + selectionArgs); 1.265 + } 1.266 + 1.267 + @Override 1.268 + public void updateVisitedHistory(ContentResolver cr, String uri) { 1.269 + ContentValues values = new ContentValues(); 1.270 + 1.271 + values.put(History.URL, uri); 1.272 + values.put(History.DATE_LAST_VISITED, System.currentTimeMillis()); 1.273 + values.put(History.IS_DELETED, 0); 1.274 + 1.275 + // This will insert a new history entry if one for this URL 1.276 + // doesn't already exist 1.277 + cr.update(mUpdateHistoryUriWithProfile, 1.278 + values, 1.279 + History.URL + " = ?", 1.280 + new String[] { uri }); 1.281 + } 1.282 + 1.283 + @Override 1.284 + public void updateHistoryTitle(ContentResolver cr, String uri, String title) { 1.285 + ContentValues values = new ContentValues(); 1.286 + values.put(History.TITLE, title); 1.287 + 1.288 + cr.update(mHistoryUriWithProfile, 1.289 + values, 1.290 + History.URL + " = ?", 1.291 + new String[] { uri }); 1.292 + } 1.293 + 1.294 + @Override 1.295 + public void updateHistoryEntry(ContentResolver cr, String uri, String title, 1.296 + long date, int visits) { 1.297 + int oldVisits = 0; 1.298 + Cursor cursor = null; 1.299 + try { 1.300 + cursor = cr.query(mHistoryUriWithProfile, 1.301 + new String[] { History.VISITS }, 1.302 + History.URL + " = ?", 1.303 + new String[] { uri }, 1.304 + null); 1.305 + 1.306 + if (cursor.moveToFirst()) { 1.307 + oldVisits = cursor.getInt(0); 1.308 + } 1.309 + } finally { 1.310 + if (cursor != null) 1.311 + cursor.close(); 1.312 + } 1.313 + 1.314 + ContentValues values = new ContentValues(); 1.315 + values.put(History.DATE_LAST_VISITED, date); 1.316 + values.put(History.VISITS, oldVisits + visits); 1.317 + if (title != null) { 1.318 + values.put(History.TITLE, title); 1.319 + } 1.320 + 1.321 + cr.update(mHistoryUriWithProfile, 1.322 + values, 1.323 + History.URL + " = ?", 1.324 + new String[] { uri }); 1.325 + } 1.326 + 1.327 + @Override 1.328 + public Cursor getAllVisitedHistory(ContentResolver cr) { 1.329 + Cursor c = cr.query(mHistoryUriWithProfile, 1.330 + new String[] { History.URL }, 1.331 + History.VISITS + " > 0", 1.332 + null, 1.333 + null); 1.334 + 1.335 + return new LocalDBCursor(c); 1.336 + } 1.337 + 1.338 + @Override 1.339 + public Cursor getRecentHistory(ContentResolver cr, int limit) { 1.340 + Cursor c = cr.query(combinedUriWithLimit(limit), 1.341 + new String[] { Combined._ID, 1.342 + Combined.BOOKMARK_ID, 1.343 + Combined.HISTORY_ID, 1.344 + Combined.URL, 1.345 + Combined.TITLE, 1.346 + Combined.DISPLAY, 1.347 + Combined.DATE_LAST_VISITED, 1.348 + Combined.VISITS }, 1.349 + History.DATE_LAST_VISITED + " > 0", 1.350 + null, 1.351 + History.DATE_LAST_VISITED + " DESC"); 1.352 + 1.353 + return new LocalDBCursor(c); 1.354 + } 1.355 + 1.356 + @Override 1.357 + public void expireHistory(ContentResolver cr, ExpirePriority priority) { 1.358 + Uri url = mHistoryExpireUriWithProfile; 1.359 + url = url.buildUpon().appendQueryParameter(BrowserContract.PARAM_EXPIRE_PRIORITY, priority.toString()).build(); 1.360 + cr.delete(url, null, null); 1.361 + } 1.362 + 1.363 + @Override 1.364 + public void removeHistoryEntry(ContentResolver cr, int id) { 1.365 + cr.delete(mHistoryUriWithProfile, 1.366 + History._ID + " = ?", 1.367 + new String[] { String.valueOf(id) }); 1.368 + } 1.369 + 1.370 + @Override 1.371 + public void removeHistoryEntry(ContentResolver cr, String url) { 1.372 + int deleted = cr.delete(mHistoryUriWithProfile, 1.373 + History.URL + " = ?", 1.374 + new String[] { url }); 1.375 + } 1.376 + 1.377 + @Override 1.378 + public void clearHistory(ContentResolver cr) { 1.379 + cr.delete(mHistoryUriWithProfile, null, null); 1.380 + } 1.381 + 1.382 + @Override 1.383 + public Cursor getBookmarksInFolder(ContentResolver cr, long folderId) { 1.384 + Cursor c = null; 1.385 + boolean addDesktopFolder = false; 1.386 + 1.387 + // We always want to show mobile bookmarks in the root view. 1.388 + if (folderId == Bookmarks.FIXED_ROOT_ID) { 1.389 + folderId = getFolderIdFromGuid(cr, Bookmarks.MOBILE_FOLDER_GUID); 1.390 + 1.391 + // We'll add a fake "Desktop Bookmarks" folder to the root view if desktop 1.392 + // bookmarks exist, so that the user can still access non-mobile bookmarks. 1.393 + addDesktopFolder = desktopBookmarksExist(cr); 1.394 + } 1.395 + 1.396 + if (folderId == Bookmarks.FAKE_DESKTOP_FOLDER_ID) { 1.397 + // Since the "Desktop Bookmarks" folder doesn't actually exist, we 1.398 + // just fake it by querying specifically certain known desktop folders. 1.399 + c = cr.query(mBookmarksUriWithProfile, 1.400 + DEFAULT_BOOKMARK_COLUMNS, 1.401 + Bookmarks.GUID + " = ? OR " + 1.402 + Bookmarks.GUID + " = ? OR " + 1.403 + Bookmarks.GUID + " = ?", 1.404 + new String[] { Bookmarks.TOOLBAR_FOLDER_GUID, 1.405 + Bookmarks.MENU_FOLDER_GUID, 1.406 + Bookmarks.UNFILED_FOLDER_GUID }, 1.407 + null); 1.408 + } else { 1.409 + // Right now, we only support showing folder and bookmark type of 1.410 + // entries. We should add support for other types though (bug 737024) 1.411 + c = cr.query(mBookmarksUriWithProfile, 1.412 + DEFAULT_BOOKMARK_COLUMNS, 1.413 + Bookmarks.PARENT + " = ? AND " + 1.414 + "(" + Bookmarks.TYPE + " = ? OR " + 1.415 + "(" + Bookmarks.TYPE + " = ? AND " + Bookmarks.URL + " IS NOT NULL))", 1.416 + new String[] { String.valueOf(folderId), 1.417 + String.valueOf(Bookmarks.TYPE_FOLDER), 1.418 + String.valueOf(Bookmarks.TYPE_BOOKMARK) }, 1.419 + null); 1.420 + } 1.421 + 1.422 + if (addDesktopFolder) { 1.423 + // Wrap cursor to add fake desktop bookmarks and reading list folders 1.424 + c = new SpecialFoldersCursorWrapper(c, addDesktopFolder); 1.425 + } 1.426 + 1.427 + return new LocalDBCursor(c); 1.428 + } 1.429 + 1.430 + @Override 1.431 + public Cursor getReadingList(ContentResolver cr) { 1.432 + return cr.query(mReadingListUriWithProfile, 1.433 + ReadingListItems.DEFAULT_PROJECTION, 1.434 + null, 1.435 + null, 1.436 + null); 1.437 + } 1.438 + 1.439 + 1.440 + // Returns true if any desktop bookmarks exist, which will be true if the user 1.441 + // has set up sync at one point, or done a profile migration from XUL fennec. 1.442 + private boolean desktopBookmarksExist(ContentResolver cr) { 1.443 + if (mDesktopBookmarksExist != null) 1.444 + return mDesktopBookmarksExist; 1.445 + 1.446 + Cursor c = null; 1.447 + int count = 0; 1.448 + try { 1.449 + // Check to see if there are any bookmarks in one of our three 1.450 + // fixed "Desktop Boomarks" folders. 1.451 + c = cr.query(bookmarksUriWithLimit(1), 1.452 + new String[] { Bookmarks._ID }, 1.453 + Bookmarks.PARENT + " = ? OR " + 1.454 + Bookmarks.PARENT + " = ? OR " + 1.455 + Bookmarks.PARENT + " = ?", 1.456 + new String[] { String.valueOf(getFolderIdFromGuid(cr, Bookmarks.TOOLBAR_FOLDER_GUID)), 1.457 + String.valueOf(getFolderIdFromGuid(cr, Bookmarks.MENU_FOLDER_GUID)), 1.458 + String.valueOf(getFolderIdFromGuid(cr, Bookmarks.UNFILED_FOLDER_GUID)) }, 1.459 + null); 1.460 + count = c.getCount(); 1.461 + } finally { 1.462 + if (c != null) 1.463 + c.close(); 1.464 + } 1.465 + 1.466 + // Cache result for future queries 1.467 + mDesktopBookmarksExist = (count > 0); 1.468 + return mDesktopBookmarksExist; 1.469 + } 1.470 + 1.471 + @Override 1.472 + public int getReadingListCount(ContentResolver cr) { 1.473 + Cursor c = null; 1.474 + try { 1.475 + c = cr.query(mReadingListUriWithProfile, 1.476 + new String[] { ReadingListItems._ID }, 1.477 + null, 1.478 + null, 1.479 + null); 1.480 + return c.getCount(); 1.481 + } finally { 1.482 + if (c != null) { 1.483 + c.close(); 1.484 + } 1.485 + } 1.486 + } 1.487 + 1.488 + @Override 1.489 + public boolean isBookmark(ContentResolver cr, String uri) { 1.490 + // This method is about normal bookmarks, not the Reading List. 1.491 + Cursor c = null; 1.492 + try { 1.493 + c = cr.query(bookmarksUriWithLimit(1), 1.494 + new String[] { Bookmarks._ID }, 1.495 + Bookmarks.URL + " = ? AND " + 1.496 + Bookmarks.PARENT + " != ? AND " + 1.497 + Bookmarks.PARENT + " != ?", 1.498 + new String[] { uri, 1.499 + String.valueOf(Bookmarks.FIXED_READING_LIST_ID), 1.500 + String.valueOf(Bookmarks.FIXED_PINNED_LIST_ID) }, 1.501 + Bookmarks.URL); 1.502 + return c.getCount() > 0; 1.503 + } catch (NullPointerException e) { 1.504 + Log.e(LOGTAG, "NullPointerException in isBookmark"); 1.505 + } finally { 1.506 + if (c != null) 1.507 + c.close(); 1.508 + } 1.509 + 1.510 + return false; 1.511 + } 1.512 + 1.513 + @Override 1.514 + public boolean isReadingListItem(ContentResolver cr, String uri) { 1.515 + Cursor c = null; 1.516 + try { 1.517 + c = cr.query(mReadingListUriWithProfile, 1.518 + new String[] { ReadingListItems._ID }, 1.519 + ReadingListItems.URL + " = ? ", 1.520 + new String[] { uri }, 1.521 + null); 1.522 + return c.getCount() > 0; 1.523 + } catch (NullPointerException e) { 1.524 + Log.e(LOGTAG, "NullPointerException in isReadingListItem"); 1.525 + } finally { 1.526 + if (c != null) 1.527 + c.close(); 1.528 + } 1.529 + 1.530 + return false; 1.531 + } 1.532 + 1.533 + /** 1.534 + * For a given URI, we want to return a number of things: 1.535 + * 1.536 + * * Is this URI the URI of a bookmark? 1.537 + * * ... a reading list item? 1.538 + * 1.539 + * This will expand as necessary to eliminate multiple consecutive queries. 1.540 + */ 1.541 + @Override 1.542 + public int getItemFlags(ContentResolver cr, String uri) { 1.543 + final Cursor c = cr.query(mFlagsUriWithProfile, 1.544 + null, 1.545 + null, 1.546 + new String[] { uri }, 1.547 + null); 1.548 + if (c == null) { 1.549 + return 0; 1.550 + } 1.551 + 1.552 + try { 1.553 + // This should never fail: it returns a single `flags` row. 1.554 + c.moveToFirst(); 1.555 + return Bookmarks.FLAG_SUCCESS | c.getInt(0); 1.556 + } finally { 1.557 + c.close(); 1.558 + } 1.559 + } 1.560 + 1.561 + @Override 1.562 + public String getUrlForKeyword(ContentResolver cr, String keyword) { 1.563 + Cursor c = null; 1.564 + try { 1.565 + c = cr.query(mBookmarksUriWithProfile, 1.566 + new String[] { Bookmarks.URL }, 1.567 + Bookmarks.KEYWORD + " = ?", 1.568 + new String[] { keyword }, 1.569 + null); 1.570 + 1.571 + if (c.moveToFirst()) 1.572 + return c.getString(c.getColumnIndexOrThrow(Bookmarks.URL)); 1.573 + } finally { 1.574 + if (c != null) 1.575 + c.close(); 1.576 + } 1.577 + 1.578 + return null; 1.579 + } 1.580 + 1.581 + private synchronized long getFolderIdFromGuid(ContentResolver cr, String guid) { 1.582 + if (mFolderIdMap.containsKey(guid)) 1.583 + return mFolderIdMap.get(guid); 1.584 + 1.585 + long folderId = -1; 1.586 + Cursor c = null; 1.587 + 1.588 + try { 1.589 + c = cr.query(mBookmarksUriWithProfile, 1.590 + new String[] { Bookmarks._ID }, 1.591 + Bookmarks.GUID + " = ?", 1.592 + new String[] { guid }, 1.593 + null); 1.594 + 1.595 + if (c.moveToFirst()) 1.596 + folderId = c.getLong(c.getColumnIndexOrThrow(Bookmarks._ID)); 1.597 + } finally { 1.598 + if (c != null) 1.599 + c.close(); 1.600 + } 1.601 + 1.602 + mFolderIdMap.put(guid, folderId); 1.603 + return folderId; 1.604 + } 1.605 + 1.606 + /** 1.607 + * Find parents of records that match the provided criteria, and bump their 1.608 + * modified timestamp. 1.609 + */ 1.610 + protected void bumpParents(ContentResolver cr, String param, String value) { 1.611 + ContentValues values = new ContentValues(); 1.612 + values.put(Bookmarks.DATE_MODIFIED, System.currentTimeMillis()); 1.613 + 1.614 + String where = param + " = ?"; 1.615 + String[] args = new String[] { value }; 1.616 + int updated = cr.update(mParentsUriWithProfile, values, where, args); 1.617 + debug("Updated " + updated + " rows to new modified time."); 1.618 + } 1.619 + 1.620 + private void addBookmarkItem(ContentResolver cr, String title, String uri, long folderId) { 1.621 + final long now = System.currentTimeMillis(); 1.622 + ContentValues values = new ContentValues(); 1.623 + values.put(Browser.BookmarkColumns.TITLE, title); 1.624 + values.put(Bookmarks.URL, uri); 1.625 + values.put(Bookmarks.PARENT, folderId); 1.626 + values.put(Bookmarks.DATE_MODIFIED, now); 1.627 + 1.628 + // Get the page's favicon ID from the history table 1.629 + Cursor c = null; 1.630 + try { 1.631 + c = cr.query(mHistoryUriWithProfile, 1.632 + new String[] { History.FAVICON_ID }, 1.633 + History.URL + " = ?", 1.634 + new String[] { uri }, 1.635 + null); 1.636 + 1.637 + if (c.moveToFirst()) { 1.638 + int columnIndex = c.getColumnIndexOrThrow(History.FAVICON_ID); 1.639 + if (!c.isNull(columnIndex)) 1.640 + values.put(Bookmarks.FAVICON_ID, c.getLong(columnIndex)); 1.641 + } 1.642 + } finally { 1.643 + if (c != null) 1.644 + c.close(); 1.645 + } 1.646 + 1.647 + // Restore deleted record if possible 1.648 + values.put(Bookmarks.IS_DELETED, 0); 1.649 + 1.650 + final Uri bookmarksWithInsert = mBookmarksUriWithProfile.buildUpon() 1.651 + .appendQueryParameter(BrowserContract.PARAM_INSERT_IF_NEEDED, "true") 1.652 + .build(); 1.653 + cr.update(bookmarksWithInsert, 1.654 + values, 1.655 + Bookmarks.URL + " = ? AND " + 1.656 + Bookmarks.PARENT + " = " + folderId, 1.657 + new String[] { uri }); 1.658 + 1.659 + // Bump parent modified time using its ID. 1.660 + debug("Bumping parent modified time for addition to: " + folderId); 1.661 + final String where = Bookmarks._ID + " = ?"; 1.662 + final String[] args = new String[] { String.valueOf(folderId) }; 1.663 + 1.664 + ContentValues bumped = new ContentValues(); 1.665 + bumped.put(Bookmarks.DATE_MODIFIED, now); 1.666 + 1.667 + final int updated = cr.update(mBookmarksUriWithProfile, bumped, where, args); 1.668 + debug("Updated " + updated + " rows to new modified time."); 1.669 + } 1.670 + 1.671 + @Override 1.672 + public void addBookmark(ContentResolver cr, String title, String uri) { 1.673 + long folderId = getFolderIdFromGuid(cr, Bookmarks.MOBILE_FOLDER_GUID); 1.674 + addBookmarkItem(cr, title, uri, folderId); 1.675 + } 1.676 + 1.677 + @Override 1.678 + public void removeBookmark(ContentResolver cr, int id) { 1.679 + Uri contentUri = mBookmarksUriWithProfile; 1.680 + 1.681 + // Do this now so that the item still exists! 1.682 + final String idString = String.valueOf(id); 1.683 + bumpParents(cr, Bookmarks._ID, idString); 1.684 + 1.685 + final String[] idArgs = new String[] { idString }; 1.686 + final String idEquals = Bookmarks._ID + " = ?"; 1.687 + cr.delete(contentUri, idEquals, idArgs); 1.688 + } 1.689 + 1.690 + @Override 1.691 + public void removeBookmarksWithURL(ContentResolver cr, String uri) { 1.692 + Uri contentUri = mBookmarksUriWithProfile; 1.693 + 1.694 + // Do this now so that the items still exist! 1.695 + bumpParents(cr, Bookmarks.URL, uri); 1.696 + 1.697 + // Toggling bookmark on an URL should not affect the items in the reading list or pinned sites. 1.698 + final String[] urlArgs = new String[] { uri, String.valueOf(Bookmarks.FIXED_READING_LIST_ID), String.valueOf(Bookmarks.FIXED_PINNED_LIST_ID) }; 1.699 + final String urlEquals = Bookmarks.URL + " = ? AND " + Bookmarks.PARENT + " != ? AND " + Bookmarks.PARENT + " != ? "; 1.700 + 1.701 + cr.delete(contentUri, urlEquals, urlArgs); 1.702 + } 1.703 + 1.704 + @Override 1.705 + public void addReadingListItem(ContentResolver cr, ContentValues values) { 1.706 + // Check that required fields are present. 1.707 + for (String field: ReadingListItems.REQUIRED_FIELDS) { 1.708 + if (!values.containsKey(field)) { 1.709 + throw new IllegalArgumentException("Missing required field for reading list item: " + field); 1.710 + } 1.711 + } 1.712 + 1.713 + // Clear delete flag if necessary 1.714 + values.put(ReadingListItems.IS_DELETED, 0); 1.715 + 1.716 + // Restore deleted record if possible 1.717 + final Uri insertUri = mReadingListUriWithProfile 1.718 + .buildUpon() 1.719 + .appendQueryParameter(BrowserContract.PARAM_INSERT_IF_NEEDED, "true") 1.720 + .build(); 1.721 + 1.722 + final int updated = cr.update(insertUri, 1.723 + values, 1.724 + ReadingListItems.URL + " = ? ", 1.725 + new String[] { values.getAsString(ReadingListItems.URL) }); 1.726 + 1.727 + debug("Updated " + updated + " rows to new modified time."); 1.728 + } 1.729 + 1.730 + @Override 1.731 + public void removeReadingListItemWithURL(ContentResolver cr, String uri) { 1.732 + cr.delete(mReadingListUriWithProfile, ReadingListItems.URL + " = ? ", new String[] { uri }); 1.733 + } 1.734 + 1.735 + @Override 1.736 + public void removeReadingListItem(ContentResolver cr, int id) { 1.737 + cr.delete(mReadingListUriWithProfile, ReadingListItems._ID + " = ? ", new String[] { String.valueOf(id) }); 1.738 + } 1.739 + 1.740 + @Override 1.741 + public void registerBookmarkObserver(ContentResolver cr, ContentObserver observer) { 1.742 + cr.registerContentObserver(mBookmarksUriWithProfile, false, observer); 1.743 + } 1.744 + 1.745 + @Override 1.746 + public void registerHistoryObserver(ContentResolver cr, ContentObserver observer) { 1.747 + cr.registerContentObserver(mHistoryUriWithProfile, false, observer); 1.748 + } 1.749 + 1.750 + @Override 1.751 + public void updateBookmark(ContentResolver cr, int id, String uri, String title, String keyword) { 1.752 + ContentValues values = new ContentValues(); 1.753 + values.put(Browser.BookmarkColumns.TITLE, title); 1.754 + values.put(Bookmarks.URL, uri); 1.755 + values.put(Bookmarks.KEYWORD, keyword); 1.756 + values.put(Bookmarks.DATE_MODIFIED, System.currentTimeMillis()); 1.757 + 1.758 + cr.update(mBookmarksUriWithProfile, 1.759 + values, 1.760 + Bookmarks._ID + " = ?", 1.761 + new String[] { String.valueOf(id) }); 1.762 + } 1.763 + 1.764 + /** 1.765 + * Get the favicon from the database, if any, associated with the given favicon URL. (That is, 1.766 + * the URL of the actual favicon image, not the URL of the page with which the favicon is associated.) 1.767 + * @param cr The ContentResolver to use. 1.768 + * @param faviconURL The URL of the favicon to fetch from the database. 1.769 + * @return The decoded Bitmap from the database, if any. null if none is stored. 1.770 + */ 1.771 + @Override 1.772 + public LoadFaviconResult getFaviconForUrl(ContentResolver cr, String faviconURL) { 1.773 + Cursor c = null; 1.774 + byte[] b = null; 1.775 + 1.776 + try { 1.777 + c = cr.query(mFaviconsUriWithProfile, 1.778 + new String[] { Favicons.DATA }, 1.779 + Favicons.URL + " = ? AND " + Favicons.DATA + " IS NOT NULL", 1.780 + new String[] { faviconURL }, 1.781 + null); 1.782 + 1.783 + if (!c.moveToFirst()) { 1.784 + return null; 1.785 + } 1.786 + 1.787 + final int faviconIndex = c.getColumnIndexOrThrow(Favicons.DATA); 1.788 + b = c.getBlob(faviconIndex); 1.789 + } finally { 1.790 + if (c != null) { 1.791 + c.close(); 1.792 + } 1.793 + } 1.794 + 1.795 + if (b == null) { 1.796 + return null; 1.797 + } 1.798 + 1.799 + return FaviconDecoder.decodeFavicon(b); 1.800 + } 1.801 + 1.802 + @Override 1.803 + public String getFaviconUrlForHistoryUrl(ContentResolver cr, String uri) { 1.804 + Cursor c = null; 1.805 + 1.806 + try { 1.807 + c = cr.query(mHistoryUriWithProfile, 1.808 + new String[] { History.FAVICON_URL }, 1.809 + Combined.URL + " = ?", 1.810 + new String[] { uri }, 1.811 + null); 1.812 + 1.813 + if (c.moveToFirst()) 1.814 + return c.getString(c.getColumnIndexOrThrow(History.FAVICON_URL)); 1.815 + } finally { 1.816 + if (c != null) 1.817 + c.close(); 1.818 + } 1.819 + 1.820 + return null; 1.821 + } 1.822 + 1.823 + @Override 1.824 + public void updateFaviconForUrl(ContentResolver cr, String pageUri, 1.825 + byte[] encodedFavicon, String faviconUri) { 1.826 + ContentValues values = new ContentValues(); 1.827 + values.put(Favicons.URL, faviconUri); 1.828 + values.put(Favicons.PAGE_URL, pageUri); 1.829 + values.put(Favicons.DATA, encodedFavicon); 1.830 + 1.831 + // Update or insert 1.832 + Uri faviconsUri = getAllFaviconsUri().buildUpon(). 1.833 + appendQueryParameter(BrowserContract.PARAM_INSERT_IF_NEEDED, "true").build(); 1.834 + 1.835 + cr.update(faviconsUri, 1.836 + values, 1.837 + Favicons.URL + " = ?", 1.838 + new String[] { faviconUri }); 1.839 + } 1.840 + 1.841 + @Override 1.842 + public void updateThumbnailForUrl(ContentResolver cr, String uri, 1.843 + BitmapDrawable thumbnail) { 1.844 + Bitmap bitmap = thumbnail.getBitmap(); 1.845 + 1.846 + byte[] data = null; 1.847 + ByteArrayOutputStream stream = new ByteArrayOutputStream(); 1.848 + if (bitmap.compress(Bitmap.CompressFormat.PNG, 0, stream)) { 1.849 + data = stream.toByteArray(); 1.850 + } else { 1.851 + Log.w(LOGTAG, "Favicon compression failed."); 1.852 + } 1.853 + 1.854 + ContentValues values = new ContentValues(); 1.855 + values.put(Thumbnails.DATA, data); 1.856 + values.put(Thumbnails.URL, uri); 1.857 + 1.858 + Uri thumbnailsUri = mThumbnailsUriWithProfile.buildUpon(). 1.859 + appendQueryParameter(BrowserContract.PARAM_INSERT_IF_NEEDED, "true").build(); 1.860 + cr.update(thumbnailsUri, 1.861 + values, 1.862 + Thumbnails.URL + " = ?", 1.863 + new String[] { uri }); 1.864 + } 1.865 + 1.866 + @Override 1.867 + public byte[] getThumbnailForUrl(ContentResolver cr, String uri) { 1.868 + Cursor c = null; 1.869 + byte[] b = null; 1.870 + try { 1.871 + c = cr.query(mThumbnailsUriWithProfile, 1.872 + new String[]{ Thumbnails.DATA }, 1.873 + Thumbnails.URL + " = ? AND " + Thumbnails.DATA + " IS NOT NULL", 1.874 + new String[]{ uri }, 1.875 + null); 1.876 + 1.877 + if (!c.moveToFirst()) { 1.878 + return null; 1.879 + } 1.880 + 1.881 + int thumbnailIndex = c.getColumnIndexOrThrow(Thumbnails.DATA); 1.882 + b = c.getBlob(thumbnailIndex); 1.883 + } finally { 1.884 + if (c != null) { 1.885 + c.close(); 1.886 + } 1.887 + } 1.888 + 1.889 + return b; 1.890 + } 1.891 + 1.892 + /** 1.893 + * Query for non-null thumbnails matching the provided <code>urls</code>. 1.894 + * The returned cursor will have no more than, but possibly fewer than, 1.895 + * the requested number of thumbnails. 1.896 + * 1.897 + * Returns null if the provided list of URLs is empty or null. 1.898 + */ 1.899 + @Override 1.900 + public Cursor getThumbnailsForUrls(ContentResolver cr, List<String> urls) { 1.901 + if (urls == null) { 1.902 + return null; 1.903 + } 1.904 + 1.905 + int urlCount = urls.size(); 1.906 + if (urlCount == 0) { 1.907 + return null; 1.908 + } 1.909 + 1.910 + // Don't match against null thumbnails. 1.911 + StringBuilder selection = new StringBuilder( 1.912 + Thumbnails.DATA + " IS NOT NULL AND " + 1.913 + Thumbnails.URL + " IN (" 1.914 + ); 1.915 + 1.916 + // Compute a (?, ?, ?) sequence to match the provided URLs. 1.917 + int i = 1; 1.918 + while (i++ < urlCount) { 1.919 + selection.append("?, "); 1.920 + } 1.921 + selection.append("?)"); 1.922 + 1.923 + String[] selectionArgs = urls.toArray(new String[urlCount]); 1.924 + 1.925 + return cr.query(mThumbnailsUriWithProfile, 1.926 + new String[] { Thumbnails.URL, Thumbnails.DATA }, 1.927 + selection.toString(), 1.928 + selectionArgs, 1.929 + null); 1.930 + } 1.931 + 1.932 + @Override 1.933 + public void removeThumbnails(ContentResolver cr) { 1.934 + cr.delete(mThumbnailsUriWithProfile, null, null); 1.935 + } 1.936 + 1.937 + // Utility function for updating existing history using batch operations 1.938 + public void updateHistoryInBatch(ContentResolver cr, 1.939 + Collection<ContentProviderOperation> operations, 1.940 + String url, String title, 1.941 + long date, int visits) { 1.942 + Cursor cursor = null; 1.943 + 1.944 + try { 1.945 + final String[] projection = new String[] { 1.946 + History._ID, 1.947 + History.VISITS, 1.948 + History.DATE_LAST_VISITED 1.949 + }; 1.950 + 1.951 + // We need to get the old visit count. 1.952 + cursor = cr.query(getAllHistoryUri(), 1.953 + projection, 1.954 + History.URL + " = ?", 1.955 + new String[] { url }, 1.956 + null); 1.957 + 1.958 + ContentValues values = new ContentValues(); 1.959 + 1.960 + // Restore deleted record if possible 1.961 + values.put(History.IS_DELETED, 0); 1.962 + 1.963 + if (cursor.moveToFirst()) { 1.964 + int visitsCol = cursor.getColumnIndexOrThrow(History.VISITS); 1.965 + int dateCol = cursor.getColumnIndexOrThrow(History.DATE_LAST_VISITED); 1.966 + int oldVisits = cursor.getInt(visitsCol); 1.967 + long oldDate = cursor.getLong(dateCol); 1.968 + values.put(History.VISITS, oldVisits + visits); 1.969 + // Only update last visited if newer. 1.970 + if (date > oldDate) { 1.971 + values.put(History.DATE_LAST_VISITED, date); 1.972 + } 1.973 + } else { 1.974 + values.put(History.VISITS, visits); 1.975 + values.put(History.DATE_LAST_VISITED, date); 1.976 + } 1.977 + if (title != null) { 1.978 + values.put(History.TITLE, title); 1.979 + } 1.980 + values.put(History.URL, url); 1.981 + 1.982 + Uri historyUri = getAllHistoryUri().buildUpon(). 1.983 + appendQueryParameter(BrowserContract.PARAM_INSERT_IF_NEEDED, "true").build(); 1.984 + 1.985 + // Update or insert 1.986 + ContentProviderOperation.Builder builder = 1.987 + ContentProviderOperation.newUpdate(historyUri); 1.988 + builder.withSelection(History.URL + " = ?", new String[] { url }); 1.989 + builder.withValues(values); 1.990 + 1.991 + // Queue the operation 1.992 + operations.add(builder.build()); 1.993 + } finally { 1.994 + if (cursor != null) 1.995 + cursor.close(); 1.996 + } 1.997 + } 1.998 + 1.999 + public void updateBookmarkInBatch(ContentResolver cr, 1.1000 + Collection<ContentProviderOperation> operations, 1.1001 + String url, String title, String guid, 1.1002 + long parent, long added, 1.1003 + long modified, long position, 1.1004 + String keyword, int type) { 1.1005 + ContentValues values = new ContentValues(); 1.1006 + if (title == null && url != null) { 1.1007 + title = url; 1.1008 + } 1.1009 + if (title != null) { 1.1010 + values.put(Bookmarks.TITLE, title); 1.1011 + } 1.1012 + if (url != null) { 1.1013 + values.put(Bookmarks.URL, url); 1.1014 + } 1.1015 + if (guid != null) { 1.1016 + values.put(SyncColumns.GUID, guid); 1.1017 + } 1.1018 + if (keyword != null) { 1.1019 + values.put(Bookmarks.KEYWORD, keyword); 1.1020 + } 1.1021 + if (added > 0) { 1.1022 + values.put(SyncColumns.DATE_CREATED, added); 1.1023 + } 1.1024 + if (modified > 0) { 1.1025 + values.put(SyncColumns.DATE_MODIFIED, modified); 1.1026 + } 1.1027 + values.put(Bookmarks.POSITION, position); 1.1028 + // Restore deleted record if possible 1.1029 + values.put(Bookmarks.IS_DELETED, 0); 1.1030 + 1.1031 + // This assumes no "real" folder has a negative ID. Only 1.1032 + // things like the reading list folder do. 1.1033 + if (parent < 0) { 1.1034 + parent = getFolderIdFromGuid(cr, Bookmarks.MOBILE_FOLDER_GUID); 1.1035 + } 1.1036 + values.put(Bookmarks.PARENT, parent); 1.1037 + values.put(Bookmarks.TYPE, type); 1.1038 + 1.1039 + Uri bookmarkUri = getAllBookmarksUri().buildUpon(). 1.1040 + appendQueryParameter(BrowserContract.PARAM_INSERT_IF_NEEDED, "true").build(); 1.1041 + // Update or insert 1.1042 + ContentProviderOperation.Builder builder = 1.1043 + ContentProviderOperation.newUpdate(bookmarkUri); 1.1044 + if (url != null) { 1.1045 + // Bookmarks are defined by their URL and Folder. 1.1046 + builder.withSelection(Bookmarks.URL + " = ? AND " 1.1047 + + Bookmarks.PARENT + " = ? AND " 1.1048 + + Bookmarks.PARENT + " != ?", 1.1049 + new String[] { url, 1.1050 + Long.toString(parent), 1.1051 + String.valueOf(Bookmarks.FIXED_READING_LIST_ID) 1.1052 + }); 1.1053 + } else if (title != null) { 1.1054 + // Or their title and parent folder. (Folders!) 1.1055 + builder.withSelection(Bookmarks.TITLE + " = ? AND " 1.1056 + + Bookmarks.PARENT + " = ? AND " 1.1057 + + Bookmarks.PARENT + " != ?", 1.1058 + new String[] { title, 1.1059 + Long.toString(parent), 1.1060 + String.valueOf(Bookmarks.FIXED_READING_LIST_ID) 1.1061 + }); 1.1062 + } else if (type == Bookmarks.TYPE_SEPARATOR) { 1.1063 + // Or their their position (seperators) 1.1064 + builder.withSelection(Bookmarks.POSITION + " = ? AND " 1.1065 + + Bookmarks.PARENT + " = ? AND " 1.1066 + + Bookmarks.PARENT + " != ?", 1.1067 + new String[] { Long.toString(position), 1.1068 + Long.toString(parent), 1.1069 + String.valueOf(Bookmarks.FIXED_READING_LIST_ID) 1.1070 + }); 1.1071 + } else { 1.1072 + Log.e(LOGTAG, "Bookmark entry without url or title and not a seperator, not added."); 1.1073 + } 1.1074 + builder.withValues(values); 1.1075 + 1.1076 + // Queue the operation 1.1077 + operations.add(builder.build()); 1.1078 + } 1.1079 + 1.1080 + public void updateFaviconInBatch(ContentResolver cr, 1.1081 + Collection<ContentProviderOperation> operations, 1.1082 + String url, String faviconUrl, 1.1083 + String faviconGuid, byte[] data) { 1.1084 + ContentValues values = new ContentValues(); 1.1085 + values.put(Favicons.DATA, data); 1.1086 + values.put(Favicons.PAGE_URL, url); 1.1087 + if (faviconUrl != null) { 1.1088 + values.put(Favicons.URL, faviconUrl); 1.1089 + } 1.1090 + 1.1091 + // Update or insert 1.1092 + Uri faviconsUri = getAllFaviconsUri().buildUpon(). 1.1093 + appendQueryParameter(BrowserContract.PARAM_INSERT_IF_NEEDED, "true").build(); 1.1094 + // Update or insert 1.1095 + ContentProviderOperation.Builder builder = 1.1096 + ContentProviderOperation.newUpdate(faviconsUri); 1.1097 + builder.withValues(values); 1.1098 + builder.withSelection(Favicons.PAGE_URL + " = ?", new String[] { url }); 1.1099 + // Queue the operation 1.1100 + operations.add(builder.build()); 1.1101 + } 1.1102 + 1.1103 + // This wrapper adds a fake "Desktop Bookmarks" folder entry to the 1.1104 + // beginning of the cursor's data set. 1.1105 + private class SpecialFoldersCursorWrapper extends CursorWrapper { 1.1106 + private int mIndexOffset; 1.1107 + 1.1108 + private int mDesktopBookmarksIndex = -1; 1.1109 + 1.1110 + private boolean mAtDesktopBookmarksPosition = false; 1.1111 + 1.1112 + public SpecialFoldersCursorWrapper(Cursor c, boolean showDesktopBookmarks) { 1.1113 + super(c); 1.1114 + 1.1115 + mIndexOffset = 0; 1.1116 + 1.1117 + if (showDesktopBookmarks) { 1.1118 + mDesktopBookmarksIndex = mIndexOffset; 1.1119 + mIndexOffset++; 1.1120 + } 1.1121 + } 1.1122 + 1.1123 + @Override 1.1124 + public int getCount() { 1.1125 + return super.getCount() + mIndexOffset; 1.1126 + } 1.1127 + 1.1128 + @Override 1.1129 + public boolean moveToPosition(int position) { 1.1130 + mAtDesktopBookmarksPosition = (mDesktopBookmarksIndex == position); 1.1131 + 1.1132 + if (mAtDesktopBookmarksPosition) 1.1133 + return true; 1.1134 + 1.1135 + return super.moveToPosition(position - mIndexOffset); 1.1136 + } 1.1137 + 1.1138 + @Override 1.1139 + public long getLong(int columnIndex) { 1.1140 + if (!mAtDesktopBookmarksPosition) 1.1141 + return super.getLong(columnIndex); 1.1142 + 1.1143 + if (columnIndex == getColumnIndex(Bookmarks.PARENT)) { 1.1144 + return Bookmarks.FIXED_ROOT_ID; 1.1145 + } 1.1146 + 1.1147 + return -1; 1.1148 + } 1.1149 + 1.1150 + @Override 1.1151 + public int getInt(int columnIndex) { 1.1152 + if (!mAtDesktopBookmarksPosition) 1.1153 + return super.getInt(columnIndex); 1.1154 + 1.1155 + if (columnIndex == getColumnIndex(Bookmarks._ID) && mAtDesktopBookmarksPosition) 1.1156 + return Bookmarks.FAKE_DESKTOP_FOLDER_ID; 1.1157 + 1.1158 + if (columnIndex == getColumnIndex(Bookmarks.TYPE)) 1.1159 + return Bookmarks.TYPE_FOLDER; 1.1160 + 1.1161 + return -1; 1.1162 + } 1.1163 + 1.1164 + @Override 1.1165 + public String getString(int columnIndex) { 1.1166 + if (!mAtDesktopBookmarksPosition) 1.1167 + return super.getString(columnIndex); 1.1168 + 1.1169 + if (columnIndex == getColumnIndex(Bookmarks.GUID) && mAtDesktopBookmarksPosition) 1.1170 + return Bookmarks.FAKE_DESKTOP_FOLDER_GUID; 1.1171 + 1.1172 + return ""; 1.1173 + } 1.1174 + } 1.1175 + 1.1176 + private static class LocalDBCursor extends CursorWrapper { 1.1177 + public LocalDBCursor(Cursor c) { 1.1178 + super(c); 1.1179 + } 1.1180 + 1.1181 + private String translateColumnName(String columnName) { 1.1182 + if (columnName.equals(BrowserDB.URLColumns.URL)) { 1.1183 + columnName = URLColumns.URL; 1.1184 + } else if (columnName.equals(BrowserDB.URLColumns.TITLE)) { 1.1185 + columnName = URLColumns.TITLE; 1.1186 + } else if (columnName.equals(BrowserDB.URLColumns.FAVICON)) { 1.1187 + columnName = FaviconColumns.FAVICON; 1.1188 + } else if (columnName.equals(BrowserDB.URLColumns.DATE_LAST_VISITED)) { 1.1189 + columnName = History.DATE_LAST_VISITED; 1.1190 + } else if (columnName.equals(BrowserDB.URLColumns.VISITS)) { 1.1191 + columnName = History.VISITS; 1.1192 + } 1.1193 + 1.1194 + return columnName; 1.1195 + } 1.1196 + 1.1197 + @Override 1.1198 + public int getColumnIndex(String columnName) { 1.1199 + return super.getColumnIndex(translateColumnName(columnName)); 1.1200 + } 1.1201 + 1.1202 + @Override 1.1203 + public int getColumnIndexOrThrow(String columnName) { 1.1204 + return super.getColumnIndexOrThrow(translateColumnName(columnName)); 1.1205 + } 1.1206 + } 1.1207 + 1.1208 + 1.1209 + @Override 1.1210 + public void pinSite(ContentResolver cr, String url, String title, int position) { 1.1211 + ContentValues values = new ContentValues(); 1.1212 + final long now = System.currentTimeMillis(); 1.1213 + values.put(Bookmarks.TITLE, title); 1.1214 + values.put(Bookmarks.URL, url); 1.1215 + values.put(Bookmarks.PARENT, Bookmarks.FIXED_PINNED_LIST_ID); 1.1216 + values.put(Bookmarks.DATE_MODIFIED, now); 1.1217 + values.put(Bookmarks.POSITION, position); 1.1218 + values.put(Bookmarks.IS_DELETED, 0); 1.1219 + 1.1220 + // We do an update-and-replace here without deleting any existing pins for the given URL. 1.1221 + // That means if the user pins a URL, then edits another thumbnail to use the same URL, 1.1222 + // we'll end up with two pins for that site. This is the intended behavior, which 1.1223 + // incidentally saves us a delete query. 1.1224 + Uri uri = mBookmarksUriWithProfile.buildUpon() 1.1225 + .appendQueryParameter(BrowserContract.PARAM_INSERT_IF_NEEDED, "true").build(); 1.1226 + cr.update(uri, 1.1227 + values, 1.1228 + Bookmarks.POSITION + " = ? AND " + 1.1229 + Bookmarks.PARENT + " = ?", 1.1230 + new String[] { Integer.toString(position), 1.1231 + String.valueOf(Bookmarks.FIXED_PINNED_LIST_ID) }); 1.1232 + } 1.1233 + 1.1234 + @Override 1.1235 + public Cursor getPinnedSites(ContentResolver cr, int limit) { 1.1236 + return cr.query(bookmarksUriWithLimit(limit), 1.1237 + new String[] { Bookmarks._ID, 1.1238 + Bookmarks.URL, 1.1239 + Bookmarks.TITLE, 1.1240 + Bookmarks.POSITION }, 1.1241 + Bookmarks.PARENT + " == ?", 1.1242 + new String[] { String.valueOf(Bookmarks.FIXED_PINNED_LIST_ID) }, 1.1243 + Bookmarks.POSITION + " ASC"); 1.1244 + } 1.1245 + 1.1246 + @Override 1.1247 + public void unpinSite(ContentResolver cr, int position) { 1.1248 + cr.delete(mBookmarksUriWithProfile, 1.1249 + Bookmarks.PARENT + " == ? AND " + Bookmarks.POSITION + " = ?", 1.1250 + new String[] { 1.1251 + String.valueOf(Bookmarks.FIXED_PINNED_LIST_ID), 1.1252 + Integer.toString(position) 1.1253 + }); 1.1254 + } 1.1255 + 1.1256 + @Override 1.1257 + public void unpinAllSites(ContentResolver cr) { 1.1258 + cr.delete(mBookmarksUriWithProfile, 1.1259 + Bookmarks.PARENT + " == ?", 1.1260 + new String[] { 1.1261 + String.valueOf(Bookmarks.FIXED_PINNED_LIST_ID) 1.1262 + }); 1.1263 + } 1.1264 + 1.1265 + @Override 1.1266 + public boolean isVisited(ContentResolver cr, String uri) { 1.1267 + int count = 0; 1.1268 + Cursor c = null; 1.1269 + 1.1270 + try { 1.1271 + c = cr.query(historyUriWithLimit(1), 1.1272 + new String[] { History._ID }, 1.1273 + History.URL + " = ?", 1.1274 + new String[] { uri }, 1.1275 + History.URL); 1.1276 + count = c.getCount(); 1.1277 + } catch (NullPointerException e) { 1.1278 + Log.e(LOGTAG, "NullPointerException in isVisited"); 1.1279 + } finally { 1.1280 + if (c != null) 1.1281 + c.close(); 1.1282 + } 1.1283 + 1.1284 + return (count > 0); 1.1285 + } 1.1286 + 1.1287 + public Cursor getBookmarkForUrl(ContentResolver cr, String url) { 1.1288 + Cursor c = cr.query(bookmarksUriWithLimit(1), 1.1289 + new String[] { Bookmarks._ID, 1.1290 + Bookmarks.URL, 1.1291 + Bookmarks.TITLE, 1.1292 + Bookmarks.KEYWORD }, 1.1293 + Bookmarks.URL + " = ?", 1.1294 + new String[] { url }, 1.1295 + null); 1.1296 + 1.1297 + if (c != null && c.getCount() == 0) { 1.1298 + c.close(); 1.1299 + c = null; 1.1300 + } 1.1301 + 1.1302 + return c; 1.1303 + } 1.1304 +}