Wed, 31 Dec 2014 07:22:50 +0100
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 | } |