OSDN Git Service

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