| /* |
| * Copyright (C) 2006 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.browser; |
| |
| import android.content.ContentResolver; |
| import android.content.ContentUris; |
| import android.content.ContentValues; |
| import android.database.ContentObserver; |
| import android.database.Cursor; |
| import android.database.DataSetObserver; |
| import android.graphics.Bitmap; |
| import android.graphics.BitmapFactory; |
| import android.net.Uri; |
| import android.os.Bundle; |
| import android.os.Handler; |
| 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.WebIconDatabase.IconListener; |
| import android.webkit.WebView; |
| import android.widget.BaseAdapter; |
| import android.widget.ImageView; |
| 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; |
| |
| // Implementation of WebIconDatabase.IconListener |
| private class IconReceiver implements IconListener { |
| public void onReceivedIcon(String url, Bitmap icon) { |
| updateBookmarkFavicon(mContentResolver, null, url, icon); |
| } |
| } |
| |
| // Instance of IconReceiver |
| private final IconReceiver mIconReceiver = new IconReceiver(); |
| |
| /** |
| * 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 + " != 0"; |
| } |
| 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; |
| |
| // FIXME: This requires another query of the database after the |
| // managedQuery. Can we optimize this? |
| Browser.requestAllIcons(mContentResolver, |
| Browser.BookmarkColumns.FAVICON + " is NULL AND " + |
| Browser.BookmarkColumns.BOOKMARK + " == 1", mIconReceiver); |
| } |
| |
| /** |
| * 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(ContentResolver cr, |
| String originalUrl, String url, Bitmap favicon) { |
| final Cursor c = queryBookmarksForUrl(cr, originalUrl, url, true); |
| if (c == null) { |
| return; |
| } |
| boolean succeed = c.moveToFirst(); |
| ContentValues values = null; |
| while (succeed) { |
| if (values == null) { |
| final ByteArrayOutputStream os = new ByteArrayOutputStream(); |
| favicon.compress(Bitmap.CompressFormat.PNG, 100, os); |
| values = new ContentValues(); |
| values.put(Browser.BookmarkColumns.FAVICON, os.toByteArray()); |
| } |
| cr.update(ContentUris.withAppendedId(Browser.BOOKMARKS_URI, c |
| .getInt(0)), values, null, null); |
| succeed = c.moveToNext(); |
| } |
| c.close(); |
| } |
| |
| /* 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 + " GLOB ? || '*' OR " |
| + BookmarkColumns.URL + " GLOB ? || '*'"; |
| 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. |
| 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; |
| } |
| |
| /** |
| * 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 = getBitmap(Browser.HISTORY_PROJECTION_THUMBNAIL_INDEX, 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 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); |
| |
| String title = mCursor.getString(Browser.HISTORY_PROJECTION_TITLE_INDEX); |
| if (title.length() > BrowserSettings.MAX_TEXTVIEW_LEN) { |
| title = title.substring(0, BrowserSettings.MAX_TEXTVIEW_LEN); |
| } |
| b.setName(title); |
| String url = mCursor.getString(Browser.HISTORY_PROJECTION_URL_INDEX); |
| if (url.length() > BrowserSettings.MAX_TEXTVIEW_LEN) { |
| url = url.substring(0, BrowserSettings.MAX_TEXTVIEW_LEN); |
| } |
| b.setUrl(url); |
| byte[] data = mCursor.getBlob(Browser.HISTORY_PROJECTION_FAVICON_INDEX); |
| if (data != null) { |
| b.setFavicon(BitmapFactory.decodeByteArray(data, 0, data.length)); |
| } else { |
| b.setFavicon(CombinedBookmarkHistoryActivity.getIconListenerSet() |
| .getFavicon(url)); |
| } |
| } |
| |
| private class ChangeObserver extends ContentObserver { |
| public ChangeObserver() { |
| super(new Handler()); |
| } |
| |
| @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(); |
| } |
| } |
| } |