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