1 // Copyright 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.
5 package org.chromium.chrome.browser;
7 import android.annotation.SuppressLint;
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.os.UserHandle;
26 import android.preference.PreferenceManager;
27 import android.provider.BaseColumns;
28 import android.provider.Browser;
29 import android.provider.Browser.BookmarkColumns;
30 import android.provider.Browser.SearchColumns;
31 import android.text.TextUtils;
32 import android.util.Log;
34 import com.google.common.annotations.VisibleForTesting;
36 import org.chromium.base.CalledByNative;
37 import org.chromium.base.CalledByNativeUnchecked;
38 import org.chromium.base.ThreadUtils;
39 import org.chromium.chrome.browser.database.SQLiteCursor;
40 import org.chromium.sync.notifier.SyncStatusHelper;
42 import java.util.ArrayList;
43 import java.util.Arrays;
44 import java.util.HashMap;
45 import java.util.List;
46 import java.util.Vector;
47 import java.util.concurrent.atomic.AtomicBoolean;
50 * This class provides access to user data stored in Chrome, such as bookmarks, most visited pages,
51 * etc. It is used to support android.provider.Browser.
53 public class ChromeBrowserProvider extends ContentProvider {
54 private static final String TAG = "ChromeBrowserProvider";
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";
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";
75 // Defines Chrome's API authority, so it can be run and tested
77 private static final String API_AUTHORITY_SUFFIX = ".browser";
79 private static final String BROWSER_CONTRACT_API_AUTHORITY =
80 "com.google.android.apps.chrome.browser-contract";
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";
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";
100 public static final Uri BROWSER_CONTRACTS_BOOKMAKRS_API_URI = buildContentUri(
101 BROWSER_CONTRACT_API_AUTHORITY, BOOKMARKS_PATH);
103 public static final Uri BROWSER_CONTRACTS_SEARCHES_API_URI = buildContentUri(
104 BROWSER_CONTRACT_API_AUTHORITY, SEARCHES_PATH);
106 public static final Uri BROWSER_CONTRACTS_HISTORY_API_URI = buildContentUri(
107 BROWSER_CONTRACT_API_AUTHORITY, HISTORY_PATH);
109 public static final Uri BROWSER_CONTRACTS_COMBINED_API_URI = buildContentUri(
110 BROWSER_CONTRACT_API_AUTHORITY, COMBINED_PATH);
112 /** The parameter used to specify a bookmark parent ID in ContentValues. */
113 public static final String BOOKMARK_PARENT_ID_PARAM = "parentId";
115 /** The parameter used to specify whether this is a bookmark folder. */
116 public static final String BOOKMARK_IS_FOLDER_PARAM = "isFolder";
119 * Invalid ID value for the Android ContentProvider API calls.
120 * The value 0 is intentional: if the ID represents a bookmark node then it's the root node
121 * and not accessible. Otherwise it represents a SQLite row id, so 0 is also invalid.
123 public static final long INVALID_CONTENT_PROVIDER_ID = 0;
125 // ID used to indicate an invalid id for bookmark nodes.
126 // Client API queries should use ChromeBrowserProviderClient.INVALID_BOOKMARK_ID.
127 static final long INVALID_BOOKMARK_ID = -1;
129 private static final String LAST_MODIFIED_BOOKMARK_FOLDER_ID_KEY = "last_bookmark_folder_id";
131 private static final int URI_MATCH_BOOKMARKS = 0;
132 private static final int URI_MATCH_BOOKMARKS_ID = 1;
133 private static final int URL_MATCH_API_BOOKMARK = 2;
134 private static final int URL_MATCH_API_BOOKMARK_ID = 3;
135 private static final int URL_MATCH_API_SEARCHES = 4;
136 private static final int URL_MATCH_API_SEARCHES_ID = 5;
137 private static final int URL_MATCH_API_HISTORY_CONTENT = 6;
138 private static final int URL_MATCH_API_HISTORY_CONTENT_ID = 7;
139 private static final int URL_MATCH_API_BOOKMARK_CONTENT = 8;
140 private static final int URL_MATCH_API_BOOKMARK_CONTENT_ID = 9;
141 private static final int URL_MATCH_BOOKMARK_SUGGESTIONS_ID = 10;
142 private static final int URL_MATCH_BOOKMARK_HISTORY_SUGGESTIONS_ID = 11;
144 // TODO : Using Android.provider.Browser.HISTORY_PROJECTION once THUMBNAIL,
145 // TOUCH_ICON, and USER_ENTERED fields are supported.
146 private static final String[] BOOKMARK_DEFAULT_PROJECTION = new String[] {
147 BookmarkColumns._ID, BookmarkColumns.URL, BookmarkColumns.VISITS,
148 BookmarkColumns.DATE, BookmarkColumns.BOOKMARK, BookmarkColumns.TITLE,
149 BookmarkColumns.FAVICON, BookmarkColumns.CREATED
152 private static final String[] SUGGEST_PROJECTION = new String[] {
154 BookmarkColumns.TITLE,
156 BookmarkColumns.DATE,
157 BookmarkColumns.BOOKMARK
160 private final Object mInitializeUriMatcherLock = new Object();
161 private final Object mLoadNativeLock = new Object();
162 private UriMatcher mUriMatcher;
163 private long mLastModifiedBookmarkFolderId = INVALID_BOOKMARK_ID;
164 private long mNativeChromeBrowserProvider;
165 private BookmarkNode mMobileBookmarksFolder;
168 * Records whether we've received a call to one of the public ContentProvider APIs.
170 protected boolean mContentProviderApiCalled;
172 private void ensureUriMatcherInitialized() {
173 synchronized (mInitializeUriMatcherLock) {
174 if (mUriMatcher != null) return;
176 mUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
178 String authority = getContext().getPackageName() + AUTHORITY_SUFFIX;
179 mUriMatcher.addURI(authority, BOOKMARKS_PATH, URI_MATCH_BOOKMARKS);
180 mUriMatcher.addURI(authority, BOOKMARKS_PATH + "/#", URI_MATCH_BOOKMARKS_ID);
181 // The internal authority for public APIs
182 String apiAuthority = getContext().getPackageName() + API_AUTHORITY_SUFFIX;
183 mUriMatcher.addURI(apiAuthority, BOOKMARKS_PATH, URL_MATCH_API_BOOKMARK);
184 mUriMatcher.addURI(apiAuthority, BOOKMARKS_PATH + "/#", URL_MATCH_API_BOOKMARK_ID);
185 mUriMatcher.addURI(apiAuthority, SEARCHES_PATH, URL_MATCH_API_SEARCHES);
186 mUriMatcher.addURI(apiAuthority, SEARCHES_PATH + "/#", URL_MATCH_API_SEARCHES_ID);
187 mUriMatcher.addURI(apiAuthority, HISTORY_PATH, URL_MATCH_API_HISTORY_CONTENT);
188 mUriMatcher.addURI(apiAuthority, HISTORY_PATH + "/#", URL_MATCH_API_HISTORY_CONTENT_ID);
189 mUriMatcher.addURI(apiAuthority, COMBINED_PATH, URL_MATCH_API_BOOKMARK);
190 mUriMatcher.addURI(apiAuthority, COMBINED_PATH + "/#", URL_MATCH_API_BOOKMARK_ID);
191 // The internal authority for BrowserContracts
192 mUriMatcher.addURI(BROWSER_CONTRACT_API_AUTHORITY, HISTORY_PATH,
193 URL_MATCH_API_HISTORY_CONTENT);
194 mUriMatcher.addURI(BROWSER_CONTRACT_API_AUTHORITY, HISTORY_PATH + "/#",
195 URL_MATCH_API_HISTORY_CONTENT_ID);
196 mUriMatcher.addURI(BROWSER_CONTRACT_API_AUTHORITY, COMBINED_PATH,
197 URL_MATCH_API_BOOKMARK);
198 mUriMatcher.addURI(BROWSER_CONTRACT_API_AUTHORITY, COMBINED_PATH + "/#",
199 URL_MATCH_API_BOOKMARK_ID);
200 mUriMatcher.addURI(BROWSER_CONTRACT_API_AUTHORITY, SEARCHES_PATH,
201 URL_MATCH_API_SEARCHES);
202 mUriMatcher.addURI(BROWSER_CONTRACT_API_AUTHORITY, SEARCHES_PATH + "/#",
203 URL_MATCH_API_SEARCHES_ID);
204 mUriMatcher.addURI(BROWSER_CONTRACT_API_AUTHORITY, BOOKMARKS_PATH,
205 URL_MATCH_API_BOOKMARK_CONTENT);
206 mUriMatcher.addURI(BROWSER_CONTRACT_API_AUTHORITY, BOOKMARKS_PATH + "/#",
207 URL_MATCH_API_BOOKMARK_CONTENT_ID);
208 // Added the Android Framework URIs, so the provider can easily switched
209 // by adding 'browser' and 'com.android.browser' in manifest.
210 // The Android's BrowserContract
211 mUriMatcher.addURI(BROWSER_CONTRACT_AUTHORITY, HISTORY_PATH,
212 URL_MATCH_API_HISTORY_CONTENT);
213 mUriMatcher.addURI(BROWSER_CONTRACT_AUTHORITY, HISTORY_PATH + "/#",
214 URL_MATCH_API_HISTORY_CONTENT_ID);
215 mUriMatcher.addURI(BROWSER_CONTRACT_AUTHORITY, "combined", URL_MATCH_API_BOOKMARK);
216 mUriMatcher.addURI(BROWSER_CONTRACT_AUTHORITY, "combined/#", URL_MATCH_API_BOOKMARK_ID);
217 mUriMatcher.addURI(BROWSER_CONTRACT_AUTHORITY, SEARCHES_PATH, URL_MATCH_API_SEARCHES);
218 mUriMatcher.addURI(BROWSER_CONTRACT_AUTHORITY, SEARCHES_PATH + "/#",
219 URL_MATCH_API_SEARCHES_ID);
220 mUriMatcher.addURI(BROWSER_CONTRACT_AUTHORITY, BOOKMARKS_PATH,
221 URL_MATCH_API_BOOKMARK_CONTENT);
222 mUriMatcher.addURI(BROWSER_CONTRACT_AUTHORITY, BOOKMARKS_PATH + "/#",
223 URL_MATCH_API_BOOKMARK_CONTENT_ID);
224 // For supporting android.provider.browser.BookmarkColumns and
226 mUriMatcher.addURI("browser", BOOKMARKS_PATH, URL_MATCH_API_BOOKMARK);
227 mUriMatcher.addURI("browser", BOOKMARKS_PATH + "/#", URL_MATCH_API_BOOKMARK_ID);
228 mUriMatcher.addURI("browser", SEARCHES_PATH, URL_MATCH_API_SEARCHES);
229 mUriMatcher.addURI("browser", SEARCHES_PATH + "/#", URL_MATCH_API_SEARCHES_ID);
231 mUriMatcher.addURI(apiAuthority,
232 BOOKMARKS_PATH + "/" + SearchManager.SUGGEST_URI_PATH_QUERY,
233 URL_MATCH_BOOKMARK_SUGGESTIONS_ID);
234 mUriMatcher.addURI(apiAuthority,
235 SearchManager.SUGGEST_URI_PATH_QUERY,
236 URL_MATCH_BOOKMARK_HISTORY_SUGGESTIONS_ID);
241 public boolean onCreate() {
242 // Pre-load shared preferences object, this happens on a separate thread
243 PreferenceManager.getDefaultSharedPreferences(getContext());
248 * Lazily fetches the last modified bookmark folder id.
250 private long getLastModifiedBookmarkFolderId() {
251 if (mLastModifiedBookmarkFolderId == INVALID_BOOKMARK_ID) {
252 SharedPreferences sharedPreferences =
253 PreferenceManager.getDefaultSharedPreferences(getContext());
254 mLastModifiedBookmarkFolderId = sharedPreferences.getLong(
255 LAST_MODIFIED_BOOKMARK_FOLDER_ID_KEY, INVALID_BOOKMARK_ID);
257 return mLastModifiedBookmarkFolderId;
260 private String buildSuggestWhere(String selection, int argc) {
261 StringBuilder sb = new StringBuilder(selection);
262 for (int i = 0; i < argc - 1; i++) {
264 sb.append(selection);
266 return sb.toString();
269 private String getReadWritePermissionNameForBookmarkFolders() {
270 return getContext().getApplicationContext().getPackageName() + ".permission."
271 + PERMISSION_READ_WRITE_BOOKMARKS;
274 private Cursor getBookmarkHistorySuggestions(String selection, String[] selectionArgs,
275 String sortOrder, boolean excludeHistory) {
276 boolean matchTitles = false;
277 Vector<String> args = new Vector<String>();
278 String like = selectionArgs[0] + "%";
279 if (selectionArgs[0].startsWith("http") || selectionArgs[0].startsWith("file")) {
282 // Match against common URL prefixes.
283 args.add("http://" + like);
284 args.add("https://" + like);
285 args.add("http://www." + like);
286 args.add("https://www." + like);
287 args.add("file://" + like);
291 StringBuilder urlWhere = new StringBuilder("(");
292 urlWhere.append(buildSuggestWhere(selection, args.size()));
295 urlWhere.append(" OR title LIKE ?");
297 urlWhere.append(")");
299 if (excludeHistory) {
300 urlWhere.append(" AND bookmark=?");
304 selectionArgs = args.toArray(selectionArgs);
305 Cursor cursor = queryBookmarkFromAPI(SUGGEST_PROJECTION, urlWhere.toString(),
306 selectionArgs, sortOrder);
307 return new ChromeBrowserProviderSuggestionsCursor(cursor);
311 * @see android.content.ContentUris#parseId(Uri)
312 * @return The id from a content URI or -1 if the URI has no id or is malformed.
314 private static long getContentUriId(Uri uri) {
316 return ContentUris.parseId(uri);
317 } catch (UnsupportedOperationException e) {
319 } catch (NumberFormatException e) {
325 public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
327 if (!canHandleContentProviderApiCall()) return null;
329 // Check for invalid id values if provided.
330 long bookmarkId = getContentUriId(uri);
331 if (bookmarkId == INVALID_CONTENT_PROVIDER_ID) return null;
333 int match = mUriMatcher.match(uri);
334 Cursor cursor = null;
336 case URL_MATCH_BOOKMARK_SUGGESTIONS_ID:
337 cursor = getBookmarkHistorySuggestions(selection, selectionArgs, sortOrder, true);
339 case URL_MATCH_BOOKMARK_HISTORY_SUGGESTIONS_ID:
340 cursor = getBookmarkHistorySuggestions(selection, selectionArgs, sortOrder, false);
342 case URL_MATCH_API_BOOKMARK:
343 cursor = queryBookmarkFromAPI(projection, selection, selectionArgs, sortOrder);
345 case URL_MATCH_API_BOOKMARK_ID:
346 cursor = queryBookmarkFromAPI(projection, buildWhereClause(bookmarkId, selection),
347 selectionArgs, sortOrder);
349 case URL_MATCH_API_SEARCHES:
350 cursor = querySearchTermFromAPI(projection, selection, selectionArgs, sortOrder);
352 case URL_MATCH_API_SEARCHES_ID:
353 cursor = querySearchTermFromAPI(projection, buildWhereClause(bookmarkId, selection),
354 selectionArgs, sortOrder);
356 case URL_MATCH_API_HISTORY_CONTENT:
357 cursor = queryBookmarkFromAPI(projection, buildHistoryWhereClause(selection),
358 selectionArgs, sortOrder);
360 case URL_MATCH_API_HISTORY_CONTENT_ID:
361 cursor = queryBookmarkFromAPI(projection,
362 buildHistoryWhereClause(bookmarkId, selection), selectionArgs, sortOrder);
364 case URL_MATCH_API_BOOKMARK_CONTENT:
365 cursor = queryBookmarkFromAPI(projection, buildBookmarkWhereClause(selection),
366 selectionArgs, sortOrder);
368 case URL_MATCH_API_BOOKMARK_CONTENT_ID:
369 cursor = queryBookmarkFromAPI(projection,
370 buildBookmarkWhereClause(bookmarkId, selection), selectionArgs, sortOrder);
373 throw new IllegalArgumentException(TAG + ": query - unknown URL uri = " + uri);
375 if (cursor == null) {
376 cursor = new MatrixCursor(new String[] { });
378 cursor.setNotificationUri(getContext().getContentResolver(), uri);
383 public Uri insert(Uri uri, ContentValues values) {
384 if (!canHandleContentProviderApiCall()) return null;
386 int match = mUriMatcher.match(uri);
390 case URI_MATCH_BOOKMARKS:
391 id = addBookmark(values);
392 if (id == INVALID_BOOKMARK_ID) return null;
394 case URL_MATCH_API_BOOKMARK_CONTENT:
395 values.put(BookmarkColumns.BOOKMARK, 1);
397 case URL_MATCH_API_BOOKMARK:
398 case URL_MATCH_API_HISTORY_CONTENT:
399 id = addBookmarkFromAPI(values);
400 if (id == INVALID_CONTENT_PROVIDER_ID) return null;
402 case URL_MATCH_API_SEARCHES:
403 id = addSearchTermFromAPI(values);
404 if (id == INVALID_CONTENT_PROVIDER_ID) return null;
407 throw new IllegalArgumentException(TAG + ": insert - unknown URL " + uri);
410 res = ContentUris.withAppendedId(uri, id);
416 public int delete(Uri uri, String selection, String[] selectionArgs) {
417 if (!canHandleContentProviderApiCall()) return 0;
419 // Check for invalid id values if provided.
420 long bookmarkId = getContentUriId(uri);
421 if (bookmarkId == INVALID_CONTENT_PROVIDER_ID) return 0;
423 int match = mUriMatcher.match(uri);
426 case URI_MATCH_BOOKMARKS_ID :
427 result = nativeRemoveBookmark(mNativeChromeBrowserProvider, bookmarkId);
429 case URL_MATCH_API_BOOKMARK_ID:
430 result = removeBookmarkFromAPI(
431 buildWhereClause(bookmarkId, selection), selectionArgs);
433 case URL_MATCH_API_BOOKMARK:
434 result = removeBookmarkFromAPI(selection, selectionArgs);
436 case URL_MATCH_API_SEARCHES_ID:
437 result = removeSearchFromAPI(buildWhereClause(bookmarkId, selection),
440 case URL_MATCH_API_SEARCHES:
441 result = removeSearchFromAPI(selection, selectionArgs);
443 case URL_MATCH_API_HISTORY_CONTENT:
444 result = removeHistoryFromAPI(selection, selectionArgs);
446 case URL_MATCH_API_HISTORY_CONTENT_ID:
447 result = removeHistoryFromAPI(buildWhereClause(bookmarkId, selection),
450 case URL_MATCH_API_BOOKMARK_CONTENT:
451 result = removeBookmarkFromAPI(buildBookmarkWhereClause(selection), selectionArgs);
453 case URL_MATCH_API_BOOKMARK_CONTENT_ID:
454 result = removeBookmarkFromAPI(buildBookmarkWhereClause(bookmarkId, selection),
458 throw new IllegalArgumentException(TAG + ": delete - unknown URL " + uri);
460 if (result != 0) notifyChange(uri);
465 public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
466 if (!canHandleContentProviderApiCall()) return 0;
468 // Check for invalid id values if provided.
469 long bookmarkId = getContentUriId(uri);
470 if (bookmarkId == INVALID_CONTENT_PROVIDER_ID) return 0;
472 int match = mUriMatcher.match(uri);
475 case URI_MATCH_BOOKMARKS_ID:
477 if (values.containsKey(Browser.BookmarkColumns.URL)) {
478 url = values.getAsString(Browser.BookmarkColumns.URL);
480 String title = values.getAsString(Browser.BookmarkColumns.TITLE);
481 long parentId = INVALID_BOOKMARK_ID;
482 if (values.containsKey(BOOKMARK_PARENT_ID_PARAM)) {
483 parentId = values.getAsLong(BOOKMARK_PARENT_ID_PARAM);
485 result = nativeUpdateBookmark(mNativeChromeBrowserProvider, bookmarkId, url, title,
487 updateLastModifiedBookmarkFolder(parentId);
489 case URL_MATCH_API_BOOKMARK_ID:
490 result = updateBookmarkFromAPI(values, buildWhereClause(bookmarkId, selection),
493 case URL_MATCH_API_BOOKMARK:
494 result = updateBookmarkFromAPI(values, selection, selectionArgs);
496 case URL_MATCH_API_SEARCHES_ID:
497 result = updateSearchTermFromAPI(values, buildWhereClause(bookmarkId, selection),
500 case URL_MATCH_API_SEARCHES:
501 result = updateSearchTermFromAPI(values, selection, selectionArgs);
503 case URL_MATCH_API_HISTORY_CONTENT:
504 result = updateBookmarkFromAPI(values, buildHistoryWhereClause(selection),
507 case URL_MATCH_API_HISTORY_CONTENT_ID:
508 result = updateBookmarkFromAPI(values,
509 buildHistoryWhereClause(bookmarkId, selection), selectionArgs);
511 case URL_MATCH_API_BOOKMARK_CONTENT:
512 result = updateBookmarkFromAPI(values, buildBookmarkWhereClause(selection),
515 case URL_MATCH_API_BOOKMARK_CONTENT_ID:
516 result = updateBookmarkFromAPI(values,
517 buildBookmarkWhereClause(bookmarkId, selection), selectionArgs);
520 throw new IllegalArgumentException(TAG + ": update - unknown URL " + uri);
522 if (result != 0) notifyChange(uri);
527 public String getType(Uri uri) {
528 ensureUriMatcherInitialized();
529 int match = mUriMatcher.match(uri);
531 case URI_MATCH_BOOKMARKS:
532 case URL_MATCH_API_BOOKMARK:
533 return "vnd.android.cursor.dir/bookmark";
534 case URI_MATCH_BOOKMARKS_ID:
535 case URL_MATCH_API_BOOKMARK_ID:
536 return "vnd.android.cursor.item/bookmark";
537 case URL_MATCH_API_SEARCHES:
538 return "vnd.android.cursor.dir/searches";
539 case URL_MATCH_API_SEARCHES_ID:
540 return "vnd.android.cursor.item/searches";
541 case URL_MATCH_API_HISTORY_CONTENT:
542 return BROWSER_CONTRACT_HISTORY_CONTENT_TYPE;
543 case URL_MATCH_API_HISTORY_CONTENT_ID:
544 return BROWSER_CONTRACT_HISTORY_CONTENT_ITEM_TYPE;
546 throw new IllegalArgumentException(TAG + ": getType - unknown URL " + uri);
550 private long addBookmark(ContentValues values) {
551 String url = values.getAsString(Browser.BookmarkColumns.URL);
552 String title = values.getAsString(Browser.BookmarkColumns.TITLE);
553 boolean isFolder = false;
554 if (values.containsKey(BOOKMARK_IS_FOLDER_PARAM)) {
555 isFolder = values.getAsBoolean(BOOKMARK_IS_FOLDER_PARAM);
557 long parentId = INVALID_BOOKMARK_ID;
558 if (values.containsKey(BOOKMARK_PARENT_ID_PARAM)) {
559 parentId = values.getAsLong(BOOKMARK_PARENT_ID_PARAM);
561 long id = nativeAddBookmark(mNativeChromeBrowserProvider, url, title, isFolder, parentId);
562 if (id == INVALID_BOOKMARK_ID) return id;
565 updateLastModifiedBookmarkFolder(id);
567 updateLastModifiedBookmarkFolder(parentId);
572 private void updateLastModifiedBookmarkFolder(long id) {
573 if (getLastModifiedBookmarkFolderId() == id) return;
575 mLastModifiedBookmarkFolderId = id;
576 SharedPreferences sharedPreferences =
577 PreferenceManager.getDefaultSharedPreferences(getContext());
578 sharedPreferences.edit()
579 .putLong(LAST_MODIFIED_BOOKMARK_FOLDER_ID_KEY, mLastModifiedBookmarkFolderId)
583 public static String getApiAuthority(Context context) {
584 return context.getPackageName() + API_AUTHORITY_SUFFIX;
587 public static String getInternalAuthority(Context context) {
588 return context.getPackageName() + AUTHORITY_SUFFIX;
591 public static Uri getBookmarksUri(Context context) {
592 return buildContentUri(getInternalAuthority(context), BOOKMARKS_PATH);
595 public static Uri getBookmarkFolderUri(Context context) {
596 return buildContentUri(getInternalAuthority(context), BOOKMARK_FOLDER_PATH);
599 public static Uri getBookmarksApiUri(Context context) {
600 return buildContentUri(getApiAuthority(context), BOOKMARKS_PATH);
603 public static Uri getSearchesApiUri(Context context) {
604 return buildContentUri(getApiAuthority(context), SEARCHES_PATH);
607 private boolean bookmarkNodeExists(long nodeId) {
608 if (nodeId < 0) return false;
609 return nativeBookmarkNodeExists(mNativeChromeBrowserProvider, nodeId);
612 private long createBookmarksFolderOnce(String title, long parentId) {
613 return nativeCreateBookmarksFolderOnce(mNativeChromeBrowserProvider, title, parentId);
616 private BookmarkNode getBookmarkFolderHierarchy() {
617 return nativeGetAllBookmarkFolders(mNativeChromeBrowserProvider);
620 protected BookmarkNode getBookmarkNode(long nodeId, boolean getParent, boolean getChildren,
621 boolean getFavicons, boolean getThumbnails) {
622 // Don't allow going up the hierarchy if sync is disabled and the requested node
623 // is the Mobile Bookmarks folder.
624 if (getParent && nodeId == getMobileBookmarksFolderId()
625 && !SyncStatusHelper.get(getContext()).isSyncEnabled()) {
629 BookmarkNode node = nativeGetBookmarkNode(mNativeChromeBrowserProvider, nodeId, getParent,
631 if (!getFavicons && !getThumbnails) return node;
633 // Favicons and thumbnails need to be populated separately as they are provided
634 // asynchronously by Chromium services other than the bookmark model.
635 if (node.parent() != null) populateNodeImages(node.parent(), getFavicons, getThumbnails);
636 for (BookmarkNode child : node.children()) {
637 populateNodeImages(child, getFavicons, getThumbnails);
643 private BookmarkNode getDefaultBookmarkFolder() {
644 // Try to access the bookmark folder last modified by us. If it doesn't exist anymore
645 // then use the synced node (Mobile Bookmarks).
646 BookmarkNode lastModified = getBookmarkNode(getLastModifiedBookmarkFolderId(), false, false,
648 if (lastModified == null) {
649 lastModified = getMobileBookmarksFolder();
650 mLastModifiedBookmarkFolderId = lastModified != null ? lastModified.id() :
656 private void populateNodeImages(BookmarkNode node, boolean favicon, boolean thumbnail) {
657 if (node == null || node.type() != Type.URL) return;
660 node.setFavicon(nativeGetFaviconOrTouchIcon(mNativeChromeBrowserProvider, node.url()));
664 node.setThumbnail(nativeGetThumbnail(mNativeChromeBrowserProvider, node.url()));
668 private BookmarkNode getMobileBookmarksFolder() {
669 if (mMobileBookmarksFolder == null) {
670 mMobileBookmarksFolder = nativeGetMobileBookmarksFolder(mNativeChromeBrowserProvider);
672 return mMobileBookmarksFolder;
675 protected long getMobileBookmarksFolderId() {
676 BookmarkNode mobileBookmarks = getMobileBookmarksFolder();
677 return mobileBookmarks != null ? mobileBookmarks.id() : INVALID_BOOKMARK_ID;
680 private boolean isBookmarkInMobileBookmarksBranch(long nodeId) {
681 if (nodeId <= 0) return false;
682 return nativeIsBookmarkInMobileBookmarksBranch(mNativeChromeBrowserProvider, nodeId);
685 static String argKey(int i) {
690 public Bundle call(String method, String arg, Bundle extras) {
691 // TODO(shashishekhar): Refactor this code into a separate class.
692 // Caller must have the READ_WRITE_BOOKMARK_FOLDERS permission.
693 getContext().enforcePermission(getReadWritePermissionNameForBookmarkFolders(),
694 Binder.getCallingPid(), Binder.getCallingUid(), TAG);
695 if (!canHandleContentProviderApiCall()) return null;
696 if (method == null || extras == null) return null;
698 Bundle result = new Bundle();
699 if (CLIENT_API_BOOKMARK_NODE_EXISTS.equals(method)) {
700 result.putBoolean(CLIENT_API_RESULT_KEY,
701 bookmarkNodeExists(extras.getLong(argKey(0))));
702 } else if (CLIENT_API_CREATE_BOOKMARKS_FOLDER_ONCE.equals(method)) {
703 result.putLong(CLIENT_API_RESULT_KEY,
704 createBookmarksFolderOnce(extras.getString(argKey(0)),
705 extras.getLong(argKey(1))));
706 } else if (CLIENT_API_GET_BOOKMARK_FOLDER_HIERARCHY.equals(method)) {
707 result.putParcelable(CLIENT_API_RESULT_KEY, getBookmarkFolderHierarchy());
708 } else if (CLIENT_API_GET_BOOKMARK_NODE.equals(method)) {
709 result.putParcelable(CLIENT_API_RESULT_KEY,
710 getBookmarkNode(extras.getLong(argKey(0)),
711 extras.getBoolean(argKey(1)),
712 extras.getBoolean(argKey(2)),
713 extras.getBoolean(argKey(3)),
714 extras.getBoolean(argKey(4))));
715 } else if (CLIENT_API_GET_DEFAULT_BOOKMARK_FOLDER.equals(method)) {
716 result.putParcelable(CLIENT_API_RESULT_KEY, getDefaultBookmarkFolder());
717 } else if (method.equals(CLIENT_API_GET_MOBILE_BOOKMARKS_FOLDER_ID)) {
718 result.putLong(CLIENT_API_RESULT_KEY, getMobileBookmarksFolderId());
719 } else if (CLIENT_API_IS_BOOKMARK_IN_MOBILE_BOOKMARKS_BRANCH.equals(method)) {
720 result.putBoolean(CLIENT_API_RESULT_KEY,
721 isBookmarkInMobileBookmarksBranch(extras.getLong(argKey(0))));
722 } else if (CLIENT_API_DELETE_ALL_BOOKMARKS.equals(method)) {
723 nativeRemoveAllBookmarks(mNativeChromeBrowserProvider);
725 Log.w(TAG, "Received invalid method " + method);
733 * Checks whether Chrome is sufficiently initialized to handle a call to the
734 * ChromeBrowserProvider.
736 private boolean canHandleContentProviderApiCall() {
737 mContentProviderApiCalled = true;
739 if (isInUiThread()) return false;
740 if (!ensureNativeChromeLoaded()) return false;
745 * The type of a BookmarkNode.
756 * Simple Data Object representing the chrome bookmark node.
758 public static class BookmarkNode implements Parcelable {
759 private final long mId;
760 private final String mName;
761 private final String mUrl;
762 private final Type mType;
763 private final BookmarkNode mParent;
764 private final List<BookmarkNode> mChildren = new ArrayList<BookmarkNode>();
766 // Favicon and thumbnail optionally set in a 2-step procedure.
767 private byte[] mFavicon;
768 private byte[] mThumbnail;
770 /** Used to pass structured data back from the native code. */
772 public BookmarkNode(long id, Type type, String name, String url, BookmarkNode parent) {
781 * @return The id of this bookmark entry.
788 * @return The name of this bookmark entry.
790 public String name() {
795 * @return The URL of this bookmark entry.
797 public String url() {
802 * @return The type of this bookmark entry.
809 * @return The bookmark favicon, if any.
811 public byte[] favicon() {
816 * @return The bookmark thumbnail, if any.
818 public byte[] thumbnail() {
823 * @return The parent folder of this bookmark entry.
825 public BookmarkNode parent() {
830 * Adds a child to this node.
833 * Used solely by the native code.
836 @CalledByNativeUnchecked("BookmarkNode")
837 public void addChild(BookmarkNode child) {
838 mChildren.add(child);
842 * @return The child bookmark nodes of this node.
844 public List<BookmarkNode> children() {
849 * @return Whether this node represents a bookmarked URL or not.
851 public boolean isUrl() {
856 * @return true if the two individual nodes contain the same information.
857 * The existence of parent and children nodes is checked, but their contents are not.
859 public boolean equalContents(BookmarkNode node) {
860 return node != null &&
862 !(mName == null ^ node.mName == null) &&
863 (mName == null || mName.equals(node.mName)) &&
864 !(mUrl == null ^ node.mUrl == null) &&
865 (mUrl == null || mUrl.equals(node.mUrl)) &&
866 mType == node.mType &&
867 byteArrayEqual(mFavicon, node.mFavicon) &&
868 byteArrayEqual(mThumbnail, node.mThumbnail) &&
869 !(mParent == null ^ node.mParent == null) &&
870 children().size() == node.children().size();
873 private static boolean byteArrayEqual(byte[] byte1, byte[] byte2) {
874 if (byte1 == null && byte2 != null) return byte2.length == 0;
875 if (byte2 == null && byte1 != null) return byte1.length == 0;
876 return Arrays.equals(byte1, byte2);
879 @CalledByNative("BookmarkNode")
880 private static BookmarkNode create(
881 long id, int type, String name, String url, BookmarkNode parent) {
882 return new BookmarkNode(id, Type.values()[type], name, url, parent);
886 public void setFavicon(byte[] favicon) {
891 public void setThumbnail(byte[] thumbnail) {
892 mThumbnail = thumbnail;
896 public int describeContents() {
901 public void writeToParcel(Parcel dest, int flags) {
902 // Write the current node id.
905 // Serialize the full hierarchy from the root.
906 getHierarchyRoot().writeNodeContentsRecursive(dest);
910 public BookmarkNode getHierarchyRoot() {
911 BookmarkNode root = this;
912 while (root.parent() != null) {
913 root = root.parent();
918 private void writeNodeContentsRecursive(Parcel dest) {
919 writeNodeContents(dest);
920 dest.writeInt(mChildren.size());
921 for (BookmarkNode child : mChildren) {
922 child.writeNodeContentsRecursive(dest);
926 private void writeNodeContents(Parcel dest) {
928 dest.writeString(mName);
929 dest.writeString(mUrl);
930 dest.writeInt(mType.ordinal());
931 dest.writeByteArray(mFavicon);
932 dest.writeByteArray(mThumbnail);
933 dest.writeLong(mParent != null ? mParent.mId : INVALID_BOOKMARK_ID);
936 public static final Creator<BookmarkNode> CREATOR = new Creator<BookmarkNode>() {
937 private HashMap<Long, BookmarkNode> mNodeMap;
940 public BookmarkNode createFromParcel(Parcel source) {
941 mNodeMap = new HashMap<Long, BookmarkNode>();
942 long currentNodeId = source.readLong();
943 readNodeContentsRecursive(source);
944 BookmarkNode node = getNode(currentNodeId);
950 public BookmarkNode[] newArray(int size) {
951 return new BookmarkNode[size];
954 private BookmarkNode getNode(long id) {
955 if (id == INVALID_BOOKMARK_ID) return null;
956 Long nodeId = Long.valueOf(id);
957 if (!mNodeMap.containsKey(nodeId)) {
958 Log.e(TAG, "Invalid BookmarkNode hierarchy. Unknown id " + id);
961 return mNodeMap.get(nodeId);
964 private BookmarkNode readNodeContents(Parcel source) {
965 long id = source.readLong();
966 String name = source.readString();
967 String url = source.readString();
968 int type = source.readInt();
969 byte[] favicon = source.createByteArray();
970 byte[] thumbnail = source.createByteArray();
971 long parentId = source.readLong();
972 if (type < 0 || type >= Type.values().length) {
973 Log.w(TAG, "Invalid node type ordinal value.");
977 BookmarkNode node = new BookmarkNode(id, Type.values()[type], name, url,
979 node.setFavicon(favicon);
980 node.setThumbnail(thumbnail);
984 private BookmarkNode readNodeContentsRecursive(Parcel source) {
985 BookmarkNode node = readNodeContents(source);
986 if (node == null) return null;
988 Long nodeId = Long.valueOf(node.id());
989 if (mNodeMap.containsKey(nodeId)) {
990 Log.e(TAG, "Invalid BookmarkNode hierarchy. Duplicate id " + node.id());
993 mNodeMap.put(nodeId, node);
995 int numChildren = source.readInt();
996 for (int i = 0; i < numChildren; ++i) {
997 node.addChild(readNodeContentsRecursive(source));
1005 private long addBookmarkFromAPI(ContentValues values) {
1006 BookmarkRow row = BookmarkRow.fromContentValues(values);
1007 if (row.url == null) {
1008 throw new IllegalArgumentException("Must have a bookmark URL");
1010 return nativeAddBookmarkFromAPI(mNativeChromeBrowserProvider,
1011 row.url, row.created, row.isBookmark, row.date, row.favicon,
1012 row.title, row.visits, row.parentId);
1015 private Cursor queryBookmarkFromAPI(String[] projectionIn, String selection,
1016 String[] selectionArgs, String sortOrder) {
1017 String[] projection = null;
1018 if (projectionIn == null || projectionIn.length == 0) {
1019 projection = BOOKMARK_DEFAULT_PROJECTION;
1021 projection = projectionIn;
1024 return nativeQueryBookmarkFromAPI(mNativeChromeBrowserProvider, projection, selection,
1025 selectionArgs, sortOrder);
1028 private int updateBookmarkFromAPI(ContentValues values, String selection,
1029 String[] selectionArgs) {
1030 BookmarkRow row = BookmarkRow.fromContentValues(values);
1031 return nativeUpdateBookmarkFromAPI(mNativeChromeBrowserProvider,
1032 row.url, row.created, row.isBookmark, row.date,
1033 row.favicon, row.title, row.visits, row.parentId, selection, selectionArgs);
1036 private int removeBookmarkFromAPI(String selection, String[] selectionArgs) {
1037 return nativeRemoveBookmarkFromAPI(mNativeChromeBrowserProvider, selection, selectionArgs);
1040 private int removeHistoryFromAPI(String selection, String[] selectionArgs) {
1041 return nativeRemoveHistoryFromAPI(mNativeChromeBrowserProvider, selection, selectionArgs);
1045 private void onBookmarkChanged() {
1046 notifyChange(buildAPIContentUri(getContext(), BOOKMARKS_PATH));
1050 private void onSearchTermChanged() {
1051 notifyChange(buildAPIContentUri(getContext(), SEARCHES_PATH));
1054 private long addSearchTermFromAPI(ContentValues values) {
1055 SearchRow row = SearchRow.fromContentValues(values);
1056 if (row.term == null) {
1057 throw new IllegalArgumentException("Must have a search term");
1059 return nativeAddSearchTermFromAPI(mNativeChromeBrowserProvider, row.term, row.date);
1062 private int updateSearchTermFromAPI(ContentValues values, String selection,
1063 String[] selectionArgs) {
1064 SearchRow row = SearchRow.fromContentValues(values);
1065 return nativeUpdateSearchTermFromAPI(mNativeChromeBrowserProvider,
1066 row.term, row.date, selection, selectionArgs);
1069 private Cursor querySearchTermFromAPI(String[] projectionIn, String selection,
1070 String[] selectionArgs, String sortOrder) {
1071 String[] projection = null;
1072 if (projectionIn == null || projectionIn.length == 0) {
1073 projection = android.provider.Browser.SEARCHES_PROJECTION;
1075 projection = projectionIn;
1077 return nativeQuerySearchTermFromAPI(mNativeChromeBrowserProvider, projection, selection,
1078 selectionArgs, sortOrder);
1081 private int removeSearchFromAPI(String selection, String[] selectionArgs) {
1082 return nativeRemoveSearchTermFromAPI(mNativeChromeBrowserProvider,
1083 selection, selectionArgs);
1086 private static boolean isInUiThread() {
1087 if (!ThreadUtils.runningOnUiThread()) return false;
1089 if (!"REL".equals(Build.VERSION.CODENAME)) {
1090 throw new IllegalStateException("Shouldn't run in the UI thread");
1093 Log.w(TAG, "ChromeBrowserProvider methods cannot be called from the UI thread.");
1097 private static Uri buildContentUri(String authority, String path) {
1098 return Uri.parse("content://" + authority + "/" + path);
1101 private static Uri buildAPIContentUri(Context context, String path) {
1102 return buildContentUri(context.getPackageName() + API_AUTHORITY_SUFFIX, path);
1105 private static String buildWhereClause(long id, String selection) {
1106 StringBuffer sb = new StringBuffer();
1107 sb.append(BaseColumns._ID);
1110 if (!TextUtils.isEmpty(selection)) {
1111 sb.append(" AND (");
1112 sb.append(selection);
1115 return sb.toString();
1118 private static String buildHistoryWhereClause(long id, String selection) {
1119 return buildWhereClause(id, buildBookmarkWhereClause(selection, false));
1122 private static String buildHistoryWhereClause(String selection) {
1123 return buildBookmarkWhereClause(selection, false);
1127 * @return a SQL where class which is inserted the bookmark condition.
1129 private static String buildBookmarkWhereClause(String selection, boolean isBookmark) {
1130 StringBuffer sb = new StringBuffer();
1131 sb.append(BookmarkColumns.BOOKMARK);
1132 sb.append(isBookmark ? " = 1 " : " = 0");
1133 if (!TextUtils.isEmpty(selection)) {
1134 sb.append(" AND (");
1135 sb.append(selection);
1138 return sb.toString();
1141 private static String buildBookmarkWhereClause(long id, String selection) {
1142 return buildWhereClause(id, buildBookmarkWhereClause(selection, true));
1145 private static String buildBookmarkWhereClause(String selection) {
1146 return buildBookmarkWhereClause(selection, true);
1149 // Wrap the value of BookmarkColumn.
1150 private static class BookmarkRow {
1160 static BookmarkRow fromContentValues(ContentValues values) {
1161 BookmarkRow row = new BookmarkRow();
1162 if (values.containsKey(BookmarkColumns.URL)) {
1163 row.url = values.getAsString(BookmarkColumns.URL);
1165 if (values.containsKey(BookmarkColumns.BOOKMARK)) {
1166 row.isBookmark = values.getAsInteger(BookmarkColumns.BOOKMARK) != 0;
1168 if (values.containsKey(BookmarkColumns.CREATED)) {
1169 row.created = values.getAsLong(BookmarkColumns.CREATED);
1171 if (values.containsKey(BookmarkColumns.DATE)) {
1172 row.date = values.getAsLong(BookmarkColumns.DATE);
1174 if (values.containsKey(BookmarkColumns.FAVICON)) {
1175 row.favicon = values.getAsByteArray(BookmarkColumns.FAVICON);
1176 // We need to know that the caller set the favicon column.
1177 if (row.favicon == null) {
1178 row.favicon = new byte[0];
1181 if (values.containsKey(BookmarkColumns.TITLE)) {
1182 row.title = values.getAsString(BookmarkColumns.TITLE);
1184 if (values.containsKey(BookmarkColumns.VISITS)) {
1185 row.visits = values.getAsInteger(BookmarkColumns.VISITS);
1187 if (values.containsKey(BOOKMARK_PARENT_ID_PARAM)) {
1188 row.parentId = values.getAsLong(BOOKMARK_PARENT_ID_PARAM);
1194 // Wrap the value of SearchColumn.
1195 private static class SearchRow {
1199 static SearchRow fromContentValues(ContentValues values) {
1200 SearchRow row = new SearchRow();
1201 if (values.containsKey(SearchColumns.SEARCH)) {
1202 row.term = values.getAsString(SearchColumns.SEARCH);
1204 if (values.containsKey(SearchColumns.DATE)) {
1205 row.date = values.getAsLong(SearchColumns.DATE);
1212 * Returns true if the native side of the class is initialized.
1214 protected boolean isNativeSideInitialized() {
1215 return mNativeChromeBrowserProvider != 0;
1219 * Make sure chrome is running. This method mustn't run on UI thread.
1221 * @return Whether the native chrome process is running successfully once this has returned.
1223 private boolean ensureNativeChromeLoaded() {
1224 ensureUriMatcherInitialized();
1226 synchronized (mLoadNativeLock) {
1227 if (mNativeChromeBrowserProvider != 0) return true;
1229 final AtomicBoolean retVal = new AtomicBoolean(true);
1230 ThreadUtils.runOnUiThreadBlocking(new Runnable() {
1233 retVal.set(ensureNativeChromeLoadedOnUIThread());
1236 return retVal.get();
1241 * This method should only run on UI thread.
1243 protected boolean ensureNativeChromeLoadedOnUIThread() {
1244 if (isNativeSideInitialized()) return true;
1245 mNativeChromeBrowserProvider = nativeInit();
1246 return isNativeSideInitialized();
1250 protected void finalize() throws Throwable {
1252 // Tests might try to destroy this in the wrong thread.
1253 ThreadUtils.runOnUiThreadBlocking(new Runnable() {
1256 ensureNativeChromeDestroyedOnUIThread();
1265 * This method should only run on UI thread.
1267 private void ensureNativeChromeDestroyedOnUIThread() {
1268 if (isNativeSideInitialized()) {
1269 nativeDestroy(mNativeChromeBrowserProvider);
1270 mNativeChromeBrowserProvider = 0;
1275 * Call to get the intent to create a bookmark shortcut on homescreen.
1277 public static Intent getShortcutToBookmark(String url, String title, Bitmap favicon, int rValue,
1278 int gValue, int bValue, Context context) {
1279 return BookmarkUtils.createAddToHomeIntent(
1280 context, url, title, favicon, rValue, gValue, bValue);
1283 @SuppressLint("NewApi")
1284 private void notifyChange(final Uri uri) {
1285 // If the calling user is different than current one, we need to post a
1286 // task to notify change, otherwise, a system level hidden permission
1287 // INTERACT_ACROSS_USERS_FULL is needed.
1288 // The related APIs were added in API 17, it should be safe to fallback to
1289 // normal way for notifying change, because caller can't be other users in
1290 // devices whose API level is less than API 17.
1291 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
1292 UserHandle callingUserHandle = Binder.getCallingUserHandle();
1293 if (callingUserHandle != null &&
1294 !callingUserHandle.equals(android.os.Process.myUserHandle())) {
1295 ThreadUtils.postOnUiThread(new Runnable() {
1298 getContext().getContentResolver().notifyChange(uri, null);
1304 getContext().getContentResolver().notifyChange(uri, null);
1307 private native long nativeInit();
1308 private native void nativeDestroy(long nativeChromeBrowserProvider);
1310 // Public API native methods.
1311 private native long nativeAddBookmark(long nativeChromeBrowserProvider,
1312 String url, String title, boolean isFolder, long parentId);
1314 private native int nativeRemoveBookmark(long nativeChromeBrowserProvider, long id);
1316 private native int nativeUpdateBookmark(long nativeChromeBrowserProvider,
1317 long id, String url, String title, long parentId);
1319 private native long nativeAddBookmarkFromAPI(long nativeChromeBrowserProvider,
1320 String url, Long created, Boolean isBookmark, Long date, byte[] favicon,
1321 String title, Integer visits, long parentId);
1323 private native SQLiteCursor nativeQueryBookmarkFromAPI(long nativeChromeBrowserProvider,
1324 String[] projection, String selection, String[] selectionArgs, String sortOrder);
1326 private native int nativeUpdateBookmarkFromAPI(long nativeChromeBrowserProvider,
1327 String url, Long created, Boolean isBookmark, Long date, byte[] favicon,
1328 String title, Integer visits, long parentId, String selection, String[] selectionArgs);
1330 private native int nativeRemoveBookmarkFromAPI(long nativeChromeBrowserProvider,
1331 String selection, String[] selectionArgs);
1333 private native int nativeRemoveHistoryFromAPI(long nativeChromeBrowserProvider,
1334 String selection, String[] selectionArgs);
1336 private native long nativeAddSearchTermFromAPI(long nativeChromeBrowserProvider,
1337 String term, Long date);
1339 private native SQLiteCursor nativeQuerySearchTermFromAPI(long nativeChromeBrowserProvider,
1340 String[] projection, String selection, String[] selectionArgs, String sortOrder);
1342 private native int nativeUpdateSearchTermFromAPI(long nativeChromeBrowserProvider,
1343 String search, Long date, String selection, String[] selectionArgs);
1345 private native int nativeRemoveSearchTermFromAPI(long nativeChromeBrowserProvider,
1346 String selection, String[] selectionArgs);
1348 // Client API native methods.
1349 private native boolean nativeBookmarkNodeExists(long nativeChromeBrowserProvider, long id);
1351 private native long nativeCreateBookmarksFolderOnce(long nativeChromeBrowserProvider,
1352 String title, long parentId);
1354 private native BookmarkNode nativeGetAllBookmarkFolders(long nativeChromeBrowserProvider);
1356 private native void nativeRemoveAllBookmarks(long nativeChromeBrowserProvider);
1358 private native BookmarkNode nativeGetBookmarkNode(long nativeChromeBrowserProvider,
1359 long id, boolean getParent, boolean getChildren);
1361 private native BookmarkNode nativeGetMobileBookmarksFolder(long nativeChromeBrowserProvider);
1363 private native boolean nativeIsBookmarkInMobileBookmarksBranch(long nativeChromeBrowserProvider,
1366 private native byte[] nativeGetFaviconOrTouchIcon(long nativeChromeBrowserProvider, String url);
1368 private native byte[] nativeGetThumbnail(long nativeChromeBrowserProvider, String url);