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