1 /*
2  * Copyright (C) 2009 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.browser;
18 
19 import android.content.ContentResolver;
20 import android.content.ContentUris;
21 import android.content.ContentValues;
22 import android.content.Context;
23 import android.content.SharedPreferences;
24 import android.database.Cursor;
25 import android.graphics.Bitmap;
26 import android.net.Uri;
27 import android.os.AsyncTask;
28 import android.preference.PreferenceManager;
29 import android.provider.BrowserContract;
30 import android.provider.BrowserContract.Combined;
31 import android.provider.BrowserContract.Images;
32 import android.text.TextUtils;
33 import android.util.Log;
34 import android.webkit.WebIconDatabase;
35 import android.widget.Toast;
36 
37 import java.io.ByteArrayOutputStream;
38 
39 /**
40  *  This class is purely to have a common place for adding/deleting bookmarks.
41  */
42 public class Bookmarks {
43     // We only want the user to be able to bookmark content that
44     // the browser can handle directly.
45     private static final String acceptableBookmarkSchemes[] = {
46             "http:",
47             "https:",
48             "about:",
49             "data:",
50             "javascript:",
51             "file:",
52             "content:"
53     };
54 
55     private final static String LOGTAG = "Bookmarks";
56     /**
57      *  Add a bookmark to the database.
58      *  @param context Context of the calling Activity.  This is used to make
59      *          Toast confirming that the bookmark has been added.  If the
60      *          caller provides null, the Toast will not be shown.
61      *  @param url URL of the website to be bookmarked.
62      *  @param name Provided name for the bookmark.
63      *  @param thumbnail A thumbnail for the bookmark.
64      *  @param retainIcon Whether to retain the page's icon in the icon database.
65      *          This will usually be <code>true</code> except when bookmarks are
66      *          added by a settings restore agent.
67      *  @param parent ID of the parent folder.
68      */
addBookmark(Context context, boolean showToast, String url, String name, Bitmap thumbnail, long parent)69     /* package */ static void addBookmark(Context context, boolean showToast, String url,
70             String name, Bitmap thumbnail, long parent) {
71         // Want to append to the beginning of the list
72         ContentValues values = new ContentValues();
73         try {
74             SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
75             values.put(BrowserContract.Bookmarks.TITLE, name);
76             values.put(BrowserContract.Bookmarks.URL, url);
77             values.put(BrowserContract.Bookmarks.IS_FOLDER, 0);
78             values.put(BrowserContract.Bookmarks.THUMBNAIL,
79                     bitmapToBytes(thumbnail));
80             values.put(BrowserContract.Bookmarks.PARENT, parent);
81             context.getContentResolver().insert(BrowserContract.Bookmarks.CONTENT_URI, values);
82         } catch (IllegalStateException e) {
83             Log.e(LOGTAG, "addBookmark", e);
84         }
85         if (showToast) {
86             Toast.makeText(context, R.string.added_to_bookmarks,
87                     Toast.LENGTH_LONG).show();
88         }
89     }
90 
91     /**
92      *  Remove a bookmark from the database.  If the url is a visited site, it
93      *  will remain in the database, but only as a history item, and not as a
94      *  bookmarked site.
95      *  @param context Context of the calling Activity.  This is used to make
96      *          Toast confirming that the bookmark has been removed and to
97      *          lookup the correct content uri.  It must not be null.
98      *  @param cr The ContentResolver being used to remove the bookmark.
99      *  @param url URL of the website to be removed.
100      */
removeFromBookmarks(Context context, ContentResolver cr, String url, String title)101     /* package */ static void removeFromBookmarks(Context context,
102             ContentResolver cr, String url, String title) {
103         Cursor cursor = null;
104         try {
105             Uri uri = BookmarkUtils.getBookmarksUri(context);
106             cursor = cr.query(uri,
107                     new String[] { BrowserContract.Bookmarks._ID },
108                     BrowserContract.Bookmarks.URL + " = ? AND " +
109                             BrowserContract.Bookmarks.TITLE + " = ?",
110                     new String[] { url, title },
111                     null);
112 
113             if (!cursor.moveToFirst()) {
114                 return;
115             }
116 
117             // Remove from bookmarks
118             WebIconDatabase.getInstance().releaseIconForPageUrl(url);
119             uri = ContentUris.withAppendedId(BrowserContract.Bookmarks.CONTENT_URI,
120                     cursor.getLong(0));
121             cr.delete(uri, null, null);
122             if (context != null) {
123                 Toast.makeText(context, R.string.removed_from_bookmarks,
124                         Toast.LENGTH_LONG).show();
125             }
126         } catch (IllegalStateException e) {
127             Log.e(LOGTAG, "removeFromBookmarks", e);
128         } finally {
129             if (cursor != null) cursor.close();
130         }
131     }
132 
bitmapToBytes(Bitmap bm)133     private static byte[] bitmapToBytes(Bitmap bm) {
134         if (bm == null) {
135             return null;
136         }
137 
138         final ByteArrayOutputStream os = new ByteArrayOutputStream();
139         bm.compress(Bitmap.CompressFormat.PNG, 100, os);
140         return os.toByteArray();
141     }
142 
urlHasAcceptableScheme(String url)143     /* package */ static boolean urlHasAcceptableScheme(String url) {
144         if (url == null) {
145             return false;
146         }
147 
148         for (int i = 0; i < acceptableBookmarkSchemes.length; i++) {
149             if (url.startsWith(acceptableBookmarkSchemes[i])) {
150                 return true;
151             }
152         }
153         return false;
154     }
155 
156     static final String QUERY_BOOKMARKS_WHERE =
157             Combined.URL + " == ? OR " +
158             Combined.URL + " == ?";
159 
queryCombinedForUrl(ContentResolver cr, String originalUrl, String url)160     public static Cursor queryCombinedForUrl(ContentResolver cr,
161             String originalUrl, String url) {
162         if (cr == null || url == null) {
163             return null;
164         }
165 
166         // If originalUrl is null, just set it to url.
167         if (originalUrl == null) {
168             originalUrl = url;
169         }
170 
171         // Look for both the original url and the actual url. This takes in to
172         // account redirects.
173 
174         final String[] selArgs = new String[] { originalUrl, url };
175         final String[] projection = new String[] { Combined.URL };
176         return cr.query(Combined.CONTENT_URI, projection, QUERY_BOOKMARKS_WHERE, selArgs, null);
177     }
178 
179     // Strip the query from the given url.
removeQuery(String url)180     static String removeQuery(String url) {
181         if (url == null) {
182             return null;
183         }
184         int query = url.indexOf('?');
185         String noQuery = url;
186         if (query != -1) {
187             noQuery = url.substring(0, query);
188         }
189         return noQuery;
190     }
191 
192     /**
193      * Update the bookmark's favicon. This is a convenience method for updating
194      * a bookmark favicon for the originalUrl and url of the passed in WebView.
195      * @param cr The ContentResolver to use.
196      * @param originalUrl The original url before any redirects.
197      * @param url The current url.
198      * @param favicon The favicon bitmap to write to the db.
199      */
updateFavicon(final ContentResolver cr, final String originalUrl, final String url, final Bitmap favicon)200     /* package */ static void updateFavicon(final ContentResolver cr,
201             final String originalUrl, final String url, final Bitmap favicon) {
202         new AsyncTask<Void, Void, Void>() {
203             @Override
204             protected Void doInBackground(Void... unused) {
205                 final ByteArrayOutputStream os = new ByteArrayOutputStream();
206                 favicon.compress(Bitmap.CompressFormat.PNG, 100, os);
207 
208                 // The Images update will insert if it doesn't exist
209                 ContentValues values = new ContentValues();
210                 values.put(Images.FAVICON, os.toByteArray());
211                 updateImages(cr, originalUrl, values);
212                 updateImages(cr, url, values);
213                 return null;
214             }
215 
216             private void updateImages(final ContentResolver cr,
217                     final String url, ContentValues values) {
218                 String iurl = removeQuery(url);
219                 if (!TextUtils.isEmpty(iurl)) {
220                     values.put(Images.URL, iurl);
221                     cr.update(BrowserContract.Images.CONTENT_URI, values, null, null);
222                 }
223             }
224         }.execute();
225     }
226 }
227