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