OSDN Git Service

Merge change 26168 into eclair
[android-x86/packages-apps-Browser.git] / src / com / android / browser / BrowserBookmarksAdapter.java
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
17 package com.android.browser;
18
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.LayoutInflater;
34 import android.view.View;
35 import android.view.ViewGroup;
36 import android.webkit.WebIconDatabase;
37 import android.webkit.WebIconDatabase.IconListener;
38 import android.webkit.WebView;
39 import android.widget.BaseAdapter;
40 import android.widget.ImageView;
41 import android.widget.TextView;
42
43 import java.io.ByteArrayOutputStream;
44
45 class BrowserBookmarksAdapter extends BaseAdapter {
46
47     private String                  mCurrentPage;
48     private String                  mCurrentTitle;
49     private Bitmap                  mCurrentThumbnail;
50     private Cursor                  mCursor;
51     private int                     mCount;
52     private BrowserBookmarksPage    mBookmarksPage;
53     private ContentResolver         mContentResolver;
54     private boolean                 mDataValid;
55     private BookmarkViewMode        mViewMode;
56     private boolean                 mMostVisited;
57     private boolean                 mNeedsOffset;
58     private int                     mExtraOffset;
59
60     // Implementation of WebIconDatabase.IconListener
61     private class IconReceiver implements IconListener {
62         public void onReceivedIcon(String url, Bitmap icon) {
63             updateBookmarkFavicon(mContentResolver, null, url, icon);
64         }
65     }
66
67     // Instance of IconReceiver
68     private final IconReceiver mIconReceiver = new IconReceiver();
69
70     /**
71      *  Create a new BrowserBookmarksAdapter.
72      *  @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,
77             String curTitle, Bitmap curThumbnail, boolean createShortcut,
78             boolean mostVisited) {
79         mNeedsOffset = !(createShortcut || mostVisited);
80         mMostVisited = mostVisited;
81         mExtraOffset = mNeedsOffset ? 1 : 0;
82         mBookmarksPage = b;
83         mCurrentPage = b.getResources().getString(R.string.current_page)
84                 + curPage;
85         mCurrentTitle = curTitle;
86         mCurrentThumbnail = curThumbnail;
87         mContentResolver = b.getContentResolver();
88         mViewMode = BookmarkViewMode.LIST;
89
90         String whereClause;
91         // FIXME: Should have a default sort order that the user selects.
92         String orderBy = Browser.BookmarkColumns.VISITS + " DESC";
93         if (mostVisited) {
94             whereClause = Browser.BookmarkColumns.VISITS + " != 0";
95         } else {
96             whereClause = Browser.BookmarkColumns.BOOKMARK + " != 0";
97         }
98         mCursor = b.managedQuery(Browser.BOOKMARKS_URI,
99                 Browser.HISTORY_PROJECTION, whereClause, null, orderBy);
100         mCursor.registerContentObserver(new ChangeObserver());
101         mCursor.registerDataSetObserver(new MyDataSetObserver());
102
103         mDataValid = true;
104         notifyDataSetChanged();
105
106         mCount = mCursor.getCount() + mExtraOffset;
107
108         // FIXME: This requires another query of the database after the
109         // managedQuery. Can we optimize this?
110         Browser.requestAllIcons(mContentResolver,
111                 Browser.BookmarkColumns.FAVICON + " is NULL AND " +
112                 Browser.BookmarkColumns.BOOKMARK + " == 1", mIconReceiver);
113     }
114     
115     /**
116      *  Return a hashmap with one row's Title, Url, and favicon.
117      *  @param position  Position in the list.
118      *  @return Bundle  Stores title, url of row position, favicon, and id
119      *                   for the url.  Return a blank map if position is out of
120      *                   range.
121      */
122     public Bundle getRow(int position) {
123         Bundle map = new Bundle();
124         if (position < mExtraOffset || position >= mCount) {
125             return map;
126         }
127         mCursor.moveToPosition(position- mExtraOffset);
128         String url = mCursor.getString(Browser.HISTORY_PROJECTION_URL_INDEX);
129         map.putString(Browser.BookmarkColumns.TITLE, 
130                 mCursor.getString(Browser.HISTORY_PROJECTION_TITLE_INDEX));
131         map.putString(Browser.BookmarkColumns.URL, url);
132         byte[] data = mCursor.getBlob(Browser.HISTORY_PROJECTION_FAVICON_INDEX);
133         if (data != null) {
134             map.putParcelable(Browser.BookmarkColumns.FAVICON,
135                     BitmapFactory.decodeByteArray(data, 0, data.length));
136         }
137         map.putInt("id", mCursor.getInt(Browser.HISTORY_PROJECTION_ID_INDEX));
138         return map;
139     }
140
141     /**
142      *  Update a row in the database with new information. 
143      *  Requeries the database if the information has changed.
144      *  @param map  Bundle storing id, title and url of new information
145      */
146     public void updateRow(Bundle map) {
147
148         // Find the record
149         int id = map.getInt("id");
150         int position = -1;
151         for (mCursor.moveToFirst(); !mCursor.isAfterLast(); mCursor.moveToNext()) {
152             if (mCursor.getInt(Browser.HISTORY_PROJECTION_ID_INDEX) == id) {
153                 position = mCursor.getPosition();
154                 break;
155             }
156         }
157         if (position < 0) {
158             return;
159         }
160
161         mCursor.moveToPosition(position);
162         ContentValues values = new ContentValues();
163         String title = map.getString(Browser.BookmarkColumns.TITLE);
164         if (!title.equals(mCursor
165                 .getString(Browser.HISTORY_PROJECTION_TITLE_INDEX))) {
166             values.put(Browser.BookmarkColumns.TITLE, title);
167         }
168         String url = map.getString(Browser.BookmarkColumns.URL);
169         if (!url.equals(mCursor.
170                 getString(Browser.HISTORY_PROJECTION_URL_INDEX))) {
171             values.put(Browser.BookmarkColumns.URL, url);
172         }
173         if (values.size() > 0
174                 && mContentResolver.update(Browser.BOOKMARKS_URI, values,
175                         "_id = " + id, null) != -1) {
176             refreshList();
177         }
178     }
179
180     /**
181      *  Delete a row from the database.  Requeries the database.  
182      *  Does nothing if the provided position is out of range.
183      *  @param position Position in the list.
184      */
185     public void deleteRow(int position) {
186         if (position < mExtraOffset || position >= getCount()) {
187             return;
188         }
189         mCursor.moveToPosition(position- mExtraOffset);
190         String url = mCursor.getString(Browser.HISTORY_PROJECTION_URL_INDEX);
191         Bookmarks.removeFromBookmarks(null, mContentResolver, url);
192         refreshList();
193     }
194     
195     /**
196      *  Delete all bookmarks from the db. Requeries the database.  
197      *  All bookmarks with become visited URLs or if never visited 
198      *  are removed
199      */
200     public void deleteAllRows() {
201         StringBuilder deleteIds = null;
202         StringBuilder convertIds = null;
203         
204         for (mCursor.moveToFirst(); !mCursor.isAfterLast(); mCursor.moveToNext()) {
205             String url = mCursor.getString(Browser.HISTORY_PROJECTION_URL_INDEX);
206             WebIconDatabase.getInstance().releaseIconForPageUrl(url);
207             int id = mCursor.getInt(Browser.HISTORY_PROJECTION_ID_INDEX);
208             int numVisits = mCursor.getInt(Browser.HISTORY_PROJECTION_VISITS_INDEX);
209             if (0 == numVisits) {
210                 if (deleteIds == null) {
211                     deleteIds = new StringBuilder();
212                     deleteIds.append("( ");
213                 } else {
214                     deleteIds.append(" OR ( ");
215                 }
216                 deleteIds.append(BookmarkColumns._ID);
217                 deleteIds.append(" = ");
218                 deleteIds.append(id);
219                 deleteIds.append(" )");
220             } else {
221                 // It is no longer a bookmark, but it is still a visited site.
222                 if (convertIds == null) {
223                     convertIds = new StringBuilder();
224                     convertIds.append("( ");
225                 } else {
226                     convertIds.append(" OR ( ");
227                 }
228                 convertIds.append(BookmarkColumns._ID);
229                 convertIds.append(" = ");
230                 convertIds.append(id);
231                 convertIds.append(" )");
232             }
233         }
234         
235         if (deleteIds != null) {
236             mContentResolver.delete(Browser.BOOKMARKS_URI, deleteIds.toString(), 
237                 null);
238         }
239         if (convertIds != null) {
240             ContentValues values = new ContentValues();
241             values.put(Browser.BookmarkColumns.BOOKMARK, 0);
242             mContentResolver.update(Browser.BOOKMARKS_URI, values, 
243                     convertIds.toString(), null);
244         }
245         refreshList();
246     }
247
248     /**
249      *  Refresh list to recognize a change in the database.
250      */
251     public void refreshList() {
252         mCursor.requery();
253         mCount = mCursor.getCount() + mExtraOffset;
254         notifyDataSetChanged();
255     }
256
257     /**
258      * Update the bookmark's favicon. This is a convenience method for updating
259      * a bookmark favicon for the originalUrl and url of the passed in WebView.
260      * @param cr The ContentResolver to use.
261      * @param originalUrl The original url before any redirects.
262      * @param url The current url.
263      * @param favicon The favicon bitmap to write to the db.
264      */
265     /* package */ static void updateBookmarkFavicon(ContentResolver cr,
266             String originalUrl, String url, Bitmap favicon) {
267         final Cursor c = queryBookmarksForUrl(cr, originalUrl, url, true);
268         if (c == null) {
269             return;
270         }
271         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
287     /* package */ static Cursor queryBookmarksForUrl(ContentResolver cr,
288             String originalUrl, String url, boolean onlyBookmarks) {
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         String where = BookmarkColumns.URL + " == ? OR "
313                 + BookmarkColumns.URL + " == ? OR "
314                 + BookmarkColumns.URL + " GLOB ? || '*' OR "
315                 + BookmarkColumns.URL + " GLOB ? || '*'";
316         if (onlyBookmarks) {
317             where = "(" + where + ") AND " + BookmarkColumns.BOOKMARK + " == 1";
318         }
319         final String[] projection =
320                 new String[] { Browser.BookmarkColumns._ID };
321         return cr.query(Browser.BOOKMARKS_URI, projection, where, selArgs,
322                 null);
323     }
324
325     // Strip the query from the given url.
326     private static String removeQuery(String url) {
327         if (url == null) {
328             return null;
329         }
330         int query = url.indexOf('?');
331         String noQuery = url;
332         if (query != -1) {
333             noQuery = url.substring(0, query);
334         }
335         return noQuery;
336     }
337
338     /**
339      * How many items should be displayed in the list.
340      * @return Count of items.
341      */
342     public int getCount() {
343         if (mDataValid) {
344             return mCount;
345         } else {
346             return 0;
347         }
348     }
349
350     public boolean areAllItemsEnabled() {
351         return true;
352     }
353
354     public boolean isEnabled(int position) {
355         return true;
356     }
357
358     /**
359      * Get the data associated with the specified position in the list.
360      * @param position Index of the item whose data we want.
361      * @return The data at the specified position.
362      */
363     public Object getItem(int position) {
364         return null;
365     }
366
367     /**
368      * Get the row id associated with the specified position in the list.
369      * @param position Index of the item whose row id we want.
370      * @return The id of the item at the specified position.
371      */
372     public long getItemId(int position) {
373         return position;
374     }
375
376     /* package */ void switchViewMode(BookmarkViewMode viewMode) {
377         mViewMode = viewMode;
378     }
379
380     /* package */ void populateBookmarkItem(BookmarkItem b, int position) {
381         mCursor.moveToPosition(position - mExtraOffset);
382         String url = mCursor.getString(Browser.HISTORY_PROJECTION_URL_INDEX);
383         b.setUrl(url);
384         b.setName(mCursor.getString(Browser.HISTORY_PROJECTION_TITLE_INDEX));
385         byte[] data = mCursor.getBlob(Browser.HISTORY_PROJECTION_FAVICON_INDEX);
386         Bitmap bitmap = null;
387         if (data == null) {
388             bitmap = CombinedBookmarkHistoryActivity.getIconListenerSet()
389                     .getFavicon(url);
390         } else {
391             bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
392         }
393         b.setFavicon(bitmap);
394     }
395
396     /**
397      * Get a View that displays the data at the specified position
398      * in the list.
399      * @param position Index of the item whose view we want.
400      * @return A View corresponding to the data at the specified position.
401      */
402     public View getView(int position, View convertView, ViewGroup parent) {
403         if (!mDataValid) {
404             throw new IllegalStateException(
405                     "this should only be called when the cursor is valid");
406         }
407         if (position < 0 || position > mCount) {
408             throw new AssertionError(
409                     "BrowserBookmarksAdapter tried to get a view out of range");
410         }
411         if (mViewMode == BookmarkViewMode.GRID) {
412             if (convertView == null || convertView instanceof AddNewBookmark
413                     || convertView instanceof BookmarkItem) {
414                 LayoutInflater factory = LayoutInflater.from(mBookmarksPage);
415                 convertView
416                         = factory.inflate(R.layout.bookmark_thumbnail, null);
417             }
418             View holder = convertView.findViewById(R.id.holder);
419             ImageView thumb = (ImageView) convertView.findViewById(R.id.thumb);
420             TextView tv = (TextView) convertView.findViewById(R.id.label);
421
422             if (0 == position && mNeedsOffset) {
423                 // This is to create a bookmark for the current page.
424                 holder.setVisibility(View.VISIBLE);
425                 tv.setText(mCurrentTitle);
426
427                 if (mCurrentThumbnail != null) {
428                     thumb.setImageBitmap(mCurrentThumbnail);
429                 } else {
430                     thumb.setImageResource(
431                             R.drawable.ic_launcher_shortcut_browser_bookmark);
432                 }
433                 return convertView;
434             }
435             holder.setVisibility(View.GONE);
436             mCursor.moveToPosition(position - mExtraOffset);
437             tv.setText(mCursor.getString(
438                     Browser.HISTORY_PROJECTION_TITLE_INDEX));
439             byte[] data = mCursor.getBlob(
440                     Browser.HISTORY_PROJECTION_THUMBNAIL_INDEX);
441             if (data == null) {
442                 thumb.setImageResource(R.drawable.ic_launcher_shortcut_browser_bookmark);
443             } else {
444                 thumb.setImageBitmap(
445                         BitmapFactory.decodeByteArray(data, 0, data.length));
446             }
447
448             return convertView;
449
450         }
451         if (position == 0 && mNeedsOffset) {
452             AddNewBookmark b;
453             if (convertView instanceof AddNewBookmark) {
454                 b = (AddNewBookmark) convertView;
455             } else {
456                 b = new AddNewBookmark(mBookmarksPage);
457             }
458             b.setUrl(mCurrentPage);
459             return b;
460         }
461         if (mMostVisited) {
462             if (convertView == null || !(convertView instanceof HistoryItem)) {
463                 convertView = new HistoryItem(mBookmarksPage);
464             }
465         } else {
466             if (convertView == null || !(convertView instanceof BookmarkItem)) {
467                 convertView = new BookmarkItem(mBookmarksPage);
468             }
469         }
470         bind((BookmarkItem) convertView, position);
471         if (mMostVisited) {
472             ((HistoryItem) convertView).setIsBookmark(
473                     getIsBookmark(position));
474         }
475         return convertView;
476     }
477
478     /**
479      *  Return the title for this item in the list.
480      */
481     public String getTitle(int position) {
482         return getString(Browser.HISTORY_PROJECTION_TITLE_INDEX, position);
483     }
484
485     /**
486      *  Return the Url for this item in the list.
487      */
488     public String getUrl(int position) {
489         return getString(Browser.HISTORY_PROJECTION_URL_INDEX, position);
490     }
491
492     /**
493      * Return the favicon for this item in the list.
494      */
495     public Bitmap getFavicon(int position) {
496         return getBitmap(Browser.HISTORY_PROJECTION_FAVICON_INDEX, position);
497     }
498
499     public Bitmap getTouchIcon(int position) {
500         return getBitmap(Browser.HISTORY_PROJECTION_TOUCH_ICON_INDEX, position);
501     }
502
503     private Bitmap getBitmap(int cursorIndex, int position) {
504         if (position < mExtraOffset || position > mCount) {
505             return null;
506         }
507         mCursor.moveToPosition(position - mExtraOffset);
508         byte[] data = mCursor.getBlob(cursorIndex);
509         if (data == null) {
510             return null;
511         }
512         return BitmapFactory.decodeByteArray(data, 0, data.length);
513     }
514
515     /**
516      * Return whether or not this item represents a bookmarked site.
517      */
518     public boolean getIsBookmark(int position) {
519         if (position < mExtraOffset || position > mCount) {
520             return false;
521         }
522         mCursor.moveToPosition(position - mExtraOffset);
523         return (1 == mCursor.getInt(Browser.HISTORY_PROJECTION_BOOKMARK_INDEX));
524     }
525
526     /**
527      * Private helper function to return the title or url.
528      */
529     private String getString(int cursorIndex, int position) {
530         if (position < mExtraOffset || position > mCount) {
531             return "";
532         }
533         mCursor.moveToPosition(position- mExtraOffset);
534         return mCursor.getString(cursorIndex);
535     }
536
537     private void bind(BookmarkItem b, int position) {
538         mCursor.moveToPosition(position- mExtraOffset);
539
540         String title = mCursor.getString(Browser.HISTORY_PROJECTION_TITLE_INDEX);
541         if (title.length() > BrowserSettings.MAX_TEXTVIEW_LEN) {
542             title = title.substring(0, BrowserSettings.MAX_TEXTVIEW_LEN);
543         }
544         b.setName(title);
545         String url = mCursor.getString(Browser.HISTORY_PROJECTION_URL_INDEX);
546         if (url.length() > BrowserSettings.MAX_TEXTVIEW_LEN) {
547             url = url.substring(0, BrowserSettings.MAX_TEXTVIEW_LEN);
548         }
549         b.setUrl(url);
550         byte[] data = mCursor.getBlob(Browser.HISTORY_PROJECTION_FAVICON_INDEX);
551         if (data != null) {
552             b.setFavicon(BitmapFactory.decodeByteArray(data, 0, data.length));
553         } else {
554             b.setFavicon(CombinedBookmarkHistoryActivity.getIconListenerSet()
555                     .getFavicon(url));
556         }
557     }
558
559     private class ChangeObserver extends ContentObserver {
560         public ChangeObserver() {
561             super(new Handler());
562         }
563
564         @Override
565         public boolean deliverSelfNotifications() {
566             return true;
567         }
568
569         @Override
570         public void onChange(boolean selfChange) {
571             refreshList();
572         }
573     }
574     
575     private class MyDataSetObserver extends DataSetObserver {
576         @Override
577         public void onChanged() {
578             mDataValid = true;
579             notifyDataSetChanged();
580         }
581
582         @Override
583         public void onInvalidated() {
584             mDataValid = false;
585             notifyDataSetInvalidated();
586         }
587     }
588 }