BrowserBookmarksAdapter.java revision 0c7865002753aea5de117ea4c08043dca95d07b6
1/* 2 * Copyright (C) 2006 The Android Open Source Project 3 * 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 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 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. 15 */ 16 17package com.android.browser; 18 19import android.content.ContentResolver; 20import android.content.ContentUris; 21import android.content.ContentValues; 22import android.database.ContentObserver; 23import android.database.Cursor; 24import android.database.DataSetObserver; 25import android.graphics.Bitmap; 26import android.graphics.BitmapFactory; 27import android.net.Uri; 28import android.os.Bundle; 29import android.os.Handler; 30import android.provider.Browser; 31import android.provider.Browser.BookmarkColumns; 32import android.view.KeyEvent; 33import android.view.LayoutInflater; 34import android.view.View; 35import android.view.ViewGroup; 36import android.webkit.WebIconDatabase; 37import android.webkit.WebIconDatabase.IconListener; 38import android.widget.BaseAdapter; 39import android.widget.ImageView; 40import android.widget.TextView; 41 42import java.io.ByteArrayOutputStream; 43 44class BrowserBookmarksAdapter extends BaseAdapter { 45 46 private String mCurrentPage; 47 private String mCurrentTitle; 48 private Cursor mCursor; 49 private int mCount; 50 private BrowserBookmarksPage mBookmarksPage; 51 private ContentResolver mContentResolver; 52 private boolean mDataValid; 53 private boolean mGridMode; 54 55 // When true, this adapter is used to pick a bookmark to create a shortcut 56 private boolean mCreateShortcut; 57 private int mExtraOffset; 58 59 // Implementation of WebIconDatabase.IconListener 60 private class IconReceiver implements IconListener { 61 public void onReceivedIcon(String url, Bitmap icon) { 62 updateBookmarkFavicon(mContentResolver, url, icon); 63 } 64 } 65 66 // Instance of IconReceiver 67 private final IconReceiver mIconReceiver = new IconReceiver(); 68 69 /** 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. 74 */ 75 public BrowserBookmarksAdapter(BrowserBookmarksPage b, String curPage, 76 String curTitle, boolean createShortcut) { 77 mDataValid = false; 78 mCreateShortcut = createShortcut; 79 mExtraOffset = createShortcut ? 0 : 1; 80 mBookmarksPage = b; 81 mCurrentPage = b.getResources().getString(R.string.current_page) 82 + curPage; 83 mCurrentTitle = curTitle; 84 mContentResolver = b.getContentResolver(); 85 mGridMode = false; 86 87 // FIXME: Should have a default sort order that the user selects. 88 String whereClause = Browser.BookmarkColumns.BOOKMARK + " != 0"; 89 String orderBy = Browser.BookmarkColumns.VISITS + " DESC"; 90 mCursor = b.managedQuery(Browser.BOOKMARKS_URI, 91 Browser.HISTORY_PROJECTION, whereClause, null, orderBy); 92 mCursor.registerContentObserver(new ChangeObserver()); 93 mCursor.registerDataSetObserver(new MyDataSetObserver()); 94 95 mDataValid = true; 96 notifyDataSetChanged(); 97 98 mCount = mCursor.getCount() + mExtraOffset; 99 100 // FIXME: This requires another query of the database after the 101 // managedQuery. Can we optimize this? 102 Browser.requestAllIcons(mContentResolver, 103 Browser.BookmarkColumns.FAVICON + " is NULL AND " + 104 Browser.BookmarkColumns.BOOKMARK + " == 1", mIconReceiver); 105 } 106 107 /** 108 * Return a hashmap with one row's Title, Url, and favicon. 109 * @param position Position in the list. 110 * @return Bundle Stores title, url of row position, favicon, and id 111 * for the url. Return a blank map if position is out of 112 * range. 113 */ 114 public Bundle getRow(int position) { 115 Bundle map = new Bundle(); 116 if (position < mExtraOffset || position >= mCount) { 117 return map; 118 } 119 mCursor.moveToPosition(position- mExtraOffset); 120 String url = mCursor.getString(Browser.HISTORY_PROJECTION_URL_INDEX); 121 map.putString(Browser.BookmarkColumns.TITLE, 122 mCursor.getString(Browser.HISTORY_PROJECTION_TITLE_INDEX)); 123 map.putString(Browser.BookmarkColumns.URL, url); 124 byte[] data = mCursor.getBlob(Browser.HISTORY_PROJECTION_FAVICON_INDEX); 125 if (data != null) { 126 map.putParcelable(Browser.BookmarkColumns.FAVICON, 127 BitmapFactory.decodeByteArray(data, 0, data.length)); 128 } 129 map.putInt("id", mCursor.getInt(Browser.HISTORY_PROJECTION_ID_INDEX)); 130 return map; 131 } 132 133 /** 134 * Update a row in the database with new information. 135 * Requeries the database if the information has changed. 136 * @param map Bundle storing id, title and url of new information 137 */ 138 public void updateRow(Bundle map) { 139 140 // Find the record 141 int id = map.getInt("id"); 142 int position = -1; 143 for (mCursor.moveToFirst(); !mCursor.isAfterLast(); mCursor.moveToNext()) { 144 if (mCursor.getInt(Browser.HISTORY_PROJECTION_ID_INDEX) == id) { 145 position = mCursor.getPosition(); 146 break; 147 } 148 } 149 if (position < 0) { 150 return; 151 } 152 153 mCursor.moveToPosition(position); 154 ContentValues values = new ContentValues(); 155 String title = map.getString(Browser.BookmarkColumns.TITLE); 156 if (!title.equals(mCursor 157 .getString(Browser.HISTORY_PROJECTION_TITLE_INDEX))) { 158 values.put(Browser.BookmarkColumns.TITLE, title); 159 } 160 String url = map.getString(Browser.BookmarkColumns.URL); 161 if (!url.equals(mCursor. 162 getString(Browser.HISTORY_PROJECTION_URL_INDEX))) { 163 values.put(Browser.BookmarkColumns.URL, url); 164 } 165 if (values.size() > 0 166 && mContentResolver.update(Browser.BOOKMARKS_URI, values, 167 "_id = " + id, null) != -1) { 168 refreshList(); 169 } 170 } 171 172 /** 173 * Delete a row from the database. Requeries the database. 174 * Does nothing if the provided position is out of range. 175 * @param position Position in the list. 176 */ 177 public void deleteRow(int position) { 178 if (position < mExtraOffset || position >= getCount()) { 179 return; 180 } 181 mCursor.moveToPosition(position- mExtraOffset); 182 String url = mCursor.getString(Browser.HISTORY_PROJECTION_URL_INDEX); 183 Bookmarks.removeFromBookmarks(null, mContentResolver, url); 184 refreshList(); 185 } 186 187 /** 188 * Delete all bookmarks from the db. Requeries the database. 189 * All bookmarks with become visited URLs or if never visited 190 * are removed 191 */ 192 public void deleteAllRows() { 193 StringBuilder deleteIds = null; 194 StringBuilder convertIds = null; 195 196 for (mCursor.moveToFirst(); !mCursor.isAfterLast(); mCursor.moveToNext()) { 197 String url = mCursor.getString(Browser.HISTORY_PROJECTION_URL_INDEX); 198 WebIconDatabase.getInstance().releaseIconForPageUrl(url); 199 int id = mCursor.getInt(Browser.HISTORY_PROJECTION_ID_INDEX); 200 int numVisits = mCursor.getInt(Browser.HISTORY_PROJECTION_VISITS_INDEX); 201 if (0 == numVisits) { 202 if (deleteIds == null) { 203 deleteIds = new StringBuilder(); 204 deleteIds.append("( "); 205 } else { 206 deleteIds.append(" OR ( "); 207 } 208 deleteIds.append(BookmarkColumns._ID); 209 deleteIds.append(" = "); 210 deleteIds.append(id); 211 deleteIds.append(" )"); 212 } else { 213 // It is no longer a bookmark, but it is still a visited site. 214 if (convertIds == null) { 215 convertIds = new StringBuilder(); 216 convertIds.append("( "); 217 } else { 218 convertIds.append(" OR ( "); 219 } 220 convertIds.append(BookmarkColumns._ID); 221 convertIds.append(" = "); 222 convertIds.append(id); 223 convertIds.append(" )"); 224 } 225 } 226 227 if (deleteIds != null) { 228 mContentResolver.delete(Browser.BOOKMARKS_URI, deleteIds.toString(), 229 null); 230 } 231 if (convertIds != null) { 232 ContentValues values = new ContentValues(); 233 values.put(Browser.BookmarkColumns.BOOKMARK, 0); 234 mContentResolver.update(Browser.BOOKMARKS_URI, values, 235 convertIds.toString(), null); 236 } 237 refreshList(); 238 } 239 240 /** 241 * Refresh list to recognize a change in the database. 242 */ 243 public void refreshList() { 244 mCursor.requery(); 245 mCount = mCursor.getCount() + mExtraOffset; 246 notifyDataSetChanged(); 247 } 248 249 /** 250 * Update the bookmark's favicon. 251 * @param cr The ContentResolver to use. 252 * @param url The url of the bookmark to update. 253 * @param favicon The favicon bitmap to write to the db. 254 */ 255 /* package */ static void updateBookmarkFavicon(ContentResolver cr, 256 String url, Bitmap favicon) { 257 if (url == null || favicon == null) { 258 return; 259 } 260 // Strip the query. 261 int query = url.indexOf('?'); 262 String noQuery = url; 263 if (query != -1) { 264 noQuery = url.substring(0, query); 265 } 266 url = noQuery + '?'; 267 // Use noQuery to search for the base url (i.e. if the url is 268 // http://www.yahoo.com/?rs=1, search for http://www.yahoo.com) 269 // Use url to match the base url with other queries (i.e. if the url is 270 // http://www.google.com/m, search for 271 // http://www.google.com/m?some_query) 272 final String[] selArgs = new String[] { noQuery, url }; 273 final String where = "(" + Browser.BookmarkColumns.URL + " == ? OR " 274 + Browser.BookmarkColumns.URL + " GLOB ? || '*') AND " 275 + Browser.BookmarkColumns.BOOKMARK + " == 1"; 276 final String[] projection = new String[] { Browser.BookmarkColumns._ID }; 277 final Cursor c = cr.query(Browser.BOOKMARKS_URI, projection, where, 278 selArgs, null); 279 boolean succeed = c.moveToFirst(); 280 ContentValues values = null; 281 while (succeed) { 282 if (values == null) { 283 final ByteArrayOutputStream os = new ByteArrayOutputStream(); 284 favicon.compress(Bitmap.CompressFormat.PNG, 100, os); 285 values = new ContentValues(); 286 values.put(Browser.BookmarkColumns.FAVICON, os.toByteArray()); 287 } 288 cr.update(ContentUris.withAppendedId(Browser.BOOKMARKS_URI, c 289 .getInt(0)), values, null, null); 290 succeed = c.moveToNext(); 291 } 292 c.close(); 293 } 294 295 /** 296 * How many items should be displayed in the list. 297 * @return Count of items. 298 */ 299 public int getCount() { 300 if (mDataValid) { 301 return mCount; 302 } else { 303 return 0; 304 } 305 } 306 307 public boolean areAllItemsEnabled() { 308 return true; 309 } 310 311 public boolean isEnabled(int position) { 312 return true; 313 } 314 315 /** 316 * Get the data associated with the specified position in the list. 317 * @param position Index of the item whose data we want. 318 * @return The data at the specified position. 319 */ 320 public Object getItem(int position) { 321 return null; 322 } 323 324 /** 325 * Get the row id associated with the specified position in the list. 326 * @param position Index of the item whose row id we want. 327 * @return The id of the item at the specified position. 328 */ 329 public long getItemId(int position) { 330 return position; 331 } 332 333 /* package */ void switchViewMode(boolean toGrid) { 334 mGridMode = toGrid; 335 } 336 337 /* package */ void populateBookmarkItem(BookmarkItem b, int position) { 338 mCursor.moveToPosition(position - mExtraOffset); 339 b.setUrl(mCursor.getString(Browser.HISTORY_PROJECTION_URL_INDEX)); 340 b.setName(mCursor.getString(Browser.HISTORY_PROJECTION_TITLE_INDEX)); 341 byte[] data = mCursor.getBlob(Browser.HISTORY_PROJECTION_FAVICON_INDEX); 342 Bitmap bitmap = (null == data) ? null : 343 BitmapFactory.decodeByteArray(data, 0, data.length); 344 b.setFavicon(bitmap); 345 } 346 347 /** 348 * Get a View that displays the data at the specified position 349 * in the list. 350 * @param position Index of the item whose view we want. 351 * @return A View corresponding to the data at the specified position. 352 */ 353 public View getView(int position, View convertView, ViewGroup parent) { 354 if (!mDataValid) { 355 throw new IllegalStateException( 356 "this should only be called when the cursor is valid"); 357 } 358 if (position < 0 || position > mCount) { 359 throw new AssertionError( 360 "BrowserBookmarksAdapter tried to get a view out of range"); 361 } 362 if (mGridMode) { 363 if (convertView == null || convertView instanceof AddNewBookmark 364 || convertView instanceof BookmarkItem) { 365 LayoutInflater factory = LayoutInflater.from(mBookmarksPage); 366 convertView 367 = factory.inflate(R.layout.bookmark_thumbnail, null); 368 } 369 View holder = convertView.findViewById(R.id.holder); 370 ImageView thumb = (ImageView) convertView.findViewById(R.id.thumb); 371 TextView tv = (TextView) convertView.findViewById(R.id.label); 372 373 if (0 == position && !mCreateShortcut) { 374 // This is to create a bookmark for the current page. 375 holder.setVisibility(View.VISIBLE); 376 tv.setText(mCurrentTitle); 377 // FIXME: Want to show the screenshot of the current page 378 thumb.setImageResource(R.drawable.blank); 379 return convertView; 380 } 381 holder.setVisibility(View.GONE); 382 mCursor.moveToPosition(position - mExtraOffset); 383 tv.setText(mCursor.getString( 384 Browser.HISTORY_PROJECTION_TITLE_INDEX)); 385 byte[] data = mCursor.getBlob( 386 Browser.HISTORY_PROJECTION_THUMBNAIL_INDEX); 387 if (data == null) { 388 // Backup is to just show white 389 thumb.setImageResource(R.drawable.blank); 390 } else { 391 thumb.setImageBitmap( 392 BitmapFactory.decodeByteArray(data, 0, data.length)); 393 } 394 395 return convertView; 396 397 } 398 if (position == 0 && !mCreateShortcut) { 399 AddNewBookmark b; 400 if (convertView instanceof AddNewBookmark) { 401 b = (AddNewBookmark) convertView; 402 } else { 403 b = new AddNewBookmark(mBookmarksPage); 404 } 405 b.setUrl(mCurrentPage); 406 return b; 407 } 408 if (convertView == null || !(convertView instanceof BookmarkItem)) { 409 convertView = new BookmarkItem(mBookmarksPage); 410 } 411 bind((BookmarkItem)convertView, position); 412 return convertView; 413 } 414 415 /** 416 * Return the title for this item in the list. 417 */ 418 public String getTitle(int position) { 419 return getString(Browser.HISTORY_PROJECTION_TITLE_INDEX, position); 420 } 421 422 /** 423 * Return the Url for this item in the list. 424 */ 425 public String getUrl(int position) { 426 return getString(Browser.HISTORY_PROJECTION_URL_INDEX, position); 427 } 428 429 /** 430 * Return the favicon for this item in the list. 431 */ 432 public Bitmap getFavicon(int position) { 433 if (position < mExtraOffset || position > mCount) { 434 return null; 435 } 436 mCursor.moveToPosition(position - mExtraOffset); 437 byte[] data = mCursor.getBlob(Browser.HISTORY_PROJECTION_FAVICON_INDEX); 438 if (data == null) { 439 return null; 440 } 441 return BitmapFactory.decodeByteArray(data, 0, data.length); 442 } 443 444 /** 445 * Private helper function to return the title or url. 446 */ 447 private String getString(int cursorIndex, int position) { 448 if (position < mExtraOffset || position > mCount) { 449 return ""; 450 } 451 mCursor.moveToPosition(position- mExtraOffset); 452 return mCursor.getString(cursorIndex); 453 } 454 455 private void bind(BookmarkItem b, int position) { 456 mCursor.moveToPosition(position- mExtraOffset); 457 458 String title = mCursor.getString(Browser.HISTORY_PROJECTION_TITLE_INDEX); 459 if (title.length() > BrowserSettings.MAX_TEXTVIEW_LEN) { 460 title = title.substring(0, BrowserSettings.MAX_TEXTVIEW_LEN); 461 } 462 b.setName(title); 463 String url = mCursor.getString(Browser.HISTORY_PROJECTION_URL_INDEX); 464 if (url.length() > BrowserSettings.MAX_TEXTVIEW_LEN) { 465 url = url.substring(0, BrowserSettings.MAX_TEXTVIEW_LEN); 466 } 467 b.setUrl(url); 468 byte[] data = mCursor.getBlob(Browser.HISTORY_PROJECTION_FAVICON_INDEX); 469 if (data != null) { 470 b.setFavicon(BitmapFactory.decodeByteArray(data, 0, data.length)); 471 } else { 472 b.setFavicon(null); 473 } 474 } 475 476 private class ChangeObserver extends ContentObserver { 477 public ChangeObserver() { 478 super(new Handler()); 479 } 480 481 @Override 482 public boolean deliverSelfNotifications() { 483 return true; 484 } 485 486 @Override 487 public void onChange(boolean selfChange) { 488 refreshList(); 489 } 490 } 491 492 private class MyDataSetObserver extends DataSetObserver { 493 @Override 494 public void onChanged() { 495 mDataValid = true; 496 notifyDataSetChanged(); 497 } 498 499 @Override 500 public void onInvalidated() { 501 mDataValid = false; 502 notifyDataSetInvalidated(); 503 } 504 } 505} 506