OSDN Git Service

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