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.View;
34 import android.view.ViewGroup;
35 import android.webkit.WebIconDatabase;
36 import android.webkit.WebIconDatabase.IconListener;
37 import android.widget.BaseAdapter;
39 import java.io.ByteArrayOutputStream;
41 class BrowserBookmarksAdapter extends BaseAdapter {
43 private String mCurrentPage;
44 private Cursor mCursor;
46 private String mLastWhereClause;
47 private String[] mLastSelectionArgs;
48 private String mLastOrderBy;
49 private BrowserBookmarksPage mBookmarksPage;
50 private ContentResolver mContentResolver;
51 private ChangeObserver mChangeObserver;
52 private DataSetObserver mDataSetObserver;
53 private boolean mDataValid;
55 // When true, this adapter is used to pick a bookmark to create a shortcut
56 private boolean mCreateShortcut;
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, 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 this(b, curPage, false);
80 * Create a new BrowserBookmarksAdapter.
81 * @param b BrowserBookmarksPage that instantiated this.
82 * Necessary so it will adjust its focus
83 * appropriately after a search.
85 public BrowserBookmarksAdapter(BrowserBookmarksPage b, String curPage,
86 boolean createShortcut) {
88 mCreateShortcut = createShortcut;
89 mExtraOffset = createShortcut ? 0 : 1;
91 mCurrentPage = b.getResources().getString(R.string.current_page) +
93 mContentResolver = b.getContentResolver();
94 mLastOrderBy = Browser.BookmarkColumns.CREATED + " DESC";
95 mChangeObserver = new ChangeObserver();
96 mDataSetObserver = new MyDataSetObserver();
97 // FIXME: Should have a default sort order that the user selects.
99 // FIXME: This requires another query of the database after the
100 // initial search(null). Can we optimize this?
101 Browser.requestAllIcons(mContentResolver,
102 Browser.BookmarkColumns.FAVICON + " is NULL AND " +
103 Browser.BookmarkColumns.BOOKMARK + " == 1", mIconReceiver);
107 * Return a hashmap with one row's Title, Url, and favicon.
108 * @param position Position in the list.
109 * @return Bundle Stores title, url of row position, favicon, and id
110 * for the url. Return a blank map if position is out of
113 public Bundle getRow(int position) {
114 Bundle map = new Bundle();
115 if (position < mExtraOffset || position >= mCount) {
118 mCursor.moveToPosition(position- mExtraOffset);
119 String url = mCursor.getString(Browser.HISTORY_PROJECTION_URL_INDEX);
120 map.putString(Browser.BookmarkColumns.TITLE,
121 mCursor.getString(Browser.HISTORY_PROJECTION_TITLE_INDEX));
122 map.putString(Browser.BookmarkColumns.URL, url);
123 byte[] data = mCursor.getBlob(Browser.HISTORY_PROJECTION_FAVICON_INDEX);
125 map.putParcelable(Browser.BookmarkColumns.FAVICON,
126 BitmapFactory.decodeByteArray(data, 0, data.length));
128 map.putInt("id", mCursor.getInt(Browser.HISTORY_PROJECTION_ID_INDEX));
133 * Update a row in the database with new information.
134 * Requeries the database if the information has changed.
135 * @param map Bundle storing id, title and url of new information
137 public void updateRow(Bundle map) {
140 int id = map.getInt("id");
142 for (mCursor.moveToFirst(); !mCursor.isAfterLast(); mCursor.moveToNext()) {
143 if (mCursor.getInt(Browser.HISTORY_PROJECTION_ID_INDEX) == id) {
144 position = mCursor.getPosition();
152 mCursor.moveToPosition(position);
153 ContentValues values = new ContentValues();
154 String title = map.getString(Browser.BookmarkColumns.TITLE);
155 if (!title.equals(mCursor
156 .getString(Browser.HISTORY_PROJECTION_TITLE_INDEX))) {
157 values.put(Browser.BookmarkColumns.TITLE, title);
159 String url = map.getString(Browser.BookmarkColumns.URL);
160 if (!url.equals(mCursor.
161 getString(Browser.HISTORY_PROJECTION_URL_INDEX))) {
162 values.put(Browser.BookmarkColumns.URL, url);
164 if (values.size() > 0
165 && mContentResolver.update(Browser.BOOKMARKS_URI, values,
166 "_id = " + id, null) != -1) {
172 * Delete a row from the database. Requeries the database.
173 * Does nothing if the provided position is out of range.
174 * @param position Position in the list.
176 public void deleteRow(int position) {
177 if (position < mExtraOffset || position >= getCount()) {
180 mCursor.moveToPosition(position- mExtraOffset);
181 String url = mCursor.getString(Browser.HISTORY_PROJECTION_URL_INDEX);
182 Bookmarks.removeFromBookmarks(null, mContentResolver, url);
187 * Delete all bookmarks from the db. Requeries the database.
188 * All bookmarks with become visited URLs or if never visited
191 public void deleteAllRows() {
192 StringBuilder deleteIds = null;
193 StringBuilder convertIds = null;
195 for (mCursor.moveToFirst(); !mCursor.isAfterLast(); mCursor.moveToNext()) {
196 String url = mCursor.getString(Browser.HISTORY_PROJECTION_URL_INDEX);
197 WebIconDatabase.getInstance().releaseIconForPageUrl(url);
198 int id = mCursor.getInt(Browser.HISTORY_PROJECTION_ID_INDEX);
199 int numVisits = mCursor.getInt(Browser.HISTORY_PROJECTION_VISITS_INDEX);
200 if (0 == numVisits) {
201 if (deleteIds == null) {
202 deleteIds = new StringBuilder();
203 deleteIds.append("( ");
205 deleteIds.append(" OR ( ");
207 deleteIds.append(BookmarkColumns._ID);
208 deleteIds.append(" = ");
209 deleteIds.append(id);
210 deleteIds.append(" )");
212 // It is no longer a bookmark, but it is still a visited site.
213 if (convertIds == null) {
214 convertIds = new StringBuilder();
215 convertIds.append("( ");
217 convertIds.append(" OR ( ");
219 convertIds.append(BookmarkColumns._ID);
220 convertIds.append(" = ");
221 convertIds.append(id);
222 convertIds.append(" )");
226 if (deleteIds != null) {
227 mContentResolver.delete(Browser.BOOKMARKS_URI, deleteIds.toString(),
230 if (convertIds != null) {
231 ContentValues values = new ContentValues();
232 values.put(Browser.BookmarkColumns.BOOKMARK, 0);
233 mContentResolver.update(Browser.BOOKMARKS_URI, values,
234 convertIds.toString(), null);
240 * Refresh list to recognize a change in the database.
242 public void refreshList() {
243 // FIXME: consider using requery().
244 // Need to do more work to get it to function though.
245 searchInternal(mLastWhereClause, mLastSelectionArgs, mLastOrderBy);
249 * Search the database for bookmarks that match the input string.
250 * @param like String to use to search the database. Strings with spaces
251 * are treated as having multiple search terms using the
252 * OR operator. Search both the title and url.
254 public void search(String like) {
255 String whereClause = Browser.BookmarkColumns.BOOKMARK + " == 1";
256 String[] selectionArgs = null;
258 String[] likes = like.split(" ");
260 boolean firstTerm = true;
261 StringBuilder andClause = new StringBuilder(256);
262 for (int j = 0; j < likes.length; j++) {
263 if (likes[j].length() > 0) {
267 andClause.append(" OR ");
269 andClause.append(Browser.BookmarkColumns.TITLE
270 + " LIKE ? OR " + Browser.BookmarkColumns.URL
276 selectionArgs = new String[count];
278 for (int j = 0; j < likes.length; j++) {
279 if (likes[j].length() > 0) {
280 like = "%" + likes[j] + "%";
281 selectionArgs[count++] = like;
282 selectionArgs[count++] = like;
285 whereClause += " AND (" + andClause + ")";
288 searchInternal(whereClause, selectionArgs, mLastOrderBy);
292 * Update the bookmark's favicon.
293 * @param cr The ContentResolver to use.
294 * @param url The url of the bookmark to update.
295 * @param favicon The favicon bitmap to write to the db.
297 /* package */ static void updateBookmarkFavicon(ContentResolver cr,
298 String url, Bitmap favicon) {
299 if (url == null || favicon == null) {
303 int query = url.indexOf('?');
304 String noQuery = url;
306 noQuery = url.substring(0, query);
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[] { noQuery, url };
315 final String where = "(" + Browser.BookmarkColumns.URL + " == ? OR "
316 + Browser.BookmarkColumns.URL + " GLOB ? || '*') AND "
317 + Browser.BookmarkColumns.BOOKMARK + " == 1";
318 final String[] projection = new String[] { Browser.BookmarkColumns._ID };
319 final Cursor c = cr.query(Browser.BOOKMARKS_URI, projection, where,
321 boolean succeed = c.moveToFirst();
322 ContentValues values = null;
324 if (values == null) {
325 final ByteArrayOutputStream os = new ByteArrayOutputStream();
326 favicon.compress(Bitmap.CompressFormat.PNG, 100, os);
327 values = new ContentValues();
328 values.put(Browser.BookmarkColumns.FAVICON, os.toByteArray());
330 cr.update(ContentUris.withAppendedId(Browser.BOOKMARKS_URI, c
331 .getInt(0)), values, null, null);
332 succeed = c.moveToNext();
338 * This sorts alphabetically, with non-capitalized titles before
341 public void sortAlphabetical() {
342 searchInternal(mLastWhereClause, mLastSelectionArgs,
343 Browser.BookmarkColumns.TITLE + " COLLATE UNICODE ASC");
347 * Internal function used in search, sort, and refreshList.
349 private void searchInternal(String whereClause, String[] selectionArgs,
351 if (mCursor != null) {
352 mCursor.unregisterContentObserver(mChangeObserver);
353 mCursor.unregisterDataSetObserver(mDataSetObserver);
354 mBookmarksPage.stopManagingCursor(mCursor);
355 mCursor.deactivate();
358 mLastWhereClause = whereClause;
359 mLastSelectionArgs = selectionArgs;
360 mLastOrderBy = orderBy;
361 mCursor = mContentResolver.query(
362 Browser.BOOKMARKS_URI,
363 Browser.HISTORY_PROJECTION,
367 mCursor.registerContentObserver(mChangeObserver);
368 mCursor.registerDataSetObserver(mDataSetObserver);
369 mBookmarksPage.startManagingCursor(mCursor);
372 notifyDataSetChanged();
374 mCount = mCursor.getCount() + mExtraOffset;
378 * How many items should be displayed in the list.
379 * @return Count of items.
381 public int getCount() {
389 public boolean areAllItemsEnabled() {
393 public boolean isEnabled(int position) {
398 * Get the data associated with the specified position in the list.
399 * @param position Index of the item whose data we want.
400 * @return The data at the specified position.
402 public Object getItem(int position) {
407 * Get the row id associated with the specified position in the list.
408 * @param position Index of the item whose row id we want.
409 * @return The id of the item at the specified position.
411 public long getItemId(int position) {
416 * Get a View that displays the data at the specified position
418 * @param position Index of the item whose view we want.
419 * @return A View corresponding to the data at the specified position.
421 public View getView(int position, View convertView, ViewGroup parent) {
423 throw new IllegalStateException(
424 "this should only be called when the cursor is valid");
426 if (position < 0 || position > mCount) {
427 throw new AssertionError(
428 "BrowserBookmarksAdapter tried to get a view out of range");
430 if (position == 0 && !mCreateShortcut) {
432 if (convertView instanceof AddNewBookmark) {
433 b = (AddNewBookmark) convertView;
435 b = new AddNewBookmark(mBookmarksPage);
437 b.setUrl(mCurrentPage);
440 if (convertView == null || convertView instanceof AddNewBookmark) {
441 convertView = new BookmarkItem(mBookmarksPage);
443 bind((BookmarkItem)convertView, position);
448 * Return the title for this item in the list.
450 public String getTitle(int position) {
451 return getString(Browser.HISTORY_PROJECTION_TITLE_INDEX, position);
455 * Return the Url for this item in the list.
457 public String getUrl(int position) {
458 return getString(Browser.HISTORY_PROJECTION_URL_INDEX, position);
462 * Return the favicon for this item in the list.
464 public Bitmap getFavicon(int position) {
465 if (position < mExtraOffset || position > mCount) {
468 mCursor.moveToPosition(position - mExtraOffset);
469 byte[] data = mCursor.getBlob(Browser.HISTORY_PROJECTION_FAVICON_INDEX);
473 return BitmapFactory.decodeByteArray(data, 0, data.length);
477 * Private helper function to return the title or url.
479 private String getString(int cursorIndex, int position) {
480 if (position < mExtraOffset || position > mCount) {
483 mCursor.moveToPosition(position- mExtraOffset);
484 return mCursor.getString(cursorIndex);
487 private void bind(BookmarkItem b, int position) {
488 mCursor.moveToPosition(position- mExtraOffset);
490 String title = mCursor.getString(Browser.HISTORY_PROJECTION_TITLE_INDEX);
491 if (title.length() > BrowserSettings.MAX_TEXTVIEW_LEN) {
492 title = title.substring(0, BrowserSettings.MAX_TEXTVIEW_LEN);
495 String url = mCursor.getString(Browser.HISTORY_PROJECTION_URL_INDEX);
496 if (url.length() > BrowserSettings.MAX_TEXTVIEW_LEN) {
497 url = url.substring(0, BrowserSettings.MAX_TEXTVIEW_LEN);
500 byte[] data = mCursor.getBlob(Browser.HISTORY_PROJECTION_FAVICON_INDEX);
502 b.setFavicon(BitmapFactory.decodeByteArray(data, 0, data.length));
508 private class ChangeObserver extends ContentObserver {
509 public ChangeObserver() {
510 super(new Handler());
514 public boolean deliverSelfNotifications() {
519 public void onChange(boolean selfChange) {
524 private class MyDataSetObserver extends DataSetObserver {
526 public void onChanged() {
528 notifyDataSetChanged();
532 public void onInvalidated() {
534 notifyDataSetInvalidated();