2 * Copyright (C) 2006 The Android Open Source Project
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 package com.android.browser;
19 import android.content.ContentResolver;
20 import android.content.ContentUris;
21 import android.content.ContentValues;
22 import android.database.ContentObserver;
23 import android.database.Cursor;
24 import android.database.DataSetObserver;
25 import android.graphics.Bitmap;
26 import android.graphics.BitmapFactory;
27 import android.net.Uri;
28 import android.os.Bundle;
29 import android.os.Handler;
30 import android.provider.Browser;
31 import android.provider.Browser.BookmarkColumns;
32 import android.view.KeyEvent;
33 import android.view.LayoutInflater;
34 import android.view.View;
35 import android.view.ViewGroup;
36 import android.webkit.WebIconDatabase;
37 import android.webkit.WebIconDatabase.IconListener;
38 import android.webkit.WebView;
39 import android.widget.BaseAdapter;
40 import android.widget.ImageView;
41 import android.widget.TextView;
43 import java.io.ByteArrayOutputStream;
45 class BrowserBookmarksAdapter extends BaseAdapter {
47 private String mCurrentPage;
48 private String mCurrentTitle;
49 private Cursor mCursor;
51 private BrowserBookmarksPage mBookmarksPage;
52 private ContentResolver mContentResolver;
53 private boolean mDataValid;
54 private BookmarkViewMode mViewMode;
55 private boolean mMostVisited;
56 private boolean mNeedsOffset;
57 private int mExtraOffset;
59 // Implementation of WebIconDatabase.IconListener
60 private class IconReceiver implements IconListener {
61 public void onReceivedIcon(String url, Bitmap icon) {
62 updateBookmarkFavicon(mContentResolver, null, url, icon);
66 // Instance of IconReceiver
67 private final IconReceiver mIconReceiver = new IconReceiver();
70 * Create a new BrowserBookmarksAdapter.
71 * @param b BrowserBookmarksPage that instantiated this.
72 * Necessary so it will adjust its focus
73 * appropriately after a search.
75 public BrowserBookmarksAdapter(BrowserBookmarksPage b, String curPage,
76 String curTitle, boolean createShortcut, boolean mostVisited) {
77 mNeedsOffset = !(createShortcut || mostVisited);
78 mMostVisited = mostVisited;
79 mExtraOffset = mNeedsOffset ? 1 : 0;
81 mCurrentPage = b.getResources().getString(R.string.current_page)
83 mCurrentTitle = curTitle;
84 mContentResolver = b.getContentResolver();
85 mViewMode = BookmarkViewMode.LIST;
88 // FIXME: Should have a default sort order that the user selects.
89 String orderBy = Browser.BookmarkColumns.VISITS + " DESC";
91 whereClause = Browser.BookmarkColumns.VISITS + " != 0";
93 whereClause = Browser.BookmarkColumns.BOOKMARK + " != 0";
95 mCursor = b.managedQuery(Browser.BOOKMARKS_URI,
96 Browser.HISTORY_PROJECTION, whereClause, null, orderBy);
97 mCursor.registerContentObserver(new ChangeObserver());
98 mCursor.registerDataSetObserver(new MyDataSetObserver());
101 notifyDataSetChanged();
103 mCount = mCursor.getCount() + mExtraOffset;
105 // FIXME: This requires another query of the database after the
106 // managedQuery. Can we optimize this?
107 Browser.requestAllIcons(mContentResolver,
108 Browser.BookmarkColumns.FAVICON + " is NULL AND " +
109 Browser.BookmarkColumns.BOOKMARK + " == 1", mIconReceiver);
113 * Return a hashmap with one row's Title, Url, and favicon.
114 * @param position Position in the list.
115 * @return Bundle Stores title, url of row position, favicon, and id
116 * for the url. Return a blank map if position is out of
119 public Bundle getRow(int position) {
120 Bundle map = new Bundle();
121 if (position < mExtraOffset || position >= mCount) {
124 mCursor.moveToPosition(position- mExtraOffset);
125 String url = mCursor.getString(Browser.HISTORY_PROJECTION_URL_INDEX);
126 map.putString(Browser.BookmarkColumns.TITLE,
127 mCursor.getString(Browser.HISTORY_PROJECTION_TITLE_INDEX));
128 map.putString(Browser.BookmarkColumns.URL, url);
129 byte[] data = mCursor.getBlob(Browser.HISTORY_PROJECTION_FAVICON_INDEX);
131 map.putParcelable(Browser.BookmarkColumns.FAVICON,
132 BitmapFactory.decodeByteArray(data, 0, data.length));
134 map.putInt("id", mCursor.getInt(Browser.HISTORY_PROJECTION_ID_INDEX));
139 * Update a row in the database with new information.
140 * Requeries the database if the information has changed.
141 * @param map Bundle storing id, title and url of new information
143 public void updateRow(Bundle map) {
146 int id = map.getInt("id");
148 for (mCursor.moveToFirst(); !mCursor.isAfterLast(); mCursor.moveToNext()) {
149 if (mCursor.getInt(Browser.HISTORY_PROJECTION_ID_INDEX) == id) {
150 position = mCursor.getPosition();
158 mCursor.moveToPosition(position);
159 ContentValues values = new ContentValues();
160 String title = map.getString(Browser.BookmarkColumns.TITLE);
161 if (!title.equals(mCursor
162 .getString(Browser.HISTORY_PROJECTION_TITLE_INDEX))) {
163 values.put(Browser.BookmarkColumns.TITLE, title);
165 String url = map.getString(Browser.BookmarkColumns.URL);
166 if (!url.equals(mCursor.
167 getString(Browser.HISTORY_PROJECTION_URL_INDEX))) {
168 values.put(Browser.BookmarkColumns.URL, url);
170 if (values.size() > 0
171 && mContentResolver.update(Browser.BOOKMARKS_URI, values,
172 "_id = " + id, null) != -1) {
178 * Delete a row from the database. Requeries the database.
179 * Does nothing if the provided position is out of range.
180 * @param position Position in the list.
182 public void deleteRow(int position) {
183 if (position < mExtraOffset || position >= getCount()) {
186 mCursor.moveToPosition(position- mExtraOffset);
187 String url = mCursor.getString(Browser.HISTORY_PROJECTION_URL_INDEX);
188 Bookmarks.removeFromBookmarks(null, mContentResolver, url);
193 * Delete all bookmarks from the db. Requeries the database.
194 * All bookmarks with become visited URLs or if never visited
197 public void deleteAllRows() {
198 StringBuilder deleteIds = null;
199 StringBuilder convertIds = null;
201 for (mCursor.moveToFirst(); !mCursor.isAfterLast(); mCursor.moveToNext()) {
202 String url = mCursor.getString(Browser.HISTORY_PROJECTION_URL_INDEX);
203 WebIconDatabase.getInstance().releaseIconForPageUrl(url);
204 int id = mCursor.getInt(Browser.HISTORY_PROJECTION_ID_INDEX);
205 int numVisits = mCursor.getInt(Browser.HISTORY_PROJECTION_VISITS_INDEX);
206 if (0 == numVisits) {
207 if (deleteIds == null) {
208 deleteIds = new StringBuilder();
209 deleteIds.append("( ");
211 deleteIds.append(" OR ( ");
213 deleteIds.append(BookmarkColumns._ID);
214 deleteIds.append(" = ");
215 deleteIds.append(id);
216 deleteIds.append(" )");
218 // It is no longer a bookmark, but it is still a visited site.
219 if (convertIds == null) {
220 convertIds = new StringBuilder();
221 convertIds.append("( ");
223 convertIds.append(" OR ( ");
225 convertIds.append(BookmarkColumns._ID);
226 convertIds.append(" = ");
227 convertIds.append(id);
228 convertIds.append(" )");
232 if (deleteIds != null) {
233 mContentResolver.delete(Browser.BOOKMARKS_URI, deleteIds.toString(),
236 if (convertIds != null) {
237 ContentValues values = new ContentValues();
238 values.put(Browser.BookmarkColumns.BOOKMARK, 0);
239 mContentResolver.update(Browser.BOOKMARKS_URI, values,
240 convertIds.toString(), null);
246 * Refresh list to recognize a change in the database.
248 public void refreshList() {
250 mCount = mCursor.getCount() + mExtraOffset;
251 notifyDataSetChanged();
255 * Update the bookmark's favicon. This is a convenience method for updating
256 * a bookmark favicon for the originalUrl and url of the passed in WebView.
257 * @param cr The ContentResolver to use.
258 * @param WebView The WebView containing the url to update.
259 * @param favicon The favicon bitmap to write to the db.
261 /* package */ static void updateBookmarkFavicon(ContentResolver cr,
262 WebView view, Bitmap favicon) {
264 updateBookmarkFavicon(cr, view.getOriginalUrl(), view.getUrl(),
269 private static void updateBookmarkFavicon(ContentResolver cr,
270 String originalUrl, String url, Bitmap favicon) {
271 final Cursor c = queryBookmarksForUrl(cr, originalUrl, url, true);
275 boolean succeed = c.moveToFirst();
276 ContentValues values = null;
278 if (values == null) {
279 final ByteArrayOutputStream os = new ByteArrayOutputStream();
280 favicon.compress(Bitmap.CompressFormat.PNG, 100, os);
281 values = new ContentValues();
282 values.put(Browser.BookmarkColumns.FAVICON, os.toByteArray());
284 cr.update(ContentUris.withAppendedId(Browser.BOOKMARKS_URI, c
285 .getInt(0)), values, null, null);
286 succeed = c.moveToNext();
291 /* package */ static Cursor queryBookmarksForUrl(ContentResolver cr,
292 String originalUrl, String url, boolean onlyBookmarks) {
293 if (cr == null || url == null) {
297 // If originalUrl is null, just set it to url.
298 if (originalUrl == null) {
302 // Look for both the original url and the actual url. This takes in to
303 // account redirects.
304 String originalUrlNoQuery = removeQuery(originalUrl);
305 String urlNoQuery = removeQuery(url);
306 originalUrl = originalUrlNoQuery + '?';
307 url = urlNoQuery + '?';
309 // Use NoQuery to search for the base url (i.e. if the url is
310 // http://www.yahoo.com/?rs=1, search for http://www.yahoo.com)
311 // Use url to match the base url with other queries (i.e. if the url is
312 // http://www.google.com/m, search for
313 // http://www.google.com/m?some_query)
314 final String[] selArgs = new String[] {
315 originalUrlNoQuery, urlNoQuery, originalUrl, url };
316 String where = BookmarkColumns.URL + " == ? OR "
317 + BookmarkColumns.URL + " == ? OR "
318 + BookmarkColumns.URL + " GLOB ? || '*' OR "
319 + BookmarkColumns.URL + " GLOB ? || '*'";
321 where = "(" + where + ") AND " + BookmarkColumns.BOOKMARK + " == 1";
323 final String[] projection =
324 new String[] { Browser.BookmarkColumns._ID };
325 return cr.query(Browser.BOOKMARKS_URI, projection, where, selArgs,
329 // Strip the query from the given url.
330 private static String removeQuery(String url) {
334 int query = url.indexOf('?');
335 String noQuery = url;
337 noQuery = url.substring(0, query);
343 * How many items should be displayed in the list.
344 * @return Count of items.
346 public int getCount() {
354 public boolean areAllItemsEnabled() {
358 public boolean isEnabled(int position) {
363 * Get the data associated with the specified position in the list.
364 * @param position Index of the item whose data we want.
365 * @return The data at the specified position.
367 public Object getItem(int position) {
372 * Get the row id associated with the specified position in the list.
373 * @param position Index of the item whose row id we want.
374 * @return The id of the item at the specified position.
376 public long getItemId(int position) {
380 /* package */ void switchViewMode(BookmarkViewMode viewMode) {
381 mViewMode = viewMode;
384 /* package */ void populateBookmarkItem(BookmarkItem b, int position) {
385 mCursor.moveToPosition(position - mExtraOffset);
386 b.setUrl(mCursor.getString(Browser.HISTORY_PROJECTION_URL_INDEX));
387 b.setName(mCursor.getString(Browser.HISTORY_PROJECTION_TITLE_INDEX));
388 byte[] data = mCursor.getBlob(Browser.HISTORY_PROJECTION_FAVICON_INDEX);
389 Bitmap bitmap = (null == data) ? null :
390 BitmapFactory.decodeByteArray(data, 0, data.length);
391 b.setFavicon(bitmap);
395 * Get a View that displays the data at the specified position
397 * @param position Index of the item whose view we want.
398 * @return A View corresponding to the data at the specified position.
400 public View getView(int position, View convertView, ViewGroup parent) {
402 throw new IllegalStateException(
403 "this should only be called when the cursor is valid");
405 if (position < 0 || position > mCount) {
406 throw new AssertionError(
407 "BrowserBookmarksAdapter tried to get a view out of range");
409 if (mViewMode == BookmarkViewMode.GRID) {
410 if (convertView == null || convertView instanceof AddNewBookmark
411 || convertView instanceof BookmarkItem) {
412 LayoutInflater factory = LayoutInflater.from(mBookmarksPage);
414 = factory.inflate(R.layout.bookmark_thumbnail, null);
416 View holder = convertView.findViewById(R.id.holder);
417 ImageView thumb = (ImageView) convertView.findViewById(R.id.thumb);
418 TextView tv = (TextView) convertView.findViewById(R.id.label);
420 if (0 == position && mNeedsOffset) {
421 // This is to create a bookmark for the current page.
422 holder.setVisibility(View.VISIBLE);
423 tv.setText(mCurrentTitle);
424 // FIXME: Want to show the screenshot of the current page
425 thumb.setImageResource(R.drawable.blank);
428 holder.setVisibility(View.GONE);
429 mCursor.moveToPosition(position - mExtraOffset);
430 tv.setText(mCursor.getString(
431 Browser.HISTORY_PROJECTION_TITLE_INDEX));
432 byte[] data = mCursor.getBlob(
433 Browser.HISTORY_PROJECTION_THUMBNAIL_INDEX);
435 thumb.setImageResource(R.drawable.ic_launcher_shortcut_browser_bookmark);
437 thumb.setImageBitmap(
438 BitmapFactory.decodeByteArray(data, 0, data.length));
444 if (position == 0 && mNeedsOffset) {
446 if (convertView instanceof AddNewBookmark) {
447 b = (AddNewBookmark) convertView;
449 b = new AddNewBookmark(mBookmarksPage);
451 b.setUrl(mCurrentPage);
455 if (convertView == null || !(convertView instanceof HistoryItem)) {
456 convertView = new HistoryItem(mBookmarksPage);
459 if (convertView == null || !(convertView instanceof BookmarkItem)) {
460 convertView = new BookmarkItem(mBookmarksPage);
463 bind((BookmarkItem) convertView, position);
465 ((HistoryItem) convertView).setIsBookmark(
466 getIsBookmark(position));
472 * Return the title for this item in the list.
474 public String getTitle(int position) {
475 return getString(Browser.HISTORY_PROJECTION_TITLE_INDEX, position);
479 * Return the Url for this item in the list.
481 public String getUrl(int position) {
482 return getString(Browser.HISTORY_PROJECTION_URL_INDEX, position);
486 * Return the favicon for this item in the list.
488 public Bitmap getFavicon(int position) {
489 return getBitmap(Browser.HISTORY_PROJECTION_FAVICON_INDEX, position);
492 public Bitmap getTouchIcon(int position) {
493 return getBitmap(Browser.HISTORY_PROJECTION_TOUCH_ICON_INDEX, position);
496 private Bitmap getBitmap(int cursorIndex, int position) {
497 if (position < mExtraOffset || position > mCount) {
500 mCursor.moveToPosition(position - mExtraOffset);
501 byte[] data = mCursor.getBlob(cursorIndex);
505 return BitmapFactory.decodeByteArray(data, 0, data.length);
509 * Return whether or not this item represents a bookmarked site.
511 public boolean getIsBookmark(int position) {
512 if (position < mExtraOffset || position > mCount) {
515 mCursor.moveToPosition(position - mExtraOffset);
516 return (1 == mCursor.getInt(Browser.HISTORY_PROJECTION_BOOKMARK_INDEX));
520 * Private helper function to return the title or url.
522 private String getString(int cursorIndex, int position) {
523 if (position < mExtraOffset || position > mCount) {
526 mCursor.moveToPosition(position- mExtraOffset);
527 return mCursor.getString(cursorIndex);
530 private void bind(BookmarkItem b, int position) {
531 mCursor.moveToPosition(position- mExtraOffset);
533 String title = mCursor.getString(Browser.HISTORY_PROJECTION_TITLE_INDEX);
534 if (title.length() > BrowserSettings.MAX_TEXTVIEW_LEN) {
535 title = title.substring(0, BrowserSettings.MAX_TEXTVIEW_LEN);
538 String url = mCursor.getString(Browser.HISTORY_PROJECTION_URL_INDEX);
539 if (url.length() > BrowserSettings.MAX_TEXTVIEW_LEN) {
540 url = url.substring(0, BrowserSettings.MAX_TEXTVIEW_LEN);
543 byte[] data = mCursor.getBlob(Browser.HISTORY_PROJECTION_FAVICON_INDEX);
545 b.setFavicon(BitmapFactory.decodeByteArray(data, 0, data.length));
551 private class ChangeObserver extends ContentObserver {
552 public ChangeObserver() {
553 super(new Handler());
557 public boolean deliverSelfNotifications() {
562 public void onChange(boolean selfChange) {
567 private class MyDataSetObserver extends DataSetObserver {
569 public void onChanged() {
571 notifyDataSetChanged();
575 public void onInvalidated() {
577 notifyDataSetInvalidated();