diff options
Diffstat (limited to 'src/com/android/browser')
-rw-r--r-- | src/com/android/browser/Bookmarks.java | 164 | ||||
-rw-r--r-- | src/com/android/browser/BookmarksLoader.java | 49 | ||||
-rw-r--r-- | src/com/android/browser/BrowserActivity.java | 2 | ||||
-rw-r--r-- | src/com/android/browser/BrowserBookmarksAdapter.java | 565 | ||||
-rw-r--r-- | src/com/android/browser/BrowserBookmarksPage.java | 741 | ||||
-rw-r--r-- | src/com/android/browser/BrowserProvider.java | 4 | ||||
-rw-r--r-- | src/com/android/browser/CombinedBookmarkHistoryActivity.java | 10 | ||||
-rw-r--r-- | src/com/android/browser/DownloadTouchIcon.java | 2 | ||||
-rw-r--r-- | src/com/android/browser/Tab.java | 4 | ||||
-rw-r--r-- | src/com/android/browser/provider/BrowserContract.java | 367 | ||||
-rw-r--r-- | src/com/android/browser/provider/BrowserProvider2.java | 725 | ||||
-rw-r--r-- | src/com/android/browser/provider/SQLiteContentProvider.java | 276 |
12 files changed, 1821 insertions, 1088 deletions
diff --git a/src/com/android/browser/Bookmarks.java b/src/com/android/browser/Bookmarks.java index 5ada9dc..da39799 100644 --- a/src/com/android/browser/Bookmarks.java +++ b/src/com/android/browser/Bookmarks.java @@ -16,6 +16,8 @@ package com.android.browser; +import com.android.browser.provider.BrowserContract; + import android.content.ContentResolver; import android.content.ContentUris; import android.content.ContentValues; @@ -23,7 +25,9 @@ import android.content.Context; import android.database.Cursor; import android.graphics.Bitmap; import android.net.Uri; +import android.os.AsyncTask; import android.provider.Browser; +import android.provider.Browser.BookmarkColumns; import android.util.Log; import android.webkit.WebIconDatabase; import android.widget.Toast; @@ -61,72 +65,18 @@ import java.util.Date; * This will usually be <code>true</code> except when bookmarks are * added by a settings restore agent. */ - /* package */ static void addBookmark(Context context, - ContentResolver cr, String url, String name, - Bitmap thumbnail, boolean retainIcon) { + /* package */ static void addBookmark(Context context, ContentResolver cr, String url, + String name, Bitmap thumbnail, boolean retainIcon) { // Want to append to the beginning of the list - long creationTime = new Date().getTime(); - ContentValues map = new ContentValues(); + ContentValues values = new ContentValues(); Cursor cursor = null; try { - cursor = Browser.getVisitedLike(cr, url); - if (cursor.moveToFirst() && cursor.getInt( - Browser.HISTORY_PROJECTION_BOOKMARK_INDEX) == 0) { - // This means we have been to this site but not bookmarked - // it, so convert the history item to a bookmark - map.put(Browser.BookmarkColumns.CREATED, creationTime); - map.put(Browser.BookmarkColumns.TITLE, name); - map.put(Browser.BookmarkColumns.BOOKMARK, 1); - map.put(Browser.BookmarkColumns.THUMBNAIL, - bitmapToBytes(thumbnail)); - cr.update(Browser.BOOKMARKS_URI, map, - "_id = " + cursor.getInt(0), null); - } else { - int count = cursor.getCount(); - boolean matchedTitle = false; - for (int i = 0; i < count; i++) { - // One or more bookmarks already exist for this site. - // Check the names of each - cursor.moveToPosition(i); - if (cursor.getString(Browser.HISTORY_PROJECTION_TITLE_INDEX) - .equals(name)) { - // The old bookmark has the same name. - // Update its creation time. - map.put(Browser.BookmarkColumns.CREATED, - creationTime); - cr.update(Browser.BOOKMARKS_URI, map, - "_id = " + cursor.getInt(0), null); - matchedTitle = true; - break; - } - } - if (!matchedTitle) { - // Adding a bookmark for a site the user has visited, - // or a new bookmark (with a different name) for a site - // the user has visited - map.put(Browser.BookmarkColumns.TITLE, name); - map.put(Browser.BookmarkColumns.URL, url); - map.put(Browser.BookmarkColumns.CREATED, creationTime); - map.put(Browser.BookmarkColumns.BOOKMARK, 1); - map.put(Browser.BookmarkColumns.DATE, 0); - map.put(Browser.BookmarkColumns.THUMBNAIL, - bitmapToBytes(thumbnail)); - int visits = 0; - if (count > 0) { - // The user has already bookmarked, and possibly - // visited this site. However, they are creating - // a new bookmark with the same url but a different - // name. The new bookmark should have the same - // number of visits as the already created bookmark. - visits = cursor.getInt( - Browser.HISTORY_PROJECTION_VISITS_INDEX); - } - // Bookmark starts with 3 extra visits so that it will - // bubble up in the most visited and goto search box - map.put(Browser.BookmarkColumns.VISITS, visits + 3); - cr.insert(Browser.BOOKMARKS_URI, map); - } - } + values.put(BrowserContract.Bookmarks.TITLE, name); + values.put(BrowserContract.Bookmarks.URL, url); + values.put(BrowserContract.Bookmarks.IS_FOLDER, 0); + values.put(BrowserContract.Bookmarks.THUMBNAIL, + bitmapToBytes(thumbnail)); + cr.insert(BrowserContract.Bookmarks.CONTENT_URI, values); } catch (IllegalStateException e) { Log.e(LOGTAG, "addBookmark", e); } finally { @@ -219,4 +169,92 @@ import java.util.Date; } return false; } + + /* package */ static Cursor queryBookmarksForUrl(ContentResolver cr, + String originalUrl, String url, boolean onlyBookmarks) { + if (cr == null || url == null) { + return null; + } + + // If originalUrl is null, just set it to url. + if (originalUrl == null) { + originalUrl = url; + } + + // Look for both the original url and the actual url. This takes in to + // account redirects. + String originalUrlNoQuery = Bookmarks.removeQuery(originalUrl); + String urlNoQuery = Bookmarks.removeQuery(url); + originalUrl = originalUrlNoQuery + '?'; + url = urlNoQuery + '?'; + + // Use NoQuery to search for the base url (i.e. if the url is + // http://www.yahoo.com/?rs=1, search for http://www.yahoo.com) + // Use url to match the base url with other queries (i.e. if the url is + // http://www.google.com/m, search for + // http://www.google.com/m?some_query) + final String[] selArgs = new String[] { + originalUrlNoQuery, urlNoQuery, originalUrl, url }; + String where = BookmarkColumns.URL + " == ? OR " + + BookmarkColumns.URL + " == ? OR " + + BookmarkColumns.URL + " LIKE ? || '%' OR " + + BookmarkColumns.URL + " LIKE ? || '%'"; + if (onlyBookmarks) { + where = "(" + where + ") AND " + BookmarkColumns.BOOKMARK + " == 1"; + } + final String[] projection = + new String[] { Browser.BookmarkColumns._ID }; + return cr.query(Browser.BOOKMARKS_URI, projection, where, selArgs, + null); + } + + // Strip the query from the given url. + static String removeQuery(String url) { + if (url == null) { + return null; + } + int query = url.indexOf('?'); + String noQuery = url; + if (query != -1) { + noQuery = url.substring(0, query); + } + return noQuery; + } + + /** + * Update the bookmark's favicon. This is a convenience method for updating + * a bookmark favicon for the originalUrl and url of the passed in WebView. + * @param cr The ContentResolver to use. + * @param originalUrl The original url before any redirects. + * @param url The current url. + * @param favicon The favicon bitmap to write to the db. + */ + /* package */ static void updateBookmarkFavicon(final ContentResolver cr, + final String originalUrl, final String url, final Bitmap favicon) { + new AsyncTask<Void, Void, Void>() { + @Override + protected Void doInBackground(Void... unused) { + final Cursor c = + Bookmarks.queryBookmarksForUrl(cr, originalUrl, url, true); + if (c == null) { + return null; + } + if (c.moveToFirst()) { + ContentValues values = new ContentValues(); + final ByteArrayOutputStream os = + new ByteArrayOutputStream(); + favicon.compress(Bitmap.CompressFormat.PNG, 100, os); + values.put(Browser.BookmarkColumns.FAVICON, + os.toByteArray()); + do { + cr.update(ContentUris.withAppendedId( + Browser.BOOKMARKS_URI, c.getInt(0)), + values, null, null); + } while (c.moveToNext()); + } + c.close(); + return null; + } + }.execute(); + } } diff --git a/src/com/android/browser/BookmarksLoader.java b/src/com/android/browser/BookmarksLoader.java new file mode 100644 index 0000000..cdb0317 --- /dev/null +++ b/src/com/android/browser/BookmarksLoader.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2010 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 com.android.browser.provider.BrowserContract.Bookmarks; + +import android.content.Context; +import android.content.CursorLoader; + +public class BookmarksLoader extends CursorLoader { + public static final String ARG_ROOT_FOLDER = "root"; + + public static final int COLUMN_INDEX_ID = 0; + public static final int COLUMN_INDEX_URL = 1; + public static final int COLUMN_INDEX_TITLE = 2; + public static final int COLUMN_INDEX_FAVICON = 3; + public static final int COLUMN_INDEX_THUMBNAIL = 4; + public static final int COLUMN_INDEX_TOUCH_ICON = 5; + public static final int COLUMN_INDEX_IS_FOLDER = 6; + + public static final String[] PROJECTION = new String[] { + Bookmarks._ID, // 0 + Bookmarks.URL, // 1 + Bookmarks.TITLE, // 2 + Bookmarks.FAVICON, // 3 + Bookmarks.THUMBNAIL, // 4 + Bookmarks.TOUCH_ICON, // 5 + Bookmarks.IS_FOLDER, // 6 + Bookmarks.POSITION, // 7 + }; + + public BookmarksLoader(Context context, int rootFolder) { + super(context, Bookmarks.CONTENT_URI_DEFAULT_FOLDER, PROJECTION, null, null, null); + } +} diff --git a/src/com/android/browser/BrowserActivity.java b/src/com/android/browser/BrowserActivity.java index ee231a7..a2c2f32 100644 --- a/src/com/android/browser/BrowserActivity.java +++ b/src/com/android/browser/BrowserActivity.java @@ -2474,7 +2474,7 @@ public class BrowserActivity extends Activity protected Void doInBackground(Void... unused) { Cursor c = null; try { - c = BrowserBookmarksAdapter.queryBookmarksForUrl( + c = Bookmarks.queryBookmarksForUrl( cr, originalUrl, url, true); if (c != null) { if (c.moveToFirst()) { diff --git a/src/com/android/browser/BrowserBookmarksAdapter.java b/src/com/android/browser/BrowserBookmarksAdapter.java index 241b33b..e29ca18 100644 --- a/src/com/android/browser/BrowserBookmarksAdapter.java +++ b/src/com/android/browser/BrowserBookmarksAdapter.java @@ -16,567 +16,44 @@ package com.android.browser; -import android.content.ContentResolver; -import android.content.ContentUris; -import android.content.ContentValues; -import android.database.ContentObserver; +import android.content.Context; import android.database.Cursor; -import android.database.DataSetObserver; import android.graphics.Bitmap; import android.graphics.BitmapFactory; -import android.net.Uri; -import android.os.AsyncTask; -import android.os.Bundle; -import android.os.Handler; -import android.os.Looper; -import android.provider.Browser; -import android.provider.Browser.BookmarkColumns; -import android.view.KeyEvent; -import android.view.LayoutInflater; import android.view.View; -import android.view.ViewGroup; -import android.webkit.WebIconDatabase; -import android.webkit.WebView; -import android.widget.BaseAdapter; import android.widget.ImageView; +import android.widget.ResourceCursorAdapter; import android.widget.TextView; -import java.io.ByteArrayOutputStream; - -class BrowserBookmarksAdapter extends BaseAdapter { - - private String mCurrentPage; - private String mCurrentTitle; - private Bitmap mCurrentThumbnail; - private Cursor mCursor; - private int mCount; - private BrowserBookmarksPage mBookmarksPage; - private ContentResolver mContentResolver; - private boolean mDataValid; - private BookmarkViewMode mViewMode; - private boolean mMostVisited; - private boolean mNeedsOffset; - private int mExtraOffset; - +class BrowserBookmarksAdapter extends ResourceCursorAdapter { /** * Create a new BrowserBookmarksAdapter. - * @param b BrowserBookmarksPage that instantiated this. - * Necessary so it will adjust its focus - * appropriately after a search. - */ - public BrowserBookmarksAdapter(BrowserBookmarksPage b, String curPage, - String curTitle, Bitmap curThumbnail, boolean createShortcut, - boolean mostVisited) { - mNeedsOffset = !(createShortcut || mostVisited); - mMostVisited = mostVisited; - mExtraOffset = mNeedsOffset ? 1 : 0; - mBookmarksPage = b; - mCurrentPage = b.getResources().getString(R.string.current_page) - + curPage; - mCurrentTitle = curTitle; - mCurrentThumbnail = curThumbnail; - mContentResolver = b.getContentResolver(); - mViewMode = BookmarkViewMode.LIST; - - String whereClause; - // FIXME: Should have a default sort order that the user selects. - String orderBy = Browser.BookmarkColumns.VISITS + " DESC"; - if (mostVisited) { - whereClause = Browser.BookmarkColumns.VISITS + " != 0"; - } else { - whereClause = Browser.BookmarkColumns.BOOKMARK + " = 1"; - } - mCursor = b.managedQuery(Browser.BOOKMARKS_URI, - Browser.HISTORY_PROJECTION, whereClause, null, orderBy); - mCursor.registerContentObserver(new ChangeObserver()); - mCursor.registerDataSetObserver(new MyDataSetObserver()); - - mDataValid = true; - notifyDataSetChanged(); - - mCount = mCursor.getCount() + mExtraOffset; - } - - /** - * Return a hashmap with one row's Title, Url, and favicon. - * @param position Position in the list. - * @return Bundle Stores title, url of row position, favicon, and id - * for the url. Return a blank map if position is out of - * range. - */ - public Bundle getRow(int position) { - Bundle map = new Bundle(); - if (position < mExtraOffset || position >= mCount) { - return map; - } - mCursor.moveToPosition(position- mExtraOffset); - String url = mCursor.getString(Browser.HISTORY_PROJECTION_URL_INDEX); - map.putString(Browser.BookmarkColumns.TITLE, - mCursor.getString(Browser.HISTORY_PROJECTION_TITLE_INDEX)); - map.putString(Browser.BookmarkColumns.URL, url); - byte[] data = mCursor.getBlob(Browser.HISTORY_PROJECTION_FAVICON_INDEX); - if (data != null) { - map.putParcelable(Browser.BookmarkColumns.FAVICON, - BitmapFactory.decodeByteArray(data, 0, data.length)); - } - map.putInt("id", mCursor.getInt(Browser.HISTORY_PROJECTION_ID_INDEX)); - return map; - } - - /** - * Update a row in the database with new information. - * Requeries the database if the information has changed. - * @param map Bundle storing id, title and url of new information - */ - public void updateRow(Bundle map) { - - // Find the record - int id = map.getInt("id"); - int position = -1; - for (mCursor.moveToFirst(); !mCursor.isAfterLast(); mCursor.moveToNext()) { - if (mCursor.getInt(Browser.HISTORY_PROJECTION_ID_INDEX) == id) { - position = mCursor.getPosition(); - break; - } - } - if (position < 0) { - return; - } - - mCursor.moveToPosition(position); - ContentValues values = new ContentValues(); - String title = map.getString(Browser.BookmarkColumns.TITLE); - if (!title.equals(mCursor - .getString(Browser.HISTORY_PROJECTION_TITLE_INDEX))) { - values.put(Browser.BookmarkColumns.TITLE, title); - } - String url = map.getString(Browser.BookmarkColumns.URL); - if (!url.equals(mCursor. - getString(Browser.HISTORY_PROJECTION_URL_INDEX))) { - values.put(Browser.BookmarkColumns.URL, url); - } - - if (map.getBoolean("invalidateThumbnail") == true) { - values.put(Browser.BookmarkColumns.THUMBNAIL, new byte[0]); - } - if (values.size() > 0 - && mContentResolver.update(Browser.BOOKMARKS_URI, values, - "_id = " + id, null) != -1) { - refreshList(); - } - } - - /** - * Delete a row from the database. Requeries the database. - * Does nothing if the provided position is out of range. - * @param position Position in the list. - */ - public void deleteRow(int position) { - if (position < mExtraOffset || position >= getCount()) { - return; - } - mCursor.moveToPosition(position- mExtraOffset); - String url = mCursor.getString(Browser.HISTORY_PROJECTION_URL_INDEX); - String title = mCursor.getString(Browser.HISTORY_PROJECTION_TITLE_INDEX); - Bookmarks.removeFromBookmarks(null, mContentResolver, url, title); - refreshList(); - } - - /** - * Delete all bookmarks from the db. Requeries the database. - * All bookmarks with become visited URLs or if never visited - * are removed */ - public void deleteAllRows() { - StringBuilder deleteIds = null; - StringBuilder convertIds = null; - - for (mCursor.moveToFirst(); !mCursor.isAfterLast(); mCursor.moveToNext()) { - String url = mCursor.getString(Browser.HISTORY_PROJECTION_URL_INDEX); - WebIconDatabase.getInstance().releaseIconForPageUrl(url); - int id = mCursor.getInt(Browser.HISTORY_PROJECTION_ID_INDEX); - int numVisits = mCursor.getInt(Browser.HISTORY_PROJECTION_VISITS_INDEX); - if (0 == numVisits) { - if (deleteIds == null) { - deleteIds = new StringBuilder(); - deleteIds.append("( "); - } else { - deleteIds.append(" OR ( "); - } - deleteIds.append(BookmarkColumns._ID); - deleteIds.append(" = "); - deleteIds.append(id); - deleteIds.append(" )"); - } else { - // It is no longer a bookmark, but it is still a visited site. - if (convertIds == null) { - convertIds = new StringBuilder(); - convertIds.append("( "); - } else { - convertIds.append(" OR ( "); - } - convertIds.append(BookmarkColumns._ID); - convertIds.append(" = "); - convertIds.append(id); - convertIds.append(" )"); - } - } - - if (deleteIds != null) { - mContentResolver.delete(Browser.BOOKMARKS_URI, deleteIds.toString(), - null); - } - if (convertIds != null) { - ContentValues values = new ContentValues(); - values.put(Browser.BookmarkColumns.BOOKMARK, 0); - mContentResolver.update(Browser.BOOKMARKS_URI, values, - convertIds.toString(), null); - } - refreshList(); - } - - /** - * Refresh list to recognize a change in the database. - */ - public void refreshList() { - mCursor.requery(); - mCount = mCursor.getCount() + mExtraOffset; - notifyDataSetChanged(); - } - - /** - * Update the bookmark's favicon. This is a convenience method for updating - * a bookmark favicon for the originalUrl and url of the passed in WebView. - * @param cr The ContentResolver to use. - * @param originalUrl The original url before any redirects. - * @param url The current url. - * @param favicon The favicon bitmap to write to the db. - */ - /* package */ static void updateBookmarkFavicon(final ContentResolver cr, - final String originalUrl, final String url, final Bitmap favicon) { - new AsyncTask<Void, Void, Void>() { - protected Void doInBackground(Void... unused) { - final Cursor c = - queryBookmarksForUrl(cr, originalUrl, url, true); - if (c == null) { - return null; - } - if (c.moveToFirst()) { - ContentValues values = new ContentValues(); - final ByteArrayOutputStream os = - new ByteArrayOutputStream(); - favicon.compress(Bitmap.CompressFormat.PNG, 100, os); - values.put(Browser.BookmarkColumns.FAVICON, - os.toByteArray()); - do { - cr.update(ContentUris.withAppendedId( - Browser.BOOKMARKS_URI, c.getInt(0)), - values, null, null); - } while (c.moveToNext()); - } - c.close(); - return null; - } - }.execute(); + public BrowserBookmarksAdapter(Context context) { + // Make sure to tell the CursorAdapter to avoid the observer and auto-requery + // since the Loader will do that for us. + super(context, R.layout.bookmark_thumbnail, null); } - /* package */ static Cursor queryBookmarksForUrl(ContentResolver cr, - String originalUrl, String url, boolean onlyBookmarks) { - if (cr == null || url == null) { - return null; - } - - // If originalUrl is null, just set it to url. - if (originalUrl == null) { - originalUrl = url; - } - - // Look for both the original url and the actual url. This takes in to - // account redirects. - String originalUrlNoQuery = removeQuery(originalUrl); - String urlNoQuery = removeQuery(url); - originalUrl = originalUrlNoQuery + '?'; - url = urlNoQuery + '?'; - - // Use NoQuery to search for the base url (i.e. if the url is - // http://www.yahoo.com/?rs=1, search for http://www.yahoo.com) - // Use url to match the base url with other queries (i.e. if the url is - // http://www.google.com/m, search for - // http://www.google.com/m?some_query) - final String[] selArgs = new String[] { - originalUrlNoQuery, urlNoQuery, originalUrl, url }; - String where = BookmarkColumns.URL + " == ? OR " - + BookmarkColumns.URL + " == ? OR " - + BookmarkColumns.URL + " LIKE ? || '%' OR " - + BookmarkColumns.URL + " LIKE ? || '%'"; - if (onlyBookmarks) { - where = "(" + where + ") AND " + BookmarkColumns.BOOKMARK + " == 1"; - } - final String[] projection = - new String[] { Browser.BookmarkColumns._ID }; - return cr.query(Browser.BOOKMARKS_URI, projection, where, selArgs, - null); - } + @Override + public void bindView(View view, Context context, Cursor cursor) { + View holder = view.findViewById(R.id.holder); + ImageView thumb = (ImageView) view.findViewById(R.id.thumb); + TextView tv = (TextView) view.findViewById(R.id.label); - // Strip the query from the given url. - private static String removeQuery(String url) { - if (url == null) { - return null; - } - int query = url.indexOf('?'); - String noQuery = url; - if (query != -1) { - noQuery = url.substring(0, query); - } - return noQuery; - } - - /** - * How many items should be displayed in the list. - * @return Count of items. - */ - public int getCount() { - if (mDataValid) { - return mCount; - } else { - return 0; - } - } - - public boolean areAllItemsEnabled() { - return true; - } - - public boolean isEnabled(int position) { - return true; - } - - /** - * Get the data associated with the specified position in the list. - * @param position Index of the item whose data we want. - * @return The data at the specified position. - */ - public Object getItem(int position) { - return null; - } + holder.setVisibility(View.GONE); + tv.setText(cursor.getString(BookmarksLoader.COLUMN_INDEX_TITLE)); - /** - * Get the row id associated with the specified position in the list. - * @param position Index of the item whose row id we want. - * @return The id of the item at the specified position. - */ - public long getItemId(int position) { - return position; - } - - /* package */ void switchViewMode(BookmarkViewMode viewMode) { - mViewMode = viewMode; - } - - /* package */ void populateBookmarkItem(BookmarkItem b, int position) { - mCursor.moveToPosition(position - mExtraOffset); - String url = mCursor.getString(Browser.HISTORY_PROJECTION_URL_INDEX); - b.setUrl(url); - b.setName(mCursor.getString(Browser.HISTORY_PROJECTION_TITLE_INDEX)); - byte[] data = mCursor.getBlob(Browser.HISTORY_PROJECTION_FAVICON_INDEX); - Bitmap bitmap = null; - if (data == null) { - bitmap = CombinedBookmarkHistoryActivity.getIconListenerSet() - .getFavicon(url); - } else { - bitmap = BitmapFactory.decodeByteArray(data, 0, data.length); - } - b.setFavicon(bitmap); - } - - /** - * Get a View that displays the data at the specified position - * in the list. - * @param position Index of the item whose view we want. - * @return A View corresponding to the data at the specified position. - */ - public View getView(int position, View convertView, ViewGroup parent) { - if (!mDataValid) { - throw new IllegalStateException( - "this should only be called when the cursor is valid"); - } - if (position < 0 || position > mCount) { - throw new AssertionError( - "BrowserBookmarksAdapter tried to get a view out of range"); - } - if (mViewMode == BookmarkViewMode.GRID) { - if (convertView == null || convertView instanceof AddNewBookmark - || convertView instanceof BookmarkItem) { - LayoutInflater factory = LayoutInflater.from(mBookmarksPage); - convertView - = factory.inflate(R.layout.bookmark_thumbnail, null); - } - View holder = convertView.findViewById(R.id.holder); - ImageView thumb = (ImageView) convertView.findViewById(R.id.thumb); - TextView tv = (TextView) convertView.findViewById(R.id.label); - - if (0 == position && mNeedsOffset) { - // This is to create a bookmark for the current page. - holder.setVisibility(View.VISIBLE); - tv.setText(mCurrentTitle); - - if (mCurrentThumbnail != null) { - thumb.setImageBitmap(mCurrentThumbnail); - } else { - thumb.setImageResource( - R.drawable.browser_thumbnail); - } - return convertView; - } - holder.setVisibility(View.GONE); - mCursor.moveToPosition(position - mExtraOffset); - tv.setText(mCursor.getString( - Browser.HISTORY_PROJECTION_TITLE_INDEX)); - Bitmap thumbnail = getScreenshot(position); - if (thumbnail == null) { - thumb.setImageResource(R.drawable.browser_thumbnail); - } else { - thumb.setImageBitmap(thumbnail); - } - - return convertView; - - } - if (position == 0 && mNeedsOffset) { - AddNewBookmark b; - if (convertView instanceof AddNewBookmark) { - b = (AddNewBookmark) convertView; - } else { - b = new AddNewBookmark(mBookmarksPage); - } - b.setUrl(mCurrentPage); - return b; - } - if (mMostVisited) { - if (convertView == null || !(convertView instanceof HistoryItem)) { - convertView = new HistoryItem(mBookmarksPage); - } - } else { - if (convertView == null || !(convertView instanceof BookmarkItem)) { - convertView = new BookmarkItem(mBookmarksPage); - } - } - bind((BookmarkItem) convertView, position); - if (mMostVisited) { - ((HistoryItem) convertView).setIsBookmark( - getIsBookmark(position)); - } - return convertView; - } - - /** - * Return the title for this item in the list. - */ - public String getTitle(int position) { - return getString(Browser.HISTORY_PROJECTION_TITLE_INDEX, position); - } - - /** - * Return the Url for this item in the list. - */ - public String getUrl(int position) { - return getString(Browser.HISTORY_PROJECTION_URL_INDEX, position); - } - - /** - * Return the screenshot for this item in the list. - */ - public Bitmap getScreenshot(int position) { - return getBitmap(Browser.HISTORY_PROJECTION_THUMBNAIL_INDEX, position); - } - - /** - * Return the favicon for this item in the list. - */ - public Bitmap getFavicon(int position) { - return getBitmap(Browser.HISTORY_PROJECTION_FAVICON_INDEX, position); - } - - public Bitmap getTouchIcon(int position) { - return getBitmap(Browser.HISTORY_PROJECTION_TOUCH_ICON_INDEX, position); - } - - private Bitmap getBitmap(int cursorIndex, int position) { - if (position < mExtraOffset || position > mCount) { - return null; - } - mCursor.moveToPosition(position - mExtraOffset); - byte[] data = mCursor.getBlob(cursorIndex); - if (data == null) { - return null; - } - return BitmapFactory.decodeByteArray(data, 0, data.length); - } - - /** - * Return whether or not this item represents a bookmarked site. - */ - public boolean getIsBookmark(int position) { - if (position < mExtraOffset || position > mCount) { - return false; - } - mCursor.moveToPosition(position - mExtraOffset); - return (1 == mCursor.getInt(Browser.HISTORY_PROJECTION_BOOKMARK_INDEX)); - } - - /** - * Private helper function to return the title or url. - */ - private String getString(int cursorIndex, int position) { - if (position < mExtraOffset || position > mCount) { - return ""; - } - mCursor.moveToPosition(position- mExtraOffset); - return mCursor.getString(cursorIndex); - } - - private void bind(BookmarkItem b, int position) { - mCursor.moveToPosition(position- mExtraOffset); - - b.setName(mCursor.getString(Browser.HISTORY_PROJECTION_TITLE_INDEX)); - String url = mCursor.getString(Browser.HISTORY_PROJECTION_URL_INDEX); - b.setUrl(url); - byte[] data = mCursor.getBlob(Browser.HISTORY_PROJECTION_FAVICON_INDEX); + Bitmap thumbnail = null; + byte[] data = cursor.getBlob(BookmarksLoader.COLUMN_INDEX_THUMBNAIL); if (data != null) { - b.setFavicon(BitmapFactory.decodeByteArray(data, 0, data.length)); - } else { - b.setFavicon(CombinedBookmarkHistoryActivity.getIconListenerSet() - .getFavicon(url)); + thumbnail = BitmapFactory.decodeByteArray(data, 0, data.length); } - } - private class ChangeObserver extends ContentObserver { - public ChangeObserver() { - super(new Handler(Looper.getMainLooper())); - } - - @Override - public boolean deliverSelfNotifications() { - return true; - } - - @Override - public void onChange(boolean selfChange) { - refreshList(); - } - } - - private class MyDataSetObserver extends DataSetObserver { - @Override - public void onChanged() { - mDataValid = true; - notifyDataSetChanged(); - } - - @Override - public void onInvalidated() { - mDataValid = false; - notifyDataSetInvalidated(); + if (thumbnail == null) { + thumb.setImageResource(R.drawable.browser_thumbnail); + } else { + thumb.setImageBitmap(thumbnail); } } } diff --git a/src/com/android/browser/BrowserBookmarksPage.java b/src/com/android/browser/BrowserBookmarksPage.java index 4d5c5fa..40fd1e1 100644 --- a/src/com/android/browser/BrowserBookmarksPage.java +++ b/src/com/android/browser/BrowserBookmarksPage.java @@ -16,60 +16,115 @@ package com.android.browser; +import com.android.browser.provider.BrowserContract; + import android.app.Activity; import android.app.AlertDialog; +import android.app.LoaderManager; import android.content.ClipboardManager; +import android.content.ClippedData; +import android.content.ContentUris; +import android.content.ContentValues; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; -import android.content.SharedPreferences; -import android.content.SharedPreferences.Editor; +import android.content.Loader; +import android.database.Cursor; import android.graphics.Bitmap; -import android.os.AsyncTask; +import android.graphics.BitmapFactory; +import android.net.Uri; import android.os.Bundle; -import android.os.Handler; -import android.os.Message; -import android.os.ServiceManager; import android.provider.Browser; -import android.util.Log; +import android.util.Pair; import android.view.ContextMenu; -import android.view.Menu; +import android.view.ContextMenu.ContextMenuInfo; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; +import android.view.View.OnClickListener; import android.view.ViewGroup; -import android.view.ContextMenu.ContextMenuInfo; import android.webkit.WebIconDatabase.IconListener; import android.widget.AdapterView; +import android.widget.AdapterView.OnItemClickListener; +import android.widget.Button; import android.widget.GridView; -import android.widget.ListView; import android.widget.Toast; -import android.widget.AdapterView.OnItemClickListener; -/*package*/ enum BookmarkViewMode { NONE, GRID, LIST } +import java.util.Stack; + /** * View showing the user's bookmarks in the browser. */ -public class BrowserBookmarksPage extends Activity implements - View.OnCreateContextMenuListener { - - private BookmarkViewMode mViewMode = BookmarkViewMode.NONE; - private GridView mGridPage; - private ListView mVerticalList; - private BrowserBookmarksAdapter mBookmarksAdapter; - private static final int BOOKMARKS_SAVE = 1; - private boolean mDisableNewWindow; - private BookmarkItem mContextHeader; - private AddNewBookmark mAddHeader; - private boolean mCanceled = false; - private boolean mCreateShortcut; - private boolean mMostVisited; - private View mEmptyView; - private int mIconSize; - - private final static String LOGTAG = "browser"; - private final static String PREF_BOOKMARK_VIEW_MODE = "pref_bookmark_view_mode"; - private final static String PREF_MOST_VISITED_VIEW_MODE = "pref_most_visited_view_mode"; +public class BrowserBookmarksPage extends Activity implements View.OnCreateContextMenuListener, + LoaderManager.LoaderCallbacks<Cursor>, OnItemClickListener, IconListener, OnClickListener { + + static final int BOOKMARKS_SAVE = 1; + static final String LOGTAG = "browser"; + + static final int LOADER_BOOKMARKS = 1; + + GridView mGrid; + BrowserBookmarksAdapter mAdapter; + boolean mDisableNewWindow; + BookmarkItem mContextHeader; + boolean mCanceled = false; + boolean mCreateShortcut; + View mEmptyView; + View mContentView; + Stack<Pair<String, Uri>> mFolderStack = new Stack<Pair<String, Uri>>(); + Button mUpButton; + + @Override + public Loader<Cursor> onCreateLoader(int id, Bundle args) { + switch (id) { + case LOADER_BOOKMARKS: { + int rootFolder = 0; + if (args != null) { + args.getInt(BookmarksLoader.ARG_ROOT_FOLDER, 0); + } + return new BookmarksLoader(this, rootFolder); + } + } + throw new UnsupportedOperationException("Unknown loader id " + id); + } + + @Override + public void onLoadFinished(Loader<Cursor> loader, Cursor data) { + // Set the visibility of the empty vs. content views + if (data == null || data.getCount() == 0) { + mEmptyView.setVisibility(View.VISIBLE); + mContentView.setVisibility(View.GONE); + } else { + mEmptyView.setVisibility(View.GONE); + mContentView.setVisibility(View.VISIBLE); + } + + // Fill in the "up" button if needed + BookmarksLoader bl = (BookmarksLoader) loader; + boolean rootFolder = + (BrowserContract.Bookmarks.CONTENT_URI_DEFAULT_FOLDER.equals(bl.getUri())); + if (rootFolder) { + mUpButton.setText(R.string.defaultBookmarksUpButton); + mUpButton.setEnabled(false); + } else { + mUpButton.setText(mFolderStack.peek().first); + mUpButton.setEnabled(true); + } + + // Give the new data to the adapter + mAdapter.changeCursor(data); + } + + @Override + public void onClick(View view) { + if (view == mUpButton) { + Pair<String, Uri> pair = mFolderStack.pop(); + BookmarksLoader loader = + (BookmarksLoader) ((Loader) getLoaderManager().getLoader(LOADER_BOOKMARKS)); + loader.setUri(pair.second); + loader.forceLoad(); + } + } @Override public boolean onContextItemSelected(MenuItem item) { @@ -86,9 +141,6 @@ public class BrowserBookmarksPage extends Activity implements } switch (item.getItemId()) { - case R.id.new_context_menu_id: - saveCurrentPage(); - break; case R.id.open_context_menu_id: loadUrl(i.position); break; @@ -99,117 +151,88 @@ public class BrowserBookmarksPage extends Activity implements sendBroadcast(createShortcutIntent(i.position)); break; case R.id.delete_context_menu_id: - if (mMostVisited) { - Browser.deleteFromHistory(getContentResolver(), - getUrl(i.position)); - refreshList(); - } else { - displayRemoveBookmarkDialog(i.position); - } + displayRemoveBookmarkDialog(i.position); break; case R.id.new_window_context_menu_id: openInNewWindow(i.position); break; - case R.id.share_link_context_menu_id: + case R.id.share_link_context_menu_id: { + Cursor cursor = (Cursor) mAdapter.getItem(i.position); BrowserActivity.sharePage(BrowserBookmarksPage.this, - mBookmarksAdapter.getTitle(i.position), getUrl(i.position), - getFavicon(i.position), - mBookmarksAdapter.getScreenshot(i.position)); + cursor.getString(BookmarksLoader.COLUMN_INDEX_TITLE), + cursor.getString(BookmarksLoader.COLUMN_INDEX_URL), + getBitmap(cursor, BookmarksLoader.COLUMN_INDEX_FAVICON), + getBitmap(cursor, BookmarksLoader.COLUMN_INDEX_THUMBNAIL)); break; + } case R.id.copy_url_context_menu_id: copy(getUrl(i.position)); break; - case R.id.homepage_context_menu_id: + case R.id.homepage_context_menu_id: { BrowserSettings.getInstance().setHomePage(this, getUrl(i.position)); Toast.makeText(this, R.string.homepage_set, Toast.LENGTH_LONG).show(); break; + } // Only for the Most visited page - case R.id.save_to_bookmarks_menu_id: - boolean isBookmark; - String name; - String url; - if (mViewMode == BookmarkViewMode.GRID) { - isBookmark = mBookmarksAdapter.getIsBookmark(i.position); - name = mBookmarksAdapter.getTitle(i.position); - url = mBookmarksAdapter.getUrl(i.position); - } else { - HistoryItem historyItem = ((HistoryItem) i.targetView); - isBookmark = historyItem.isBookmark(); - name = historyItem.getName(); - url = historyItem.getUrl(); - } + case R.id.save_to_bookmarks_menu_id: { + Cursor cursor = (Cursor) mAdapter.getItem(i.position); + String name = cursor.getString(BookmarksLoader.COLUMN_INDEX_TITLE); + String url = cursor.getString(BookmarksLoader.COLUMN_INDEX_URL); // If the site is bookmarked, the item becomes remove from // bookmarks. - if (isBookmark) { - Bookmarks.removeFromBookmarks(this, getContentResolver(), url, name); - } else { - Browser.saveBookmark(this, name, url); - } + Bookmarks.removeFromBookmarks(this, getContentResolver(), url, name); break; + } default: return super.onContextItemSelected(item); } return true; } + Bitmap getBitmap(Cursor cursor, int columnIndex) { + byte[] data = cursor.getBlob(columnIndex); + if (data == null) { + return null; + } + return BitmapFactory.decodeByteArray(data, 0, data.length); + } + @Override - public void onCreateContextMenu(ContextMenu menu, View v, - ContextMenuInfo menuInfo) { - AdapterView.AdapterContextMenuInfo i = - (AdapterView.AdapterContextMenuInfo) menuInfo; - - MenuInflater inflater = getMenuInflater(); - if (mMostVisited) { - inflater.inflate(R.menu.historycontext, menu); - } else { - inflater.inflate(R.menu.bookmarkscontext, menu); - } + public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { + AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuInfo; + + MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.bookmarkscontext, menu); + + if (mDisableNewWindow) { + menu.findItem(R.id.new_window_context_menu_id).setVisible(false); + } - if (0 == i.position && !mMostVisited) { - menu.setGroupVisible(R.id.CONTEXT_MENU, false); - if (mAddHeader == null) { - mAddHeader = new AddNewBookmark(BrowserBookmarksPage.this); - } else if (mAddHeader.getParent() != null) { - ((ViewGroup) mAddHeader.getParent()). - removeView(mAddHeader); - } - mAddHeader.setUrl(getIntent().getStringExtra("url")); - menu.setHeaderView(mAddHeader); - return; - } - if (mMostVisited) { - if ((mViewMode == BookmarkViewMode.LIST - && ((HistoryItem) i.targetView).isBookmark()) - || mBookmarksAdapter.getIsBookmark(i.position)) { - MenuItem item = menu.findItem( - R.id.save_to_bookmarks_menu_id); - item.setTitle(R.string.remove_from_bookmarks); - } - } else { - // The historycontext menu has no ADD_MENU group. - menu.setGroupVisible(R.id.ADD_MENU, false); - } - if (mDisableNewWindow) { - menu.findItem(R.id.new_window_context_menu_id).setVisible( - false); - } - if (mContextHeader == null) { - mContextHeader = new BookmarkItem(BrowserBookmarksPage.this); - } else if (mContextHeader.getParent() != null) { - ((ViewGroup) mContextHeader.getParent()). - removeView(mContextHeader); - } - if (mViewMode == BookmarkViewMode.GRID) { - mBookmarksAdapter.populateBookmarkItem(mContextHeader, - i.position); - } else { - BookmarkItem b = (BookmarkItem) i.targetView; - b.copyTo(mContextHeader); - } - menu.setHeaderView(mContextHeader); + if (mContextHeader == null) { + mContextHeader = new BookmarkItem(BrowserBookmarksPage.this); + } else if (mContextHeader.getParent() != null) { + ((ViewGroup) mContextHeader.getParent()).removeView(mContextHeader); + } + + populateBookmarkItem(mAdapter, mContextHeader, info.position); + + menu.setHeaderView(mContextHeader); + } + + private void populateBookmarkItem(BrowserBookmarksAdapter adapter, BookmarkItem item, + int position) { + Cursor cursor = (Cursor) mAdapter.getItem(position); + String url = cursor.getString(BookmarksLoader.COLUMN_INDEX_URL); + item.setUrl(url); + item.setName(cursor.getString(BookmarksLoader.COLUMN_INDEX_TITLE)); + Bitmap bitmap = getBitmap(cursor, BookmarksLoader.COLUMN_INDEX_FAVICON); + if (bitmap == null) { + bitmap = CombinedBookmarkHistoryActivity.getIconListenerSet().getFavicon(url); } + item.setFavicon(bitmap); + } /** * Create a new BrowserBookmarksPage. @@ -218,339 +241,126 @@ public class BrowserBookmarksPage extends Activity implements protected void onCreate(Bundle icicle) { super.onCreate(icicle); - // Grab the app icon size as a resource. - mIconSize = getResources().getDimensionPixelSize( - android.R.dimen.app_icon_size); - Intent intent = getIntent(); if (Intent.ACTION_CREATE_SHORTCUT.equals(intent.getAction())) { mCreateShortcut = true; - } - mDisableNewWindow = intent.getBooleanExtra("disable_new_window", - false); - mMostVisited = intent.getBooleanExtra("mostVisited", false); - - if (mCreateShortcut) { setTitle(R.string.browser_bookmarks_page_bookmarks_text); } + mDisableNewWindow = intent.getBooleanExtra("disable_new_window", false); + + setContentView(R.layout.bookmarks); + mEmptyView = findViewById(android.R.id.empty); + mContentView = findViewById(android.R.id.content); + + mGrid = (GridView) findViewById(R.id.grid); + mGrid.setOnItemClickListener(this); + mGrid.setColumnWidth( + BrowserActivity.getDesiredThumbnailWidth(this)); + if (!mCreateShortcut) { + mGrid.setOnCreateContextMenuListener(this); + } - setContentView(R.layout.empty_history); - mEmptyView = findViewById(R.id.empty_view); - mEmptyView.setVisibility(View.GONE); + mUpButton = (Button) findViewById(R.id.up); + mUpButton.setEnabled(false); + mUpButton.setOnClickListener(this); - SharedPreferences p = getPreferences(MODE_PRIVATE); + mAdapter = new BrowserBookmarksAdapter(this); + mGrid.setAdapter(mAdapter); - // See if the user has set a preference for the view mode of their - // bookmarks. Otherwise default to grid mode. - BookmarkViewMode preference = BookmarkViewMode.NONE; - if (mMostVisited) { - // For the most visited page, only use list mode. - preference = BookmarkViewMode.LIST; - } else { - preference = BookmarkViewMode.values()[p.getInt( - PREF_BOOKMARK_VIEW_MODE, BookmarkViewMode.GRID.ordinal())]; - } - switchViewMode(preference); - - final boolean createShortcut = mCreateShortcut; - final boolean mostVisited = mMostVisited; - final String url = intent.getStringExtra("url"); - final String title = intent.getStringExtra("title"); - final Bitmap thumbnail = - (Bitmap) intent.getParcelableExtra("thumbnail"); - new AsyncTask<Void, Void, Void>() { - @Override - protected Void doInBackground(Void... unused) { - BrowserBookmarksAdapter adapter = - new BrowserBookmarksAdapter( - BrowserBookmarksPage.this, - url, - title, - thumbnail, - createShortcut, - mostVisited); - mHandler.obtainMessage(ADAPTER_CREATED, adapter).sendToTarget(); - return null; - } - }.execute(); + // Start the loader for the bookmark data + getLoaderManager().initLoader(LOADER_BOOKMARKS, null, this); + + // Add our own listener in case there are favicons that have yet to be loaded. + CombinedBookmarkHistoryActivity.getIconListenerSet().addListener(this); } @Override - protected void onDestroy() { - mHandler.removeCallbacksAndMessages(null); - super.onDestroy(); + public void onReceivedIcon(String url, Bitmap icon) { + // A new favicon has been loaded, so let anything attached to the adapter know about it + // so new icons will be loaded. + mAdapter.notifyDataSetChanged(); } - /** - * Set the ContentView to be either the grid of thumbnails or the vertical - * list. - */ - private void switchViewMode(BookmarkViewMode viewMode) { - if (mViewMode == viewMode) { + @Override + public void onItemClick(AdapterView parent, View v, int position, long id) { + // It is possible that the view has been canceled when we get to + // this point as back has a higher priority + if (mCanceled) { + android.util.Log.e(LOGTAG, "item clicked when dismissing"); return; } - - mViewMode = viewMode; - - // Update the preferences to make the new view mode sticky. - SharedPreferences preferences = getPreferences(MODE_PRIVATE); - Editor ed = preferences.edit(); - int pref = mViewMode.ordinal(); - if (mMostVisited && preferences.getInt(PREF_MOST_VISITED_VIEW_MODE, -1) != pref) { - ed.putInt(PREF_MOST_VISITED_VIEW_MODE, pref); - } else if (!mMostVisited && preferences.getInt(PREF_BOOKMARK_VIEW_MODE, -1) != pref) { - ed.putInt(PREF_BOOKMARK_VIEW_MODE, pref); + if (mCreateShortcut) { + setResultToParent(RESULT_OK, createShortcutIntent(position)); + finish(); + return; } - ed.commit(); - if (mBookmarksAdapter != null) { - mBookmarksAdapter.switchViewMode(viewMode); - } - if (mViewMode == BookmarkViewMode.GRID) { - if (mGridPage == null) { - mGridPage = new GridView(this); - if (mBookmarksAdapter != null) { - mGridPage.setAdapter(mBookmarksAdapter); - } - mGridPage.setOnItemClickListener(mListener); - mGridPage.setNumColumns(GridView.AUTO_FIT); - mGridPage.setColumnWidth( - BrowserActivity.getDesiredThumbnailWidth(this)); - mGridPage.setFocusable(true); - mGridPage.setFocusableInTouchMode(true); - mGridPage.setSelector(android.R.drawable.gallery_thumb); - float density = getResources().getDisplayMetrics().density; - mGridPage.setVerticalSpacing((int) (14 * density)); - mGridPage.setHorizontalSpacing((int) (8 * density)); - mGridPage.setStretchMode(GridView.STRETCH_SPACING); - mGridPage.setScrollBarStyle(View.SCROLLBARS_INSIDE_INSET); - mGridPage.setDrawSelectorOnTop(true); - if (mMostVisited) { - mGridPage.setEmptyView(mEmptyView); - } - if (!mCreateShortcut) { - mGridPage.setOnCreateContextMenuListener(this); - } - } - addContentView(mGridPage, FULL_SCREEN_PARAMS); - if (mVerticalList != null) { - ViewGroup parent = (ViewGroup) mVerticalList.getParent(); - if (parent != null) { - parent.removeView(mVerticalList); - } - } + Cursor cursor = (Cursor) mAdapter.getItem(position); + boolean isFolder = cursor.getInt(BookmarksLoader.COLUMN_INDEX_IS_FOLDER) != 0; + if (!isFolder) { + loadUrl(position); } else { - if (null == mVerticalList) { - ListView listView = new ListView(this); - if (mBookmarksAdapter != null) { - listView.setAdapter(mBookmarksAdapter); - } - listView.setDrawSelectorOnTop(false); - listView.setVerticalScrollBarEnabled(true); - listView.setOnItemClickListener(mListListener); - if (mMostVisited) { - listView.setEmptyView(mEmptyView); - } - if (!mCreateShortcut) { - listView.setOnCreateContextMenuListener(this); - } - mVerticalList = listView; - } - addContentView(mVerticalList, FULL_SCREEN_PARAMS); - if (mGridPage != null) { - ViewGroup parent = (ViewGroup) mGridPage.getParent(); - if (parent != null) { - parent.removeView(mGridPage); - } - } - } - } - - private static final ViewGroup.LayoutParams FULL_SCREEN_PARAMS - = new ViewGroup.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.MATCH_PARENT); - - private static final int SAVE_CURRENT_PAGE = 1000; - private static final int ADAPTER_CREATED = 1001; - private final Handler mHandler = new Handler() { - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case SAVE_CURRENT_PAGE: - saveCurrentPage(); - break; - case ADAPTER_CREATED: - mBookmarksAdapter = (BrowserBookmarksAdapter) msg.obj; - mBookmarksAdapter.switchViewMode(mViewMode); - if (mGridPage != null) { - mGridPage.setAdapter(mBookmarksAdapter); - } - if (mVerticalList != null) { - mVerticalList.setAdapter(mBookmarksAdapter); - } - // Add our own listener in case there are favicons that - // have yet to be loaded. - if (mMostVisited) { - IconListener listener = new IconListener() { - public void onReceivedIcon(String url, - Bitmap icon) { - if (mGridPage != null) { - mGridPage.setAdapter(mBookmarksAdapter); - } - if (mVerticalList != null) { - mVerticalList.setAdapter(mBookmarksAdapter); - } - } - }; - CombinedBookmarkHistoryActivity.getIconListenerSet() - .addListener(listener); - } - break; - } - } - }; - - private OnItemClickListener mListener = new OnItemClickListener() { - public void onItemClick(AdapterView parent, View v, int position, long id) { - // It is possible that the view has been canceled when we get to - // this point as back has a higher priority - if (mCanceled) { - android.util.Log.e(LOGTAG, "item clicked when dismissing"); - return; - } - if (!mCreateShortcut) { - if (0 == position && !mMostVisited) { - // XXX: Work-around for a framework issue. - mHandler.sendEmptyMessage(SAVE_CURRENT_PAGE); - } else { - loadUrl(position); - } + String title; + if (mFolderStack.size() != 0) { + title = cursor.getString(BookmarksLoader.COLUMN_INDEX_TITLE); } else { - setResultToParent(RESULT_OK, createShortcutIntent(position)); - finish(); + // TODO localize + title = "Bookmarks"; } + LoaderManager manager = getLoaderManager(); + BookmarksLoader loader = + (BookmarksLoader) ((Loader) manager.getLoader(LOADER_BOOKMARKS)); + mFolderStack.push(new Pair(title, loader.getUri())); + Uri uri = ContentUris.withAppendedId( + BrowserContract.Bookmarks.CONTENT_URI_DEFAULT_FOLDER, id); + loader.setUri(uri); + loader.forceLoad(); } - }; - - private OnItemClickListener mListListener = new OnItemClickListener() { - public void onItemClick(AdapterView parent, View v, int position, long id) { - // It is possible that the view has been canceled when we get to - // this point as back has a higher priority - if (mCanceled) { - android.util.Log.e(LOGTAG, "item clicked when dismissing"); - return; - } - if (!mCreateShortcut) { - if (0 == position && !mMostVisited) { - // XXX: Work-around for a framework issue. - mHandler.sendEmptyMessage(SAVE_CURRENT_PAGE); - } else { - loadUrl(position); - } - } else { - setResultToParent(RESULT_OK, createShortcutIntent(position)); - finish(); - } - } - }; + } private Intent createShortcutIntent(int position) { - String url = getUrl(position); - String title = getBookmarkTitle(position); - Bitmap touchIcon = getTouchIcon(position); - Bitmap favicon = getFavicon(position); + Cursor cursor = (Cursor) mAdapter.getItem(position); + String url = cursor.getString(BookmarksLoader.COLUMN_INDEX_URL); + String title = cursor.getString(BookmarksLoader.COLUMN_INDEX_URL); + Bitmap touchIcon = getBitmap(cursor, BookmarksLoader.COLUMN_INDEX_TOUCH_ICON); + Bitmap favicon = getBitmap(cursor, BookmarksLoader.COLUMN_INDEX_FAVICON); return BookmarkUtils.createAddToHomeIntent(this, url, title, touchIcon, favicon); } - private void saveCurrentPage() { - Intent i = new Intent(BrowserBookmarksPage.this, - AddBookmarkPage.class); - i.putExtras(getIntent()); - startActivityForResult(i, BOOKMARKS_SAVE); - } - private void loadUrl(int position) { - Intent intent = (new Intent()).setAction(getUrl(position)); + Cursor cursor = (Cursor) mAdapter.getItem(position); + Intent intent = new Intent(cursor.getString(BookmarksLoader.COLUMN_INDEX_URL)); setResultToParent(RESULT_OK, intent); finish(); } - @Override - public boolean onCreateOptionsMenu(Menu menu) { - boolean result = super.onCreateOptionsMenu(menu); - if (!mCreateShortcut && !mMostVisited) { - MenuInflater inflater = getMenuInflater(); - inflater.inflate(R.menu.bookmarks, menu); - return true; - } - return result; - } - - @Override - public boolean onPrepareOptionsMenu(Menu menu) { - boolean result = super.onPrepareOptionsMenu(menu); - if (mCreateShortcut || mMostVisited || mBookmarksAdapter == null - || mBookmarksAdapter.getCount() == 0) { - // No need to show the menu if there are no items. - return result; - } - MenuItem switchItem = menu.findItem(R.id.switch_mode_menu_id); - int titleResId; - int iconResId; - if (mViewMode == BookmarkViewMode.GRID) { - titleResId = R.string.switch_to_list; - iconResId = R.drawable.ic_menu_list; - } else { - titleResId = R.string.switch_to_thumbnails; - iconResId = R.drawable.ic_menu_thumbnail; - } - switchItem.setTitle(titleResId); - switchItem.setIcon(iconResId); - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case R.id.new_context_menu_id: - saveCurrentPage(); - break; - - case R.id.switch_mode_menu_id: - if (mViewMode == BookmarkViewMode.GRID) { - switchViewMode(BookmarkViewMode.LIST); - } else { - switchViewMode(BookmarkViewMode.GRID); - } - break; - - default: - return super.onOptionsItemSelected(item); - } - return true; - } - private void openInNewWindow(int position) { Bundle b = new Bundle(); b.putBoolean("new_window", true); - setResultToParent(RESULT_OK, - (new Intent()).setAction(getUrl(position)).putExtras(b)); - + setResultToParent(RESULT_OK, (new Intent(getUrl(position))).putExtras(b)); finish(); } - private void editBookmark(int position) { - Intent intent = new Intent(BrowserBookmarksPage.this, - AddBookmarkPage.class); - intent.putExtra("bookmark", getRow(position)); + Intent intent = new Intent(this, AddBookmarkPage.class); + Cursor cursor = (Cursor) mAdapter.getItem(position); + Bundle item = new Bundle(); + item.putString(Browser.BookmarkColumns.TITLE, + cursor.getString(BookmarksLoader.COLUMN_INDEX_TITLE)); + item.putString(Browser.BookmarkColumns.URL, + cursor.getString(BookmarksLoader.COLUMN_INDEX_URL)); + byte[] data = cursor.getBlob(BookmarksLoader.COLUMN_INDEX_FAVICON); + if (data != null) { + item.putParcelable(Browser.BookmarkColumns.FAVICON, + BitmapFactory.decodeByteArray(data, 0, data.length)); + } + item.putInt("id", cursor.getInt(BookmarksLoader.COLUMN_INDEX_ID)); + intent.putExtra("bookmark", item); startActivityForResult(intent, BOOKMARKS_SAVE); } @Override - protected void onActivityResult(int requestCode, int resultCode, - Intent data) { + protected void onActivityResult(int requestCode, int resultCode, Intent data) { switch(requestCode) { case BOOKMARKS_SAVE: if (resultCode == RESULT_OK) { @@ -561,92 +371,93 @@ public class BrowserBookmarksPage extends Activity implements String title = extras.getString("title"); String url = extras.getString("url"); if (title != null && url != null) { - mBookmarksAdapter.updateRow(extras); + updateRow(extras); } - } else { - // extras == null then a new bookmark was added to - // the database. - refreshList(); } } break; - default: + } + } + + /** + * Update a row in the database with new information. + * Requeries the database if the information has changed. + * @param map Bundle storing id, title and url of new information + */ + public void updateRow(Bundle map) { + + // Find the record + int id = map.getInt("id"); + int position = -1; + Cursor cursor = mAdapter.getCursor(); + for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) { + if (cursor.getInt(BookmarksLoader.COLUMN_INDEX_ID) == id) { + position = cursor.getPosition(); break; + } + } + if (position < 0) { + return; + } + + cursor.moveToPosition(position); + ContentValues values = new ContentValues(); + String title = map.getString(Browser.BookmarkColumns.TITLE); + if (!title.equals(cursor.getString(Browser.HISTORY_PROJECTION_TITLE_INDEX))) { + values.put(Browser.BookmarkColumns.TITLE, title); + } + String url = map.getString(Browser.BookmarkColumns.URL); + if (!url.equals(cursor.getString(Browser.HISTORY_PROJECTION_URL_INDEX))) { + values.put(Browser.BookmarkColumns.URL, url); + } + + if (map.getBoolean("invalidateThumbnail") == true) { + values.put(Browser.BookmarkColumns.THUMBNAIL, new byte[0]); + } + + if (values.size() > 0) { + getContentResolver().update(Browser.BOOKMARKS_URI, values, + "_id = ?", new String[] { Integer.toString(id) }); } } - private void displayRemoveBookmarkDialog(int position) { + private void displayRemoveBookmarkDialog(final int position) { // Put up a dialog asking if the user really wants to // delete the bookmark - final int deletePos = position; + Cursor cursor = (Cursor) mAdapter.getItem(position); new AlertDialog.Builder(this) .setTitle(R.string.delete_bookmark) .setIcon(android.R.drawable.ic_dialog_alert) .setMessage(getText(R.string.delete_bookmark_warning).toString().replace( - "%s", getBookmarkTitle(deletePos))) + "%s", cursor.getString(BookmarksLoader.COLUMN_INDEX_TITLE))) .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { - deleteBookmark(deletePos); + deleteBookmark(position); } }) .setNegativeButton(R.string.cancel, null) .show(); } - /** - * Refresh the shown list after the database has changed. - */ - private void refreshList() { - if (mBookmarksAdapter == null) return; - mBookmarksAdapter.refreshList(); + private String getUrl(int position) { + Cursor cursor = (Cursor) mAdapter.getItem(position); + return cursor.getString(BookmarksLoader.COLUMN_INDEX_URL); } - - /** - * Return a hashmap representing the currently highlighted row. - */ - public Bundle getRow(int position) { - return mBookmarksAdapter == null ? null - : mBookmarksAdapter.getRow(position); - } - - /** - * Return the url of the currently highlighted row. - */ - public String getUrl(int position) { - return mBookmarksAdapter == null ? null - : mBookmarksAdapter.getUrl(position); - } - - /** - * Return the favicon of the currently highlighted row. - */ - public Bitmap getFavicon(int position) { - return mBookmarksAdapter == null ? null - : mBookmarksAdapter.getFavicon(position); - } - - private Bitmap getTouchIcon(int position) { - return mBookmarksAdapter == null ? null - : mBookmarksAdapter.getTouchIcon(position); - } - + private void copy(CharSequence text) { - ClipboardManager cm = (ClipboardManager)getSystemService(Context.CLIPBOARD_SERVICE); - cm.setText(text); - } - - public String getBookmarkTitle(int position) { - return mBookmarksAdapter == null ? null - : mBookmarksAdapter.getTitle(position); + ClipboardManager cm = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE); + cm.setPrimaryClip(new ClippedData(null, null, new ClippedData.Item(text))); } /** * Delete the currently highlighted row. */ public void deleteBookmark(int position) { - if (mBookmarksAdapter == null) return; - mBookmarksAdapter.deleteRow(position); + Cursor cursor = (Cursor) mAdapter.getItem(position); + String url = cursor.getString(BookmarksLoader.COLUMN_INDEX_URL); + String title = cursor.getString(BookmarksLoader.COLUMN_INDEX_TITLE); + Bookmarks.removeFromBookmarks(null, getContentResolver(), url, title); } @Override diff --git a/src/com/android/browser/BrowserProvider.java b/src/com/android/browser/BrowserProvider.java index 36cc490..87c38e2 100644 --- a/src/com/android/browser/BrowserProvider.java +++ b/src/com/android/browser/BrowserProvider.java @@ -27,8 +27,8 @@ import android.content.ContentValues; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; -import android.content.UriMatcher; import android.content.SharedPreferences.Editor; +import android.content.UriMatcher; import android.content.res.Configuration; import android.database.AbstractCursor; import android.database.ContentObserver; @@ -41,8 +41,8 @@ import android.os.Handler; import android.os.Process; import android.preference.PreferenceManager; import android.provider.Browser; -import android.provider.Settings; import android.provider.Browser.BookmarkColumns; +import android.provider.Settings; import android.speech.RecognizerResultsIntent; import android.text.TextUtils; import android.util.Log; diff --git a/src/com/android/browser/CombinedBookmarkHistoryActivity.java b/src/com/android/browser/CombinedBookmarkHistoryActivity.java index 64e8673..78fcb4b 100644 --- a/src/com/android/browser/CombinedBookmarkHistoryActivity.java +++ b/src/com/android/browser/CombinedBookmarkHistoryActivity.java @@ -54,7 +54,6 @@ public class CombinedBookmarkHistoryActivity extends TabActivity private boolean mNewTabMode; /* package */ static String BOOKMARKS_TAB = "bookmark"; - /* package */ static String VISITED_TAB = "visited"; /* package */ static String HISTORY_TAB = "history"; /* package */ static String STARTING_TAB = "tab"; @@ -117,15 +116,6 @@ public class CombinedBookmarkHistoryActivity extends TabActivity createTab(bookmarksIntent, R.string.tab_bookmarks, R.drawable.browser_bookmark_tab, BOOKMARKS_TAB); - Intent visitedIntent = new Intent(this, BrowserBookmarksPage.class); - // Need to copy extras so the bookmarks activity and this one will be - // different - Bundle visitedExtras = extras == null ? new Bundle() : new Bundle(extras); - visitedExtras.putBoolean("mostVisited", true); - visitedIntent.putExtras(visitedExtras); - createTab(visitedIntent, R.string.tab_most_visited, - R.drawable.browser_visited_tab, VISITED_TAB); - Intent historyIntent = new Intent(this, BrowserHistoryPage.class); String defaultTab = null; if (extras != null) { diff --git a/src/com/android/browser/DownloadTouchIcon.java b/src/com/android/browser/DownloadTouchIcon.java index 765d288..2816f58 100644 --- a/src/com/android/browser/DownloadTouchIcon.java +++ b/src/com/android/browser/DownloadTouchIcon.java @@ -101,7 +101,7 @@ class DownloadTouchIcon extends AsyncTask<String, Void, Void> { @Override public Void doInBackground(String... values) { if (mContentResolver != null) { - mCursor = BrowserBookmarksAdapter.queryBookmarksForUrl(mContentResolver, + mCursor = Bookmarks.queryBookmarksForUrl(mContentResolver, mOriginalUrl, mUrl, true); } diff --git a/src/com/android/browser/Tab.java b/src/com/android/browser/Tab.java index 1d9482b..b45b8cb 100644 --- a/src/com/android/browser/Tab.java +++ b/src/com/android/browser/Tab.java @@ -488,7 +488,7 @@ class Tab { // update the bookmark database for favicon if (favicon != null) { - BrowserBookmarksAdapter.updateBookmarkFavicon(mActivity + Bookmarks.updateBookmarkFavicon(mActivity .getContentResolver(), null, url, favicon); } @@ -1029,7 +1029,7 @@ class Tab { @Override public void onReceivedIcon(WebView view, Bitmap icon) { if (icon != null) { - BrowserBookmarksAdapter.updateBookmarkFavicon(mActivity + Bookmarks.updateBookmarkFavicon(mActivity .getContentResolver(), view.getOriginalUrl(), view .getUrl(), icon); } diff --git a/src/com/android/browser/provider/BrowserContract.java b/src/com/android/browser/provider/BrowserContract.java new file mode 100644 index 0000000..1c31c85 --- /dev/null +++ b/src/com/android/browser/provider/BrowserContract.java @@ -0,0 +1,367 @@ +/* + * Copyright (C) 2010 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.provider; + +import android.accounts.Account; +import android.content.ContentProviderClient; +import android.content.ContentProviderOperation; +import android.content.ContentResolver; +import android.content.ContentUris; +import android.graphics.BitmapFactory; +import android.net.Uri; +import android.os.RemoteException; +import android.provider.SyncStateContract; +import android.util.Pair; + +public class BrowserContract { + /** The authority for the browser provider */ + public static final String AUTHORITY = "com.android.browser"; + + /** A content:// style uri to the authority for the browser provider */ + public static final Uri AUTHORITY_URI = Uri.parse("content://" + AUTHORITY); + + /** + * An optional insert, update or delete URI parameter that allows the caller + * to specify that it is a sync adapter. The default value is false. If true + * the dirty flag is not automatically set and the "syncToNetwork" parameter + * is set to false when calling + * {@link ContentResolver#notifyChange(android.net.Uri, android.database.ContentObserver, boolean)}. + */ + public static final String CALLER_IS_SYNCADAPTER = "caller_is_syncadapter"; + + /** + * Generic columns for use by sync adapters. The specific functions of + * these columns are private to the sync adapter. Other clients of the API + * should not attempt to either read or write these columns. + */ + interface BaseSyncColumns { + /** Generic column for use by sync adapters. */ + public static final String SYNC1 = "sync1"; + /** Generic column for use by sync adapters. */ + public static final String SYNC2 = "sync2"; + /** Generic column for use by sync adapters. */ + public static final String SYNC3 = "sync3"; + /** Generic column for use by sync adapters. */ + public static final String SYNC4 = "sync4"; + /** Generic column for use by sync adapters. */ + public static final String SYNC5 = "sync5"; + } + + /** + * Columns that appear when each row of a table belongs to a specific + * account, including sync information that an account may need. + */ + interface SyncColumns extends BaseSyncColumns { + /** + * The name of the account instance to which this row belongs, which when paired with + * {@link #ACCOUNT_TYPE} identifies a specific account. + * <P>Type: TEXT</P> + */ + public static final String ACCOUNT_NAME = "account_name"; + + /** + * The type of account to which this row belongs, which when paired with + * {@link #ACCOUNT_NAME} identifies a specific account. + * <P>Type: TEXT</P> + */ + public static final String ACCOUNT_TYPE = "account_type"; + + /** + * String that uniquely identifies this row to its source account. + * <P>Type: TEXT</P> + */ + public static final String SOURCE_ID = "sourceid"; + + /** + * Version number that is updated whenever this row or its related data + * changes. + * <P>Type: INTEGER</P> + */ + public static final String VERSION = "version"; + + /** + * Flag indicating that {@link #VERSION} has changed, and this row needs + * to be synchronized by its owning account. + * <P>Type: INTEGER (boolean)</P> + */ + public static final String DIRTY = "dirty"; + } + + interface BookmarkColumns { + /** + * The unique ID for a row. + * <P>Type: INTEGER (long)</P> + */ + public static final String _ID = "_id"; + + /** + * The URL of the bookmark. + * <P>Type: TEXT (URL)</P> + */ + public static final String URL = "url"; + + /** + * The user visible title of the bookmark. + * <P>Type: TEXT</P> + */ + public static final String TITLE = "title"; + + /** + * The favicon of the bookmark, may be NULL. + * Must decode via {@link BitmapFactory#decodeByteArray}. + * <p>Type: BLOB (image)</p> + */ + public static final String FAVICON = "favicon"; + + /** + * A thumbnail of the page,may be NULL. + * Must decode via {@link BitmapFactory#decodeByteArray}. + * <p>Type: BLOB (image)</p> + */ + public static final String THUMBNAIL = "thumbnail"; + + /** + * The touch icon for the web page, may be NULL. + * Must decode via {@link BitmapFactory#decodeByteArray}. + * <p>Type: BLOB (image)</p> + * @hide + */ + public static final String TOUCH_ICON = "touch_icon"; + + /** + * @hide + */ + public static final String USER_ENTERED = "user_entered"; + } + + /** + * The bookmarks table, which holds the user's browser bookmarks. + */ + public static final class Bookmarks implements BookmarkColumns, SyncColumns { + /** + * This utility class cannot be instantiated. + */ + private Bookmarks() {} + + /** + * The content:// style URI for this table + */ + public static final Uri CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI, "bookmarks"); + + /** + * The content:// style URI for the default folder + */ + public static final Uri CONTENT_URI_DEFAULT_FOLDER = + Uri.withAppendedPath(CONTENT_URI, "folder"); + + /** + * Builds a URI that points to a specific folder. + * @param folderId the ID of the folder to point to + */ + public static final Uri buildFolderUri(long folderId) { + return ContentUris.withAppendedId(CONTENT_URI_DEFAULT_FOLDER, folderId); + } + + /** + * The MIME type of {@link #CONTENT_URI} providing a directory of bookmarks. + */ + public static final String CONTENT_TYPE = "vnd.android.cursor.dir/bookmark"; + + /** + * The MIME type of a {@link #CONTENT_URI} of a single bookmark. + */ + public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/bookmark"; + + /** + * Query parameter to use if you want to see deleted bookmarks that are still + * around on the device and haven't been synced yet. + * @see #IS_DELETED + */ + public static final String QUERY_PARAMETER_SHOW_DELETED = "show_deleted"; + + /** + * Flag indicating if an item is a folder or bookmark. Non-zero values indicate + * a folder and zero indicates a bookmark. + * <P>Type: INTEGER (boolean)</P> + */ + public static final String IS_FOLDER = "folder"; + + /** + * The ID of the parent folder. ID 0 is the root folder. + * <P>Type: INTEGER (reference to item in the same table)</P> + */ + public static final String PARENT = "parent"; + + /** + * The position of the bookmark in relation to it's siblings that share the same + * {@link #PARENT}. May be negative. + * <P>Type: INTEGER</P> + */ + public static final String POSITION = "position"; + + /** + * The item that the bookmark should be inserted after. + * May be negative. + * <P>Type: INTEGER</P> + */ + public static final String INSERT_AFTER = "insert_after"; + + /** + * A flag to indicate if an item has been deleted. Queries will not return deleted + * entries unless you add the {@link #QUERY_PARAMETER_SHOW_DELETED} query paramter + * to the URI when performing your query. + * <p>Type: INTEGER (non-zero if the item has been deleted, zero if it hasn't) + * @see #QUERY_PARAMETER_SHOW_DELETED + */ + public static final String IS_DELETED = "deleted"; + } + + /** + * The history table, which holds the browsing history. + */ + public static final class History implements BookmarkColumns { + /** + * This utility class cannot be instantiated. + */ + private History() {} + + /** + * The content:// style URI for this table + */ + public static final Uri CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI, "history"); + + /** + * The MIME type of {@link #CONTENT_URI} providing a directory of browser history items. + */ + public static final String CONTENT_TYPE = "vnd.android.cursor.dir/browser-history"; + + /** + * The MIME type of a {@link #CONTENT_URI} of a single browser history item. + */ + public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/browser-history"; + + /** + * The date the item was last visited, in milliseconds since the epoch. + * <p>Type: INTEGER (date in milliseconds since January 1, 1970)</p> + */ + public static final String DATE_LAST_VISITED = "date"; + + /** + * The date the item created, in milliseconds since the epoch. + * <p>Type: NUMBER (date in milliseconds since January 1, 1970)</p> + */ + public static final String DATE_CREATED = "created"; + + /** + * The number of times the item has been visited. + * <p>Type: INTEGER</p> + */ + public static final String VISITS = "visits"; + } + + /** + * The search history table. + * @hide + */ + public static final class Searches { + private Searches() {} + + /** + * The content:// style URI for this table + */ + public static final Uri CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI, "searches"); + + /** + * The MIME type of {@link #CONTENT_URI} providing a directory of browser search items. + */ + public static final String CONTENT_TYPE = "vnd.android.cursor.dir/searches"; + + /** + * The MIME type of a {@link #CONTENT_URI} of a single browser search item. + */ + public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/searches"; + + /** + * The unique ID for a row. + * <P>Type: INTEGER (long)</P> + */ + public static final String _ID = "_id"; + + /** + * The user entered search term. + */ + public static final String SEARCH = "search"; + + /** + * The date the search was performed, in milliseconds since the epoch. + * <p>Type: NUMBER (date in milliseconds since January 1, 1970)</p> + */ + public static final String DATE = "date"; + } + + /** + * A table provided for sync adapters to use for storing private sync state data. + * + * @see SyncStateContract + */ + public static final class SyncState implements SyncStateContract.Columns { + /** + * This utility class cannot be instantiated + */ + private SyncState() {} + + public static final String CONTENT_DIRECTORY = + SyncStateContract.Constants.CONTENT_DIRECTORY; + + /** + * The content:// style URI for this table + */ + public static final Uri CONTENT_URI = + Uri.withAppendedPath(AUTHORITY_URI, CONTENT_DIRECTORY); + + /** + * @see android.provider.SyncStateContract.Helpers#get + */ + public static byte[] get(ContentProviderClient provider, Account account) + throws RemoteException { + return SyncStateContract.Helpers.get(provider, CONTENT_URI, account); + } + + /** + * @see android.provider.SyncStateContract.Helpers#get + */ + public static Pair<Uri, byte[]> getWithUri(ContentProviderClient provider, Account account) + throws RemoteException { + return SyncStateContract.Helpers.getWithUri(provider, CONTENT_URI, account); + } + + /** + * @see android.provider.SyncStateContract.Helpers#set + */ + public static void set(ContentProviderClient provider, Account account, byte[] data) + throws RemoteException { + SyncStateContract.Helpers.set(provider, CONTENT_URI, account, data); + } + + /** + * @see android.provider.SyncStateContract.Helpers#newSetOperation + */ + public static ContentProviderOperation newSetOperation(Account account, byte[] data) { + return SyncStateContract.Helpers.newSetOperation(CONTENT_URI, account, data); + } + } +} diff --git a/src/com/android/browser/provider/BrowserProvider2.java b/src/com/android/browser/provider/BrowserProvider2.java new file mode 100644 index 0000000..8392404 --- /dev/null +++ b/src/com/android/browser/provider/BrowserProvider2.java @@ -0,0 +1,725 @@ +/* + * Copyright (C) 2010 he 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.provider; + +import com.android.browser.R; +import com.android.browser.provider.BrowserContract.Bookmarks; +import com.android.browser.provider.BrowserContract.History; +import com.android.browser.provider.BrowserContract.Searches; +import com.android.browser.provider.BrowserContract.SyncState; +import com.android.internal.content.SyncStateContentProviderHelper; + +import android.content.ContentResolver; +import android.content.ContentUris; +import android.content.ContentValues; +import android.content.Context; +import android.content.UriMatcher; +import android.database.Cursor; +import android.database.DatabaseUtils; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; +import android.database.sqlite.SQLiteQueryBuilder; +import android.net.Uri; +import android.provider.ContactsContract.RawContacts; +import android.provider.SyncStateContract; +import android.text.TextUtils; + +import java.util.HashMap; + +public class BrowserProvider2 extends SQLiteContentProvider { + + static final Uri LEGACY_BROWSER_AUTHORITY_URI = Uri.parse("browser"); + + static final String TABLE_BOOKMARKS = "bookmarks"; + static final String TABLE_HISTORY = "history"; + static final String TABLE_SEARCHES = "searches"; + static final String TABLE_SYNC_STATE = "syncstate"; + + static final String HISTORY_JOIN_BOOKMARKS = + "history LEFT OUTER JOIN bookmarks ON (history.url = bookmarks.url)"; + + static final int BOOKMARKS = 1000; + static final int BOOKMARKS_ID = 1001; + static final int BOOKMARKS_FOLDER = 1002; + static final int BOOKMARKS_FOLDER_ID = 1003; + + static final int HISTORY = 2000; + static final int HISTORY_ID = 2001; + + static final int SEARCHES = 3000; + static final int SEARCHES_ID = 3001; + + static final int SYNCSTATE = 4000; + static final int SYNCSTATE_ID = 4001; + + static final long FIXED_ID_BOOKMARKS = 1; + static final long FIXED_ID_BOOKMARKS_BAR = 2; + static final long FIXED_ID_OTHER_BOOKMARKS = 3; + + static final String DEFAULT_BOOKMARKS_SORT_ORDER = "position ASC, _id ASC"; + + static final UriMatcher URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH); + + static final HashMap<String, String> BOOKMARKS_PROJECTION_MAP = new HashMap<String, String>(); + static final HashMap<String, String> OTHER_BOOKMARKS_PROJECTION_MAP = new HashMap<String, String>(); + static final HashMap<String, String> HISTORY_PROJECTION_MAP = new HashMap<String, String>(); + static final HashMap<String, String> SEARCHES_PROJECTION_MAP = new HashMap<String, String>(); + static final HashMap<String, String> SYNC_STATE_PROJECTION_MAP = new HashMap<String, String>(); + + static { + final UriMatcher matcher = URI_MATCHER; + matcher.addURI(BrowserContract.AUTHORITY, "bookmarks", BOOKMARKS); + matcher.addURI(BrowserContract.AUTHORITY, "bookmarks/#", BOOKMARKS_ID); + matcher.addURI(BrowserContract.AUTHORITY, "bookmarks/folder", BOOKMARKS_FOLDER); + matcher.addURI(BrowserContract.AUTHORITY, "bookmarks/folder/#", BOOKMARKS_FOLDER_ID); + matcher.addURI(BrowserContract.AUTHORITY, "history", HISTORY); + matcher.addURI(BrowserContract.AUTHORITY, "history/#", HISTORY_ID); + matcher.addURI(BrowserContract.AUTHORITY, "searches", SEARCHES); + matcher.addURI(BrowserContract.AUTHORITY, "searches/#", SEARCHES_ID); + matcher.addURI(BrowserContract.AUTHORITY, "syncstate", SYNCSTATE); + matcher.addURI(BrowserContract.AUTHORITY, "syncstate/#", SYNCSTATE_ID); + + // Common BookmarkColumns + HashMap<String, String> bookmarksColumns = new HashMap(); + bookmarksColumns.put(Bookmarks.TITLE, Bookmarks.TITLE); + bookmarksColumns.put(Bookmarks.URL, Bookmarks.URL); + bookmarksColumns.put(Bookmarks.FAVICON, Bookmarks.FAVICON); + bookmarksColumns.put(Bookmarks.THUMBNAIL, Bookmarks.THUMBNAIL); + bookmarksColumns.put(Bookmarks.TOUCH_ICON, Bookmarks.TOUCH_ICON); + bookmarksColumns.put(Bookmarks.USER_ENTERED, Bookmarks.USER_ENTERED); + + // Bookmarks + HashMap<String, String> map = BOOKMARKS_PROJECTION_MAP; + map.putAll(bookmarksColumns); + map.put(Bookmarks._ID, TABLE_BOOKMARKS + "._id AS _id"); + map.put(Bookmarks.IS_FOLDER, Bookmarks.IS_FOLDER); + map.put(Bookmarks.PARENT, Bookmarks.PARENT); + map.put(Bookmarks.POSITION, Bookmarks.POSITION); + map.put(Bookmarks.IS_DELETED, Bookmarks.IS_DELETED); + map.put(Bookmarks.ACCOUNT_NAME, Bookmarks.ACCOUNT_NAME); + map.put(Bookmarks.ACCOUNT_TYPE, Bookmarks.ACCOUNT_TYPE); + map.put(Bookmarks.SOURCE_ID, Bookmarks.SOURCE_ID); + map.put(Bookmarks.VERSION, Bookmarks.VERSION); + map.put(Bookmarks.DIRTY, Bookmarks.DIRTY); + map.put(Bookmarks.SYNC1, Bookmarks.SYNC1); + map.put(Bookmarks.SYNC2, Bookmarks.SYNC2); + map.put(Bookmarks.SYNC3, Bookmarks.SYNC3); + map.put(Bookmarks.SYNC4, Bookmarks.SYNC4); + map.put(Bookmarks.SYNC5, Bookmarks.SYNC5); + + // Other bookmarks + OTHER_BOOKMARKS_PROJECTION_MAP.putAll(BOOKMARKS_PROJECTION_MAP); + OTHER_BOOKMARKS_PROJECTION_MAP.put(Bookmarks.POSITION, + Long.toString(Long.MAX_VALUE) + " AS " + Bookmarks.POSITION); + + // History + map = HISTORY_PROJECTION_MAP; + map.putAll(bookmarksColumns); + map.put(History._ID, TABLE_HISTORY + "._id AS _id"); + map.put(History.DATE_CREATED, History.DATE_CREATED); + map.put(History.DATE_LAST_VISITED, History.DATE_LAST_VISITED); + map.put(History.VISITS, History.VISITS); + + // Sync state + map = SYNC_STATE_PROJECTION_MAP; + map.put(SyncState._ID, SyncState._ID); + map.put(SyncState.ACCOUNT_NAME, SyncState.ACCOUNT_NAME); + map.put(SyncState.ACCOUNT_TYPE, SyncState.ACCOUNT_TYPE); + map.put(SyncState.DATA, SyncState.DATA); + } + + DatabaseHelper mOpenHelper; + SyncStateContentProviderHelper mSyncHelper = new SyncStateContentProviderHelper(); + + final class DatabaseHelper extends SQLiteOpenHelper { + static final String DATABASE_NAME = "browser2.db"; + static final int DATABASE_VERSION = 10; + public DatabaseHelper(Context context) { + super(context, DATABASE_NAME, null, DATABASE_VERSION); + } + + @Override + public void onCreate(SQLiteDatabase db) { + db.execSQL("CREATE TABLE " + TABLE_BOOKMARKS + "(" + + Bookmarks._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," + + Bookmarks.TITLE + " TEXT," + + Bookmarks.URL + " TEXT," + + Bookmarks.FAVICON + " BLOB," + + Bookmarks.THUMBNAIL + " BLOB," + + Bookmarks.TOUCH_ICON + " BLOB," + + Bookmarks.USER_ENTERED + " INTEGER," + + Bookmarks.IS_FOLDER + " INTEGER NOT NULL DEFAULT 0," + + Bookmarks.PARENT + " INTEGER NOT NULL DEFAULT 0," + + Bookmarks.POSITION + " INTEGER NOT NULL," + + Bookmarks.INSERT_AFTER + " INTEGER," + + Bookmarks.IS_DELETED + " INTEGER NOT NULL DEFAULT 0," + + Bookmarks.ACCOUNT_NAME + " TEXT," + + Bookmarks.ACCOUNT_TYPE + " TEXT," + + Bookmarks.SOURCE_ID + " TEXT," + + Bookmarks.VERSION + " INTEGER NOT NULL DEFAULT 1," + + Bookmarks.DIRTY + " INTEGER NOT NULL DEFAULT 0," + + Bookmarks.SYNC1 + " TEXT," + + Bookmarks.SYNC2 + " TEXT," + + Bookmarks.SYNC3 + " TEXT," + + Bookmarks.SYNC4 + " TEXT," + + Bookmarks.SYNC5 + " TEXT" + + ");"); + + // TODO indices + + createDefaultBookmarks(db); + + db.execSQL("CREATE TABLE " + TABLE_HISTORY + "(" + + History._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," + + History.URL + " TEXT NOT NULL," + + History.DATE_CREATED + " INTEGER," + + History.DATE_LAST_VISITED + " INTEGER," + + History.VISITS + " INTEGER NOT NULL DEFAULT 0" + + ");"); + + db.execSQL("CREATE TABLE " + TABLE_SEARCHES + " (" + + Searches._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," + + Searches.SEARCH + " TEXT," + + Searches.DATE + " LONG" + + ");"); + + mSyncHelper.createDatabase(db); + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + // TODO write upgrade logic + db.execSQL("DROP TABLE IF EXISTS " + TABLE_BOOKMARKS); + db.execSQL("DROP TABLE IF EXISTS " + TABLE_HISTORY); + db.execSQL("DROP TABLE IF EXISTS " + TABLE_SEARCHES); + onCreate(db); + } + + @Override + public void onOpen(SQLiteDatabase db) { + mSyncHelper.onDatabaseOpened(db); + } + + private void createDefaultBookmarks(SQLiteDatabase db) { + ContentValues values = new ContentValues(); + // TODO figure out how to deal with localization for the defaults + // TODO fill in the server unique tags for the sync adapter + + // Bookmarks folder + values.put(Bookmarks._ID, FIXED_ID_BOOKMARKS); + values.put(Bookmarks.TITLE, "Bookmarks"); + values.put(Bookmarks.PARENT, 0); + values.put(Bookmarks.POSITION, 0); + values.put(Bookmarks.IS_FOLDER, true); + values.put(Bookmarks.DIRTY, true); + long bookmarksId = db.insertOrThrow(TABLE_BOOKMARKS, null, values); + + // Bookmarks Bar folder + values.clear(); + values.put(Bookmarks._ID, FIXED_ID_BOOKMARKS_BAR); + values.put(Bookmarks.TITLE, "Bookmarks Bar"); + values.put(Bookmarks.PARENT, bookmarksId); + values.put(Bookmarks.POSITION, 0); + values.put(Bookmarks.IS_FOLDER, true); + values.put(Bookmarks.DIRTY, true); + db.insertOrThrow(TABLE_BOOKMARKS, null, values); + + // Other Bookmarks folder + values.clear(); + values.put(Bookmarks._ID, FIXED_ID_OTHER_BOOKMARKS); + values.put(Bookmarks.TITLE, "Other Bookmarks"); + values.put(Bookmarks.PARENT, bookmarksId); + values.put(Bookmarks.POSITION, 1000); + values.put(Bookmarks.IS_FOLDER, true); + values.put(Bookmarks.DIRTY, true); + db.insertOrThrow(TABLE_BOOKMARKS, null, values); + + addDefaultBookmarks(db, FIXED_ID_BOOKMARKS_BAR); + + // TODO remove this testing code + db.execSQL("INSERT INTO bookmarks (" + + Bookmarks.TITLE + ", " + + Bookmarks.URL + ", " + + Bookmarks.IS_FOLDER + "," + + Bookmarks.PARENT + "," + + Bookmarks.POSITION + + ") VALUES (" + + "'Google Reader', " + + "'http://reader.google.com', " + + "0," + + Long.toString(FIXED_ID_OTHER_BOOKMARKS) + "," + + 0 + + ");"); + } + + private void addDefaultBookmarks(SQLiteDatabase db, long parentId) { + final CharSequence[] bookmarks = getContext().getResources().getTextArray( + R.array.bookmarks); + int size = bookmarks.length; + try { + for (int i = 0; i < size; i = i + 2) { + CharSequence bookmarkDestination = replaceSystemPropertyInString(getContext(), + bookmarks[i + 1]); + db.execSQL("INSERT INTO bookmarks (" + + Bookmarks.TITLE + ", " + + Bookmarks.URL + ", " + + Bookmarks.IS_FOLDER + "," + + Bookmarks.PARENT + "," + + Bookmarks.POSITION + + ") VALUES (" + + "'" + bookmarks[i] + "', " + + "'" + bookmarkDestination + "', " + + "0," + + Long.toString(parentId) + "," + + Integer.toString(i) + + ");"); + } + } catch (ArrayIndexOutOfBoundsException e) { + } + } + + // XXX: This is a major hack to remove our dependency on gsf constants and + // its content provider. http://b/issue?id=2425179 + private String getClientId(ContentResolver cr) { + String ret = "android-google"; + Cursor c = null; + try { + c = cr.query(Uri.parse("content://com.google.settings/partner"), + new String[] { "value" }, "name='client_id'", null, null); + if (c != null && c.moveToNext()) { + ret = c.getString(0); + } + } catch (RuntimeException ex) { + // fall through to return the default + } finally { + if (c != null) { + c.close(); + } + } + return ret; + } + + private CharSequence replaceSystemPropertyInString(Context context, CharSequence srcString) { + StringBuffer sb = new StringBuffer(); + int lastCharLoc = 0; + + final String client_id = getClientId(context.getContentResolver()); + + 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(); + if (propertyKeyValue.equals("CLIENT_ID")) { + sb.append(client_id); + } else { + sb.append("unknown"); + } + 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; + } + } + + @Override + public SQLiteOpenHelper getDatabaseHelper(Context context) { + synchronized (this) { + if (mOpenHelper == null) { + mOpenHelper = new DatabaseHelper(context); + } + return mOpenHelper; + } + } + + @Override + public boolean isCallerSyncAdapter(Uri uri) { + return uri.getBooleanQueryParameter(BrowserContract.CALLER_IS_SYNCADAPTER, false); + } + + @Override + public void notifyChange(boolean callerIsSyncAdapter) { + ContentResolver resolver = getContext().getContentResolver(); + resolver.notifyChange(BrowserContract.AUTHORITY_URI, null, !callerIsSyncAdapter); + resolver.notifyChange(LEGACY_BROWSER_AUTHORITY_URI, null, !callerIsSyncAdapter); + } + + @Override + public String getType(Uri uri) { + final int match = URI_MATCHER.match(uri); + switch (match) { + case BOOKMARKS: + return Bookmarks.CONTENT_TYPE; + case BOOKMARKS_ID: + return Bookmarks.CONTENT_ITEM_TYPE; + case HISTORY: + return History.CONTENT_TYPE; + case HISTORY_ID: + return History.CONTENT_ITEM_TYPE; + case SEARCHES: + return Searches.CONTENT_TYPE; + case SEARCHES_ID: + return Searches.CONTENT_ITEM_TYPE; +// case SUGGEST: +// return SearchManager.SUGGEST_MIME_TYPE; + } + return null; + } + + @Override + public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, + String sortOrder) { + SQLiteDatabase db = mOpenHelper.getReadableDatabase(); + final int match = URI_MATCHER.match(uri); + SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); + switch (match) { + case BOOKMARKS_FOLDER_ID: + case BOOKMARKS_ID: + case BOOKMARKS: { + // Only show deleted bookmarks if requested to do so + if (!uri.getBooleanQueryParameter(Bookmarks.QUERY_PARAMETER_SHOW_DELETED, false)) { + selection = DatabaseUtils.concatenateWhere( + Bookmarks.IS_DELETED + "=0", selection); + } + + if (match == BOOKMARKS_ID) { + // Tack on the ID of the specific bookmark requested + selection = DatabaseUtils.concatenateWhere( + TABLE_BOOKMARKS + "." + Bookmarks._ID + "=?", selection); + selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs, + new String[] { Long.toString(ContentUris.parseId(uri)) }); + } else if (match == BOOKMARKS_FOLDER_ID) { + // Tack on the ID of the specific folder requested + selection = DatabaseUtils.concatenateWhere( + TABLE_BOOKMARKS + "." + Bookmarks.PARENT + "=?", selection); + selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs, + new String[] { Long.toString(ContentUris.parseId(uri)) }); + } + + if (TextUtils.isEmpty(sortOrder)) { + sortOrder = DEFAULT_BOOKMARKS_SORT_ORDER; + } + + qb.setProjectionMap(BOOKMARKS_PROJECTION_MAP); + qb.setTables(TABLE_BOOKMARKS); + break; + } + + case BOOKMARKS_FOLDER: { + // Don't allow selections to be applied to the default folder + if (!TextUtils.isEmpty(selection) || selectionArgs != null) { + throw new UnsupportedOperationException( + "selections aren't supported on this URI"); + } + + qb.setTables(TABLE_BOOKMARKS); + qb.setProjectionMap(BOOKMARKS_PROJECTION_MAP); + String bookmarksBarQuery = qb.buildQuery(projection, + Bookmarks.PARENT + "=?", + null, null, null, null, null); + + qb.setProjectionMap(OTHER_BOOKMARKS_PROJECTION_MAP); + String otherBookmarksQuery = qb.buildQuery(projection, + Bookmarks._ID + "=?", + null, null, null, null, null); + + String query = qb.buildUnionQuery( + new String[] { bookmarksBarQuery, otherBookmarksQuery }, + DEFAULT_BOOKMARKS_SORT_ORDER, null); + + return db.rawQuery(query, new String[] { + Long.toString(FIXED_ID_BOOKMARKS_BAR), + Long.toString(FIXED_ID_OTHER_BOOKMARKS)}); + } + + case HISTORY_ID: { + selection = DatabaseUtils.concatenateWhere( + TABLE_HISTORY + "._id=?", selection); + selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs, + new String[] { Long.toString(ContentUris.parseId(uri)) }); + // fall through + } + case HISTORY: { + qb.setProjectionMap(HISTORY_PROJECTION_MAP); + qb.setTables(HISTORY_JOIN_BOOKMARKS); + break; + } + + case SYNCSTATE: { + return mSyncHelper.query(db, projection, selection, selectionArgs, sortOrder); + } + + case SYNCSTATE_ID: { + selection = appendAccountToSelection(uri, selection); + String selectionWithId = + (SyncStateContract.Columns._ID + "=" + ContentUris.parseId(uri) + " ") + + (selection == null ? "" : " AND (" + selection + ")"); + return mSyncHelper.query(db, projection, selectionWithId, selectionArgs, sortOrder); + } + + default: { + throw new UnsupportedOperationException("Unknown URL " + uri.toString()); + } + } + + Cursor cursor = qb.query(db, projection, selection, selectionArgs, null, null, sortOrder); + cursor.setNotificationUri(getContext().getContentResolver(), BrowserContract.AUTHORITY_URI); + return cursor; + } + + @Override + public int deleteInTransaction(Uri uri, String selection, String[] selectionArgs, + boolean callerIsSyncAdapter) { + final int match = URI_MATCHER.match(uri); + final SQLiteDatabase db = mOpenHelper.getWritableDatabase(); + switch (match) { + case BOOKMARKS_ID: + case BOOKMARKS: { + //TODO cascade deletes down from folders + if (!callerIsSyncAdapter) { + // If the caller isn't a sync adapter just go through and update all the + // bookmarks to have the deleted flag set. + ContentValues values = new ContentValues(); + values.put(Bookmarks.IS_DELETED, 1); + return updateInTransaction(uri, values, selection, selectionArgs, + callerIsSyncAdapter); + } else { + // Sync adapters are allowed to actually delete things + if (match == BOOKMARKS_ID) { + selection = DatabaseUtils.concatenateWhere(selection, + TABLE_BOOKMARKS + "._id=?"); + selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs, + new String[] { Long.toString(ContentUris.parseId(uri)) }); + } + return db.delete(TABLE_BOOKMARKS, selection, selectionArgs); + } + } + + case HISTORY_ID: { + selection = DatabaseUtils.concatenateWhere(selection, TABLE_HISTORY + "._id=?"); + selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs, + new String[] { Long.toString(ContentUris.parseId(uri)) }); + // fall through + } + case HISTORY: { + return db.delete(TABLE_HISTORY, selection, selectionArgs); + } + + case SEARCHES_ID: { + selection = DatabaseUtils.concatenateWhere(selection, TABLE_SEARCHES + "._id=?"); + selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs, + new String[] { Long.toString(ContentUris.parseId(uri)) }); + // fall through + } + case SEARCHES: { + return db.delete(TABLE_SEARCHES, selection, selectionArgs); + } + + case SYNCSTATE: { + return mSyncHelper.delete(db, selection, selectionArgs); + } + case SYNCSTATE_ID: { + String selectionWithId = + (SyncStateContract.Columns._ID + "=" + ContentUris.parseId(uri) + " ") + + (selection == null ? "" : " AND (" + selection + ")"); + return mSyncHelper.delete(db, selectionWithId, selectionArgs); + } + } + throw new UnsupportedOperationException("Unknown update URI " + uri); + } + + @Override + public Uri insertInTransaction(Uri uri, ContentValues values, boolean callerIsSyncAdapter) { + final int match = URI_MATCHER.match(uri); + final SQLiteDatabase db = mOpenHelper.getWritableDatabase(); + long id = -1; + switch (match) { + case BOOKMARKS: { + // Mark rows dirty if they're not coming from a sync adapater + if (!callerIsSyncAdapter) { + values.put(Bookmarks.DIRTY, 1); + } + + // If no parent is set default to the "Bookmarks Bar" folder + if (!values.containsKey(Bookmarks.PARENT)) { + values.put(Bookmarks.PARENT, FIXED_ID_BOOKMARKS_BAR); + } + + // If no position is requested put the bookmark at the beginning of the list + if (!values.containsKey(Bookmarks.POSITION)) { + values.put(Bookmarks.POSITION, Long.toString(Long.MIN_VALUE)); + } + + id = db.insertOrThrow(TABLE_BOOKMARKS, Bookmarks.DIRTY, values); + break; + } + + case HISTORY: { + id = db.insertOrThrow(TABLE_HISTORY, History.VISITS, values); + break; + } + + case SEARCHES: { + id = db.insertOrThrow(TABLE_SEARCHES, Searches.SEARCH, values); + break; + } + + case SYNCSTATE: { + id = mSyncHelper.insert(mDb, values); + break; + } + + default: { + throw new UnsupportedOperationException("Unknown insert URI " + uri); + } + } + + if (id >= 0) { + return ContentUris.withAppendedId(uri, id); + } else { + return null; + } + } + + @Override + public int updateInTransaction(Uri uri, ContentValues values, String selection, + String[] selectionArgs, boolean callerIsSyncAdapter) { + final int match = URI_MATCHER.match(uri); + final SQLiteDatabase db = mOpenHelper.getWritableDatabase(); + switch (match) { + case BOOKMARKS_ID: { + // Mark the bookmark dirty if the caller isn't a sync adapter + if (!callerIsSyncAdapter) { + values = new ContentValues(values); + values.put(Bookmarks.DIRTY, 1); + } + selection = DatabaseUtils.concatenateWhere(selection, + TABLE_BOOKMARKS + "._id=?"); + selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs, + new String[] { Long.toString(ContentUris.parseId(uri)) }); + return db.update(TABLE_BOOKMARKS, values, selection, selectionArgs); + } + + case BOOKMARKS: { + if (!callerIsSyncAdapter) { + values = new ContentValues(values); + values.put(Bookmarks.DIRTY, 1); + } + return updateBookmarksInTransaction(values, selection, selectionArgs); + } + + case HISTORY_ID: { + selection = DatabaseUtils.concatenateWhere(selection, TABLE_HISTORY + "._id=?"); + selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs, + new String[] { Long.toString(ContentUris.parseId(uri)) }); + // fall through + } + case HISTORY: { + return db.update(TABLE_HISTORY, values, selection, selectionArgs); + } + + case SEARCHES_ID: { + selection = DatabaseUtils.concatenateWhere(selection, TABLE_SEARCHES + "._id=?"); + selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs, + new String[] { Long.toString(ContentUris.parseId(uri)) }); + // fall through + } + case SEARCHES: { + return db.update(TABLE_SEARCHES, values, selection, selectionArgs); + } + + case SYNCSTATE: { + return mSyncHelper.update(mDb, values, + appendAccountToSelection(uri, selection), selectionArgs); + } + + case SYNCSTATE_ID: { + selection = appendAccountToSelection(uri, selection); + String selectionWithId = + (SyncStateContract.Columns._ID + "=" + ContentUris.parseId(uri) + " ") + + (selection == null ? "" : " AND (" + selection + ")"); + return mSyncHelper.update(mDb, values, + selectionWithId, selectionArgs); + } + } + throw new UnsupportedOperationException("Unknown update URI " + uri); + } + + /** + * Does a query to find the matching bookmarks and updates each one with the provided values. + */ + private int updateBookmarksInTransaction(ContentValues values, String selection, + String[] selectionArgs) { + int count = 0; + final SQLiteDatabase db = mOpenHelper.getWritableDatabase(); + Cursor cursor = query(Bookmarks.CONTENT_URI, new String[] { Bookmarks._ID }, + selection, selectionArgs, null); + try { + String[] args = new String[1]; + while (cursor.moveToNext()) { + args[0] = cursor.getString(0); + count += db.update(TABLE_BOOKMARKS, values, "_id=?", args); + } + } finally { + if (cursor != null) cursor.close(); + } + return count; + } + + private String appendAccountToSelection(Uri uri, String selection) { + final String accountName = uri.getQueryParameter(RawContacts.ACCOUNT_NAME); + final String accountType = uri.getQueryParameter(RawContacts.ACCOUNT_TYPE); + + final boolean partialUri = TextUtils.isEmpty(accountName) ^ TextUtils.isEmpty(accountType); + if (partialUri) { + // Throw when either account is incomplete + throw new IllegalArgumentException( + "Must specify both or neither of ACCOUNT_NAME and ACCOUNT_TYPE for " + uri); + } + + // Accounts are valid by only checking one parameter, since we've + // already ruled out partial accounts. + final boolean validAccount = !TextUtils.isEmpty(accountName); + if (validAccount) { + StringBuilder selectionSb = new StringBuilder(RawContacts.ACCOUNT_NAME + "=" + + DatabaseUtils.sqlEscapeString(accountName) + " AND " + + RawContacts.ACCOUNT_TYPE + "=" + + DatabaseUtils.sqlEscapeString(accountType)); + if (!TextUtils.isEmpty(selection)) { + selectionSb.append(" AND ("); + selectionSb.append(selection); + selectionSb.append(')'); + } + return selectionSb.toString(); + } else { + return selection; + } + } +} diff --git a/src/com/android/browser/provider/SQLiteContentProvider.java b/src/com/android/browser/provider/SQLiteContentProvider.java new file mode 100644 index 0000000..a50894a --- /dev/null +++ b/src/com/android/browser/provider/SQLiteContentProvider.java @@ -0,0 +1,276 @@ +/* + * Copyright (C) 2009 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.provider; + +import android.content.ContentProvider; +import android.content.ContentProviderOperation; +import android.content.ContentProviderResult; +import android.content.ContentValues; +import android.content.Context; +import android.content.OperationApplicationException; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; +import android.database.sqlite.SQLiteTransactionListener; +import android.net.Uri; + +import java.util.ArrayList; + +/** + * General purpose {@link ContentProvider} base class that uses SQLiteDatabase for storage. + */ +public abstract class SQLiteContentProvider extends ContentProvider + implements SQLiteTransactionListener { + + private static final String TAG = "SQLiteContentProvider"; + + private SQLiteOpenHelper mOpenHelper; + private volatile boolean mNotifyChange; + protected SQLiteDatabase mDb; + + private final ThreadLocal<Boolean> mApplyingBatch = new ThreadLocal<Boolean>(); + private static final int SLEEP_AFTER_YIELD_DELAY = 4000; + + /** + * Maximum number of operations allowed in a batch between yield points. + */ + private static final int MAX_OPERATIONS_PER_YIELD_POINT = 500; + + @Override + public boolean onCreate() { + Context context = getContext(); + mOpenHelper = getDatabaseHelper(context); + return true; + } + + /** + * Returns a {@link SQLiteOpenHelper} that can open the database. + */ + public abstract SQLiteOpenHelper getDatabaseHelper(Context context); + + /** + * The equivalent of the {@link #insert} method, but invoked within a transaction. + */ + public abstract Uri insertInTransaction(Uri uri, ContentValues values, + boolean callerIsSyncAdapter); + + /** + * The equivalent of the {@link #update} method, but invoked within a transaction. + */ + public abstract int updateInTransaction(Uri uri, ContentValues values, String selection, + String[] selectionArgs, boolean callerIsSyncAdapter); + + /** + * The equivalent of the {@link #delete} method, but invoked within a transaction. + */ + public abstract int deleteInTransaction(Uri uri, String selection, String[] selectionArgs, + boolean callerIsSyncAdapter); + + /** + * Called when the provider needs to notify the system of a change. + * @param callerIsSyncAdapter true if the caller that caused the change was a sync adapter. + */ + public abstract void notifyChange(boolean callerIsSyncAdapter); + + public boolean isCallerSyncAdapter(Uri uri) { + return false; + } + + public SQLiteOpenHelper getDatabaseHelper() { + return mOpenHelper; + } + + private boolean applyingBatch() { + return mApplyingBatch.get() != null && mApplyingBatch.get(); + } + + @Override + public Uri insert(Uri uri, ContentValues values) { + Uri result = null; + boolean callerIsSyncAdapter = isCallerSyncAdapter(uri); + boolean applyingBatch = applyingBatch(); + if (!applyingBatch) { + mDb = mOpenHelper.getWritableDatabase(); + mDb.beginTransactionWithListener(this); + try { + result = insertInTransaction(uri, values, callerIsSyncAdapter); + if (result != null) { + mNotifyChange = true; + } + mDb.setTransactionSuccessful(); + } finally { + mDb.endTransaction(); + } + + onEndTransaction(callerIsSyncAdapter); + } else { + result = insertInTransaction(uri, values, callerIsSyncAdapter); + if (result != null) { + mNotifyChange = true; + } + } + return result; + } + + @Override + public int bulkInsert(Uri uri, ContentValues[] values) { + int numValues = values.length; + boolean callerIsSyncAdapter = isCallerSyncAdapter(uri); + mDb = mOpenHelper.getWritableDatabase(); + mDb.beginTransactionWithListener(this); + try { + for (int i = 0; i < numValues; i++) { + Uri result = insertInTransaction(uri, values[i], callerIsSyncAdapter); + if (result != null) { + mNotifyChange = true; + } + mDb.yieldIfContendedSafely(); + } + mDb.setTransactionSuccessful(); + } finally { + mDb.endTransaction(); + } + + onEndTransaction(callerIsSyncAdapter); + return numValues; + } + + @Override + public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { + int count = 0; + boolean callerIsSyncAdapter = isCallerSyncAdapter(uri); + boolean applyingBatch = applyingBatch(); + if (!applyingBatch) { + mDb = mOpenHelper.getWritableDatabase(); + mDb.beginTransactionWithListener(this); + try { + count = updateInTransaction(uri, values, selection, selectionArgs, + callerIsSyncAdapter); + if (count > 0) { + mNotifyChange = true; + } + mDb.setTransactionSuccessful(); + } finally { + mDb.endTransaction(); + } + + onEndTransaction(callerIsSyncAdapter); + } else { + count = updateInTransaction(uri, values, selection, selectionArgs, callerIsSyncAdapter); + if (count > 0) { + mNotifyChange = true; + } + } + + return count; + } + + @Override + public int delete(Uri uri, String selection, String[] selectionArgs) { + int count = 0; + boolean callerIsSyncAdapter = isCallerSyncAdapter(uri); + boolean applyingBatch = applyingBatch(); + if (!applyingBatch) { + mDb = mOpenHelper.getWritableDatabase(); + mDb.beginTransactionWithListener(this); + try { + count = deleteInTransaction(uri, selection, selectionArgs, callerIsSyncAdapter); + if (count > 0) { + mNotifyChange = true; + } + mDb.setTransactionSuccessful(); + } finally { + mDb.endTransaction(); + } + + onEndTransaction(callerIsSyncAdapter); + } else { + count = deleteInTransaction(uri, selection, selectionArgs, callerIsSyncAdapter); + if (count > 0) { + mNotifyChange = true; + } + } + return count; + } + + @Override + public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations) + throws OperationApplicationException { + int ypCount = 0; + int opCount = 0; + boolean callerIsSyncAdapter = false; + mDb = mOpenHelper.getWritableDatabase(); + mDb.beginTransactionWithListener(this); + try { + mApplyingBatch.set(true); + final int numOperations = operations.size(); + final ContentProviderResult[] results = new ContentProviderResult[numOperations]; + for (int i = 0; i < numOperations; i++) { + if (++opCount >= MAX_OPERATIONS_PER_YIELD_POINT) { + throw new OperationApplicationException( + "Too many content provider operations between yield points. " + + "The maximum number of operations per yield point is " + + MAX_OPERATIONS_PER_YIELD_POINT, ypCount); + } + final ContentProviderOperation operation = operations.get(i); + if (!callerIsSyncAdapter && isCallerSyncAdapter(operation.getUri())) { + callerIsSyncAdapter = true; + } + if (i > 0 && operation.isYieldAllowed()) { + opCount = 0; + if (mDb.yieldIfContendedSafely(SLEEP_AFTER_YIELD_DELAY)) { + ypCount++; + } + } + results[i] = operation.apply(this, results, i); + } + mDb.setTransactionSuccessful(); + return results; + } finally { + mApplyingBatch.set(false); + mDb.endTransaction(); + onEndTransaction(callerIsSyncAdapter); + } + } + + @Override + public void onBegin() { + onBeginTransaction(); + } + + @Override + public void onCommit() { + beforeTransactionCommit(); + } + + @Override + public void onRollback() { + // not used + } + + protected void onBeginTransaction() { + } + + protected void beforeTransactionCommit() { + } + + protected void onEndTransaction(boolean callerIsSyncAdapter) { + if (mNotifyChange) { + mNotifyChange = false; + notifyChange(callerIsSyncAdapter); + } + } +} |