BrowserProvider.java revision 62b71f79a80e471bdb84bc06a88cd78a8162e3a5
1/* 2 * Copyright (C) 2006 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.browser; 18 19import com.google.android.providers.GoogleSettings.Partner; 20 21import android.app.SearchManager; 22import android.content.ComponentName; 23import android.content.ContentProvider; 24import android.content.ContentUris; 25import android.content.ContentValues; 26import android.content.Context; 27import android.content.Intent; 28import android.content.SharedPreferences; 29import android.content.UriMatcher; 30import android.content.SharedPreferences.Editor; 31import android.content.pm.PackageManager; 32import android.content.pm.ResolveInfo; 33import android.database.AbstractCursor; 34import android.database.ContentObserver; 35import android.database.Cursor; 36import android.database.sqlite.SQLiteDatabase; 37import android.database.sqlite.SQLiteOpenHelper; 38import android.net.Uri; 39import android.os.Handler; 40import android.preference.PreferenceManager; 41import android.provider.Browser; 42import android.provider.Settings; 43import android.server.search.SearchableInfo; 44import android.text.TextUtils; 45import android.text.util.Regex; 46import android.util.Log; 47import android.util.TypedValue; 48 49import java.util.Date; 50import java.util.regex.Matcher; 51import java.util.regex.Pattern; 52 53 54public class BrowserProvider extends ContentProvider { 55 56 private SQLiteOpenHelper mOpenHelper; 57 private static final String sDatabaseName = "browser.db"; 58 private static final String TAG = "BrowserProvider"; 59 private static final String ORDER_BY = "visits DESC, date DESC"; 60 61 private static final String PICASA_URL = "http://picasaweb.google.com/m/" + 62 "viewer?source=androidclient"; 63 64 private static final String[] TABLE_NAMES = new String[] { 65 "bookmarks", "searches" 66 }; 67 private static final String[] SUGGEST_PROJECTION = new String[] { 68 "_id", "url", "title", "bookmark" 69 }; 70 private static final String SUGGEST_SELECTION = 71 "url LIKE ? OR url LIKE ? OR url LIKE ? OR url LIKE ?"; 72 private String[] SUGGEST_ARGS = new String[4]; 73 74 // shared suggestion array index, make sure to match COLUMNS 75 private static final int SUGGEST_COLUMN_INTENT_ACTION_ID = 1; 76 private static final int SUGGEST_COLUMN_INTENT_DATA_ID = 2; 77 private static final int SUGGEST_COLUMN_TEXT_1_ID = 3; 78 private static final int SUGGEST_COLUMN_TEXT_2_ID = 4; 79 private static final int SUGGEST_COLUMN_ICON_1_ID = 5; 80 private static final int SUGGEST_COLUMN_ICON_2_ID = 6; 81 private static final int SUGGEST_COLUMN_QUERY_ID = 7; 82 private static final int SUGGEST_COLUMN_FORMAT = 8; 83 84 // shared suggestion columns 85 private static final String[] COLUMNS = new String[] { 86 "_id", 87 SearchManager.SUGGEST_COLUMN_INTENT_ACTION, 88 SearchManager.SUGGEST_COLUMN_INTENT_DATA, 89 SearchManager.SUGGEST_COLUMN_TEXT_1, 90 SearchManager.SUGGEST_COLUMN_TEXT_2, 91 SearchManager.SUGGEST_COLUMN_ICON_1, 92 SearchManager.SUGGEST_COLUMN_ICON_2, 93 SearchManager.SUGGEST_COLUMN_QUERY, 94 SearchManager.SUGGEST_COLUMN_FORMAT}; 95 96 private static final int MAX_SUGGESTION_SHORT_ENTRIES = 3; 97 private static final int MAX_SUGGESTION_LONG_ENTRIES = 6; 98 99 // make sure that these match the index of TABLE_NAMES 100 private static final int URI_MATCH_BOOKMARKS = 0; 101 private static final int URI_MATCH_SEARCHES = 1; 102 // (id % 10) should match the table name index 103 private static final int URI_MATCH_BOOKMARKS_ID = 10; 104 private static final int URI_MATCH_SEARCHES_ID = 11; 105 // 106 private static final int URI_MATCH_SUGGEST = 20; 107 private static final int URI_MATCH_BOOKMARKS_SUGGEST = 21; 108 109 private static final UriMatcher URI_MATCHER; 110 111 static { 112 URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH); 113 URI_MATCHER.addURI("browser", TABLE_NAMES[URI_MATCH_BOOKMARKS], 114 URI_MATCH_BOOKMARKS); 115 URI_MATCHER.addURI("browser", TABLE_NAMES[URI_MATCH_BOOKMARKS] + "/#", 116 URI_MATCH_BOOKMARKS_ID); 117 URI_MATCHER.addURI("browser", TABLE_NAMES[URI_MATCH_SEARCHES], 118 URI_MATCH_SEARCHES); 119 URI_MATCHER.addURI("browser", TABLE_NAMES[URI_MATCH_SEARCHES] + "/#", 120 URI_MATCH_SEARCHES_ID); 121 URI_MATCHER.addURI("browser", SearchManager.SUGGEST_URI_PATH_QUERY, 122 URI_MATCH_SUGGEST); 123 URI_MATCHER.addURI("browser", 124 TABLE_NAMES[URI_MATCH_BOOKMARKS] + "/" + SearchManager.SUGGEST_URI_PATH_QUERY, 125 URI_MATCH_BOOKMARKS_SUGGEST); 126 } 127 128 // 1 -> 2 add cache table 129 // 2 -> 3 update history table 130 // 3 -> 4 add passwords table 131 // 4 -> 5 add settings table 132 // 5 -> 6 ? 133 // 6 -> 7 ? 134 // 7 -> 8 drop proxy table 135 // 8 -> 9 drop settings table 136 // 9 -> 10 add form_urls and form_data 137 // 10 -> 11 add searches table 138 // 11 -> 12 modify cache table 139 // 12 -> 13 modify cache table 140 // 13 -> 14 correspond with Google Bookmarks schema 141 // 14 -> 15 move couple of tables to either browser private database or webview database 142 // 15 -> 17 Set it up for the SearchManager 143 // 17 -> 18 Added favicon in bookmarks table for Home shortcuts 144 // 18 -> 19 Remove labels table 145 private static final int DATABASE_VERSION = 19; 146 147 // Regular expression which matches http://, followed by some stuff, followed by 148 // optionally a trailing slash, all matched as separate groups. 149 private static final Pattern STRIP_URL_PATTERN = Pattern.compile("^(http://)(.*?)(/$)?"); 150 151 // The hex color string to be applied to urls of website suggestions, as derived from 152 // the current theme. This is not set until/unless beautifyUrl is called, at which point 153 // this variable caches the color value. 154 private static String mSearchUrlColorHex; 155 156 public BrowserProvider() { 157 } 158 159 160 private static CharSequence replaceSystemPropertyInString(Context context, CharSequence srcString) { 161 StringBuffer sb = new StringBuffer(); 162 int lastCharLoc = 0; 163 164 final String client_id = Partner.getString(context.getContentResolver(), Partner.CLIENT_ID); 165 166 for (int i = 0; i < srcString.length(); ++i) { 167 char c = srcString.charAt(i); 168 if (c == '{') { 169 sb.append(srcString.subSequence(lastCharLoc, i)); 170 lastCharLoc = i; 171 inner: 172 for (int j = i; j < srcString.length(); ++j) { 173 char k = srcString.charAt(j); 174 if (k == '}') { 175 String propertyKeyValue = srcString.subSequence(i + 1, j).toString(); 176 if (propertyKeyValue.equals("CLIENT_ID")) { 177 sb.append(client_id); 178 } else { 179 sb.append("unknown"); 180 } 181 lastCharLoc = j + 1; 182 i = j; 183 break inner; 184 } 185 } 186 } 187 } 188 if (srcString.length() - lastCharLoc > 0) { 189 // Put on the tail, if there is one 190 sb.append(srcString.subSequence(lastCharLoc, srcString.length())); 191 } 192 return sb; 193 } 194 195 private static class DatabaseHelper extends SQLiteOpenHelper { 196 private Context mContext; 197 198 public DatabaseHelper(Context context) { 199 super(context, sDatabaseName, null, DATABASE_VERSION); 200 mContext = context; 201 } 202 203 @Override 204 public void onCreate(SQLiteDatabase db) { 205 db.execSQL("CREATE TABLE bookmarks (" + 206 "_id INTEGER PRIMARY KEY," + 207 "title TEXT," + 208 "url TEXT," + 209 "visits INTEGER," + 210 "date LONG," + 211 "created LONG," + 212 "description TEXT," + 213 "bookmark INTEGER," + 214 "favicon BLOB DEFAULT NULL" + 215 ");"); 216 217 final CharSequence[] bookmarks = mContext.getResources() 218 .getTextArray(R.array.bookmarks); 219 int size = bookmarks.length; 220 try { 221 for (int i = 0; i < size; i = i + 2) { 222 CharSequence bookmarkDestination = replaceSystemPropertyInString(mContext, bookmarks[i + 1]); 223 db.execSQL("INSERT INTO bookmarks (title, url, visits, " + 224 "date, created, bookmark)" + " VALUES('" + 225 bookmarks[i] + "', '" + bookmarkDestination + 226 "', 0, 0, 0, 1);"); 227 } 228 } catch (ArrayIndexOutOfBoundsException e) { 229 } 230 231 db.execSQL("CREATE TABLE searches (" + 232 "_id INTEGER PRIMARY KEY," + 233 "search TEXT," + 234 "date LONG" + 235 ");"); 236 } 237 238 @Override 239 public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { 240 Log.w(TAG, "Upgrading database from version " + oldVersion + " to " 241 + newVersion + ", which will destroy all old data"); 242 if (oldVersion == 18) { 243 db.execSQL("DROP TABLE IF EXISTS labels"); 244 } else { 245 db.execSQL("DROP TABLE IF EXISTS bookmarks"); 246 db.execSQL("DROP TABLE IF EXISTS searches"); 247 onCreate(db); 248 } 249 } 250 } 251 252 @Override 253 public boolean onCreate() { 254 final Context context = getContext(); 255 mOpenHelper = new DatabaseHelper(context); 256 // we added "picasa web album" into default bookmarks for version 19. 257 // To avoid erasing the bookmark table, we added it explicitly for 258 // version 18 and 19 as in the other cases, we will erase the table. 259 if (DATABASE_VERSION == 18 || DATABASE_VERSION == 19) { 260 SharedPreferences p = PreferenceManager 261 .getDefaultSharedPreferences(context); 262 boolean fix = p.getBoolean("fix_picasa", true); 263 if (fix) { 264 fixPicasaBookmark(); 265 Editor ed = p.edit(); 266 ed.putBoolean("fix_picasa", false); 267 ed.commit(); 268 } 269 } 270 mShowWebSuggestionsSettingChangeObserver 271 = new ShowWebSuggestionsSettingChangeObserver(); 272 context.getContentResolver().registerContentObserver( 273 Settings.System.getUriFor( 274 Settings.System.SHOW_WEB_SUGGESTIONS), 275 true, mShowWebSuggestionsSettingChangeObserver); 276 updateShowWebSuggestions(); 277 return true; 278 } 279 280 /** 281 * This Observer will ensure that if the user changes the system 282 * setting of whether to display web suggestions, we will 283 * change accordingly. 284 */ 285 /* package */ class ShowWebSuggestionsSettingChangeObserver 286 extends ContentObserver { 287 public ShowWebSuggestionsSettingChangeObserver() { 288 super(new Handler()); 289 } 290 291 @Override 292 public void onChange(boolean selfChange) { 293 updateShowWebSuggestions(); 294 } 295 } 296 297 private ShowWebSuggestionsSettingChangeObserver 298 mShowWebSuggestionsSettingChangeObserver; 299 300 // If non-null, then the system is set to show web suggestions, 301 // and this is the SearchableInfo to use to get them. 302 private SearchableInfo mSearchableInfo; 303 304 /** 305 * Check the system settings to see whether web suggestions are 306 * allowed. If so, store the SearchableInfo to grab suggestions 307 * while the user is typing. 308 */ 309 private void updateShowWebSuggestions() { 310 mSearchableInfo = null; 311 Context context = getContext(); 312 if (Settings.System.getInt(context.getContentResolver(), 313 Settings.System.SHOW_WEB_SUGGESTIONS, 314 1 /* default on */) == 1) { 315 Intent intent = new Intent(Intent.ACTION_WEB_SEARCH); 316 intent.addCategory(Intent.CATEGORY_DEFAULT); 317 ResolveInfo info = context.getPackageManager().resolveActivity( 318 intent, PackageManager.MATCH_DEFAULT_ONLY); 319 if (info != null) { 320 ComponentName googleSearchComponent = 321 new ComponentName(info.activityInfo.packageName, 322 info.activityInfo.name); 323 mSearchableInfo = SearchManager.getSearchableInfo( 324 googleSearchComponent, false); 325 } 326 } 327 } 328 329 private void fixPicasaBookmark() { 330 SQLiteDatabase db = mOpenHelper.getWritableDatabase(); 331 Cursor cursor = db.rawQuery("SELECT _id FROM bookmarks WHERE " + 332 "bookmark = 1 AND url = ?", new String[] { PICASA_URL }); 333 try { 334 if (!cursor.moveToFirst()) { 335 // set "created" so that it will be on the top of the list 336 db.execSQL("INSERT INTO bookmarks (title, url, visits, " + 337 "date, created, bookmark)" + " VALUES('" + 338 getContext().getString(R.string.picasa) + "', '" 339 + PICASA_URL + "', 0, 0, " + new Date().getTime() 340 + ", 1);"); 341 } 342 } finally { 343 if (cursor != null) { 344 cursor.close(); 345 } 346 } 347 } 348 349 /* 350 * Subclass AbstractCursor so we can combine multiple Cursors and add 351 * "Google Search". 352 * Here are the rules. 353 * 1. We only have MAX_SUGGESTION_LONG_ENTRIES in the list plus 354 * "Google Search"; 355 * 2. If bookmark/history entries are less than 356 * (MAX_SUGGESTION_SHORT_ENTRIES -1), we include Google suggest. 357 */ 358 private class MySuggestionCursor extends AbstractCursor { 359 private Cursor mHistoryCursor; 360 private Cursor mSuggestCursor; 361 private int mHistoryCount; 362 private int mSuggestionCount; 363 private boolean mBeyondCursor; 364 private String mString; 365 private int mSuggestText1Id; 366 private int mSuggestText2Id; 367 private int mSuggestQueryId; 368 369 public MySuggestionCursor(Cursor hc, Cursor sc, String string) { 370 mHistoryCursor = hc; 371 mSuggestCursor = sc; 372 mHistoryCount = hc.getCount(); 373 mSuggestionCount = sc != null ? sc.getCount() : 0; 374 if (mSuggestionCount > (MAX_SUGGESTION_LONG_ENTRIES - mHistoryCount)) { 375 mSuggestionCount = MAX_SUGGESTION_LONG_ENTRIES - mHistoryCount; 376 } 377 mString = string; 378 mBeyondCursor = false; 379 380 // Some web suggest providers only give suggestions and have no description string for 381 // items. The order of the result columns may be different as well. So retrieve the 382 // column indices for the fields we need now and check before using below. 383 if (mSuggestCursor == null) { 384 mSuggestText1Id = -1; 385 mSuggestText2Id = -1; 386 mSuggestQueryId = -1; 387 } else { 388 mSuggestText1Id = mSuggestCursor.getColumnIndex( 389 SearchManager.SUGGEST_COLUMN_TEXT_1); 390 mSuggestText2Id = mSuggestCursor.getColumnIndex( 391 SearchManager.SUGGEST_COLUMN_TEXT_2); 392 mSuggestQueryId = mSuggestCursor.getColumnIndex( 393 SearchManager.SUGGEST_COLUMN_QUERY); 394 } 395 } 396 397 @Override 398 public boolean onMove(int oldPosition, int newPosition) { 399 if (mHistoryCursor == null) { 400 return false; 401 } 402 if (mHistoryCount > newPosition) { 403 mHistoryCursor.moveToPosition(newPosition); 404 mBeyondCursor = false; 405 } else if (mHistoryCount + mSuggestionCount > newPosition) { 406 mSuggestCursor.moveToPosition(newPosition - mHistoryCount); 407 mBeyondCursor = false; 408 } else { 409 mBeyondCursor = true; 410 } 411 return true; 412 } 413 414 @Override 415 public int getCount() { 416 if (mString.length() > 0) { 417 return mHistoryCount + mSuggestionCount + 1; 418 } else { 419 return mHistoryCount + mSuggestionCount; 420 } 421 } 422 423 @Override 424 public String[] getColumnNames() { 425 return COLUMNS; 426 } 427 428 @Override 429 public String getString(int columnIndex) { 430 if ((mPos != -1 && mHistoryCursor != null)) { 431 switch(columnIndex) { 432 case SUGGEST_COLUMN_INTENT_ACTION_ID: 433 if (mHistoryCount > mPos) { 434 return Intent.ACTION_VIEW; 435 } else { 436 return Intent.ACTION_SEARCH; 437 } 438 439 case SUGGEST_COLUMN_INTENT_DATA_ID: 440 if (mHistoryCount > mPos) { 441 return mHistoryCursor.getString(1); 442 } else { 443 return null; 444 } 445 446 case SUGGEST_COLUMN_TEXT_1_ID: 447 if (mHistoryCount > mPos) { 448 return getHistoryTitle(); 449 } else if (!mBeyondCursor) { 450 if (mSuggestText1Id == -1) return null; 451 return mSuggestCursor.getString(mSuggestText1Id); 452 } else { 453 return mString; 454 } 455 456 case SUGGEST_COLUMN_TEXT_2_ID: 457 if (mHistoryCount > mPos) { 458 return getHistorySubtitle(); 459 } else if (!mBeyondCursor) { 460 if (mSuggestText2Id == -1) return null; 461 return mSuggestCursor.getString(mSuggestText2Id); 462 } else { 463 return getContext().getString(R.string.search_the_web); 464 } 465 466 case SUGGEST_COLUMN_ICON_1_ID: 467 if (mHistoryCount > mPos) { 468 if (mHistoryCursor.getInt(3) == 1) { 469 return new Integer( 470 R.drawable.ic_search_category_bookmark) 471 .toString(); 472 } else { 473 return new Integer( 474 R.drawable.ic_search_category_history) 475 .toString(); 476 } 477 } else { 478 return new Integer( 479 R.drawable.ic_search_category_suggest) 480 .toString(); 481 } 482 483 case SUGGEST_COLUMN_ICON_2_ID: 484 return new String("0"); 485 486 case SUGGEST_COLUMN_QUERY_ID: 487 if (mHistoryCount > mPos) { 488 return null; 489 } else if (!mBeyondCursor) { 490 if (mSuggestQueryId == -1) return null; 491 return mSuggestCursor.getString(mSuggestQueryId); 492 } else { 493 return mString; 494 } 495 496 case SUGGEST_COLUMN_FORMAT: 497 return "html"; 498 } 499 } 500 return null; 501 } 502 503 @Override 504 public double getDouble(int column) { 505 throw new UnsupportedOperationException(); 506 } 507 508 @Override 509 public float getFloat(int column) { 510 throw new UnsupportedOperationException(); 511 } 512 513 @Override 514 public int getInt(int column) { 515 throw new UnsupportedOperationException(); 516 } 517 518 @Override 519 public long getLong(int column) { 520 if ((mPos != -1) && column == 0) { 521 return mPos; // use row# as the _Id 522 } 523 throw new UnsupportedOperationException(); 524 } 525 526 @Override 527 public short getShort(int column) { 528 throw new UnsupportedOperationException(); 529 } 530 531 @Override 532 public boolean isNull(int column) { 533 throw new UnsupportedOperationException(); 534 } 535 536 // TODO Temporary change, finalize after jq's changes go in 537 public void deactivate() { 538 if (mHistoryCursor != null) { 539 mHistoryCursor.deactivate(); 540 } 541 if (mSuggestCursor != null) { 542 mSuggestCursor.deactivate(); 543 } 544 super.deactivate(); 545 } 546 547 public boolean requery() { 548 return (mHistoryCursor != null ? mHistoryCursor.requery() : false) | 549 (mSuggestCursor != null ? mSuggestCursor.requery() : false); 550 } 551 552 // TODO Temporary change, finalize after jq's changes go in 553 public void close() { 554 super.close(); 555 if (mHistoryCursor != null) { 556 mHistoryCursor.close(); 557 mHistoryCursor = null; 558 } 559 if (mSuggestCursor != null) { 560 mSuggestCursor.close(); 561 mSuggestCursor = null; 562 } 563 } 564 565 /** 566 * Provides the title (text line 1) for a browser suggestion, which should be the 567 * webpage title. If the webpage title is empty, returns the stripped url instead. 568 * 569 * @return the title string to use 570 */ 571 private String getHistoryTitle() { 572 String title = mHistoryCursor.getString(2 /* webpage title */); 573 if (TextUtils.isEmpty(title) || TextUtils.getTrimmedLength(title) == 0) { 574 title = beautifyUrl(mHistoryCursor.getString(1 /* url */)); 575 } 576 return title; 577 } 578 579 /** 580 * Provides the subtitle (text line 2) for a browser suggestion, which should be the 581 * webpage url. If the webpage title is empty, then the url should go in the title 582 * instead, and the subtitle should be empty, so this would return null. 583 * 584 * @return the subtitle string to use, or null if none 585 */ 586 private String getHistorySubtitle() { 587 String title = mHistoryCursor.getString(2 /* webpage title */); 588 if (TextUtils.isEmpty(title) || TextUtils.getTrimmedLength(title) == 0) { 589 return null; 590 } else { 591 return beautifyUrl(mHistoryCursor.getString(1 /* url */)); 592 } 593 } 594 595 /** 596 * Strips "http://" from the beginning of a url and "/" from the end, 597 * and adds html formatting to make it green. 598 */ 599 private String beautifyUrl(String url) { 600 if (mSearchUrlColorHex == null) { 601 // Get the color used for this purpose from the current theme. 602 TypedValue colorValue = new TypedValue(); 603 getContext().getTheme().resolveAttribute( 604 com.android.internal.R.attr.textColorSearchUrl, colorValue, true); 605 int color = getContext().getResources().getColor(colorValue.resourceId); 606 607 // Convert the int color value into a hex string, and strip the first two 608 // characters which will be the alpha transparency (html doesn't want this). 609 mSearchUrlColorHex = Integer.toHexString(color).substring(2); 610 } 611 612 return "<font color=\"#" + mSearchUrlColorHex + "\">" + stripUrl(url) + "</font>"; 613 } 614 } 615 616 @Override 617 public Cursor query(Uri url, String[] projectionIn, String selection, 618 String[] selectionArgs, String sortOrder) 619 throws IllegalStateException { 620 SQLiteDatabase db = mOpenHelper.getReadableDatabase(); 621 622 int match = URI_MATCHER.match(url); 623 if (match == -1) { 624 throw new IllegalArgumentException("Unknown URL"); 625 } 626 627 if (match == URI_MATCH_SUGGEST || match == URI_MATCH_BOOKMARKS_SUGGEST) { 628 String suggestSelection; 629 String [] myArgs; 630 if (selectionArgs[0] == null || selectionArgs[0].equals("")) { 631 suggestSelection = null; 632 myArgs = null; 633 } else { 634 String like = selectionArgs[0] + "%"; 635 if (selectionArgs[0].startsWith("http")) { 636 myArgs = new String[1]; 637 myArgs[0] = like; 638 suggestSelection = selection; 639 } else { 640 SUGGEST_ARGS[0] = "http://" + like; 641 SUGGEST_ARGS[1] = "http://www." + like; 642 SUGGEST_ARGS[2] = "https://" + like; 643 SUGGEST_ARGS[3] = "https://www." + like; 644 myArgs = SUGGEST_ARGS; 645 suggestSelection = SUGGEST_SELECTION; 646 } 647 } 648 649 Cursor c = db.query(TABLE_NAMES[URI_MATCH_BOOKMARKS], 650 SUGGEST_PROJECTION, suggestSelection, myArgs, null, null, 651 ORDER_BY, 652 (new Integer(MAX_SUGGESTION_LONG_ENTRIES)).toString()); 653 654 if (match == URI_MATCH_BOOKMARKS_SUGGEST 655 || Regex.WEB_URL_PATTERN.matcher(selectionArgs[0]).matches()) { 656 return new MySuggestionCursor(c, null, ""); 657 } else { 658 // get Google suggest if there is still space in the list 659 if (myArgs != null && myArgs.length > 1 660 && mSearchableInfo != null 661 && c.getCount() < (MAX_SUGGESTION_SHORT_ENTRIES - 1)) { 662 Cursor sc = SearchManager.getSuggestions( 663 getContext(), mSearchableInfo, selectionArgs[0]); 664 return new MySuggestionCursor(c, sc, selectionArgs[0]); 665 } 666 return new MySuggestionCursor(c, null, selectionArgs[0]); 667 } 668 } 669 670 String[] projection = null; 671 if (projectionIn != null && projectionIn.length > 0) { 672 projection = new String[projectionIn.length + 1]; 673 System.arraycopy(projectionIn, 0, projection, 0, projectionIn.length); 674 projection[projectionIn.length] = "_id AS _id"; 675 } 676 677 StringBuilder whereClause = new StringBuilder(256); 678 if (match == URI_MATCH_BOOKMARKS_ID || match == URI_MATCH_SEARCHES_ID) { 679 whereClause.append("(_id = ").append(url.getPathSegments().get(1)) 680 .append(")"); 681 } 682 683 // Tack on the user's selection, if present 684 if (selection != null && selection.length() > 0) { 685 if (whereClause.length() > 0) { 686 whereClause.append(" AND "); 687 } 688 689 whereClause.append('('); 690 whereClause.append(selection); 691 whereClause.append(')'); 692 } 693 Cursor c = db.query(TABLE_NAMES[match % 10], projection, 694 whereClause.toString(), selectionArgs, null, null, sortOrder, 695 null); 696 c.setNotificationUri(getContext().getContentResolver(), url); 697 return c; 698 } 699 700 @Override 701 public String getType(Uri url) { 702 int match = URI_MATCHER.match(url); 703 switch (match) { 704 case URI_MATCH_BOOKMARKS: 705 return "vnd.android.cursor.dir/bookmark"; 706 707 case URI_MATCH_BOOKMARKS_ID: 708 return "vnd.android.cursor.item/bookmark"; 709 710 case URI_MATCH_SEARCHES: 711 return "vnd.android.cursor.dir/searches"; 712 713 case URI_MATCH_SEARCHES_ID: 714 return "vnd.android.cursor.item/searches"; 715 716 case URI_MATCH_SUGGEST: 717 return SearchManager.SUGGEST_MIME_TYPE; 718 719 default: 720 throw new IllegalArgumentException("Unknown URL"); 721 } 722 } 723 724 @Override 725 public Uri insert(Uri url, ContentValues initialValues) { 726 SQLiteDatabase db = mOpenHelper.getWritableDatabase(); 727 728 int match = URI_MATCHER.match(url); 729 Uri uri = null; 730 switch (match) { 731 case URI_MATCH_BOOKMARKS: { 732 // Insert into the bookmarks table 733 long rowID = db.insert(TABLE_NAMES[URI_MATCH_BOOKMARKS], "url", 734 initialValues); 735 if (rowID > 0) { 736 uri = ContentUris.withAppendedId(Browser.BOOKMARKS_URI, 737 rowID); 738 } 739 break; 740 } 741 742 case URI_MATCH_SEARCHES: { 743 // Insert into the searches table 744 long rowID = db.insert(TABLE_NAMES[URI_MATCH_SEARCHES], "url", 745 initialValues); 746 if (rowID > 0) { 747 uri = ContentUris.withAppendedId(Browser.SEARCHES_URI, 748 rowID); 749 } 750 break; 751 } 752 753 default: 754 throw new IllegalArgumentException("Unknown URL"); 755 } 756 757 if (uri == null) { 758 throw new IllegalArgumentException("Unknown URL"); 759 } 760 getContext().getContentResolver().notifyChange(uri, null); 761 return uri; 762 } 763 764 @Override 765 public int delete(Uri url, String where, String[] whereArgs) { 766 SQLiteDatabase db = mOpenHelper.getWritableDatabase(); 767 768 int match = URI_MATCHER.match(url); 769 if (match == -1 || match == URI_MATCH_SUGGEST) { 770 throw new IllegalArgumentException("Unknown URL"); 771 } 772 773 if (match == URI_MATCH_BOOKMARKS_ID || match == URI_MATCH_SEARCHES_ID) { 774 StringBuilder sb = new StringBuilder(); 775 if (where != null && where.length() > 0) { 776 sb.append("( "); 777 sb.append(where); 778 sb.append(" ) AND "); 779 } 780 sb.append("_id = "); 781 sb.append(url.getPathSegments().get(1)); 782 where = sb.toString(); 783 } 784 785 int count = db.delete(TABLE_NAMES[match % 10], where, whereArgs); 786 getContext().getContentResolver().notifyChange(url, null); 787 return count; 788 } 789 790 @Override 791 public int update(Uri url, ContentValues values, String where, 792 String[] whereArgs) { 793 SQLiteDatabase db = mOpenHelper.getWritableDatabase(); 794 795 int match = URI_MATCHER.match(url); 796 if (match == -1 || match == URI_MATCH_SUGGEST) { 797 throw new IllegalArgumentException("Unknown URL"); 798 } 799 800 if (match == URI_MATCH_BOOKMARKS_ID || match == URI_MATCH_SEARCHES_ID) { 801 StringBuilder sb = new StringBuilder(); 802 if (where != null && where.length() > 0) { 803 sb.append("( "); 804 sb.append(where); 805 sb.append(" ) AND "); 806 } 807 sb.append("_id = "); 808 sb.append(url.getPathSegments().get(1)); 809 where = sb.toString(); 810 } 811 812 int ret = db.update(TABLE_NAMES[match % 10], values, where, whereArgs); 813 getContext().getContentResolver().notifyChange(url, null); 814 return ret; 815 } 816 817 /** 818 * Strips the provided url of preceding "http://" and any trailing "/". Does not 819 * strip "https://". If the provided string cannot be stripped, the original string 820 * is returned. 821 * 822 * TODO: Put this in TextUtils to be used by other packages doing something similar. 823 * 824 * @param url a url to strip, like "http://www.google.com/" 825 * @return a stripped url like "www.google.com", or the original string if it could 826 * not be stripped 827 */ 828 private static String stripUrl(String url) { 829 if (url == null) return null; 830 Matcher m = STRIP_URL_PATTERN.matcher(url); 831 if (m.matches() && m.groupCount() == 3) { 832 return m.group(2); 833 } else { 834 return url; 835 } 836 } 837 838} 839