1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 package org.chromium.chrome.browser; 6 7 import android.app.Activity; 8 import android.app.SearchManager; 9 import android.content.ContentProvider; 10 import android.content.ContentUris; 11 import android.content.ContentValues; 12 import android.content.Context; 13 import android.content.Intent; 14 import android.content.SharedPreferences; 15 import android.content.UriMatcher; 16 import android.database.Cursor; 17 import android.database.MatrixCursor; 18 import android.graphics.Bitmap; 19 import android.net.Uri; 20 import android.os.Binder; 21 import android.os.Build; 22 import android.os.Bundle; 23 import android.os.Parcel; 24 import android.os.Parcelable; 25 import android.preference.PreferenceManager; 26 import android.provider.BaseColumns; 27 import android.provider.Browser; 28 import android.provider.Browser.BookmarkColumns; 29 import android.provider.Browser.SearchColumns; 30 import android.text.TextUtils; 31 import android.util.Log; 32 33 import com.google.common.annotations.VisibleForTesting; 34 35 import org.chromium.base.CalledByNative; 36 import org.chromium.base.CalledByNativeUnchecked; 37 import org.chromium.base.ThreadUtils; 38 import org.chromium.chrome.browser.database.SQLiteCursor; 39 import org.chromium.sync.notifier.SyncStatusHelper; 40 41 import java.util.ArrayList; 42 import java.util.Arrays; 43 import java.util.HashMap; 44 import java.util.List; 45 import java.util.Vector; 46 import java.util.concurrent.atomic.AtomicBoolean; 47 48 /** 49 * This class provides various information of Chrome, like bookmarks, most 50 * visited page etc. It is used to support android.provider.Browser. 51 * 52 */ 53 public class ChromeBrowserProvider extends ContentProvider { 54 private static final String TAG = "ChromeBrowserProvider"; 55 56 // The permission required for using the bookmark folders API. Android build system does 57 // not generate Manifest.java for java libraries, hence use the permission name string. When 58 // making changes to this permission, also update the permission in AndroidManifest.xml. 59 private static final String PERMISSION_READ_WRITE_BOOKMARKS = "READ_WRITE_BOOKMARK_FOLDERS"; 60 61 // Defines the API methods that the Client can call by name. 62 static final String CLIENT_API_BOOKMARK_NODE_EXISTS = "BOOKMARK_NODE_EXISTS"; 63 static final String CLIENT_API_CREATE_BOOKMARKS_FOLDER_ONCE = "CREATE_BOOKMARKS_FOLDER_ONCE"; 64 static final String CLIENT_API_GET_BOOKMARK_FOLDER_HIERARCHY = "GET_BOOKMARK_FOLDER_HIERARCHY"; 65 static final String CLIENT_API_GET_BOOKMARK_NODE = "GET_BOOKMARK_NODE"; 66 static final String CLIENT_API_GET_DEFAULT_BOOKMARK_FOLDER = "GET_DEFAULT_BOOKMARK_FOLDER"; 67 static final String CLIENT_API_GET_MOBILE_BOOKMARKS_FOLDER_ID = 68 "GET_MOBILE_BOOKMARKS_FOLDER_ID"; 69 static final String CLIENT_API_IS_BOOKMARK_IN_MOBILE_BOOKMARKS_BRANCH = 70 "IS_BOOKMARK_IN_MOBILE_BOOKMARKS_BRANCH"; 71 static final String CLIENT_API_DELETE_ALL_BOOKMARKS = "DELETE_ALL_BOOKMARKS"; 72 static final String CLIENT_API_RESULT_KEY = "result"; 73 74 75 // Defines Chrome's API authority, so it can be run and tested 76 // independently. 77 private static final String API_AUTHORITY_SUFFIX = ".browser"; 78 79 private static final String BROWSER_CONTRACT_API_AUTHORITY = 80 "com.google.android.apps.chrome.browser-contract"; 81 82 // These values are taken from android.provider.BrowserContract.java since 83 // that class is hidden from the SDK. 84 private static final String BROWSER_CONTRACT_AUTHORITY = "com.android.browser"; 85 private static final String BROWSER_CONTRACT_HISTORY_CONTENT_TYPE = 86 "vnd.android.cursor.dir/browser-history"; 87 private static final String BROWSER_CONTRACT_HISTORY_CONTENT_ITEM_TYPE = 88 "vnd.android.cursor.item/browser-history"; 89 90 // This Authority is for internal interface. It's concatenated with 91 // Context.getPackageName() so that we can install different channels 92 // SxS and have different authorities. 93 private static final String AUTHORITY_SUFFIX = ".ChromeBrowserProvider"; 94 private static final String BOOKMARKS_PATH = "bookmarks"; 95 private static final String SEARCHES_PATH = "searches"; 96 private static final String HISTORY_PATH = "history"; 97 private static final String COMBINED_PATH = "combined"; 98 private static final String BOOKMARK_FOLDER_PATH = "hierarchy"; 99 100 public static final Uri BROWSER_CONTRACTS_BOOKMAKRS_API_URI = buildContentUri( 101 BROWSER_CONTRACT_API_AUTHORITY, BOOKMARKS_PATH); 102 103 public static final Uri BROWSER_CONTRACTS_SEARCHES_API_URI = buildContentUri( 104 BROWSER_CONTRACT_API_AUTHORITY, SEARCHES_PATH); 105 106 public static final Uri BROWSER_CONTRACTS_HISTORY_API_URI = buildContentUri( 107 BROWSER_CONTRACT_API_AUTHORITY, HISTORY_PATH); 108 109 public static final Uri BROWSER_CONTRACTS_COMBINED_API_URI = buildContentUri( 110 BROWSER_CONTRACT_API_AUTHORITY, COMBINED_PATH); 111 112 /** The parameter used to specify a bookmark parent ID in ContentValues. */ 113 public static final String BOOKMARK_PARENT_ID_PARAM = "parentId"; 114 115 /** The parameter used to specify whether this is a bookmark folder. */ 116 public static final String BOOKMARK_IS_FOLDER_PARAM = "isFolder"; 117 118 /** Invalid id value for the Android ContentProvider API calls. */ 119 public static final long INVALID_CONTENT_PROVIDER_ID = 0; 120 121 // ID used to indicate an invalid id for bookmark nodes. 122 // Client API queries should use ChromeBrowserProviderClient.INVALID_BOOKMARK_ID. 123 static final long INVALID_BOOKMARK_ID = -1; 124 125 private static final String LAST_MODIFIED_BOOKMARK_FOLDER_ID_KEY = "last_bookmark_folder_id"; 126 127 private static final int URI_MATCH_BOOKMARKS = 0; 128 private static final int URI_MATCH_BOOKMARKS_ID = 1; 129 private static final int URL_MATCH_API_BOOKMARK = 2; 130 private static final int URL_MATCH_API_BOOKMARK_ID = 3; 131 private static final int URL_MATCH_API_SEARCHES = 4; 132 private static final int URL_MATCH_API_SEARCHES_ID = 5; 133 private static final int URL_MATCH_API_HISTORY_CONTENT = 6; 134 private static final int URL_MATCH_API_HISTORY_CONTENT_ID = 7; 135 private static final int URL_MATCH_API_BOOKMARK_CONTENT = 8; 136 private static final int URL_MATCH_API_BOOKMARK_CONTENT_ID = 9; 137 private static final int URL_MATCH_BOOKMARK_SUGGESTIONS_ID = 10; 138 private static final int URL_MATCH_BOOKMARK_HISTORY_SUGGESTIONS_ID = 11; 139 140 // TODO : Using Android.provider.Browser.HISTORY_PROJECTION once THUMBNAIL, 141 // TOUCH_ICON, and USER_ENTERED fields are supported. 142 private static final String[] BOOKMARK_DEFAULT_PROJECTION = new String[] { 143 BookmarkColumns._ID, BookmarkColumns.URL, BookmarkColumns.VISITS, 144 BookmarkColumns.DATE, BookmarkColumns.BOOKMARK, BookmarkColumns.TITLE, 145 BookmarkColumns.FAVICON, BookmarkColumns.CREATED}; 146 147 private static final String[] SUGGEST_PROJECTION = new String[] { 148 BookmarkColumns._ID, 149 BookmarkColumns.TITLE, 150 BookmarkColumns.URL, 151 BookmarkColumns.DATE, 152 BookmarkColumns.BOOKMARK 153 }; 154 155 private final Object mInitializeUriMatcherLock = new Object(); 156 private final Object mLoadNativeLock = new Object(); 157 private UriMatcher mUriMatcher; 158 private long mLastModifiedBookmarkFolderId = INVALID_BOOKMARK_ID; 159 private int mNativeChromeBrowserProvider; 160 private BookmarkNode mMobileBookmarksFolder; 161 162 /** 163 * Records whether we've received a call to one of the public ContentProvider APIs. 164 */ 165 protected boolean mContentProviderApiCalled; 166 167 private void ensureUriMatcherInitialized() { 168 synchronized (mInitializeUriMatcherLock) { 169 if (mUriMatcher != null) return; 170 171 mUriMatcher = new UriMatcher(UriMatcher.NO_MATCH); 172 // The internal URIs 173 String authority = getContext().getPackageName() + AUTHORITY_SUFFIX; 174 mUriMatcher.addURI(authority, BOOKMARKS_PATH, URI_MATCH_BOOKMARKS); 175 mUriMatcher.addURI(authority, BOOKMARKS_PATH + "/#", URI_MATCH_BOOKMARKS_ID); 176 // The internal authority for public APIs 177 String apiAuthority = getContext().getPackageName() + API_AUTHORITY_SUFFIX; 178 mUriMatcher.addURI(apiAuthority, BOOKMARKS_PATH, URL_MATCH_API_BOOKMARK); 179 mUriMatcher.addURI(apiAuthority, BOOKMARKS_PATH + "/#", URL_MATCH_API_BOOKMARK_ID); 180 mUriMatcher.addURI(apiAuthority, SEARCHES_PATH, URL_MATCH_API_SEARCHES); 181 mUriMatcher.addURI(apiAuthority, SEARCHES_PATH + "/#", URL_MATCH_API_SEARCHES_ID); 182 mUriMatcher.addURI(apiAuthority, HISTORY_PATH, URL_MATCH_API_HISTORY_CONTENT); 183 mUriMatcher.addURI(apiAuthority, HISTORY_PATH + "/#", URL_MATCH_API_HISTORY_CONTENT_ID); 184 mUriMatcher.addURI(apiAuthority, COMBINED_PATH, URL_MATCH_API_BOOKMARK); 185 mUriMatcher.addURI(apiAuthority, COMBINED_PATH + "/#", URL_MATCH_API_BOOKMARK_ID); 186 // The internal authority for BrowserContracts 187 mUriMatcher.addURI(BROWSER_CONTRACT_API_AUTHORITY, HISTORY_PATH, 188 URL_MATCH_API_HISTORY_CONTENT); 189 mUriMatcher.addURI(BROWSER_CONTRACT_API_AUTHORITY, HISTORY_PATH + "/#", 190 URL_MATCH_API_HISTORY_CONTENT_ID); 191 mUriMatcher.addURI(BROWSER_CONTRACT_API_AUTHORITY, COMBINED_PATH, 192 URL_MATCH_API_BOOKMARK); 193 mUriMatcher.addURI(BROWSER_CONTRACT_API_AUTHORITY, COMBINED_PATH + "/#", 194 URL_MATCH_API_BOOKMARK_ID); 195 mUriMatcher.addURI(BROWSER_CONTRACT_API_AUTHORITY, SEARCHES_PATH, 196 URL_MATCH_API_SEARCHES); 197 mUriMatcher.addURI(BROWSER_CONTRACT_API_AUTHORITY, SEARCHES_PATH + "/#", 198 URL_MATCH_API_SEARCHES_ID); 199 mUriMatcher.addURI(BROWSER_CONTRACT_API_AUTHORITY, BOOKMARKS_PATH, 200 URL_MATCH_API_BOOKMARK_CONTENT); 201 mUriMatcher.addURI(BROWSER_CONTRACT_API_AUTHORITY, BOOKMARKS_PATH + "/#", 202 URL_MATCH_API_BOOKMARK_CONTENT_ID); 203 // Added the Android Framework URIs, so the provider can easily switched 204 // by adding 'browser' and 'com.android.browser' in manifest. 205 // The Android's BrowserContract 206 mUriMatcher.addURI(BROWSER_CONTRACT_AUTHORITY, HISTORY_PATH, 207 URL_MATCH_API_HISTORY_CONTENT); 208 mUriMatcher.addURI(BROWSER_CONTRACT_AUTHORITY, HISTORY_PATH + "/#", 209 URL_MATCH_API_HISTORY_CONTENT_ID); 210 mUriMatcher.addURI(BROWSER_CONTRACT_AUTHORITY, "combined", URL_MATCH_API_BOOKMARK); 211 mUriMatcher.addURI(BROWSER_CONTRACT_AUTHORITY, "combined/#", URL_MATCH_API_BOOKMARK_ID); 212 mUriMatcher.addURI(BROWSER_CONTRACT_AUTHORITY, SEARCHES_PATH, URL_MATCH_API_SEARCHES); 213 mUriMatcher.addURI(BROWSER_CONTRACT_AUTHORITY, SEARCHES_PATH + "/#", 214 URL_MATCH_API_SEARCHES_ID); 215 mUriMatcher.addURI(BROWSER_CONTRACT_AUTHORITY, BOOKMARKS_PATH, 216 URL_MATCH_API_BOOKMARK_CONTENT); 217 mUriMatcher.addURI(BROWSER_CONTRACT_AUTHORITY, BOOKMARKS_PATH + "/#", 218 URL_MATCH_API_BOOKMARK_CONTENT_ID); 219 // For supporting android.provider.browser.BookmarkColumns and 220 // SearchColumns 221 mUriMatcher.addURI("browser", BOOKMARKS_PATH, URL_MATCH_API_BOOKMARK); 222 mUriMatcher.addURI("browser", BOOKMARKS_PATH + "/#", URL_MATCH_API_BOOKMARK_ID); 223 mUriMatcher.addURI("browser", SEARCHES_PATH, URL_MATCH_API_SEARCHES); 224 mUriMatcher.addURI("browser", SEARCHES_PATH + "/#", URL_MATCH_API_SEARCHES_ID); 225 226 mUriMatcher.addURI(apiAuthority, 227 BOOKMARKS_PATH + "/" + SearchManager.SUGGEST_URI_PATH_QUERY, 228 URL_MATCH_BOOKMARK_SUGGESTIONS_ID); 229 mUriMatcher.addURI(apiAuthority, 230 SearchManager.SUGGEST_URI_PATH_QUERY, 231 URL_MATCH_BOOKMARK_HISTORY_SUGGESTIONS_ID); 232 } 233 } 234 235 @Override 236 public boolean onCreate() { 237 // Pre-load shared preferences object, this happens on a separate thread 238 PreferenceManager.getDefaultSharedPreferences(getContext()); 239 return true; 240 } 241 242 /** 243 * Lazily fetches the last modified bookmark folder id. 244 */ 245 private long getLastModifiedBookmarkFolderId() { 246 if (mLastModifiedBookmarkFolderId == INVALID_BOOKMARK_ID) { 247 SharedPreferences sharedPreferences = 248 PreferenceManager.getDefaultSharedPreferences(getContext()); 249 mLastModifiedBookmarkFolderId = sharedPreferences.getLong( 250 LAST_MODIFIED_BOOKMARK_FOLDER_ID_KEY, INVALID_BOOKMARK_ID); 251 } 252 return mLastModifiedBookmarkFolderId; 253 } 254 255 private String buildSuggestWhere(String selection, int argc) { 256 StringBuilder sb = new StringBuilder(selection); 257 for (int i = 0; i < argc - 1; i++) { 258 sb.append(" OR "); 259 sb.append(selection); 260 } 261 return sb.toString(); 262 } 263 264 private String getReadWritePermissionNameForBookmarkFolders() { 265 return getContext().getApplicationContext().getPackageName() + ".permission." 266 + PERMISSION_READ_WRITE_BOOKMARKS; 267 } 268 269 private Cursor getBookmarkHistorySuggestions(String selection, String[] selectionArgs, 270 String sortOrder, boolean excludeHistory) { 271 boolean matchTitles = false; 272 Vector<String> args = new Vector<String>(); 273 String like = selectionArgs[0] + "%"; 274 if (selectionArgs[0].startsWith("http") || selectionArgs[0].startsWith("file")) { 275 args.add(like); 276 } else { 277 // Match against common URL prefixes. 278 args.add("http://" + like); 279 args.add("https://" + like); 280 args.add("http://www." + like); 281 args.add("https://www." + like); 282 args.add("file://" + like); 283 matchTitles = true; 284 } 285 286 StringBuilder urlWhere = new StringBuilder("("); 287 urlWhere.append(buildSuggestWhere(selection, args.size())); 288 if (matchTitles) { 289 args.add(like); 290 urlWhere.append(" OR title LIKE ?"); 291 } 292 urlWhere.append(")"); 293 294 if (excludeHistory) { 295 urlWhere.append(" AND bookmark=?"); 296 args.add("1"); 297 } 298 299 selectionArgs = args.toArray(selectionArgs); 300 Cursor cursor = queryBookmarkFromAPI(SUGGEST_PROJECTION, urlWhere.toString(), 301 selectionArgs, sortOrder); 302 return new ChromeBrowserProviderSuggestionsCursor(cursor); 303 } 304 305 @Override 306 public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, 307 String sortOrder) { 308 if (!canHandleContentProviderApiCall()) return null; 309 310 // Check for invalid id values if provided. 311 // If it represents a bookmark node then it's the root node. Don't provide access here. 312 // Otherwise it represents a SQLite row id, so 0 is invalid. 313 long bookmarkId = INVALID_CONTENT_PROVIDER_ID; 314 try { 315 bookmarkId = ContentUris.parseId(uri); 316 if (bookmarkId == INVALID_CONTENT_PROVIDER_ID) return null; 317 } catch (Exception e) { 318 } 319 320 int match = mUriMatcher.match(uri); 321 Cursor cursor = null; 322 switch (match) { 323 case URL_MATCH_BOOKMARK_SUGGESTIONS_ID: 324 cursor = getBookmarkHistorySuggestions(selection, selectionArgs, sortOrder, true); 325 break; 326 case URL_MATCH_BOOKMARK_HISTORY_SUGGESTIONS_ID: 327 cursor = getBookmarkHistorySuggestions(selection, selectionArgs, sortOrder, false); 328 break; 329 case URL_MATCH_API_BOOKMARK: 330 cursor = queryBookmarkFromAPI(projection, selection, selectionArgs, sortOrder); 331 break; 332 case URL_MATCH_API_BOOKMARK_ID: 333 cursor = queryBookmarkFromAPI(projection, buildWhereClause(bookmarkId, selection), 334 selectionArgs, sortOrder); 335 break; 336 case URL_MATCH_API_SEARCHES: 337 cursor = querySearchTermFromAPI(projection, selection, selectionArgs, sortOrder); 338 break; 339 case URL_MATCH_API_SEARCHES_ID: 340 cursor = querySearchTermFromAPI(projection, buildWhereClause(bookmarkId, selection), 341 selectionArgs, sortOrder); 342 break; 343 case URL_MATCH_API_HISTORY_CONTENT: 344 cursor = queryBookmarkFromAPI(projection, buildHistoryWhereClause(selection), 345 selectionArgs, sortOrder); 346 break; 347 case URL_MATCH_API_HISTORY_CONTENT_ID: 348 cursor = queryBookmarkFromAPI(projection, 349 buildHistoryWhereClause(bookmarkId, selection), selectionArgs, sortOrder); 350 break; 351 case URL_MATCH_API_BOOKMARK_CONTENT: 352 cursor = queryBookmarkFromAPI(projection, buildBookmarkWhereClause(selection), 353 selectionArgs, sortOrder); 354 break; 355 case URL_MATCH_API_BOOKMARK_CONTENT_ID: 356 cursor = queryBookmarkFromAPI(projection, 357 buildBookmarkWhereClause(bookmarkId, selection), selectionArgs, sortOrder); 358 break; 359 default: 360 throw new IllegalArgumentException(TAG + ": query - unknown URL uri = " + uri); 361 } 362 if (cursor == null) { 363 cursor = new MatrixCursor(new String[] { }); 364 } 365 cursor.setNotificationUri(getContext().getContentResolver(), uri); 366 return cursor; 367 } 368 369 @Override 370 public Uri insert(Uri uri, ContentValues values) { 371 if (!canHandleContentProviderApiCall()) return null; 372 373 int match = mUriMatcher.match(uri); 374 Uri res = null; 375 long id; 376 switch (match) { 377 case URI_MATCH_BOOKMARKS: 378 id = addBookmark(values); 379 if (id == INVALID_BOOKMARK_ID) return null; 380 break; 381 case URL_MATCH_API_BOOKMARK_CONTENT: 382 values.put(BookmarkColumns.BOOKMARK, 1); 383 //$FALL-THROUGH$ 384 case URL_MATCH_API_BOOKMARK: 385 case URL_MATCH_API_HISTORY_CONTENT: 386 id = addBookmarkFromAPI(values); 387 if (id == INVALID_CONTENT_PROVIDER_ID) return null; 388 break; 389 case URL_MATCH_API_SEARCHES: 390 id = addSearchTermFromAPI(values); 391 if (id == INVALID_CONTENT_PROVIDER_ID) return null; 392 break; 393 default: 394 throw new IllegalArgumentException(TAG + ": insert - unknown URL " + uri); 395 } 396 397 res = ContentUris.withAppendedId(uri, id); 398 getContext().getContentResolver().notifyChange(res, null); 399 return res; 400 } 401 402 @Override 403 public int delete(Uri uri, String selection, String[] selectionArgs) { 404 if (!canHandleContentProviderApiCall()) return 0; 405 406 // Check for invalid id values if provided. 407 // If it represents a bookmark node then it's the root node and not mutable. 408 // Otherwise it represents a SQLite row id, so 0 is invalid. 409 long bookmarkId = INVALID_CONTENT_PROVIDER_ID; 410 try { 411 bookmarkId = ContentUris.parseId(uri); 412 if (bookmarkId == INVALID_CONTENT_PROVIDER_ID) return 0; 413 } catch (Exception e) { 414 } 415 416 int match = mUriMatcher.match(uri); 417 int result; 418 switch (match) { 419 case URI_MATCH_BOOKMARKS_ID : 420 result = nativeRemoveBookmark(mNativeChromeBrowserProvider, bookmarkId); 421 break; 422 case URL_MATCH_API_BOOKMARK_ID: 423 result = removeBookmarkFromAPI( 424 buildWhereClause(bookmarkId, selection), selectionArgs); 425 break; 426 case URL_MATCH_API_BOOKMARK: 427 result = removeBookmarkFromAPI(selection, selectionArgs); 428 break; 429 case URL_MATCH_API_SEARCHES_ID: 430 result = removeSearchFromAPI(buildWhereClause(bookmarkId, selection), 431 selectionArgs); 432 break; 433 case URL_MATCH_API_SEARCHES: 434 result = removeSearchFromAPI(selection, selectionArgs); 435 break; 436 case URL_MATCH_API_HISTORY_CONTENT: 437 result = removeHistoryFromAPI(selection, selectionArgs); 438 break; 439 case URL_MATCH_API_HISTORY_CONTENT_ID: 440 result = removeHistoryFromAPI(buildWhereClause(bookmarkId, selection), 441 selectionArgs); 442 break; 443 case URL_MATCH_API_BOOKMARK_CONTENT: 444 result = removeBookmarkFromAPI(buildBookmarkWhereClause(selection), selectionArgs); 445 break; 446 case URL_MATCH_API_BOOKMARK_CONTENT_ID: 447 result = removeBookmarkFromAPI(buildBookmarkWhereClause(bookmarkId, selection), 448 selectionArgs); 449 break; 450 default: 451 throw new IllegalArgumentException(TAG + ": delete - unknown URL " + uri); 452 } 453 if (result != 0) { 454 getContext().getContentResolver().notifyChange(uri, null); 455 } 456 return result; 457 } 458 459 @Override 460 public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { 461 if (!canHandleContentProviderApiCall()) return 0; 462 463 // Check for invalid id values if provided. 464 // If it represents a bookmark node then it's the root node and not mutable. 465 // Otherwise it represents a SQLite row id, so 0 is invalid. 466 long bookmarkId = INVALID_CONTENT_PROVIDER_ID; 467 try { 468 bookmarkId = ContentUris.parseId(uri); 469 if (bookmarkId == INVALID_CONTENT_PROVIDER_ID) return 0; 470 } catch (Exception e) { 471 } 472 473 int match = mUriMatcher.match(uri); 474 int result; 475 switch (match) { 476 case URI_MATCH_BOOKMARKS_ID: 477 String url = null; 478 if (values.containsKey(Browser.BookmarkColumns.URL)) { 479 url = values.getAsString(Browser.BookmarkColumns.URL); 480 } 481 String title = values.getAsString(Browser.BookmarkColumns.TITLE); 482 long parentId = INVALID_BOOKMARK_ID; 483 if (values.containsKey(BOOKMARK_PARENT_ID_PARAM)) { 484 parentId = values.getAsLong(BOOKMARK_PARENT_ID_PARAM); 485 } 486 result = nativeUpdateBookmark(mNativeChromeBrowserProvider, bookmarkId, url, title, 487 parentId); 488 updateLastModifiedBookmarkFolder(parentId); 489 break; 490 case URL_MATCH_API_BOOKMARK_ID: 491 result = updateBookmarkFromAPI(values, buildWhereClause(bookmarkId, selection), 492 selectionArgs); 493 break; 494 case URL_MATCH_API_BOOKMARK: 495 result = updateBookmarkFromAPI(values, selection, selectionArgs); 496 break; 497 case URL_MATCH_API_SEARCHES_ID: 498 result = updateSearchTermFromAPI(values, buildWhereClause(bookmarkId, selection), 499 selectionArgs); 500 break; 501 case URL_MATCH_API_SEARCHES: 502 result = updateSearchTermFromAPI(values, selection, selectionArgs); 503 break; 504 case URL_MATCH_API_HISTORY_CONTENT: 505 result = updateBookmarkFromAPI(values, buildHistoryWhereClause(selection), 506 selectionArgs); 507 break; 508 case URL_MATCH_API_HISTORY_CONTENT_ID: 509 result = updateBookmarkFromAPI(values, 510 buildHistoryWhereClause(bookmarkId, selection), selectionArgs); 511 break; 512 case URL_MATCH_API_BOOKMARK_CONTENT: 513 result = updateBookmarkFromAPI(values, buildBookmarkWhereClause(selection), 514 selectionArgs); 515 break; 516 case URL_MATCH_API_BOOKMARK_CONTENT_ID: 517 result = updateBookmarkFromAPI(values, 518 buildBookmarkWhereClause(bookmarkId, selection), selectionArgs); 519 break; 520 default: 521 throw new IllegalArgumentException(TAG + ": update - unknown URL " + uri); 522 } 523 if (result != 0) { 524 getContext().getContentResolver().notifyChange(uri, null); 525 } 526 return result; 527 } 528 529 @Override 530 public String getType(Uri uri) { 531 ensureUriMatcherInitialized(); 532 int match = mUriMatcher.match(uri); 533 switch (match) { 534 case URI_MATCH_BOOKMARKS: 535 case URL_MATCH_API_BOOKMARK: 536 return "vnd.android.cursor.dir/bookmark"; 537 case URI_MATCH_BOOKMARKS_ID: 538 case URL_MATCH_API_BOOKMARK_ID: 539 return "vnd.android.cursor.item/bookmark"; 540 case URL_MATCH_API_SEARCHES: 541 return "vnd.android.cursor.dir/searches"; 542 case URL_MATCH_API_SEARCHES_ID: 543 return "vnd.android.cursor.item/searches"; 544 case URL_MATCH_API_HISTORY_CONTENT: 545 return BROWSER_CONTRACT_HISTORY_CONTENT_TYPE; 546 case URL_MATCH_API_HISTORY_CONTENT_ID: 547 return BROWSER_CONTRACT_HISTORY_CONTENT_ITEM_TYPE; 548 default: 549 throw new IllegalArgumentException(TAG + ": getType - unknown URL " + uri); 550 } 551 } 552 553 private long addBookmark(ContentValues values) { 554 String url = values.getAsString(Browser.BookmarkColumns.URL); 555 String title = values.getAsString(Browser.BookmarkColumns.TITLE); 556 boolean isFolder = false; 557 if (values.containsKey(BOOKMARK_IS_FOLDER_PARAM)) { 558 isFolder = values.getAsBoolean(BOOKMARK_IS_FOLDER_PARAM); 559 } 560 long parentId = INVALID_BOOKMARK_ID; 561 if (values.containsKey(BOOKMARK_PARENT_ID_PARAM)) { 562 parentId = values.getAsLong(BOOKMARK_PARENT_ID_PARAM); 563 } 564 long id = nativeAddBookmark(mNativeChromeBrowserProvider, url, title, isFolder, parentId); 565 if (id == INVALID_BOOKMARK_ID) return id; 566 567 if (isFolder) { 568 updateLastModifiedBookmarkFolder(id); 569 } else { 570 updateLastModifiedBookmarkFolder(parentId); 571 } 572 return id; 573 } 574 575 private void updateLastModifiedBookmarkFolder(long id) { 576 if (getLastModifiedBookmarkFolderId() == id) return; 577 578 mLastModifiedBookmarkFolderId = id; 579 SharedPreferences sharedPreferences = 580 PreferenceManager.getDefaultSharedPreferences(getContext()); 581 sharedPreferences.edit() 582 .putLong(LAST_MODIFIED_BOOKMARK_FOLDER_ID_KEY, mLastModifiedBookmarkFolderId) 583 .apply(); 584 } 585 586 public static String getApiAuthority(Context context) { 587 return context.getPackageName() + API_AUTHORITY_SUFFIX; 588 } 589 590 public static String getInternalAuthority(Context context) { 591 return context.getPackageName() + AUTHORITY_SUFFIX; 592 } 593 594 public static Uri getBookmarksUri(Context context) { 595 return buildContentUri(getInternalAuthority(context), BOOKMARKS_PATH); 596 } 597 598 public static Uri getBookmarkFolderUri(Context context) { 599 return buildContentUri(getInternalAuthority(context), BOOKMARK_FOLDER_PATH); 600 } 601 602 public static Uri getBookmarksApiUri(Context context) { 603 return buildContentUri(getApiAuthority(context), BOOKMARKS_PATH); 604 } 605 606 public static Uri getSearchesApiUri(Context context) { 607 return buildContentUri(getApiAuthority(context), SEARCHES_PATH); 608 } 609 610 private boolean bookmarkNodeExists(long nodeId) { 611 if (nodeId < 0) return false; 612 return nativeBookmarkNodeExists(mNativeChromeBrowserProvider, nodeId); 613 } 614 615 private long createBookmarksFolderOnce(String title, long parentId) { 616 return nativeCreateBookmarksFolderOnce(mNativeChromeBrowserProvider, title, parentId); 617 } 618 619 private BookmarkNode getBookmarkFolderHierarchy() { 620 return nativeGetAllBookmarkFolders(mNativeChromeBrowserProvider); 621 } 622 623 protected BookmarkNode getBookmarkNode(long nodeId, boolean getParent, boolean getChildren, 624 boolean getFavicons, boolean getThumbnails) { 625 // Don't allow going up the hierarchy if sync is disabled and the requested node 626 // is the Mobile Bookmarks folder. 627 if (getParent && nodeId == getMobileBookmarksFolderId() 628 && !SyncStatusHelper.get(getContext()).isSyncEnabled()) { 629 getParent = false; 630 } 631 632 BookmarkNode node = nativeGetBookmarkNode(mNativeChromeBrowserProvider, nodeId, getParent, 633 getChildren); 634 if (!getFavicons && !getThumbnails) return node; 635 636 // Favicons and thumbnails need to be populated separately as they are provided 637 // asynchronously by Chromium services other than the bookmark model. 638 if (node.parent() != null) populateNodeImages(node.parent(), getFavicons, getThumbnails); 639 for (BookmarkNode child : node.children()) { 640 populateNodeImages(child, getFavicons, getThumbnails); 641 } 642 643 return node; 644 } 645 646 private BookmarkNode getDefaultBookmarkFolder() { 647 // Try to access the bookmark folder last modified by us. If it doesn't exist anymore 648 // then use the synced node (Mobile Bookmarks). 649 BookmarkNode lastModified = getBookmarkNode(getLastModifiedBookmarkFolderId(), false, false, 650 false, false); 651 if (lastModified == null) { 652 lastModified = getMobileBookmarksFolder(); 653 mLastModifiedBookmarkFolderId = lastModified != null ? lastModified.id() : 654 INVALID_BOOKMARK_ID; 655 } 656 return lastModified; 657 } 658 659 private void populateNodeImages(BookmarkNode node, boolean favicon, boolean thumbnail) { 660 if (node == null || node.type() != Type.URL) return; 661 662 if (favicon) { 663 node.setFavicon(nativeGetFaviconOrTouchIcon(mNativeChromeBrowserProvider, node.url())); 664 } 665 666 if (thumbnail) { 667 node.setThumbnail(nativeGetThumbnail(mNativeChromeBrowserProvider, node.url())); 668 } 669 } 670 671 private BookmarkNode getMobileBookmarksFolder() { 672 if (mMobileBookmarksFolder == null) { 673 mMobileBookmarksFolder = nativeGetMobileBookmarksFolder(mNativeChromeBrowserProvider); 674 } 675 return mMobileBookmarksFolder; 676 } 677 678 protected long getMobileBookmarksFolderId() { 679 BookmarkNode mobileBookmarks = getMobileBookmarksFolder(); 680 return mobileBookmarks != null ? mobileBookmarks.id() : INVALID_BOOKMARK_ID; 681 } 682 683 private boolean isBookmarkInMobileBookmarksBranch(long nodeId) { 684 if (nodeId <= 0) return false; 685 return nativeIsBookmarkInMobileBookmarksBranch(mNativeChromeBrowserProvider, nodeId); 686 } 687 688 static String argKey(int i) { 689 return "arg" + i; 690 } 691 692 @Override 693 public Bundle call(String method, String arg, Bundle extras) { 694 // TODO(shashishekhar): Refactor this code into a separate class. 695 // Caller must have the READ_WRITE_BOOKMARK_FOLDERS permission. 696 getContext().enforcePermission(getReadWritePermissionNameForBookmarkFolders(), 697 Binder.getCallingPid(), Binder.getCallingUid(), TAG); 698 if (!canHandleContentProviderApiCall()) return null; 699 if (method == null || extras == null) return null; 700 701 Bundle result = new Bundle(); 702 if (CLIENT_API_BOOKMARK_NODE_EXISTS.equals(method)) { 703 result.putBoolean(CLIENT_API_RESULT_KEY, 704 bookmarkNodeExists(extras.getLong(argKey(0)))); 705 } else if (CLIENT_API_CREATE_BOOKMARKS_FOLDER_ONCE.equals(method)) { 706 result.putLong(CLIENT_API_RESULT_KEY, 707 createBookmarksFolderOnce(extras.getString(argKey(0)), 708 extras.getLong(argKey(1)))); 709 } else if (CLIENT_API_GET_BOOKMARK_FOLDER_HIERARCHY.equals(method)) { 710 result.putParcelable(CLIENT_API_RESULT_KEY, getBookmarkFolderHierarchy()); 711 } else if (CLIENT_API_GET_BOOKMARK_NODE.equals(method)) { 712 result.putParcelable(CLIENT_API_RESULT_KEY, 713 getBookmarkNode(extras.getLong(argKey(0)), 714 extras.getBoolean(argKey(1)), 715 extras.getBoolean(argKey(2)), 716 extras.getBoolean(argKey(3)), 717 extras.getBoolean(argKey(4)))); 718 } else if (CLIENT_API_GET_DEFAULT_BOOKMARK_FOLDER.equals(method)) { 719 result.putParcelable(CLIENT_API_RESULT_KEY, getDefaultBookmarkFolder()); 720 } else if (method.equals(CLIENT_API_GET_MOBILE_BOOKMARKS_FOLDER_ID)) { 721 result.putLong(CLIENT_API_RESULT_KEY, getMobileBookmarksFolderId()); 722 } else if (CLIENT_API_IS_BOOKMARK_IN_MOBILE_BOOKMARKS_BRANCH.equals(method)) { 723 result.putBoolean(CLIENT_API_RESULT_KEY, 724 isBookmarkInMobileBookmarksBranch(extras.getLong(argKey(0)))); 725 } else if(CLIENT_API_DELETE_ALL_BOOKMARKS.equals(method)) { 726 nativeRemoveAllBookmarks(mNativeChromeBrowserProvider); 727 } else { 728 Log.w(TAG, "Received invalid method " + method); 729 return null; 730 } 731 732 return result; 733 } 734 735 /** 736 * Checks whether Chrome is sufficiently initialized to handle a call to the 737 * ChromeBrowserProvider. 738 */ 739 private boolean canHandleContentProviderApiCall() { 740 mContentProviderApiCalled = true; 741 742 if (isInUiThread()) return false; 743 if (!ensureNativeChromeLoaded()) return false; 744 return true; 745 } 746 747 /** 748 * The type of a BookmarkNode. 749 */ 750 public enum Type { 751 URL, 752 FOLDER, 753 BOOKMARK_BAR, 754 OTHER_NODE, 755 MOBILE 756 } 757 758 /** 759 * Simple Data Object representing the chrome bookmark node. 760 */ 761 public static class BookmarkNode implements Parcelable { 762 private final long mId; 763 private final String mName; 764 private final String mUrl; 765 private final Type mType; 766 private final BookmarkNode mParent; 767 private final List<BookmarkNode> mChildren = new ArrayList<BookmarkNode>(); 768 769 // Favicon and thumbnail optionally set in a 2-step procedure. 770 private byte[] mFavicon; 771 private byte[] mThumbnail; 772 773 /** Used to pass structured data back from the native code. */ 774 @VisibleForTesting 775 public BookmarkNode(long id, Type type, String name, String url, BookmarkNode parent) { 776 mId = id; 777 mName = name; 778 mUrl = url; 779 mType = type; 780 mParent = parent; 781 } 782 783 /** 784 * @return The id of this bookmark entry. 785 */ 786 public long id() { 787 return mId; 788 } 789 790 /** 791 * @return The name of this bookmark entry. 792 */ 793 public String name() { 794 return mName; 795 } 796 797 /** 798 * @return The URL of this bookmark entry. 799 */ 800 public String url() { 801 return mUrl; 802 } 803 804 /** 805 * @return The type of this bookmark entry. 806 */ 807 public Type type() { 808 return mType; 809 } 810 811 /** 812 * @return The bookmark favicon, if any. 813 */ 814 public byte[] favicon() { 815 return mFavicon; 816 } 817 818 /** 819 * @return The bookmark thumbnail, if any. 820 */ 821 public byte[] thumbnail() { 822 return mThumbnail; 823 } 824 825 /** 826 * @return The parent folder of this bookmark entry. 827 */ 828 public BookmarkNode parent() { 829 return mParent; 830 } 831 832 /** 833 * Adds a child to this node. 834 * 835 * <p> 836 * Used solely by the native code. 837 */ 838 @VisibleForTesting 839 @CalledByNativeUnchecked("BookmarkNode") 840 public void addChild(BookmarkNode child) { 841 mChildren.add(child); 842 } 843 844 /** 845 * @return The child bookmark nodes of this node. 846 */ 847 public List<BookmarkNode> children() { 848 return mChildren; 849 } 850 851 /** 852 * @return Whether this node represents a bookmarked URL or not. 853 */ 854 public boolean isUrl() { 855 return mUrl != null; 856 } 857 858 /** 859 * @return true if the two individual nodes contain the same information. 860 * The existence of parent and children nodes is checked, but their contents are not. 861 */ 862 public boolean equalContents(BookmarkNode node) { 863 return node != null && 864 mId == node.mId && 865 !(mName == null ^ node.mName == null) && 866 (mName == null || mName.equals(node.mName)) && 867 !(mUrl == null ^ node.mUrl == null) && 868 (mUrl == null || mUrl.equals(node.mUrl)) && 869 mType == node.mType && 870 byteArrayEqual(mFavicon, node.mFavicon) && 871 byteArrayEqual(mThumbnail, node.mThumbnail) && 872 !(mParent == null ^ node.mParent == null) && 873 children().size() == node.children().size(); 874 } 875 876 private static boolean byteArrayEqual(byte[] byte1, byte[] byte2) { 877 if (byte1 == null && byte2 != null) return byte2.length == 0; 878 if (byte2 == null && byte1 != null) return byte1.length == 0; 879 return Arrays.equals(byte1, byte2); 880 } 881 882 @CalledByNative("BookmarkNode") 883 private static BookmarkNode create( 884 long id, int type, String name, String url, BookmarkNode parent) { 885 return new BookmarkNode(id, Type.values()[type], name, url, parent); 886 } 887 888 @VisibleForTesting 889 public void setFavicon(byte[] favicon) { 890 mFavicon = favicon; 891 } 892 893 @VisibleForTesting 894 public void setThumbnail(byte[] thumbnail) { 895 mThumbnail = thumbnail; 896 } 897 898 @Override 899 public int describeContents() { 900 return 0; 901 } 902 903 @Override 904 public void writeToParcel(Parcel dest, int flags) { 905 // Write the current node id. 906 dest.writeLong(mId); 907 908 // Serialize the full hierarchy from the root. 909 getHierarchyRoot().writeNodeContentsRecursive(dest); 910 } 911 912 @VisibleForTesting 913 public BookmarkNode getHierarchyRoot() { 914 BookmarkNode root = this; 915 while (root.parent() != null) { 916 root = root.parent(); 917 } 918 return root; 919 } 920 921 private void writeNodeContentsRecursive(Parcel dest) { 922 writeNodeContents(dest); 923 dest.writeInt(mChildren.size()); 924 for (BookmarkNode child : mChildren) { 925 child.writeNodeContentsRecursive(dest); 926 } 927 } 928 929 private void writeNodeContents(Parcel dest) { 930 dest.writeLong(mId); 931 dest.writeString(mName); 932 dest.writeString(mUrl); 933 dest.writeInt(mType.ordinal()); 934 dest.writeByteArray(mFavicon); 935 dest.writeByteArray(mThumbnail); 936 dest.writeLong(mParent != null ? mParent.mId : INVALID_BOOKMARK_ID); 937 } 938 939 public static final Creator<BookmarkNode> CREATOR = new Creator<BookmarkNode>() { 940 private HashMap<Long, BookmarkNode> mNodeMap; 941 942 @Override 943 public BookmarkNode createFromParcel(Parcel source) { 944 mNodeMap = new HashMap<Long, BookmarkNode>(); 945 long currentNodeId = source.readLong(); 946 readNodeContentsRecursive(source); 947 BookmarkNode node = getNode(currentNodeId); 948 mNodeMap.clear(); 949 return node; 950 } 951 952 @Override 953 public BookmarkNode[] newArray(int size) { 954 return new BookmarkNode[size]; 955 } 956 957 private BookmarkNode getNode(long id) { 958 if (id == INVALID_BOOKMARK_ID) return null; 959 Long nodeId = Long.valueOf(id); 960 if (!mNodeMap.containsKey(nodeId)) { 961 Log.e(TAG, "Invalid BookmarkNode hierarchy. Unknown id " + id); 962 return null; 963 } 964 return mNodeMap.get(nodeId); 965 } 966 967 private BookmarkNode readNodeContents(Parcel source) { 968 long id = source.readLong(); 969 String name = source.readString(); 970 String url = source.readString(); 971 int type = source.readInt(); 972 byte[] favicon = source.createByteArray(); 973 byte[] thumbnail = source.createByteArray(); 974 long parentId = source.readLong(); 975 if (type < 0 || type >= Type.values().length) { 976 Log.w(TAG, "Invalid node type ordinal value."); 977 return null; 978 } 979 980 BookmarkNode node = new BookmarkNode(id, Type.values()[type], name, url, 981 getNode(parentId)); 982 node.setFavicon(favicon); 983 node.setThumbnail(thumbnail); 984 return node; 985 } 986 987 private BookmarkNode readNodeContentsRecursive(Parcel source) { 988 BookmarkNode node = readNodeContents(source); 989 if (node == null) return null; 990 991 Long nodeId = Long.valueOf(node.id()); 992 if (mNodeMap.containsKey(nodeId)) { 993 Log.e(TAG, "Invalid BookmarkNode hierarchy. Duplicate id " + node.id()); 994 return null; 995 } 996 mNodeMap.put(nodeId, node); 997 998 int numChildren = source.readInt(); 999 for (int i = 0; i < numChildren; ++i) { 1000 node.addChild(readNodeContentsRecursive(source)); 1001 } 1002 1003 return node; 1004 } 1005 }; 1006 } 1007 1008 private long addBookmarkFromAPI(ContentValues values) { 1009 BookmarkRow row = BookmarkRow.fromContentValues(values); 1010 if (row.url == null) { 1011 throw new IllegalArgumentException("Must have a bookmark URL"); 1012 } 1013 return nativeAddBookmarkFromAPI(mNativeChromeBrowserProvider, 1014 row.url, row.created, row.isBookmark, row.date, row.favicon, 1015 row.title, row.visits, row.parentId); 1016 } 1017 1018 private Cursor queryBookmarkFromAPI(String[] projectionIn, String selection, 1019 String[] selectionArgs, String sortOrder) { 1020 String[] projection = null; 1021 if (projectionIn == null || projectionIn.length == 0) { 1022 projection = BOOKMARK_DEFAULT_PROJECTION; 1023 } else { 1024 projection = projectionIn; 1025 } 1026 1027 return nativeQueryBookmarkFromAPI(mNativeChromeBrowserProvider, projection, selection, 1028 selectionArgs, sortOrder); 1029 } 1030 1031 private int updateBookmarkFromAPI(ContentValues values, String selection, 1032 String[] selectionArgs) { 1033 BookmarkRow row = BookmarkRow.fromContentValues(values); 1034 return nativeUpdateBookmarkFromAPI(mNativeChromeBrowserProvider, 1035 row.url, row.created, row.isBookmark, row.date, 1036 row.favicon, row.title, row.visits, row.parentId, selection, selectionArgs); 1037 } 1038 1039 private int removeBookmarkFromAPI(String selection, String[] selectionArgs) { 1040 return nativeRemoveBookmarkFromAPI(mNativeChromeBrowserProvider, selection, selectionArgs); 1041 } 1042 1043 private int removeHistoryFromAPI(String selection, String[] selectionArgs) { 1044 return nativeRemoveHistoryFromAPI(mNativeChromeBrowserProvider, selection, selectionArgs); 1045 } 1046 1047 @CalledByNative 1048 private void onBookmarkChanged() { 1049 getContext().getContentResolver().notifyChange( 1050 buildAPIContentUri(getContext(), BOOKMARKS_PATH), null); 1051 } 1052 1053 @CalledByNative 1054 private void onSearchTermChanged() { 1055 getContext().getContentResolver().notifyChange( 1056 buildAPIContentUri(getContext(), SEARCHES_PATH), null); 1057 } 1058 1059 private long addSearchTermFromAPI(ContentValues values) { 1060 SearchRow row = SearchRow.fromContentValues(values); 1061 if (row.term == null) { 1062 throw new IllegalArgumentException("Must have a search term"); 1063 } 1064 return nativeAddSearchTermFromAPI(mNativeChromeBrowserProvider, row.term, row.date); 1065 } 1066 1067 private int updateSearchTermFromAPI(ContentValues values, String selection, 1068 String[] selectionArgs) { 1069 SearchRow row = SearchRow.fromContentValues(values); 1070 return nativeUpdateSearchTermFromAPI(mNativeChromeBrowserProvider, 1071 row.term, row.date, selection, selectionArgs); 1072 } 1073 1074 private Cursor querySearchTermFromAPI(String[] projectionIn, String selection, 1075 String[] selectionArgs, String sortOrder) { 1076 String[] projection = null; 1077 if (projectionIn == null || projectionIn.length == 0) { 1078 projection = android.provider.Browser.SEARCHES_PROJECTION; 1079 } else { 1080 projection = projectionIn; 1081 } 1082 return nativeQuerySearchTermFromAPI(mNativeChromeBrowserProvider, projection, selection, 1083 selectionArgs, sortOrder); 1084 } 1085 1086 private int removeSearchFromAPI(String selection, String[] selectionArgs) { 1087 return nativeRemoveSearchTermFromAPI(mNativeChromeBrowserProvider, 1088 selection, selectionArgs); 1089 } 1090 1091 private static boolean isInUiThread() { 1092 if (!ThreadUtils.runningOnUiThread()) return false; 1093 1094 if (!"REL".equals(Build.VERSION.CODENAME)) { 1095 throw new IllegalStateException("Shouldn't run in the UI thread"); 1096 } 1097 1098 Log.w(TAG, "ChromeBrowserProvider methods cannot be called from the UI thread."); 1099 return true; 1100 } 1101 1102 private static Uri buildContentUri(String authority, String path) { 1103 return Uri.parse("content://" + authority + "/" + path); 1104 } 1105 1106 private static Uri buildAPIContentUri(Context context, String path) { 1107 return buildContentUri(context.getPackageName() + API_AUTHORITY_SUFFIX, path); 1108 } 1109 1110 private static String buildWhereClause(long id, String selection) { 1111 StringBuffer sb = new StringBuffer(); 1112 sb.append(BaseColumns._ID); 1113 sb.append(" = "); 1114 sb.append(id); 1115 if (!TextUtils.isEmpty(selection)) { 1116 sb.append(" AND ("); 1117 sb.append(selection); 1118 sb.append(")"); 1119 } 1120 return sb.toString(); 1121 } 1122 1123 private static String buildHistoryWhereClause(long id, String selection) { 1124 return buildWhereClause(id, buildBookmarkWhereClause(selection, false)); 1125 } 1126 1127 private static String buildHistoryWhereClause(String selection) { 1128 return buildBookmarkWhereClause(selection, false); 1129 } 1130 1131 /** 1132 * @return a SQL where class which is inserted the bookmark condition. 1133 */ 1134 private static String buildBookmarkWhereClause(String selection, boolean is_bookmark) { 1135 StringBuffer sb = new StringBuffer(); 1136 sb.append(BookmarkColumns.BOOKMARK); 1137 sb.append(is_bookmark ? " = 1 " : " = 0"); 1138 if (!TextUtils.isEmpty(selection)) { 1139 sb.append(" AND ("); 1140 sb.append(selection); 1141 sb.append(")"); 1142 } 1143 return sb.toString(); 1144 } 1145 1146 private static String buildBookmarkWhereClause(long id, String selection) { 1147 return buildWhereClause(id, buildBookmarkWhereClause(selection, true)); 1148 } 1149 1150 private static String buildBookmarkWhereClause(String selection) { 1151 return buildBookmarkWhereClause(selection, true); 1152 } 1153 1154 // Wrap the value of BookmarkColumn. 1155 private static class BookmarkRow { 1156 Boolean isBookmark; 1157 Long created; 1158 String url; 1159 Long date; 1160 byte[] favicon; 1161 String title; 1162 Integer visits; 1163 long parentId; 1164 1165 static BookmarkRow fromContentValues(ContentValues values) { 1166 BookmarkRow row = new BookmarkRow(); 1167 if (values.containsKey(BookmarkColumns.URL)) { 1168 row.url = values.getAsString(BookmarkColumns.URL); 1169 } 1170 if (values.containsKey(BookmarkColumns.BOOKMARK)) { 1171 row.isBookmark = values.getAsInteger(BookmarkColumns.BOOKMARK) != 0; 1172 } 1173 if (values.containsKey(BookmarkColumns.CREATED)) { 1174 row.created = values.getAsLong(BookmarkColumns.CREATED); 1175 } 1176 if (values.containsKey(BookmarkColumns.DATE)) { 1177 row.date = values.getAsLong(BookmarkColumns.DATE); 1178 } 1179 if (values.containsKey(BookmarkColumns.FAVICON)) { 1180 row.favicon = values.getAsByteArray(BookmarkColumns.FAVICON); 1181 // We need to know that the caller set the favicon column. 1182 if (row.favicon == null) { 1183 row.favicon = new byte[0]; 1184 } 1185 } 1186 if (values.containsKey(BookmarkColumns.TITLE)) { 1187 row.title = values.getAsString(BookmarkColumns.TITLE); 1188 } 1189 if (values.containsKey(BookmarkColumns.VISITS)) { 1190 row.visits = values.getAsInteger(BookmarkColumns.VISITS); 1191 } 1192 if (values.containsKey(BOOKMARK_PARENT_ID_PARAM)) { 1193 row.parentId = values.getAsLong(BOOKMARK_PARENT_ID_PARAM); 1194 } 1195 return row; 1196 } 1197 } 1198 1199 // Wrap the value of SearchColumn. 1200 private static class SearchRow { 1201 String term; 1202 Long date; 1203 1204 static SearchRow fromContentValues(ContentValues values) { 1205 SearchRow row = new SearchRow(); 1206 if (values.containsKey(SearchColumns.SEARCH)) { 1207 row.term = values.getAsString(SearchColumns.SEARCH); 1208 } 1209 if (values.containsKey(SearchColumns.DATE)) { 1210 row.date = values.getAsLong(SearchColumns.DATE); 1211 } 1212 return row; 1213 } 1214 } 1215 1216 /** 1217 * Returns true if the native side of the class is initialized. 1218 */ 1219 protected boolean isNativeSideInitialized() { 1220 return mNativeChromeBrowserProvider != 0; 1221 } 1222 1223 /** 1224 * Make sure chrome is running. This method mustn't run on UI thread. 1225 * 1226 * @return Whether the native chrome process is running successfully once this has returned. 1227 */ 1228 private boolean ensureNativeChromeLoaded() { 1229 ensureUriMatcherInitialized(); 1230 1231 synchronized(mLoadNativeLock) { 1232 if (mNativeChromeBrowserProvider != 0) return true; 1233 1234 final AtomicBoolean retVal = new AtomicBoolean(true); 1235 ThreadUtils.runOnUiThreadBlocking(new Runnable() { 1236 @Override 1237 public void run() { 1238 retVal.set(ensureNativeChromeLoadedOnUIThread()); 1239 } 1240 }); 1241 return retVal.get(); 1242 } 1243 } 1244 1245 /** 1246 * This method should only run on UI thread. 1247 */ 1248 protected boolean ensureNativeChromeLoadedOnUIThread() { 1249 if (isNativeSideInitialized()) return true; 1250 mNativeChromeBrowserProvider = nativeInit(); 1251 return isNativeSideInitialized(); 1252 } 1253 1254 @Override 1255 protected void finalize() throws Throwable { 1256 try { 1257 // Tests might try to destroy this in the wrong thread. 1258 ThreadUtils.runOnUiThreadBlocking(new Runnable() { 1259 @Override 1260 public void run() { 1261 ensureNativeChromeDestroyedOnUIThread(); 1262 } 1263 }); 1264 } finally { 1265 super.finalize(); 1266 } 1267 } 1268 1269 /** 1270 * This method should only run on UI thread. 1271 */ 1272 private void ensureNativeChromeDestroyedOnUIThread() { 1273 if (isNativeSideInitialized()) { 1274 nativeDestroy(mNativeChromeBrowserProvider); 1275 mNativeChromeBrowserProvider = 0; 1276 } 1277 } 1278 1279 /** 1280 * Call to get the intent to create a bookmark shortcut on homescreen. 1281 */ 1282 public static Intent getShortcutToBookmark(String url, String title, Bitmap favicon, int rValue, 1283 int gValue, int bValue, Activity activity) { 1284 return BookmarkUtils.createAddToHomeIntent(activity, url, title, favicon, rValue, gValue, 1285 bValue); 1286 } 1287 1288 private native int nativeInit(); 1289 private native void nativeDestroy(int nativeChromeBrowserProvider); 1290 1291 // Public API native methods. 1292 private native long nativeAddBookmark(int nativeChromeBrowserProvider, 1293 String url, String title, boolean isFolder, long parentId); 1294 1295 private native int nativeRemoveBookmark(int nativeChromeBrowserProvider, long id); 1296 1297 private native int nativeUpdateBookmark(int nativeChromeBrowserProvider, 1298 long id, String url, String title, long parentId); 1299 1300 private native long nativeAddBookmarkFromAPI(int nativeChromeBrowserProvider, 1301 String url, Long created, Boolean isBookmark, Long date, byte[] favicon, 1302 String title, Integer visits, long parentId); 1303 1304 private native SQLiteCursor nativeQueryBookmarkFromAPI(int nativeChromeBrowserProvider, 1305 String[] projection, String selection, String[] selectionArgs, String sortOrder); 1306 1307 private native int nativeUpdateBookmarkFromAPI(int nativeChromeBrowserProvider, 1308 String url, Long created, Boolean isBookmark, Long date, byte[] favicon, 1309 String title, Integer visits, long parentId, String selection, String[] selectionArgs); 1310 1311 private native int nativeRemoveBookmarkFromAPI(int nativeChromeBrowserProvider, 1312 String selection, String[] selectionArgs); 1313 1314 private native int nativeRemoveHistoryFromAPI(int nativeChromeBrowserProvider, 1315 String selection, String[] selectionArgs); 1316 1317 private native long nativeAddSearchTermFromAPI(int nativeChromeBrowserProvider, 1318 String term, Long date); 1319 1320 private native SQLiteCursor nativeQuerySearchTermFromAPI(int nativeChromeBrowserProvider, 1321 String[] projection, String selection, String[] selectionArgs, String sortOrder); 1322 1323 private native int nativeUpdateSearchTermFromAPI(int nativeChromeBrowserProvider, 1324 String search, Long date, String selection, String[] selectionArgs); 1325 1326 private native int nativeRemoveSearchTermFromAPI(int nativeChromeBrowserProvider, 1327 String selection, String[] selectionArgs); 1328 1329 // Client API native methods. 1330 private native boolean nativeBookmarkNodeExists(int nativeChromeBrowserProvider, long id); 1331 1332 private native long nativeCreateBookmarksFolderOnce(int nativeChromeBrowserProvider, 1333 String title, long parentId); 1334 1335 private native BookmarkNode nativeGetAllBookmarkFolders(int nativeChromeBrowserProvider); 1336 1337 private native void nativeRemoveAllBookmarks(int nativeChromeBrowserProvider); 1338 1339 private native BookmarkNode nativeGetBookmarkNode(int nativeChromeBrowserProvider, 1340 long id, boolean getParent, boolean getChildren); 1341 1342 private native BookmarkNode nativeGetMobileBookmarksFolder(int nativeChromeBrowserProvider); 1343 1344 private native boolean nativeIsBookmarkInMobileBookmarksBranch(int nativeChromeBrowserProvider, 1345 long id); 1346 1347 private native byte[] nativeGetFaviconOrTouchIcon(int nativeChromeBrowserProvider, String url); 1348 1349 private native byte[] nativeGetThumbnail(int nativeChromeBrowserProvider, String url); 1350 } 1351