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 Bitmap mCurrentThumbnail;
50 private Cursor mCursor;
52 private BrowserBookmarksPage mBookmarksPage;
53 private ContentResolver mContentResolver;
54 private boolean mDataValid;
55 private BookmarkViewMode mViewMode;
56 private boolean mMostVisited;
57 private boolean mNeedsOffset;
58 private int mExtraOffset;
60 // Implementation of WebIconDatabase.IconListener
61 private class IconReceiver implements IconListener {
62 public void onReceivedIcon(String url, Bitmap icon) {
63 updateBookmarkFavicon(mContentResolver, null, url, icon);
67 // Instance of IconReceiver
68 private final IconReceiver mIconReceiver = new IconReceiver();
71 * Create a new BrowserBookmarksAdapter.
72 * @param b BrowserBookmarksPage that instantiated this.
73 * Necessary so it will adjust its focus
74 * appropriately after a search.
76 public BrowserBookmarksAdapter(BrowserBookmarksPage b, String curPage,
77 String curTitle, Bitmap curThumbnail, boolean createShortcut,
78 boolean mostVisited) {
79 mNeedsOffset = !(createShortcut || mostVisited);
80 mMostVisited = mostVisited;
81 mExtraOffset = mNeedsOffset ? 1 : 0;
83 mCurrentPage = b.getResources().getString(R.string.current_page)
85 mCurrentTitle = curTitle;
86 mCurrentThumbnail = curThumbnail;
87 mContentResolver = b.getContentResolver();
88 mViewMode = BookmarkViewMode.LIST;
91 // FIXME: Should have a default sort order that the user selects.
92 String orderBy = Browser.BookmarkColumns.VISITS + " DESC";
94 whereClause = Browser.BookmarkColumns.VISITS + " != 0";
96 whereClause = Browser.BookmarkColumns.BOOKMARK + " != 0";
98 mCursor = b.managedQuery(Browser.BOOKMARKS_URI,
99 Browser.HISTORY_PROJECTION, whereClause, null, orderBy);
100 mCursor.registerContentObserver(new ChangeObserver());
101 mCursor.registerDataSetObserver(new MyDataSetObserver());
104 notifyDataSetChanged();
106 mCount = mCursor.getCount() + mExtraOffset;
108 // FIXME: This requires another query of the database after the
109 // managedQuery. Can we optimize this?
110 Browser.requestAllIcons(mContentResolver,
111 Browser.BookmarkColumns.FAVICON + " is NULL AND " +
112 Browser.BookmarkColumns.BOOKMARK + " == 1", mIconReceiver);
116 * Return a hashmap with one row's Title, Url, and favicon.
117 * @param position Position in the list.
118 * @return Bundle Stores title, url of row position, favicon, and id
119 * for the url. Return a blank map if position is out of
122 public Bundle getRow(int position) {
123 Bundle map = new Bundle();
124 if (position < mExtraOffset || position >= mCount) {
127 mCursor.moveToPosition(position- mExtraOffset);
128 String url = mCursor.getString(Browser.HISTORY_PROJECTION_URL_INDEX);
129 map.putString(Browser.BookmarkColumns.TITLE,
130 mCursor.getString(Browser.HISTORY_PROJECTION_TITLE_INDEX));
131 map.putString(Browser.BookmarkColumns.URL, url);
132 byte[] data = mCursor.getBlob(Browser.HISTORY_PROJECTION_FAVICON_INDEX);
134 map.putParcelable(Browser.BookmarkColumns.FAVICON,
135 BitmapFactory.decodeByteArray(data, 0, data.length));
137 map.putInt("id", mCursor.getInt(Browser.HISTORY_PROJECTION_ID_INDEX));
142 * Update a row in the database with new information.
143 * Requeries the database if the information has changed.
144 * @param map Bundle storing id, title and url of new information
146 public void updateRow(Bundle map) {
149 int id = map.getInt("id");
151 for (mCursor.moveToFirst(); !mCursor.isAfterLast(); mCursor.moveToNext()) {
152 if (mCursor.getInt(Browser.HISTORY_PROJECTION_ID_INDEX) == id) {
153 position = mCursor.getPosition();
161 mCursor.moveToPosition(position);
162 ContentValues values = new ContentValues();
163 String title = map.getString(Browser.BookmarkColumns.TITLE);
164 if (!title.equals(mCursor
165 .getString(Browser.HISTORY_PROJECTION_TITLE_INDEX))) {
166 values.put(Browser.BookmarkColumns.TITLE, title);
168 String url = map.getString(Browser.BookmarkColumns.URL);
169 if (!url.equals(mCursor.
170 getString(Browser.HISTORY_PROJECTION_URL_INDEX))) {
171 values.put(Browser.BookmarkColumns.URL, url);
174 if (map.getBoolean("invalidateThumbnail") == true) {
175 values.put(Browser.BookmarkColumns.THUMBNAIL, new byte[0]);
177 if (values.size() > 0
178 && mContentResolver.update(Browser.BOOKMARKS_URI, values,
179 "_id = " + id, null) != -1) {
185 * Delete a row from the database. Requeries the database.
186 * Does nothing if the provided position is out of range.
187 * @param position Position in the list.
189 public void deleteRow(int position) {
190 if (position < mExtraOffset || position >= getCount()) {
193 mCursor.moveToPosition(position- mExtraOffset);
194 String url = mCursor.getString(Browser.HISTORY_PROJECTION_URL_INDEX);
195 String title = mCursor.getString(Browser.HISTORY_PROJECTION_TITLE_INDEX);
196 Bookmarks.removeFromBookmarks(null, mContentResolver, url, title);
201 * Delete all bookmarks from the db. Requeries the database.
202 * All bookmarks with become visited URLs or if never visited
205 public void deleteAllRows() {
206 StringBuilder deleteIds = null;
207 StringBuilder convertIds = null;
209 for (mCursor.moveToFirst(); !mCursor.isAfterLast(); mCursor.moveToNext()) {
210 String url = mCursor.getString(Browser.HISTORY_PROJECTION_URL_INDEX);
211 WebIconDatabase.getInstance().releaseIconForPageUrl(url);
212 int id = mCursor.getInt(Browser.HISTORY_PROJECTION_ID_INDEX);
213 int numVisits = mCursor.getInt(Browser.HISTORY_PROJECTION_VISITS_INDEX);
214 if (0 == numVisits) {
215 if (deleteIds == null) {
216 deleteIds = new StringBuilder();
217 deleteIds.append("( ");
219 deleteIds.append(" OR ( ");
221 deleteIds.append(BookmarkColumns._ID);
222 deleteIds.append(" = ");
223 deleteIds.append(id);
224 deleteIds.append(" )");
226 // It is no longer a bookmark, but it is still a visited site.
227 if (convertIds == null) {
228 convertIds = new StringBuilder();
229 convertIds.append("( ");
231 convertIds.append(" OR ( ");
233 convertIds.append(BookmarkColumns._ID);
234 convertIds.append(" = ");
235 convertIds.append(id);
236 convertIds.append(" )");
240 if (deleteIds != null) {
241 mContentResolver.delete(Browser.BOOKMARKS_URI, deleteIds.toString(),
244 if (convertIds != null) {
245 ContentValues values = new ContentValues();
246 values.put(Browser.BookmarkColumns.BOOKMARK, 0);
247 mContentResolver.update(Browser.BOOKMARKS_URI, values,
248 convertIds.toString(), null);
254 * Refresh list to recognize a change in the database.
256 public void refreshList() {
258 mCount = mCursor.getCount() + mExtraOffset;
259 notifyDataSetChanged();
263 * Update the bookmark's favicon. This is a convenience method for updating
264 * a bookmark favicon for the originalUrl and url of the passed in WebView.
265 * @param cr The ContentResolver to use.
266 * @param originalUrl The original url before any redirects.
267 * @param url The current url.
268 * @param favicon The favicon bitmap to write to the db.
270 /* package */ static void updateBookmarkFavicon(ContentResolver cr,
271 String originalUrl, String url, Bitmap favicon) {
272 final Cursor c = queryBookmarksForUrl(cr, originalUrl, url, true);
276 boolean succeed = c.moveToFirst();
277 ContentValues values = null;
279 if (values == null) {
280 final ByteArrayOutputStream os = new ByteArrayOutputStream();
281 favicon.compress(Bitmap.CompressFormat.PNG, 100, os);
282 values = new ContentValues();
283 values.put(Browser.BookmarkColumns.FAVICON, os.toByteArray());
285 cr.update(ContentUris.withAppendedId(Browser.BOOKMARKS_URI, c
286 .getInt(0)), values, null, null);
287 succeed = c.moveToNext();
292 /* package */ static Cursor queryBookmarksForUrl(ContentResolver cr,
293 String originalUrl, String url, boolean onlyBookmarks) {
294 if (cr == null || url == null) {
298 // If originalUrl is null, just set it to url.
299 if (originalUrl == null) {
303 // Look for both the original url and the actual url. This takes in to
304 // account redirects.
305 String originalUrlNoQuery = removeQuery(originalUrl);
306 String urlNoQuery = removeQuery(url);
307 originalUrl = originalUrlNoQuery + '?';
308 url = urlNoQuery + '?';
310 // Use NoQuery to search for the base url (i.e. if the url is
311 // http://www.yahoo.com/?rs=1, search for http://www.yahoo.com)
312 // Use url to match the base url with other queries (i.e. if the url is
313 // http://www.google.com/m, search for
314 // http://www.google.com/m?some_query)
315 final String[] selArgs = new String[] {
316 originalUrlNoQuery, urlNoQuery, originalUrl, url };
317 String where = BookmarkColumns.URL + " == ? OR "
318 + BookmarkColumns.URL + " == ? OR "
319 + BookmarkColumns.URL + " GLOB ? || '*' OR "
320 + BookmarkColumns.URL + " GLOB ? || '*'";
322 where = "(" + where + ") AND " + BookmarkColumns.BOOKMARK + " == 1";
324 final String[] projection =
325 new String[] { Browser.BookmarkColumns._ID };
326 return cr.query(Browser.BOOKMARKS_URI, projection, where, selArgs,
330 // Strip the query from the given url.
331 private static String removeQuery(String url) {
335 int query = url.indexOf('?');
336 String noQuery = url;
338 noQuery = url.substring(0, query);
344 * How many items should be displayed in the list.
345 * @return Count of items.
347 public int getCount() {
355 public boolean areAllItemsEnabled() {
359 public boolean isEnabled(int position) {
364 * Get the data associated with the specified position in the list.
365 * @param position Index of the item whose data we want.
366 * @return The data at the specified position.
368 public Object getItem(int position) {
373 * Get the row id associated with the specified position in the list.
374 * @param position Index of the item whose row id we want.
375 * @return The id of the item at the specified position.
377 public long getItemId(int position) {
381 /* package */ void switchViewMode(BookmarkViewMode viewMode) {
382 mViewMode = viewMode;
385 /* package */ void populateBookmarkItem(BookmarkItem b, int position) {
386 mCursor.moveToPosition(position - mExtraOffset);
387 String url = mCursor.getString(Browser.HISTORY_PROJECTION_URL_INDEX);
389 b.setName(mCursor.getString(Browser.HISTORY_PROJECTION_TITLE_INDEX));
390 byte[] data = mCursor.getBlob(Browser.HISTORY_PROJECTION_FAVICON_INDEX);
391 Bitmap bitmap = null;
393 bitmap = CombinedBookmarkHistoryActivity.getIconListenerSet()
396 bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
398 b.setFavicon(bitmap);
402 * Get a View that displays the data at the specified position
404 * @param position Index of the item whose view we want.
405 * @return A View corresponding to the data at the specified position.
407 public View getView(int position, View convertView, ViewGroup parent) {
409 throw new IllegalStateException(
410 "this should only be called when the cursor is valid");
412 if (position < 0 || position > mCount) {
413 throw new AssertionError(
414 "BrowserBookmarksAdapter tried to get a view out of range");
416 if (mViewMode == BookmarkViewMode.GRID) {
417 if (convertView == null || convertView instanceof AddNewBookmark
418 || convertView instanceof BookmarkItem) {
419 LayoutInflater factory = LayoutInflater.from(mBookmarksPage);
421 = factory.inflate(R.layout.bookmark_thumbnail, null);
423 View holder = convertView.findViewById(R.id.holder);
424 ImageView thumb = (ImageView) convertView.findViewById(R.id.thumb);
425 TextView tv = (TextView) convertView.findViewById(R.id.label);
427 if (0 == position && mNeedsOffset) {
428 // This is to create a bookmark for the current page.
429 holder.setVisibility(View.VISIBLE);
430 tv.setText(mCurrentTitle);
432 if (mCurrentThumbnail != null) {
433 thumb.setImageBitmap(mCurrentThumbnail);
435 thumb.setImageResource(
436 R.drawable.browser_thumbnail);
440 holder.setVisibility(View.GONE);
441 mCursor.moveToPosition(position - mExtraOffset);
442 tv.setText(mCursor.getString(
443 Browser.HISTORY_PROJECTION_TITLE_INDEX));
444 Bitmap thumbnail = getBitmap(Browser.HISTORY_PROJECTION_THUMBNAIL_INDEX, position);
445 if (thumbnail == null) {
446 thumb.setImageResource(R.drawable.browser_thumbnail);
448 thumb.setImageBitmap(thumbnail);
454 if (position == 0 && mNeedsOffset) {
456 if (convertView instanceof AddNewBookmark) {
457 b = (AddNewBookmark) convertView;
459 b = new AddNewBookmark(mBookmarksPage);
461 b.setUrl(mCurrentPage);
465 if (convertView == null || !(convertView instanceof HistoryItem)) {
466 convertView = new HistoryItem(mBookmarksPage);
469 if (convertView == null || !(convertView instanceof BookmarkItem)) {
470 convertView = new BookmarkItem(mBookmarksPage);
473 bind((BookmarkItem) convertView, position);
475 ((HistoryItem) convertView).setIsBookmark(
476 getIsBookmark(position));
482 * Return the title for this item in the list.
484 public String getTitle(int position) {
485 return getString(Browser.HISTORY_PROJECTION_TITLE_INDEX, position);
489 * Return the Url for this item in the list.
491 public String getUrl(int position) {
492 return getString(Browser.HISTORY_PROJECTION_URL_INDEX, position);
496 * Return the favicon for this item in the list.
498 public Bitmap getFavicon(int position) {
499 return getBitmap(Browser.HISTORY_PROJECTION_FAVICON_INDEX, position);
502 public Bitmap getTouchIcon(int position) {
503 return getBitmap(Browser.HISTORY_PROJECTION_TOUCH_ICON_INDEX, position);
506 private Bitmap getBitmap(int cursorIndex, int position) {
507 if (position < mExtraOffset || position > mCount) {
510 mCursor.moveToPosition(position - mExtraOffset);
511 byte[] data = mCursor.getBlob(cursorIndex);
515 return BitmapFactory.decodeByteArray(data, 0, data.length);
519 * Return whether or not this item represents a bookmarked site.
521 public boolean getIsBookmark(int position) {
522 if (position < mExtraOffset || position > mCount) {
525 mCursor.moveToPosition(position - mExtraOffset);
526 return (1 == mCursor.getInt(Browser.HISTORY_PROJECTION_BOOKMARK_INDEX));
530 * Private helper function to return the title or url.
532 private String getString(int cursorIndex, int position) {
533 if (position < mExtraOffset || position > mCount) {
536 mCursor.moveToPosition(position- mExtraOffset);
537 return mCursor.getString(cursorIndex);
540 private void bind(BookmarkItem b, int position) {
541 mCursor.moveToPosition(position- mExtraOffset);
543 String title = mCursor.getString(Browser.HISTORY_PROJECTION_TITLE_INDEX);
544 if (title.length() > BrowserSettings.MAX_TEXTVIEW_LEN) {
545 title = title.substring(0, BrowserSettings.MAX_TEXTVIEW_LEN);
548 String url = mCursor.getString(Browser.HISTORY_PROJECTION_URL_INDEX);
549 if (url.length() > BrowserSettings.MAX_TEXTVIEW_LEN) {
550 url = url.substring(0, BrowserSettings.MAX_TEXTVIEW_LEN);
553 byte[] data = mCursor.getBlob(Browser.HISTORY_PROJECTION_FAVICON_INDEX);
555 b.setFavicon(BitmapFactory.decodeByteArray(data, 0, data.length));
557 b.setFavicon(CombinedBookmarkHistoryActivity.getIconListenerSet()
562 private class ChangeObserver extends ContentObserver {
563 public ChangeObserver() {
564 super(new Handler());
568 public boolean deliverSelfNotifications() {
573 public void onChange(boolean selfChange) {
578 private class MyDataSetObserver extends DataSetObserver {
580 public void onChanged() {
582 notifyDataSetChanged();
586 public void onInvalidated() {
588 notifyDataSetInvalidated();