blob: c3ccdfd3f60f397bf0d09ae3d34fa81b648d2ada [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;
Patrick Scott3918d442009-08-04 13:22:29 -040038import android.webkit.WebView;
The Android Open Source Project0c908882009-03-03 19:32:16 -080039import android.widget.BaseAdapter;
Leon Scroggins892df312009-07-14 14:48:02 -040040import android.widget.ImageView;
41import android.widget.TextView;
The Android Open Source Project0c908882009-03-03 19:32:16 -080042
43import java.io.ByteArrayOutputStream;
44
45class BrowserBookmarksAdapter extends BaseAdapter {
46
The Android Open Source Project0c908882009-03-03 19:32:16 -080047 private String mCurrentPage;
Leon Scroggins89c6d362009-07-15 16:54:37 -040048 private String mCurrentTitle;
The Android Open Source Project0c908882009-03-03 19:32:16 -080049 private Cursor mCursor;
50 private int mCount;
The Android Open Source Project0c908882009-03-03 19:32:16 -080051 private BrowserBookmarksPage mBookmarksPage;
52 private ContentResolver mContentResolver;
The Android Open Source Project0c908882009-03-03 19:32:16 -080053 private boolean mDataValid;
Leon Scroggins892df312009-07-14 14:48:02 -040054 private boolean mGridMode;
Leon Scroggins892df312009-07-14 14:48:02 -040055
The Android Open Source Project0c908882009-03-03 19:32:16 -080056 // When true, this adapter is used to pick a bookmark to create a shortcut
57 private boolean mCreateShortcut;
58 private int mExtraOffset;
59
60 // Implementation of WebIconDatabase.IconListener
61 private class IconReceiver implements IconListener {
62 public void onReceivedIcon(String url, Bitmap icon) {
Patrick Scott3918d442009-08-04 13:22:29 -040063 updateBookmarkFavicon(mContentResolver, null, url, icon);
The Android Open Source Project0c908882009-03-03 19:32:16 -080064 }
65 }
66
67 // Instance of IconReceiver
68 private final IconReceiver mIconReceiver = new IconReceiver();
69
70 /**
71 * Create a new BrowserBookmarksAdapter.
The Android Open Source Project0c908882009-03-03 19:32:16 -080072 * @param b BrowserBookmarksPage that instantiated this.
73 * Necessary so it will adjust its focus
74 * appropriately after a search.
75 */
76 public BrowserBookmarksAdapter(BrowserBookmarksPage b, String curPage,
Leon Scroggins89c6d362009-07-15 16:54:37 -040077 String curTitle, boolean createShortcut) {
The Android Open Source Project0c908882009-03-03 19:32:16 -080078 mDataValid = false;
79 mCreateShortcut = createShortcut;
80 mExtraOffset = createShortcut ? 0 : 1;
81 mBookmarksPage = b;
Leon Scroggins89c6d362009-07-15 16:54:37 -040082 mCurrentPage = b.getResources().getString(R.string.current_page)
83 + curPage;
84 mCurrentTitle = curTitle;
The Android Open Source Project0c908882009-03-03 19:32:16 -080085 mContentResolver = b.getContentResolver();
Leon Scroggins892df312009-07-14 14:48:02 -040086 mGridMode = false;
87
The Android Open Source Project0c908882009-03-03 19:32:16 -080088 // FIXME: Should have a default sort order that the user selects.
Leon Scroggins892df312009-07-14 14:48:02 -040089 String whereClause = Browser.BookmarkColumns.BOOKMARK + " != 0";
90 String orderBy = Browser.BookmarkColumns.VISITS + " DESC";
91 mCursor = b.managedQuery(Browser.BOOKMARKS_URI,
92 Browser.HISTORY_PROJECTION, whereClause, null, orderBy);
93 mCursor.registerContentObserver(new ChangeObserver());
94 mCursor.registerDataSetObserver(new MyDataSetObserver());
95
96 mDataValid = true;
97 notifyDataSetChanged();
98
99 mCount = mCursor.getCount() + mExtraOffset;
100
The Android Open Source Project0c908882009-03-03 19:32:16 -0800101 // FIXME: This requires another query of the database after the
Leon Scroggins89c6d362009-07-15 16:54:37 -0400102 // managedQuery. Can we optimize this?
The Android Open Source Project0c908882009-03-03 19:32:16 -0800103 Browser.requestAllIcons(mContentResolver,
104 Browser.BookmarkColumns.FAVICON + " is NULL AND " +
105 Browser.BookmarkColumns.BOOKMARK + " == 1", mIconReceiver);
106 }
107
108 /**
109 * Return a hashmap with one row's Title, Url, and favicon.
110 * @param position Position in the list.
111 * @return Bundle Stores title, url of row position, favicon, and id
112 * for the url. Return a blank map if position is out of
113 * range.
114 */
115 public Bundle getRow(int position) {
116 Bundle map = new Bundle();
117 if (position < mExtraOffset || position >= mCount) {
118 return map;
119 }
120 mCursor.moveToPosition(position- mExtraOffset);
121 String url = mCursor.getString(Browser.HISTORY_PROJECTION_URL_INDEX);
122 map.putString(Browser.BookmarkColumns.TITLE,
123 mCursor.getString(Browser.HISTORY_PROJECTION_TITLE_INDEX));
124 map.putString(Browser.BookmarkColumns.URL, url);
125 byte[] data = mCursor.getBlob(Browser.HISTORY_PROJECTION_FAVICON_INDEX);
126 if (data != null) {
127 map.putParcelable(Browser.BookmarkColumns.FAVICON,
128 BitmapFactory.decodeByteArray(data, 0, data.length));
129 }
130 map.putInt("id", mCursor.getInt(Browser.HISTORY_PROJECTION_ID_INDEX));
131 return map;
132 }
133
134 /**
135 * Update a row in the database with new information.
136 * Requeries the database if the information has changed.
137 * @param map Bundle storing id, title and url of new information
138 */
139 public void updateRow(Bundle map) {
140
141 // Find the record
142 int id = map.getInt("id");
143 int position = -1;
144 for (mCursor.moveToFirst(); !mCursor.isAfterLast(); mCursor.moveToNext()) {
145 if (mCursor.getInt(Browser.HISTORY_PROJECTION_ID_INDEX) == id) {
146 position = mCursor.getPosition();
147 break;
148 }
149 }
150 if (position < 0) {
151 return;
152 }
153
154 mCursor.moveToPosition(position);
155 ContentValues values = new ContentValues();
156 String title = map.getString(Browser.BookmarkColumns.TITLE);
157 if (!title.equals(mCursor
158 .getString(Browser.HISTORY_PROJECTION_TITLE_INDEX))) {
159 values.put(Browser.BookmarkColumns.TITLE, title);
160 }
161 String url = map.getString(Browser.BookmarkColumns.URL);
162 if (!url.equals(mCursor.
163 getString(Browser.HISTORY_PROJECTION_URL_INDEX))) {
164 values.put(Browser.BookmarkColumns.URL, url);
165 }
166 if (values.size() > 0
167 && mContentResolver.update(Browser.BOOKMARKS_URI, values,
168 "_id = " + id, null) != -1) {
169 refreshList();
170 }
171 }
172
173 /**
174 * Delete a row from the database. Requeries the database.
175 * Does nothing if the provided position is out of range.
176 * @param position Position in the list.
177 */
178 public void deleteRow(int position) {
179 if (position < mExtraOffset || position >= getCount()) {
180 return;
181 }
182 mCursor.moveToPosition(position- mExtraOffset);
183 String url = mCursor.getString(Browser.HISTORY_PROJECTION_URL_INDEX);
Leon Scrogginse372c022009-06-12 17:07:29 -0400184 Bookmarks.removeFromBookmarks(null, mContentResolver, url);
The Android Open Source Project0c908882009-03-03 19:32:16 -0800185 refreshList();
186 }
187
188 /**
189 * Delete all bookmarks from the db. Requeries the database.
190 * All bookmarks with become visited URLs or if never visited
191 * are removed
192 */
193 public void deleteAllRows() {
194 StringBuilder deleteIds = null;
195 StringBuilder convertIds = null;
196
197 for (mCursor.moveToFirst(); !mCursor.isAfterLast(); mCursor.moveToNext()) {
198 String url = mCursor.getString(Browser.HISTORY_PROJECTION_URL_INDEX);
199 WebIconDatabase.getInstance().releaseIconForPageUrl(url);
200 int id = mCursor.getInt(Browser.HISTORY_PROJECTION_ID_INDEX);
201 int numVisits = mCursor.getInt(Browser.HISTORY_PROJECTION_VISITS_INDEX);
202 if (0 == numVisits) {
203 if (deleteIds == null) {
204 deleteIds = new StringBuilder();
205 deleteIds.append("( ");
206 } else {
207 deleteIds.append(" OR ( ");
208 }
209 deleteIds.append(BookmarkColumns._ID);
210 deleteIds.append(" = ");
211 deleteIds.append(id);
212 deleteIds.append(" )");
213 } else {
214 // It is no longer a bookmark, but it is still a visited site.
215 if (convertIds == null) {
216 convertIds = new StringBuilder();
217 convertIds.append("( ");
218 } else {
219 convertIds.append(" OR ( ");
220 }
221 convertIds.append(BookmarkColumns._ID);
222 convertIds.append(" = ");
223 convertIds.append(id);
224 convertIds.append(" )");
225 }
226 }
227
228 if (deleteIds != null) {
229 mContentResolver.delete(Browser.BOOKMARKS_URI, deleteIds.toString(),
230 null);
231 }
232 if (convertIds != null) {
233 ContentValues values = new ContentValues();
234 values.put(Browser.BookmarkColumns.BOOKMARK, 0);
235 mContentResolver.update(Browser.BOOKMARKS_URI, values,
236 convertIds.toString(), null);
237 }
238 refreshList();
239 }
240
241 /**
242 * Refresh list to recognize a change in the database.
243 */
244 public void refreshList() {
Leon Scroggins892df312009-07-14 14:48:02 -0400245 mCursor.requery();
246 mCount = mCursor.getCount() + mExtraOffset;
247 notifyDataSetChanged();
The Android Open Source Project0c908882009-03-03 19:32:16 -0800248 }
249
250 /**
Patrick Scott3918d442009-08-04 13:22:29 -0400251 * Update the bookmark's favicon. This is a convenience method for updating
252 * a bookmark favicon for the originalUrl and url of the passed in WebView.
The Android Open Source Project0c908882009-03-03 19:32:16 -0800253 * @param cr The ContentResolver to use.
Patrick Scott3918d442009-08-04 13:22:29 -0400254 * @param WebView The WebView containing the url to update.
The Android Open Source Project0c908882009-03-03 19:32:16 -0800255 * @param favicon The favicon bitmap to write to the db.
256 */
257 /* package */ static void updateBookmarkFavicon(ContentResolver cr,
Patrick Scott3918d442009-08-04 13:22:29 -0400258 WebView view, Bitmap favicon) {
259 if (view != null) {
260 updateBookmarkFavicon(cr, view.getOriginalUrl(), view.getUrl(),
261 favicon);
262 }
263 }
264
265 private static void updateBookmarkFavicon(ContentResolver cr,
266 String originalUrl, String url, Bitmap favicon) {
267 final Cursor c = queryBookmarksForUrl(cr, originalUrl, url);
268 if (c == null) {
The Android Open Source Project0c908882009-03-03 19:32:16 -0800269 return;
270 }
The Android Open Source Project0c908882009-03-03 19:32:16 -0800271 boolean succeed = c.moveToFirst();
272 ContentValues values = null;
273 while (succeed) {
274 if (values == null) {
275 final ByteArrayOutputStream os = new ByteArrayOutputStream();
276 favicon.compress(Bitmap.CompressFormat.PNG, 100, os);
277 values = new ContentValues();
278 values.put(Browser.BookmarkColumns.FAVICON, os.toByteArray());
279 }
280 cr.update(ContentUris.withAppendedId(Browser.BOOKMARKS_URI, c
281 .getInt(0)), values, null, null);
282 succeed = c.moveToNext();
283 }
284 c.close();
285 }
286
Patrick Scott3918d442009-08-04 13:22:29 -0400287 /* package */ static Cursor queryBookmarksForUrl(ContentResolver cr,
288 String originalUrl, String url) {
289 if (cr == null || url == null) {
290 return null;
291 }
292
293 // If originalUrl is null, just set it to url.
294 if (originalUrl == null) {
295 originalUrl = url;
296 }
297
298 // Look for both the original url and the actual url. This takes in to
299 // account redirects.
300 String originalUrlNoQuery = removeQuery(originalUrl);
301 String urlNoQuery = removeQuery(url);
302 originalUrl = originalUrlNoQuery + '?';
303 url = urlNoQuery + '?';
304
305 // Use NoQuery to search for the base url (i.e. if the url is
306 // http://www.yahoo.com/?rs=1, search for http://www.yahoo.com)
307 // Use url to match the base url with other queries (i.e. if the url is
308 // http://www.google.com/m, search for
309 // http://www.google.com/m?some_query)
310 final String[] selArgs = new String[] {
311 originalUrlNoQuery, urlNoQuery, originalUrl, url };
312 final String where = "(" + BookmarkColumns.URL + " == ? OR "
313 + BookmarkColumns.URL + " == ? OR "
314 + BookmarkColumns.URL + " GLOB ? || '*' OR "
315 + BookmarkColumns.URL + " GLOB ? || '*') AND "
316 + BookmarkColumns.BOOKMARK + " == 1";
317 final String[] projection =
318 new String[] { Browser.BookmarkColumns._ID };
319 return cr.query(Browser.BOOKMARKS_URI, projection, where, selArgs,
320 null);
321 }
322
323 // Strip the query from the given url.
324 private static String removeQuery(String url) {
325 if (url == null) {
326 return null;
327 }
328 int query = url.indexOf('?');
329 String noQuery = url;
330 if (query != -1) {
331 noQuery = url.substring(0, query);
332 }
333 return noQuery;
334 }
335
The Android Open Source Project0c908882009-03-03 19:32:16 -0800336 /**
The Android Open Source Project0c908882009-03-03 19:32:16 -0800337 * How many items should be displayed in the list.
338 * @return Count of items.
339 */
340 public int getCount() {
341 if (mDataValid) {
342 return mCount;
343 } else {
344 return 0;
345 }
346 }
347
348 public boolean areAllItemsEnabled() {
349 return true;
350 }
351
352 public boolean isEnabled(int position) {
353 return true;
354 }
355
356 /**
357 * Get the data associated with the specified position in the list.
358 * @param position Index of the item whose data we want.
359 * @return The data at the specified position.
360 */
361 public Object getItem(int position) {
362 return null;
363 }
364
365 /**
366 * Get the row id associated with the specified position in the list.
367 * @param position Index of the item whose row id we want.
368 * @return The id of the item at the specified position.
369 */
370 public long getItemId(int position) {
371 return position;
372 }
373
Leon Scroggins892df312009-07-14 14:48:02 -0400374 /* package */ void switchViewMode(boolean toGrid) {
375 mGridMode = toGrid;
376 }
377
378 /* package */ void populateBookmarkItem(BookmarkItem b, int position) {
379 mCursor.moveToPosition(position - mExtraOffset);
380 b.setUrl(mCursor.getString(Browser.HISTORY_PROJECTION_URL_INDEX));
381 b.setName(mCursor.getString(Browser.HISTORY_PROJECTION_TITLE_INDEX));
382 byte[] data = mCursor.getBlob(Browser.HISTORY_PROJECTION_FAVICON_INDEX);
383 Bitmap bitmap = (null == data) ? null :
384 BitmapFactory.decodeByteArray(data, 0, data.length);
385 b.setFavicon(bitmap);
386 }
387
The Android Open Source Project0c908882009-03-03 19:32:16 -0800388 /**
389 * Get a View that displays the data at the specified position
390 * in the list.
391 * @param position Index of the item whose view we want.
392 * @return A View corresponding to the data at the specified position.
393 */
394 public View getView(int position, View convertView, ViewGroup parent) {
395 if (!mDataValid) {
396 throw new IllegalStateException(
397 "this should only be called when the cursor is valid");
398 }
399 if (position < 0 || position > mCount) {
400 throw new AssertionError(
401 "BrowserBookmarksAdapter tried to get a view out of range");
402 }
Leon Scroggins892df312009-07-14 14:48:02 -0400403 if (mGridMode) {
404 if (convertView == null || convertView instanceof AddNewBookmark
405 || convertView instanceof BookmarkItem) {
406 LayoutInflater factory = LayoutInflater.from(mBookmarksPage);
407 convertView
408 = factory.inflate(R.layout.bookmark_thumbnail, null);
409 }
Leon Scroggins89c6d362009-07-15 16:54:37 -0400410 View holder = convertView.findViewById(R.id.holder);
Leon Scroggins892df312009-07-14 14:48:02 -0400411 ImageView thumb = (ImageView) convertView.findViewById(R.id.thumb);
Leon Scroggins892df312009-07-14 14:48:02 -0400412 TextView tv = (TextView) convertView.findViewById(R.id.label);
413
Leon Scroggins892df312009-07-14 14:48:02 -0400414 if (0 == position && !mCreateShortcut) {
415 // This is to create a bookmark for the current page.
Leon Scroggins89c6d362009-07-15 16:54:37 -0400416 holder.setVisibility(View.VISIBLE);
Leon Scroggins89c6d362009-07-15 16:54:37 -0400417 tv.setText(mCurrentTitle);
418 // FIXME: Want to show the screenshot of the current page
419 thumb.setImageResource(R.drawable.blank);
Leon Scroggins892df312009-07-14 14:48:02 -0400420 return convertView;
421 }
Leon Scroggins89c6d362009-07-15 16:54:37 -0400422 holder.setVisibility(View.GONE);
Leon Scroggins892df312009-07-14 14:48:02 -0400423 mCursor.moveToPosition(position - mExtraOffset);
424 tv.setText(mCursor.getString(
425 Browser.HISTORY_PROJECTION_TITLE_INDEX));
426 byte[] data = mCursor.getBlob(
427 Browser.HISTORY_PROJECTION_THUMBNAIL_INDEX);
428 if (data == null) {
429 // Backup is to just show white
430 thumb.setImageResource(R.drawable.blank);
431 } else {
432 thumb.setImageBitmap(
433 BitmapFactory.decodeByteArray(data, 0, data.length));
434 }
Leon Scroggins89c6d362009-07-15 16:54:37 -0400435
Leon Scroggins892df312009-07-14 14:48:02 -0400436 return convertView;
437
438 }
The Android Open Source Project0c908882009-03-03 19:32:16 -0800439 if (position == 0 && !mCreateShortcut) {
440 AddNewBookmark b;
441 if (convertView instanceof AddNewBookmark) {
442 b = (AddNewBookmark) convertView;
443 } else {
444 b = new AddNewBookmark(mBookmarksPage);
445 }
446 b.setUrl(mCurrentPage);
447 return b;
448 }
Leon Scroggins892df312009-07-14 14:48:02 -0400449 if (convertView == null || !(convertView instanceof BookmarkItem)) {
The Android Open Source Project0c908882009-03-03 19:32:16 -0800450 convertView = new BookmarkItem(mBookmarksPage);
451 }
452 bind((BookmarkItem)convertView, position);
453 return convertView;
454 }
455
456 /**
457 * Return the title for this item in the list.
458 */
459 public String getTitle(int position) {
460 return getString(Browser.HISTORY_PROJECTION_TITLE_INDEX, position);
461 }
462
463 /**
464 * Return the Url for this item in the list.
465 */
466 public String getUrl(int position) {
467 return getString(Browser.HISTORY_PROJECTION_URL_INDEX, position);
468 }
469
470 /**
Patrick Scotte09761e2009-03-24 20:43:37 -0700471 * Return the favicon for this item in the list.
472 */
473 public Bitmap getFavicon(int position) {
Patrick Scott3918d442009-08-04 13:22:29 -0400474 return getBitmap(Browser.HISTORY_PROJECTION_FAVICON_INDEX, position);
475 }
476
477 public Bitmap getTouchIcon(int position) {
478 return getBitmap(Browser.HISTORY_PROJECTION_TOUCH_ICON_INDEX, position);
479 }
480
481 private Bitmap getBitmap(int cursorIndex, int position) {
Patrick Scotte09761e2009-03-24 20:43:37 -0700482 if (position < mExtraOffset || position > mCount) {
483 return null;
484 }
485 mCursor.moveToPosition(position - mExtraOffset);
Patrick Scott3918d442009-08-04 13:22:29 -0400486 byte[] data = mCursor.getBlob(cursorIndex);
Patrick Scotte09761e2009-03-24 20:43:37 -0700487 if (data == null) {
488 return null;
489 }
490 return BitmapFactory.decodeByteArray(data, 0, data.length);
491 }
492
493 /**
The Android Open Source Project0c908882009-03-03 19:32:16 -0800494 * Private helper function to return the title or url.
495 */
496 private String getString(int cursorIndex, int position) {
497 if (position < mExtraOffset || position > mCount) {
498 return "";
499 }
500 mCursor.moveToPosition(position- mExtraOffset);
501 return mCursor.getString(cursorIndex);
502 }
503
504 private void bind(BookmarkItem b, int position) {
505 mCursor.moveToPosition(position- mExtraOffset);
506
507 String title = mCursor.getString(Browser.HISTORY_PROJECTION_TITLE_INDEX);
508 if (title.length() > BrowserSettings.MAX_TEXTVIEW_LEN) {
509 title = title.substring(0, BrowserSettings.MAX_TEXTVIEW_LEN);
510 }
511 b.setName(title);
512 String url = mCursor.getString(Browser.HISTORY_PROJECTION_URL_INDEX);
513 if (url.length() > BrowserSettings.MAX_TEXTVIEW_LEN) {
514 url = url.substring(0, BrowserSettings.MAX_TEXTVIEW_LEN);
515 }
516 b.setUrl(url);
517 byte[] data = mCursor.getBlob(Browser.HISTORY_PROJECTION_FAVICON_INDEX);
518 if (data != null) {
519 b.setFavicon(BitmapFactory.decodeByteArray(data, 0, data.length));
520 } else {
521 b.setFavicon(null);
522 }
523 }
524
525 private class ChangeObserver extends ContentObserver {
526 public ChangeObserver() {
527 super(new Handler());
528 }
529
530 @Override
531 public boolean deliverSelfNotifications() {
532 return true;
533 }
534
535 @Override
536 public void onChange(boolean selfChange) {
537 refreshList();
538 }
539 }
540
541 private class MyDataSetObserver extends DataSetObserver {
542 @Override
543 public void onChanged() {
544 mDataValid = true;
545 notifyDataSetChanged();
546 }
547
548 @Override
549 public void onInvalidated() {
550 mDataValid = false;
551 notifyDataSetInvalidated();
552 }
553 }
554}