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.webkit; 18 19 import android.content.ContentResolver; 20 import android.database.Cursor; 21 import android.graphics.Bitmap; 22 import android.os.Handler; 23 import android.os.Message; 24 import android.provider.Browser; 25 import android.util.Log; 26 27 import java.util.HashMap; 28 import java.util.Vector; 29 30 /** 31 * Functions for manipulating the icon database used by WebView. 32 * These functions require that a WebView be constructed before being invoked 33 * and WebView.getIconDatabase() will return a WebIconDatabase object. This 34 * WebIconDatabase object is a single instance and all methods operate on that 35 * single object. 36 */ 37 public final class WebIconDatabase { 38 private static final String LOGTAG = "WebIconDatabase"; 39 // Global instance of a WebIconDatabase 40 private static WebIconDatabase sIconDatabase; 41 // EventHandler for handling messages before and after the WebCore thread is 42 // ready. 43 private final EventHandler mEventHandler = new EventHandler(); 44 45 // Class to handle messages before WebCore is ready 46 private static class EventHandler extends Handler { 47 // Message ids 48 static final int OPEN = 0; 49 static final int CLOSE = 1; 50 static final int REMOVE_ALL = 2; 51 static final int REQUEST_ICON = 3; 52 static final int RETAIN_ICON = 4; 53 static final int RELEASE_ICON = 5; 54 static final int BULK_REQUEST_ICON = 6; 55 // Message for dispatching icon request results 56 private static final int ICON_RESULT = 10; 57 // Actual handler that runs in WebCore thread 58 private Handler mHandler; 59 // Vector of messages before the WebCore thread is ready 60 private Vector<Message> mMessages = new Vector<Message>(); 61 // Class to handle a result dispatch 62 private class IconResult { 63 private final String mUrl; 64 private final Bitmap mIcon; 65 private final IconListener mListener; 66 IconResult(String url, Bitmap icon, IconListener l) { 67 mUrl = url; 68 mIcon = icon; 69 mListener = l; 70 } 71 void dispatch() { 72 mListener.onReceivedIcon(mUrl, mIcon); 73 } 74 } 75 76 @Override 77 public void handleMessage(Message msg) { 78 // Note: This is the message handler for the UI thread. 79 switch (msg.what) { 80 case ICON_RESULT: 81 ((IconResult) msg.obj).dispatch(); 82 break; 83 } 84 } 85 86 // Called by WebCore thread to create the actual handler 87 private synchronized void createHandler() { 88 if (mHandler == null) { 89 mHandler = new Handler() { 90 @Override 91 public void handleMessage(Message msg) { 92 // Note: This is the message handler for the WebCore 93 // thread. 94 switch (msg.what) { 95 case OPEN: 96 nativeOpen((String) msg.obj); 97 break; 98 99 case CLOSE: 100 nativeClose(); 101 break; 102 103 case REMOVE_ALL: 104 nativeRemoveAllIcons(); 105 break; 106 107 case REQUEST_ICON: 108 IconListener l = (IconListener) msg.obj; 109 String url = msg.getData().getString("url"); 110 requestIconAndSendResult(url, l); 111 break; 112 113 case BULK_REQUEST_ICON: 114 bulkRequestIcons(msg); 115 break; 116 117 case RETAIN_ICON: 118 nativeRetainIconForPageUrl((String) msg.obj); 119 break; 120 121 case RELEASE_ICON: 122 nativeReleaseIconForPageUrl((String) msg.obj); 123 break; 124 } 125 } 126 }; 127 // Transfer all pending messages 128 for (int size = mMessages.size(); size > 0; size--) { 129 mHandler.sendMessage(mMessages.remove(0)); 130 } 131 mMessages = null; 132 } 133 } 134 135 private synchronized boolean hasHandler() { 136 return mHandler != null; 137 } 138 139 private synchronized void postMessage(Message msg) { 140 if (mMessages != null) { 141 mMessages.add(msg); 142 } else { 143 mHandler.sendMessage(msg); 144 } 145 } 146 147 private void bulkRequestIcons(Message msg) { 148 HashMap map = (HashMap) msg.obj; 149 IconListener listener = (IconListener) map.get("listener"); 150 ContentResolver cr = (ContentResolver) map.get("contentResolver"); 151 String where = (String) map.get("where"); 152 153 Cursor c = null; 154 try { 155 c = cr.query( 156 Browser.BOOKMARKS_URI, 157 new String[] { Browser.BookmarkColumns.URL }, 158 where, null, null); 159 if (c.moveToFirst()) { 160 do { 161 String url = c.getString(0); 162 requestIconAndSendResult(url, listener); 163 } while (c.moveToNext()); 164 } 165 } catch (IllegalStateException e) { 166 Log.e(LOGTAG, "BulkRequestIcons", e); 167 } finally { 168 if (c != null) c.close(); 169 } 170 } 171 172 private void requestIconAndSendResult(String url, IconListener listener) { 173 Bitmap icon = nativeIconForPageUrl(url); 174 if (icon != null) { 175 sendMessage(obtainMessage(ICON_RESULT, 176 new IconResult(url, icon, listener))); 177 } 178 } 179 } 180 181 /** 182 * Interface for receiving icons from the database. 183 */ 184 public interface IconListener { 185 /** 186 * Called when the icon has been retrieved from the database and the 187 * result is non-null. 188 * @param url The url passed in the request. 189 * @param icon The favicon for the given url. 190 */ 191 public void onReceivedIcon(String url, Bitmap icon); 192 } 193 194 /** 195 * Open a the icon database and store the icons in the given path. 196 * @param path The directory path where the icon database will be stored. 197 * @return True if the database was successfully opened or created in 198 * the given path. 199 */ 200 public void open(String path) { 201 if (path != null) { 202 mEventHandler.postMessage( 203 Message.obtain(null, EventHandler.OPEN, path)); 204 } 205 } 206 207 /** 208 * Close the shared instance of the icon database. 209 */ 210 public void close() { 211 mEventHandler.postMessage( 212 Message.obtain(null, EventHandler.CLOSE)); 213 } 214 215 /** 216 * Removes all the icons in the database. 217 */ 218 public void removeAllIcons() { 219 mEventHandler.postMessage( 220 Message.obtain(null, EventHandler.REMOVE_ALL)); 221 } 222 223 /** 224 * Request the Bitmap representing the icon for the given page 225 * url. If the icon exists, the listener will be called with the result. 226 * @param url The page's url. 227 * @param listener An implementation on IconListener to receive the result. 228 */ 229 public void requestIconForPageUrl(String url, IconListener listener) { 230 if (listener == null || url == null) { 231 return; 232 } 233 Message msg = Message.obtain(null, EventHandler.REQUEST_ICON, listener); 234 msg.getData().putString("url", url); 235 mEventHandler.postMessage(msg); 236 } 237 238 /** {@hide} 239 */ 240 public void bulkRequestIconForPageUrl(ContentResolver cr, String where, 241 IconListener listener) { 242 if (listener == null) { 243 return; 244 } 245 246 // Special case situation: we don't want to add this message to the 247 // queue if there is no handler because we may never have a real 248 // handler to service the messages and the cursor will never get 249 // closed. 250 if (mEventHandler.hasHandler()) { 251 // Don't use Bundle as it is parcelable. 252 HashMap<String, Object> map = new HashMap<String, Object>(); 253 map.put("contentResolver", cr); 254 map.put("where", where); 255 map.put("listener", listener); 256 Message msg = 257 Message.obtain(null, EventHandler.BULK_REQUEST_ICON, map); 258 mEventHandler.postMessage(msg); 259 } 260 } 261 262 /** 263 * Retain the icon for the given page url. 264 * @param url The page's url. 265 */ 266 public void retainIconForPageUrl(String url) { 267 if (url != null) { 268 mEventHandler.postMessage( 269 Message.obtain(null, EventHandler.RETAIN_ICON, url)); 270 } 271 } 272 273 /** 274 * Release the icon for the given page url. 275 * @param url The page's url. 276 */ 277 public void releaseIconForPageUrl(String url) { 278 if (url != null) { 279 mEventHandler.postMessage( 280 Message.obtain(null, EventHandler.RELEASE_ICON, url)); 281 } 282 } 283 284 /** 285 * Get the global instance of WebIconDatabase. 286 * @return A single instance of WebIconDatabase. It will be the same 287 * instance for the current process each time this method is 288 * called. 289 */ 290 public static WebIconDatabase getInstance() { 291 // XXX: Must be created in the UI thread. 292 if (sIconDatabase == null) { 293 sIconDatabase = new WebIconDatabase(); 294 } 295 return sIconDatabase; 296 } 297 298 /** 299 * Create the internal handler and transfer all pending messages. 300 * XXX: Called by WebCore thread only! 301 */ 302 /*package*/ void createHandler() { 303 mEventHandler.createHandler(); 304 } 305 306 /** 307 * Private constructor to avoid anyone else creating an instance. 308 */ 309 private WebIconDatabase() {} 310 311 // Native functions 312 private static native void nativeOpen(String path); 313 private static native void nativeClose(); 314 private static native void nativeRemoveAllIcons(); 315 private static native Bitmap nativeIconForPageUrl(String url); 316 private static native void nativeRetainIconForPageUrl(String url); 317 private static native void nativeReleaseIconForPageUrl(String url); 318 } 319