diff options
Diffstat (limited to 'src/com/android/browser/BrowserProvider.java')
-rw-r--r-- | src/com/android/browser/BrowserProvider.java | 654 |
1 files changed, 654 insertions, 0 deletions
diff --git a/src/com/android/browser/BrowserProvider.java b/src/com/android/browser/BrowserProvider.java new file mode 100644 index 00000000..0c930c60 --- /dev/null +++ b/src/com/android/browser/BrowserProvider.java @@ -0,0 +1,654 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.browser; + +import android.app.ISearchManager; +import android.app.SearchManager; +import android.content.ComponentName; +import android.content.ContentProvider; +import android.content.ContentUris; +import android.content.ContentValues; +import android.content.Context; +import android.content.Intent; +import android.content.UriMatcher; +import android.database.AbstractCursor; +import android.database.Cursor; +import android.database.sqlite.SQLiteOpenHelper; +import android.database.sqlite.SQLiteDatabase; +import android.net.Uri; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.SystemProperties; +import android.provider.Browser; +import android.util.Log; +import android.server.search.SearchableInfo; +import android.text.util.Regex; + +public class BrowserProvider extends ContentProvider { + + private SQLiteOpenHelper mOpenHelper; + private static final String sDatabaseName = "browser.db"; + private static final String TAG = "BrowserProvider"; + private static final String ORDER_BY = "visits DESC, date DESC"; + + private static final String[] TABLE_NAMES = new String[] { + "bookmarks", "searches" + }; + private static final String[] SUGGEST_PROJECTION = new String[] { + "_id", "url", "title", "bookmark" + }; + private static final String SUGGEST_SELECTION = + "url LIKE ? OR url LIKE ? OR url LIKE ? OR url LIKE ?"; + private String[] SUGGEST_ARGS = new String[4]; + + // shared suggestion array index, make sure to match COLUMNS + private static final int SUGGEST_COLUMN_INTENT_ACTION_ID = 1; + private static final int SUGGEST_COLUMN_INTENT_DATA_ID = 2; + private static final int SUGGEST_COLUMN_TEXT_1_ID = 3; + private static final int SUGGEST_COLUMN_TEXT_2_ID = 4; + private static final int SUGGEST_COLUMN_ICON_1_ID = 5; + private static final int SUGGEST_COLUMN_ICON_2_ID = 6; + private static final int SUGGEST_COLUMN_QUERY_ID = 7; + + // shared suggestion columns + private static final String[] COLUMNS = new String[] { + "_id", + SearchManager.SUGGEST_COLUMN_INTENT_ACTION, + SearchManager.SUGGEST_COLUMN_INTENT_DATA, + SearchManager.SUGGEST_COLUMN_TEXT_1, + SearchManager.SUGGEST_COLUMN_TEXT_2, + SearchManager.SUGGEST_COLUMN_ICON_1, + SearchManager.SUGGEST_COLUMN_ICON_2, + SearchManager.SUGGEST_COLUMN_QUERY}; + + private static final int MAX_SUGGESTION_SHORT_ENTRIES = 3; + private static final int MAX_SUGGESTION_LONG_ENTRIES = 6; + + // make sure that these match the index of TABLE_NAMES + private static final int URI_MATCH_BOOKMARKS = 0; + private static final int URI_MATCH_SEARCHES = 1; + // (id % 10) should match the table name index + private static final int URI_MATCH_BOOKMARKS_ID = 10; + private static final int URI_MATCH_SEARCHES_ID = 11; + // + private static final int URI_MATCH_SUGGEST = 20; + + private static final UriMatcher URI_MATCHER; + + static { + URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH); + URI_MATCHER.addURI("browser", TABLE_NAMES[URI_MATCH_BOOKMARKS], + URI_MATCH_BOOKMARKS); + URI_MATCHER.addURI("browser", TABLE_NAMES[URI_MATCH_BOOKMARKS] + "/#", + URI_MATCH_BOOKMARKS_ID); + URI_MATCHER.addURI("browser", TABLE_NAMES[URI_MATCH_SEARCHES], + URI_MATCH_SEARCHES); + URI_MATCHER.addURI("browser", TABLE_NAMES[URI_MATCH_SEARCHES] + "/#", + URI_MATCH_SEARCHES_ID); + URI_MATCHER.addURI("browser", SearchManager.SUGGEST_URI_PATH_QUERY, + URI_MATCH_SUGGEST); + } + + // 1 -> 2 add cache table + // 2 -> 3 update history table + // 3 -> 4 add passwords table + // 4 -> 5 add settings table + // 5 -> 6 ? + // 6 -> 7 ? + // 7 -> 8 drop proxy table + // 8 -> 9 drop settings table + // 9 -> 10 add form_urls and form_data + // 10 -> 11 add searches table + // 11 -> 12 modify cache table + // 12 -> 13 modify cache table + // 13 -> 14 correspond with Google Bookmarks schema + // 14 -> 15 move couple of tables to either browser private database or webview database + // 15 -> 17 Set it up for the SearchManager + // 17 -> 18 Added favicon in bookmarks table for Home shortcuts + // 18 -> 19 Remove labels table + private static final int DATABASE_VERSION = 19; + + public BrowserProvider() { + } + + + private static CharSequence replaceSystemPropertyInString(CharSequence srcString) { + StringBuffer sb = new StringBuffer(); + int lastCharLoc = 0; + for (int i = 0; i < srcString.length(); ++i) { + char c = srcString.charAt(i); + if (c == '{') { + sb.append(srcString.subSequence(lastCharLoc, i)); + lastCharLoc = i; + inner: + for (int j = i; j < srcString.length(); ++j) { + char k = srcString.charAt(j); + if (k == '}') { + String propertyKeyValue = srcString.subSequence(i + 1, j).toString(); + // See if the propertyKeyValue specifies a default value + int defaultOffset = propertyKeyValue.indexOf(':'); + if (defaultOffset == -1) { + sb.append(SystemProperties.get(propertyKeyValue)); + } else { + String propertyKey = propertyKeyValue.substring(0, defaultOffset); + String defaultValue = + propertyKeyValue.substring(defaultOffset + 1, + propertyKeyValue.length()); + sb.append(SystemProperties.get(propertyKey, defaultValue)); + } + lastCharLoc = j + 1; + i = j; + break inner; + } + } + } + } + if (srcString.length() - lastCharLoc > 0) { + // Put on the tail, if there is one + sb.append(srcString.subSequence(lastCharLoc, srcString.length())); + } + return sb; + } + + private static class DatabaseHelper extends SQLiteOpenHelper { + private Context mContext; + + public DatabaseHelper(Context context) { + super(context, sDatabaseName, null, DATABASE_VERSION); + mContext = context; + } + + @Override + public void onCreate(SQLiteDatabase db) { + db.execSQL("CREATE TABLE bookmarks (" + + "_id INTEGER PRIMARY KEY," + + "title TEXT," + + "url TEXT," + + "visits INTEGER," + + "date LONG," + + "created LONG," + + "description TEXT," + + "bookmark INTEGER," + + "favicon BLOB DEFAULT NULL" + + ");"); + + final CharSequence[] bookmarks = mContext.getResources() + .getTextArray(R.array.bookmarks); + int size = bookmarks.length; + try { + for (int i = 0; i < size; i = i + 2) { + CharSequence bookmarkDestination = replaceSystemPropertyInString(bookmarks[i + 1]); + db.execSQL("INSERT INTO bookmarks (title, url, visits, " + + "date, created, bookmark)" + " VALUES('" + + bookmarks[i] + "', '" + bookmarkDestination + + "', 0, 0, 0, 1);"); + } + } catch (ArrayIndexOutOfBoundsException e) { + } + + db.execSQL("CREATE TABLE searches (" + + "_id INTEGER PRIMARY KEY," + + "search TEXT," + + "date LONG" + + ");"); + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + Log.w(TAG, "Upgrading database from version " + oldVersion + " to " + + newVersion + ", which will destroy all old data"); + if (oldVersion == 18) { + db.execSQL("DROP TABLE IF EXISTS labels"); + } else { + db.execSQL("DROP TABLE IF EXISTS bookmarks"); + db.execSQL("DROP TABLE IF EXISTS searches"); + onCreate(db); + } + } + } + + @Override + public boolean onCreate() { + mOpenHelper = new DatabaseHelper(getContext()); + return true; + } + + /* + * Subclass AbstractCursor so we can combine multiple Cursors and add + * "Google Search". + * Here are the rules. + * 1. We only have MAX_SUGGESTION_LONG_ENTRIES in the list plus + * "Google Search"; + * 2. If bookmark/history entries are less than + * (MAX_SUGGESTION_SHORT_ENTRIES -1), we include Google suggest. + */ + private class MySuggestionCursor extends AbstractCursor { + private Cursor mHistoryCursor; + private Cursor mSuggestCursor; + private int mHistoryCount; + private int mSuggestionCount; + private boolean mBeyondCursor; + private String mString; + + public MySuggestionCursor(Cursor hc, Cursor sc, String string) { + mHistoryCursor = hc; + mSuggestCursor = sc; + mHistoryCount = hc.getCount(); + mSuggestionCount = sc != null ? sc.getCount() : 0; + if (mSuggestionCount > (MAX_SUGGESTION_LONG_ENTRIES - mHistoryCount)) { + mSuggestionCount = MAX_SUGGESTION_LONG_ENTRIES - mHistoryCount; + } + mString = string; + mBeyondCursor = false; + } + + @Override + public boolean onMove(int oldPosition, int newPosition) { + if (mHistoryCursor == null) { + return false; + } + if (mHistoryCount > newPosition) { + mHistoryCursor.moveToPosition(newPosition); + mBeyondCursor = false; + } else if (mHistoryCount + mSuggestionCount > newPosition) { + mSuggestCursor.moveToPosition(newPosition - mHistoryCount); + mBeyondCursor = false; + } else { + mBeyondCursor = true; + } + return true; + } + + @Override + public int getCount() { + if (mString.length() > 0) { + return mHistoryCount + mSuggestionCount + 1; + } else { + return mHistoryCount + mSuggestionCount; + } + } + + @Override + public String[] getColumnNames() { + return COLUMNS; + } + + @Override + public String getString(int columnIndex) { + if ((mPos != -1 && mHistoryCursor != null)) { + switch(columnIndex) { + case SUGGEST_COLUMN_INTENT_ACTION_ID: + if (mHistoryCount > mPos) { + return Intent.ACTION_VIEW; + } else { + return Intent.ACTION_SEARCH; + } + + case SUGGEST_COLUMN_INTENT_DATA_ID: + if (mHistoryCount > mPos) { + return mHistoryCursor.getString(1); + } else { + return null; + } + + case SUGGEST_COLUMN_TEXT_1_ID: + if (mHistoryCount > mPos) { + return mHistoryCursor.getString(1); + } else if (!mBeyondCursor) { + return mSuggestCursor.getString(1); + } else { + return mString; + } + + case SUGGEST_COLUMN_TEXT_2_ID: + if (mHistoryCount > mPos) { + return mHistoryCursor.getString(2); + } else if (!mBeyondCursor) { + return mSuggestCursor.getString(2); + } else { + return getContext().getString(R.string.search_google); + } + + case SUGGEST_COLUMN_ICON_1_ID: + if (mHistoryCount > mPos) { + if (mHistoryCursor.getInt(3) == 1) { + return new Integer( + R.drawable.ic_search_category_bookmark) + .toString(); + } else { + return new Integer( + R.drawable.ic_search_category_history) + .toString(); + } + } else { + return new Integer( + R.drawable.ic_search_category_suggest) + .toString(); + } + + case SUGGEST_COLUMN_ICON_2_ID: + return new String("0"); + + case SUGGEST_COLUMN_QUERY_ID: + if (mHistoryCount > mPos) { + return null; + } else if (!mBeyondCursor) { + return mSuggestCursor.getString(3); + } else { + return mString; + } + } + } + return null; + } + + @Override + public double getDouble(int column) { + throw new UnsupportedOperationException(); + } + + @Override + public float getFloat(int column) { + throw new UnsupportedOperationException(); + } + + @Override + public int getInt(int column) { + throw new UnsupportedOperationException(); + } + + @Override + public long getLong(int column) { + if ((mPos != -1) && column == 0) { + return mPos; // use row# as the _Id + } + throw new UnsupportedOperationException(); + } + + @Override + public short getShort(int column) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isNull(int column) { + throw new UnsupportedOperationException(); + } + + // TODO Temporary change, finalize after jq's changes go in + public void deactivate() { + if (mHistoryCursor != null) { + mHistoryCursor.deactivate(); + } + if (mSuggestCursor != null) { + mSuggestCursor.deactivate(); + } + super.deactivate(); + } + + public boolean requery() { + return (mHistoryCursor != null ? mHistoryCursor.requery() : false) | + (mSuggestCursor != null ? mSuggestCursor.requery() : false); + } + + // TODO Temporary change, finalize after jq's changes go in + public void close() { + super.close(); + if (mHistoryCursor != null) { + mHistoryCursor.close(); + mHistoryCursor = null; + } + if (mSuggestCursor != null) { + mSuggestCursor.close(); + mSuggestCursor = null; + } + } + } + + @Override + public Cursor query(Uri url, String[] projectionIn, String selection, + String[] selectionArgs, String sortOrder) + throws IllegalStateException { + SQLiteDatabase db = mOpenHelper.getReadableDatabase(); + + int match = URI_MATCHER.match(url); + if (match == -1) { + throw new IllegalArgumentException("Unknown URL"); + } + + if (match == URI_MATCH_SUGGEST) { + String suggestSelection; + String [] myArgs; + if (selectionArgs[0] == null || selectionArgs[0].equals("")) { + suggestSelection = null; + myArgs = null; + } else { + String like = selectionArgs[0] + "%"; + if (selectionArgs[0].startsWith("http")) { + myArgs = new String[1]; + myArgs[0] = like; + suggestSelection = selection; + } else { + SUGGEST_ARGS[0] = "http://" + like; + SUGGEST_ARGS[1] = "http://www." + like; + SUGGEST_ARGS[2] = "https://" + like; + SUGGEST_ARGS[3] = "https://www." + like; + myArgs = SUGGEST_ARGS; + suggestSelection = SUGGEST_SELECTION; + } + } + + Cursor c = db.query(TABLE_NAMES[URI_MATCH_BOOKMARKS], + SUGGEST_PROJECTION, suggestSelection, myArgs, null, null, + ORDER_BY, + (new Integer(MAX_SUGGESTION_LONG_ENTRIES)).toString()); + + if (Regex.WEB_URL_PATTERN.matcher(selectionArgs[0]).matches()) { + return new MySuggestionCursor(c, null, ""); + } else { + // get Google suggest if there is still space in the list + if (myArgs != null && myArgs.length > 1 + && c.getCount() < (MAX_SUGGESTION_SHORT_ENTRIES - 1)) { + ISearchManager sm = ISearchManager.Stub + .asInterface(ServiceManager + .getService(Context.SEARCH_SERVICE)); + SearchableInfo si = null; + try { + // use the global search to get Google suggest provider + si = sm.getSearchableInfo(new ComponentName( + getContext(), "com.android.browser"), true); + + // similar to the getSuggestions() in SearchDialog.java + StringBuilder uriStr = new StringBuilder("content://"); + uriStr.append(si.getSuggestAuthority()); + // if content path provided, insert it now + final String contentPath = si.getSuggestPath(); + if (contentPath != null) { + uriStr.append('/'); + uriStr.append(contentPath); + } + // append standard suggestion query path + uriStr.append('/' + SearchManager.SUGGEST_URI_PATH_QUERY); + // inject query, either as selection args or inline + String[] selArgs = null; + if (si.getSuggestSelection() != null) { + selArgs = new String[] {selectionArgs[0]}; + } else { + uriStr.append('/'); + uriStr.append(Uri.encode(selectionArgs[0])); + } + + // finally, make the query + Cursor sc = getContext().getContentResolver().query( + Uri.parse(uriStr.toString()), null, + si.getSuggestSelection(), selArgs, null); + + return new MySuggestionCursor(c, sc, selectionArgs[0]); + } catch (RemoteException e) { + } + } + return new MySuggestionCursor(c, null, selectionArgs[0]); + } + } + + String[] projection = null; + if (projectionIn != null && projectionIn.length > 0) { + projection = new String[projectionIn.length + 1]; + System.arraycopy(projectionIn, 0, projection, 0, projectionIn.length); + projection[projectionIn.length] = "_id AS _id"; + } + + StringBuilder whereClause = new StringBuilder(256); + if (match == URI_MATCH_BOOKMARKS_ID || match == URI_MATCH_SEARCHES_ID) { + whereClause.append("(_id = ").append(url.getPathSegments().get(1)) + .append(")"); + } + + // Tack on the user's selection, if present + if (selection != null && selection.length() > 0) { + if (whereClause.length() > 0) { + whereClause.append(" AND "); + } + + whereClause.append('('); + whereClause.append(selection); + whereClause.append(')'); + } + Cursor c = db.query(TABLE_NAMES[match % 10], projection, + whereClause.toString(), selectionArgs, null, null, sortOrder, + null); + c.setNotificationUri(getContext().getContentResolver(), url); + return c; + } + + @Override + public String getType(Uri url) { + int match = URI_MATCHER.match(url); + switch (match) { + case URI_MATCH_BOOKMARKS: + return "vnd.android.cursor.dir/bookmark"; + + case URI_MATCH_BOOKMARKS_ID: + return "vnd.android.cursor.item/bookmark"; + + case URI_MATCH_SEARCHES: + return "vnd.android.cursor.dir/searches"; + + case URI_MATCH_SEARCHES_ID: + return "vnd.android.cursor.item/searches"; + + case URI_MATCH_SUGGEST: + return SearchManager.SUGGEST_MIME_TYPE; + + default: + throw new IllegalArgumentException("Unknown URL"); + } + } + + @Override + public Uri insert(Uri url, ContentValues initialValues) { + SQLiteDatabase db = mOpenHelper.getWritableDatabase(); + + int match = URI_MATCHER.match(url); + Uri uri = null; + switch (match) { + case URI_MATCH_BOOKMARKS: { + // Insert into the bookmarks table + long rowID = db.insert(TABLE_NAMES[URI_MATCH_BOOKMARKS], "url", + initialValues); + if (rowID > 0) { + uri = ContentUris.withAppendedId(Browser.BOOKMARKS_URI, + rowID); + } + break; + } + + case URI_MATCH_SEARCHES: { + // Insert into the searches table + long rowID = db.insert(TABLE_NAMES[URI_MATCH_SEARCHES], "url", + initialValues); + if (rowID > 0) { + uri = ContentUris.withAppendedId(Browser.SEARCHES_URI, + rowID); + } + break; + } + + default: + throw new IllegalArgumentException("Unknown URL"); + } + + if (uri == null) { + throw new IllegalArgumentException("Unknown URL"); + } + getContext().getContentResolver().notifyChange(uri, null); + return uri; + } + + @Override + public int delete(Uri url, String where, String[] whereArgs) { + SQLiteDatabase db = mOpenHelper.getWritableDatabase(); + + int match = URI_MATCHER.match(url); + if (match == -1 || match == URI_MATCH_SUGGEST) { + throw new IllegalArgumentException("Unknown URL"); + } + + if (match == URI_MATCH_BOOKMARKS_ID || match == URI_MATCH_SEARCHES_ID) { + StringBuilder sb = new StringBuilder(); + if (where != null && where.length() > 0) { + sb.append("( "); + sb.append(where); + sb.append(" ) AND "); + } + sb.append("_id = "); + sb.append(url.getPathSegments().get(1)); + where = sb.toString(); + } + + int count = db.delete(TABLE_NAMES[match % 10], where, whereArgs); + getContext().getContentResolver().notifyChange(url, null); + return count; + } + + @Override + public int update(Uri url, ContentValues values, String where, + String[] whereArgs) { + SQLiteDatabase db = mOpenHelper.getWritableDatabase(); + + int match = URI_MATCHER.match(url); + if (match == -1 || match == URI_MATCH_SUGGEST) { + throw new IllegalArgumentException("Unknown URL"); + } + + if (match == URI_MATCH_BOOKMARKS_ID || match == URI_MATCH_SEARCHES_ID) { + StringBuilder sb = new StringBuilder(); + if (where != null && where.length() > 0) { + sb.append("( "); + sb.append(where); + sb.append(" ) AND "); + } + sb.append("_id = "); + sb.append(url.getPathSegments().get(1)); + where = sb.toString(); + } + + int ret = db.update(TABLE_NAMES[match % 10], values, where, whereArgs); + getContext().getContentResolver().notifyChange(url, null); + return ret; + } +} |