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.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