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 org.chromium.base.CalledByNative;
33 import org.chromium.base.CalledByNativeUnchecked;
34 import org.chromium.base.ThreadUtils;
35 import org.chromium.base.VisibleForTesting;
36 import org.chromium.chrome.browser.database.SQLiteCursor;
37 import org.chromium.sync.notifier.SyncStatusHelper;
39 import java.util.ArrayList;
40 import java.util.Arrays;
41 import java.util.HashMap;
42 import java.util.List;
43 import java.util.Vector;
44 import java.util.concurrent.atomic.AtomicBoolean;
47 * This class provides access to user data stored in Chrome, such as bookmarks, most visited pages,
48 * etc. It is used to support android.provider.Browser.
50 public class ChromeBrowserProvider extends ContentProvider {
51 private static final String TAG = "ChromeBrowserProvider";
53 // The permission required for using the bookmark folders API. Android build system does
54 // not generate Manifest.java for java libraries, hence use the permission name string. When
55 // making changes to this permission, also update the permission in AndroidManifest.xml.
56 private static final String PERMISSION_READ_WRITE_BOOKMARKS = "READ_WRITE_BOOKMARK_FOLDERS";
58 // Defines the API methods that the Client can call by name.
59 static final String CLIENT_API_BOOKMARK_NODE_EXISTS = "BOOKMARK_NODE_EXISTS";
60 static final String CLIENT_API_CREATE_BOOKMARKS_FOLDER_ONCE = "CREATE_BOOKMARKS_FOLDER_ONCE";
61 static final String CLIENT_API_GET_EDITABLE_BOOKMARK_FOLDER_HIERARCHY =
62 "GET_EDITABLE_BOOKMARK_FOLDER_HIERARCHY";
63 static final String CLIENT_API_GET_BOOKMARK_NODE = "GET_BOOKMARK_NODE";
64 static final String CLIENT_API_GET_DEFAULT_BOOKMARK_FOLDER = "GET_DEFAULT_BOOKMARK_FOLDER";
65 static final String CLIENT_API_GET_MOBILE_BOOKMARKS_FOLDER_ID =
66 "GET_MOBILE_BOOKMARKS_FOLDER_ID";
67 static final String CLIENT_API_IS_BOOKMARK_IN_MOBILE_BOOKMARKS_BRANCH =
68 "IS_BOOKMARK_IN_MOBILE_BOOKMARKS_BRANCH";
69 static final String CLIENT_API_DELETE_ALL_USER_BOOKMARKS = "DELETE_ALL_USER_BOOKMARKS";
70 static final String CLIENT_API_RESULT_KEY = "result";
73 // Defines Chrome's API authority, so it can be run and tested
75 private static final String API_AUTHORITY_SUFFIX = ".browser";
77 private static final String BROWSER_CONTRACT_API_AUTHORITY =
78 "com.google.android.apps.chrome.browser-contract";
80 // These values are taken from android.provider.BrowserContract.java since
81 // that class is hidden from the SDK.
82 private static final String BROWSER_CONTRACT_AUTHORITY = "com.android.browser";
83 private static final String BROWSER_CONTRACT_HISTORY_CONTENT_TYPE =
84 "vnd.android.cursor.dir/browser-history";
85 private static final String BROWSER_CONTRACT_HISTORY_CONTENT_ITEM_TYPE =
86 "vnd.android.cursor.item/browser-history";
87 private static final String BROWSER_CONTRACT_BOOKMARK_CONTENT_TYPE =
88 "vnd.android.cursor.dir/bookmark";
89 private static final String BROWSER_CONTRACT_BOOKMARK_CONTENT_ITEM_TYPE =
90 "vnd.android.cursor.item/bookmark";
91 private static final String BROWSER_CONTRACT_SEARCH_CONTENT_TYPE =
92 "vnd.android.cursor.dir/searches";
93 private static final String BROWSER_CONTRACT_SEARCH_CONTENT_ITEM_TYPE =
94 "vnd.android.cursor.item/searches";
96 // This Authority is for internal interface. It's concatenated with
97 // Context.getPackageName() so that we can install different channels
98 // SxS and have different authorities.
99 private static final String AUTHORITY_SUFFIX = ".ChromeBrowserProvider";
100 private static final String BOOKMARKS_PATH = "bookmarks";
101 private static final String SEARCHES_PATH = "searches";
102 private static final String HISTORY_PATH = "history";
103 private static final String COMBINED_PATH = "combined";
104 private static final String BOOKMARK_FOLDER_PATH = "hierarchy";
106 public static final Uri BROWSER_CONTRACTS_BOOKMAKRS_API_URI = buildContentUri(
107 BROWSER_CONTRACT_API_AUTHORITY, BOOKMARKS_PATH);
109 public static final Uri BROWSER_CONTRACTS_SEARCHES_API_URI = buildContentUri(
110 BROWSER_CONTRACT_API_AUTHORITY, SEARCHES_PATH);
112 public static final Uri BROWSER_CONTRACTS_HISTORY_API_URI = buildContentUri(
113 BROWSER_CONTRACT_API_AUTHORITY, HISTORY_PATH);
115 public static final Uri BROWSER_CONTRACTS_COMBINED_API_URI = buildContentUri(
116 BROWSER_CONTRACT_API_AUTHORITY, COMBINED_PATH);
118 /** The parameter used to specify a bookmark parent ID in ContentValues. */
119 public static final String BOOKMARK_PARENT_ID_PARAM = "parentId";
121 /** The parameter used to specify whether this is a bookmark folder. */
122 public static final String BOOKMARK_IS_FOLDER_PARAM = "isFolder";
125 * Invalid ID value for the Android ContentProvider API calls.
126 * The value 0 is intentional: if the ID represents a bookmark node then it's the root node
127 * and not accessible. Otherwise it represents a SQLite row id, so 0 is also invalid.
129 public static final long INVALID_CONTENT_PROVIDER_ID = 0;
131 // ID used to indicate an invalid id for bookmark nodes.
132 // Client API queries should use ChromeBrowserProviderClient.INVALID_BOOKMARK_ID.
133 static final long INVALID_BOOKMARK_ID = -1;
135 private static final String LAST_MODIFIED_BOOKMARK_FOLDER_ID_KEY = "last_bookmark_folder_id";
137 private static final int URI_MATCH_BOOKMARKS = 0;
138 private static final int URI_MATCH_BOOKMARKS_ID = 1;
139 private static final int URL_MATCH_API_BOOKMARK = 2;
140 private static final int URL_MATCH_API_BOOKMARK_ID = 3;
141 private static final int URL_MATCH_API_SEARCHES = 4;
142 private static final int URL_MATCH_API_SEARCHES_ID = 5;
143 private static final int URL_MATCH_API_HISTORY_CONTENT = 6;
144 private static final int URL_MATCH_API_HISTORY_CONTENT_ID = 7;
145 private static final int URL_MATCH_API_BOOKMARK_CONTENT = 8;
146 private static final int URL_MATCH_API_BOOKMARK_CONTENT_ID = 9;
147 private static final int URL_MATCH_BOOKMARK_SUGGESTIONS_ID = 10;
148 private static final int URL_MATCH_BOOKMARK_HISTORY_SUGGESTIONS_ID = 11;
150 // TODO : Using Android.provider.Browser.HISTORY_PROJECTION once THUMBNAIL,
151 // TOUCH_ICON, and USER_ENTERED fields are supported.
152 private static final String[] BOOKMARK_DEFAULT_PROJECTION = new String[] {
153 BookmarkColumns._ID, BookmarkColumns.URL, BookmarkColumns.VISITS,
154 BookmarkColumns.DATE, BookmarkColumns.BOOKMARK, BookmarkColumns.TITLE,
155 BookmarkColumns.FAVICON, BookmarkColumns.CREATED
158 private static final String[] SUGGEST_PROJECTION = new String[] {
160 BookmarkColumns.TITLE,
162 BookmarkColumns.DATE,
163 BookmarkColumns.BOOKMARK
166 private final Object mInitializeUriMatcherLock = new Object();
167 private final Object mLoadNativeLock = new Object();
168 private UriMatcher mUriMatcher;
169 private long mLastModifiedBookmarkFolderId = INVALID_BOOKMARK_ID;
170 private long mNativeChromeBrowserProvider;
171 private BookmarkNode mMobileBookmarksFolder;
174 * Records whether we've received a call to one of the public ContentProvider APIs.
176 protected boolean mContentProviderApiCalled;
178 private void ensureUriMatcherInitialized() {
179 synchronized (mInitializeUriMatcherLock) {
180 if (mUriMatcher != null) return;
182 mUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
184 String authority = getContext().getPackageName() + AUTHORITY_SUFFIX;
185 mUriMatcher.addURI(authority, BOOKMARKS_PATH, URI_MATCH_BOOKMARKS);
186 mUriMatcher.addURI(authority, BOOKMARKS_PATH + "/#", URI_MATCH_BOOKMARKS_ID);
187 // The internal authority for public APIs
188 String apiAuthority = getContext().getPackageName() + API_AUTHORITY_SUFFIX;
189 mUriMatcher.addURI(apiAuthority, BOOKMARKS_PATH, URL_MATCH_API_BOOKMARK);
190 mUriMatcher.addURI(apiAuthority, BOOKMARKS_PATH + "/#", URL_MATCH_API_BOOKMARK_ID);
191 mUriMatcher.addURI(apiAuthority, SEARCHES_PATH, URL_MATCH_API_SEARCHES);
192 mUriMatcher.addURI(apiAuthority, SEARCHES_PATH + "/#", URL_MATCH_API_SEARCHES_ID);
193 mUriMatcher.addURI(apiAuthority, HISTORY_PATH, URL_MATCH_API_HISTORY_CONTENT);
194 mUriMatcher.addURI(apiAuthority, HISTORY_PATH + "/#", URL_MATCH_API_HISTORY_CONTENT_ID);
195 mUriMatcher.addURI(apiAuthority, COMBINED_PATH, URL_MATCH_API_BOOKMARK);
196 mUriMatcher.addURI(apiAuthority, COMBINED_PATH + "/#", URL_MATCH_API_BOOKMARK_ID);
197 // The internal authority for BrowserContracts
198 mUriMatcher.addURI(BROWSER_CONTRACT_API_AUTHORITY, HISTORY_PATH,
199 URL_MATCH_API_HISTORY_CONTENT);
200 mUriMatcher.addURI(BROWSER_CONTRACT_API_AUTHORITY, HISTORY_PATH + "/#",
201 URL_MATCH_API_HISTORY_CONTENT_ID);
202 mUriMatcher.addURI(BROWSER_CONTRACT_API_AUTHORITY, COMBINED_PATH,
203 URL_MATCH_API_BOOKMARK);
204 mUriMatcher.addURI(BROWSER_CONTRACT_API_AUTHORITY, COMBINED_PATH + "/#",
205 URL_MATCH_API_BOOKMARK_ID);
206 mUriMatcher.addURI(BROWSER_CONTRACT_API_AUTHORITY, SEARCHES_PATH,
207 URL_MATCH_API_SEARCHES);
208 mUriMatcher.addURI(BROWSER_CONTRACT_API_AUTHORITY, SEARCHES_PATH + "/#",
209 URL_MATCH_API_SEARCHES_ID);
210 mUriMatcher.addURI(BROWSER_CONTRACT_API_AUTHORITY, BOOKMARKS_PATH,
211 URL_MATCH_API_BOOKMARK_CONTENT);
212 mUriMatcher.addURI(BROWSER_CONTRACT_API_AUTHORITY, BOOKMARKS_PATH + "/#",
213 URL_MATCH_API_BOOKMARK_CONTENT_ID);
214 // Added the Android Framework URIs, so the provider can easily switched
215 // by adding 'browser' and 'com.android.browser' in manifest.
216 // The Android's BrowserContract
217 mUriMatcher.addURI(BROWSER_CONTRACT_AUTHORITY, HISTORY_PATH,
218 URL_MATCH_API_HISTORY_CONTENT);
219 mUriMatcher.addURI(BROWSER_CONTRACT_AUTHORITY, HISTORY_PATH + "/#",
220 URL_MATCH_API_HISTORY_CONTENT_ID);
221 mUriMatcher.addURI(BROWSER_CONTRACT_AUTHORITY, "combined", URL_MATCH_API_BOOKMARK);
222 mUriMatcher.addURI(BROWSER_CONTRACT_AUTHORITY, "combined/#", URL_MATCH_API_BOOKMARK_ID);
223 mUriMatcher.addURI(BROWSER_CONTRACT_AUTHORITY, SEARCHES_PATH, URL_MATCH_API_SEARCHES);
224 mUriMatcher.addURI(BROWSER_CONTRACT_AUTHORITY, SEARCHES_PATH + "/#",
225 URL_MATCH_API_SEARCHES_ID);
226 mUriMatcher.addURI(BROWSER_CONTRACT_AUTHORITY, BOOKMARKS_PATH,
227 URL_MATCH_API_BOOKMARK_CONTENT);
228 mUriMatcher.addURI(BROWSER_CONTRACT_AUTHORITY, BOOKMARKS_PATH + "/#",
229 URL_MATCH_API_BOOKMARK_CONTENT_ID);
230 // For supporting android.provider.browser.BookmarkColumns and
232 mUriMatcher.addURI("browser", BOOKMARKS_PATH, URL_MATCH_API_BOOKMARK);
233 mUriMatcher.addURI("browser", BOOKMARKS_PATH + "/#", URL_MATCH_API_BOOKMARK_ID);
234 mUriMatcher.addURI("browser", SEARCHES_PATH, URL_MATCH_API_SEARCHES);
235 mUriMatcher.addURI("browser", SEARCHES_PATH + "/#", URL_MATCH_API_SEARCHES_ID);
237 mUriMatcher.addURI(apiAuthority,
238 BOOKMARKS_PATH + "/" + SearchManager.SUGGEST_URI_PATH_QUERY,
239 URL_MATCH_BOOKMARK_SUGGESTIONS_ID);
240 mUriMatcher.addURI(apiAuthority,
241 SearchManager.SUGGEST_URI_PATH_QUERY,
242 URL_MATCH_BOOKMARK_HISTORY_SUGGESTIONS_ID);
247 public boolean onCreate() {
248 // Pre-load shared preferences object, this happens on a separate thread
249 PreferenceManager.getDefaultSharedPreferences(getContext());
254 * Lazily fetches the last modified bookmark folder id.
256 private long getLastModifiedBookmarkFolderId() {
257 if (mLastModifiedBookmarkFolderId == INVALID_BOOKMARK_ID) {
258 SharedPreferences sharedPreferences =
259 PreferenceManager.getDefaultSharedPreferences(getContext());
260 mLastModifiedBookmarkFolderId = sharedPreferences.getLong(
261 LAST_MODIFIED_BOOKMARK_FOLDER_ID_KEY, INVALID_BOOKMARK_ID);
263 return mLastModifiedBookmarkFolderId;
266 private String buildSuggestWhere(String selection, int argc) {
267 StringBuilder sb = new StringBuilder(selection);
268 for (int i = 0; i < argc - 1; i++) {
270 sb.append(selection);
272 return sb.toString();
275 private String getReadWritePermissionNameForBookmarkFolders() {
276 return getContext().getApplicationContext().getPackageName() + ".permission."
277 + PERMISSION_READ_WRITE_BOOKMARKS;
280 private Cursor getBookmarkHistorySuggestions(String selection, String[] selectionArgs,
281 String sortOrder, boolean excludeHistory) {
282 boolean matchTitles = false;
283 Vector<String> args = new Vector<String>();
284 String like = selectionArgs[0] + "%";
285 if (selectionArgs[0].startsWith("http") || selectionArgs[0].startsWith("file")) {
288 // Match against common URL prefixes.
289 args.add("http://" + like);
290 args.add("https://" + like);
291 args.add("http://www." + like);
292 args.add("https://www." + like);
293 args.add("file://" + like);
297 StringBuilder urlWhere = new StringBuilder("(");
298 urlWhere.append(buildSuggestWhere(selection, args.size()));
301 urlWhere.append(" OR title LIKE ?");
303 urlWhere.append(")");
305 if (excludeHistory) {
306 urlWhere.append(" AND bookmark=?");
310 selectionArgs = args.toArray(selectionArgs);
311 Cursor cursor = queryBookmarkFromAPI(SUGGEST_PROJECTION, urlWhere.toString(),
312 selectionArgs, sortOrder);
313 return new ChromeBrowserProviderSuggestionsCursor(cursor);
317 * @see android.content.ContentUris#parseId(Uri)
318 * @return The id from a content URI or -1 if the URI has no id or is malformed.
320 private static long getContentUriId(Uri uri) {
322 return ContentUris.parseId(uri);
323 } catch (UnsupportedOperationException e) {
325 } catch (NumberFormatException e) {
331 public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
333 if (!canHandleContentProviderApiCall()) return null;
335 // Check for invalid id values if provided.
336 long bookmarkId = getContentUriId(uri);
337 if (bookmarkId == INVALID_CONTENT_PROVIDER_ID) return null;
339 int match = mUriMatcher.match(uri);
340 Cursor cursor = null;
342 case URL_MATCH_BOOKMARK_SUGGESTIONS_ID:
343 cursor = getBookmarkHistorySuggestions(selection, selectionArgs, sortOrder, true);
345 case URL_MATCH_BOOKMARK_HISTORY_SUGGESTIONS_ID:
346 cursor = getBookmarkHistorySuggestions(selection, selectionArgs, sortOrder, false);
348 case URL_MATCH_API_BOOKMARK:
349 cursor = queryBookmarkFromAPI(projection, selection, selectionArgs, sortOrder);
351 case URL_MATCH_API_BOOKMARK_ID:
352 cursor = queryBookmarkFromAPI(projection, buildWhereClause(bookmarkId, selection),
353 selectionArgs, sortOrder);
355 case URL_MATCH_API_SEARCHES:
356 cursor = querySearchTermFromAPI(projection, selection, selectionArgs, sortOrder);
358 case URL_MATCH_API_SEARCHES_ID:
359 cursor = querySearchTermFromAPI(projection, buildWhereClause(bookmarkId, selection),
360 selectionArgs, sortOrder);
362 case URL_MATCH_API_HISTORY_CONTENT:
363 cursor = queryBookmarkFromAPI(projection, buildHistoryWhereClause(selection),
364 selectionArgs, sortOrder);
366 case URL_MATCH_API_HISTORY_CONTENT_ID:
367 cursor = queryBookmarkFromAPI(projection,
368 buildHistoryWhereClause(bookmarkId, selection), selectionArgs, sortOrder);
370 case URL_MATCH_API_BOOKMARK_CONTENT:
371 cursor = queryBookmarkFromAPI(projection, buildBookmarkWhereClause(selection),
372 selectionArgs, sortOrder);
374 case URL_MATCH_API_BOOKMARK_CONTENT_ID:
375 cursor = queryBookmarkFromAPI(projection,
376 buildBookmarkWhereClause(bookmarkId, selection), selectionArgs, sortOrder);
379 throw new IllegalArgumentException(TAG + ": query - unknown URL uri = " + uri);
381 if (cursor == null) {
382 cursor = new MatrixCursor(new String[] { });
384 cursor.setNotificationUri(getContext().getContentResolver(), uri);
389 public Uri insert(Uri uri, ContentValues values) {
390 if (!canHandleContentProviderApiCall()) return null;
392 int match = mUriMatcher.match(uri);
396 case URI_MATCH_BOOKMARKS:
397 id = addBookmark(values);
398 if (id == INVALID_BOOKMARK_ID) return null;
400 case URL_MATCH_API_BOOKMARK_CONTENT:
401 values.put(BookmarkColumns.BOOKMARK, 1);
403 case URL_MATCH_API_BOOKMARK:
404 case URL_MATCH_API_HISTORY_CONTENT:
405 id = addBookmarkFromAPI(values);
406 if (id == INVALID_CONTENT_PROVIDER_ID) return null;
408 case URL_MATCH_API_SEARCHES:
409 id = addSearchTermFromAPI(values);
410 if (id == INVALID_CONTENT_PROVIDER_ID) return null;
413 throw new IllegalArgumentException(TAG + ": insert - unknown URL " + uri);
416 res = ContentUris.withAppendedId(uri, id);
422 public int delete(Uri uri, String selection, String[] selectionArgs) {
423 if (!canHandleContentProviderApiCall()) return 0;
425 // Check for invalid id values if provided.
426 long bookmarkId = getContentUriId(uri);
427 if (bookmarkId == INVALID_CONTENT_PROVIDER_ID) return 0;
429 int match = mUriMatcher.match(uri);
432 case URI_MATCH_BOOKMARKS_ID :
433 result = nativeRemoveBookmark(mNativeChromeBrowserProvider, bookmarkId);
435 case URL_MATCH_API_BOOKMARK_ID:
436 result = removeBookmarkFromAPI(
437 buildWhereClause(bookmarkId, selection), selectionArgs);
439 case URL_MATCH_API_BOOKMARK:
440 result = removeBookmarkFromAPI(selection, selectionArgs);
442 case URL_MATCH_API_SEARCHES_ID:
443 result = removeSearchFromAPI(buildWhereClause(bookmarkId, selection),
446 case URL_MATCH_API_SEARCHES:
447 result = removeSearchFromAPI(selection, selectionArgs);
449 case URL_MATCH_API_HISTORY_CONTENT:
450 result = removeHistoryFromAPI(selection, selectionArgs);
452 case URL_MATCH_API_HISTORY_CONTENT_ID:
453 result = removeHistoryFromAPI(buildWhereClause(bookmarkId, selection),
456 case URL_MATCH_API_BOOKMARK_CONTENT:
457 result = removeBookmarkFromAPI(buildBookmarkWhereClause(selection), selectionArgs);
459 case URL_MATCH_API_BOOKMARK_CONTENT_ID:
460 result = removeBookmarkFromAPI(buildBookmarkWhereClause(bookmarkId, selection),
464 throw new IllegalArgumentException(TAG + ": delete - unknown URL " + uri);
466 if (result != 0) notifyChange(uri);
471 public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
472 if (!canHandleContentProviderApiCall()) return 0;
474 // Check for invalid id values if provided.
475 long bookmarkId = getContentUriId(uri);
476 if (bookmarkId == INVALID_CONTENT_PROVIDER_ID) return 0;
478 int match = mUriMatcher.match(uri);
481 case URI_MATCH_BOOKMARKS_ID:
483 if (values.containsKey(Browser.BookmarkColumns.URL)) {
484 url = values.getAsString(Browser.BookmarkColumns.URL);
486 String title = values.getAsString(Browser.BookmarkColumns.TITLE);
487 long parentId = INVALID_BOOKMARK_ID;
488 if (values.containsKey(BOOKMARK_PARENT_ID_PARAM)) {
489 parentId = values.getAsLong(BOOKMARK_PARENT_ID_PARAM);
491 result = nativeUpdateBookmark(mNativeChromeBrowserProvider, bookmarkId, url, title,
493 updateLastModifiedBookmarkFolder(parentId);
495 case URL_MATCH_API_BOOKMARK_ID:
496 result = updateBookmarkFromAPI(values, buildWhereClause(bookmarkId, selection),
499 case URL_MATCH_API_BOOKMARK:
500 result = updateBookmarkFromAPI(values, selection, selectionArgs);
502 case URL_MATCH_API_SEARCHES_ID:
503 result = updateSearchTermFromAPI(values, buildWhereClause(bookmarkId, selection),
506 case URL_MATCH_API_SEARCHES:
507 result = updateSearchTermFromAPI(values, selection, selectionArgs);
509 case URL_MATCH_API_HISTORY_CONTENT:
510 result = updateBookmarkFromAPI(values, buildHistoryWhereClause(selection),
513 case URL_MATCH_API_HISTORY_CONTENT_ID:
514 result = updateBookmarkFromAPI(values,
515 buildHistoryWhereClause(bookmarkId, selection), selectionArgs);
517 case URL_MATCH_API_BOOKMARK_CONTENT:
518 result = updateBookmarkFromAPI(values, buildBookmarkWhereClause(selection),
521 case URL_MATCH_API_BOOKMARK_CONTENT_ID:
522 result = updateBookmarkFromAPI(values,
523 buildBookmarkWhereClause(bookmarkId, selection), selectionArgs);
526 throw new IllegalArgumentException(TAG + ": update - unknown URL " + uri);
528 if (result != 0) notifyChange(uri);
533 public String getType(Uri uri) {
534 ensureUriMatcherInitialized();
535 int match = mUriMatcher.match(uri);
537 case URI_MATCH_BOOKMARKS:
538 case URL_MATCH_API_BOOKMARK:
539 return BROWSER_CONTRACT_BOOKMARK_CONTENT_TYPE;
540 case URI_MATCH_BOOKMARKS_ID:
541 case URL_MATCH_API_BOOKMARK_ID:
542 return BROWSER_CONTRACT_BOOKMARK_CONTENT_ITEM_TYPE;
543 case URL_MATCH_API_SEARCHES:
544 return BROWSER_CONTRACT_SEARCH_CONTENT_TYPE;
545 case URL_MATCH_API_SEARCHES_ID:
546 return BROWSER_CONTRACT_SEARCH_CONTENT_ITEM_TYPE;
547 case URL_MATCH_API_HISTORY_CONTENT:
548 return BROWSER_CONTRACT_HISTORY_CONTENT_TYPE;
549 case URL_MATCH_API_HISTORY_CONTENT_ID:
550 return BROWSER_CONTRACT_HISTORY_CONTENT_ITEM_TYPE;
552 throw new IllegalArgumentException(TAG + ": getType - unknown URL " + uri);
556 private long addBookmark(ContentValues values) {
557 String url = values.getAsString(Browser.BookmarkColumns.URL);
558 String title = values.getAsString(Browser.BookmarkColumns.TITLE);
559 boolean isFolder = false;
560 if (values.containsKey(BOOKMARK_IS_FOLDER_PARAM)) {
561 isFolder = values.getAsBoolean(BOOKMARK_IS_FOLDER_PARAM);
563 long parentId = INVALID_BOOKMARK_ID;
564 if (values.containsKey(BOOKMARK_PARENT_ID_PARAM)) {
565 parentId = values.getAsLong(BOOKMARK_PARENT_ID_PARAM);
567 long id = nativeAddBookmark(mNativeChromeBrowserProvider, url, title, isFolder, parentId);
568 if (id == INVALID_BOOKMARK_ID) return id;
571 updateLastModifiedBookmarkFolder(id);
573 updateLastModifiedBookmarkFolder(parentId);
578 private void updateLastModifiedBookmarkFolder(long id) {
579 if (getLastModifiedBookmarkFolderId() == id) return;
581 mLastModifiedBookmarkFolderId = id;
582 SharedPreferences sharedPreferences =
583 PreferenceManager.getDefaultSharedPreferences(getContext());
584 sharedPreferences.edit()
585 .putLong(LAST_MODIFIED_BOOKMARK_FOLDER_ID_KEY, mLastModifiedBookmarkFolderId)
589 public static String getApiAuthority(Context context) {
590 return context.getPackageName() + API_AUTHORITY_SUFFIX;
593 public static String getInternalAuthority(Context context) {
594 return context.getPackageName() + AUTHORITY_SUFFIX;
597 public static Uri getBookmarksUri(Context context) {
598 return buildContentUri(getInternalAuthority(context), BOOKMARKS_PATH);
601 public static Uri getBookmarkFolderUri(Context context) {
602 return buildContentUri(getInternalAuthority(context), BOOKMARK_FOLDER_PATH);
605 public static Uri getBookmarksApiUri(Context context) {
606 return buildContentUri(getApiAuthority(context), BOOKMARKS_PATH);
609 public static Uri getSearchesApiUri(Context context) {
610 return buildContentUri(getApiAuthority(context), SEARCHES_PATH);
613 private boolean bookmarkNodeExists(long nodeId) {
614 if (nodeId < 0) return false;
615 return nativeBookmarkNodeExists(mNativeChromeBrowserProvider, nodeId);
618 private long createBookmarksFolderOnce(String title, long parentId) {
619 return nativeCreateBookmarksFolderOnce(mNativeChromeBrowserProvider, title, parentId);
622 private BookmarkNode getEditableBookmarkFolderHierarchy() {
623 return nativeGetEditableBookmarkFolders(mNativeChromeBrowserProvider);
626 protected BookmarkNode getBookmarkNode(long nodeId, boolean getParent, boolean getChildren,
627 boolean getFavicons, boolean getThumbnails) {
628 // Don't allow going up the hierarchy if sync is disabled and the requested node
629 // is the Mobile Bookmarks folder.
630 if (getParent && nodeId == getMobileBookmarksFolderId()
631 && !SyncStatusHelper.get(getContext()).isSyncEnabled()) {
635 BookmarkNode node = nativeGetBookmarkNode(mNativeChromeBrowserProvider, nodeId, getParent,
637 if (!getFavicons && !getThumbnails) return node;
639 // Favicons and thumbnails need to be populated separately as they are provided
640 // asynchronously by Chromium services other than the bookmark model.
641 if (node.parent() != null) populateNodeImages(node.parent(), getFavicons, getThumbnails);
642 for (BookmarkNode child : node.children()) {
643 populateNodeImages(child, getFavicons, getThumbnails);
649 private BookmarkNode getDefaultBookmarkFolder() {
650 // Try to access the bookmark folder last modified by us. If it doesn't exist anymore
651 // then use the synced node (Mobile Bookmarks).
652 BookmarkNode lastModified = getBookmarkNode(getLastModifiedBookmarkFolderId(), false, false,
654 if (lastModified == null || lastModified.isUrl()) {
655 lastModified = getMobileBookmarksFolder();
656 mLastModifiedBookmarkFolderId = lastModified != null ? lastModified.id() :
662 private void populateNodeImages(BookmarkNode node, boolean favicon, boolean thumbnail) {
663 if (node == null || node.type() != Type.URL) return;
666 node.setFavicon(nativeGetFaviconOrTouchIcon(mNativeChromeBrowserProvider, node.url()));
670 node.setThumbnail(nativeGetThumbnail(mNativeChromeBrowserProvider, node.url()));
674 private BookmarkNode getMobileBookmarksFolder() {
675 if (mMobileBookmarksFolder == null) {
676 mMobileBookmarksFolder = nativeGetMobileBookmarksFolder(mNativeChromeBrowserProvider);
678 return mMobileBookmarksFolder;
681 protected long getMobileBookmarksFolderId() {
682 BookmarkNode mobileBookmarks = getMobileBookmarksFolder();
683 return mobileBookmarks != null ? mobileBookmarks.id() : INVALID_BOOKMARK_ID;
686 private boolean isBookmarkInMobileBookmarksBranch(long nodeId) {
687 if (nodeId <= 0) return false;
688 return nativeIsBookmarkInMobileBookmarksBranch(mNativeChromeBrowserProvider, nodeId);
691 static String argKey(int i) {
696 public Bundle call(String method, String arg, Bundle extras) {
697 // TODO(shashishekhar): Refactor this code into a separate class.
698 // Caller must have the READ_WRITE_BOOKMARK_FOLDERS permission.
699 getContext().enforcePermission(getReadWritePermissionNameForBookmarkFolders(),
700 Binder.getCallingPid(), Binder.getCallingUid(), TAG);
701 if (!canHandleContentProviderApiCall()) return null;
702 if (method == null || extras == null) return null;
704 Bundle result = new Bundle();
705 if (CLIENT_API_BOOKMARK_NODE_EXISTS.equals(method)) {
706 result.putBoolean(CLIENT_API_RESULT_KEY,
707 bookmarkNodeExists(extras.getLong(argKey(0))));
708 } else if (CLIENT_API_CREATE_BOOKMARKS_FOLDER_ONCE.equals(method)) {
709 result.putLong(CLIENT_API_RESULT_KEY,
710 createBookmarksFolderOnce(extras.getString(argKey(0)),
711 extras.getLong(argKey(1))));
712 } else if (CLIENT_API_GET_EDITABLE_BOOKMARK_FOLDER_HIERARCHY.equals(method)) {
713 result.putParcelable(CLIENT_API_RESULT_KEY, getEditableBookmarkFolderHierarchy());
714 } else if (CLIENT_API_GET_BOOKMARK_NODE.equals(method)) {
715 result.putParcelable(CLIENT_API_RESULT_KEY,
716 getBookmarkNode(extras.getLong(argKey(0)),
717 extras.getBoolean(argKey(1)),
718 extras.getBoolean(argKey(2)),
719 extras.getBoolean(argKey(3)),
720 extras.getBoolean(argKey(4))));
721 } else if (CLIENT_API_GET_DEFAULT_BOOKMARK_FOLDER.equals(method)) {
722 result.putParcelable(CLIENT_API_RESULT_KEY, getDefaultBookmarkFolder());
723 } else if (method.equals(CLIENT_API_GET_MOBILE_BOOKMARKS_FOLDER_ID)) {
724 result.putLong(CLIENT_API_RESULT_KEY, getMobileBookmarksFolderId());
725 } else if (CLIENT_API_IS_BOOKMARK_IN_MOBILE_BOOKMARKS_BRANCH.equals(method)) {
726 result.putBoolean(CLIENT_API_RESULT_KEY,
727 isBookmarkInMobileBookmarksBranch(extras.getLong(argKey(0))));
728 } else if (CLIENT_API_DELETE_ALL_USER_BOOKMARKS.equals(method)) {
729 nativeRemoveAllUserBookmarks(mNativeChromeBrowserProvider);
731 Log.w(TAG, "Received invalid method " + method);
739 * Checks whether Chrome is sufficiently initialized to handle a call to the
740 * ChromeBrowserProvider.
742 private boolean canHandleContentProviderApiCall() {
743 mContentProviderApiCalled = true;
745 if (isInUiThread()) return false;
746 if (!ensureNativeChromeLoaded()) return false;
751 * The type of a BookmarkNode.
762 * Simple Data Object representing the chrome bookmark node.
764 public static class BookmarkNode implements Parcelable {
765 private final long mId;
766 private final String mName;
767 private final String mUrl;
768 private final Type mType;
769 private final BookmarkNode mParent;
770 private final List<BookmarkNode> mChildren = new ArrayList<BookmarkNode>();
772 // Favicon and thumbnail optionally set in a 2-step procedure.
773 private byte[] mFavicon;
774 private byte[] mThumbnail;
776 /** Used to pass structured data back from the native code. */
778 public BookmarkNode(long id, Type type, String name, String url, BookmarkNode parent) {
787 * @return The id of this bookmark entry.
794 * @return The name of this bookmark entry.
796 public String name() {
801 * @return The URL of this bookmark entry.
803 public String url() {
808 * @return The type of this bookmark entry.
815 * @return The bookmark favicon, if any.
817 public byte[] favicon() {
822 * @return The bookmark thumbnail, if any.
824 public byte[] thumbnail() {
829 * @return The parent folder of this bookmark entry.
831 public BookmarkNode parent() {
836 * Adds a child to this node.
839 * Used solely by the native code.
842 @CalledByNativeUnchecked("BookmarkNode")
843 public void addChild(BookmarkNode child) {
844 mChildren.add(child);
848 * @return The child bookmark nodes of this node.
850 public List<BookmarkNode> children() {
855 * @return Whether this node represents a bookmarked URL or not.
857 public boolean isUrl() {
862 * @return true if the two individual nodes contain the same information.
863 * The existence of parent and children nodes is checked, but their contents are not.
865 public boolean equalContents(BookmarkNode node) {
866 return node != null &&
868 !(mName == null ^ node.mName == null) &&
869 (mName == null || mName.equals(node.mName)) &&
870 !(mUrl == null ^ node.mUrl == null) &&
871 (mUrl == null || mUrl.equals(node.mUrl)) &&
872 mType == node.mType &&
873 byteArrayEqual(mFavicon, node.mFavicon) &&
874 byteArrayEqual(mThumbnail, node.mThumbnail) &&
875 !(mParent == null ^ node.mParent == null) &&
876 children().size() == node.children().size();
879 private static boolean byteArrayEqual(byte[] byte1, byte[] byte2) {
880 if (byte1 == null && byte2 != null) return byte2.length == 0;
881 if (byte2 == null && byte1 != null) return byte1.length == 0;
882 return Arrays.equals(byte1, byte2);
885 @CalledByNative("BookmarkNode")
886 private static BookmarkNode create(
887 long id, int type, String name, String url, BookmarkNode parent) {
888 return new BookmarkNode(id, Type.values()[type], name, url, parent);
892 public void setFavicon(byte[] favicon) {
897 public void setThumbnail(byte[] thumbnail) {
898 mThumbnail = thumbnail;
902 public int describeContents() {
907 public void writeToParcel(Parcel dest, int flags) {
908 // Write the current node id.
911 // Serialize the full hierarchy from the root.
912 getHierarchyRoot().writeNodeContentsRecursive(dest);
916 public BookmarkNode getHierarchyRoot() {
917 BookmarkNode root = this;
918 while (root.parent() != null) {
919 root = root.parent();
924 private void writeNodeContentsRecursive(Parcel dest) {
925 writeNodeContents(dest);
926 dest.writeInt(mChildren.size());
927 for (BookmarkNode child : mChildren) {
928 child.writeNodeContentsRecursive(dest);
932 private void writeNodeContents(Parcel dest) {
934 dest.writeString(mName);
935 dest.writeString(mUrl);
936 dest.writeInt(mType.ordinal());
937 dest.writeByteArray(mFavicon);
938 dest.writeByteArray(mThumbnail);
939 dest.writeLong(mParent != null ? mParent.mId : INVALID_BOOKMARK_ID);
942 public static final Creator<BookmarkNode> CREATOR = new Creator<BookmarkNode>() {
943 private HashMap<Long, BookmarkNode> mNodeMap;
946 public BookmarkNode createFromParcel(Parcel source) {
947 mNodeMap = new HashMap<Long, BookmarkNode>();
948 long currentNodeId = source.readLong();
949 readNodeContentsRecursive(source);
950 BookmarkNode node = getNode(currentNodeId);
956 public BookmarkNode[] newArray(int size) {
957 return new BookmarkNode[size];
960 private BookmarkNode getNode(long id) {
961 if (id == INVALID_BOOKMARK_ID) return null;
962 Long nodeId = Long.valueOf(id);
963 if (!mNodeMap.containsKey(nodeId)) {
964 Log.e(TAG, "Invalid BookmarkNode hierarchy. Unknown id " + id);
967 return mNodeMap.get(nodeId);
970 private BookmarkNode readNodeContents(Parcel source) {
971 long id = source.readLong();
972 String name = source.readString();
973 String url = source.readString();
974 int type = source.readInt();
975 byte[] favicon = source.createByteArray();
976 byte[] thumbnail = source.createByteArray();
977 long parentId = source.readLong();
978 if (type < 0 || type >= Type.values().length) {
979 Log.w(TAG, "Invalid node type ordinal value.");
983 BookmarkNode node = new BookmarkNode(id, Type.values()[type], name, url,
985 node.setFavicon(favicon);
986 node.setThumbnail(thumbnail);
990 private BookmarkNode readNodeContentsRecursive(Parcel source) {
991 BookmarkNode node = readNodeContents(source);
992 if (node == null) return null;
994 Long nodeId = Long.valueOf(node.id());
995 if (mNodeMap.containsKey(nodeId)) {
996 Log.e(TAG, "Invalid BookmarkNode hierarchy. Duplicate id " + node.id());
999 mNodeMap.put(nodeId, node);
1001 int numChildren = source.readInt();
1002 for (int i = 0; i < numChildren; ++i) {
1003 node.addChild(readNodeContentsRecursive(source));
1011 private long addBookmarkFromAPI(ContentValues values) {
1012 BookmarkRow row = BookmarkRow.fromContentValues(values);
1013 if (row.mUrl == null) {
1014 throw new IllegalArgumentException("Must have a bookmark URL");
1016 return nativeAddBookmarkFromAPI(mNativeChromeBrowserProvider,
1017 row.mUrl, row.mCreated, row.mIsBookmark, row.mDate, row.mFavicon,
1018 row.mTitle, row.mVisits, row.mParentId);
1021 private Cursor queryBookmarkFromAPI(String[] projectionIn, String selection,
1022 String[] selectionArgs, String sortOrder) {
1023 String[] projection = null;
1024 if (projectionIn == null || projectionIn.length == 0) {
1025 projection = BOOKMARK_DEFAULT_PROJECTION;
1027 projection = projectionIn;
1030 return nativeQueryBookmarkFromAPI(mNativeChromeBrowserProvider, projection, selection,
1031 selectionArgs, sortOrder);
1034 private int updateBookmarkFromAPI(ContentValues values, String selection,
1035 String[] selectionArgs) {
1036 BookmarkRow row = BookmarkRow.fromContentValues(values);
1037 return nativeUpdateBookmarkFromAPI(mNativeChromeBrowserProvider,
1038 row.mUrl, row.mCreated, row.mIsBookmark, row.mDate,
1039 row.mFavicon, row.mTitle, row.mVisits, row.mParentId, selection, selectionArgs);
1042 private int removeBookmarkFromAPI(String selection, String[] selectionArgs) {
1043 return nativeRemoveBookmarkFromAPI(mNativeChromeBrowserProvider, selection, selectionArgs);
1046 private int removeHistoryFromAPI(String selection, String[] selectionArgs) {
1047 return nativeRemoveHistoryFromAPI(mNativeChromeBrowserProvider, selection, selectionArgs);
1051 private void onBookmarkChanged() {
1052 notifyChange(buildAPIContentUri(getContext(), BOOKMARKS_PATH));
1056 private void onHistoryChanged() {
1057 notifyChange(buildAPIContentUri(getContext(), HISTORY_PATH));
1061 private void onSearchTermChanged() {
1062 notifyChange(buildAPIContentUri(getContext(), SEARCHES_PATH));
1065 private long addSearchTermFromAPI(ContentValues values) {
1066 SearchRow row = SearchRow.fromContentValues(values);
1067 if (row.mTerm == null) {
1068 throw new IllegalArgumentException("Must have a search term");
1070 return nativeAddSearchTermFromAPI(mNativeChromeBrowserProvider, row.mTerm, row.mDate);
1073 private int updateSearchTermFromAPI(ContentValues values, String selection,
1074 String[] selectionArgs) {
1075 SearchRow row = SearchRow.fromContentValues(values);
1076 return nativeUpdateSearchTermFromAPI(mNativeChromeBrowserProvider,
1077 row.mTerm, row.mDate, selection, selectionArgs);
1080 private Cursor querySearchTermFromAPI(String[] projectionIn, String selection,
1081 String[] selectionArgs, String sortOrder) {
1082 String[] projection = null;
1083 if (projectionIn == null || projectionIn.length == 0) {
1084 projection = android.provider.Browser.SEARCHES_PROJECTION;
1086 projection = projectionIn;
1088 return nativeQuerySearchTermFromAPI(mNativeChromeBrowserProvider, projection, selection,
1089 selectionArgs, sortOrder);
1092 private int removeSearchFromAPI(String selection, String[] selectionArgs) {
1093 return nativeRemoveSearchTermFromAPI(mNativeChromeBrowserProvider,
1094 selection, selectionArgs);
1097 private static boolean isInUiThread() {
1098 if (!ThreadUtils.runningOnUiThread()) return false;
1100 if (!"REL".equals(Build.VERSION.CODENAME)) {
1101 throw new IllegalStateException("Shouldn't run in the UI thread");
1104 Log.w(TAG, "ChromeBrowserProvider methods cannot be called from the UI thread.");
1108 private static Uri buildContentUri(String authority, String path) {
1109 return Uri.parse("content://" + authority + "/" + path);
1112 private static Uri buildAPIContentUri(Context context, String path) {
1113 return buildContentUri(context.getPackageName() + API_AUTHORITY_SUFFIX, path);
1116 private static String buildWhereClause(long id, String selection) {
1117 StringBuffer sb = new StringBuffer();
1118 sb.append(BaseColumns._ID);
1121 if (!TextUtils.isEmpty(selection)) {
1122 sb.append(" AND (");
1123 sb.append(selection);
1126 return sb.toString();
1129 private static String buildHistoryWhereClause(long id, String selection) {
1130 return buildWhereClause(id, buildBookmarkWhereClause(selection, false));
1133 private static String buildHistoryWhereClause(String selection) {
1134 return buildBookmarkWhereClause(selection, false);
1138 * @return a SQL where class which is inserted the bookmark condition.
1140 private static String buildBookmarkWhereClause(String selection, boolean isBookmark) {
1141 StringBuffer sb = new StringBuffer();
1142 sb.append(BookmarkColumns.BOOKMARK);
1143 sb.append(isBookmark ? " = 1 " : " = 0");
1144 if (!TextUtils.isEmpty(selection)) {
1145 sb.append(" AND (");
1146 sb.append(selection);
1149 return sb.toString();
1152 private static String buildBookmarkWhereClause(long id, String selection) {
1153 return buildWhereClause(id, buildBookmarkWhereClause(selection, true));
1156 private static String buildBookmarkWhereClause(String selection) {
1157 return buildBookmarkWhereClause(selection, true);
1160 // Wrap the value of BookmarkColumn.
1161 private static class BookmarkRow {
1162 Boolean mIsBookmark;
1171 static BookmarkRow fromContentValues(ContentValues values) {
1172 BookmarkRow row = new BookmarkRow();
1173 if (values.containsKey(BookmarkColumns.URL)) {
1174 row.mUrl = values.getAsString(BookmarkColumns.URL);
1176 if (values.containsKey(BookmarkColumns.BOOKMARK)) {
1177 row.mIsBookmark = values.getAsInteger(BookmarkColumns.BOOKMARK) != 0;
1179 if (values.containsKey(BookmarkColumns.CREATED)) {
1180 row.mCreated = values.getAsLong(BookmarkColumns.CREATED);
1182 if (values.containsKey(BookmarkColumns.DATE)) {
1183 row.mDate = values.getAsLong(BookmarkColumns.DATE);
1185 if (values.containsKey(BookmarkColumns.FAVICON)) {
1186 row.mFavicon = values.getAsByteArray(BookmarkColumns.FAVICON);
1187 // We need to know that the caller set the favicon column.
1188 if (row.mFavicon == null) {
1189 row.mFavicon = new byte[0];
1192 if (values.containsKey(BookmarkColumns.TITLE)) {
1193 row.mTitle = values.getAsString(BookmarkColumns.TITLE);
1195 if (values.containsKey(BookmarkColumns.VISITS)) {
1196 row.mVisits = values.getAsInteger(BookmarkColumns.VISITS);
1198 if (values.containsKey(BOOKMARK_PARENT_ID_PARAM)) {
1199 row.mParentId = values.getAsLong(BOOKMARK_PARENT_ID_PARAM);
1205 // Wrap the value of SearchColumn.
1206 private static class SearchRow {
1210 static SearchRow fromContentValues(ContentValues values) {
1211 SearchRow row = new SearchRow();
1212 if (values.containsKey(SearchColumns.SEARCH)) {
1213 row.mTerm = values.getAsString(SearchColumns.SEARCH);
1215 if (values.containsKey(SearchColumns.DATE)) {
1216 row.mDate = values.getAsLong(SearchColumns.DATE);
1223 * Returns true if the native side of the class is initialized.
1225 protected boolean isNativeSideInitialized() {
1226 return mNativeChromeBrowserProvider != 0;
1230 * Make sure chrome is running. This method mustn't run on UI thread.
1232 * @return Whether the native chrome process is running successfully once this has returned.
1234 private boolean ensureNativeChromeLoaded() {
1235 ensureUriMatcherInitialized();
1237 synchronized (mLoadNativeLock) {
1238 if (mNativeChromeBrowserProvider != 0) return true;
1240 final AtomicBoolean retVal = new AtomicBoolean(true);
1241 ThreadUtils.runOnUiThreadBlocking(new Runnable() {
1244 retVal.set(ensureNativeChromeLoadedOnUIThread());
1247 return retVal.get();
1252 * This method should only run on UI thread.
1254 protected boolean ensureNativeChromeLoadedOnUIThread() {
1255 if (isNativeSideInitialized()) return true;
1256 mNativeChromeBrowserProvider = nativeInit();
1257 return isNativeSideInitialized();
1261 protected void finalize() throws Throwable {
1263 // Tests might try to destroy this in the wrong thread.
1264 ThreadUtils.runOnUiThreadBlocking(new Runnable() {
1267 ensureNativeChromeDestroyedOnUIThread();
1276 * This method should only run on UI thread.
1278 private void ensureNativeChromeDestroyedOnUIThread() {
1279 if (isNativeSideInitialized()) {
1280 nativeDestroy(mNativeChromeBrowserProvider);
1281 mNativeChromeBrowserProvider = 0;
1285 @SuppressLint("NewApi")
1286 private void notifyChange(final Uri uri) {
1287 // If the calling user is different than current one, we need to post a
1288 // task to notify change, otherwise, a system level hidden permission
1289 // INTERACT_ACROSS_USERS_FULL is needed.
1290 // The related APIs were added in API 17, it should be safe to fallback to
1291 // normal way for notifying change, because caller can't be other users in
1292 // devices whose API level is less than API 17.
1293 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
1294 UserHandle callingUserHandle = Binder.getCallingUserHandle();
1295 if (callingUserHandle != null &&
1296 !callingUserHandle.equals(android.os.Process.myUserHandle())) {
1297 ThreadUtils.postOnUiThread(new Runnable() {
1300 getContext().getContentResolver().notifyChange(uri, null);
1306 getContext().getContentResolver().notifyChange(uri, null);
1309 private native long nativeInit();
1310 private native void nativeDestroy(long nativeChromeBrowserProvider);
1312 // Public API native methods.
1313 private native long nativeAddBookmark(long nativeChromeBrowserProvider,
1314 String url, String title, boolean isFolder, long parentId);
1316 private native int nativeRemoveBookmark(long nativeChromeBrowserProvider, long id);
1318 private native int nativeUpdateBookmark(long nativeChromeBrowserProvider,
1319 long id, String url, String title, long parentId);
1321 private native long nativeAddBookmarkFromAPI(long nativeChromeBrowserProvider,
1322 String url, Long created, Boolean isBookmark, Long date, byte[] favicon,
1323 String title, Integer visits, long parentId);
1325 private native SQLiteCursor nativeQueryBookmarkFromAPI(long nativeChromeBrowserProvider,
1326 String[] projection, String selection, String[] selectionArgs, String sortOrder);
1328 private native int nativeUpdateBookmarkFromAPI(long nativeChromeBrowserProvider,
1329 String url, Long created, Boolean isBookmark, Long date, byte[] favicon,
1330 String title, Integer visits, long parentId, String selection, String[] selectionArgs);
1332 private native int nativeRemoveBookmarkFromAPI(long nativeChromeBrowserProvider,
1333 String selection, String[] selectionArgs);
1335 private native int nativeRemoveHistoryFromAPI(long nativeChromeBrowserProvider,
1336 String selection, String[] selectionArgs);
1338 private native long nativeAddSearchTermFromAPI(long nativeChromeBrowserProvider,
1339 String term, Long date);
1341 private native SQLiteCursor nativeQuerySearchTermFromAPI(long nativeChromeBrowserProvider,
1342 String[] projection, String selection, String[] selectionArgs, String sortOrder);
1344 private native int nativeUpdateSearchTermFromAPI(long nativeChromeBrowserProvider,
1345 String search, Long date, String selection, String[] selectionArgs);
1347 private native int nativeRemoveSearchTermFromAPI(long nativeChromeBrowserProvider,
1348 String selection, String[] selectionArgs);
1350 // Client API native methods.
1351 private native boolean nativeBookmarkNodeExists(long nativeChromeBrowserProvider, long id);
1353 private native long nativeCreateBookmarksFolderOnce(long nativeChromeBrowserProvider,
1354 String title, long parentId);
1356 private native BookmarkNode nativeGetEditableBookmarkFolders(long nativeChromeBrowserProvider);
1358 private native void nativeRemoveAllUserBookmarks(long nativeChromeBrowserProvider);
1360 private native BookmarkNode nativeGetBookmarkNode(long nativeChromeBrowserProvider,
1361 long id, boolean getParent, boolean getChildren);
1363 private native BookmarkNode nativeGetMobileBookmarksFolder(long nativeChromeBrowserProvider);
1365 private native boolean nativeIsBookmarkInMobileBookmarksBranch(long nativeChromeBrowserProvider,
1368 private native byte[] nativeGetFaviconOrTouchIcon(long nativeChromeBrowserProvider, String url);
1370 private native byte[] nativeGetThumbnail(long nativeChromeBrowserProvider, String url);