blob: dd56d2f91339043e2d9eb9dbbe9be985dc349794 [file] [log] [blame]
/*
* 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();
}
}
}