BrowserProvider.java revision 655e867add447f1e1aeb84557b02ea18d8ea1b34
166f2cfede1affd65ebc0b2e6854d2aabcfd9bb90Marco Nelissen/* 266f2cfede1affd65ebc0b2e6854d2aabcfd9bb90Marco Nelissen * Copyright (C) 2006 The Android Open Source Project 366f2cfede1affd65ebc0b2e6854d2aabcfd9bb90Marco Nelissen * 466f2cfede1affd65ebc0b2e6854d2aabcfd9bb90Marco Nelissen * Licensed under the Apache License, Version 2.0 (the "License"); 566f2cfede1affd65ebc0b2e6854d2aabcfd9bb90Marco Nelissen * you may not use this file except in compliance with the License. 666f2cfede1affd65ebc0b2e6854d2aabcfd9bb90Marco Nelissen * You may obtain a copy of the License at 766f2cfede1affd65ebc0b2e6854d2aabcfd9bb90Marco Nelissen * 866f2cfede1affd65ebc0b2e6854d2aabcfd9bb90Marco Nelissen * http://www.apache.org/licenses/LICENSE-2.0 966f2cfede1affd65ebc0b2e6854d2aabcfd9bb90Marco Nelissen * 1066f2cfede1affd65ebc0b2e6854d2aabcfd9bb90Marco Nelissen * Unless required by applicable law or agreed to in writing, software 1166f2cfede1affd65ebc0b2e6854d2aabcfd9bb90Marco Nelissen * distributed under the License is distributed on an "AS IS" BASIS, 1266f2cfede1affd65ebc0b2e6854d2aabcfd9bb90Marco Nelissen * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1366f2cfede1affd65ebc0b2e6854d2aabcfd9bb90Marco Nelissen * See the License for the specific language governing permissions and 1466f2cfede1affd65ebc0b2e6854d2aabcfd9bb90Marco Nelissen * limitations under the License. 1566f2cfede1affd65ebc0b2e6854d2aabcfd9bb90Marco Nelissen */ 1666f2cfede1affd65ebc0b2e6854d2aabcfd9bb90Marco Nelissen 1766f2cfede1affd65ebc0b2e6854d2aabcfd9bb90Marco Nelissenpackage com.android.browser; 1866f2cfede1affd65ebc0b2e6854d2aabcfd9bb90Marco Nelissen 1966f2cfede1affd65ebc0b2e6854d2aabcfd9bb90Marco Nelissenimport android.app.SearchManager; 2066f2cfede1affd65ebc0b2e6854d2aabcfd9bb90Marco Nelissenimport android.app.SearchableInfo; 2166f2cfede1affd65ebc0b2e6854d2aabcfd9bb90Marco Nelissenimport android.app.backup.BackupManager; 2266f2cfede1affd65ebc0b2e6854d2aabcfd9bb90Marco Nelissenimport android.content.ComponentName; 2366f2cfede1affd65ebc0b2e6854d2aabcfd9bb90Marco Nelissenimport android.content.ContentProvider; 2466f2cfede1affd65ebc0b2e6854d2aabcfd9bb90Marco Nelissenimport android.content.ContentResolver; 2566f2cfede1affd65ebc0b2e6854d2aabcfd9bb90Marco Nelissenimport android.content.ContentUris; 2666f2cfede1affd65ebc0b2e6854d2aabcfd9bb90Marco Nelissenimport android.content.ContentValues; 2766f2cfede1affd65ebc0b2e6854d2aabcfd9bb90Marco Nelissenimport android.content.Context; 2866f2cfede1affd65ebc0b2e6854d2aabcfd9bb90Marco Nelissenimport android.content.Intent; 2966f2cfede1affd65ebc0b2e6854d2aabcfd9bb90Marco Nelissenimport android.content.SharedPreferences; 3066f2cfede1affd65ebc0b2e6854d2aabcfd9bb90Marco Nelissenimport android.content.UriMatcher; 3166f2cfede1affd65ebc0b2e6854d2aabcfd9bb90Marco Nelissenimport android.content.SharedPreferences.Editor; 3266f2cfede1affd65ebc0b2e6854d2aabcfd9bb90Marco Nelissenimport android.content.pm.PackageManager; 3366f2cfede1affd65ebc0b2e6854d2aabcfd9bb90Marco Nelissenimport android.content.pm.ResolveInfo; 3466f2cfede1affd65ebc0b2e6854d2aabcfd9bb90Marco Nelissenimport android.database.AbstractCursor; 3566f2cfede1affd65ebc0b2e6854d2aabcfd9bb90Marco Nelissenimport android.database.ContentObserver; 3666f2cfede1affd65ebc0b2e6854d2aabcfd9bb90Marco Nelissenimport android.database.Cursor; 3766f2cfede1affd65ebc0b2e6854d2aabcfd9bb90Marco Nelissenimport android.database.sqlite.SQLiteDatabase; 3866f2cfede1affd65ebc0b2e6854d2aabcfd9bb90Marco Nelissenimport android.database.sqlite.SQLiteOpenHelper; 3966f2cfede1affd65ebc0b2e6854d2aabcfd9bb90Marco Nelissenimport android.net.Uri; 4066f2cfede1affd65ebc0b2e6854d2aabcfd9bb90Marco Nelissenimport android.os.Handler; 4166f2cfede1affd65ebc0b2e6854d2aabcfd9bb90Marco Nelissenimport android.os.Process; 4266f2cfede1affd65ebc0b2e6854d2aabcfd9bb90Marco Nelissenimport android.preference.PreferenceManager; 4366f2cfede1affd65ebc0b2e6854d2aabcfd9bb90Marco Nelissenimport android.provider.Browser; 4466f2cfede1affd65ebc0b2e6854d2aabcfd9bb90Marco Nelissenimport android.provider.Settings; 4566f2cfede1affd65ebc0b2e6854d2aabcfd9bb90Marco Nelissenimport android.provider.Browser.BookmarkColumns; 4666f2cfede1affd65ebc0b2e6854d2aabcfd9bb90Marco Nelissenimport android.speech.RecognizerResultsIntent; 4766f2cfede1affd65ebc0b2e6854d2aabcfd9bb90Marco Nelissenimport android.text.TextUtils; 4866f2cfede1affd65ebc0b2e6854d2aabcfd9bb90Marco Nelissenimport android.util.Log; 4966f2cfede1affd65ebc0b2e6854d2aabcfd9bb90Marco Nelissenimport android.util.Patterns; 5066f2cfede1affd65ebc0b2e6854d2aabcfd9bb90Marco Nelissenimport android.util.TypedValue; 5166f2cfede1affd65ebc0b2e6854d2aabcfd9bb90Marco Nelissen 5266f2cfede1affd65ebc0b2e6854d2aabcfd9bb90Marco Nelissen 5366f2cfede1affd65ebc0b2e6854d2aabcfd9bb90Marco Nelissenimport java.io.File; 5466f2cfede1affd65ebc0b2e6854d2aabcfd9bb90Marco Nelissenimport java.io.FilenameFilter; 5566f2cfede1affd65ebc0b2e6854d2aabcfd9bb90Marco Nelissenimport java.util.ArrayList; 5666f2cfede1affd65ebc0b2e6854d2aabcfd9bb90Marco Nelissenimport java.util.Date; 5766f2cfede1affd65ebc0b2e6854d2aabcfd9bb90Marco Nelissenimport java.util.regex.Matcher; 5866f2cfede1affd65ebc0b2e6854d2aabcfd9bb90Marco Nelissenimport java.util.regex.Pattern; 5966f2cfede1affd65ebc0b2e6854d2aabcfd9bb90Marco Nelissen 6066f2cfede1affd65ebc0b2e6854d2aabcfd9bb90Marco Nelissen 6166f2cfede1affd65ebc0b2e6854d2aabcfd9bb90Marco Nelissenpublic class BrowserProvider extends ContentProvider { 6266f2cfede1affd65ebc0b2e6854d2aabcfd9bb90Marco Nelissen 6366f2cfede1affd65ebc0b2e6854d2aabcfd9bb90Marco Nelissen private SQLiteOpenHelper mOpenHelper; 6466f2cfede1affd65ebc0b2e6854d2aabcfd9bb90Marco Nelissen private BackupManager mBackupManager; 6566f2cfede1affd65ebc0b2e6854d2aabcfd9bb90Marco Nelissen private static final String sDatabaseName = "browser.db"; 6666f2cfede1affd65ebc0b2e6854d2aabcfd9bb90Marco Nelissen private static final String TAG = "BrowserProvider"; 6766f2cfede1affd65ebc0b2e6854d2aabcfd9bb90Marco Nelissen private static final String ORDER_BY = "visits DESC, date DESC"; 6866f2cfede1affd65ebc0b2e6854d2aabcfd9bb90Marco Nelissen 6966f2cfede1affd65ebc0b2e6854d2aabcfd9bb90Marco Nelissen private static final String PICASA_URL = "http://picasaweb.google.com/m/" + 7066f2cfede1affd65ebc0b2e6854d2aabcfd9bb90Marco Nelissen "viewer?source=androidclient"; 7166f2cfede1affd65ebc0b2e6854d2aabcfd9bb90Marco Nelissen 7266f2cfede1affd65ebc0b2e6854d2aabcfd9bb90Marco Nelissen private static final String[] TABLE_NAMES = new String[] { 7366f2cfede1affd65ebc0b2e6854d2aabcfd9bb90Marco Nelissen "bookmarks", "searches" 7466f2cfede1affd65ebc0b2e6854d2aabcfd9bb90Marco Nelissen }; 7566f2cfede1affd65ebc0b2e6854d2aabcfd9bb90Marco Nelissen private static final String[] SUGGEST_PROJECTION = new String[] { 7666f2cfede1affd65ebc0b2e6854d2aabcfd9bb90Marco Nelissen "_id", "url", "title", "bookmark", "user_entered" 7766f2cfede1affd65ebc0b2e6854d2aabcfd9bb90Marco Nelissen }; 7866f2cfede1affd65ebc0b2e6854d2aabcfd9bb90Marco Nelissen private static final String SUGGEST_SELECTION = 7966f2cfede1affd65ebc0b2e6854d2aabcfd9bb90Marco Nelissen "(url LIKE ? OR url LIKE ? OR url LIKE ? OR url LIKE ?" 8066f2cfede1affd65ebc0b2e6854d2aabcfd9bb90Marco Nelissen + " OR title LIKE ?) AND (bookmark = 1 OR user_entered = 1)"; 8166f2cfede1affd65ebc0b2e6854d2aabcfd9bb90Marco Nelissen private String[] SUGGEST_ARGS = new String[5]; 8266f2cfede1affd65ebc0b2e6854d2aabcfd9bb90Marco Nelissen 8366f2cfede1affd65ebc0b2e6854d2aabcfd9bb90Marco Nelissen // shared suggestion array index, make sure to match COLUMNS 8466f2cfede1affd65ebc0b2e6854d2aabcfd9bb90Marco Nelissen private static final int SUGGEST_COLUMN_INTENT_ACTION_ID = 1; 8566f2cfede1affd65ebc0b2e6854d2aabcfd9bb90Marco Nelissen private static final int SUGGEST_COLUMN_INTENT_DATA_ID = 2; 8666f2cfede1affd65ebc0b2e6854d2aabcfd9bb90Marco Nelissen private static final int SUGGEST_COLUMN_TEXT_1_ID = 3; 8766f2cfede1affd65ebc0b2e6854d2aabcfd9bb90Marco Nelissen private static final int SUGGEST_COLUMN_TEXT_2_ID = 4; 8866f2cfede1affd65ebc0b2e6854d2aabcfd9bb90Marco Nelissen private static final int SUGGEST_COLUMN_TEXT_2_URL_ID = 5; 8966f2cfede1affd65ebc0b2e6854d2aabcfd9bb90Marco Nelissen private static final int SUGGEST_COLUMN_ICON_1_ID = 6; 9066f2cfede1affd65ebc0b2e6854d2aabcfd9bb90Marco Nelissen private static final int SUGGEST_COLUMN_ICON_2_ID = 7; 9166f2cfede1affd65ebc0b2e6854d2aabcfd9bb90Marco Nelissen private static final int SUGGEST_COLUMN_QUERY_ID = 8; 9266f2cfede1affd65ebc0b2e6854d2aabcfd9bb90Marco Nelissen private static final int SUGGEST_COLUMN_INTENT_EXTRA_DATA = 9; 9366f2cfede1affd65ebc0b2e6854d2aabcfd9bb90Marco Nelissen 9466f2cfede1affd65ebc0b2e6854d2aabcfd9bb90Marco Nelissen // shared suggestion columns 9566f2cfede1affd65ebc0b2e6854d2aabcfd9bb90Marco Nelissen private static final String[] COLUMNS = new String[] { 9666f2cfede1affd65ebc0b2e6854d2aabcfd9bb90Marco Nelissen "_id", 9766f2cfede1affd65ebc0b2e6854d2aabcfd9bb90Marco Nelissen SearchManager.SUGGEST_COLUMN_INTENT_ACTION, 9866f2cfede1affd65ebc0b2e6854d2aabcfd9bb90Marco Nelissen SearchManager.SUGGEST_COLUMN_INTENT_DATA, 9966f2cfede1affd65ebc0b2e6854d2aabcfd9bb90Marco Nelissen SearchManager.SUGGEST_COLUMN_TEXT_1, 10066f2cfede1affd65ebc0b2e6854d2aabcfd9bb90Marco Nelissen SearchManager.SUGGEST_COLUMN_TEXT_2, 10166f2cfede1affd65ebc0b2e6854d2aabcfd9bb90Marco Nelissen SearchManager.SUGGEST_COLUMN_TEXT_2_URL, 10266f2cfede1affd65ebc0b2e6854d2aabcfd9bb90Marco Nelissen SearchManager.SUGGEST_COLUMN_ICON_1, 10366f2cfede1affd65ebc0b2e6854d2aabcfd9bb90Marco Nelissen SearchManager.SUGGEST_COLUMN_ICON_2, 10466f2cfede1affd65ebc0b2e6854d2aabcfd9bb90Marco Nelissen SearchManager.SUGGEST_COLUMN_QUERY, 10566f2cfede1affd65ebc0b2e6854d2aabcfd9bb90Marco Nelissen 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 // 22 -> 23 Added user_entered 162 // 23 -> 24 Url not allowed to be null anymore. 163 private static final int DATABASE_VERSION = 24; 164 165 // Regular expression which matches http://, followed by some stuff, followed by 166 // optionally a trailing slash, all matched as separate groups. 167 private static final Pattern STRIP_URL_PATTERN = Pattern.compile("^(http://)(.*?)(/$)?"); 168 169 private SearchManager mSearchManager; 170 171 public BrowserProvider() { 172 } 173 174 // XXX: This is a major hack to remove our dependency on gsf constants and 175 // its content provider. http://b/issue?id=2425179 176 static String getClientId(ContentResolver cr) { 177 String ret = "android-google"; 178 Cursor c = null; 179 try { 180 c = cr.query(Uri.parse("content://com.google.settings/partner"), 181 new String[] { "value" }, "name='client_id'", null, null); 182 if (c != null && c.moveToNext()) { 183 ret = c.getString(0); 184 } 185 } catch (RuntimeException ex) { 186 // fall through to return the default 187 } finally { 188 if (c != null) { 189 c.close(); 190 } 191 } 192 return ret; 193 } 194 195 private static CharSequence replaceSystemPropertyInString(Context context, CharSequence srcString) { 196 StringBuffer sb = new StringBuffer(); 197 int lastCharLoc = 0; 198 199 final String client_id = getClientId(context.getContentResolver()); 200 201 for (int i = 0; i < srcString.length(); ++i) { 202 char c = srcString.charAt(i); 203 if (c == '{') { 204 sb.append(srcString.subSequence(lastCharLoc, i)); 205 lastCharLoc = i; 206 inner: 207 for (int j = i; j < srcString.length(); ++j) { 208 char k = srcString.charAt(j); 209 if (k == '}') { 210 String propertyKeyValue = srcString.subSequence(i + 1, j).toString(); 211 if (propertyKeyValue.equals("CLIENT_ID")) { 212 sb.append(client_id); 213 } else { 214 sb.append("unknown"); 215 } 216 lastCharLoc = j + 1; 217 i = j; 218 break inner; 219 } 220 } 221 } 222 } 223 if (srcString.length() - lastCharLoc > 0) { 224 // Put on the tail, if there is one 225 sb.append(srcString.subSequence(lastCharLoc, srcString.length())); 226 } 227 return sb; 228 } 229 230 private static class DatabaseHelper extends SQLiteOpenHelper { 231 private Context mContext; 232 233 public DatabaseHelper(Context context) { 234 super(context, sDatabaseName, null, DATABASE_VERSION); 235 mContext = context; 236 } 237 238 @Override 239 public void onCreate(SQLiteDatabase db) { 240 db.execSQL("CREATE TABLE bookmarks (" + 241 "_id INTEGER PRIMARY KEY," + 242 "title TEXT," + 243 "url TEXT NOT NULL," + 244 "visits INTEGER," + 245 "date LONG," + 246 "created LONG," + 247 "description TEXT," + 248 "bookmark INTEGER," + 249 "favicon BLOB DEFAULT NULL," + 250 "thumbnail BLOB DEFAULT NULL," + 251 "touch_icon BLOB DEFAULT NULL," + 252 "user_entered INTEGER" + 253 ");"); 254 255 final CharSequence[] bookmarks = mContext.getResources() 256 .getTextArray(R.array.bookmarks); 257 int size = bookmarks.length; 258 try { 259 for (int i = 0; i < size; i = i + 2) { 260 CharSequence bookmarkDestination = replaceSystemPropertyInString(mContext, bookmarks[i + 1]); 261 db.execSQL("INSERT INTO bookmarks (title, url, visits, " + 262 "date, created, bookmark)" + " VALUES('" + 263 bookmarks[i] + "', '" + bookmarkDestination + 264 "', 0, 0, 0, 1);"); 265 } 266 } catch (ArrayIndexOutOfBoundsException e) { 267 } 268 269 db.execSQL("CREATE TABLE searches (" + 270 "_id INTEGER PRIMARY KEY," + 271 "search TEXT," + 272 "date LONG" + 273 ");"); 274 } 275 276 @Override 277 public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { 278 Log.w(TAG, "Upgrading database from version " + oldVersion + " to " 279 + newVersion); 280 if (oldVersion == 18) { 281 db.execSQL("DROP TABLE IF EXISTS labels"); 282 } 283 if (oldVersion <= 19) { 284 db.execSQL("ALTER TABLE bookmarks ADD COLUMN thumbnail BLOB DEFAULT NULL;"); 285 } 286 if (oldVersion < 21) { 287 db.execSQL("ALTER TABLE bookmarks ADD COLUMN touch_icon BLOB DEFAULT NULL;"); 288 } 289 if (oldVersion < 22) { 290 db.execSQL("DELETE FROM bookmarks WHERE (bookmark = 0 AND url LIKE \"%.google.%client=ms-%\")"); 291 removeGears(); 292 } 293 if (oldVersion < 23) { 294 db.execSQL("ALTER TABLE bookmarks ADD COLUMN user_entered INTEGER;"); 295 } 296 if (oldVersion < 24) { 297 /* SQLite does not support ALTER COLUMN, hence the lengthy code. */ 298 db.execSQL("DELETE FROM bookmarks WHERE url IS NULL;"); 299 db.execSQL("ALTER TABLE bookmarks RENAME TO bookmarks_temp;"); 300 db.execSQL("CREATE TABLE bookmarks (" + 301 "_id INTEGER PRIMARY KEY," + 302 "title TEXT," + 303 "url TEXT NOT NULL," + 304 "visits INTEGER," + 305 "date LONG," + 306 "created LONG," + 307 "description TEXT," + 308 "bookmark INTEGER," + 309 "favicon BLOB DEFAULT NULL," + 310 "thumbnail BLOB DEFAULT NULL," + 311 "touch_icon BLOB DEFAULT NULL," + 312 "user_entered INTEGER" + 313 ");"); 314 db.execSQL("INSERT INTO bookmarks SELECT * FROM bookmarks_temp;"); 315 db.execSQL("DROP TABLE bookmarks_temp;"); 316 } else { 317 db.execSQL("DROP TABLE IF EXISTS bookmarks"); 318 db.execSQL("DROP TABLE IF EXISTS searches"); 319 onCreate(db); 320 } 321 } 322 323 private void removeGears() { 324 new Thread() { 325 @Override 326 public void run() { 327 Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); 328 String browserDataDirString = mContext.getApplicationInfo().dataDir; 329 final String appPluginsDirString = "app_plugins"; 330 final String gearsPrefix = "gears"; 331 File appPluginsDir = new File(browserDataDirString + File.separator 332 + appPluginsDirString); 333 if (!appPluginsDir.exists()) { 334 return; 335 } 336 // Delete the Gears plugin files 337 File[] gearsFiles = appPluginsDir.listFiles(new FilenameFilter() { 338 public boolean accept(File dir, String filename) { 339 return filename.startsWith(gearsPrefix); 340 } 341 }); 342 for (int i = 0; i < gearsFiles.length; ++i) { 343 if (gearsFiles[i].isDirectory()) { 344 deleteDirectory(gearsFiles[i]); 345 } else { 346 gearsFiles[i].delete(); 347 } 348 } 349 // Delete the Gears data files 350 File gearsDataDir = new File(browserDataDirString + File.separator 351 + gearsPrefix); 352 if (!gearsDataDir.exists()) { 353 return; 354 } 355 deleteDirectory(gearsDataDir); 356 } 357 358 private void deleteDirectory(File currentDir) { 359 File[] files = currentDir.listFiles(); 360 for (int i = 0; i < files.length; ++i) { 361 if (files[i].isDirectory()) { 362 deleteDirectory(files[i]); 363 } 364 files[i].delete(); 365 } 366 currentDir.delete(); 367 } 368 }.start(); 369 } 370 } 371 372 @Override 373 public boolean onCreate() { 374 final Context context = getContext(); 375 mOpenHelper = new DatabaseHelper(context); 376 mBackupManager = new BackupManager(context); 377 // we added "picasa web album" into default bookmarks for version 19. 378 // To avoid erasing the bookmark table, we added it explicitly for 379 // version 18 and 19 as in the other cases, we will erase the table. 380 if (DATABASE_VERSION == 18 || DATABASE_VERSION == 19) { 381 SharedPreferences p = PreferenceManager 382 .getDefaultSharedPreferences(context); 383 boolean fix = p.getBoolean("fix_picasa", true); 384 if (fix) { 385 fixPicasaBookmark(); 386 Editor ed = p.edit(); 387 ed.putBoolean("fix_picasa", false); 388 ed.commit(); 389 } 390 } 391 mSearchManager = (SearchManager) context.getSystemService(Context.SEARCH_SERVICE); 392 mShowWebSuggestionsSettingChangeObserver 393 = new ShowWebSuggestionsSettingChangeObserver(); 394 context.getContentResolver().registerContentObserver( 395 Settings.System.getUriFor( 396 Settings.System.SHOW_WEB_SUGGESTIONS), 397 true, mShowWebSuggestionsSettingChangeObserver); 398 updateShowWebSuggestions(); 399 return true; 400 } 401 402 /** 403 * This Observer will ensure that if the user changes the system 404 * setting of whether to display web suggestions, we will 405 * change accordingly. 406 */ 407 /* package */ class ShowWebSuggestionsSettingChangeObserver 408 extends ContentObserver { 409 public ShowWebSuggestionsSettingChangeObserver() { 410 super(new Handler()); 411 } 412 413 @Override 414 public void onChange(boolean selfChange) { 415 updateShowWebSuggestions(); 416 } 417 } 418 419 private ShowWebSuggestionsSettingChangeObserver 420 mShowWebSuggestionsSettingChangeObserver; 421 422 // If non-null, then the system is set to show web suggestions, 423 // and this is the SearchableInfo to use to get them. 424 private SearchableInfo mSearchableInfo; 425 426 /** 427 * Check the system settings to see whether web suggestions are 428 * allowed. If so, store the SearchableInfo to grab suggestions 429 * while the user is typing. 430 */ 431 private void updateShowWebSuggestions() { 432 mSearchableInfo = null; 433 Context context = getContext(); 434 if (Settings.System.getInt(context.getContentResolver(), 435 Settings.System.SHOW_WEB_SUGGESTIONS, 436 1 /* default on */) == 1) { 437 ComponentName webSearchComponent = mSearchManager.getWebSearchActivity(); 438 if (webSearchComponent != null) { 439 mSearchableInfo = mSearchManager.getSearchableInfo(webSearchComponent); 440 } 441 } 442 } 443 444 private void fixPicasaBookmark() { 445 SQLiteDatabase db = mOpenHelper.getWritableDatabase(); 446 Cursor cursor = db.rawQuery("SELECT _id FROM bookmarks WHERE " + 447 "bookmark = 1 AND url = ?", new String[] { PICASA_URL }); 448 try { 449 if (!cursor.moveToFirst()) { 450 // set "created" so that it will be on the top of the list 451 db.execSQL("INSERT INTO bookmarks (title, url, visits, " + 452 "date, created, bookmark)" + " VALUES('" + 453 getContext().getString(R.string.picasa) + "', '" 454 + PICASA_URL + "', 0, 0, " + new Date().getTime() 455 + ", 1);"); 456 } 457 } finally { 458 if (cursor != null) { 459 cursor.close(); 460 } 461 } 462 } 463 464 /* 465 * Subclass AbstractCursor so we can combine multiple Cursors and add 466 * "Search the web". 467 * Here are the rules. 468 * 1. We only have MAX_SUGGESTION_LONG_ENTRIES in the list plus 469 * "Search the web"; 470 * 2. If bookmark/history entries has a match, "Search the web" shows up at 471 * the second place. Otherwise, "Search the web" shows up at the first 472 * place. 473 */ 474 private class MySuggestionCursor extends AbstractCursor { 475 private Cursor mHistoryCursor; 476 private Cursor mSuggestCursor; 477 private int mHistoryCount; 478 private int mSuggestionCount; 479 private boolean mIncludeWebSearch; 480 private String mString; 481 private int mSuggestText1Id; 482 private int mSuggestText2Id; 483 private int mSuggestText2UrlId; 484 private int mSuggestQueryId; 485 private int mSuggestIntentExtraDataId; 486 487 public MySuggestionCursor(Cursor hc, Cursor sc, String string) { 488 mHistoryCursor = hc; 489 mSuggestCursor = sc; 490 mHistoryCount = hc.getCount(); 491 mSuggestionCount = sc != null ? sc.getCount() : 0; 492 if (mSuggestionCount > (MAX_SUGGESTION_LONG_ENTRIES - mHistoryCount)) { 493 mSuggestionCount = MAX_SUGGESTION_LONG_ENTRIES - mHistoryCount; 494 } 495 mString = string; 496 mIncludeWebSearch = string.length() > 0; 497 498 // Some web suggest providers only give suggestions and have no description string for 499 // items. The order of the result columns may be different as well. So retrieve the 500 // column indices for the fields we need now and check before using below. 501 if (mSuggestCursor == null) { 502 mSuggestText1Id = -1; 503 mSuggestText2Id = -1; 504 mSuggestText2UrlId = -1; 505 mSuggestQueryId = -1; 506 mSuggestIntentExtraDataId = -1; 507 } else { 508 mSuggestText1Id = mSuggestCursor.getColumnIndex( 509 SearchManager.SUGGEST_COLUMN_TEXT_1); 510 mSuggestText2Id = mSuggestCursor.getColumnIndex( 511 SearchManager.SUGGEST_COLUMN_TEXT_2); 512 mSuggestText2UrlId = mSuggestCursor.getColumnIndex( 513 SearchManager.SUGGEST_COLUMN_TEXT_2_URL); 514 mSuggestQueryId = mSuggestCursor.getColumnIndex( 515 SearchManager.SUGGEST_COLUMN_QUERY); 516 mSuggestIntentExtraDataId = mSuggestCursor.getColumnIndex( 517 SearchManager.SUGGEST_COLUMN_INTENT_EXTRA_DATA); 518 } 519 } 520 521 @Override 522 public boolean onMove(int oldPosition, int newPosition) { 523 if (mHistoryCursor == null) { 524 return false; 525 } 526 if (mIncludeWebSearch) { 527 if (mHistoryCount == 0 && newPosition == 0) { 528 return true; 529 } else if (mHistoryCount > 0) { 530 if (newPosition == 0) { 531 mHistoryCursor.moveToPosition(0); 532 return true; 533 } else if (newPosition == 1) { 534 return true; 535 } 536 } 537 newPosition--; 538 } 539 if (mHistoryCount > newPosition) { 540 mHistoryCursor.moveToPosition(newPosition); 541 } else { 542 mSuggestCursor.moveToPosition(newPosition - mHistoryCount); 543 } 544 return true; 545 } 546 547 @Override 548 public int getCount() { 549 if (mIncludeWebSearch) { 550 return mHistoryCount + mSuggestionCount + 1; 551 } else { 552 return mHistoryCount + mSuggestionCount; 553 } 554 } 555 556 @Override 557 public String[] getColumnNames() { 558 return COLUMNS; 559 } 560 561 @Override 562 public String getString(int columnIndex) { 563 if ((mPos != -1 && mHistoryCursor != null)) { 564 int type = -1; // 0: web search; 1: history; 2: suggestion 565 if (mIncludeWebSearch) { 566 if (mHistoryCount == 0 && mPos == 0) { 567 type = 0; 568 } else if (mHistoryCount > 0) { 569 if (mPos == 0) { 570 type = 1; 571 } else if (mPos == 1) { 572 type = 0; 573 } 574 } 575 if (type == -1) type = (mPos - 1) < mHistoryCount ? 1 : 2; 576 } else { 577 type = mPos < mHistoryCount ? 1 : 2; 578 } 579 580 switch(columnIndex) { 581 case SUGGEST_COLUMN_INTENT_ACTION_ID: 582 if (type == 1) { 583 return Intent.ACTION_VIEW; 584 } else { 585 return Intent.ACTION_SEARCH; 586 } 587 588 case SUGGEST_COLUMN_INTENT_DATA_ID: 589 if (type == 1) { 590 return mHistoryCursor.getString(1); 591 } else { 592 return null; 593 } 594 595 case SUGGEST_COLUMN_TEXT_1_ID: 596 if (type == 0) { 597 return mString; 598 } else if (type == 1) { 599 return getHistoryTitle(); 600 } else { 601 if (mSuggestText1Id == -1) return null; 602 return mSuggestCursor.getString(mSuggestText1Id); 603 } 604 605 case SUGGEST_COLUMN_TEXT_2_ID: 606 if (type == 0) { 607 return getContext().getString(R.string.search_the_web); 608 } else if (type == 1) { 609 return null; // Use TEXT_2_URL instead 610 } else { 611 if (mSuggestText2Id == -1) return null; 612 return mSuggestCursor.getString(mSuggestText2Id); 613 } 614 615 case SUGGEST_COLUMN_TEXT_2_URL_ID: 616 if (type == 0) { 617 return null; 618 } else if (type == 1) { 619 return getHistoryUrl(); 620 } else { 621 if (mSuggestText2UrlId == -1) return null; 622 return mSuggestCursor.getString(mSuggestText2UrlId); 623 } 624 625 case SUGGEST_COLUMN_ICON_1_ID: 626 if (type == 1) { 627 if (mHistoryCursor.getInt(3) == 1) { 628 return Integer.valueOf( 629 R.drawable.ic_search_category_bookmark) 630 .toString(); 631 } else { 632 return Integer.valueOf( 633 R.drawable.ic_search_category_history) 634 .toString(); 635 } 636 } else { 637 return Integer.valueOf( 638 R.drawable.ic_search_category_suggest) 639 .toString(); 640 } 641 642 case SUGGEST_COLUMN_ICON_2_ID: 643 return "0"; 644 645 case SUGGEST_COLUMN_QUERY_ID: 646 if (type == 0) { 647 return mString; 648 } else if (type == 1) { 649 // Return the url in the intent query column. This is ignored 650 // within the browser because our searchable is set to 651 // android:searchMode="queryRewriteFromData", but it is used by 652 // global search for query rewriting. 653 return mHistoryCursor.getString(1); 654 } else { 655 if (mSuggestQueryId == -1) return null; 656 return mSuggestCursor.getString(mSuggestQueryId); 657 } 658 659 case SUGGEST_COLUMN_INTENT_EXTRA_DATA: 660 if (type == 0) { 661 return null; 662 } else if (type == 1) { 663 return null; 664 } else { 665 if (mSuggestIntentExtraDataId == -1) return null; 666 return mSuggestCursor.getString(mSuggestIntentExtraDataId); 667 } 668 } 669 } 670 return null; 671 } 672 673 @Override 674 public double getDouble(int column) { 675 throw new UnsupportedOperationException(); 676 } 677 678 @Override 679 public float getFloat(int column) { 680 throw new UnsupportedOperationException(); 681 } 682 683 @Override 684 public int getInt(int column) { 685 throw new UnsupportedOperationException(); 686 } 687 688 @Override 689 public long getLong(int column) { 690 if ((mPos != -1) && column == 0) { 691 return mPos; // use row# as the _Id 692 } 693 throw new UnsupportedOperationException(); 694 } 695 696 @Override 697 public short getShort(int column) { 698 throw new UnsupportedOperationException(); 699 } 700 701 @Override 702 public boolean isNull(int column) { 703 throw new UnsupportedOperationException(); 704 } 705 706 // TODO Temporary change, finalize after jq's changes go in 707 public void deactivate() { 708 if (mHistoryCursor != null) { 709 mHistoryCursor.deactivate(); 710 } 711 if (mSuggestCursor != null) { 712 mSuggestCursor.deactivate(); 713 } 714 super.deactivate(); 715 } 716 717 public boolean requery() { 718 return (mHistoryCursor != null ? mHistoryCursor.requery() : false) | 719 (mSuggestCursor != null ? mSuggestCursor.requery() : false); 720 } 721 722 // TODO Temporary change, finalize after jq's changes go in 723 public void close() { 724 super.close(); 725 if (mHistoryCursor != null) { 726 mHistoryCursor.close(); 727 mHistoryCursor = null; 728 } 729 if (mSuggestCursor != null) { 730 mSuggestCursor.close(); 731 mSuggestCursor = null; 732 } 733 } 734 735 /** 736 * Provides the title (text line 1) for a browser suggestion, which should be the 737 * webpage title. If the webpage title is empty, returns the stripped url instead. 738 * 739 * @return the title string to use 740 */ 741 private String getHistoryTitle() { 742 String title = mHistoryCursor.getString(2 /* webpage title */); 743 if (TextUtils.isEmpty(title) || TextUtils.getTrimmedLength(title) == 0) { 744 title = stripUrl(mHistoryCursor.getString(1 /* url */)); 745 } 746 return title; 747 } 748 749 /** 750 * Provides the subtitle (text line 2) for a browser suggestion, which should be the 751 * webpage url. If the webpage title is empty, then the url should go in the title 752 * instead, and the subtitle should be empty, so this would return null. 753 * 754 * @return the subtitle string to use, or null if none 755 */ 756 private String getHistoryUrl() { 757 String title = mHistoryCursor.getString(2 /* webpage title */); 758 if (TextUtils.isEmpty(title) || TextUtils.getTrimmedLength(title) == 0) { 759 return null; 760 } else { 761 return stripUrl(mHistoryCursor.getString(1 /* url */)); 762 } 763 } 764 765 } 766 767 private static class ResultsCursor extends AbstractCursor { 768 // Array indices for RESULTS_COLUMNS 769 private static final int RESULT_ACTION_ID = 1; 770 private static final int RESULT_DATA_ID = 2; 771 private static final int RESULT_TEXT_ID = 3; 772 private static final int RESULT_ICON_ID = 4; 773 private static final int RESULT_EXTRA_ID = 5; 774 775 private static final String[] RESULTS_COLUMNS = new String[] { 776 "_id", 777 SearchManager.SUGGEST_COLUMN_INTENT_ACTION, 778 SearchManager.SUGGEST_COLUMN_INTENT_DATA, 779 SearchManager.SUGGEST_COLUMN_TEXT_1, 780 SearchManager.SUGGEST_COLUMN_ICON_1, 781 SearchManager.SUGGEST_COLUMN_INTENT_EXTRA_DATA 782 }; 783 private final ArrayList<String> mResults; 784 public ResultsCursor(ArrayList<String> results) { 785 mResults = results; 786 } 787 public int getCount() { return mResults.size(); } 788 789 public String[] getColumnNames() { 790 return RESULTS_COLUMNS; 791 } 792 793 public String getString(int column) { 794 switch (column) { 795 case RESULT_ACTION_ID: 796 return RecognizerResultsIntent.ACTION_VOICE_SEARCH_RESULTS; 797 case RESULT_TEXT_ID: 798 // The data is used when the phone is in landscape mode. We 799 // still want to show the result string. 800 case RESULT_DATA_ID: 801 return mResults.get(mPos); 802 case RESULT_EXTRA_ID: 803 // The Intent's extra data will store the index into 804 // mResults so the BrowserActivity will know which result to 805 // use. 806 return Integer.toString(mPos); 807 case RESULT_ICON_ID: 808 return Integer.valueOf(R.drawable.magnifying_glass) 809 .toString(); 810 default: 811 return null; 812 } 813 } 814 public short getShort(int column) { 815 throw new UnsupportedOperationException(); 816 } 817 public int getInt(int column) { 818 throw new UnsupportedOperationException(); 819 } 820 public long getLong(int column) { 821 if ((mPos != -1) && column == 0) { 822 return mPos; // use row# as the _id 823 } 824 throw new UnsupportedOperationException(); 825 } 826 public float getFloat(int column) { 827 throw new UnsupportedOperationException(); 828 } 829 public double getDouble(int column) { 830 throw new UnsupportedOperationException(); 831 } 832 public boolean isNull(int column) { 833 throw new UnsupportedOperationException(); 834 } 835 } 836 837 private ResultsCursor mResultsCursor; 838 839 /** 840 * Provide a set of results to be returned to query, intended to be used 841 * by the SearchDialog when the BrowserActivity is in voice search mode. 842 * @param results Strings to display in the dropdown from the SearchDialog 843 */ 844 /* package */ void setQueryResults(ArrayList<String> results) { 845 if (results == null) { 846 mResultsCursor = null; 847 } else { 848 mResultsCursor = new ResultsCursor(results); 849 } 850 } 851 852 @Override 853 public Cursor query(Uri url, String[] projectionIn, String selection, 854 String[] selectionArgs, String sortOrder) 855 throws IllegalStateException { 856 int match = URI_MATCHER.match(url); 857 if (match == -1) { 858 throw new IllegalArgumentException("Unknown URL"); 859 } 860 if (match == URI_MATCH_SUGGEST && mResultsCursor != null) { 861 Cursor results = mResultsCursor; 862 mResultsCursor = null; 863 return results; 864 } 865 SQLiteDatabase db = mOpenHelper.getReadableDatabase(); 866 867 if (match == URI_MATCH_SUGGEST || match == URI_MATCH_BOOKMARKS_SUGGEST) { 868 String suggestSelection; 869 String [] myArgs; 870 if (selectionArgs[0] == null || selectionArgs[0].equals("")) { 871 suggestSelection = null; 872 myArgs = null; 873 } else { 874 String like = selectionArgs[0] + "%"; 875 if (selectionArgs[0].startsWith("http") 876 || selectionArgs[0].startsWith("file")) { 877 myArgs = new String[1]; 878 myArgs[0] = like; 879 suggestSelection = selection; 880 } else { 881 SUGGEST_ARGS[0] = "http://" + like; 882 SUGGEST_ARGS[1] = "http://www." + like; 883 SUGGEST_ARGS[2] = "https://" + like; 884 SUGGEST_ARGS[3] = "https://www." + like; 885 // To match against titles. 886 SUGGEST_ARGS[4] = like; 887 myArgs = SUGGEST_ARGS; 888 suggestSelection = SUGGEST_SELECTION; 889 } 890 } 891 892 Cursor c = db.query(TABLE_NAMES[URI_MATCH_BOOKMARKS], 893 SUGGEST_PROJECTION, suggestSelection, myArgs, null, null, 894 ORDER_BY, MAX_SUGGESTION_LONG_ENTRIES_STRING); 895 896 if (match == URI_MATCH_BOOKMARKS_SUGGEST 897 || Patterns.WEB_URL.matcher(selectionArgs[0]).matches()) { 898 return new MySuggestionCursor(c, null, ""); 899 } else { 900 // get Google suggest if there is still space in the list 901 if (myArgs != null && myArgs.length > 1 902 && mSearchableInfo != null 903 && c.getCount() < (MAX_SUGGESTION_SHORT_ENTRIES - 1)) { 904 Cursor sc = mSearchManager.getSuggestions(mSearchableInfo, selectionArgs[0]); 905 return new MySuggestionCursor(c, sc, selectionArgs[0]); 906 } 907 return new MySuggestionCursor(c, null, selectionArgs[0]); 908 } 909 } 910 911 String[] projection = null; 912 if (projectionIn != null && projectionIn.length > 0) { 913 projection = new String[projectionIn.length + 1]; 914 System.arraycopy(projectionIn, 0, projection, 0, projectionIn.length); 915 projection[projectionIn.length] = "_id AS _id"; 916 } 917 918 StringBuilder whereClause = new StringBuilder(256); 919 if (match == URI_MATCH_BOOKMARKS_ID || match == URI_MATCH_SEARCHES_ID) { 920 whereClause.append("(_id = ").append(url.getPathSegments().get(1)) 921 .append(")"); 922 } 923 924 // Tack on the user's selection, if present 925 if (selection != null && selection.length() > 0) { 926 if (whereClause.length() > 0) { 927 whereClause.append(" AND "); 928 } 929 930 whereClause.append('('); 931 whereClause.append(selection); 932 whereClause.append(')'); 933 } 934 Cursor c = db.query(TABLE_NAMES[match % 10], projection, 935 whereClause.toString(), selectionArgs, null, null, sortOrder, 936 null); 937 c.setNotificationUri(getContext().getContentResolver(), url); 938 return c; 939 } 940 941 @Override 942 public String getType(Uri url) { 943 int match = URI_MATCHER.match(url); 944 switch (match) { 945 case URI_MATCH_BOOKMARKS: 946 return "vnd.android.cursor.dir/bookmark"; 947 948 case URI_MATCH_BOOKMARKS_ID: 949 return "vnd.android.cursor.item/bookmark"; 950 951 case URI_MATCH_SEARCHES: 952 return "vnd.android.cursor.dir/searches"; 953 954 case URI_MATCH_SEARCHES_ID: 955 return "vnd.android.cursor.item/searches"; 956 957 case URI_MATCH_SUGGEST: 958 return SearchManager.SUGGEST_MIME_TYPE; 959 960 default: 961 throw new IllegalArgumentException("Unknown URL"); 962 } 963 } 964 965 @Override 966 public Uri insert(Uri url, ContentValues initialValues) { 967 boolean isBookmarkTable = false; 968 SQLiteDatabase db = mOpenHelper.getWritableDatabase(); 969 970 int match = URI_MATCHER.match(url); 971 Uri uri = null; 972 switch (match) { 973 case URI_MATCH_BOOKMARKS: { 974 // Insert into the bookmarks table 975 long rowID = db.insert(TABLE_NAMES[URI_MATCH_BOOKMARKS], "url", 976 initialValues); 977 if (rowID > 0) { 978 uri = ContentUris.withAppendedId(Browser.BOOKMARKS_URI, 979 rowID); 980 } 981 isBookmarkTable = true; 982 break; 983 } 984 985 case URI_MATCH_SEARCHES: { 986 // Insert into the searches table 987 long rowID = db.insert(TABLE_NAMES[URI_MATCH_SEARCHES], "url", 988 initialValues); 989 if (rowID > 0) { 990 uri = ContentUris.withAppendedId(Browser.SEARCHES_URI, 991 rowID); 992 } 993 break; 994 } 995 996 default: 997 throw new IllegalArgumentException("Unknown URL"); 998 } 999 1000 if (uri == null) { 1001 throw new IllegalArgumentException("Unknown URL"); 1002 } 1003 getContext().getContentResolver().notifyChange(uri, null); 1004 1005 // Back up the new bookmark set if we just inserted one. 1006 // A row created when bookmarks are added from scratch will have 1007 // bookmark=1 in the initial value set. 1008 if (isBookmarkTable 1009 && initialValues.containsKey(BookmarkColumns.BOOKMARK) 1010 && initialValues.getAsInteger(BookmarkColumns.BOOKMARK) != 0) { 1011 mBackupManager.dataChanged(); 1012 } 1013 return uri; 1014 } 1015 1016 @Override 1017 public int delete(Uri url, String where, String[] whereArgs) { 1018 SQLiteDatabase db = mOpenHelper.getWritableDatabase(); 1019 1020 int match = URI_MATCHER.match(url); 1021 if (match == -1 || match == URI_MATCH_SUGGEST) { 1022 throw new IllegalArgumentException("Unknown URL"); 1023 } 1024 1025 // need to know whether it's the bookmarks table for a couple of reasons 1026 boolean isBookmarkTable = (match == URI_MATCH_BOOKMARKS_ID); 1027 String id = null; 1028 1029 if (isBookmarkTable || match == URI_MATCH_SEARCHES_ID) { 1030 StringBuilder sb = new StringBuilder(); 1031 if (where != null && where.length() > 0) { 1032 sb.append("( "); 1033 sb.append(where); 1034 sb.append(" ) AND "); 1035 } 1036 id = url.getPathSegments().get(1); 1037 sb.append("_id = "); 1038 sb.append(id); 1039 where = sb.toString(); 1040 } 1041 1042 ContentResolver cr = getContext().getContentResolver(); 1043 1044 // we'lll need to back up the bookmark set if we are about to delete one 1045 if (isBookmarkTable) { 1046 Cursor cursor = cr.query(Browser.BOOKMARKS_URI, 1047 new String[] { BookmarkColumns.BOOKMARK }, 1048 "_id = " + id, null, null); 1049 if (cursor.moveToNext()) { 1050 if (cursor.getInt(0) != 0) { 1051 // yep, this record is a bookmark 1052 mBackupManager.dataChanged(); 1053 } 1054 } 1055 cursor.close(); 1056 } 1057 1058 int count = db.delete(TABLE_NAMES[match % 10], where, whereArgs); 1059 cr.notifyChange(url, null); 1060 return count; 1061 } 1062 1063 @Override 1064 public int update(Uri url, ContentValues values, String where, 1065 String[] whereArgs) { 1066 SQLiteDatabase db = mOpenHelper.getWritableDatabase(); 1067 1068 int match = URI_MATCHER.match(url); 1069 if (match == -1 || match == URI_MATCH_SUGGEST) { 1070 throw new IllegalArgumentException("Unknown URL"); 1071 } 1072 1073 if (match == URI_MATCH_BOOKMARKS_ID || match == URI_MATCH_SEARCHES_ID) { 1074 StringBuilder sb = new StringBuilder(); 1075 if (where != null && where.length() > 0) { 1076 sb.append("( "); 1077 sb.append(where); 1078 sb.append(" ) AND "); 1079 } 1080 String id = url.getPathSegments().get(1); 1081 sb.append("_id = "); 1082 sb.append(id); 1083 where = sb.toString(); 1084 } 1085 1086 ContentResolver cr = getContext().getContentResolver(); 1087 1088 // Not all bookmark-table updates should be backed up. Look to see 1089 // whether we changed the title, url, or "is a bookmark" state, and 1090 // request a backup if so. 1091 if (match == URI_MATCH_BOOKMARKS_ID || match == URI_MATCH_BOOKMARKS) { 1092 boolean changingBookmarks = false; 1093 // Alterations to the bookmark field inherently change the bookmark 1094 // set, so we don't need to query the record; we know a priori that 1095 // we will need to back up this change. 1096 if (values.containsKey(BookmarkColumns.BOOKMARK)) { 1097 changingBookmarks = true; 1098 } else if ((values.containsKey(BookmarkColumns.TITLE) 1099 || values.containsKey(BookmarkColumns.URL)) 1100 && values.containsKey(BookmarkColumns._ID)) { 1101 // If a title or URL has been changed, check to see if it is to 1102 // a bookmark. The ID should have been included in the update, 1103 // so use it. 1104 Cursor cursor = cr.query(Browser.BOOKMARKS_URI, 1105 new String[] { BookmarkColumns.BOOKMARK }, 1106 BookmarkColumns._ID + " = " 1107 + values.getAsString(BookmarkColumns._ID), null, null); 1108 if (cursor.moveToNext()) { 1109 changingBookmarks = (cursor.getInt(0) != 0); 1110 } 1111 cursor.close(); 1112 } 1113 1114 // if this *is* a bookmark row we're altering, we need to back it up. 1115 if (changingBookmarks) { 1116 mBackupManager.dataChanged(); 1117 } 1118 } 1119 1120 int ret = db.update(TABLE_NAMES[match % 10], values, where, whereArgs); 1121 cr.notifyChange(url, null); 1122 return ret; 1123 } 1124 1125 /** 1126 * Strips the provided url of preceding "http://" and any trailing "/". Does not 1127 * strip "https://". If the provided string cannot be stripped, the original string 1128 * is returned. 1129 * 1130 * TODO: Put this in TextUtils to be used by other packages doing something similar. 1131 * 1132 * @param url a url to strip, like "http://www.google.com/" 1133 * @return a stripped url like "www.google.com", or the original string if it could 1134 * not be stripped 1135 */ 1136 private static String stripUrl(String url) { 1137 if (url == null) return null; 1138 Matcher m = STRIP_URL_PATTERN.matcher(url); 1139 if (m.matches() && m.groupCount() == 3) { 1140 return m.group(2); 1141 } else { 1142 return url; 1143 } 1144 } 1145 1146} 1147