OSDN Git Service

am 8e5d209b: Import new translations
[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.View;
34 import android.view.ViewGroup;
35 import android.webkit.WebIconDatabase;
36 import android.webkit.WebIconDatabase.IconListener;
37 import android.widget.BaseAdapter;
38
39 import java.io.ByteArrayOutputStream;
40
41 class BrowserBookmarksAdapter extends BaseAdapter {
42
43     private String                  mCurrentPage;
44     private Cursor                  mCursor;
45     private int                     mCount;
46     private String                  mLastWhereClause;
47     private String[]                mLastSelectionArgs;
48     private String                  mLastOrderBy;
49     private BrowserBookmarksPage    mBookmarksPage;
50     private ContentResolver         mContentResolver;
51     private ChangeObserver          mChangeObserver;
52     private DataSetObserver         mDataSetObserver;
53     private boolean                 mDataValid;
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         this(b, curPage, false);
77     }
78
79     /**
80      *  Create a new BrowserBookmarksAdapter.
81      *  @param b        BrowserBookmarksPage that instantiated this.
82      *                  Necessary so it will adjust its focus
83      *                  appropriately after a search.
84      */
85     public BrowserBookmarksAdapter(BrowserBookmarksPage b, String curPage,
86             boolean createShortcut) {
87         mDataValid = false;
88         mCreateShortcut = createShortcut;
89         mExtraOffset = createShortcut ? 0 : 1;
90         mBookmarksPage = b;
91         mCurrentPage = b.getResources().getString(R.string.current_page) + 
92                 curPage;
93         mContentResolver = b.getContentResolver();
94         mLastOrderBy = Browser.BookmarkColumns.CREATED + " DESC";
95         mChangeObserver = new ChangeObserver();
96         mDataSetObserver = new MyDataSetObserver();
97         // FIXME: Should have a default sort order that the user selects.
98         search(null);
99         // FIXME: This requires another query of the database after the
100         // initial search(null). Can we optimize this?
101         Browser.requestAllIcons(mContentResolver,
102                 Browser.BookmarkColumns.FAVICON + " is NULL AND " +
103                 Browser.BookmarkColumns.BOOKMARK + " == 1", mIconReceiver);
104     }
105     
106     /**
107      *  Return a hashmap with one row's Title, Url, and favicon.
108      *  @param position  Position in the list.
109      *  @return Bundle  Stores title, url of row position, favicon, and id
110      *                   for the url.  Return a blank map if position is out of
111      *                   range.
112      */
113     public Bundle getRow(int position) {
114         Bundle map = new Bundle();
115         if (position < mExtraOffset || position >= mCount) {
116             return map;
117         }
118         mCursor.moveToPosition(position- mExtraOffset);
119         String url = mCursor.getString(Browser.HISTORY_PROJECTION_URL_INDEX);
120         map.putString(Browser.BookmarkColumns.TITLE, 
121                 mCursor.getString(Browser.HISTORY_PROJECTION_TITLE_INDEX));
122         map.putString(Browser.BookmarkColumns.URL, url);
123         byte[] data = mCursor.getBlob(Browser.HISTORY_PROJECTION_FAVICON_INDEX);
124         if (data != null) {
125             map.putParcelable(Browser.BookmarkColumns.FAVICON,
126                     BitmapFactory.decodeByteArray(data, 0, data.length));
127         }
128         map.putInt("id", mCursor.getInt(Browser.HISTORY_PROJECTION_ID_INDEX));
129         return map;
130     }
131
132     /**
133      *  Update a row in the database with new information. 
134      *  Requeries the database if the information has changed.
135      *  @param map  Bundle storing id, title and url of new information
136      */
137     public void updateRow(Bundle map) {
138
139         // Find the record
140         int id = map.getInt("id");
141         int position = -1;
142         for (mCursor.moveToFirst(); !mCursor.isAfterLast(); mCursor.moveToNext()) {
143             if (mCursor.getInt(Browser.HISTORY_PROJECTION_ID_INDEX) == id) {
144                 position = mCursor.getPosition();
145                 break;
146             }
147         }
148         if (position < 0) {
149             return;
150         }
151
152         mCursor.moveToPosition(position);
153         ContentValues values = new ContentValues();
154         String title = map.getString(Browser.BookmarkColumns.TITLE);
155         if (!title.equals(mCursor
156                 .getString(Browser.HISTORY_PROJECTION_TITLE_INDEX))) {
157             values.put(Browser.BookmarkColumns.TITLE, title);
158         }
159         String url = map.getString(Browser.BookmarkColumns.URL);
160         if (!url.equals(mCursor.
161                 getString(Browser.HISTORY_PROJECTION_URL_INDEX))) {
162             values.put(Browser.BookmarkColumns.URL, url);
163         }
164         if (values.size() > 0
165                 && mContentResolver.update(Browser.BOOKMARKS_URI, values,
166                         "_id = " + id, null) != -1) {
167             refreshList();
168         }
169     }
170
171     /**
172      *  Delete a row from the database.  Requeries the database.  
173      *  Does nothing if the provided position is out of range.
174      *  @param position Position in the list.
175      */
176     public void deleteRow(int position) {
177         if (position < mExtraOffset || position >= getCount()) {
178             return;
179         }
180         mCursor.moveToPosition(position- mExtraOffset);
181         String url = mCursor.getString(Browser.HISTORY_PROJECTION_URL_INDEX);
182         Bookmarks.removeFromBookmarks(null, mContentResolver, url);
183         refreshList();
184     }
185     
186     /**
187      *  Delete all bookmarks from the db. Requeries the database.  
188      *  All bookmarks with become visited URLs or if never visited 
189      *  are removed
190      */
191     public void deleteAllRows() {
192         StringBuilder deleteIds = null;
193         StringBuilder convertIds = null;
194         
195         for (mCursor.moveToFirst(); !mCursor.isAfterLast(); mCursor.moveToNext()) {
196             String url = mCursor.getString(Browser.HISTORY_PROJECTION_URL_INDEX);
197             WebIconDatabase.getInstance().releaseIconForPageUrl(url);
198             int id = mCursor.getInt(Browser.HISTORY_PROJECTION_ID_INDEX);
199             int numVisits = mCursor.getInt(Browser.HISTORY_PROJECTION_VISITS_INDEX);
200             if (0 == numVisits) {
201                 if (deleteIds == null) {
202                     deleteIds = new StringBuilder();
203                     deleteIds.append("( ");
204                 } else {
205                     deleteIds.append(" OR ( ");
206                 }
207                 deleteIds.append(BookmarkColumns._ID);
208                 deleteIds.append(" = ");
209                 deleteIds.append(id);
210                 deleteIds.append(" )");
211             } else {
212                 // It is no longer a bookmark, but it is still a visited site.
213                 if (convertIds == null) {
214                     convertIds = new StringBuilder();
215                     convertIds.append("( ");
216                 } else {
217                     convertIds.append(" OR ( ");
218                 }
219                 convertIds.append(BookmarkColumns._ID);
220                 convertIds.append(" = ");
221                 convertIds.append(id);
222                 convertIds.append(" )");
223             }
224         }
225         
226         if (deleteIds != null) {
227             mContentResolver.delete(Browser.BOOKMARKS_URI, deleteIds.toString(), 
228                 null);
229         }
230         if (convertIds != null) {
231             ContentValues values = new ContentValues();
232             values.put(Browser.BookmarkColumns.BOOKMARK, 0);
233             mContentResolver.update(Browser.BOOKMARKS_URI, values, 
234                     convertIds.toString(), null);
235         }
236         refreshList();
237     }
238
239     /**
240      *  Refresh list to recognize a change in the database.
241      */
242     public void refreshList() {
243         // FIXME: consider using requery().
244         // Need to do more work to get it to function though.
245         searchInternal(mLastWhereClause, mLastSelectionArgs, mLastOrderBy);
246     }
247
248     /**
249      *  Search the database for bookmarks that match the input string.
250      *  @param like String to use to search the database.  Strings with spaces 
251      *              are treated as having multiple search terms using the
252      *              OR operator.  Search both the title and url.
253      */
254     public void search(String like) {
255         String whereClause = Browser.BookmarkColumns.BOOKMARK + " == 1";
256         String[] selectionArgs = null;
257         if (like != null) {
258             String[] likes = like.split(" ");
259             int count = 0;
260             boolean firstTerm = true;
261             StringBuilder andClause = new StringBuilder(256);
262             for (int j = 0; j < likes.length; j++) {
263                 if (likes[j].length() > 0) {
264                     if (firstTerm) {
265                         firstTerm = false;
266                     } else {
267                         andClause.append(" OR ");
268                     }
269                     andClause.append(Browser.BookmarkColumns.TITLE
270                             + " LIKE ? OR " + Browser.BookmarkColumns.URL
271                             + " LIKE ? ");
272                     count += 2;
273                 }
274             }
275             if (count > 0) {
276                 selectionArgs = new String[count];
277                 count = 0;
278                 for (int j = 0; j < likes.length; j++) {
279                     if (likes[j].length() > 0) {
280                         like = "%" + likes[j] + "%";
281                         selectionArgs[count++] = like;
282                         selectionArgs[count++] = like;
283                     }
284                 }
285                 whereClause += " AND (" + andClause + ")";
286             }
287         }
288         searchInternal(whereClause, selectionArgs, mLastOrderBy);
289     }
290
291     /**
292      * Update the bookmark's favicon.
293      * @param cr The ContentResolver to use.
294      * @param url The url of the bookmark to update.
295      * @param favicon The favicon bitmap to write to the db.
296      */
297     /* package */ static void updateBookmarkFavicon(ContentResolver cr,
298             String url, Bitmap favicon) {
299         if (url == null || favicon == null) {
300             return;
301         }
302         // Strip the query.
303         int query = url.indexOf('?');
304         String noQuery = url;
305         if (query != -1) {
306             noQuery = url.substring(0, query);
307         }
308         url = noQuery + '?';
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[] { noQuery, url };
315         final String where = "(" + Browser.BookmarkColumns.URL + " == ? OR "
316                 + Browser.BookmarkColumns.URL + " GLOB ? || '*') AND "
317                 + Browser.BookmarkColumns.BOOKMARK + " == 1";
318         final String[] projection = new String[] { Browser.BookmarkColumns._ID };
319         final Cursor c = cr.query(Browser.BOOKMARKS_URI, projection, where,
320                 selArgs, null);
321         boolean succeed = c.moveToFirst();
322         ContentValues values = null;
323         while (succeed) {
324             if (values == null) {
325                 final ByteArrayOutputStream os = new ByteArrayOutputStream();
326                 favicon.compress(Bitmap.CompressFormat.PNG, 100, os);
327                 values = new ContentValues();
328                 values.put(Browser.BookmarkColumns.FAVICON, os.toByteArray());
329             }
330             cr.update(ContentUris.withAppendedId(Browser.BOOKMARKS_URI, c
331                     .getInt(0)), values, null, null);
332             succeed = c.moveToNext();
333         }
334         c.close();
335     }
336
337     /**
338      *  This sorts alphabetically, with non-capitalized titles before 
339      *  capitalized.
340      */
341     public void sortAlphabetical() {
342         searchInternal(mLastWhereClause, mLastSelectionArgs,
343                 Browser.BookmarkColumns.TITLE + " COLLATE UNICODE ASC");
344     }
345
346     /**
347      *  Internal function used in search, sort, and refreshList.
348      */
349     private void searchInternal(String whereClause, String[] selectionArgs,
350             String orderBy) {
351         if (mCursor != null) {
352             mCursor.unregisterContentObserver(mChangeObserver);
353             mCursor.unregisterDataSetObserver(mDataSetObserver);
354             mBookmarksPage.stopManagingCursor(mCursor);
355             mCursor.deactivate();
356         }
357
358         mLastWhereClause = whereClause;
359         mLastSelectionArgs = selectionArgs;
360         mLastOrderBy = orderBy;
361         mCursor = mContentResolver.query(
362             Browser.BOOKMARKS_URI,
363             Browser.HISTORY_PROJECTION,
364             whereClause,
365             selectionArgs, 
366             orderBy);
367         mCursor.registerContentObserver(mChangeObserver);
368         mCursor.registerDataSetObserver(mDataSetObserver);
369         mBookmarksPage.startManagingCursor(mCursor);
370
371         mDataValid = true;
372         notifyDataSetChanged();
373
374         mCount = mCursor.getCount() + mExtraOffset;
375     }
376
377     /**
378      * How many items should be displayed in the list.
379      * @return Count of items.
380      */
381     public int getCount() {
382         if (mDataValid) {
383             return mCount;
384         } else {
385             return 0;
386         }
387     }
388
389     public boolean areAllItemsEnabled() {
390         return true;
391     }
392
393     public boolean isEnabled(int position) {
394         return true;
395     }
396
397     /**
398      * Get the data associated with the specified position in the list.
399      * @param position Index of the item whose data we want.
400      * @return The data at the specified position.
401      */
402     public Object getItem(int position) {
403         return null;
404     }
405
406     /**
407      * Get the row id associated with the specified position in the list.
408      * @param position Index of the item whose row id we want.
409      * @return The id of the item at the specified position.
410      */
411     public long getItemId(int position) {
412         return position;
413     }
414
415     /**
416      * Get a View that displays the data at the specified position
417      * in the list.
418      * @param position Index of the item whose view we want.
419      * @return A View corresponding to the data at the specified position.
420      */
421     public View getView(int position, View convertView, ViewGroup parent) {
422         if (!mDataValid) {
423             throw new IllegalStateException(
424                     "this should only be called when the cursor is valid");
425         }
426         if (position < 0 || position > mCount) {
427             throw new AssertionError(
428                     "BrowserBookmarksAdapter tried to get a view out of range");
429         }
430         if (position == 0 && !mCreateShortcut) {
431             AddNewBookmark b;
432             if (convertView instanceof AddNewBookmark) {
433                 b = (AddNewBookmark) convertView;
434             } else {
435                 b = new AddNewBookmark(mBookmarksPage);
436             }
437             b.setUrl(mCurrentPage);
438             return b;
439         }
440         if (convertView == null || convertView instanceof AddNewBookmark) {
441             convertView = new BookmarkItem(mBookmarksPage);
442         }
443         bind((BookmarkItem)convertView, position);
444         return convertView;
445     }
446
447     /**
448      *  Return the title for this item in the list.
449      */
450     public String getTitle(int position) {
451         return getString(Browser.HISTORY_PROJECTION_TITLE_INDEX, position);
452     }
453
454     /**
455      *  Return the Url for this item in the list.
456      */
457     public String getUrl(int position) {
458         return getString(Browser.HISTORY_PROJECTION_URL_INDEX, position);
459     }
460
461     /**
462      * Return the favicon for this item in the list.
463      */
464     public Bitmap getFavicon(int position) {
465         if (position < mExtraOffset || position > mCount) {
466             return null;
467         }
468         mCursor.moveToPosition(position - mExtraOffset);
469         byte[] data = mCursor.getBlob(Browser.HISTORY_PROJECTION_FAVICON_INDEX);
470         if (data == null) {
471             return null;
472         }
473         return BitmapFactory.decodeByteArray(data, 0, data.length);
474     }
475
476     /**
477      * Private helper function to return the title or url.
478      */
479     private String getString(int cursorIndex, int position) {
480         if (position < mExtraOffset || position > mCount) {
481             return "";
482         }
483         mCursor.moveToPosition(position- mExtraOffset);
484         return mCursor.getString(cursorIndex);
485     }
486
487     private void bind(BookmarkItem b, int position) {
488         mCursor.moveToPosition(position- mExtraOffset);
489
490         String title = mCursor.getString(Browser.HISTORY_PROJECTION_TITLE_INDEX);
491         if (title.length() > BrowserSettings.MAX_TEXTVIEW_LEN) {
492             title = title.substring(0, BrowserSettings.MAX_TEXTVIEW_LEN);
493         }
494         b.setName(title);
495         String url = mCursor.getString(Browser.HISTORY_PROJECTION_URL_INDEX);
496         if (url.length() > BrowserSettings.MAX_TEXTVIEW_LEN) {
497             url = url.substring(0, BrowserSettings.MAX_TEXTVIEW_LEN);
498         }
499         b.setUrl(url);
500         byte[] data = mCursor.getBlob(Browser.HISTORY_PROJECTION_FAVICON_INDEX);
501         if (data != null) {
502             b.setFavicon(BitmapFactory.decodeByteArray(data, 0, data.length));
503         } else {
504             b.setFavicon(null);
505         }
506     }
507
508     private class ChangeObserver extends ContentObserver {
509         public ChangeObserver() {
510             super(new Handler());
511         }
512
513         @Override
514         public boolean deliverSelfNotifications() {
515             return true;
516         }
517
518         @Override
519         public void onChange(boolean selfChange) {
520             refreshList();
521         }
522     }
523     
524     private class MyDataSetObserver extends DataSetObserver {
525         @Override
526         public void onChanged() {
527             mDataValid = true;
528             notifyDataSetChanged();
529         }
530
531         @Override
532         public void onInvalidated() {
533             mDataValid = false;
534             notifyDataSetInvalidated();
535         }
536     }
537 }