/WebVox/src/com/marvin/webvox/BrowserProvider.java
Java | 1006 lines | 739 code | 108 blank | 159 comment | 182 complexity | cb5829d625d624720df6fdbf1471460e MD5 | raw file
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.marvin.webvox; 18 19import com.marvin.webvox.R; 20//import com.google.android.providers.GoogleSettings.Partner; 21 22import android.app.SearchManager; 23//import android.backup.BackupManager; 24import android.content.ComponentName; 25import android.content.ContentProvider; 26import android.content.ContentResolver; 27import android.content.ContentUris; 28import android.content.ContentValues; 29import android.content.Context; 30import android.content.Intent; 31import android.content.SharedPreferences; 32import android.content.UriMatcher; 33import android.content.SharedPreferences.Editor; 34import android.content.pm.PackageManager; 35import android.content.pm.ResolveInfo; 36import android.database.AbstractCursor; 37import android.database.ContentObserver; 38import android.database.Cursor; 39import android.database.sqlite.SQLiteDatabase; 40import android.database.sqlite.SQLiteOpenHelper; 41import android.net.Uri; 42import android.os.AsyncTask; 43import android.os.Handler; 44import android.preference.PreferenceManager; 45import android.provider.Browser; 46import android.provider.Settings; 47import android.provider.Browser.BookmarkColumns; 48//import android.server.search.SearchableInfo; 49import android.text.TextUtils; 50//import android.text.util.Regex; 51import android.util.Log; 52import android.util.TypedValue; 53 54import java.io.File; 55import java.io.FilenameFilter; 56import java.util.Date; 57import java.util.regex.Matcher; 58import java.util.regex.Pattern; 59 60 61public class BrowserProvider extends ContentProvider { 62 63 private SQLiteOpenHelper mOpenHelper; 64// private BackupManager mBackupManager; 65 private static final String sDatabaseName = "browser.db"; 66 private static final String TAG = "BrowserProvider"; 67 private static final String ORDER_BY = "visits DESC, date DESC"; 68 69 private static final String PICASA_URL = "http://picasaweb.google.com/m/" + 70 "viewer?source=androidclient"; 71 72 private static final String[] TABLE_NAMES = new String[] { 73 "bookmarks", "searches" 74 }; 75 private static final String[] SUGGEST_PROJECTION = new String[] { 76 "_id", "url", "title", "bookmark" 77 }; 78 private static final String SUGGEST_SELECTION = 79 "url LIKE ? OR url LIKE ? OR url LIKE ? OR url LIKE ?" 80 + " OR title LIKE ?"; 81 private String[] SUGGEST_ARGS = new String[5]; 82 83 // shared suggestion array index, make sure to match COLUMNS 84 private static final int SUGGEST_COLUMN_INTENT_ACTION_ID = 1; 85 private static final int SUGGEST_COLUMN_INTENT_DATA_ID = 2; 86 private static final int SUGGEST_COLUMN_TEXT_1_ID = 3; 87 private static final int SUGGEST_COLUMN_TEXT_2_ID = 4; 88 private static final int SUGGEST_COLUMN_ICON_1_ID = 5; 89 private static final int SUGGEST_COLUMN_ICON_2_ID = 6; 90 private static final int SUGGEST_COLUMN_QUERY_ID = 7; 91 private static final int SUGGEST_COLUMN_FORMAT = 8; 92 private static final int SUGGEST_COLUMN_INTENT_EXTRA_DATA = 9; 93 94 // shared suggestion columns 95 private static final String[] COLUMNS = new String[] { 96 "_id", 97 SearchManager.SUGGEST_COLUMN_INTENT_ACTION, 98 SearchManager.SUGGEST_COLUMN_INTENT_DATA, 99 SearchManager.SUGGEST_COLUMN_TEXT_1, 100 SearchManager.SUGGEST_COLUMN_TEXT_2, 101 SearchManager.SUGGEST_COLUMN_ICON_1, 102 SearchManager.SUGGEST_COLUMN_ICON_2, 103 SearchManager.SUGGEST_COLUMN_QUERY, 104 SearchManager.SUGGEST_COLUMN_FORMAT, 105 SearchManager.SUGGEST_COLUMN_INTENT_EXTRA_DATA}; 106 107 private static final int MAX_SUGGESTION_SHORT_ENTRIES = 3; 108 private static final int MAX_SUGGESTION_LONG_ENTRIES = 6; 109 private static final String MAX_SUGGESTION_LONG_ENTRIES_STRING = 110 Integer.valueOf(MAX_SUGGESTION_LONG_ENTRIES).toString(); 111 112 // make sure that these match the index of TABLE_NAMES 113 private static final int URI_MATCH_BOOKMARKS = 0; 114 private static final int URI_MATCH_SEARCHES = 1; 115 // (id % 10) should match the table name index 116 private static final int URI_MATCH_BOOKMARKS_ID = 10; 117 private static final int URI_MATCH_SEARCHES_ID = 11; 118 // 119 private static final int URI_MATCH_SUGGEST = 20; 120 private static final int URI_MATCH_BOOKMARKS_SUGGEST = 21; 121 122 private static final UriMatcher URI_MATCHER; 123 124 static { 125 URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH); 126 URI_MATCHER.addURI("browser", TABLE_NAMES[URI_MATCH_BOOKMARKS], 127 URI_MATCH_BOOKMARKS); 128 URI_MATCHER.addURI("browser", TABLE_NAMES[URI_MATCH_BOOKMARKS] + "/#", 129 URI_MATCH_BOOKMARKS_ID); 130 URI_MATCHER.addURI("browser", TABLE_NAMES[URI_MATCH_SEARCHES], 131 URI_MATCH_SEARCHES); 132 URI_MATCHER.addURI("browser", TABLE_NAMES[URI_MATCH_SEARCHES] + "/#", 133 URI_MATCH_SEARCHES_ID); 134 URI_MATCHER.addURI("browser", SearchManager.SUGGEST_URI_PATH_QUERY, 135 URI_MATCH_SUGGEST); 136 URI_MATCHER.addURI("browser", 137 TABLE_NAMES[URI_MATCH_BOOKMARKS] + "/" + SearchManager.SUGGEST_URI_PATH_QUERY, 138 URI_MATCH_BOOKMARKS_SUGGEST); 139 } 140 141 // 1 -> 2 add cache table 142 // 2 -> 3 update history table 143 // 3 -> 4 add passwords table 144 // 4 -> 5 add settings table 145 // 5 -> 6 ? 146 // 6 -> 7 ? 147 // 7 -> 8 drop proxy table 148 // 8 -> 9 drop settings table 149 // 9 -> 10 add form_urls and form_data 150 // 10 -> 11 add searches table 151 // 11 -> 12 modify cache table 152 // 12 -> 13 modify cache table 153 // 13 -> 14 correspond with Google Bookmarks schema 154 // 14 -> 15 move couple of tables to either browser private database or webview database 155 // 15 -> 17 Set it up for the SearchManager 156 // 17 -> 18 Added favicon in bookmarks table for Home shortcuts 157 // 18 -> 19 Remove labels table 158 // 19 -> 20 Added thumbnail 159 // 20 -> 21 Added touch_icon 160 // 21 -> 22 Remove "clientid" 161 private static final int DATABASE_VERSION = 22; 162 163 // Regular expression which matches http://, followed by some stuff, followed by 164 // optionally a trailing slash, all matched as separate groups. 165 private static final Pattern STRIP_URL_PATTERN = Pattern.compile("^(http://)(.*?)(/$)?"); 166 167 private SearchManager mSearchManager; 168 169 // The ID of the ColorStateList to be applied to urls of website suggestions, as derived from 170 // the current theme. This is not set until/unless beautifyUrl is called, at which point 171 // this variable caches the color value. 172 private static String mSearchUrlColorId; 173 174 public BrowserProvider() { 175 } 176 177 178 private static CharSequence replaceSystemPropertyInString(Context context, CharSequence srcString) { 179 StringBuffer sb = new StringBuffer(); 180 int lastCharLoc = 0; 181 182// final String client_id = Partner.getString(context.getContentResolver(), 183// Partner.CLIENT_ID, "android-google"); 184 185 for (int i = 0; i < srcString.length(); ++i) { 186 char c = srcString.charAt(i); 187 if (c == '{') { 188 sb.append(srcString.subSequence(lastCharLoc, i)); 189 lastCharLoc = i; 190 inner: 191 for (int j = i; j < srcString.length(); ++j) { 192 char k = srcString.charAt(j); 193 if (k == '}') { 194 String propertyKeyValue = srcString.subSequence(i + 1, j).toString(); 195 196 sb.append("unknown"); 197 198 lastCharLoc = j + 1; 199 i = j; 200 break inner; 201 } 202 } 203 } 204 } 205 if (srcString.length() - lastCharLoc > 0) { 206 // Put on the tail, if there is one 207 sb.append(srcString.subSequence(lastCharLoc, srcString.length())); 208 } 209 return sb; 210 } 211 212 private static class DatabaseHelper extends SQLiteOpenHelper { 213 private Context mContext; 214 215 public DatabaseHelper(Context context) { 216 super(context, sDatabaseName, null, DATABASE_VERSION); 217 mContext = context; 218 } 219 220 @Override 221 public void onCreate(SQLiteDatabase db) { 222 db.execSQL("CREATE TABLE bookmarks (" + 223 "_id INTEGER PRIMARY KEY," + 224 "title TEXT," + 225 "url TEXT," + 226 "visits INTEGER," + 227 "date LONG," + 228 "created LONG," + 229 "description TEXT," + 230 "bookmark INTEGER," + 231 "favicon BLOB DEFAULT NULL," + 232 "thumbnail BLOB DEFAULT NULL," + 233 "touch_icon BLOB DEFAULT NULL" + 234 ");"); 235 236 final CharSequence[] bookmarks = mContext.getResources() 237 .getTextArray(R.array.bookmarks); 238 int size = bookmarks.length; 239 try { 240 for (int i = 0; i < size; i = i + 2) { 241 CharSequence bookmarkDestination = replaceSystemPropertyInString(mContext, bookmarks[i + 1]); 242 db.execSQL("INSERT INTO bookmarks (title, url, visits, " + 243 "date, created, bookmark)" + " VALUES('" + 244 bookmarks[i] + "', '" + bookmarkDestination + 245 "', 0, 0, 0, 1);"); 246 } 247 } catch (ArrayIndexOutOfBoundsException e) { 248 } 249 250 db.execSQL("CREATE TABLE searches (" + 251 "_id INTEGER PRIMARY KEY," + 252 "search TEXT," + 253 "date LONG" + 254 ");"); 255 } 256 257 @Override 258 public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { 259 Log.w(TAG, "Upgrading database from version " + oldVersion + " to " 260 + newVersion); 261 if (oldVersion == 18) { 262 db.execSQL("DROP TABLE IF EXISTS labels"); 263 } 264 if (oldVersion <= 19) { 265 db.execSQL("ALTER TABLE bookmarks ADD COLUMN thumbnail BLOB DEFAULT NULL;"); 266 } 267 if (oldVersion < 21) { 268 db.execSQL("ALTER TABLE bookmarks ADD COLUMN touch_icon BLOB DEFAULT NULL;"); 269 } 270 if (oldVersion < 22) { 271 db.execSQL("DELETE FROM bookmarks WHERE (bookmark = 0 AND url LIKE \"%.google.%client=ms-%\")"); 272 removeGears(); 273 } else { 274 db.execSQL("DROP TABLE IF EXISTS bookmarks"); 275 db.execSQL("DROP TABLE IF EXISTS searches"); 276 onCreate(db); 277 } 278 } 279 280 private void removeGears() { 281 AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() { 282 public Void doInBackground(Void... unused) { 283 String browserDataDirString = mContext.getApplicationInfo().dataDir; 284 final String appPluginsDirString = "app_plugins"; 285 final String gearsPrefix = "gears"; 286 File appPluginsDir = new File(browserDataDirString + File.separator 287 + appPluginsDirString); 288 if (!appPluginsDir.exists()) { 289 return null; 290 } 291 // Delete the Gears plugin files 292 File[] gearsFiles = appPluginsDir.listFiles(new FilenameFilter() { 293 public boolean accept(File dir, String filename) { 294 return filename.startsWith(gearsPrefix); 295 } 296 }); 297 for (int i = 0; i < gearsFiles.length; ++i) { 298 if (gearsFiles[i].isDirectory()) { 299 deleteDirectory(gearsFiles[i]); 300 } else { 301 gearsFiles[i].delete(); 302 } 303 } 304 // Delete the Gears data files 305 File gearsDataDir = new File(browserDataDirString + File.separator 306 + gearsPrefix); 307 if (!gearsDataDir.exists()) { 308 return null; 309 } 310 deleteDirectory(gearsDataDir); 311 return null; 312 } 313 314 private void deleteDirectory(File currentDir) { 315 File[] files = currentDir.listFiles(); 316 for (int i = 0; i < files.length; ++i) { 317 if (files[i].isDirectory()) { 318 deleteDirectory(files[i]); 319 } 320 files[i].delete(); 321 } 322 currentDir.delete(); 323 } 324 }; 325 326 task.execute(); 327 } 328 } 329 330 @Override 331 public boolean onCreate() { 332 final Context context = getContext(); 333 mOpenHelper = new DatabaseHelper(context); 334// mBackupManager = new BackupManager(context); 335 // we added "picasa web album" into default bookmarks for version 19. 336 // To avoid erasing the bookmark table, we added it explicitly for 337 // version 18 and 19 as in the other cases, we will erase the table. 338 if (DATABASE_VERSION == 18 || DATABASE_VERSION == 19) { 339 SharedPreferences p = PreferenceManager 340 .getDefaultSharedPreferences(context); 341 boolean fix = p.getBoolean("fix_picasa", true); 342 if (fix) { 343 fixPicasaBookmark(); 344 Editor ed = p.edit(); 345 ed.putBoolean("fix_picasa", false); 346 ed.commit(); 347 } 348 } 349 mSearchManager = (SearchManager) context.getSystemService(Context.SEARCH_SERVICE); 350 mShowWebSuggestionsSettingChangeObserver 351 = new ShowWebSuggestionsSettingChangeObserver(); 352 context.getContentResolver().registerContentObserver( 353 Settings.System.getUriFor( 354 Settings.System.SHOW_WEB_SUGGESTIONS), 355 true, mShowWebSuggestionsSettingChangeObserver); 356 updateShowWebSuggestions(); 357 return true; 358 } 359 360 /** 361 * This Observer will ensure that if the user changes the system 362 * setting of whether to display web suggestions, we will 363 * change accordingly. 364 */ 365 /* package */ class ShowWebSuggestionsSettingChangeObserver 366 extends ContentObserver { 367 public ShowWebSuggestionsSettingChangeObserver() { 368 super(new Handler()); 369 } 370 371 @Override 372 public void onChange(boolean selfChange) { 373 updateShowWebSuggestions(); 374 } 375 } 376 377 private ShowWebSuggestionsSettingChangeObserver 378 mShowWebSuggestionsSettingChangeObserver; 379 380 // If non-null, then the system is set to show web suggestions, 381 // and this is the SearchableInfo to use to get them. 382// private SearchableInfo mSearchableInfo; 383 384 /** 385 * Check the system settings to see whether web suggestions are 386 * allowed. If so, store the SearchableInfo to grab suggestions 387 * while the user is typing. 388 */ 389 private void updateShowWebSuggestions() { 390// mSearchableInfo = null; 391 Context context = getContext(); 392 if (Settings.System.getInt(context.getContentResolver(), 393 Settings.System.SHOW_WEB_SUGGESTIONS, 394 1 /* default on */) == 1) { 395 Intent intent = new Intent(Intent.ACTION_WEB_SEARCH); 396 intent.addCategory(Intent.CATEGORY_DEFAULT); 397 ResolveInfo info = context.getPackageManager().resolveActivity( 398 intent, PackageManager.MATCH_DEFAULT_ONLY); 399 if (info != null) { 400 ComponentName googleSearchComponent = 401 new ComponentName(info.activityInfo.packageName, 402 info.activityInfo.name); 403// mSearchableInfo = mSearchManager.getSearchableInfo( 404// googleSearchComponent, false); 405 } 406 } 407 } 408 409 private void fixPicasaBookmark() { 410 SQLiteDatabase db = mOpenHelper.getWritableDatabase(); 411 Cursor cursor = db.rawQuery("SELECT _id FROM bookmarks WHERE " + 412 "bookmark = 1 AND url = ?", new String[] { PICASA_URL }); 413 try { 414 if (!cursor.moveToFirst()) { 415 // set "created" so that it will be on the top of the list 416 db.execSQL("INSERT INTO bookmarks (title, url, visits, " + 417 "date, created, bookmark)" + " VALUES('" + 418 getContext().getString(R.string.picasa) + "', '" 419 + PICASA_URL + "', 0, 0, " + new Date().getTime() 420 + ", 1);"); 421 } 422 } finally { 423 if (cursor != null) { 424 cursor.close(); 425 } 426 } 427 } 428 429 /* 430 * Subclass AbstractCursor so we can combine multiple Cursors and add 431 * "Google Search". 432 * Here are the rules. 433 * 1. We only have MAX_SUGGESTION_LONG_ENTRIES in the list plus 434 * "Google Search"; 435 * 2. If bookmark/history entries are less than 436 * (MAX_SUGGESTION_SHORT_ENTRIES -1), we include Google suggest. 437 */ 438 private class MySuggestionCursor extends AbstractCursor { 439 private Cursor mHistoryCursor; 440 private Cursor mSuggestCursor; 441 private int mHistoryCount; 442 private int mSuggestionCount; 443 private boolean mBeyondCursor; 444 private String mString; 445 private int mSuggestText1Id; 446 private int mSuggestText2Id; 447 private int mSuggestQueryId; 448 private int mSuggestIntentExtraDataId; 449 450 public MySuggestionCursor(Cursor hc, Cursor sc, String string) { 451 mHistoryCursor = hc; 452 mSuggestCursor = sc; 453 mHistoryCount = hc.getCount(); 454 mSuggestionCount = sc != null ? sc.getCount() : 0; 455 if (mSuggestionCount > (MAX_SUGGESTION_LONG_ENTRIES - mHistoryCount)) { 456 mSuggestionCount = MAX_SUGGESTION_LONG_ENTRIES - mHistoryCount; 457 } 458 mString = string; 459 mBeyondCursor = false; 460 461 // Some web suggest providers only give suggestions and have no description string for 462 // items. The order of the result columns may be different as well. So retrieve the 463 // column indices for the fields we need now and check before using below. 464 if (mSuggestCursor == null) { 465 mSuggestText1Id = -1; 466 mSuggestText2Id = -1; 467 mSuggestQueryId = -1; 468 mSuggestIntentExtraDataId = -1; 469 } else { 470 mSuggestText1Id = mSuggestCursor.getColumnIndex( 471 SearchManager.SUGGEST_COLUMN_TEXT_1); 472 mSuggestText2Id = mSuggestCursor.getColumnIndex( 473 SearchManager.SUGGEST_COLUMN_TEXT_2); 474 mSuggestQueryId = mSuggestCursor.getColumnIndex( 475 SearchManager.SUGGEST_COLUMN_QUERY); 476 mSuggestIntentExtraDataId = mSuggestCursor.getColumnIndex( 477 SearchManager.SUGGEST_COLUMN_INTENT_EXTRA_DATA); 478 } 479 } 480 481 @Override 482 public boolean onMove(int oldPosition, int newPosition) { 483 if (mHistoryCursor == null) { 484 return false; 485 } 486 if (mHistoryCount > newPosition) { 487 mHistoryCursor.moveToPosition(newPosition); 488 mBeyondCursor = false; 489 } else if (mHistoryCount + mSuggestionCount > newPosition) { 490 mSuggestCursor.moveToPosition(newPosition - mHistoryCount); 491 mBeyondCursor = false; 492 } else { 493 mBeyondCursor = true; 494 } 495 return true; 496 } 497 498 @Override 499 public int getCount() { 500 if (mString.length() > 0) { 501 return mHistoryCount + mSuggestionCount + 1; 502 } else { 503 return mHistoryCount + mSuggestionCount; 504 } 505 } 506 507 @Override 508 public String[] getColumnNames() { 509 return COLUMNS; 510 } 511 512 @Override 513 public String getString(int columnIndex) { 514 if ((mPos != -1 && mHistoryCursor != null)) { 515 switch(columnIndex) { 516 case SUGGEST_COLUMN_INTENT_ACTION_ID: 517 if (mHistoryCount > mPos) { 518 return Intent.ACTION_VIEW; 519 } else { 520 return Intent.ACTION_SEARCH; 521 } 522 523 case SUGGEST_COLUMN_INTENT_DATA_ID: 524 if (mHistoryCount > mPos) { 525 return mHistoryCursor.getString(1); 526 } else { 527 return null; 528 } 529 530 case SUGGEST_COLUMN_TEXT_1_ID: 531 if (mHistoryCount > mPos) { 532 return getHistoryTitle(); 533 } else if (!mBeyondCursor) { 534 if (mSuggestText1Id == -1) return null; 535 return mSuggestCursor.getString(mSuggestText1Id); 536 } else { 537 return mString; 538 } 539 540 case SUGGEST_COLUMN_TEXT_2_ID: 541 if (mHistoryCount > mPos) { 542 return getHistorySubtitle(); 543 } else if (!mBeyondCursor) { 544 if (mSuggestText2Id == -1) return null; 545 return mSuggestCursor.getString(mSuggestText2Id); 546 } else { 547 return getContext().getString(R.string.search_the_web); 548 } 549 550 case SUGGEST_COLUMN_ICON_1_ID: 551 if (mHistoryCount > mPos) { 552 if (mHistoryCursor.getInt(3) == 1) { 553 return Integer.valueOf( 554 R.drawable.ic_search_category_bookmark) 555 .toString(); 556 } else { 557 return Integer.valueOf( 558 R.drawable.ic_search_category_history) 559 .toString(); 560 } 561 } else { 562 return Integer.valueOf( 563 R.drawable.ic_search_category_suggest) 564 .toString(); 565 } 566 567 case SUGGEST_COLUMN_ICON_2_ID: 568 return "0"; 569 570 case SUGGEST_COLUMN_QUERY_ID: 571 if (mHistoryCount > mPos) { 572 // Return the url in the intent query column. This is ignored 573 // within the browser because our searchable is set to 574 // android:searchMode="queryRewriteFromData", but it is used by 575 // global search for query rewriting. 576 return mHistoryCursor.getString(1); 577 } else if (!mBeyondCursor) { 578 if (mSuggestQueryId == -1) return null; 579 return mSuggestCursor.getString(mSuggestQueryId); 580 } else { 581 return mString; 582 } 583 584 case SUGGEST_COLUMN_FORMAT: 585 return "html"; 586 587 case SUGGEST_COLUMN_INTENT_EXTRA_DATA: 588 if (mHistoryCount > mPos) { 589 return null; 590 } else if (!mBeyondCursor) { 591 if (mSuggestIntentExtraDataId == -1) return null; 592 return mSuggestCursor.getString(mSuggestIntentExtraDataId); 593 } else { 594 return null; 595 } 596 } 597 } 598 return null; 599 } 600 601 @Override 602 public double getDouble(int column) { 603 throw new UnsupportedOperationException(); 604 } 605 606 @Override 607 public float getFloat(int column) { 608 throw new UnsupportedOperationException(); 609 } 610 611 @Override 612 public int getInt(int column) { 613 throw new UnsupportedOperationException(); 614 } 615 616 @Override 617 public long getLong(int column) { 618 if ((mPos != -1) && column == 0) { 619 return mPos; // use row# as the _Id 620 } 621 throw new UnsupportedOperationException(); 622 } 623 624 @Override 625 public short getShort(int column) { 626 throw new UnsupportedOperationException(); 627 } 628 629 @Override 630 public boolean isNull(int column) { 631 throw new UnsupportedOperationException(); 632 } 633 634 // TODO Temporary change, finalize after jq's changes go in 635 public void deactivate() { 636 if (mHistoryCursor != null) { 637 mHistoryCursor.deactivate(); 638 } 639 if (mSuggestCursor != null) { 640 mSuggestCursor.deactivate(); 641 } 642 super.deactivate(); 643 } 644 645 public boolean requery() { 646 return (mHistoryCursor != null ? mHistoryCursor.requery() : false) | 647 (mSuggestCursor != null ? mSuggestCursor.requery() : false); 648 } 649 650 // TODO Temporary change, finalize after jq's changes go in 651 public void close() { 652 super.close(); 653 if (mHistoryCursor != null) { 654 mHistoryCursor.close(); 655 mHistoryCursor = null; 656 } 657 if (mSuggestCursor != null) { 658 mSuggestCursor.close(); 659 mSuggestCursor = null; 660 } 661 } 662 663 /** 664 * Provides the title (text line 1) for a browser suggestion, which should be the 665 * webpage title. If the webpage title is empty, returns the stripped url instead. 666 * 667 * @return the title string to use 668 */ 669 private String getHistoryTitle() { 670 String title = mHistoryCursor.getString(2 /* webpage title */); 671 if (TextUtils.isEmpty(title) || TextUtils.getTrimmedLength(title) == 0) { 672 title = beautifyUrl(mHistoryCursor.getString(1 /* url */)); 673 } 674 return title; 675 } 676 677 /** 678 * Provides the subtitle (text line 2) for a browser suggestion, which should be the 679 * webpage url. If the webpage title is empty, then the url should go in the title 680 * instead, and the subtitle should be empty, so this would return null. 681 * 682 * @return the subtitle string to use, or null if none 683 */ 684 private String getHistorySubtitle() { 685 String title = mHistoryCursor.getString(2 /* webpage title */); 686 if (TextUtils.isEmpty(title) || TextUtils.getTrimmedLength(title) == 0) { 687 return null; 688 } else { 689 return beautifyUrl(mHistoryCursor.getString(1 /* url */)); 690 } 691 } 692 693 /** 694 * Strips "http://" from the beginning of a url and "/" from the end, 695 * and adds html formatting to make it green. 696 */ 697 private String beautifyUrl(String url) { 698 if (mSearchUrlColorId == null) { 699 // Get the color used for this purpose from the current theme. 700 TypedValue colorValue = new TypedValue(); 701// getContext().getTheme().resolveAttribute( 702// com.android.internal.R.attr.textColorSearchUrl, colorValue, true); 703 mSearchUrlColorId = Integer.toString(colorValue.resourceId); 704 } 705 706 return "<font color=\"@" + mSearchUrlColorId + "\">" + stripUrl(url) + "</font>"; 707 } 708 } 709 710 @Override 711 public Cursor query(Uri url, String[] projectionIn, String selection, 712 String[] selectionArgs, String sortOrder) 713 throws IllegalStateException { 714 SQLiteDatabase db = mOpenHelper.getReadableDatabase(); 715 716 int match = URI_MATCHER.match(url); 717 if (match == -1) { 718 throw new IllegalArgumentException("Unknown URL"); 719 } 720 721 if (match == URI_MATCH_SUGGEST || match == URI_MATCH_BOOKMARKS_SUGGEST) { 722 String suggestSelection; 723 String [] myArgs; 724 if (selectionArgs[0] == null || selectionArgs[0].equals("")) { 725 suggestSelection = null; 726 myArgs = null; 727 } else { 728 String like = selectionArgs[0] + "%"; 729 if (selectionArgs[0].startsWith("http") 730 || selectionArgs[0].startsWith("file")) { 731 myArgs = new String[1]; 732 myArgs[0] = like; 733 suggestSelection = selection; 734 } else { 735 SUGGEST_ARGS[0] = "http://" + like; 736 SUGGEST_ARGS[1] = "http://www." + like; 737 SUGGEST_ARGS[2] = "https://" + like; 738 SUGGEST_ARGS[3] = "https://www." + like; 739 // To match against titles. 740 SUGGEST_ARGS[4] = like; 741 myArgs = SUGGEST_ARGS; 742 suggestSelection = SUGGEST_SELECTION; 743 } 744 } 745 746 Cursor c = db.query(TABLE_NAMES[URI_MATCH_BOOKMARKS], 747 SUGGEST_PROJECTION, suggestSelection, myArgs, null, null, 748 ORDER_BY, MAX_SUGGESTION_LONG_ENTRIES_STRING); 749 750 if (match == URI_MATCH_BOOKMARKS_SUGGEST) { 751// || Regex.WEB_URL_PATTERN.matcher(selectionArgs[0]).matches()) { 752 return new MySuggestionCursor(c, null, ""); 753 } else { 754 // get Google suggest if there is still space in the list 755 /* 756 if (myArgs != null && myArgs.length > 1 757 && mSearchableInfo != null 758 && c.getCount() < (MAX_SUGGESTION_SHORT_ENTRIES - 1)) { 759 Cursor sc = mSearchManager.getSuggestions(mSearchableInfo, selectionArgs[0]); 760 return new MySuggestionCursor(c, sc, selectionArgs[0]); 761 } 762 */ 763 return new MySuggestionCursor(c, null, selectionArgs[0]); 764 } 765 } 766 767 String[] projection = null; 768 if (projectionIn != null && projectionIn.length > 0) { 769 projection = new String[projectionIn.length + 1]; 770 System.arraycopy(projectionIn, 0, projection, 0, projectionIn.length); 771 projection[projectionIn.length] = "_id AS _id"; 772 } 773 774 StringBuilder whereClause = new StringBuilder(256); 775 if (match == URI_MATCH_BOOKMARKS_ID || match == URI_MATCH_SEARCHES_ID) { 776 whereClause.append("(_id = ").append(url.getPathSegments().get(1)) 777 .append(")"); 778 } 779 780 // Tack on the user's selection, if present 781 if (selection != null && selection.length() > 0) { 782 if (whereClause.length() > 0) { 783 whereClause.append(" AND "); 784 } 785 786 whereClause.append('('); 787 whereClause.append(selection); 788 whereClause.append(')'); 789 } 790 Cursor c = db.query(TABLE_NAMES[match % 10], projection, 791 whereClause.toString(), selectionArgs, null, null, sortOrder, 792 null); 793 c.setNotificationUri(getContext().getContentResolver(), url); 794 return c; 795 } 796 797 @Override 798 public String getType(Uri url) { 799 int match = URI_MATCHER.match(url); 800 switch (match) { 801 case URI_MATCH_BOOKMARKS: 802 return "vnd.android.cursor.dir/bookmark"; 803 804 case URI_MATCH_BOOKMARKS_ID: 805 return "vnd.android.cursor.item/bookmark"; 806 807 case URI_MATCH_SEARCHES: 808 return "vnd.android.cursor.dir/searches"; 809 810 case URI_MATCH_SEARCHES_ID: 811 return "vnd.android.cursor.item/searches"; 812 813 case URI_MATCH_SUGGEST: 814 return SearchManager.SUGGEST_MIME_TYPE; 815 816 default: 817 throw new IllegalArgumentException("Unknown URL"); 818 } 819 } 820 821 @Override 822 public Uri insert(Uri url, ContentValues initialValues) { 823 boolean isBookmarkTable = false; 824 SQLiteDatabase db = mOpenHelper.getWritableDatabase(); 825 826 int match = URI_MATCHER.match(url); 827 Uri uri = null; 828 switch (match) { 829 case URI_MATCH_BOOKMARKS: { 830 // Insert into the bookmarks table 831 long rowID = db.insert(TABLE_NAMES[URI_MATCH_BOOKMARKS], "url", 832 initialValues); 833 if (rowID > 0) { 834 uri = ContentUris.withAppendedId(Browser.BOOKMARKS_URI, 835 rowID); 836 } 837 isBookmarkTable = true; 838 break; 839 } 840 841 case URI_MATCH_SEARCHES: { 842 // Insert into the searches table 843 long rowID = db.insert(TABLE_NAMES[URI_MATCH_SEARCHES], "url", 844 initialValues); 845 if (rowID > 0) { 846 uri = ContentUris.withAppendedId(Browser.SEARCHES_URI, 847 rowID); 848 } 849 break; 850 } 851 852 default: 853 throw new IllegalArgumentException("Unknown URL"); 854 } 855 856 if (uri == null) { 857 throw new IllegalArgumentException("Unknown URL"); 858 } 859 getContext().getContentResolver().notifyChange(uri, null); 860 861 // Back up the new bookmark set if we just inserted one. 862 // A row created when bookmarks are added from scratch will have 863 // bookmark=1 in the initial value set. 864 if (isBookmarkTable 865 && initialValues.containsKey(BookmarkColumns.BOOKMARK) 866 && initialValues.getAsInteger(BookmarkColumns.BOOKMARK) != 0) { 867// mBackupManager.dataChanged(); 868 } 869 return uri; 870 } 871 872 @Override 873 public int delete(Uri url, String where, String[] whereArgs) { 874 SQLiteDatabase db = mOpenHelper.getWritableDatabase(); 875 876 int match = URI_MATCHER.match(url); 877 if (match == -1 || match == URI_MATCH_SUGGEST) { 878 throw new IllegalArgumentException("Unknown URL"); 879 } 880 881 // need to know whether it's the bookmarks table for a couple of reasons 882 boolean isBookmarkTable = (match == URI_MATCH_BOOKMARKS_ID); 883 String id = null; 884 885 if (isBookmarkTable || match == URI_MATCH_SEARCHES_ID) { 886 StringBuilder sb = new StringBuilder(); 887 if (where != null && where.length() > 0) { 888 sb.append("( "); 889 sb.append(where); 890 sb.append(" ) AND "); 891 } 892 id = url.getPathSegments().get(1); 893 sb.append("_id = "); 894 sb.append(id); 895 where = sb.toString(); 896 } 897 898 ContentResolver cr = getContext().getContentResolver(); 899 900 // we'lll need to back up the bookmark set if we are about to delete one 901 if (isBookmarkTable) { 902 Cursor cursor = cr.query(Browser.BOOKMARKS_URI, 903 new String[] { BookmarkColumns.BOOKMARK }, 904 "_id = " + id, null, null); 905 if (cursor.moveToNext()) { 906 if (cursor.getInt(0) != 0) { 907 // yep, this record is a bookmark 908// mBackupManager.dataChanged(); 909 } 910 } 911 cursor.close(); 912 } 913 914 int count = db.delete(TABLE_NAMES[match % 10], where, whereArgs); 915 cr.notifyChange(url, null); 916 return count; 917 } 918 919 @Override 920 public int update(Uri url, ContentValues values, String where, 921 String[] whereArgs) { 922 SQLiteDatabase db = mOpenHelper.getWritableDatabase(); 923 924 int match = URI_MATCHER.match(url); 925 if (match == -1 || match == URI_MATCH_SUGGEST) { 926 throw new IllegalArgumentException("Unknown URL"); 927 } 928 929 String id = null; 930 boolean isBookmarkTable = (match == URI_MATCH_BOOKMARKS_ID); 931 boolean changingBookmarks = false; 932 933 if (isBookmarkTable || match == URI_MATCH_SEARCHES_ID) { 934 StringBuilder sb = new StringBuilder(); 935 if (where != null && where.length() > 0) { 936 sb.append("( "); 937 sb.append(where); 938 sb.append(" ) AND "); 939 } 940 id = url.getPathSegments().get(1); 941 sb.append("_id = "); 942 sb.append(id); 943 where = sb.toString(); 944 } 945 946 ContentResolver cr = getContext().getContentResolver(); 947 948 // Not all bookmark-table updates should be backed up. Look to see 949 // whether we changed the title, url, or "is a bookmark" state, and 950 // request a backup if so. 951 if (isBookmarkTable) { 952 // Alterations to the bookmark field inherently change the bookmark 953 // set, so we don't need to query the record; we know a priori that 954 // we will need to back up this change. 955 if (values.containsKey(BookmarkColumns.BOOKMARK)) { 956 changingBookmarks = true; 957 } 958 // changing the title or URL of a bookmark record requires a backup, 959 // but we don't know wether such an update is on a bookmark without 960 // querying the record 961 if (!changingBookmarks && 962 (values.containsKey(BookmarkColumns.TITLE) 963 || values.containsKey(BookmarkColumns.URL))) { 964 // when isBookmarkTable is true, the 'id' var was assigned above 965 Cursor cursor = cr.query(Browser.BOOKMARKS_URI, 966 new String[] { BookmarkColumns.BOOKMARK }, 967 "_id = " + id, null, null); 968 if (cursor.moveToNext()) { 969 changingBookmarks = (cursor.getInt(0) != 0); 970 } 971 cursor.close(); 972 } 973 974 // if this *is* a bookmark row we're altering, we need to back it up. 975 if (changingBookmarks) { 976// mBackupManager.dataChanged(); 977 } 978 } 979 980 int ret = db.update(TABLE_NAMES[match % 10], values, where, whereArgs); 981 cr.notifyChange(url, null); 982 return ret; 983 } 984 985 /** 986 * Strips the provided url of preceding "http://" and any trailing "/". Does not 987 * strip "https://". If the provided string cannot be stripped, the original string 988 * is returned. 989 * 990 * TODO: Put this in TextUtils to be used by other packages doing something similar. 991 * 992 * @param url a url to strip, like "http://www.google.com/" 993 * @return a stripped url like "www.google.com", or the original string if it could 994 * not be stripped 995 */ 996 private static String stripUrl(String url) { 997 if (url == null) return null; 998 Matcher m = STRIP_URL_PATTERN.matcher(url); 999 if (m.matches() && m.groupCount() == 3) { 1000 return m.group(2); 1001 } else { 1002 return url; 1003 } 1004 } 1005 1006}