blob: 75be45b593661dfe4515545cc44dab5404edfcbf [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;
Leon Scroggins89c6d362009-07-15 16:54:37 -040047 private String mCurrentTitle;
The Android Open Source Project0c908882009-03-03 19:32:16 -080048 private Cursor mCursor;
49 private int mCount;
The Android Open Source Project0c908882009-03-03 19:32:16 -080050 private BrowserBookmarksPage mBookmarksPage;
51 private ContentResolver mContentResolver;
The Android Open Source Project0c908882009-03-03 19:32:16 -080052 private boolean mDataValid;
Leon Scroggins892df312009-07-14 14:48:02 -040053 private boolean mGridMode;
Leon Scroggins892df312009-07-14 14:48:02 -040054
The Android Open Source Project0c908882009-03-03 19:32:16 -080055 // 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.
The Android Open Source Project0c908882009-03-03 19:32:16 -080071 * @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,
Leon Scroggins89c6d362009-07-15 16:54:37 -040076 String curTitle, boolean createShortcut) {
The Android Open Source Project0c908882009-03-03 19:32:16 -080077 mDataValid = false;
78 mCreateShortcut = createShortcut;
79 mExtraOffset = createShortcut ? 0 : 1;
80 mBookmarksPage = b;
Leon Scroggins89c6d362009-07-15 16:54:37 -040081 mCurrentPage = b.getResources().getString(R.string.current_page)
82 + curPage;
83 mCurrentTitle = curTitle;
The Android Open Source Project0c908882009-03-03 19:32:16 -080084 mContentResolver = b.getContentResolver();
Leon Scroggins892df312009-07-14 14:48:02 -040085 mGridMode = false;
86
The Android Open Source Project0c908882009-03-03 19:32:16 -080087 // FIXME: Should have a default sort order that the user selects.
Leon Scroggins892df312009-07-14 14:48:02 -040088 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
The Android Open Source Project0c908882009-03-03 19:32:16 -0800100 // FIXME: This requires another query of the database after the
Leon Scroggins89c6d362009-07-15 16:54:37 -0400101 // managedQuery. Can we optimize this?
The Android Open Source Project0c908882009-03-03 19:32:16 -0800102 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);
Leon Scrogginse372c022009-06-12 17:07:29 -0400183 Bookmarks.removeFromBookmarks(null, mContentResolver, url);
The Android Open Source Project0c908882009-03-03 19:32:16 -0800184 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() {
Leon Scroggins892df312009-07-14 14:48:02 -0400244 mCursor.requery();
245 mCount = mCursor.getCount() + mExtraOffset;
246 notifyDataSetChanged();
The Android Open Source Project0c908882009-03-03 19:32:16 -0800247 }
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 /**
The Android Open Source Project0c908882009-03-03 19:32:16 -0800296 * 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
Leon Scroggins892df312009-07-14 14:48:02 -0400333 /* 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
The Android Open Source Project0c908882009-03-03 19:32:16 -0800347 /**
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 }
Leon Scroggins892df312009-07-14 14:48:02 -0400362 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 }
Leon Scroggins89c6d362009-07-15 16:54:37 -0400369 View holder = convertView.findViewById(R.id.holder);
Leon Scroggins892df312009-07-14 14:48:02 -0400370 ImageView thumb = (ImageView) convertView.findViewById(R.id.thumb);
Leon Scroggins89c6d362009-07-15 16:54:37 -0400371 ImageView fav = (ImageView) convertView.findViewById(R.id.fav);
Leon Scroggins892df312009-07-14 14:48:02 -0400372 TextView tv = (TextView) convertView.findViewById(R.id.label);
373
Leon Scroggins892df312009-07-14 14:48:02 -0400374 if (0 == position && !mCreateShortcut) {
375 // This is to create a bookmark for the current page.
Leon Scroggins89c6d362009-07-15 16:54:37 -0400376 holder.setVisibility(View.VISIBLE);
377 fav.setVisibility(View.GONE);
378 tv.setText(mCurrentTitle);
379 // FIXME: Want to show the screenshot of the current page
380 thumb.setImageResource(R.drawable.blank);
Leon Scroggins892df312009-07-14 14:48:02 -0400381 return convertView;
382 }
Leon Scroggins89c6d362009-07-15 16:54:37 -0400383 holder.setVisibility(View.GONE);
Leon Scroggins892df312009-07-14 14:48:02 -0400384 mCursor.moveToPosition(position - mExtraOffset);
385 tv.setText(mCursor.getString(
386 Browser.HISTORY_PROJECTION_TITLE_INDEX));
387 byte[] data = mCursor.getBlob(
388 Browser.HISTORY_PROJECTION_THUMBNAIL_INDEX);
389 if (data == null) {
390 // Backup is to just show white
391 thumb.setImageResource(R.drawable.blank);
392 } else {
393 thumb.setImageBitmap(
394 BitmapFactory.decodeByteArray(data, 0, data.length));
395 }
Leon Scroggins892df312009-07-14 14:48:02 -0400396 // Now show the favicon
397 data = mCursor.getBlob(Browser.HISTORY_PROJECTION_FAVICON_INDEX);
398 if (data == null) {
399 fav.setVisibility(View.GONE);
400 } else {
401 fav.setVisibility(View.VISIBLE);
402 fav.setImageBitmap(
403 BitmapFactory.decodeByteArray(data, 0, data.length));
404 }
Leon Scroggins89c6d362009-07-15 16:54:37 -0400405
Leon Scroggins892df312009-07-14 14:48:02 -0400406 return convertView;
407
408 }
The Android Open Source Project0c908882009-03-03 19:32:16 -0800409 if (position == 0 && !mCreateShortcut) {
410 AddNewBookmark b;
411 if (convertView instanceof AddNewBookmark) {
412 b = (AddNewBookmark) convertView;
413 } else {
414 b = new AddNewBookmark(mBookmarksPage);
415 }
416 b.setUrl(mCurrentPage);
417 return b;
418 }
Leon Scroggins892df312009-07-14 14:48:02 -0400419 if (convertView == null || !(convertView instanceof BookmarkItem)) {
The Android Open Source Project0c908882009-03-03 19:32:16 -0800420 convertView = new BookmarkItem(mBookmarksPage);
421 }
422 bind((BookmarkItem)convertView, position);
423 return convertView;
424 }
425
426 /**
427 * Return the title for this item in the list.
428 */
429 public String getTitle(int position) {
430 return getString(Browser.HISTORY_PROJECTION_TITLE_INDEX, position);
431 }
432
433 /**
434 * Return the Url for this item in the list.
435 */
436 public String getUrl(int position) {
437 return getString(Browser.HISTORY_PROJECTION_URL_INDEX, position);
438 }
439
440 /**
Patrick Scotte09761e2009-03-24 20:43:37 -0700441 * Return the favicon for this item in the list.
442 */
443 public Bitmap getFavicon(int position) {
444 if (position < mExtraOffset || position > mCount) {
445 return null;
446 }
447 mCursor.moveToPosition(position - mExtraOffset);
448 byte[] data = mCursor.getBlob(Browser.HISTORY_PROJECTION_FAVICON_INDEX);
449 if (data == null) {
450 return null;
451 }
452 return BitmapFactory.decodeByteArray(data, 0, data.length);
453 }
454
455 /**
The Android Open Source Project0c908882009-03-03 19:32:16 -0800456 * Private helper function to return the title or url.
457 */
458 private String getString(int cursorIndex, int position) {
459 if (position < mExtraOffset || position > mCount) {
460 return "";
461 }
462 mCursor.moveToPosition(position- mExtraOffset);
463 return mCursor.getString(cursorIndex);
464 }
465
466 private void bind(BookmarkItem b, int position) {
467 mCursor.moveToPosition(position- mExtraOffset);
468
469 String title = mCursor.getString(Browser.HISTORY_PROJECTION_TITLE_INDEX);
470 if (title.length() > BrowserSettings.MAX_TEXTVIEW_LEN) {
471 title = title.substring(0, BrowserSettings.MAX_TEXTVIEW_LEN);
472 }
473 b.setName(title);
474 String url = mCursor.getString(Browser.HISTORY_PROJECTION_URL_INDEX);
475 if (url.length() > BrowserSettings.MAX_TEXTVIEW_LEN) {
476 url = url.substring(0, BrowserSettings.MAX_TEXTVIEW_LEN);
477 }
478 b.setUrl(url);
479 byte[] data = mCursor.getBlob(Browser.HISTORY_PROJECTION_FAVICON_INDEX);
480 if (data != null) {
481 b.setFavicon(BitmapFactory.decodeByteArray(data, 0, data.length));
482 } else {
483 b.setFavicon(null);
484 }
485 }
486
487 private class ChangeObserver extends ContentObserver {
488 public ChangeObserver() {
489 super(new Handler());
490 }
491
492 @Override
493 public boolean deliverSelfNotifications() {
494 return true;
495 }
496
497 @Override
498 public void onChange(boolean selfChange) {
499 refreshList();
500 }
501 }
502
503 private class MyDataSetObserver extends DataSetObserver {
504 @Override
505 public void onChanged() {
506 mDataValid = true;
507 notifyDataSetChanged();
508 }
509
510 @Override
511 public void onInvalidated() {
512 mDataValid = false;
513 notifyDataSetInvalidated();
514 }
515 }
516}