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