Home | History | Annotate | Download | only in webkit
      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