blob: 27782e090e01a1c17e3af1227bcf1d9aafe1ee85 [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.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;
}
// Strip the query.
int query = url.indexOf('?');
String noQuery = url;
if (query != -1) {
noQuery = url.substring(0, query);
}
url = noQuery + '?';
// 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[] { noQuery, url };
final String where = "(" + Browser.BookmarkColumns.URL + " == ? OR "
+ Browser.BookmarkColumns.URL + " GLOB ? || '*') 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);
}
/**
* Return the favicon for this item in the list.
*/
public Bitmap getFavicon(int position) {
if (position < mExtraOffset || position > mCount) {
return null;
}
mCursor.moveToPosition(position - mExtraOffset);
byte[] data = mCursor.getBlob(Browser.HISTORY_PROJECTION_FAVICON_INDEX);
if (data == null) {
return null;
}
return BitmapFactory.decodeByteArray(data, 0, data.length);
}
/**
* 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();
}
}
}