Browser.java revision 908baedfcf83abc8f5564aa8b427b05989f8591f
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
17package android.provider;
18
19import android.content.ContentResolver;
20import android.content.ContentValues;
21import android.content.Context;
22import android.content.Intent;
23import android.database.Cursor;
24import android.database.DatabaseUtils;
25import android.net.Uri;
26import android.util.Log;
27import android.webkit.WebIconDatabase;
28
29import java.util.Date;
30
31public 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    /* if you change column order you must also change indices
95       below */
96    public static final String[] HISTORY_PROJECTION = new String[] {
97        BookmarkColumns._ID, BookmarkColumns.URL, BookmarkColumns.VISITS,
98        BookmarkColumns.DATE, BookmarkColumns.BOOKMARK, BookmarkColumns.TITLE,
99        BookmarkColumns.FAVICON, BookmarkColumns.THUMBNAIL };
100
101    /* these indices dependent on HISTORY_PROJECTION */
102    public static final int HISTORY_PROJECTION_ID_INDEX = 0;
103    public static final int HISTORY_PROJECTION_URL_INDEX = 1;
104    public static final int HISTORY_PROJECTION_VISITS_INDEX = 2;
105    public static final int HISTORY_PROJECTION_DATE_INDEX = 3;
106    public static final int HISTORY_PROJECTION_BOOKMARK_INDEX = 4;
107    public static final int HISTORY_PROJECTION_TITLE_INDEX = 5;
108    public static final int HISTORY_PROJECTION_FAVICON_INDEX = 6;
109    public static final int HISTORY_PROJECTION_THUMBNAIL_INDEX = 7;
110
111    /* columns needed to determine whether to truncate history */
112    public static final String[] TRUNCATE_HISTORY_PROJECTION = new String[] {
113        BookmarkColumns._ID, BookmarkColumns.DATE, };
114    public static final int TRUNCATE_HISTORY_PROJECTION_ID_INDEX = 0;
115
116    /* truncate this many history items at a time */
117    public static final int TRUNCATE_N_OLDEST = 5;
118
119    public static final Uri SEARCHES_URI =
120        Uri.parse("content://browser/searches");
121
122    /* if you change column order you must also change indices
123       below */
124    public static final String[] SEARCHES_PROJECTION = new String[] {
125        SearchColumns._ID, SearchColumns.SEARCH, SearchColumns.DATE };
126
127    /* these indices dependent on SEARCHES_PROJECTION */
128    public static final int SEARCHES_PROJECTION_SEARCH_INDEX = 1;
129    public static final int SEARCHES_PROJECTION_DATE_INDEX = 2;
130
131    private static final String SEARCHES_WHERE_CLAUSE = "search = ?";
132
133    /* Set a cap on the count of history items in the history/bookmark
134       table, to prevent db and layout operations from dragging to a
135       crawl.  Revisit this cap when/if db/layout performance
136       improvements are made.  Note: this does not affect bookmark
137       entries -- if the user wants more bookmarks than the cap, they
138       get them. */
139    private static final int MAX_HISTORY_COUNT = 250;
140
141    /**
142     *  Open the AddBookmark activity to save a bookmark.  Launch with
143     *  and/or url, which can be edited by the user before saving.
144     *  @param c        Context used to launch the AddBookmark activity.
145     *  @param title    Title for the bookmark. Can be null or empty string.
146     *  @param url      Url for the bookmark. Can be null or empty string.
147     */
148    public static final void saveBookmark(Context c,
149                                          String title,
150                                          String url) {
151        Intent i = new Intent(Intent.ACTION_INSERT, Browser.BOOKMARKS_URI);
152        i.putExtra("title", title);
153        i.putExtra("url", url);
154        c.startActivity(i);
155    }
156
157    public static final void sendString(Context c, String s) {
158        Intent send = new Intent(Intent.ACTION_SEND);
159        send.setType("text/plain");
160        send.putExtra(Intent.EXTRA_TEXT, s);
161
162        try {
163            c.startActivity(Intent.createChooser(send,
164                    c.getText(com.android.internal.R.string.sendText)));
165        } catch(android.content.ActivityNotFoundException ex) {
166            // if no app handles it, do nothing
167        }
168    }
169
170    /**
171     *  Return a cursor pointing to a list of all the bookmarks.
172     *  @param cr   The ContentResolver used to access the database.
173     */
174    public static final Cursor getAllBookmarks(ContentResolver cr) throws
175            IllegalStateException {
176        return cr.query(BOOKMARKS_URI,
177                new String[] { BookmarkColumns.URL },
178                "bookmark = 1", null, null);
179    }
180
181    /**
182     *  Return a cursor pointing to a list of all visited site urls.
183     *  @param cr   The ContentResolver used to access the database.
184     */
185    public static final Cursor getAllVisitedUrls(ContentResolver cr) throws
186            IllegalStateException {
187        return cr.query(BOOKMARKS_URI,
188                new String[] { BookmarkColumns.URL }, null, null, null);
189    }
190
191    /**
192     *  Update the visited history to acknowledge that a site has been
193     *  visited.
194     *  @param cr   The ContentResolver used to access the database.
195     *  @param url  The site being visited.
196     *  @param real Whether this is an actual visit, and should be added to the
197     *              number of visits.
198     */
199    public static final void updateVisitedHistory(ContentResolver cr,
200                                                  String url, boolean real) {
201        long now = new Date().getTime();
202        try {
203            StringBuilder sb = new StringBuilder(BookmarkColumns.URL + " = ");
204            DatabaseUtils.appendEscapedSQLString(sb, url);
205            Cursor c = cr.query(
206                    BOOKMARKS_URI,
207                    HISTORY_PROJECTION,
208                    sb.toString(),
209                    null,
210                    null);
211            /* We should only get one answer that is exactly the same. */
212            if (c.moveToFirst()) {
213                ContentValues map = new ContentValues();
214                if (real) {
215                    map.put(BookmarkColumns.VISITS, c
216                            .getInt(HISTORY_PROJECTION_VISITS_INDEX) + 1);
217                }
218                map.put(BookmarkColumns.DATE, now);
219                cr.update(BOOKMARKS_URI, map, "_id = " + c.getInt(0), null);
220            } else {
221                truncateHistory(cr);
222                ContentValues map = new ContentValues();
223                map.put(BookmarkColumns.URL, url);
224                map.put(BookmarkColumns.VISITS, real ? 1 : 0);
225                map.put(BookmarkColumns.DATE, now);
226                map.put(BookmarkColumns.BOOKMARK, 0);
227                map.put(BookmarkColumns.TITLE, url);
228                map.put(BookmarkColumns.CREATED, 0);
229                cr.insert(BOOKMARKS_URI, map);
230            }
231            c.deactivate();
232        } catch (IllegalStateException e) {
233            return;
234        }
235    }
236
237    /**
238     * If there are more than MAX_HISTORY_COUNT non-bookmark history
239     * items in the bookmark/history table, delete TRUNCATE_N_OLDEST
240     * of them.  This is used to keep our history table to a
241     * reasonable size.  Note: it does not prune bookmarks.  If the
242     * user wants 1000 bookmarks, the user gets 1000 bookmarks.
243     *
244     * @param cr The ContentResolver used to access the database.
245     */
246    public static final void truncateHistory(ContentResolver cr) {
247        try {
248            // Select non-bookmark history, ordered by date
249            Cursor c = cr.query(
250                    BOOKMARKS_URI,
251                    TRUNCATE_HISTORY_PROJECTION,
252                    "bookmark = 0",
253                    null,
254                    BookmarkColumns.DATE);
255            // Log.v(LOGTAG, "history count " + c.count());
256            if (c.moveToFirst() && c.getCount() >= MAX_HISTORY_COUNT) {
257                /* eliminate oldest history items */
258                for (int i = 0; i < TRUNCATE_N_OLDEST; i++) {
259                    // Log.v(LOGTAG, "truncate history " +
260                    // c.getInt(TRUNCATE_HISTORY_PROJECTION_ID_INDEX));
261                    deleteHistoryWhere(
262                            cr, "_id = " +
263                            c.getInt(TRUNCATE_HISTORY_PROJECTION_ID_INDEX));
264                    if (!c.moveToNext()) break;
265                }
266            }
267            c.deactivate();
268        } catch (IllegalStateException e) {
269            Log.e(LOGTAG, "truncateHistory", e);
270            return;
271        }
272    }
273
274    /**
275     * Returns whether there is any history to clear.
276     * @param cr   The ContentResolver used to access the database.
277     * @return boolean  True if the history can be cleared.
278     */
279    public static final boolean canClearHistory(ContentResolver cr) {
280        try {
281            Cursor c = cr.query(
282                BOOKMARKS_URI,
283                new String [] { BookmarkColumns._ID,
284                                BookmarkColumns.BOOKMARK,
285                                BookmarkColumns.VISITS },
286                "bookmark = 0 OR visits > 0",
287                null,
288                null
289                );
290            boolean ret = c.moveToFirst();
291            c.deactivate();
292            return ret;
293        } catch (IllegalStateException e) {
294            return false;
295        }
296    }
297
298    /**
299     *  Delete all entries from the bookmarks/history table which are
300     *  not bookmarks.  Also set all visited bookmarks to unvisited.
301     *  @param cr   The ContentResolver used to access the database.
302     */
303    public static final void clearHistory(ContentResolver cr) {
304        deleteHistoryWhere(cr, null);
305    }
306
307    /**
308     * Helper function to delete all history items and revert all
309     * bookmarks to zero visits which meet the criteria provided.
310     * @param cr   The ContentResolver used to access the database.
311     * @param whereClause   String to limit the items affected.
312     *                      null means all items.
313     */
314    private static final void deleteHistoryWhere(ContentResolver cr,
315            String whereClause) {
316        try {
317            Cursor c = cr.query(BOOKMARKS_URI,
318                HISTORY_PROJECTION,
319                whereClause,
320                null,
321                null);
322            if (!c.moveToFirst()) {
323                c.deactivate();
324                return;
325            }
326
327            final WebIconDatabase iconDb = WebIconDatabase.getInstance();
328            /* Delete favicons, and revert bookmarks which have been visited
329             * to simply bookmarks.
330             */
331            StringBuffer sb = new StringBuffer();
332            boolean firstTime = true;
333            do {
334                String url = c.getString(HISTORY_PROJECTION_URL_INDEX);
335                boolean isBookmark =
336                    c.getInt(HISTORY_PROJECTION_BOOKMARK_INDEX) == 1;
337                if (isBookmark) {
338                    if (firstTime) {
339                        firstTime = false;
340                    } else {
341                        sb.append(" OR ");
342                    }
343                    sb.append("( _id = ");
344                    sb.append(c.getInt(0));
345                    sb.append(" )");
346                } else {
347                    iconDb.releaseIconForPageUrl(url);
348                }
349            } while (c.moveToNext());
350            c.deactivate();
351
352            if (!firstTime) {
353                ContentValues map = new ContentValues();
354                map.put(BookmarkColumns.VISITS, 0);
355                map.put(BookmarkColumns.DATE, 0);
356                /* FIXME: Should I also remove the title? */
357                cr.update(BOOKMARKS_URI, map, sb.toString(), null);
358            }
359
360            String deleteWhereClause = BookmarkColumns.BOOKMARK + " = 0";
361            if (whereClause != null) {
362                deleteWhereClause += " AND " + whereClause;
363            }
364            cr.delete(BOOKMARKS_URI, deleteWhereClause, null);
365        } catch (IllegalStateException e) {
366            return;
367        }
368    }
369
370    /**
371     * Delete all history items from begin to end.
372     * @param cr    The ContentResolver used to access the database.
373     * @param begin First date to remove.  If -1, all dates before end.
374     *              Inclusive.
375     * @param end   Last date to remove. If -1, all dates after begin.
376     *              Non-inclusive.
377     */
378    public static final void deleteHistoryTimeFrame(ContentResolver cr,
379            long begin, long end) {
380        String whereClause;
381        String date = BookmarkColumns.DATE;
382        if (-1 == begin) {
383            if (-1 == end) {
384                clearHistory(cr);
385                return;
386            }
387            whereClause = date + " < " + Long.toString(end);
388        } else if (-1 == end) {
389            whereClause = date + " >= " + Long.toString(begin);
390        } else {
391            whereClause = date + " >= " + Long.toString(begin) + " AND " + date
392                    + " < " + Long.toString(end);
393        }
394        deleteHistoryWhere(cr, whereClause);
395    }
396
397    /**
398     * Remove a specific url from the history database.
399     * @param cr    The ContentResolver used to access the database.
400     * @param url   url to remove.
401     */
402    public static final void deleteFromHistory(ContentResolver cr,
403                                               String url) {
404        StringBuilder sb = new StringBuilder(BookmarkColumns.URL + " = ");
405        DatabaseUtils.appendEscapedSQLString(sb, url);
406        String matchesUrl = sb.toString();
407        deleteHistoryWhere(cr, matchesUrl);
408    }
409
410    /**
411     * Add a search string to the searches database.
412     * @param cr   The ContentResolver used to access the database.
413     * @param search    The string to add to the searches database.
414     */
415    public static final void addSearchUrl(ContentResolver cr, String search) {
416        long now = new Date().getTime();
417        try {
418            Cursor c = cr.query(
419                SEARCHES_URI,
420                SEARCHES_PROJECTION,
421                SEARCHES_WHERE_CLAUSE,
422                new String [] { search },
423                null);
424            ContentValues map = new ContentValues();
425            map.put(SearchColumns.SEARCH, search);
426            map.put(SearchColumns.DATE, now);
427            /* We should only get one answer that is exactly the same. */
428            if (c.moveToFirst()) {
429                cr.update(SEARCHES_URI, map, "_id = " + c.getInt(0), null);
430            } else {
431                cr.insert(SEARCHES_URI, map);
432            }
433            c.deactivate();
434        } catch (IllegalStateException e) {
435            Log.e(LOGTAG, "addSearchUrl", e);
436            return;
437        }
438    }
439    /**
440     * Remove all searches from the search database.
441     * @param cr   The ContentResolver used to access the database.
442     */
443    public static final void clearSearches(ContentResolver cr) {
444        // FIXME: Should this clear the urls to which these searches lead?
445        // (i.e. remove google.com/query= blah blah blah)
446        try {
447            cr.delete(SEARCHES_URI, null, null);
448        } catch (IllegalStateException e) {
449            Log.e(LOGTAG, "clearSearches", e);
450        }
451    }
452
453    /**
454     *  Request all icons from the database.
455     *  @param  cr The ContentResolver used to access the database.
456     *  @param  where Clause to be used to limit the query from the database.
457     *          Must be an allowable string to be passed into a database query.
458     *  @param  listener IconListener that gets the icons once they are
459     *          retrieved.
460     */
461    public static final void requestAllIcons(ContentResolver cr, String where,
462            WebIconDatabase.IconListener listener) {
463        try {
464            final Cursor c = cr.query(
465                    BOOKMARKS_URI,
466                    HISTORY_PROJECTION,
467                    where, null, null);
468            if (c.moveToFirst()) {
469                final WebIconDatabase db = WebIconDatabase.getInstance();
470                do {
471                    db.requestIconForPageUrl(
472                            c.getString(HISTORY_PROJECTION_URL_INDEX),
473                            listener);
474                } while (c.moveToNext());
475            }
476            c.deactivate();
477        } catch (IllegalStateException e) {
478            Log.e(LOGTAG, "requestAllIcons", e);
479        }
480    }
481
482    public static class BookmarkColumns implements BaseColumns {
483        public static final String URL = "url";
484        public static final String VISITS = "visits";
485        public static final String DATE = "date";
486        public static final String BOOKMARK = "bookmark";
487        public static final String TITLE = "title";
488        public static final String CREATED = "created";
489        public static final String FAVICON = "favicon";
490        public static final String THUMBNAIL = "thumbnail";
491    }
492
493    public static class SearchColumns implements BaseColumns {
494        public static final String URL = "url";
495        public static final String SEARCH = "search";
496        public static final String DATE = "date";
497    }
498}
499