-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Copyright 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser;
-import android.app.Activity;
+import android.annotation.SuppressLint;
import android.app.SearchManager;
import android.content.ContentProvider;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
-import android.content.Intent;
import android.content.SharedPreferences;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.MatrixCursor;
-import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.UserHandle;
import android.preference.PreferenceManager;
import android.provider.BaseColumns;
import android.provider.Browser;
import android.text.TextUtils;
import android.util.Log;
-import com.google.common.annotations.VisibleForTesting;
-
import org.chromium.base.CalledByNative;
import org.chromium.base.CalledByNativeUnchecked;
import org.chromium.base.ThreadUtils;
+import org.chromium.base.VisibleForTesting;
import org.chromium.chrome.browser.database.SQLiteCursor;
import org.chromium.sync.notifier.SyncStatusHelper;
import java.util.concurrent.atomic.AtomicBoolean;
/**
- * This class provides various information of Chrome, like bookmarks, most
- * visited page etc. It is used to support android.provider.Browser.
- *
+ * This class provides access to user data stored in Chrome, such as bookmarks, most visited pages,
+ * etc. It is used to support android.provider.Browser.
*/
public class ChromeBrowserProvider extends ContentProvider {
private static final String TAG = "ChromeBrowserProvider";
// Defines the API methods that the Client can call by name.
static final String CLIENT_API_BOOKMARK_NODE_EXISTS = "BOOKMARK_NODE_EXISTS";
static final String CLIENT_API_CREATE_BOOKMARKS_FOLDER_ONCE = "CREATE_BOOKMARKS_FOLDER_ONCE";
- static final String CLIENT_API_GET_BOOKMARK_FOLDER_HIERARCHY = "GET_BOOKMARK_FOLDER_HIERARCHY";
+ static final String CLIENT_API_GET_EDITABLE_BOOKMARK_FOLDER_HIERARCHY =
+ "GET_EDITABLE_BOOKMARK_FOLDER_HIERARCHY";
static final String CLIENT_API_GET_BOOKMARK_NODE = "GET_BOOKMARK_NODE";
static final String CLIENT_API_GET_DEFAULT_BOOKMARK_FOLDER = "GET_DEFAULT_BOOKMARK_FOLDER";
static final String CLIENT_API_GET_MOBILE_BOOKMARKS_FOLDER_ID =
"GET_MOBILE_BOOKMARKS_FOLDER_ID";
static final String CLIENT_API_IS_BOOKMARK_IN_MOBILE_BOOKMARKS_BRANCH =
"IS_BOOKMARK_IN_MOBILE_BOOKMARKS_BRANCH";
- static final String CLIENT_API_DELETE_ALL_BOOKMARKS = "DELETE_ALL_BOOKMARKS";
+ static final String CLIENT_API_DELETE_ALL_USER_BOOKMARKS = "DELETE_ALL_USER_BOOKMARKS";
static final String CLIENT_API_RESULT_KEY = "result";
/** The parameter used to specify whether this is a bookmark folder. */
public static final String BOOKMARK_IS_FOLDER_PARAM = "isFolder";
- /** Invalid id value for the Android ContentProvider API calls. */
+ /**
+ * Invalid ID value for the Android ContentProvider API calls.
+ * The value 0 is intentional: if the ID represents a bookmark node then it's the root node
+ * and not accessible. Otherwise it represents a SQLite row id, so 0 is also invalid.
+ */
public static final long INVALID_CONTENT_PROVIDER_ID = 0;
// ID used to indicate an invalid id for bookmark nodes.
// TODO : Using Android.provider.Browser.HISTORY_PROJECTION once THUMBNAIL,
// TOUCH_ICON, and USER_ENTERED fields are supported.
private static final String[] BOOKMARK_DEFAULT_PROJECTION = new String[] {
- BookmarkColumns._ID, BookmarkColumns.URL, BookmarkColumns.VISITS,
- BookmarkColumns.DATE, BookmarkColumns.BOOKMARK, BookmarkColumns.TITLE,
- BookmarkColumns.FAVICON, BookmarkColumns.CREATED};
+ BookmarkColumns._ID, BookmarkColumns.URL, BookmarkColumns.VISITS,
+ BookmarkColumns.DATE, BookmarkColumns.BOOKMARK, BookmarkColumns.TITLE,
+ BookmarkColumns.FAVICON, BookmarkColumns.CREATED
+ };
private static final String[] SUGGEST_PROJECTION = new String[] {
BookmarkColumns._ID,
private final Object mLoadNativeLock = new Object();
private UriMatcher mUriMatcher;
private long mLastModifiedBookmarkFolderId = INVALID_BOOKMARK_ID;
- private int mNativeChromeBrowserProvider;
+ private long mNativeChromeBrowserProvider;
private BookmarkNode mMobileBookmarksFolder;
/**
return new ChromeBrowserProviderSuggestionsCursor(cursor);
}
+ /**
+ * @see android.content.ContentUris#parseId(Uri)
+ * @return The id from a content URI or -1 if the URI has no id or is malformed.
+ */
+ private static long getContentUriId(Uri uri) {
+ try {
+ return ContentUris.parseId(uri);
+ } catch (UnsupportedOperationException e) {
+ return -1;
+ } catch (NumberFormatException e) {
+ return -1;
+ }
+ }
+
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
String sortOrder) {
if (!canHandleContentProviderApiCall()) return null;
// Check for invalid id values if provided.
- // If it represents a bookmark node then it's the root node. Don't provide access here.
- // Otherwise it represents a SQLite row id, so 0 is invalid.
- long bookmarkId = INVALID_CONTENT_PROVIDER_ID;
- try {
- bookmarkId = ContentUris.parseId(uri);
- if (bookmarkId == INVALID_CONTENT_PROVIDER_ID) return null;
- } catch (Exception e) {
- }
+ long bookmarkId = getContentUriId(uri);
+ if (bookmarkId == INVALID_CONTENT_PROVIDER_ID) return null;
int match = mUriMatcher.match(uri);
Cursor cursor = null;
}
res = ContentUris.withAppendedId(uri, id);
- getContext().getContentResolver().notifyChange(res, null);
+ notifyChange(res);
return res;
}
if (!canHandleContentProviderApiCall()) return 0;
// Check for invalid id values if provided.
- // If it represents a bookmark node then it's the root node and not mutable.
- // Otherwise it represents a SQLite row id, so 0 is invalid.
- long bookmarkId = INVALID_CONTENT_PROVIDER_ID;
- try {
- bookmarkId = ContentUris.parseId(uri);
- if (bookmarkId == INVALID_CONTENT_PROVIDER_ID) return 0;
- } catch (Exception e) {
- }
+ long bookmarkId = getContentUriId(uri);
+ if (bookmarkId == INVALID_CONTENT_PROVIDER_ID) return 0;
int match = mUriMatcher.match(uri);
int result;
default:
throw new IllegalArgumentException(TAG + ": delete - unknown URL " + uri);
}
- if (result != 0) {
- getContext().getContentResolver().notifyChange(uri, null);
- }
+ if (result != 0) notifyChange(uri);
return result;
}
if (!canHandleContentProviderApiCall()) return 0;
// Check for invalid id values if provided.
- // If it represents a bookmark node then it's the root node and not mutable.
- // Otherwise it represents a SQLite row id, so 0 is invalid.
- long bookmarkId = INVALID_CONTENT_PROVIDER_ID;
- try {
- bookmarkId = ContentUris.parseId(uri);
- if (bookmarkId == INVALID_CONTENT_PROVIDER_ID) return 0;
- } catch (Exception e) {
- }
+ long bookmarkId = getContentUriId(uri);
+ if (bookmarkId == INVALID_CONTENT_PROVIDER_ID) return 0;
int match = mUriMatcher.match(uri);
int result;
default:
throw new IllegalArgumentException(TAG + ": update - unknown URL " + uri);
}
- if (result != 0) {
- getContext().getContentResolver().notifyChange(uri, null);
- }
+ if (result != 0) notifyChange(uri);
return result;
}
return nativeCreateBookmarksFolderOnce(mNativeChromeBrowserProvider, title, parentId);
}
- private BookmarkNode getBookmarkFolderHierarchy() {
- return nativeGetAllBookmarkFolders(mNativeChromeBrowserProvider);
+ private BookmarkNode getEditableBookmarkFolderHierarchy() {
+ return nativeGetEditableBookmarkFolders(mNativeChromeBrowserProvider);
}
protected BookmarkNode getBookmarkNode(long nodeId, boolean getParent, boolean getChildren,
// then use the synced node (Mobile Bookmarks).
BookmarkNode lastModified = getBookmarkNode(getLastModifiedBookmarkFolderId(), false, false,
false, false);
- if (lastModified == null) {
+ if (lastModified == null || lastModified.isUrl()) {
lastModified = getMobileBookmarksFolder();
mLastModifiedBookmarkFolderId = lastModified != null ? lastModified.id() :
INVALID_BOOKMARK_ID;
result.putLong(CLIENT_API_RESULT_KEY,
createBookmarksFolderOnce(extras.getString(argKey(0)),
extras.getLong(argKey(1))));
- } else if (CLIENT_API_GET_BOOKMARK_FOLDER_HIERARCHY.equals(method)) {
- result.putParcelable(CLIENT_API_RESULT_KEY, getBookmarkFolderHierarchy());
+ } else if (CLIENT_API_GET_EDITABLE_BOOKMARK_FOLDER_HIERARCHY.equals(method)) {
+ result.putParcelable(CLIENT_API_RESULT_KEY, getEditableBookmarkFolderHierarchy());
} else if (CLIENT_API_GET_BOOKMARK_NODE.equals(method)) {
result.putParcelable(CLIENT_API_RESULT_KEY,
getBookmarkNode(extras.getLong(argKey(0)),
} else if (CLIENT_API_IS_BOOKMARK_IN_MOBILE_BOOKMARKS_BRANCH.equals(method)) {
result.putBoolean(CLIENT_API_RESULT_KEY,
isBookmarkInMobileBookmarksBranch(extras.getLong(argKey(0))));
- } else if(CLIENT_API_DELETE_ALL_BOOKMARKS.equals(method)) {
- nativeRemoveAllBookmarks(mNativeChromeBrowserProvider);
+ } else if (CLIENT_API_DELETE_ALL_USER_BOOKMARKS.equals(method)) {
+ nativeRemoveAllUserBookmarks(mNativeChromeBrowserProvider);
} else {
Log.w(TAG, "Received invalid method " + method);
return null;
private long addBookmarkFromAPI(ContentValues values) {
BookmarkRow row = BookmarkRow.fromContentValues(values);
- if (row.url == null) {
+ if (row.mUrl == null) {
throw new IllegalArgumentException("Must have a bookmark URL");
}
return nativeAddBookmarkFromAPI(mNativeChromeBrowserProvider,
- row.url, row.created, row.isBookmark, row.date, row.favicon,
- row.title, row.visits, row.parentId);
+ row.mUrl, row.mCreated, row.mIsBookmark, row.mDate, row.mFavicon,
+ row.mTitle, row.mVisits, row.mParentId);
}
private Cursor queryBookmarkFromAPI(String[] projectionIn, String selection,
String[] selectionArgs) {
BookmarkRow row = BookmarkRow.fromContentValues(values);
return nativeUpdateBookmarkFromAPI(mNativeChromeBrowserProvider,
- row.url, row.created, row.isBookmark, row.date,
- row.favicon, row.title, row.visits, row.parentId, selection, selectionArgs);
+ row.mUrl, row.mCreated, row.mIsBookmark, row.mDate,
+ row.mFavicon, row.mTitle, row.mVisits, row.mParentId, selection, selectionArgs);
}
private int removeBookmarkFromAPI(String selection, String[] selectionArgs) {
@CalledByNative
private void onBookmarkChanged() {
- getContext().getContentResolver().notifyChange(
- buildAPIContentUri(getContext(), BOOKMARKS_PATH), null);
+ notifyChange(buildAPIContentUri(getContext(), BOOKMARKS_PATH));
+ }
+
+ @CalledByNative
+ private void onHistoryChanged() {
+ notifyChange(buildAPIContentUri(getContext(), HISTORY_PATH));
}
@CalledByNative
private void onSearchTermChanged() {
- getContext().getContentResolver().notifyChange(
- buildAPIContentUri(getContext(), SEARCHES_PATH), null);
+ notifyChange(buildAPIContentUri(getContext(), SEARCHES_PATH));
}
private long addSearchTermFromAPI(ContentValues values) {
SearchRow row = SearchRow.fromContentValues(values);
- if (row.term == null) {
+ if (row.mTerm == null) {
throw new IllegalArgumentException("Must have a search term");
}
- return nativeAddSearchTermFromAPI(mNativeChromeBrowserProvider, row.term, row.date);
+ return nativeAddSearchTermFromAPI(mNativeChromeBrowserProvider, row.mTerm, row.mDate);
}
private int updateSearchTermFromAPI(ContentValues values, String selection,
String[] selectionArgs) {
SearchRow row = SearchRow.fromContentValues(values);
return nativeUpdateSearchTermFromAPI(mNativeChromeBrowserProvider,
- row.term, row.date, selection, selectionArgs);
+ row.mTerm, row.mDate, selection, selectionArgs);
}
private Cursor querySearchTermFromAPI(String[] projectionIn, String selection,
/**
* @return a SQL where class which is inserted the bookmark condition.
*/
- private static String buildBookmarkWhereClause(String selection, boolean is_bookmark) {
+ private static String buildBookmarkWhereClause(String selection, boolean isBookmark) {
StringBuffer sb = new StringBuffer();
sb.append(BookmarkColumns.BOOKMARK);
- sb.append(is_bookmark ? " = 1 " : " = 0");
+ sb.append(isBookmark ? " = 1 " : " = 0");
if (!TextUtils.isEmpty(selection)) {
sb.append(" AND (");
sb.append(selection);
// Wrap the value of BookmarkColumn.
private static class BookmarkRow {
- Boolean isBookmark;
- Long created;
- String url;
- Long date;
- byte[] favicon;
- String title;
- Integer visits;
- long parentId;
+ Boolean mIsBookmark;
+ Long mCreated;
+ String mUrl;
+ Long mDate;
+ byte[] mFavicon;
+ String mTitle;
+ Integer mVisits;
+ long mParentId;
static BookmarkRow fromContentValues(ContentValues values) {
BookmarkRow row = new BookmarkRow();
if (values.containsKey(BookmarkColumns.URL)) {
- row.url = values.getAsString(BookmarkColumns.URL);
+ row.mUrl = values.getAsString(BookmarkColumns.URL);
}
if (values.containsKey(BookmarkColumns.BOOKMARK)) {
- row.isBookmark = values.getAsInteger(BookmarkColumns.BOOKMARK) != 0;
+ row.mIsBookmark = values.getAsInteger(BookmarkColumns.BOOKMARK) != 0;
}
if (values.containsKey(BookmarkColumns.CREATED)) {
- row.created = values.getAsLong(BookmarkColumns.CREATED);
+ row.mCreated = values.getAsLong(BookmarkColumns.CREATED);
}
if (values.containsKey(BookmarkColumns.DATE)) {
- row.date = values.getAsLong(BookmarkColumns.DATE);
+ row.mDate = values.getAsLong(BookmarkColumns.DATE);
}
if (values.containsKey(BookmarkColumns.FAVICON)) {
- row.favicon = values.getAsByteArray(BookmarkColumns.FAVICON);
+ row.mFavicon = values.getAsByteArray(BookmarkColumns.FAVICON);
// We need to know that the caller set the favicon column.
- if (row.favicon == null) {
- row.favicon = new byte[0];
+ if (row.mFavicon == null) {
+ row.mFavicon = new byte[0];
}
}
if (values.containsKey(BookmarkColumns.TITLE)) {
- row.title = values.getAsString(BookmarkColumns.TITLE);
+ row.mTitle = values.getAsString(BookmarkColumns.TITLE);
}
if (values.containsKey(BookmarkColumns.VISITS)) {
- row.visits = values.getAsInteger(BookmarkColumns.VISITS);
+ row.mVisits = values.getAsInteger(BookmarkColumns.VISITS);
}
if (values.containsKey(BOOKMARK_PARENT_ID_PARAM)) {
- row.parentId = values.getAsLong(BOOKMARK_PARENT_ID_PARAM);
+ row.mParentId = values.getAsLong(BOOKMARK_PARENT_ID_PARAM);
}
return row;
}
// Wrap the value of SearchColumn.
private static class SearchRow {
- String term;
- Long date;
+ String mTerm;
+ Long mDate;
static SearchRow fromContentValues(ContentValues values) {
SearchRow row = new SearchRow();
if (values.containsKey(SearchColumns.SEARCH)) {
- row.term = values.getAsString(SearchColumns.SEARCH);
+ row.mTerm = values.getAsString(SearchColumns.SEARCH);
}
if (values.containsKey(SearchColumns.DATE)) {
- row.date = values.getAsLong(SearchColumns.DATE);
+ row.mDate = values.getAsLong(SearchColumns.DATE);
}
return row;
}
private boolean ensureNativeChromeLoaded() {
ensureUriMatcherInitialized();
- synchronized(mLoadNativeLock) {
+ synchronized (mLoadNativeLock) {
if (mNativeChromeBrowserProvider != 0) return true;
final AtomicBoolean retVal = new AtomicBoolean(true);
}
}
- /**
- * Call to get the intent to create a bookmark shortcut on homescreen.
- */
- public static Intent getShortcutToBookmark(String url, String title, Bitmap favicon, int rValue,
- int gValue, int bValue, Context context) {
- return BookmarkUtils.createAddToHomeIntent(
- context, url, title, favicon, rValue, gValue, bValue);
+ @SuppressLint("NewApi")
+ private void notifyChange(final Uri uri) {
+ // If the calling user is different than current one, we need to post a
+ // task to notify change, otherwise, a system level hidden permission
+ // INTERACT_ACROSS_USERS_FULL is needed.
+ // The related APIs were added in API 17, it should be safe to fallback to
+ // normal way for notifying change, because caller can't be other users in
+ // devices whose API level is less than API 17.
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+ UserHandle callingUserHandle = Binder.getCallingUserHandle();
+ if (callingUserHandle != null &&
+ !callingUserHandle.equals(android.os.Process.myUserHandle())) {
+ ThreadUtils.postOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ getContext().getContentResolver().notifyChange(uri, null);
+ }
+ });
+ return;
+ }
+ }
+ getContext().getContentResolver().notifyChange(uri, null);
}
- private native int nativeInit();
- private native void nativeDestroy(int nativeChromeBrowserProvider);
+ private native long nativeInit();
+ private native void nativeDestroy(long nativeChromeBrowserProvider);
// Public API native methods.
- private native long nativeAddBookmark(int nativeChromeBrowserProvider,
+ private native long nativeAddBookmark(long nativeChromeBrowserProvider,
String url, String title, boolean isFolder, long parentId);
- private native int nativeRemoveBookmark(int nativeChromeBrowserProvider, long id);
+ private native int nativeRemoveBookmark(long nativeChromeBrowserProvider, long id);
- private native int nativeUpdateBookmark(int nativeChromeBrowserProvider,
+ private native int nativeUpdateBookmark(long nativeChromeBrowserProvider,
long id, String url, String title, long parentId);
- private native long nativeAddBookmarkFromAPI(int nativeChromeBrowserProvider,
+ private native long nativeAddBookmarkFromAPI(long nativeChromeBrowserProvider,
String url, Long created, Boolean isBookmark, Long date, byte[] favicon,
String title, Integer visits, long parentId);
- private native SQLiteCursor nativeQueryBookmarkFromAPI(int nativeChromeBrowserProvider,
+ private native SQLiteCursor nativeQueryBookmarkFromAPI(long nativeChromeBrowserProvider,
String[] projection, String selection, String[] selectionArgs, String sortOrder);
- private native int nativeUpdateBookmarkFromAPI(int nativeChromeBrowserProvider,
+ private native int nativeUpdateBookmarkFromAPI(long nativeChromeBrowserProvider,
String url, Long created, Boolean isBookmark, Long date, byte[] favicon,
String title, Integer visits, long parentId, String selection, String[] selectionArgs);
- private native int nativeRemoveBookmarkFromAPI(int nativeChromeBrowserProvider,
+ private native int nativeRemoveBookmarkFromAPI(long nativeChromeBrowserProvider,
String selection, String[] selectionArgs);
- private native int nativeRemoveHistoryFromAPI(int nativeChromeBrowserProvider,
+ private native int nativeRemoveHistoryFromAPI(long nativeChromeBrowserProvider,
String selection, String[] selectionArgs);
- private native long nativeAddSearchTermFromAPI(int nativeChromeBrowserProvider,
+ private native long nativeAddSearchTermFromAPI(long nativeChromeBrowserProvider,
String term, Long date);
- private native SQLiteCursor nativeQuerySearchTermFromAPI(int nativeChromeBrowserProvider,
+ private native SQLiteCursor nativeQuerySearchTermFromAPI(long nativeChromeBrowserProvider,
String[] projection, String selection, String[] selectionArgs, String sortOrder);
- private native int nativeUpdateSearchTermFromAPI(int nativeChromeBrowserProvider,
+ private native int nativeUpdateSearchTermFromAPI(long nativeChromeBrowserProvider,
String search, Long date, String selection, String[] selectionArgs);
- private native int nativeRemoveSearchTermFromAPI(int nativeChromeBrowserProvider,
+ private native int nativeRemoveSearchTermFromAPI(long nativeChromeBrowserProvider,
String selection, String[] selectionArgs);
// Client API native methods.
- private native boolean nativeBookmarkNodeExists(int nativeChromeBrowserProvider, long id);
+ private native boolean nativeBookmarkNodeExists(long nativeChromeBrowserProvider, long id);
- private native long nativeCreateBookmarksFolderOnce(int nativeChromeBrowserProvider,
+ private native long nativeCreateBookmarksFolderOnce(long nativeChromeBrowserProvider,
String title, long parentId);
- private native BookmarkNode nativeGetAllBookmarkFolders(int nativeChromeBrowserProvider);
+ private native BookmarkNode nativeGetEditableBookmarkFolders(long nativeChromeBrowserProvider);
- private native void nativeRemoveAllBookmarks(int nativeChromeBrowserProvider);
+ private native void nativeRemoveAllUserBookmarks(long nativeChromeBrowserProvider);
- private native BookmarkNode nativeGetBookmarkNode(int nativeChromeBrowserProvider,
+ private native BookmarkNode nativeGetBookmarkNode(long nativeChromeBrowserProvider,
long id, boolean getParent, boolean getChildren);
- private native BookmarkNode nativeGetMobileBookmarksFolder(int nativeChromeBrowserProvider);
+ private native BookmarkNode nativeGetMobileBookmarksFolder(long nativeChromeBrowserProvider);
- private native boolean nativeIsBookmarkInMobileBookmarksBranch(int nativeChromeBrowserProvider,
+ private native boolean nativeIsBookmarkInMobileBookmarksBranch(long nativeChromeBrowserProvider,
long id);
- private native byte[] nativeGetFaviconOrTouchIcon(int nativeChromeBrowserProvider, String url);
+ private native byte[] nativeGetFaviconOrTouchIcon(long nativeChromeBrowserProvider, String url);
- private native byte[] nativeGetThumbnail(int nativeChromeBrowserProvider, String url);
+ private native byte[] nativeGetThumbnail(long nativeChromeBrowserProvider, String url);
}