Home | History | Annotate | Download | only in browser
      1 /*
      2  * Copyright (C) 2008 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.app.Activity;
     20 import android.app.AlertDialog;
     21 import android.app.Dialog;
     22 import android.app.Fragment;
     23 import android.app.FragmentBreadCrumbs;
     24 import android.app.LoaderManager.LoaderCallbacks;
     25 import android.content.ClipboardManager;
     26 import android.content.ContentResolver;
     27 import android.content.Context;
     28 import android.content.CursorLoader;
     29 import android.content.DialogInterface;
     30 import android.content.Intent;
     31 import android.content.Loader;
     32 import android.content.pm.PackageManager;
     33 import android.content.pm.ResolveInfo;
     34 import android.database.Cursor;
     35 import android.database.DataSetObserver;
     36 import android.graphics.BitmapFactory;
     37 import android.graphics.drawable.Drawable;
     38 import android.net.Uri;
     39 import android.os.Bundle;
     40 import android.provider.Browser;
     41 import android.provider.BrowserContract;
     42 import android.provider.BrowserContract.Combined;
     43 import android.view.ContextMenu;
     44 import android.view.ContextMenu.ContextMenuInfo;
     45 import android.view.LayoutInflater;
     46 import android.view.Menu;
     47 import android.view.MenuInflater;
     48 import android.view.MenuItem;
     49 import android.view.View;
     50 import android.view.ViewGroup;
     51 import android.view.ViewStub;
     52 import android.widget.AbsListView;
     53 import android.widget.AdapterView;
     54 import android.widget.AdapterView.AdapterContextMenuInfo;
     55 import android.widget.AdapterView.OnItemClickListener;
     56 import android.widget.BaseAdapter;
     57 import android.widget.ExpandableListView;
     58 import android.widget.ExpandableListView.ExpandableListContextMenuInfo;
     59 import android.widget.ExpandableListView.OnChildClickListener;
     60 import android.widget.ListView;
     61 import android.widget.TextView;
     62 import android.widget.Toast;
     63 
     64 /**
     65  * Activity for displaying the browser's history, divided into
     66  * days of viewing.
     67  */
     68 public class BrowserHistoryPage extends Fragment
     69         implements LoaderCallbacks<Cursor>, OnChildClickListener {
     70 
     71     static final int LOADER_HISTORY = 1;
     72     static final int LOADER_MOST_VISITED = 2;
     73 
     74     CombinedBookmarksCallbacks mCallback;
     75     HistoryAdapter mAdapter;
     76     HistoryChildWrapper mChildWrapper;
     77     boolean mDisableNewWindow;
     78     HistoryItem mContextHeader;
     79     String mMostVisitsLimit;
     80     ListView mGroupList, mChildList;
     81     private ViewGroup mPrefsContainer;
     82     private FragmentBreadCrumbs mFragmentBreadCrumbs;
     83     private ExpandableListView mHistoryList;
     84 
     85     private View mRoot;
     86 
     87     static interface HistoryQuery {
     88         static final String[] PROJECTION = new String[] {
     89                 Combined._ID, // 0
     90                 Combined.DATE_LAST_VISITED, // 1
     91                 Combined.TITLE, // 2
     92                 Combined.URL, // 3
     93                 Combined.FAVICON, // 4
     94                 Combined.VISITS, // 5
     95                 Combined.IS_BOOKMARK, // 6
     96         };
     97 
     98         static final int INDEX_ID = 0;
     99         static final int INDEX_DATE_LAST_VISITED = 1;
    100         static final int INDEX_TITE = 2;
    101         static final int INDEX_URL = 3;
    102         static final int INDEX_FAVICON = 4;
    103         static final int INDEX_VISITS = 5;
    104         static final int INDEX_IS_BOOKMARK = 6;
    105     }
    106 
    107     private void copy(CharSequence text) {
    108         ClipboardManager cm = (ClipboardManager) getActivity().getSystemService(
    109                 Context.CLIPBOARD_SERVICE);
    110         cm.setText(text);
    111     }
    112 
    113     @Override
    114     public Loader<Cursor> onCreateLoader(int id, Bundle args) {
    115         Uri.Builder combinedBuilder = Combined.CONTENT_URI.buildUpon();
    116 
    117         switch (id) {
    118             case LOADER_HISTORY: {
    119                 String sort = Combined.DATE_LAST_VISITED + " DESC";
    120                 String where = Combined.VISITS + " > 0";
    121                 CursorLoader loader = new CursorLoader(getActivity(), combinedBuilder.build(),
    122                         HistoryQuery.PROJECTION, where, null, sort);
    123                 return loader;
    124             }
    125 
    126             case LOADER_MOST_VISITED: {
    127                 Uri uri = combinedBuilder
    128                         .appendQueryParameter(BrowserContract.PARAM_LIMIT, mMostVisitsLimit)
    129                         .build();
    130                 String where = Combined.VISITS + " > 0";
    131                 CursorLoader loader = new CursorLoader(getActivity(), uri,
    132                         HistoryQuery.PROJECTION, where, null, Combined.VISITS + " DESC");
    133                 return loader;
    134             }
    135 
    136             default: {
    137                 throw new IllegalArgumentException();
    138             }
    139         }
    140     }
    141 
    142     void selectGroup(int position) {
    143         mGroupItemClickListener.onItemClick(null,
    144                 mAdapter.getGroupView(position, false, null, null),
    145                 position, position);
    146     }
    147 
    148     void checkIfEmpty() {
    149         if (mAdapter.mMostVisited != null && mAdapter.mHistoryCursor != null) {
    150             // Both cursors have loaded - check to see if we have data
    151             if (mAdapter.isEmpty()) {
    152                 mRoot.findViewById(R.id.history).setVisibility(View.GONE);
    153                 mRoot.findViewById(android.R.id.empty).setVisibility(View.VISIBLE);
    154             } else {
    155                 mRoot.findViewById(R.id.history).setVisibility(View.VISIBLE);
    156                 mRoot.findViewById(android.R.id.empty).setVisibility(View.GONE);
    157             }
    158         }
    159     }
    160 
    161     @Override
    162     public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
    163         switch (loader.getId()) {
    164             case LOADER_HISTORY: {
    165                 mAdapter.changeCursor(data);
    166                 if (!mAdapter.isEmpty() && mGroupList != null
    167                         && mGroupList.getCheckedItemPosition() == ListView.INVALID_POSITION) {
    168                     selectGroup(0);
    169                 }
    170 
    171                 checkIfEmpty();
    172                 break;
    173             }
    174 
    175             case LOADER_MOST_VISITED: {
    176                 mAdapter.changeMostVisitedCursor(data);
    177 
    178                 checkIfEmpty();
    179                 break;
    180             }
    181 
    182             default: {
    183                 throw new IllegalArgumentException();
    184             }
    185         }
    186     }
    187 
    188     @Override
    189     public void onLoaderReset(Loader<Cursor> loader) {
    190     }
    191 
    192     @Override
    193     public void onCreate(Bundle icicle) {
    194         super.onCreate(icicle);
    195 
    196         setHasOptionsMenu(true);
    197 
    198         Bundle args = getArguments();
    199         mDisableNewWindow = args.getBoolean(BrowserBookmarksPage.EXTRA_DISABLE_WINDOW, false);
    200         int mvlimit = getResources().getInteger(R.integer.most_visits_limit);
    201         mMostVisitsLimit = Integer.toString(mvlimit);
    202         mCallback = (CombinedBookmarksCallbacks) getActivity();
    203     }
    204 
    205     @Override
    206     public View onCreateView(LayoutInflater inflater, ViewGroup container,
    207             Bundle savedInstanceState) {
    208         mRoot = inflater.inflate(R.layout.history, container, false);
    209         mAdapter = new HistoryAdapter(getActivity());
    210         ViewStub stub = (ViewStub) mRoot.findViewById(R.id.pref_stub);
    211         if (stub != null) {
    212             inflateTwoPane(stub);
    213         } else {
    214             inflateSinglePane();
    215         }
    216 
    217         // Start the loaders
    218         getLoaderManager().restartLoader(LOADER_HISTORY, null, this);
    219         getLoaderManager().restartLoader(LOADER_MOST_VISITED, null, this);
    220 
    221         return mRoot;
    222     }
    223 
    224     private void inflateSinglePane() {
    225         mHistoryList = (ExpandableListView) mRoot.findViewById(R.id.history);
    226         mHistoryList.setAdapter(mAdapter);
    227         mHistoryList.setOnChildClickListener(this);
    228         registerForContextMenu(mHistoryList);
    229     }
    230 
    231     private void inflateTwoPane(ViewStub stub) {
    232         stub.setLayoutResource(R.layout.preference_list_content);
    233         stub.inflate();
    234         mGroupList = (ListView) mRoot.findViewById(android.R.id.list);
    235         mPrefsContainer = (ViewGroup) mRoot.findViewById(R.id.prefs_frame);
    236         mFragmentBreadCrumbs = (FragmentBreadCrumbs) mRoot.findViewById(android.R.id.title);
    237         mFragmentBreadCrumbs.setMaxVisible(1);
    238         mFragmentBreadCrumbs.setActivity(getActivity());
    239         mPrefsContainer.setVisibility(View.VISIBLE);
    240         mGroupList.setAdapter(new HistoryGroupWrapper(mAdapter));
    241         mGroupList.setOnItemClickListener(mGroupItemClickListener);
    242         mGroupList.setChoiceMode(AbsListView.CHOICE_MODE_SINGLE);
    243         mChildWrapper = new HistoryChildWrapper(mAdapter);
    244         mChildList = new ListView(getActivity());
    245         mChildList.setAdapter(mChildWrapper);
    246         mChildList.setOnItemClickListener(mChildItemClickListener);
    247         registerForContextMenu(mChildList);
    248         ViewGroup prefs = (ViewGroup) mRoot.findViewById(R.id.prefs);
    249         prefs.addView(mChildList);
    250     }
    251 
    252     private OnItemClickListener mGroupItemClickListener = new OnItemClickListener() {
    253         @Override
    254         public void onItemClick(
    255                 AdapterView<?> parent, View view, int position, long id) {
    256             CharSequence title = ((TextView) view).getText();
    257             mFragmentBreadCrumbs.setTitle(title, title);
    258             mChildWrapper.setSelectedGroup(position);
    259             mGroupList.setItemChecked(position, true);
    260         }
    261     };
    262 
    263     private OnItemClickListener mChildItemClickListener = new OnItemClickListener() {
    264         @Override
    265         public void onItemClick(
    266                 AdapterView<?> parent, View view, int position, long id) {
    267             mCallback.openUrl(((HistoryItem) view).getUrl());
    268         }
    269     };
    270 
    271     @Override
    272     public boolean onChildClick(ExpandableListView parent, View view,
    273             int groupPosition, int childPosition, long id) {
    274         mCallback.openUrl(((HistoryItem) view).getUrl());
    275         return true;
    276     }
    277 
    278     @Override
    279     public void onDestroy() {
    280         super.onDestroy();
    281         getLoaderManager().destroyLoader(LOADER_HISTORY);
    282         getLoaderManager().destroyLoader(LOADER_MOST_VISITED);
    283     }
    284 
    285     @Override
    286     public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
    287         super.onCreateOptionsMenu(menu, inflater);
    288         inflater.inflate(R.menu.history, menu);
    289     }
    290 
    291     void promptToClearHistory() {
    292         final ContentResolver resolver = getActivity().getContentResolver();
    293         final ClearHistoryTask clear = new ClearHistoryTask(resolver);
    294         AlertDialog.Builder builder = new AlertDialog.Builder(getActivity())
    295                 .setMessage(R.string.pref_privacy_clear_history_dlg)
    296                 .setIconAttribute(android.R.attr.alertDialogIcon)
    297                 .setNegativeButton(R.string.cancel, null)
    298                 .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
    299                      @Override
    300                      public void onClick(DialogInterface dialog, int which) {
    301                          if (which == DialogInterface.BUTTON_POSITIVE) {
    302                              clear.start();
    303                          }
    304                      }
    305                 });
    306         final Dialog dialog = builder.create();
    307         dialog.show();
    308     }
    309 
    310     @Override
    311     public boolean onOptionsItemSelected(MenuItem item) {
    312         if (item.getItemId() == R.id.clear_history_menu_id) {
    313             promptToClearHistory();
    314             return true;
    315         }
    316         return super.onOptionsItemSelected(item);
    317     }
    318 
    319     static class ClearHistoryTask extends Thread {
    320         ContentResolver mResolver;
    321 
    322         public ClearHistoryTask(ContentResolver resolver) {
    323             mResolver = resolver;
    324         }
    325 
    326         @Override
    327         public void run() {
    328             Browser.clearHistory(mResolver);
    329         }
    330     }
    331 
    332     View getTargetView(ContextMenuInfo menuInfo) {
    333         if (menuInfo instanceof AdapterContextMenuInfo) {
    334             return ((AdapterContextMenuInfo) menuInfo).targetView;
    335         }
    336         if (menuInfo instanceof ExpandableListContextMenuInfo) {
    337             return ((ExpandableListContextMenuInfo) menuInfo).targetView;
    338         }
    339         return null;
    340     }
    341 
    342     @Override
    343     public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
    344 
    345         View targetView = getTargetView(menuInfo);
    346         if (!(targetView instanceof HistoryItem)) {
    347             return;
    348         }
    349         HistoryItem historyItem = (HistoryItem) targetView;
    350 
    351         // Inflate the menu
    352         Activity parent = getActivity();
    353         MenuInflater inflater = parent.getMenuInflater();
    354         inflater.inflate(R.menu.historycontext, menu);
    355 
    356         // Setup the header
    357         if (mContextHeader == null) {
    358             mContextHeader = new HistoryItem(parent, false);
    359             mContextHeader.setEnableScrolling(true);
    360         } else if (mContextHeader.getParent() != null) {
    361             ((ViewGroup) mContextHeader.getParent()).removeView(mContextHeader);
    362         }
    363         historyItem.copyTo(mContextHeader);
    364         menu.setHeaderView(mContextHeader);
    365 
    366         // Only show open in new tab if it was not explicitly disabled
    367         if (mDisableNewWindow) {
    368             menu.findItem(R.id.new_window_context_menu_id).setVisible(false);
    369         }
    370         // For a bookmark, provide the option to remove it from bookmarks
    371         if (historyItem.isBookmark()) {
    372             MenuItem item = menu.findItem(R.id.save_to_bookmarks_menu_id);
    373             item.setTitle(R.string.remove_from_bookmarks);
    374         }
    375         // decide whether to show the share link option
    376         PackageManager pm = parent.getPackageManager();
    377         Intent send = new Intent(Intent.ACTION_SEND);
    378         send.setType("text/plain");
    379         ResolveInfo ri = pm.resolveActivity(send, PackageManager.MATCH_DEFAULT_ONLY);
    380         menu.findItem(R.id.share_link_context_menu_id).setVisible(ri != null);
    381 
    382         super.onCreateContextMenu(menu, v, menuInfo);
    383     }
    384 
    385     @Override
    386     public boolean onContextItemSelected(MenuItem item) {
    387         ContextMenuInfo menuInfo = item.getMenuInfo();
    388         if (menuInfo == null) {
    389             return false;
    390         }
    391         View targetView = getTargetView(menuInfo);
    392         if (!(targetView instanceof HistoryItem)) {
    393             return false;
    394         }
    395         HistoryItem historyItem = (HistoryItem) targetView;
    396         String url = historyItem.getUrl();
    397         String title = historyItem.getName();
    398         Activity activity = getActivity();
    399         switch (item.getItemId()) {
    400             case R.id.open_context_menu_id:
    401                 mCallback.openUrl(url);
    402                 return true;
    403             case R.id.new_window_context_menu_id:
    404                 mCallback.openInNewTab(url);
    405                 return true;
    406             case R.id.save_to_bookmarks_menu_id:
    407                 if (historyItem.isBookmark()) {
    408                     Bookmarks.removeFromBookmarks(activity, activity.getContentResolver(),
    409                             url, title);
    410                 } else {
    411                     Browser.saveBookmark(activity, title, url);
    412                 }
    413                 return true;
    414             case R.id.share_link_context_menu_id:
    415                 Browser.sendString(activity, url,
    416                         activity.getText(R.string.choosertitle_sharevia).toString());
    417                 return true;
    418             case R.id.copy_url_context_menu_id:
    419                 copy(url);
    420                 return true;
    421             case R.id.delete_context_menu_id:
    422                 Browser.deleteFromHistory(activity.getContentResolver(), url);
    423                 return true;
    424             case R.id.homepage_context_menu_id:
    425                 BrowserSettings.getInstance().setHomePage(url);
    426                 Toast.makeText(activity, R.string.homepage_set, Toast.LENGTH_LONG).show();
    427                 return true;
    428             default:
    429                 break;
    430         }
    431         return super.onContextItemSelected(item);
    432     }
    433 
    434     private static abstract class HistoryWrapper extends BaseAdapter {
    435 
    436         protected HistoryAdapter mAdapter;
    437         private DataSetObserver mObserver = new DataSetObserver() {
    438             @Override
    439             public void onChanged() {
    440                 super.onChanged();
    441                 notifyDataSetChanged();
    442             }
    443 
    444             @Override
    445             public void onInvalidated() {
    446                 super.onInvalidated();
    447                 notifyDataSetInvalidated();
    448             }
    449         };
    450 
    451         public HistoryWrapper(HistoryAdapter adapter) {
    452             mAdapter = adapter;
    453             mAdapter.registerDataSetObserver(mObserver);
    454         }
    455 
    456     }
    457     private static class HistoryGroupWrapper extends HistoryWrapper {
    458 
    459         public HistoryGroupWrapper(HistoryAdapter adapter) {
    460             super(adapter);
    461         }
    462 
    463         @Override
    464         public int getCount() {
    465             return mAdapter.getGroupCount();
    466         }
    467 
    468         @Override
    469         public Object getItem(int position) {
    470             return null;
    471         }
    472 
    473         @Override
    474         public long getItemId(int position) {
    475             return position;
    476         }
    477 
    478         @Override
    479         public View getView(int position, View convertView, ViewGroup parent) {
    480             return mAdapter.getGroupView(position, false, convertView, parent);
    481         }
    482 
    483     }
    484 
    485     private static class HistoryChildWrapper extends HistoryWrapper {
    486 
    487         private int mSelectedGroup;
    488 
    489         public HistoryChildWrapper(HistoryAdapter adapter) {
    490             super(adapter);
    491         }
    492 
    493         void setSelectedGroup(int groupPosition) {
    494             mSelectedGroup = groupPosition;
    495             notifyDataSetChanged();
    496         }
    497 
    498         @Override
    499         public int getCount() {
    500             return mAdapter.getChildrenCount(mSelectedGroup);
    501         }
    502 
    503         @Override
    504         public Object getItem(int position) {
    505             return null;
    506         }
    507 
    508         @Override
    509         public long getItemId(int position) {
    510             return position;
    511         }
    512 
    513         @Override
    514         public View getView(int position, View convertView, ViewGroup parent) {
    515             return mAdapter.getChildView(mSelectedGroup, position,
    516                     false, convertView, parent);
    517         }
    518 
    519     }
    520 
    521     private class HistoryAdapter extends DateSortedExpandableListAdapter {
    522 
    523         private Cursor mMostVisited, mHistoryCursor;
    524         Drawable mFaviconBackground;
    525 
    526         HistoryAdapter(Context context) {
    527             super(context, HistoryQuery.INDEX_DATE_LAST_VISITED);
    528             mFaviconBackground = BookmarkUtils.createListFaviconBackground(context);
    529         }
    530 
    531         @Override
    532         public void changeCursor(Cursor cursor) {
    533             mHistoryCursor = cursor;
    534             super.changeCursor(cursor);
    535         }
    536 
    537         void changeMostVisitedCursor(Cursor cursor) {
    538             if (mMostVisited == cursor) {
    539                 return;
    540             }
    541             if (mMostVisited != null) {
    542                 mMostVisited.unregisterDataSetObserver(mDataSetObserver);
    543                 mMostVisited.close();
    544             }
    545             mMostVisited = cursor;
    546             if (mMostVisited != null) {
    547                 mMostVisited.registerDataSetObserver(mDataSetObserver);
    548             }
    549             notifyDataSetChanged();
    550         }
    551 
    552         @Override
    553         public long getChildId(int groupPosition, int childPosition) {
    554             if (moveCursorToChildPosition(groupPosition, childPosition)) {
    555                 Cursor cursor = getCursor(groupPosition);
    556                 return cursor.getLong(HistoryQuery.INDEX_ID);
    557             }
    558             return 0;
    559         }
    560 
    561         @Override
    562         public int getGroupCount() {
    563             return super.getGroupCount() + (!isMostVisitedEmpty() ? 1 : 0);
    564         }
    565 
    566         @Override
    567         public int getChildrenCount(int groupPosition) {
    568             if (groupPosition >= super.getGroupCount()) {
    569                 if (isMostVisitedEmpty()) {
    570                     return 0;
    571                 }
    572                 return mMostVisited.getCount();
    573             }
    574             return super.getChildrenCount(groupPosition);
    575         }
    576 
    577         @Override
    578         public boolean isEmpty() {
    579             if (!super.isEmpty()) {
    580                 return false;
    581             }
    582             return isMostVisitedEmpty();
    583         }
    584 
    585         private boolean isMostVisitedEmpty() {
    586             return mMostVisited == null
    587                     || mMostVisited.isClosed()
    588                     || mMostVisited.getCount() == 0;
    589         }
    590 
    591         Cursor getCursor(int groupPosition) {
    592             if (groupPosition >= super.getGroupCount()) {
    593                 return mMostVisited;
    594             }
    595             return mHistoryCursor;
    596         }
    597 
    598         @Override
    599         public View getGroupView(int groupPosition, boolean isExpanded,
    600                 View convertView, ViewGroup parent) {
    601             if (groupPosition >= super.getGroupCount()) {
    602                 if (mMostVisited == null || mMostVisited.isClosed()) {
    603                     throw new IllegalStateException("Data is not valid");
    604                 }
    605                 TextView item;
    606                 if (null == convertView || !(convertView instanceof TextView)) {
    607                     LayoutInflater factory = LayoutInflater.from(getContext());
    608                     item = (TextView) factory.inflate(R.layout.history_header, null);
    609                 } else {
    610                     item = (TextView) convertView;
    611                 }
    612                 item.setText(R.string.tab_most_visited);
    613                 return item;
    614             }
    615             return super.getGroupView(groupPosition, isExpanded, convertView, parent);
    616         }
    617 
    618         @Override
    619         boolean moveCursorToChildPosition(
    620                 int groupPosition, int childPosition) {
    621             if (groupPosition >= super.getGroupCount()) {
    622                 if (mMostVisited != null && !mMostVisited.isClosed()) {
    623                     mMostVisited.moveToPosition(childPosition);
    624                     return true;
    625                 }
    626                 return false;
    627             }
    628             return super.moveCursorToChildPosition(groupPosition, childPosition);
    629         }
    630 
    631         @Override
    632         public View getChildView(int groupPosition, int childPosition, boolean isLastChild,
    633                 View convertView, ViewGroup parent) {
    634             HistoryItem item;
    635             if (null == convertView || !(convertView instanceof HistoryItem)) {
    636                 item = new HistoryItem(getContext());
    637                 // Add padding on the left so it will be indented from the
    638                 // arrows on the group views.
    639                 item.setPadding(item.getPaddingLeft() + 10,
    640                         item.getPaddingTop(),
    641                         item.getPaddingRight(),
    642                         item.getPaddingBottom());
    643                 item.setFaviconBackground(mFaviconBackground);
    644             } else {
    645                 item = (HistoryItem) convertView;
    646             }
    647 
    648             // Bail early if the Cursor is closed.
    649             if (!moveCursorToChildPosition(groupPosition, childPosition)) {
    650                 return item;
    651             }
    652 
    653             Cursor cursor = getCursor(groupPosition);
    654             item.setName(cursor.getString(HistoryQuery.INDEX_TITE));
    655             String url = cursor.getString(HistoryQuery.INDEX_URL);
    656             item.setUrl(url);
    657             byte[] data = cursor.getBlob(HistoryQuery.INDEX_FAVICON);
    658             if (data != null) {
    659                 item.setFavicon(BitmapFactory.decodeByteArray(data, 0,
    660                         data.length));
    661             }
    662             item.setIsBookmark(cursor.getInt(HistoryQuery.INDEX_IS_BOOKMARK) == 1);
    663             return item;
    664         }
    665     }
    666 }
    667