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.SharedPreferences;
14 import android.content.UriMatcher;
15 import android.database.Cursor;
16 import android.database.MatrixCursor;
17 import android.net.Uri;
18 import android.os.Binder;
19 import android.os.Build;
20 import android.os.Bundle;
21 import android.os.Parcel;
22 import android.os.Parcelable;
23 import android.os.UserHandle;
24 import android.preference.PreferenceManager;
25 import android.provider.BaseColumns;
26 import android.provider.Browser;
27 import android.provider.Browser.BookmarkColumns;
28 import android.provider.Browser.SearchColumns;
29 import android.text.TextUtils;
30 import android.util.Log;
32 import com.google.common.annotations.VisibleForTesting;
34 import org.chromium.base.CalledByNative;
35 import org.chromium.base.CalledByNativeUnchecked;
36 import org.chromium.base.ThreadUtils;
37 import org.chromium.chrome.browser.database.SQLiteCursor;
38 import org.chromium.sync.notifier.SyncStatusHelper;
40 import java.util.ArrayList;
41 import java.util.Arrays;
42 import java.util.HashMap;
43 import java.util.List;
44 import java.util.Vector;
45 import java.util.concurrent.atomic.AtomicBoolean;
48 * This class provides access to user data stored in Chrome, such as bookmarks, most visited pages,
49 * etc. It is used to support android.provider.Browser.
51 public class ChromeBrowserProvider extends ContentProvider {
52 private static final String TAG = "ChromeBrowserProvider";
54 // The permission required for using the bookmark folders API. Android build system does
55 // not generate Manifest.java for java libraries, hence use the permission name string. When
56 // making changes to this permission, also update the permission in AndroidManifest.xml.
57 private static final String PERMISSION_READ_WRITE_BOOKMARKS = "READ_WRITE_BOOKMARK_FOLDERS";
59 // Defines the API methods that the Client can call by name.
60 static final String CLIENT_API_BOOKMARK_NODE_EXISTS = "BOOKMARK_NODE_EXISTS";
61 static final String CLIENT_API_CREATE_BOOKMARKS_FOLDER_ONCE = "CREATE_BOOKMARKS_FOLDER_ONCE";
62 static final String CLIENT_API_GET_EDITABLE_BOOKMARK_FOLDER_HIERARCHY =
63 "GET_EDITABLE_BOOKMARK_FOLDER_HIERARCHY";
64 static final String CLIENT_API_GET_BOOKMARK_NODE = "GET_BOOKMARK_NODE";
65 static final String CLIENT_API_GET_DEFAULT_BOOKMARK_FOLDER = "GET_DEFAULT_BOOKMARK_FOLDER";
66 static final String CLIENT_API_GET_MOBILE_BOOKMARKS_FOLDER_ID =
67 "GET_MOBILE_BOOKMARKS_FOLDER_ID";
68 static final String CLIENT_API_IS_BOOKMARK_IN_MOBILE_BOOKMARKS_BRANCH =
69 "IS_BOOKMARK_IN_MOBILE_BOOKMARKS_BRANCH";
70 static final String CLIENT_API_DELETE_ALL_USER_BOOKMARKS = "DELETE_ALL_USER_BOOKMARKS";
71 static final String CLIENT_API_RESULT_KEY = "result";
74 // Defines Chrome's API authority, so it can be run and tested
76 private static final String API_AUTHORITY_SUFFIX = ".browser";
78 private static final String BROWSER_CONTRACT_API_AUTHORITY =
79 "com.google.android.apps.chrome.browser-contract";
81 // These values are taken from android.provider.BrowserContract.java since
82 // that class is hidden from the SDK.
83 private static final String BROWSER_CONTRACT_AUTHORITY = "com.android.browser";
84 private static final String BROWSER_CONTRACT_HISTORY_CONTENT_TYPE =
85 "vnd.android.cursor.dir/browser-history";
86 private static final String BROWSER_CONTRACT_HISTORY_CONTENT_ITEM_TYPE =
87 "vnd.android.cursor.item/browser-history";
89 // This Authority is for internal interface. It's concatenated with
90 // Context.getPackageName() so that we can install different channels
91 // SxS and have different authorities.
92 private static final String AUTHORITY_SUFFIX = ".ChromeBrowserProvider";
93 private static final String BOOKMARKS_PATH = "bookmarks";
94 private static final String SEARCHES_PATH = "searches";
95 private static final String HISTORY_PATH = "history";
96 private static final String COMBINED_PATH = "combined";
97 private static final String BOOKMARK_FOLDER_PATH = "hierarchy";
99 public static final Uri BROWSER_CONTRACTS_BOOKMAKRS_API_URI = buildContentUri(
100 BROWSER_CONTRACT_API_AUTHORITY, BOOKMARKS_PATH);
102 public static final Uri BROWSER_CONTRACTS_SEARCHES_API_URI = buildContentUri(
103 BROWSER_CONTRACT_API_AUTHORITY, SEARCHES_PATH);
105 public static final Uri BROWSER_CONTRACTS_HISTORY_API_URI = buildContentUri(
106 BROWSER_CONTRACT_API_AUTHORITY, HISTORY_PATH);
108 public static final Uri BROWSER_CONTRACTS_COMBINED_API_URI = buildContentUri(
109 BROWSER_CONTRACT_API_AUTHORITY, COMBINED_PATH);
111 /** The parameter used to specify a bookmark parent ID in ContentValues. */
112 public static final String BOOKMARK_PARENT_ID_PARAM = "parentId";
114 /** The parameter used to specify whether this is a bookmark folder. */
115 public static final String BOOKMARK_IS_FOLDER_PARAM = "isFolder";
118 * Invalid ID value for the Android ContentProvider API calls.
119 * The value 0 is intentional: if the ID represents a bookmark node then it's the root node
120 * and not accessible. Otherwise it represents a SQLite row id, so 0 is also invalid.
122 public static final long INVALID_CONTENT_PROVIDER_ID = 0;
124 // ID used to indicate an invalid id for bookmark nodes.
125 // Client API queries should use ChromeBrowserProviderClient.INVALID_BOOKMARK_ID.
126 static final long INVALID_BOOKMARK_ID = -1;
128 private static final String LAST_MODIFIED_BOOKMARK_FOLDER_ID_KEY = "last_bookmark_folder_id";
130 private static final int URI_MATCH_BOOKMARKS = 0;
131 private static final int URI_MATCH_BOOKMARKS_ID = 1;
132 private static final int URL_MATCH_API_BOOKMARK = 2;
133 private static final int URL_MATCH_API_BOOKMARK_ID = 3;
134 private static final int URL_MATCH_API_SEARCHES = 4;
135 private static final int URL_MATCH_API_SEARCHES_ID = 5;
136 private static final int URL_MATCH_API_HISTORY_CONTENT = 6;
137 private static final int URL_MATCH_API_HISTORY_CONTENT_ID = 7;
138 private static final int URL_MATCH_API_BOOKMARK_CONTENT = 8;
139 private static final int URL_MATCH_API_BOOKMARK_CONTENT_ID = 9;
140 private static final int URL_MATCH_BOOKMARK_SUGGESTIONS_ID = 10;
141 private static final int URL_MATCH_BOOKMARK_HISTORY_SUGGESTIONS_ID = 11;
143 // TODO : Using Android.provider.Browser.HISTORY_PROJECTION once THUMBNAIL,
144 // TOUCH_ICON, and USER_ENTERED fields are supported.
145 private static final String[] BOOKMARK_DEFAULT_PROJECTION = new String[] {
146 BookmarkColumns._ID, BookmarkColumns.URL, BookmarkColumns.VISITS,
147 BookmarkColumns.DATE, BookmarkColumns.BOOKMARK, BookmarkColumns.TITLE,
148 BookmarkColumns.FAVICON, BookmarkColumns.CREATED
151 private static final String[] SUGGEST_PROJECTION = new String[] {
153 BookmarkColumns.TITLE,
155 BookmarkColumns.DATE,
156 BookmarkColumns.BOOKMARK
159 private final Object mInitializeUriMatcherLock = new Object();
160 private final Object mLoadNativeLock = new Object();
161 private UriMatcher mUriMatcher;
162 private long mLastModifiedBookmarkFolderId = INVALID_BOOKMARK_ID;
163 private long mNativeChromeBrowserProvider;
164 private BookmarkNode mMobileBookmarksFolder;
167 * Records whether we've received a call to one of the public ContentProvider APIs.
169 protected boolean mContentProviderApiCalled;
171 private void ensureUriMatcherInitialized() {
172 synchronized (mInitializeUriMatcherLock) {
173 if (mUriMatcher != null) return;
175 mUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
177 String authority = getContext().getPackageName() + AUTHORITY_SUFFIX;
178 mUriMatcher.addURI(authority, BOOKMARKS_PATH, URI_MATCH_BOOKMARKS);
179 mUriMatcher.addURI(authority, BOOKMARKS_PATH + "/#", URI_MATCH_BOOKMARKS_ID);
180 // The internal authority for public APIs
181 String apiAuthority = getContext().getPackageName() + API_AUTHORITY_SUFFIX;
182 mUriMatcher.addURI(apiAuthority, BOOKMARKS_PATH, URL_MATCH_API_BOOKMARK);
183 mUriMatcher.addURI(apiAuthority, BOOKMARKS_PATH + "/#", URL_MATCH_API_BOOKMARK_ID);
184 mUriMatcher.addURI(apiAuthority, SEARCHES_PATH, URL_MATCH_API_SEARCHES);
185 mUriMatcher.addURI(apiAuthority, SEARCHES_PATH + "/#", URL_MATCH_API_SEARCHES_ID);
186 mUriMatcher.addURI(apiAuthority, HISTORY_PATH, URL_MATCH_API_HISTORY_CONTENT);
187 mUriMatcher.addURI(apiAuthority, HISTORY_PATH + "/#", URL_MATCH_API_HISTORY_CONTENT_ID);
188 mUriMatcher.addURI(apiAuthority, COMBINED_PATH, URL_MATCH_API_BOOKMARK);
189 mUriMatcher.addURI(apiAuthority, COMBINED_PATH + "/#", URL_MATCH_API_BOOKMARK_ID);
190 // The internal authority for BrowserContracts
191 mUriMatcher.addURI(BROWSER_CONTRACT_API_AUTHORITY, HISTORY_PATH,
192 URL_MATCH_API_HISTORY_CONTENT);
193 mUriMatcher.addURI(BROWSER_CONTRACT_API_AUTHORITY, HISTORY_PATH + "/#",
194 URL_MATCH_API_HISTORY_CONTENT_ID);
195 mUriMatcher.addURI(BROWSER_CONTRACT_API_AUTHORITY, COMBINED_PATH,
196 URL_MATCH_API_BOOKMARK);
197 mUriMatcher.addURI(BROWSER_CONTRACT_API_AUTHORITY, COMBINED_PATH + "/#",
198 URL_MATCH_API_BOOKMARK_ID);
199 mUriMatcher.addURI(BROWSER_CONTRACT_API_AUTHORITY, SEARCHES_PATH,
200 URL_MATCH_API_SEARCHES);
201 mUriMatcher.addURI(BROWSER_CONTRACT_API_AUTHORITY, SEARCHES_PATH + "/#",
202 URL_MATCH_API_SEARCHES_ID);
203 mUriMatcher.addURI(BROWSER_CONTRACT_API_AUTHORITY, BOOKMARKS_PATH,
204 URL_MATCH_API_BOOKMARK_CONTENT);
205 mUriMatcher.addURI(BROWSER_CONTRACT_API_AUTHORITY, BOOKMARKS_PATH + "/#",
206 URL_MATCH_API_BOOKMARK_CONTENT_ID);
207 // Added the Android Framework URIs, so the provider can easily switched
208 // by adding 'browser' and 'com.android.browser' in manifest.
209 // The Android's BrowserContract
210 mUriMatcher.addURI(BROWSER_CONTRACT_AUTHORITY, HISTORY_PATH,
211 URL_MATCH_API_HISTORY_CONTENT);
212 mUriMatcher.addURI(BROWSER_CONTRACT_AUTHORITY, HISTORY_PATH + "/#",
213 URL_MATCH_API_HISTORY_CONTENT_ID);
214 mUriMatcher.addURI(BROWSER_CONTRACT_AUTHORITY, "combined", URL_MATCH_API_BOOKMARK);
215 mUriMatcher.addURI(BROWSER_CONTRACT_AUTHORITY, "combined/#", URL_MATCH_API_BOOKMARK_ID);
216 mUriMatcher.addURI(BROWSER_CONTRACT_AUTHORITY, SEARCHES_PATH, URL_MATCH_API_SEARCHES);
217 mUriMatcher.addURI(BROWSER_CONTRACT_AUTHORITY, SEARCHES_PATH + "/#",
218 URL_MATCH_API_SEARCHES_ID);
219 mUriMatcher.addURI(BROWSER_CONTRACT_AUTHORITY, BOOKMARKS_PATH,
220 URL_MATCH_API_BOOKMARK_CONTENT);
221 mUriMatcher.addURI(BROWSER_CONTRACT_AUTHORITY, BOOKMARKS_PATH + "/#",
222 URL_MATCH_API_BOOKMARK_CONTENT_ID);
223 // For supporting android.provider.browser.BookmarkColumns and
225 mUriMatcher.addURI("browser", BOOKMARKS_PATH, URL_MATCH_API_BOOKMARK);
226 mUriMatcher.addURI("browser", BOOKMARKS_PATH + "/#", URL_MATCH_API_BOOKMARK_ID);
227 mUriMatcher.addURI("browser", SEARCHES_PATH, URL_MATCH_API_SEARCHES);
228 mUriMatcher.addURI("browser", SEARCHES_PATH + "/#", URL_MATCH_API_SEARCHES_ID);
230 mUriMatcher.addURI(apiAuthority,
231 BOOKMARKS_PATH + "/" + SearchManager.SUGGEST_URI_PATH_QUERY,
232 URL_MATCH_BOOKMARK_SUGGESTIONS_ID);
233 mUriMatcher.addURI(apiAuthority,
234 SearchManager.SUGGEST_URI_PATH_QUERY,
235 URL_MATCH_BOOKMARK_HISTORY_SUGGESTIONS_ID);
240 public boolean onCreate() {
241 // Pre-load shared preferences object, this happens on a separate thread
242 PreferenceManager.getDefaultSharedPreferences(getContext());
247 * Lazily fetches the last modified bookmark folder id.
249 private long getLastModifiedBookmarkFolderId() {
250 if (mLastModifiedBookmarkFolderId == INVALID_BOOKMARK_ID) {
251 SharedPreferences sharedPreferences =
252 PreferenceManager.getDefaultSharedPreferences(getContext());
253 mLastModifiedBookmarkFolderId = sharedPreferences.getLong(
254 LAST_MODIFIED_BOOKMARK_FOLDER_ID_KEY, INVALID_BOOKMARK_ID);
256 return mLastModifiedBookmarkFolderId;
259 private String buildSuggestWhere(String selection, int argc) {
260 StringBuilder sb = new StringBuilder(selection);
261 for (int i = 0; i < argc - 1; i++) {
263 sb.append(selection);
265 return sb.toString();
268 private String getReadWritePermissionNameForBookmarkFolders() {
269 return getContext().getApplicationContext().getPackageName() + ".permission."
270 + PERMISSION_READ_WRITE_BOOKMARKS;
273 private Cursor getBookmarkHistorySuggestions(String selection, String[] selectionArgs,
274 String sortOrder, boolean excludeHistory) {
275 boolean matchTitles = false;
276 Vector<String> args = new Vector<String>();
277 String like = selectionArgs[0] + "%";
278 if (selectionArgs[0].startsWith("http") || selectionArgs[0].startsWith("file")) {
281 // Match against common URL prefixes.
282 args.add("http://" + like);
283 args.add("https://" + like);
284 args.add("http://www." + like);
285 args.add("https://www." + like);
286 args.add("file://" + like);
290 StringBuilder urlWhere = new StringBuilder("(");
291 urlWhere.append(buildSuggestWhere(selection, args.size()));
294 urlWhere.append(" OR title LIKE ?");
296 urlWhere.append(")");
298 if (excludeHistory) {
299 urlWhere.append(" AND bookmark=?");
303 selectionArgs = args.toArray(selectionArgs);
304 Cursor cursor = queryBookmarkFromAPI(SUGGEST_PROJECTION, urlWhere.toString(),
305 selectionArgs, sortOrder);
306 return new ChromeBrowserProviderSuggestionsCursor(cursor);
310 * @see android.content.ContentUris#parseId(Uri)
311 * @return The id from a content URI or -1 if the URI has no id or is malformed.
313 private static long getContentUriId(Uri uri) {
315 return ContentUris.parseId(uri);
316 } catch (UnsupportedOperationException e) {
318 } catch (NumberFormatException e) {
324 public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
326 if (!canHandleContentProviderApiCall()) return null;
328 // Check for invalid id values if provided.
329 long bookmarkId = getContentUriId(uri);
330 if (bookmarkId == INVALID_CONTENT_PROVIDER_ID) return null;
332 int match = mUriMatcher.match(uri);
333 Cursor cursor = null;
335 case URL_MATCH_BOOKMARK_SUGGESTIONS_ID:
336 cursor = getBookmarkHistorySuggestions(selection, selectionArgs, sortOrder, true);
338 case URL_MATCH_BOOKMARK_HISTORY_SUGGESTIONS_ID:
339 cursor = getBookmarkHistorySuggestions(selection, selectionArgs, sortOrder, false);
341 case URL_MATCH_API_BOOKMARK:
342 cursor = queryBookmarkFromAPI(projection, selection, selectionArgs, sortOrder);
344 case URL_MATCH_API_BOOKMARK_ID:
345 cursor = queryBookmarkFromAPI(projection, buildWhereClause(bookmarkId, selection),
346 selectionArgs, sortOrder);
348 case URL_MATCH_API_SEARCHES:
349 cursor = querySearchTermFromAPI(projection, selection, selectionArgs, sortOrder);
351 case URL_MATCH_API_SEARCHES_ID:
352 cursor = querySearchTermFromAPI(projection, buildWhereClause(bookmarkId, selection),
353 selectionArgs, sortOrder);
355 case URL_MATCH_API_HISTORY_CONTENT:
356 cursor = queryBookmarkFromAPI(projection, buildHistoryWhereClause(selection),
357 selectionArgs, sortOrder);
359 case URL_MATCH_API_HISTORY_CONTENT_ID:
360 cursor = queryBookmarkFromAPI(projection,
361 buildHistoryWhereClause(bookmarkId, selection), selectionArgs, sortOrder);
363 case URL_MATCH_API_BOOKMARK_CONTENT:
364 cursor = queryBookmarkFromAPI(projection, buildBookmarkWhereClause(selection),
365 selectionArgs, sortOrder);
367 case URL_MATCH_API_BOOKMARK_CONTENT_ID:
368 cursor = queryBookmarkFromAPI(projection,
369 buildBookmarkWhereClause(bookmarkId, selection), selectionArgs, sortOrder);
372 throw new IllegalArgumentException(TAG + ": query - unknown URL uri = " + uri);
374 if (cursor == null) {
375 cursor = new MatrixCursor(new String[] { });
377 cursor.setNotificationUri(getContext().getContentResolver(), uri);
382 public Uri insert(Uri uri, ContentValues values) {
383 if (!canHandleContentProviderApiCall()) return null;
385 int match = mUriMatcher.match(uri);
389 case URI_MATCH_BOOKMARKS:
390 id = addBookmark(values);
391 if (id == INVALID_BOOKMARK_ID) return null;
393 case URL_MATCH_API_BOOKMARK_CONTENT:
394 values.put(BookmarkColumns.BOOKMARK, 1);
396 case URL_MATCH_API_BOOKMARK:
397 case URL_MATCH_API_HISTORY_CONTENT:
398 id = addBookmarkFromAPI(values);
399 if (id == INVALID_CONTENT_PROVIDER_ID) return null;
401 case URL_MATCH_API_SEARCHES:
402 id = addSearchTermFromAPI(values);
403 if (id == INVALID_CONTENT_PROVIDER_ID) return null;
406 throw new IllegalArgumentException(TAG + ": insert - unknown URL " + uri);
409 res = ContentUris.withAppendedId(uri, id);
415 public int delete(Uri uri, String selection, String[] selectionArgs) {
416 if (!canHandleContentProviderApiCall()) return 0;
418 // Check for invalid id values if provided.
419 long bookmarkId = getContentUriId(uri);
420 if (bookmarkId == INVALID_CONTENT_PROVIDER_ID) return 0;
422 int match = mUriMatcher.match(uri);
425 case URI_MATCH_BOOKMARKS_ID :
426 result = nativeRemoveBookmark(mNativeChromeBrowserProvider, bookmarkId);
428 case URL_MATCH_API_BOOKMARK_ID:
429 result = removeBookmarkFromAPI(
430 buildWhereClause(bookmarkId, selection), selectionArgs);
432 case URL_MATCH_API_BOOKMARK:
433 result = removeBookmarkFromAPI(selection, selectionArgs);
435 case URL_MATCH_API_SEARCHES_ID:
436 result = removeSearchFromAPI(buildWhereClause(bookmarkId, selection),
439 case URL_MATCH_API_SEARCHES:
440 result = removeSearchFromAPI(selection, selectionArgs);
442 case URL_MATCH_API_HISTORY_CONTENT:
443 result = removeHistoryFromAPI(selection, selectionArgs);
445 case URL_MATCH_API_HISTORY_CONTENT_ID:
446 result = removeHistoryFromAPI(buildWhereClause(bookmarkId, selection),
449 case URL_MATCH_API_BOOKMARK_CONTENT:
450 result = removeBookmarkFromAPI(buildBookmarkWhereClause(selection), selectionArgs);
452 case URL_MATCH_API_BOOKMARK_CONTENT_ID:
453 result = removeBookmarkFromAPI(buildBookmarkWhereClause(bookmarkId, selection),
457 throw new IllegalArgumentException(TAG + ": delete - unknown URL " + uri);
459 if (result != 0) notifyChange(uri);
464 public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
465 if (!canHandleContentProviderApiCall()) return 0;
467 // Check for invalid id values if provided.
468 long bookmarkId = getContentUriId(uri);
469 if (bookmarkId == INVALID_CONTENT_PROVIDER_ID) return 0;
471 int match = mUriMatcher.match(uri);
474 case URI_MATCH_BOOKMARKS_ID:
476 if (values.containsKey(Browser.BookmarkColumns.URL)) {
477 url = values.getAsString(Browser.BookmarkColumns.URL);
479 String title = values.getAsString(Browser.BookmarkColumns.TITLE);
480 long parentId = INVALID_BOOKMARK_ID;
481 if (values.containsKey(BOOKMARK_PARENT_ID_PARAM)) {
482 parentId = values.getAsLong(BOOKMARK_PARENT_ID_PARAM);
484 result = nativeUpdateBookmark(mNativeChromeBrowserProvider, bookmarkId, url, title,
486 updateLastModifiedBookmarkFolder(parentId);
488 case URL_MATCH_API_BOOKMARK_ID:
489 result = updateBookmarkFromAPI(values, buildWhereClause(bookmarkId, selection),
492 case URL_MATCH_API_BOOKMARK:
493 result = updateBookmarkFromAPI(values, selection, selectionArgs);
495 case URL_MATCH_API_SEARCHES_ID:
496 result = updateSearchTermFromAPI(values, buildWhereClause(bookmarkId, selection),
499 case URL_MATCH_API_SEARCHES:
500 result = updateSearchTermFromAPI(values, selection, selectionArgs);
502 case URL_MATCH_API_HISTORY_CONTENT:
503 result = updateBookmarkFromAPI(values, buildHistoryWhereClause(selection),
506 case URL_MATCH_API_HISTORY_CONTENT_ID:
507 result = updateBookmarkFromAPI(values,
508 buildHistoryWhereClause(bookmarkId, selection), selectionArgs);
510 case URL_MATCH_API_BOOKMARK_CONTENT:
511 result = updateBookmarkFromAPI(values, buildBookmarkWhereClause(selection),
514 case URL_MATCH_API_BOOKMARK_CONTENT_ID:
515 result = updateBookmarkFromAPI(values,
516 buildBookmarkWhereClause(bookmarkId, selection), selectionArgs);
519 throw new IllegalArgumentException(TAG + ": update - unknown URL " + uri);
521 if (result != 0) notifyChange(uri);
526 public String getType(Uri uri) {
527 ensureUriMatcherInitialized();
528 int match = mUriMatcher.match(uri);
530 case URI_MATCH_BOOKMARKS:
531 case URL_MATCH_API_BOOKMARK:
532 return "vnd.android.cursor.dir/bookmark";
533 case URI_MATCH_BOOKMARKS_ID:
534 case URL_MATCH_API_BOOKMARK_ID:
535 return "vnd.android.cursor.item/bookmark";
536 case URL_MATCH_API_SEARCHES:
537 return "vnd.android.cursor.dir/searches";
538 case URL_MATCH_API_SEARCHES_ID:
539 return "vnd.android.cursor.item/searches";
540 case URL_MATCH_API_HISTORY_CONTENT:
541 return BROWSER_CONTRACT_HISTORY_CONTENT_TYPE;
542 case URL_MATCH_API_HISTORY_CONTENT_ID:
543 return BROWSER_CONTRACT_HISTORY_CONTENT_ITEM_TYPE;
545 throw new IllegalArgumentException(TAG + ": getType - unknown URL " + uri);
549 private long addBookmark(ContentValues values) {
550 String url = values.getAsString(Browser.BookmarkColumns.URL);
551 String title = values.getAsString(Browser.BookmarkColumns.TITLE);
552 boolean isFolder = false;
553 if (values.containsKey(BOOKMARK_IS_FOLDER_PARAM)) {
554 isFolder = values.getAsBoolean(BOOKMARK_IS_FOLDER_PARAM);
556 long parentId = INVALID_BOOKMARK_ID;
557 if (values.containsKey(BOOKMARK_PARENT_ID_PARAM)) {
558 parentId = values.getAsLong(BOOKMARK_PARENT_ID_PARAM);
560 long id = nativeAddBookmark(mNativeChromeBrowserProvider, url, title, isFolder, parentId);
561 if (id == INVALID_BOOKMARK_ID) return id;
564 updateLastModifiedBookmarkFolder(id);
566 updateLastModifiedBookmarkFolder(parentId);
571 private void updateLastModifiedBookmarkFolder(long id) {
572 if (getLastModifiedBookmarkFolderId() == id) return;
574 mLastModifiedBookmarkFolderId = id;
575 SharedPreferences sharedPreferences =
576 PreferenceManager.getDefaultSharedPreferences(getContext());
577 sharedPreferences.edit()
578 .putLong(LAST_MODIFIED_BOOKMARK_FOLDER_ID_KEY, mLastModifiedBookmarkFolderId)
582 public static String getApiAuthority(Context context) {
583 return context.getPackageName() + API_AUTHORITY_SUFFIX;
586 public static String getInternalAuthority(Context context) {
587 return context.getPackageName() + AUTHORITY_SUFFIX;
590 public static Uri getBookmarksUri(Context context) {
591 return buildContentUri(getInternalAuthority(context), BOOKMARKS_PATH);
594 public static Uri getBookmarkFolderUri(Context context) {
595 return buildContentUri(getInternalAuthority(context), BOOKMARK_FOLDER_PATH);
598 public static Uri getBookmarksApiUri(Context context) {
599 return buildContentUri(getApiAuthority(context), BOOKMARKS_PATH);
602 public static Uri getSearchesApiUri(Context context) {
603 return buildContentUri(getApiAuthority(context), SEARCHES_PATH);
606 private boolean bookmarkNodeExists(long nodeId) {
607 if (nodeId < 0) return false;
608 return nativeBookmarkNodeExists(mNativeChromeBrowserProvider, nodeId);
611 private long createBookmarksFolderOnce(String title, long parentId) {
612 return nativeCreateBookmarksFolderOnce(mNativeChromeBrowserProvider, title, parentId);
615 private BookmarkNode getEditableBookmarkFolderHierarchy() {
616 return nativeGetEditableBookmarkFolders(mNativeChromeBrowserProvider);
619 protected BookmarkNode getBookmarkNode(long nodeId, boolean getParent, boolean getChildren,
620 boolean getFavicons, boolean getThumbnails) {
621 // Don't allow going up the hierarchy if sync is disabled and the requested node
622 // is the Mobile Bookmarks folder.
623 if (getParent && nodeId == getMobileBookmarksFolderId()
624 && !SyncStatusHelper.get(getContext()).isSyncEnabled()) {
628 BookmarkNode node = nativeGetBookmarkNode(mNativeChromeBrowserProvider, nodeId, getParent,
630 if (!getFavicons && !getThumbnails) return node;
632 // Favicons and thumbnails need to be populated separately as they are provided
633 // asynchronously by Chromium services other than the bookmark model.
634 if (node.parent() != null) populateNodeImages(node.parent(), getFavicons, getThumbnails);
635 for (BookmarkNode child : node.children()) {
636 populateNodeImages(child, getFavicons, getThumbnails);
642 private BookmarkNode getDefaultBookmarkFolder() {
643 // Try to access the bookmark folder last modified by us. If it doesn't exist anymore
644 // then use the synced node (Mobile Bookmarks).
645 BookmarkNode lastModified = getBookmarkNode(getLastModifiedBookmarkFolderId(), false, false,
647 if (lastModified == null || lastModified.isUrl()) {
648 lastModified = getMobileBookmarksFolder();
649 mLastModifiedBookmarkFolderId = lastModified != null ? lastModified.id() :
655 private void populateNodeImages(BookmarkNode node, boolean favicon, boolean thumbnail) {
656 if (node == null || node.type() != Type.URL) return;
659 node.setFavicon(nativeGetFaviconOrTouchIcon(mNativeChromeBrowserProvider, node.url()));
663 node.setThumbnail(nativeGetThumbnail(mNativeChromeBrowserProvider, node.url()));
667 private BookmarkNode getMobileBookmarksFolder() {
668 if (mMobileBookmarksFolder == null) {
669 mMobileBookmarksFolder = nativeGetMobileBookmarksFolder(mNativeChromeBrowserProvider);
671 return mMobileBookmarksFolder;
674 protected long getMobileBookmarksFolderId() {
675 BookmarkNode mobileBookmarks = getMobileBookmarksFolder();
676 return mobileBookmarks != null ? mobileBookmarks.id() : INVALID_BOOKMARK_ID;
679 private boolean isBookmarkInMobileBookmarksBranch(long nodeId) {
680 if (nodeId <= 0) return false;
681 return nativeIsBookmarkInMobileBookmarksBranch(mNativeChromeBrowserProvider, nodeId);
684 static String argKey(int i) {
689 public Bundle call(String method, String arg, Bundle extras) {
690 // TODO(shashishekhar): Refactor this code into a separate class.
691 // Caller must have the READ_WRITE_BOOKMARK_FOLDERS permission.
692 getContext().enforcePermission(getReadWritePermissionNameForBookmarkFolders(),
693 Binder.getCallingPid(), Binder.getCallingUid(), TAG);
694 if (!canHandleContentProviderApiCall()) return null;
695 if (method == null || extras == null) return null;
697 Bundle result = new Bundle();
698 if (CLIENT_API_BOOKMARK_NODE_EXISTS.equals(method)) {
699 result.putBoolean(CLIENT_API_RESULT_KEY,
700 bookmarkNodeExists(extras.getLong(argKey(0))));
701 } else if (CLIENT_API_CREATE_BOOKMARKS_FOLDER_ONCE.equals(method)) {
702 result.putLong(CLIENT_API_RESULT_KEY,
703 createBookmarksFolderOnce(extras.getString(argKey(0)),
704 extras.getLong(argKey(1))));
705 } else if (CLIENT_API_GET_EDITABLE_BOOKMARK_FOLDER_HIERARCHY.equals(method)) {
706 result.putParcelable(CLIENT_API_RESULT_KEY, getEditableBookmarkFolderHierarchy());
707 } else if (CLIENT_API_GET_BOOKMARK_NODE.equals(method)) {
708 result.putParcelable(CLIENT_API_RESULT_KEY,
709 getBookmarkNode(extras.getLong(argKey(0)),
710 extras.getBoolean(argKey(1)),
711 extras.getBoolean(argKey(2)),
712 extras.getBoolean(argKey(3)),
713 extras.getBoolean(argKey(4))));
714 } else if (CLIENT_API_GET_DEFAULT_BOOKMARK_FOLDER.equals(method)) {
715 result.putParcelable(CLIENT_API_RESULT_KEY, getDefaultBookmarkFolder());
716 } else if (method.equals(CLIENT_API_GET_MOBILE_BOOKMARKS_FOLDER_ID)) {
717 result.putLong(CLIENT_API_RESULT_KEY, getMobileBookmarksFolderId());
718 } else if (CLIENT_API_IS_BOOKMARK_IN_MOBILE_BOOKMARKS_BRANCH.equals(method)) {
719 result.putBoolean(CLIENT_API_RESULT_KEY,
720 isBookmarkInMobileBookmarksBranch(extras.getLong(argKey(0))));
721 } else if (CLIENT_API_DELETE_ALL_USER_BOOKMARKS.equals(method)) {
722 nativeRemoveAllUserBookmarks(mNativeChromeBrowserProvider);
724 Log.w(TAG, "Received invalid method " + method);
732 * Checks whether Chrome is sufficiently initialized to handle a call to the
733 * ChromeBrowserProvider.
735 private boolean canHandleContentProviderApiCall() {
736 mContentProviderApiCalled = true;
738 if (isInUiThread()) return false;
739 if (!ensureNativeChromeLoaded()) return false;
744 * The type of a BookmarkNode.
755 * Simple Data Object representing the chrome bookmark node.
757 public static class BookmarkNode implements Parcelable {
758 private final long mId;
759 private final String mName;
760 private final String mUrl;
761 private final Type mType;
762 private final BookmarkNode mParent;
763 private final List<BookmarkNode> mChildren = new ArrayList<BookmarkNode>();
765 // Favicon and thumbnail optionally set in a 2-step procedure.
766 private byte[] mFavicon;
767 private byte[] mThumbnail;
769 /** Used to pass structured data back from the native code. */
771 public BookmarkNode(long id, Type type, String name, String url, BookmarkNode parent) {
780 * @return The id of this bookmark entry.
787 * @return The name of this bookmark entry.
789 public String name() {
794 * @return The URL of this bookmark entry.
796 public String url() {
801 * @return The type of this bookmark entry.
808 * @return The bookmark favicon, if any.
810 public byte[] favicon() {
815 * @return The bookmark thumbnail, if any.
817 public byte[] thumbnail() {
822 * @return The parent folder of this bookmark entry.
824 public BookmarkNode parent() {
829 * Adds a child to this node.
832 * Used solely by the native code.
835 @CalledByNativeUnchecked("BookmarkNode")
836 public void addChild(BookmarkNode child) {
837 mChildren.add(child);
841 * @return The child bookmark nodes of this node.
843 public List<BookmarkNode> children() {
848 * @return Whether this node represents a bookmarked URL or not.
850 public boolean isUrl() {
855 * @return true if the two individual nodes contain the same information.
856 * The existence of parent and children nodes is checked, but their contents are not.
858 public boolean equalContents(BookmarkNode node) {
859 return node != null &&
861 !(mName == null ^ node.mName == null) &&
862 (mName == null || mName.equals(node.mName)) &&
863 !(mUrl == null ^ node.mUrl == null) &&
864 (mUrl == null || mUrl.equals(node.mUrl)) &&
865 mType == node.mType &&
866 byteArrayEqual(mFavicon, node.mFavicon) &&
867 byteArrayEqual(mThumbnail, node.mThumbnail) &&
868 !(mParent == null ^ node.mParent == null) &&
869 children().size() == node.children().size();
872 private static boolean byteArrayEqual(byte[] byte1, byte[] byte2) {
873 if (byte1 == null && byte2 != null) return byte2.length == 0;
874 if (byte2 == null && byte1 != null) return byte1.length == 0;
875 return Arrays.equals(byte1, byte2);
878 @CalledByNative("BookmarkNode")
879 private static BookmarkNode create(
880 long id, int type, String name, String url, BookmarkNode parent) {
881 return new BookmarkNode(id, Type.values()[type], name, url, parent);
885 public void setFavicon(byte[] favicon) {
890 public void setThumbnail(byte[] thumbnail) {
891 mThumbnail = thumbnail;
895 public int describeContents() {
900 public void writeToParcel(Parcel dest, int flags) {
901 // Write the current node id.
904 // Serialize the full hierarchy from the root.
905 getHierarchyRoot().writeNodeContentsRecursive(dest);
909 public BookmarkNode getHierarchyRoot() {
910 BookmarkNode root = this;
911 while (root.parent() != null) {
912 root = root.parent();
917 private void writeNodeContentsRecursive(Parcel dest) {
918 writeNodeContents(dest);
919 dest.writeInt(mChildren.size());
920 for (BookmarkNode child : mChildren) {
921 child.writeNodeContentsRecursive(dest);
925 private void writeNodeContents(Parcel dest) {
927 dest.writeString(mName);
928 dest.writeString(mUrl);
929 dest.writeInt(mType.ordinal());
930 dest.writeByteArray(mFavicon);
931 dest.writeByteArray(mThumbnail);
932 dest.writeLong(mParent != null ? mParent.mId : INVALID_BOOKMARK_ID);
935 public static final Creator<BookmarkNode> CREATOR = new Creator<BookmarkNode>() {
936 private HashMap<Long, BookmarkNode> mNodeMap;
939 public BookmarkNode createFromParcel(Parcel source) {
940 mNodeMap = new HashMap<Long, BookmarkNode>();
941 long currentNodeId = source.readLong();
942 readNodeContentsRecursive(source);
943 BookmarkNode node = getNode(currentNodeId);
949 public BookmarkNode[] newArray(int size) {
950 return new BookmarkNode[size];
953 private BookmarkNode getNode(long id) {
954 if (id == INVALID_BOOKMARK_ID) return null;
955 Long nodeId = Long.valueOf(id);
956 if (!mNodeMap.containsKey(nodeId)) {
957 Log.e(TAG, "Invalid BookmarkNode hierarchy. Unknown id " + id);
960 return mNodeMap.get(nodeId);
963 private BookmarkNode readNodeContents(Parcel source) {
964 long id = source.readLong();
965 String name = source.readString();
966 String url = source.readString();
967 int type = source.readInt();
968 byte[] favicon = source.createByteArray();
969 byte[] thumbnail = source.createByteArray();
970 long parentId = source.readLong();
971 if (type < 0 || type >= Type.values().length) {
972 Log.w(TAG, "Invalid node type ordinal value.");
976 BookmarkNode node = new BookmarkNode(id, Type.values()[type], name, url,
978 node.setFavicon(favicon);
979 node.setThumbnail(thumbnail);
983 private BookmarkNode readNodeContentsRecursive(Parcel source) {
984 BookmarkNode node = readNodeContents(source);
985 if (node == null) return null;
987 Long nodeId = Long.valueOf(node.id());
988 if (mNodeMap.containsKey(nodeId)) {
989 Log.e(TAG, "Invalid BookmarkNode hierarchy. Duplicate id " + node.id());
992 mNodeMap.put(nodeId, node);
994 int numChildren = source.readInt();
995 for (int i = 0; i < numChildren; ++i) {
996 node.addChild(readNodeContentsRecursive(source));
1004 private long addBookmarkFromAPI(ContentValues values) {
1005 BookmarkRow row = BookmarkRow.fromContentValues(values);
1006 if (row.mUrl == null) {
1007 throw new IllegalArgumentException("Must have a bookmark URL");
1009 return nativeAddBookmarkFromAPI(mNativeChromeBrowserProvider,
1010 row.mUrl, row.mCreated, row.mIsBookmark, row.mDate, row.mFavicon,
1011 row.mTitle, row.mVisits, row.mParentId);
1014 private Cursor queryBookmarkFromAPI(String[] projectionIn, String selection,
1015 String[] selectionArgs, String sortOrder) {
1016 String[] projection = null;
1017 if (projectionIn == null || projectionIn.length == 0) {
1018 projection = BOOKMARK_DEFAULT_PROJECTION;
1020 projection = projectionIn;
1023 return nativeQueryBookmarkFromAPI(mNativeChromeBrowserProvider, projection, selection,
1024 selectionArgs, sortOrder);
1027 private int updateBookmarkFromAPI(ContentValues values, String selection,
1028 String[] selectionArgs) {
1029 BookmarkRow row = BookmarkRow.fromContentValues(values);
1030 return nativeUpdateBookmarkFromAPI(mNativeChromeBrowserProvider,
1031 row.mUrl, row.mCreated, row.mIsBookmark, row.mDate,
1032 row.mFavicon, row.mTitle, row.mVisits, row.mParentId, selection, selectionArgs);
1035 private int removeBookmarkFromAPI(String selection, String[] selectionArgs) {
1036 return nativeRemoveBookmarkFromAPI(mNativeChromeBrowserProvider, selection, selectionArgs);
1039 private int removeHistoryFromAPI(String selection, String[] selectionArgs) {
1040 return nativeRemoveHistoryFromAPI(mNativeChromeBrowserProvider, selection, selectionArgs);
1044 private void onBookmarkChanged() {
1045 notifyChange(buildAPIContentUri(getContext(), BOOKMARKS_PATH));
1049 private void onSearchTermChanged() {
1050 notifyChange(buildAPIContentUri(getContext(), SEARCHES_PATH));
1053 private long addSearchTermFromAPI(ContentValues values) {
1054 SearchRow row = SearchRow.fromContentValues(values);
1055 if (row.mTerm == null) {
1056 throw new IllegalArgumentException("Must have a search term");
1058 return nativeAddSearchTermFromAPI(mNativeChromeBrowserProvider, row.mTerm, row.mDate);
1061 private int updateSearchTermFromAPI(ContentValues values, String selection,
1062 String[] selectionArgs) {
1063 SearchRow row = SearchRow.fromContentValues(values);
1064 return nativeUpdateSearchTermFromAPI(mNativeChromeBrowserProvider,
1065 row.mTerm, row.mDate, selection, selectionArgs);
1068 private Cursor querySearchTermFromAPI(String[] projectionIn, String selection,
1069 String[] selectionArgs, String sortOrder) {
1070 String[] projection = null;
1071 if (projectionIn == null || projectionIn.length == 0) {
1072 projection = android.provider.Browser.SEARCHES_PROJECTION;
1074 projection = projectionIn;
1076 return nativeQuerySearchTermFromAPI(mNativeChromeBrowserProvider, projection, selection,
1077 selectionArgs, sortOrder);
1080 private int removeSearchFromAPI(String selection, String[] selectionArgs) {
1081 return nativeRemoveSearchTermFromAPI(mNativeChromeBrowserProvider,
1082 selection, selectionArgs);
1085 private static boolean isInUiThread() {
1086 if (!ThreadUtils.runningOnUiThread()) return false;
1088 if (!"REL".equals(Build.VERSION.CODENAME)) {
1089 throw new IllegalStateException("Shouldn't run in the UI thread");
1092 Log.w(TAG, "ChromeBrowserProvider methods cannot be called from the UI thread.");
1096 private static Uri buildContentUri(String authority, String path) {
1097 return Uri.parse("content://" + authority + "/" + path);
1100 private static Uri buildAPIContentUri(Context context, String path) {
1101 return buildContentUri(context.getPackageName() + API_AUTHORITY_SUFFIX, path);
1104 private static String buildWhereClause(long id, String selection) {
1105 StringBuffer sb = new StringBuffer();
1106 sb.append(BaseColumns._ID);
1109 if (!TextUtils.isEmpty(selection)) {
1110 sb.append(" AND (");
1111 sb.append(selection);
1114 return sb.toString();
1117 private static String buildHistoryWhereClause(long id, String selection) {
1118 return buildWhereClause(id, buildBookmarkWhereClause(selection, false));
1121 private static String buildHistoryWhereClause(String selection) {
1122 return buildBookmarkWhereClause(selection, false);
1126 * @return a SQL where class which is inserted the bookmark condition.
1128 private static String buildBookmarkWhereClause(String selection, boolean isBookmark) {
1129 StringBuffer sb = new StringBuffer();
1130 sb.append(BookmarkColumns.BOOKMARK);
1131 sb.append(isBookmark ? " = 1 " : " = 0");
1132 if (!TextUtils.isEmpty(selection)) {
1133 sb.append(" AND (");
1134 sb.append(selection);
1137 return sb.toString();
1140 private static String buildBookmarkWhereClause(long id, String selection) {
1141 return buildWhereClause(id, buildBookmarkWhereClause(selection, true));
1144 private static String buildBookmarkWhereClause(String selection) {
1145 return buildBookmarkWhereClause(selection, true);
1148 // Wrap the value of BookmarkColumn.
1149 private static class BookmarkRow {
1150 Boolean mIsBookmark;
1159 static BookmarkRow fromContentValues(ContentValues values) {
1160 BookmarkRow row = new BookmarkRow();
1161 if (values.containsKey(BookmarkColumns.URL)) {
1162 row.mUrl = values.getAsString(BookmarkColumns.URL);
1164 if (values.containsKey(BookmarkColumns.BOOKMARK)) {
1165 row.mIsBookmark = values.getAsInteger(BookmarkColumns.BOOKMARK) != 0;
1167 if (values.containsKey(BookmarkColumns.CREATED)) {
1168 row.mCreated = values.getAsLong(BookmarkColumns.CREATED);
1170 if (values.containsKey(BookmarkColumns.DATE)) {
1171 row.mDate = values.getAsLong(BookmarkColumns.DATE);
1173 if (values.containsKey(BookmarkColumns.FAVICON)) {
1174 row.mFavicon = values.getAsByteArray(BookmarkColumns.FAVICON);
1175 // We need to know that the caller set the favicon column.
1176 if (row.mFavicon == null) {
1177 row.mFavicon = new byte[0];
1180 if (values.containsKey(BookmarkColumns.TITLE)) {
1181 row.mTitle = values.getAsString(BookmarkColumns.TITLE);
1183 if (values.containsKey(BookmarkColumns.VISITS)) {
1184 row.mVisits = values.getAsInteger(BookmarkColumns.VISITS);
1186 if (values.containsKey(BOOKMARK_PARENT_ID_PARAM)) {
1187 row.mParentId = values.getAsLong(BOOKMARK_PARENT_ID_PARAM);
1193 // Wrap the value of SearchColumn.
1194 private static class SearchRow {
1198 static SearchRow fromContentValues(ContentValues values) {
1199 SearchRow row = new SearchRow();
1200 if (values.containsKey(SearchColumns.SEARCH)) {
1201 row.mTerm = values.getAsString(SearchColumns.SEARCH);
1203 if (values.containsKey(SearchColumns.DATE)) {
1204 row.mDate = values.getAsLong(SearchColumns.DATE);
1211 * Returns true if the native side of the class is initialized.
1213 protected boolean isNativeSideInitialized() {
1214 return mNativeChromeBrowserProvider != 0;
1218 * Make sure chrome is running. This method mustn't run on UI thread.
1220 * @return Whether the native chrome process is running successfully once this has returned.
1222 private boolean ensureNativeChromeLoaded() {
1223 ensureUriMatcherInitialized();
1225 synchronized (mLoadNativeLock) {
1226 if (mNativeChromeBrowserProvider != 0) return true;
1228 final AtomicBoolean retVal = new AtomicBoolean(true);
1229 ThreadUtils.runOnUiThreadBlocking(new Runnable() {
1232 retVal.set(ensureNativeChromeLoadedOnUIThread());
1235 return retVal.get();
1240 * This method should only run on UI thread.
1242 protected boolean ensureNativeChromeLoadedOnUIThread() {
1243 if (isNativeSideInitialized()) return true;
1244 mNativeChromeBrowserProvider = nativeInit();
1245 return isNativeSideInitialized();
1249 protected void finalize() throws Throwable {
1251 // Tests might try to destroy this in the wrong thread.
1252 ThreadUtils.runOnUiThreadBlocking(new Runnable() {
1255 ensureNativeChromeDestroyedOnUIThread();
1264 * This method should only run on UI thread.
1266 private void ensureNativeChromeDestroyedOnUIThread() {
1267 if (isNativeSideInitialized()) {
1268 nativeDestroy(mNativeChromeBrowserProvider);
1269 mNativeChromeBrowserProvider = 0;
1273 @SuppressLint("NewApi")
1274 private void notifyChange(final Uri uri) {
1275 // If the calling user is different than current one, we need to post a
1276 // task to notify change, otherwise, a system level hidden permission
1277 // INTERACT_ACROSS_USERS_FULL is needed.
1278 // The related APIs were added in API 17, it should be safe to fallback to
1279 // normal way for notifying change, because caller can't be other users in
1280 // devices whose API level is less than API 17.
1281 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
1282 UserHandle callingUserHandle = Binder.getCallingUserHandle();
1283 if (callingUserHandle != null &&
1284 !callingUserHandle.equals(android.os.Process.myUserHandle())) {
1285 ThreadUtils.postOnUiThread(new Runnable() {
1288 getContext().getContentResolver().notifyChange(uri, null);
1294 getContext().getContentResolver().notifyChange(uri, null);
1297 private native long nativeInit();
1298 private native void nativeDestroy(long nativeChromeBrowserProvider);
1300 // Public API native methods.
1301 private native long nativeAddBookmark(long nativeChromeBrowserProvider,
1302 String url, String title, boolean isFolder, long parentId);
1304 private native int nativeRemoveBookmark(long nativeChromeBrowserProvider, long id);
1306 private native int nativeUpdateBookmark(long nativeChromeBrowserProvider,
1307 long id, String url, String title, long parentId);
1309 private native long nativeAddBookmarkFromAPI(long nativeChromeBrowserProvider,
1310 String url, Long created, Boolean isBookmark, Long date, byte[] favicon,
1311 String title, Integer visits, long parentId);
1313 private native SQLiteCursor nativeQueryBookmarkFromAPI(long nativeChromeBrowserProvider,
1314 String[] projection, String selection, String[] selectionArgs, String sortOrder);
1316 private native int nativeUpdateBookmarkFromAPI(long nativeChromeBrowserProvider,
1317 String url, Long created, Boolean isBookmark, Long date, byte[] favicon,
1318 String title, Integer visits, long parentId, String selection, String[] selectionArgs);
1320 private native int nativeRemoveBookmarkFromAPI(long nativeChromeBrowserProvider,
1321 String selection, String[] selectionArgs);
1323 private native int nativeRemoveHistoryFromAPI(long nativeChromeBrowserProvider,
1324 String selection, String[] selectionArgs);
1326 private native long nativeAddSearchTermFromAPI(long nativeChromeBrowserProvider,
1327 String term, Long date);
1329 private native SQLiteCursor nativeQuerySearchTermFromAPI(long nativeChromeBrowserProvider,
1330 String[] projection, String selection, String[] selectionArgs, String sortOrder);
1332 private native int nativeUpdateSearchTermFromAPI(long nativeChromeBrowserProvider,
1333 String search, Long date, String selection, String[] selectionArgs);
1335 private native int nativeRemoveSearchTermFromAPI(long nativeChromeBrowserProvider,
1336 String selection, String[] selectionArgs);
1338 // Client API native methods.
1339 private native boolean nativeBookmarkNodeExists(long nativeChromeBrowserProvider, long id);
1341 private native long nativeCreateBookmarksFolderOnce(long nativeChromeBrowserProvider,
1342 String title, long parentId);
1344 private native BookmarkNode nativeGetEditableBookmarkFolders(long nativeChromeBrowserProvider);
1346 private native void nativeRemoveAllUserBookmarks(long nativeChromeBrowserProvider);
1348 private native BookmarkNode nativeGetBookmarkNode(long nativeChromeBrowserProvider,
1349 long id, boolean getParent, boolean getChildren);
1351 private native BookmarkNode nativeGetMobileBookmarksFolder(long nativeChromeBrowserProvider);
1353 private native boolean nativeIsBookmarkInMobileBookmarksBranch(long nativeChromeBrowserProvider,
1356 private native byte[] nativeGetFaviconOrTouchIcon(long nativeChromeBrowserProvider, String url);
1358 private native byte[] nativeGetThumbnail(long nativeChromeBrowserProvider, String url);