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