blob: 7aa5bb255399c79de0ef68468b1ae1468d52f2cb [file] [log] [blame]
The Android Open Source Projectba6d7b82008-10-21 07:00:00 -07001/*
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 android.app.SearchManager;
20import android.content.ContentProvider;
21import android.content.ContentResolver;
22import android.content.ContentUris;
23import android.content.ContentValues;
24import android.content.Context;
25import android.content.UriMatcher;
26import android.database.AbstractCursor;
27import android.database.ContentObserver;
28import android.database.Cursor;
29import android.database.DataSetObserver;
30import android.database.sqlite.SQLiteOpenHelper;
31import android.database.sqlite.SQLiteDatabase;
32import android.net.Uri;
The Android Open Source Projected217d92008-12-17 18:05:52 -080033import android.os.SystemProperties;
The Android Open Source Projectba6d7b82008-10-21 07:00:00 -070034import android.provider.Browser;
35import android.util.Log;
36import android.text.util.Regex;
37
38public class BrowserProvider extends ContentProvider {
39
40 private SQLiteOpenHelper mOpenHelper;
41 private static final String sDatabaseName = "browser.db";
42 private static final String TAG = "BrowserProvider";
43 private static final String ORDER_BY = "date DESC";
44
45 private static final String[] TABLE_NAMES = new String[] {
46 "bookmarks", "searches"
47 };
48 private static final String[] SUGGEST_PROJECTION = new String [] {
49 "0 AS " + SearchManager.SUGGEST_COLUMN_FORMAT,
50 "url AS " + SearchManager.SUGGEST_COLUMN_INTENT_DATA,
51 "url AS " + SearchManager.SUGGEST_COLUMN_TEXT_1,
52 "title AS " + SearchManager.SUGGEST_COLUMN_TEXT_2,
53 "_id"
54 };
55 private static final String SUGGEST_SELECTION =
56 "url LIKE ? OR url LIKE ? OR url LIKE ? OR url LIKE ?";
57 private String[] SUGGEST_ARGS = new String[4];
58
59 // make sure that these match the index of TABLE_NAMES
60 private static final int URI_MATCH_BOOKMARKS = 0;
61 private static final int URI_MATCH_SEARCHES = 1;
62 // (id % 10) should match the table name index
63 private static final int URI_MATCH_BOOKMARKS_ID = 10;
64 private static final int URI_MATCH_SEARCHES_ID = 11;
65 //
66 private static final int URI_MATCH_SUGGEST = 20;
67
68 private static final UriMatcher URI_MATCHER;
69
70 static {
71 URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
72 URI_MATCHER.addURI("browser", TABLE_NAMES[URI_MATCH_BOOKMARKS],
73 URI_MATCH_BOOKMARKS);
74 URI_MATCHER.addURI("browser", TABLE_NAMES[URI_MATCH_BOOKMARKS] + "/#",
75 URI_MATCH_BOOKMARKS_ID);
76 URI_MATCHER.addURI("browser", TABLE_NAMES[URI_MATCH_SEARCHES],
77 URI_MATCH_SEARCHES);
78 URI_MATCHER.addURI("browser", TABLE_NAMES[URI_MATCH_SEARCHES] + "/#",
79 URI_MATCH_SEARCHES_ID);
80 URI_MATCHER.addURI("browser", SearchManager.SUGGEST_URI_PATH_QUERY,
81 URI_MATCH_SUGGEST);
82 }
83
84 // 1 -> 2 add cache table
85 // 2 -> 3 update history table
86 // 3 -> 4 add passwords table
87 // 4 -> 5 add settings table
88 // 5 -> 6 ?
89 // 6 -> 7 ?
90 // 7 -> 8 drop proxy table
91 // 8 -> 9 drop settings table
92 // 9 -> 10 add form_urls and form_data
93 // 10 -> 11 add searches table
94 // 11 -> 12 modify cache table
95 // 12 -> 13 modify cache table
96 // 13 -> 14 correspond with Google Bookmarks schema
97 // 14 -> 15 move couple of tables to either browser private database or webview database
98 // 15 -> 17 Set it up for the SearchManager
99 // 17 -> 18 Added favicon in bookmarks table for Home shortcuts
100 // 18 -> 19 Remove labels table
101 private static final int DATABASE_VERSION = 19;
102
103 public BrowserProvider() {
104 }
The Android Open Source Projected217d92008-12-17 18:05:52 -0800105
106
107 private static CharSequence replaceSystemPropertyInString(CharSequence srcString) {
108 StringBuffer sb = new StringBuffer();
109 int lastCharLoc = 0;
110 for (int i = 0; i < srcString.length(); ++i) {
111 char c = srcString.charAt(i);
112 if (c == '{') {
113 sb.append(srcString.subSequence(lastCharLoc, i));
114 lastCharLoc = i;
115 inner:
116 for (int j = i; j < srcString.length(); ++j) {
117 char k = srcString.charAt(j);
118 if (k == '}') {
119 String propertyKeyValue = srcString.subSequence(i + 1, j).toString();
120 // See if the propertyKeyValue specifies a default value
121 int defaultOffset = propertyKeyValue.indexOf(':');
122 if (defaultOffset == -1) {
123 sb.append(SystemProperties.get(propertyKeyValue));
124 } else {
125 String propertyKey = propertyKeyValue.substring(0, defaultOffset);
126 String defaultValue =
127 propertyKeyValue.substring(defaultOffset + 1,
128 propertyKeyValue.length());
129 sb.append(SystemProperties.get(propertyKey, defaultValue));
130 }
131 lastCharLoc = j + 1;
132 i = j;
133 break inner;
134 }
135 }
136 }
137 }
138 if (srcString.length() - lastCharLoc > 0) {
139 // Put on the tail, if there is one
140 sb.append(srcString.subSequence(lastCharLoc, srcString.length()));
141 }
142 return sb;
143 }
The Android Open Source Projectba6d7b82008-10-21 07:00:00 -0700144
145 private static class DatabaseHelper extends SQLiteOpenHelper {
146 private Context mContext;
147
148 public DatabaseHelper(Context context) {
149 super(context, sDatabaseName, null, DATABASE_VERSION);
150 mContext = context;
151 }
152
153 @Override
154 public void onCreate(SQLiteDatabase db) {
155 db.execSQL("CREATE TABLE bookmarks (" +
156 "_id INTEGER PRIMARY KEY," +
157 "title TEXT," +
158 "url TEXT," +
159 "visits INTEGER," +
160 "date LONG," +
161 "created LONG," +
162 "description TEXT," +
163 "bookmark INTEGER," +
164 "favicon BLOB DEFAULT NULL" +
165 ");");
166
167 final CharSequence[] bookmarks = mContext.getResources()
168 .getTextArray(R.array.bookmarks);
169 int size = bookmarks.length;
170 try {
171 for (int i = 0; i < size; i = i + 2) {
The Android Open Source Projected217d92008-12-17 18:05:52 -0800172 CharSequence bookmarkDestination = replaceSystemPropertyInString(bookmarks[i + 1]);
The Android Open Source Projectba6d7b82008-10-21 07:00:00 -0700173 db.execSQL("INSERT INTO bookmarks (title, url, visits, " +
174 "date, created, bookmark)" + " VALUES('" +
The Android Open Source Projected217d92008-12-17 18:05:52 -0800175 bookmarks[i] + "', '" + bookmarkDestination +
The Android Open Source Projectba6d7b82008-10-21 07:00:00 -0700176 "', 0, 0, 0, 1);");
177 }
178 } catch (ArrayIndexOutOfBoundsException e) {
179 }
180
181 db.execSQL("CREATE TABLE searches (" +
182 "_id INTEGER PRIMARY KEY," +
183 "search TEXT," +
184 "date LONG" +
185 ");");
186 }
187
188 @Override
189 public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
190 Log.w(TAG, "Upgrading database from version " + oldVersion + " to "
191 + newVersion + ", which will destroy all old data");
192 if (oldVersion == 18) {
193 db.execSQL("DROP TABLE IF EXISTS labels");
194 } else {
195 db.execSQL("DROP TABLE IF EXISTS bookmarks");
196 db.execSQL("DROP TABLE IF EXISTS searches");
197 onCreate(db);
198 }
199 }
200 }
201
202 @Override
203 public boolean onCreate() {
204 mOpenHelper = new DatabaseHelper(getContext());
205 return true;
206 }
207
208 /*
209 * Subclass AbstractCursor so we can add "Google Search"
210 */
211 private class MySuggestionCursor extends AbstractCursor {
212 private Cursor mCursor;
213 private boolean mBeyondCursor;
214 private String mString;
215 private Uri mNotifyUri;
216 private ContentResolver mContentResolver;
217 private AbstractCursor.SelfContentObserver mObserver;
218 private final Object mObserverLock = new Object();
219
220 public MySuggestionCursor(Cursor c, String string) {
221 mCursor = c;
222 if (Regex.WEB_URL_PATTERN.matcher(string).matches()) {
223 mString = "";
224 } else {
225 mString = string;
226 }
227 mBeyondCursor = false;
228 }
229
230 public boolean onMove(int oldPosition, int newPosition) {
231 if (mCursor.getCount() == newPosition) {
232 mBeyondCursor = true;
233 } else {
234 mCursor.moveToPosition(newPosition);
235 mBeyondCursor = false;
236 }
237 return true;
238 }
239
240 public int getCount() {
241 if (mString.length() > 0) {
242 return mCursor.getCount() + 1;
243 } else {
244 return mCursor.getCount();
245 }
246 }
247
248 public boolean deleteRow() {
249 return !mBeyondCursor && mCursor.deleteRow();
250 }
251
252 public String[] getColumnNames() {
253 return mCursor.getColumnNames();
254 }
255
256 public int getColumnCount() {
257 return mCursor.getColumnCount();
258 }
259
260 public String getString(int columnIndex) {
261 if (!mBeyondCursor) {
262 return mCursor.getString(columnIndex);
263 }
264 switch (columnIndex) {
265 case 2: // SearchManager.SUGGEST_COLUMN_TEXT_1
266 return "Google Search for \"" + mString + "\"";
267 case 1: // SearchManager.SUGGEST_COLUMN_INTENT_DATA
268 return BrowserActivity.composeSearchUrl(mString);
269 case 3: // SearchManager.SUGGEST_COLUMN_TEXT_2
270 default:
271 return "";
272 }
273 }
274
275 public short getShort(int columnIndex) {
276 if (!mBeyondCursor) {
277 return mCursor.getShort(columnIndex);
278 }
279 if (0 == columnIndex) {
280 return 0;
281 }
282 return -1;
283 }
284
285 public int getInt(int columnIndex) {
286 if (!mBeyondCursor) {
287 return mCursor.getInt(columnIndex);
288 }
289 if (0 == columnIndex) {
290 return 0;
291 }
292 return -1;
293 }
294
295 public long getLong(int columnIndex) {
296 if (!mBeyondCursor) {
297 return mCursor.getLong(columnIndex);
298 }
299 if (0 == columnIndex) {
300 return 0;
301 }
302 return -1;
303 }
304
305 public float getFloat(int columnIndex) {
306 if (!mBeyondCursor) {
307 return mCursor.getFloat(columnIndex);
308 }
309 if (0 == columnIndex) {
310 return 0f;
311 }
312 return -1f;
313 }
314
315 public double getDouble(int columnIndex) {
316 if (!mBeyondCursor) {
317 return mCursor.getDouble(columnIndex);
318 }
319 if (0 == columnIndex) {
320 return 0.0;
321 }
322 return -1.0;
323 }
324
325 public boolean isNull(int columnIndex) {
326 return mCursor.isNull(columnIndex);
327 }
328
329 public boolean supportsUpdates() {
330 return false;
331 }
332
333 public boolean hasUpdates() {
334 return false;
335 }
336
337 public boolean updateString(int columnIndex, String value) {
338 return false;
339 }
340
341 public boolean updateShort(int columnIndex, short value) {
342 return false;
343 }
344
345 public boolean updateInt(int columnIndex, int value) {
346 return false;
347 }
348
349 public boolean updateLong(int columnIndex, long value) {
350 return false;
351 }
352
353 public boolean updateFloat(int columnIndex, float value) {
354 return false;
355 }
356
357 public boolean updateDouble(int columnIndex, double value) {
358 return false;
359 }
360
361 // TODO Temporary change, finalize after jq's changes go in
362 public void deactivate() {
363 if (mCursor != null) {
364 mCursor.deactivate();
365 }
366 super.deactivate();
367 }
368
369 public boolean requery() {
370 return mCursor.requery();
371 }
372
373 // TODO Temporary change, finalize after jq's changes go in
374 public void close() {
375 super.close();
376 if (mCursor != null) {
377 mCursor.close();
378 mCursor = null;
379 }
380 }
381
382 public void registerContentObserver(ContentObserver observer) {
383 super.registerContentObserver(observer);
384 }
385
386 public void unregisterContentObserver(ContentObserver observer) {
387 super.unregisterContentObserver(observer);
388 }
389
390 public void registerDataSetObserver(DataSetObserver observer) {
391 super.registerDataSetObserver(observer);
392 }
393
394 public void unregisterDataSetObserver(DataSetObserver observer) {
395 super.unregisterDataSetObserver(observer);
396 }
397
398 protected void onChange(boolean selfChange) {
399 synchronized (mObserverLock) {
400 super.onChange(selfChange);
401 if (mNotifyUri != null && selfChange) {
402 mContentResolver.notifyChange(mNotifyUri, mObserver);
403 }
404 }
405 }
406
407 public void setNotificationUri(ContentResolver cr, Uri uri) {
408 synchronized (mObserverLock) {
409 if (mObserver != null) {
410 cr.unregisterContentObserver(mObserver);
411 }
412 mObserver = new AbstractCursor.SelfContentObserver(this);
413 cr.registerContentObserver(uri, true, mObserver);
414 mCursor.setNotificationUri(cr, uri);
415 super.setNotificationUri(cr, uri);
416 mContentResolver = cr;
417 mNotifyUri = uri;
418 }
419 }
420
421 public boolean getWantsAllOnMoveCalls() {
422 return mCursor.getWantsAllOnMoveCalls();
423 }
424 }
425
426 @Override
427 public Cursor query(Uri url, String[] projectionIn, String selection,
428 String[] selectionArgs, String sortOrder)
429 throws IllegalStateException {
430 SQLiteDatabase db = mOpenHelper.getReadableDatabase();
431
432 int match = URI_MATCHER.match(url);
433 if (match == -1) {
434 throw new IllegalArgumentException("Unknown URL");
435 }
436
437 if (match == URI_MATCH_SUGGEST) {
438 String suggestSelection;
439 String [] myArgs;
440 if (selectionArgs[0] == null || selectionArgs[0].equals("")) {
441 suggestSelection = null;
442 myArgs = null;
443 } else {
444 String like = selectionArgs[0] + "%";
The Android Open Source Projected217d92008-12-17 18:05:52 -0800445 if (selectionArgs[0].startsWith("http")) {
446 myArgs = new String[1];
447 myArgs[0] = like;
448 suggestSelection = selection;
449 } else {
450 SUGGEST_ARGS[0] = "http://" + like;
451 SUGGEST_ARGS[1] = "http://www." + like;
452 SUGGEST_ARGS[2] = "https://" + like;
453 SUGGEST_ARGS[3] = "https://www." + like;
454 myArgs = SUGGEST_ARGS;
455 suggestSelection = SUGGEST_SELECTION;
456 }
The Android Open Source Projectba6d7b82008-10-21 07:00:00 -0700457 }
458 // Suggestions are always performed with the default sort order:
459 // date ASC.
460 Cursor c = db.query(TABLE_NAMES[URI_MATCH_BOOKMARKS],
461 SUGGEST_PROJECTION, suggestSelection, myArgs, null, null,
462 ORDER_BY, null);
463 c.setNotificationUri(getContext().getContentResolver(), url);
464 return new MySuggestionCursor(c, selectionArgs[0]);
465 }
466
467 String[] projection = null;
468 if (projectionIn != null && projectionIn.length > 0) {
469 projection = new String[projectionIn.length + 1];
470 System.arraycopy(projectionIn, 0, projection, 0, projectionIn.length);
471 projection[projectionIn.length] = "_id AS _id";
472 }
473
474 StringBuilder whereClause = new StringBuilder(256);
475 if (match == URI_MATCH_BOOKMARKS_ID || match == URI_MATCH_SEARCHES_ID) {
476 whereClause.append("(_id = ").append(url.getPathSegments().get(1))
477 .append(")");
478 }
479
480 // Tack on the user's selection, if present
481 if (selection != null && selection.length() > 0) {
482 if (whereClause.length() > 0) {
483 whereClause.append(" AND ");
484 }
485
486 whereClause.append('(');
487 whereClause.append(selection);
488 whereClause.append(')');
489 }
490 Cursor c = db.query(TABLE_NAMES[match % 10], projection,
491 whereClause.toString(), selectionArgs, null, null, sortOrder,
492 null);
493 c.setNotificationUri(getContext().getContentResolver(), url);
494 return c;
495 }
496
497 @Override
498 public String getType(Uri url) {
499 int match = URI_MATCHER.match(url);
500 switch (match) {
501 case URI_MATCH_BOOKMARKS:
502 return "vnd.android.cursor.dir/bookmark";
503
504 case URI_MATCH_BOOKMARKS_ID:
505 return "vnd.android.cursor.item/bookmark";
506
507 case URI_MATCH_SEARCHES:
508 return "vnd.android.cursor.dir/searches";
509
510 case URI_MATCH_SEARCHES_ID:
511 return "vnd.android.cursor.item/searches";
512
513 case URI_MATCH_SUGGEST:
514 return SearchManager.SUGGEST_MIME_TYPE;
515
516 default:
517 throw new IllegalArgumentException("Unknown URL");
518 }
519 }
520
521 @Override
522 public Uri insert(Uri url, ContentValues initialValues) {
523 SQLiteDatabase db = mOpenHelper.getWritableDatabase();
524
525 int match = URI_MATCHER.match(url);
526 Uri uri = null;
527 switch (match) {
528 case URI_MATCH_BOOKMARKS: {
529 // Insert into the bookmarks table
530 long rowID = db.insert(TABLE_NAMES[URI_MATCH_BOOKMARKS], "url",
531 initialValues);
532 if (rowID > 0) {
533 uri = ContentUris.withAppendedId(Browser.BOOKMARKS_URI,
534 rowID);
535 }
536 break;
537 }
538
539 case URI_MATCH_SEARCHES: {
540 // Insert into the searches table
541 long rowID = db.insert(TABLE_NAMES[URI_MATCH_SEARCHES], "url",
542 initialValues);
543 if (rowID > 0) {
544 uri = ContentUris.withAppendedId(Browser.SEARCHES_URI,
545 rowID);
546 }
547 break;
548 }
549
550 default:
551 throw new IllegalArgumentException("Unknown URL");
552 }
553
554 if (uri == null) {
555 throw new IllegalArgumentException("Unknown URL");
556 }
557 getContext().getContentResolver().notifyChange(uri, null);
558 return uri;
559 }
560
561 @Override
562 public int delete(Uri url, String where, String[] whereArgs) {
563 SQLiteDatabase db = mOpenHelper.getWritableDatabase();
564
565 int match = URI_MATCHER.match(url);
566 if (match == -1 || match == URI_MATCH_SUGGEST) {
567 throw new IllegalArgumentException("Unknown URL");
568 }
569
570 if (match == URI_MATCH_BOOKMARKS_ID || match == URI_MATCH_SEARCHES_ID) {
571 StringBuilder sb = new StringBuilder();
572 if (where != null && where.length() > 0) {
573 sb.append("( ");
574 sb.append(where);
575 sb.append(" ) AND ");
576 }
577 sb.append("_id = ");
578 sb.append(url.getPathSegments().get(1));
579 where = sb.toString();
580 }
581
582 int count = db.delete(TABLE_NAMES[match % 10], where, whereArgs);
583 getContext().getContentResolver().notifyChange(url, null);
584 return count;
585 }
586
587 @Override
588 public int update(Uri url, ContentValues values, String where,
589 String[] whereArgs) {
590 SQLiteDatabase db = mOpenHelper.getWritableDatabase();
591
592 int match = URI_MATCHER.match(url);
593 if (match == -1 || match == URI_MATCH_SUGGEST) {
594 throw new IllegalArgumentException("Unknown URL");
595 }
596
597 if (match == URI_MATCH_BOOKMARKS_ID || match == URI_MATCH_SEARCHES_ID) {
598 StringBuilder sb = new StringBuilder();
599 if (where != null && where.length() > 0) {
600 sb.append("( ");
601 sb.append(where);
602 sb.append(" ) AND ");
603 }
604 sb.append("_id = ");
605 sb.append(url.getPathSegments().get(1));
606 where = sb.toString();
607 }
608
609 int ret = db.update(TABLE_NAMES[match % 10], values, where, whereArgs);
610 getContext().getContentResolver().notifyChange(url, null);
611 return ret;
612 }
613}