/* * Copyright (C) 2012 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 android.webkit; import android.content.ContentResolver; import android.database.Cursor; import android.graphics.Bitmap; import android.os.Handler; import android.os.Message; import android.provider.Browser; import android.util.Log; import java.io.File; import java.util.HashMap; import java.util.Vector; class WebIconDatabaseClassic extends WebIconDatabase { private static final String LOGTAG = "WebIconDatabase"; // Global instance of a WebIconDatabase private static WebIconDatabaseClassic sIconDatabase; // EventHandler for handling messages before and after the WebCore thread is // ready. private final EventHandler mEventHandler = new EventHandler(); // Class to handle messages before WebCore is ready private static class EventHandler extends Handler { // Message ids static final int OPEN = 0; static final int CLOSE = 1; static final int REMOVE_ALL = 2; static final int REQUEST_ICON = 3; static final int RETAIN_ICON = 4; static final int RELEASE_ICON = 5; static final int BULK_REQUEST_ICON = 6; // Message for dispatching icon request results private static final int ICON_RESULT = 10; // Actual handler that runs in WebCore thread private Handler mHandler; // Vector of messages before the WebCore thread is ready private Vector mMessages = new Vector(); // Class to handle a result dispatch private class IconResult { private final String mUrl; private final Bitmap mIcon; private final IconListener mListener; IconResult(String url, Bitmap icon, IconListener l) { mUrl = url; mIcon = icon; mListener = l; } void dispatch() { mListener.onReceivedIcon(mUrl, mIcon); } } @Override public void handleMessage(Message msg) { // Note: This is the message handler for the UI thread. switch (msg.what) { case ICON_RESULT: ((IconResult) msg.obj).dispatch(); break; } } // Called by WebCore thread to create the actual handler private synchronized void createHandler() { if (mHandler == null) { mHandler = new Handler() { @Override public void handleMessage(Message msg) { // Note: This is the message handler for the WebCore // thread. switch (msg.what) { case OPEN: nativeOpen((String) msg.obj); break; case CLOSE: nativeClose(); break; case REMOVE_ALL: nativeRemoveAllIcons(); break; case REQUEST_ICON: IconListener l = (IconListener) msg.obj; String url = msg.getData().getString("url"); requestIconAndSendResult(url, l); break; case BULK_REQUEST_ICON: bulkRequestIcons(msg); break; case RETAIN_ICON: nativeRetainIconForPageUrl((String) msg.obj); break; case RELEASE_ICON: nativeReleaseIconForPageUrl((String) msg.obj); break; } } }; // Transfer all pending messages for (int size = mMessages.size(); size > 0; size--) { mHandler.sendMessage(mMessages.remove(0)); } mMessages = null; } } private synchronized boolean hasHandler() { return mHandler != null; } private synchronized void postMessage(Message msg) { if (mMessages != null) { mMessages.add(msg); } else { mHandler.sendMessage(msg); } } private void bulkRequestIcons(Message msg) { HashMap map = (HashMap) msg.obj; IconListener listener = (IconListener) map.get("listener"); ContentResolver cr = (ContentResolver) map.get("contentResolver"); String where = (String) map.get("where"); Cursor c = null; try { c = cr.query( Browser.BOOKMARKS_URI, new String[] { Browser.BookmarkColumns.URL }, where, null, null); if (c.moveToFirst()) { do { String url = c.getString(0); requestIconAndSendResult(url, listener); } while (c.moveToNext()); } } catch (IllegalStateException e) { Log.e(LOGTAG, "BulkRequestIcons", e); } finally { if (c != null) c.close(); } } private void requestIconAndSendResult(String url, IconListener listener) { Bitmap icon = nativeIconForPageUrl(url); if (icon != null) { sendMessage(obtainMessage(ICON_RESULT, new IconResult(url, icon, listener))); } } } @Override public void open(String path) { if (path != null) { // Make the directories and parents if they don't exist File db = new File(path); if (!db.exists()) { db.mkdirs(); } mEventHandler.postMessage( Message.obtain(null, EventHandler.OPEN, db.getAbsolutePath())); } } @Override public void close() { mEventHandler.postMessage( Message.obtain(null, EventHandler.CLOSE)); } @Override public void removeAllIcons() { mEventHandler.postMessage( Message.obtain(null, EventHandler.REMOVE_ALL)); } /** * Request the Bitmap representing the icon for the given page * url. If the icon exists, the listener will be called with the result. * @param url The page's url. * @param listener An implementation on IconListener to receive the result. */ public void requestIconForPageUrl(String url, IconListener listener) { if (listener == null || url == null) { return; } Message msg = Message.obtain(null, EventHandler.REQUEST_ICON, listener); msg.getData().putString("url", url); mEventHandler.postMessage(msg); } /** {@hide} */ public void bulkRequestIconForPageUrl(ContentResolver cr, String where, IconListener listener) { if (listener == null) { return; } // Special case situation: we don't want to add this message to the // queue if there is no handler because we may never have a real // handler to service the messages and the cursor will never get // closed. if (mEventHandler.hasHandler()) { // Don't use Bundle as it is parcelable. HashMap map = new HashMap(); map.put("contentResolver", cr); map.put("where", where); map.put("listener", listener); Message msg = Message.obtain(null, EventHandler.BULK_REQUEST_ICON, map); mEventHandler.postMessage(msg); } } @Override public void retainIconForPageUrl(String url) { if (url != null) { mEventHandler.postMessage( Message.obtain(null, EventHandler.RETAIN_ICON, url)); } } @Override public void releaseIconForPageUrl(String url) { if (url != null) { mEventHandler.postMessage( Message.obtain(null, EventHandler.RELEASE_ICON, url)); } } /** * Get the global instance of WebIconDatabase. * @return A single instance of WebIconDatabase. It will be the same * instance for the current process each time this method is * called. */ public static WebIconDatabaseClassic getInstance() { // XXX: Must be created in the UI thread. if (sIconDatabase == null) { sIconDatabase = new WebIconDatabaseClassic(); } return sIconDatabase; } /** * Create the internal handler and transfer all pending messages. * XXX: Called by WebCore thread only! */ /*package*/ void createHandler() { mEventHandler.createHandler(); } /** * Private constructor to avoid anyone else creating an instance. */ private WebIconDatabaseClassic() {} // Native functions private static native void nativeOpen(String path); private static native void nativeClose(); private static native void nativeRemoveAllIcons(); private static native Bitmap nativeIconForPageUrl(String url); private static native void nativeRetainIconForPageUrl(String url); private static native void nativeReleaseIconForPageUrl(String url); }