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