/*
* Copyright (C) 2009 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.browser;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.AsyncTask;
import android.preference.PreferenceManager;
import android.provider.BrowserContract;
import android.provider.BrowserContract.Combined;
import android.provider.BrowserContract.Images;
import android.text.TextUtils;
import android.util.Log;
import android.webkit.WebIconDatabase;
import android.widget.Toast;
import java.io.ByteArrayOutputStream;
/**
* This class is purely to have a common place for adding/deleting bookmarks.
*/
public class Bookmarks {
// We only want the user to be able to bookmark content that
// the browser can handle directly.
private static final String acceptableBookmarkSchemes[] = {
"http:",
"https:",
"about:",
"data:",
"javascript:",
"file:",
"content:"
};
private final static String LOGTAG = "Bookmarks";
/**
* Add a bookmark to the database.
* @param context Context of the calling Activity. This is used to make
* Toast confirming that the bookmark has been added. If the
* caller provides null, the Toast will not be shown.
* @param url URL of the website to be bookmarked.
* @param name Provided name for the bookmark.
* @param thumbnail A thumbnail for the bookmark.
* @param retainIcon Whether to retain the page's icon in the icon database.
* This will usually be true
except when bookmarks are
* added by a settings restore agent.
* @param parent ID of the parent folder.
*/
/* package */ static void addBookmark(Context context, boolean showToast, String url,
String name, Bitmap thumbnail, long parent) {
// Want to append to the beginning of the list
ContentValues values = new ContentValues();
try {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
values.put(BrowserContract.Bookmarks.TITLE, name);
values.put(BrowserContract.Bookmarks.URL, url);
values.put(BrowserContract.Bookmarks.IS_FOLDER, 0);
values.put(BrowserContract.Bookmarks.THUMBNAIL,
bitmapToBytes(thumbnail));
values.put(BrowserContract.Bookmarks.PARENT, parent);
context.getContentResolver().insert(BrowserContract.Bookmarks.CONTENT_URI, values);
} catch (IllegalStateException e) {
Log.e(LOGTAG, "addBookmark", e);
}
if (showToast) {
Toast.makeText(context, R.string.added_to_bookmarks,
Toast.LENGTH_LONG).show();
}
}
/**
* Remove a bookmark from the database. If the url is a visited site, it
* will remain in the database, but only as a history item, and not as a
* bookmarked site.
* @param context Context of the calling Activity. This is used to make
* Toast confirming that the bookmark has been removed and to
* lookup the correct content uri. It must not be null.
* @param cr The ContentResolver being used to remove the bookmark.
* @param url URL of the website to be removed.
*/
/* package */ static void removeFromBookmarks(Context context,
ContentResolver cr, String url, String title) {
Cursor cursor = null;
try {
Uri uri = BookmarkUtils.getBookmarksUri(context);
cursor = cr.query(uri,
new String[] { BrowserContract.Bookmarks._ID },
BrowserContract.Bookmarks.URL + " = ? AND " +
BrowserContract.Bookmarks.TITLE + " = ?",
new String[] { url, title },
null);
if (!cursor.moveToFirst()) {
return;
}
// Remove from bookmarks
WebIconDatabase.getInstance().releaseIconForPageUrl(url);
uri = ContentUris.withAppendedId(BrowserContract.Bookmarks.CONTENT_URI,
cursor.getLong(0));
cr.delete(uri, null, null);
if (context != null) {
Toast.makeText(context, R.string.removed_from_bookmarks,
Toast.LENGTH_LONG).show();
}
} catch (IllegalStateException e) {
Log.e(LOGTAG, "removeFromBookmarks", e);
} finally {
if (cursor != null) cursor.close();
}
}
private static byte[] bitmapToBytes(Bitmap bm) {
if (bm == null) {
return null;
}
final ByteArrayOutputStream os = new ByteArrayOutputStream();
bm.compress(Bitmap.CompressFormat.PNG, 100, os);
return os.toByteArray();
}
/* package */ static boolean urlHasAcceptableScheme(String url) {
if (url == null) {
return false;
}
for (int i = 0; i < acceptableBookmarkSchemes.length; i++) {
if (url.startsWith(acceptableBookmarkSchemes[i])) {
return true;
}
}
return false;
}
static final String QUERY_BOOKMARKS_WHERE =
Combined.URL + " == ? OR " +
Combined.URL + " == ?";
public static Cursor queryCombinedForUrl(ContentResolver cr,
String originalUrl, String url) {
if (cr == null || url == null) {
return null;
}
// If originalUrl is null, just set it to url.
if (originalUrl == null) {
originalUrl = url;
}
// Look for both the original url and the actual url. This takes in to
// account redirects.
final String[] selArgs = new String[] { originalUrl, url };
final String[] projection = new String[] { Combined.URL };
return cr.query(Combined.CONTENT_URI, projection, QUERY_BOOKMARKS_WHERE, selArgs, null);
}
// Strip the query from the given url.
static String removeQuery(String url) {
if (url == null) {
return null;
}
int query = url.indexOf('?');
String noQuery = url;
if (query != -1) {
noQuery = url.substring(0, query);
}
return noQuery;
}
/**
* Update the bookmark's favicon. This is a convenience method for updating
* a bookmark favicon for the originalUrl and url of the passed in WebView.
* @param cr The ContentResolver to use.
* @param originalUrl The original url before any redirects.
* @param url The current url.
* @param favicon The favicon bitmap to write to the db.
*/
/* package */ static void updateFavicon(final ContentResolver cr,
final String originalUrl, final String url, final Bitmap favicon) {
new AsyncTask() {
@Override
protected Void doInBackground(Void... unused) {
final ByteArrayOutputStream os = new ByteArrayOutputStream();
favicon.compress(Bitmap.CompressFormat.PNG, 100, os);
// The Images update will insert if it doesn't exist
ContentValues values = new ContentValues();
values.put(Images.FAVICON, os.toByteArray());
updateImages(cr, originalUrl, values);
updateImages(cr, url, values);
return null;
}
private void updateImages(final ContentResolver cr,
final String url, ContentValues values) {
String iurl = removeQuery(url);
if (!TextUtils.isEmpty(iurl)) {
values.put(Images.URL, iurl);
cr.update(BrowserContract.Images.CONTENT_URI, values, null, null);
}
}
}.execute();
}
}