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