Home | History | Annotate | Download | only in provider
      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 android.provider;
     18 
     19 import android.content.ContentResolver;
     20 import android.content.ContentValues;
     21 import android.content.Context;
     22 import android.content.Intent;
     23 import android.database.Cursor;
     24 import android.database.DatabaseUtils;
     25 import android.net.Uri;
     26 import android.os.AsyncTask;
     27 import android.util.Log;
     28 import android.webkit.WebIconDatabase;
     29 
     30 import java.util.Date;
     31 
     32 public class Browser {
     33     private static final String LOGTAG = "browser";
     34     public static final Uri BOOKMARKS_URI =
     35         Uri.parse("content://browser/bookmarks");
     36 
     37     /**
     38      * The name of extra data when starting Browser with ACTION_VIEW or
     39      * ACTION_SEARCH intent.
     40      * <p>
     41      * The value should be an integer between 0 and 1000. If not set or set to
     42      * 0, the Browser will use default. If set to 100, the Browser will start
     43      * with 100%.
     44      */
     45     public static final String INITIAL_ZOOM_LEVEL = "browser.initialZoomLevel";
     46 
     47     /**
     48      * The name of the extra data when starting the Browser from another
     49      * application.
     50      * <p>
     51      * The value is a unique identification string that will be used to
     52      * indentify the calling application. The Browser will attempt to reuse the
     53      * same window each time the application launches the Browser with the same
     54      * identifier.
     55      */
     56     public static final String EXTRA_APPLICATION_ID =
     57         "com.android.browser.application_id";
     58 
     59     /**
     60      * The name of the extra data in the VIEW intent. The data are key/value
     61      * pairs in the format of Bundle. They will be sent in the HTTP request
     62      * headers for the provided url. The keys can't be the standard HTTP headers
     63      * as they are set by the WebView. The url's schema must be http(s).
     64      * <p>
     65      */
     66     public static final String EXTRA_HEADERS = "com.android.browser.headers";
     67 
     68     /* if you change column order you must also change indices
     69        below */
     70     public static final String[] HISTORY_PROJECTION = new String[] {
     71         BookmarkColumns._ID, BookmarkColumns.URL, BookmarkColumns.VISITS,
     72         BookmarkColumns.DATE, BookmarkColumns.BOOKMARK, BookmarkColumns.TITLE,
     73         BookmarkColumns.FAVICON, BookmarkColumns.THUMBNAIL,
     74         BookmarkColumns.TOUCH_ICON, BookmarkColumns.USER_ENTERED };
     75 
     76     /* these indices dependent on HISTORY_PROJECTION */
     77     public static final int HISTORY_PROJECTION_ID_INDEX = 0;
     78     public static final int HISTORY_PROJECTION_URL_INDEX = 1;
     79     public static final int HISTORY_PROJECTION_VISITS_INDEX = 2;
     80     public static final int HISTORY_PROJECTION_DATE_INDEX = 3;
     81     public static final int HISTORY_PROJECTION_BOOKMARK_INDEX = 4;
     82     public static final int HISTORY_PROJECTION_TITLE_INDEX = 5;
     83     public static final int HISTORY_PROJECTION_FAVICON_INDEX = 6;
     84     /**
     85      * @hide
     86      */
     87     public static final int HISTORY_PROJECTION_THUMBNAIL_INDEX = 7;
     88     /**
     89      * @hide
     90      */
     91     public static final int HISTORY_PROJECTION_TOUCH_ICON_INDEX = 8;
     92 
     93     /* columns needed to determine whether to truncate history */
     94     public static final String[] TRUNCATE_HISTORY_PROJECTION = new String[] {
     95         BookmarkColumns._ID, BookmarkColumns.DATE, };
     96     public static final int TRUNCATE_HISTORY_PROJECTION_ID_INDEX = 0;
     97 
     98     /* truncate this many history items at a time */
     99     public static final int TRUNCATE_N_OLDEST = 5;
    100 
    101     public static final Uri SEARCHES_URI =
    102         Uri.parse("content://browser/searches");
    103 
    104     /* if you change column order you must also change indices
    105        below */
    106     public static final String[] SEARCHES_PROJECTION = new String[] {
    107         SearchColumns._ID, SearchColumns.SEARCH, SearchColumns.DATE };
    108 
    109     /* these indices dependent on SEARCHES_PROJECTION */
    110     public static final int SEARCHES_PROJECTION_SEARCH_INDEX = 1;
    111     public static final int SEARCHES_PROJECTION_DATE_INDEX = 2;
    112 
    113     private static final String SEARCHES_WHERE_CLAUSE = "search = ?";
    114 
    115     /* Set a cap on the count of history items in the history/bookmark
    116        table, to prevent db and layout operations from dragging to a
    117        crawl.  Revisit this cap when/if db/layout performance
    118        improvements are made.  Note: this does not affect bookmark
    119        entries -- if the user wants more bookmarks than the cap, they
    120        get them. */
    121     private static final int MAX_HISTORY_COUNT = 250;
    122 
    123     /**
    124      *  Open the AddBookmark activity to save a bookmark.  Launch with
    125      *  and/or url, which can be edited by the user before saving.
    126      *  @param c        Context used to launch the AddBookmark activity.
    127      *  @param title    Title for the bookmark. Can be null or empty string.
    128      *  @param url      Url for the bookmark. Can be null or empty string.
    129      */
    130     public static final void saveBookmark(Context c,
    131                                           String title,
    132                                           String url) {
    133         Intent i = new Intent(Intent.ACTION_INSERT, Browser.BOOKMARKS_URI);
    134         i.putExtra("title", title);
    135         i.putExtra("url", url);
    136         c.startActivity(i);
    137     }
    138 
    139     /**
    140      * Stores a Bitmap extra in an {@link Intent} representing the screenshot of
    141      * a page to share.  When receiving an {@link Intent#ACTION_SEND} from the
    142      * Browser, use this to access the screenshot.
    143      * @hide
    144      */
    145     public final static String EXTRA_SHARE_SCREENSHOT = "share_screenshot";
    146 
    147     /**
    148      * Stores a Bitmap extra in an {@link Intent} representing the favicon of a
    149      * page to share.  When receiving an {@link Intent#ACTION_SEND} from the
    150      * Browser, use this to access the favicon.
    151      * @hide
    152      */
    153     public final static String EXTRA_SHARE_FAVICON = "share_favicon";
    154 
    155     public static final void sendString(Context c, String s) {
    156         sendString(c, s, c.getString(com.android.internal.R.string.sendText));
    157     }
    158 
    159     /**
    160      *  Find an application to handle the given string and, if found, invoke
    161      *  it with the given string as a parameter.
    162      *  @param c Context used to launch the new activity.
    163      *  @param stringToSend The string to be handled.
    164      *  @param chooserDialogTitle The title of the dialog that allows the user
    165      *  to select between multiple applications that are all capable of handling
    166      *  the string.
    167      *  @hide pending API council approval
    168      */
    169     public static final void sendString(Context c,
    170                                         String stringToSend,
    171                                         String chooserDialogTitle) {
    172         Intent send = new Intent(Intent.ACTION_SEND);
    173         send.setType("text/plain");
    174         send.putExtra(Intent.EXTRA_TEXT, stringToSend);
    175 
    176         try {
    177             c.startActivity(Intent.createChooser(send, chooserDialogTitle));
    178         } catch(android.content.ActivityNotFoundException ex) {
    179             // if no app handles it, do nothing
    180         }
    181     }
    182 
    183     /**
    184      *  Return a cursor pointing to a list of all the bookmarks.
    185      *  Requires {@link android.Manifest.permission#READ_HISTORY_BOOKMARKS}
    186      *  @param cr   The ContentResolver used to access the database.
    187      */
    188     public static final Cursor getAllBookmarks(ContentResolver cr) throws
    189             IllegalStateException {
    190         return cr.query(BOOKMARKS_URI,
    191                 new String[] { BookmarkColumns.URL },
    192                 "bookmark = 1", null, null);
    193     }
    194 
    195     /**
    196      *  Return a cursor pointing to a list of all visited site urls.
    197      *  Requires {@link android.Manifest.permission#READ_HISTORY_BOOKMARKS}
    198      *  @param cr   The ContentResolver used to access the database.
    199      */
    200     public static final Cursor getAllVisitedUrls(ContentResolver cr) throws
    201             IllegalStateException {
    202         return cr.query(BOOKMARKS_URI,
    203                 new String[] { BookmarkColumns.URL }, null, null, null);
    204     }
    205 
    206     private static final void addOrUrlEquals(StringBuilder sb) {
    207         sb.append(" OR " + BookmarkColumns.URL + " = ");
    208     }
    209 
    210     /**
    211      *  Return a Cursor with all history/bookmarks that are similar to url,
    212      *  where similar means 'http(s)://' and 'www.' are optional, but the rest
    213      *  of the url is the same.
    214      *  @param cr   The ContentResolver used to access the database.
    215      *  @param url  The url to compare to.
    216      *  @hide
    217      */
    218     public static final Cursor getVisitedLike(ContentResolver cr, String url) {
    219         boolean secure = false;
    220         String compareString = url;
    221         if (compareString.startsWith("http://")) {
    222             compareString = compareString.substring(7);
    223         } else if (compareString.startsWith("https://")) {
    224             compareString = compareString.substring(8);
    225             secure = true;
    226         }
    227         if (compareString.startsWith("www.")) {
    228             compareString = compareString.substring(4);
    229         }
    230         StringBuilder whereClause = null;
    231         if (secure) {
    232             whereClause = new StringBuilder(BookmarkColumns.URL + " = ");
    233             DatabaseUtils.appendEscapedSQLString(whereClause,
    234                     "https://" + compareString);
    235             addOrUrlEquals(whereClause);
    236             DatabaseUtils.appendEscapedSQLString(whereClause,
    237                     "https://www." + compareString);
    238         } else {
    239             whereClause = new StringBuilder(BookmarkColumns.URL + " = ");
    240             DatabaseUtils.appendEscapedSQLString(whereClause,
    241                     compareString);
    242             addOrUrlEquals(whereClause);
    243             String wwwString = "www." + compareString;
    244             DatabaseUtils.appendEscapedSQLString(whereClause,
    245                     wwwString);
    246             addOrUrlEquals(whereClause);
    247             DatabaseUtils.appendEscapedSQLString(whereClause,
    248                     "http://" + compareString);
    249             addOrUrlEquals(whereClause);
    250             DatabaseUtils.appendEscapedSQLString(whereClause,
    251                     "http://" + wwwString);
    252         }
    253         return cr.query(BOOKMARKS_URI, HISTORY_PROJECTION,
    254                 whereClause.toString(), null, null);
    255     }
    256 
    257     /**
    258      *  Update the visited history to acknowledge that a site has been
    259      *  visited.
    260      *  Requires {@link android.Manifest.permission#READ_HISTORY_BOOKMARKS}
    261      *  Requires {@link android.Manifest.permission#WRITE_HISTORY_BOOKMARKS}
    262      *  @param cr   The ContentResolver used to access the database.
    263      *  @param url  The site being visited.
    264      *  @param real If true, this is an actual visit, and should add to the
    265      *              number of visits.  If false, the user entered it manually.
    266      */
    267     public static final void updateVisitedHistory(ContentResolver cr,
    268                                                   String url, boolean real) {
    269         long now = new Date().getTime();
    270         Cursor c = null;
    271         try {
    272             c = getVisitedLike(cr, url);
    273             /* We should only get one answer that is exactly the same. */
    274             if (c.moveToFirst()) {
    275                 ContentValues map = new ContentValues();
    276                 if (real) {
    277                     map.put(BookmarkColumns.VISITS, c
    278                             .getInt(HISTORY_PROJECTION_VISITS_INDEX) + 1);
    279                 } else {
    280                     map.put(BookmarkColumns.USER_ENTERED, 1);
    281                 }
    282                 map.put(BookmarkColumns.DATE, now);
    283                 String[] projection = new String[]
    284                         { Integer.valueOf(c.getInt(0)).toString() };
    285                 cr.update(BOOKMARKS_URI, map, "_id = ?", projection);
    286             } else {
    287                 truncateHistory(cr);
    288                 ContentValues map = new ContentValues();
    289                 int visits;
    290                 int user_entered;
    291                 if (real) {
    292                     visits = 1;
    293                     user_entered = 0;
    294                 } else {
    295                     visits = 0;
    296                     user_entered = 1;
    297                 }
    298                 map.put(BookmarkColumns.URL, url);
    299                 map.put(BookmarkColumns.VISITS, visits);
    300                 map.put(BookmarkColumns.DATE, now);
    301                 map.put(BookmarkColumns.BOOKMARK, 0);
    302                 map.put(BookmarkColumns.TITLE, url);
    303                 map.put(BookmarkColumns.CREATED, 0);
    304                 map.put(BookmarkColumns.USER_ENTERED, user_entered);
    305                 cr.insert(BOOKMARKS_URI, map);
    306             }
    307         } catch (IllegalStateException e) {
    308             Log.e(LOGTAG, "updateVisitedHistory", e);
    309         } finally {
    310             if (c != null) c.close();
    311         }
    312     }
    313 
    314     /**
    315      *  Returns all the URLs in the history.
    316      *  Requires {@link android.Manifest.permission#READ_HISTORY_BOOKMARKS}
    317      *  @param cr   The ContentResolver used to access the database.
    318      *  @hide pending API council approval
    319      */
    320     public static final String[] getVisitedHistory(ContentResolver cr) {
    321         Cursor c = null;
    322         String[] str = null;
    323         try {
    324             String[] projection = new String[] {
    325                 "url"
    326             };
    327             c = cr.query(BOOKMARKS_URI, projection, "visits > 0", null,
    328                     null);
    329             str = new String[c.getCount()];
    330             int i = 0;
    331             while (c.moveToNext()) {
    332                 str[i] = c.getString(0);
    333                 i++;
    334             }
    335         } catch (IllegalStateException e) {
    336             Log.e(LOGTAG, "getVisitedHistory", e);
    337             str = new String[0];
    338         } finally {
    339             if (c != null) c.close();
    340         }
    341         return str;
    342     }
    343 
    344     /**
    345      * If there are more than MAX_HISTORY_COUNT non-bookmark history
    346      * items in the bookmark/history table, delete TRUNCATE_N_OLDEST
    347      * of them.  This is used to keep our history table to a
    348      * reasonable size.  Note: it does not prune bookmarks.  If the
    349      * user wants 1000 bookmarks, the user gets 1000 bookmarks.
    350      *  Requires {@link android.Manifest.permission#READ_HISTORY_BOOKMARKS}
    351      *  Requires {@link android.Manifest.permission#WRITE_HISTORY_BOOKMARKS}
    352      *
    353      * @param cr The ContentResolver used to access the database.
    354      */
    355     public static final void truncateHistory(ContentResolver cr) {
    356         Cursor c = null;
    357         try {
    358             // Select non-bookmark history, ordered by date
    359             c = cr.query(
    360                     BOOKMARKS_URI,
    361                     TRUNCATE_HISTORY_PROJECTION,
    362                     "bookmark = 0",
    363                     null,
    364                     BookmarkColumns.DATE);
    365             // Log.v(LOGTAG, "history count " + c.count());
    366             if (c.moveToFirst() && c.getCount() >= MAX_HISTORY_COUNT) {
    367                 /* eliminate oldest history items */
    368                 for (int i = 0; i < TRUNCATE_N_OLDEST; i++) {
    369                     // Log.v(LOGTAG, "truncate history " +
    370                     // c.getInt(TRUNCATE_HISTORY_PROJECTION_ID_INDEX));
    371                     cr.delete(BOOKMARKS_URI, "_id = " +
    372                             c.getInt(TRUNCATE_HISTORY_PROJECTION_ID_INDEX),
    373                             null);
    374                     if (!c.moveToNext()) break;
    375                 }
    376             }
    377         } catch (IllegalStateException e) {
    378             Log.e(LOGTAG, "truncateHistory", e);
    379         } finally {
    380             if (c != null) c.close();
    381         }
    382     }
    383 
    384     /**
    385      * Returns whether there is any history to clear.
    386      *  Requires {@link android.Manifest.permission#READ_HISTORY_BOOKMARKS}
    387      * @param cr   The ContentResolver used to access the database.
    388      * @return boolean  True if the history can be cleared.
    389      */
    390     public static final boolean canClearHistory(ContentResolver cr) {
    391         Cursor c = null;
    392         boolean ret = false;
    393         try {
    394             c = cr.query(
    395                 BOOKMARKS_URI,
    396                 new String [] { BookmarkColumns._ID,
    397                                 BookmarkColumns.BOOKMARK,
    398                                 BookmarkColumns.VISITS },
    399                 "bookmark = 0 OR visits > 0",
    400                 null,
    401                 null
    402                 );
    403             ret = c.moveToFirst();
    404         } catch (IllegalStateException e) {
    405             Log.e(LOGTAG, "canClearHistory", e);
    406         } finally {
    407             if (c != null) c.close();
    408         }
    409         return ret;
    410     }
    411 
    412     /**
    413      *  Delete all entries from the bookmarks/history table which are
    414      *  not bookmarks.  Also set all visited bookmarks to unvisited.
    415      *  Requires {@link android.Manifest.permission#WRITE_HISTORY_BOOKMARKS}
    416      *  @param cr   The ContentResolver used to access the database.
    417      */
    418     public static final void clearHistory(ContentResolver cr) {
    419         deleteHistoryWhere(cr, null);
    420     }
    421 
    422     /**
    423      * Helper function to delete all history items and revert all
    424      * bookmarks to zero visits which meet the criteria provided.
    425      *  Requires {@link android.Manifest.permission#READ_HISTORY_BOOKMARKS}
    426      *  Requires {@link android.Manifest.permission#WRITE_HISTORY_BOOKMARKS}
    427      * @param cr   The ContentResolver used to access the database.
    428      * @param whereClause   String to limit the items affected.
    429      *                      null means all items.
    430      */
    431     private static final void deleteHistoryWhere(ContentResolver cr,
    432             String whereClause) {
    433         Cursor c = null;
    434         try {
    435             c = cr.query(BOOKMARKS_URI,
    436                 HISTORY_PROJECTION,
    437                 whereClause,
    438                 null,
    439                 null);
    440             if (c.moveToFirst()) {
    441                 final WebIconDatabase iconDb = WebIconDatabase.getInstance();
    442                 /* Delete favicons, and revert bookmarks which have been visited
    443                  * to simply bookmarks.
    444                  */
    445                 StringBuffer sb = new StringBuffer();
    446                 boolean firstTime = true;
    447                 do {
    448                     String url = c.getString(HISTORY_PROJECTION_URL_INDEX);
    449                     boolean isBookmark =
    450                         c.getInt(HISTORY_PROJECTION_BOOKMARK_INDEX) == 1;
    451                     if (isBookmark) {
    452                         if (firstTime) {
    453                             firstTime = false;
    454                         } else {
    455                             sb.append(" OR ");
    456                         }
    457                         sb.append("( _id = ");
    458                         sb.append(c.getInt(0));
    459                         sb.append(" )");
    460                     } else {
    461                         iconDb.releaseIconForPageUrl(url);
    462                     }
    463                 } while (c.moveToNext());
    464 
    465                 if (!firstTime) {
    466                     ContentValues map = new ContentValues();
    467                     map.put(BookmarkColumns.VISITS, 0);
    468                     map.put(BookmarkColumns.DATE, 0);
    469                     /* FIXME: Should I also remove the title? */
    470                     cr.update(BOOKMARKS_URI, map, sb.toString(), null);
    471                 }
    472 
    473                 String deleteWhereClause = BookmarkColumns.BOOKMARK + " = 0";
    474                 if (whereClause != null) {
    475                     deleteWhereClause += " AND " + whereClause;
    476                 }
    477                 cr.delete(BOOKMARKS_URI, deleteWhereClause, null);
    478             }
    479         } catch (IllegalStateException e) {
    480             Log.e(LOGTAG, "deleteHistoryWhere", e);
    481             return;
    482         } finally {
    483             if (c != null) c.close();
    484         }
    485     }
    486 
    487     /**
    488      * Delete all history items from begin to end.
    489      *  Requires {@link android.Manifest.permission#WRITE_HISTORY_BOOKMARKS}
    490      * @param cr    The ContentResolver used to access the database.
    491      * @param begin First date to remove.  If -1, all dates before end.
    492      *              Inclusive.
    493      * @param end   Last date to remove. If -1, all dates after begin.
    494      *              Non-inclusive.
    495      */
    496     public static final void deleteHistoryTimeFrame(ContentResolver cr,
    497             long begin, long end) {
    498         String whereClause;
    499         String date = BookmarkColumns.DATE;
    500         if (-1 == begin) {
    501             if (-1 == end) {
    502                 clearHistory(cr);
    503                 return;
    504             }
    505             whereClause = date + " < " + Long.toString(end);
    506         } else if (-1 == end) {
    507             whereClause = date + " >= " + Long.toString(begin);
    508         } else {
    509             whereClause = date + " >= " + Long.toString(begin) + " AND " + date
    510                     + " < " + Long.toString(end);
    511         }
    512         deleteHistoryWhere(cr, whereClause);
    513     }
    514 
    515     /**
    516      * Remove a specific url from the history database.
    517      *  Requires {@link android.Manifest.permission#WRITE_HISTORY_BOOKMARKS}
    518      * @param cr    The ContentResolver used to access the database.
    519      * @param url   url to remove.
    520      */
    521     public static final void deleteFromHistory(ContentResolver cr,
    522                                                String url) {
    523         StringBuilder sb = new StringBuilder(BookmarkColumns.URL + " = ");
    524         DatabaseUtils.appendEscapedSQLString(sb, url);
    525         String matchesUrl = sb.toString();
    526         deleteHistoryWhere(cr, matchesUrl);
    527     }
    528 
    529     /**
    530      * Add a search string to the searches database.
    531      *  Requires {@link android.Manifest.permission#READ_HISTORY_BOOKMARKS}
    532      *  Requires {@link android.Manifest.permission#WRITE_HISTORY_BOOKMARKS}
    533      * @param cr   The ContentResolver used to access the database.
    534      * @param search    The string to add to the searches database.
    535      */
    536     public static final void addSearchUrl(ContentResolver cr, String search) {
    537         long now = new Date().getTime();
    538         Cursor c = null;
    539         try {
    540             c = cr.query(
    541                 SEARCHES_URI,
    542                 SEARCHES_PROJECTION,
    543                 SEARCHES_WHERE_CLAUSE,
    544                 new String [] { search },
    545                 null);
    546             ContentValues map = new ContentValues();
    547             map.put(SearchColumns.SEARCH, search);
    548             map.put(SearchColumns.DATE, now);
    549             /* We should only get one answer that is exactly the same. */
    550             if (c.moveToFirst()) {
    551                 cr.update(SEARCHES_URI, map, "_id = " + c.getInt(0), null);
    552             } else {
    553                 cr.insert(SEARCHES_URI, map);
    554             }
    555         } catch (IllegalStateException e) {
    556             Log.e(LOGTAG, "addSearchUrl", e);
    557         } finally {
    558             if (c != null) c.close();
    559         }
    560     }
    561     /**
    562      * Remove all searches from the search database.
    563      *  Requires {@link android.Manifest.permission#WRITE_HISTORY_BOOKMARKS}
    564      * @param cr   The ContentResolver used to access the database.
    565      */
    566     public static final void clearSearches(ContentResolver cr) {
    567         // FIXME: Should this clear the urls to which these searches lead?
    568         // (i.e. remove google.com/query= blah blah blah)
    569         try {
    570             cr.delete(SEARCHES_URI, null, null);
    571         } catch (IllegalStateException e) {
    572             Log.e(LOGTAG, "clearSearches", e);
    573         }
    574     }
    575 
    576     /**
    577      *  Request all icons from the database.  This call must either be called
    578      *  in the main thread or have had Looper.prepare() invoked in the calling
    579      *  thread.
    580      *  Requires {@link android.Manifest.permission#READ_HISTORY_BOOKMARKS}
    581      *  @param  cr The ContentResolver used to access the database.
    582      *  @param  where Clause to be used to limit the query from the database.
    583      *          Must be an allowable string to be passed into a database query.
    584      *  @param  listener IconListener that gets the icons once they are
    585      *          retrieved.
    586      */
    587     public static final void requestAllIcons(ContentResolver cr, String where,
    588             WebIconDatabase.IconListener listener) {
    589         WebIconDatabase.getInstance()
    590                 .bulkRequestIconForPageUrl(cr, where, listener);
    591     }
    592 
    593     public static class BookmarkColumns implements BaseColumns {
    594         public static final String URL = "url";
    595         public static final String VISITS = "visits";
    596         public static final String DATE = "date";
    597         public static final String BOOKMARK = "bookmark";
    598         public static final String TITLE = "title";
    599         public static final String CREATED = "created";
    600         public static final String FAVICON = "favicon";
    601         /**
    602          * @hide
    603          */
    604         public static final String THUMBNAIL = "thumbnail";
    605         /**
    606          * @hide
    607          */
    608         public static final String TOUCH_ICON = "touch_icon";
    609         /**
    610          * @hide
    611          */
    612         public static final String USER_ENTERED = "user_entered";
    613     }
    614 
    615     public static class SearchColumns implements BaseColumns {
    616         public static final String URL = "url";
    617         public static final String SEARCH = "search";
    618         public static final String DATE = "date";
    619     }
    620 }
    621