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.

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

mercurial