diff options
Diffstat (limited to 'src/com/android/browser/BrowserBookmarksAdapter.java')
| -rw-r--r-- | src/com/android/browser/BrowserBookmarksAdapter.java | 522 |
1 files changed, 522 insertions, 0 deletions
diff --git a/src/com/android/browser/BrowserBookmarksAdapter.java b/src/com/android/browser/BrowserBookmarksAdapter.java new file mode 100644 index 000000000..3b76e757c --- /dev/null +++ b/src/com/android/browser/BrowserBookmarksAdapter.java @@ -0,0 +1,522 @@ +/* + * 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.View; +import android.view.ViewGroup; +import android.webkit.WebIconDatabase; +import android.webkit.WebIconDatabase.IconListener; +import android.widget.BaseAdapter; + +import java.io.ByteArrayOutputStream; + +class BrowserBookmarksAdapter extends BaseAdapter { + + private final String LOGTAG = "Bookmarks"; + + private String mCurrentPage; + private Cursor mCursor; + private int mCount; + private String mLastWhereClause; + private String[] mLastSelectionArgs; + private String mLastOrderBy; + private BrowserBookmarksPage mBookmarksPage; + private ContentResolver mContentResolver; + private ChangeObserver mChangeObserver; + private DataSetObserver mDataSetObserver; + private boolean mDataValid; + + // When true, this adapter is used to pick a bookmark to create a shortcut + private boolean mCreateShortcut; + private int mExtraOffset; + + // Implementation of WebIconDatabase.IconListener + private class IconReceiver implements IconListener { + public void onReceivedIcon(String url, Bitmap icon) { + updateBookmarkFavicon(mContentResolver, 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) { + this(b, curPage, false); + } + + /** + * 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, + boolean createShortcut) { + mDataValid = false; + mCreateShortcut = createShortcut; + mExtraOffset = createShortcut ? 0 : 1; + mBookmarksPage = b; + mCurrentPage = b.getResources().getString(R.string.current_page) + + curPage; + mContentResolver = b.getContentResolver(); + mLastOrderBy = Browser.BookmarkColumns.CREATED + " DESC"; + mChangeObserver = new ChangeObserver(); + mDataSetObserver = new MyDataSetObserver(); + // FIXME: Should have a default sort order that the user selects. + search(null); + // FIXME: This requires another query of the database after the + // initial search(null). 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 (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); + WebIconDatabase.getInstance().releaseIconForPageUrl(url); + Uri uri = ContentUris.withAppendedId(Browser.BOOKMARKS_URI, mCursor + .getInt(Browser.HISTORY_PROJECTION_ID_INDEX)); + int numVisits = mCursor.getInt(Browser.HISTORY_PROJECTION_VISITS_INDEX); + if (0 == numVisits) { + mContentResolver.delete(uri, null, null); + } else { + // It is no longer a bookmark, but it is still a visited site. + ContentValues values = new ContentValues(); + values.put(Browser.BookmarkColumns.BOOKMARK, 0); + mContentResolver.update(uri, values, null, null); + } + 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() { + // FIXME: consider using requery(). + // Need to do more work to get it to function though. + searchInternal(mLastWhereClause, mLastSelectionArgs, mLastOrderBy); + } + + /** + * Search the database for bookmarks that match the input string. + * @param like String to use to search the database. Strings with spaces + * are treated as having multiple search terms using the + * OR operator. Search both the title and url. + */ + public void search(String like) { + String whereClause = Browser.BookmarkColumns.BOOKMARK + " == 1"; + String[] selectionArgs = null; + if (like != null) { + String[] likes = like.split(" "); + int count = 0; + boolean firstTerm = true; + StringBuilder andClause = new StringBuilder(256); + for (int j = 0; j < likes.length; j++) { + if (likes[j].length() > 0) { + if (firstTerm) { + firstTerm = false; + } else { + andClause.append(" OR "); + } + andClause.append(Browser.BookmarkColumns.TITLE + + " LIKE ? OR " + Browser.BookmarkColumns.URL + + " LIKE ? "); + count += 2; + } + } + if (count > 0) { + selectionArgs = new String[count]; + count = 0; + for (int j = 0; j < likes.length; j++) { + if (likes[j].length() > 0) { + like = "%" + likes[j] + "%"; + selectionArgs[count++] = like; + selectionArgs[count++] = like; + } + } + whereClause += " AND (" + andClause + ")"; + } + } + searchInternal(whereClause, selectionArgs, mLastOrderBy); + } + + /** + * Update the bookmark's favicon. + * @param cr The ContentResolver to use. + * @param url The url of the bookmark to update. + * @param favicon The favicon bitmap to write to the db. + */ + /* package */ static void updateBookmarkFavicon(ContentResolver cr, + String url, Bitmap favicon) { + if (url == null || favicon == null) { + return; + } + final String[] selArgs = new String[] { url }; + final String where = Browser.BookmarkColumns.URL + " == ? AND " + + Browser.BookmarkColumns.BOOKMARK + " == 1"; + final String[] projection = new String[] { Browser.BookmarkColumns._ID }; + final Cursor c = cr.query(Browser.BOOKMARKS_URI, projection, where, + selArgs, null); + 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(); + } + + /** + * This sorts alphabetically, with non-capitalized titles before + * capitalized. + */ + public void sortAlphabetical() { + searchInternal(mLastWhereClause, mLastSelectionArgs, + Browser.BookmarkColumns.TITLE + " COLLATE UNICODE ASC"); + } + + /** + * Internal function used in search, sort, and refreshList. + */ + private void searchInternal(String whereClause, String[] selectionArgs, + String orderBy) { + if (mCursor != null) { + mCursor.unregisterContentObserver(mChangeObserver); + mCursor.unregisterDataSetObserver(mDataSetObserver); + mBookmarksPage.stopManagingCursor(mCursor); + mCursor.deactivate(); + } + + mLastWhereClause = whereClause; + mLastSelectionArgs = selectionArgs; + mLastOrderBy = orderBy; + mCursor = mContentResolver.query( + Browser.BOOKMARKS_URI, + Browser.HISTORY_PROJECTION, + whereClause, + selectionArgs, + orderBy); + mCursor.registerContentObserver(mChangeObserver); + mCursor.registerDataSetObserver(mDataSetObserver); + mBookmarksPage.startManagingCursor(mCursor); + + mDataValid = true; + notifyDataSetChanged(); + + mCount = mCursor.getCount() + mExtraOffset; + } + + /** + * 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; + } + + /** + * 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 (position == 0 && !mCreateShortcut) { + AddNewBookmark b; + if (convertView instanceof AddNewBookmark) { + b = (AddNewBookmark) convertView; + } else { + b = new AddNewBookmark(mBookmarksPage); + } + b.setUrl(mCurrentPage); + return b; + } + if (convertView == null || convertView instanceof AddNewBookmark) { + convertView = new BookmarkItem(mBookmarksPage); + } + bind((BookmarkItem)convertView, 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); + } + + /** + * 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(null); + } + } + + 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(); + } + } +} |
