OSDN Git Service

Work on the Bookmark Grid, including the favicon.
[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.widget.BaseAdapter;
39 import android.widget.ImageView;
40 import android.widget.TextView;
41
42 import java.io.ByteArrayOutputStream;
43
44 class BrowserBookmarksAdapter extends BaseAdapter {
45
46     private String                  mCurrentPage;
47     private String                  mCurrentTitle;
48     private Cursor                  mCursor;
49     private int                     mCount;
50     private BrowserBookmarksPage    mBookmarksPage;
51     private ContentResolver         mContentResolver;
52     private boolean                 mDataValid;
53     private boolean                 mGridMode;
54
55     // 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.
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) {
77         mDataValid = false;
78         mCreateShortcut = createShortcut;
79         mExtraOffset = createShortcut ? 0 : 1;
80         mBookmarksPage = b;
81         mCurrentPage = b.getResources().getString(R.string.current_page)
82                 + curPage;
83         mCurrentTitle = curTitle;
84         mContentResolver = b.getContentResolver();
85         mGridMode = false;
86
87         // FIXME: Should have a default sort order that the user selects.
88         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
100         // FIXME: This requires another query of the database after the
101         // managedQuery. Can we optimize this?
102         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);
183         Bookmarks.removeFromBookmarks(null, mContentResolver, url);
184         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() {
244         mCursor.requery();
245         mCount = mCursor.getCount() + mExtraOffset;
246         notifyDataSetChanged();
247     }
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     /**
296      * 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
333     /* 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
347     /**
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         }
362         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             }
369             View holder = convertView.findViewById(R.id.holder);
370             ImageView thumb = (ImageView) convertView.findViewById(R.id.thumb);
371             ImageView fav = (ImageView) convertView.findViewById(R.id.fav);
372             TextView tv = (TextView) convertView.findViewById(R.id.label);
373
374             if (0 == position && !mCreateShortcut) {
375                 // This is to create a bookmark for the current page.
376                 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);
381                 return convertView;
382             }
383             holder.setVisibility(View.GONE);
384             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             }
396             // 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             }
405
406             return convertView;
407
408         }
409         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         }
419         if (convertView == null || !(convertView instanceof BookmarkItem)) {
420             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     /**
441      * 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     /**
456      * 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 }