1/*
2 * Copyright 2006, The Android Open Source Project
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 *  * Redistributions of source code must retain the above copyright
8 *    notice, this list of conditions and the following disclaimer.
9 *  * Redistributions in binary form must reproduce the above copyright
10 *    notice, this list of conditions and the following disclaimer in the
11 *    documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#define LOG_TAG "favicons"
27
28#include "config.h"
29#include "WebIconDatabase.h"
30
31#include "FileSystem.h"
32#include "GraphicsJNI.h"
33#include "IconDatabase.h"
34#include "Image.h"
35#include "IntRect.h"
36#include "JavaSharedClient.h"
37#include "KURL.h"
38#include "WebCoreJni.h"
39
40#include <JNIHelp.h>
41#include <JNIUtility.h>
42#include <SharedBuffer.h>
43#include <SkBitmap.h>
44#include <SkImageDecoder.h>
45#include <SkTemplates.h>
46#include <pthread.h>
47#include <utils/misc.h>
48#include <wtf/Platform.h>
49#include <wtf/text/CString.h>
50
51namespace android {
52
53SkBitmap* webcoreImageToSkBitmap(WebCore::Image* icon)
54{
55    if (!icon)
56        return 0;
57    WebCore::SharedBuffer* buffer = icon->data();
58    if (!buffer)
59        return 0;
60    SkBitmap* bm = new SkBitmap;
61    if (!SkImageDecoder::DecodeMemory(buffer->data(), buffer->size(), bm,
62                                      SkBitmap::kNo_Config,
63                                      SkImageDecoder::kDecodePixels_Mode)
64            || bm->isNull() || !bm->width() || !bm->height()
65            || bm->config() == SkBitmap::kNo_Config) {
66        delete bm;
67        return 0;
68    }
69    return bm;
70}
71
72jobject webcoreImageToJavaBitmap(JNIEnv* env, WebCore::Image* icon)
73{
74    SkBitmap* bm = webcoreImageToSkBitmap(icon);
75    if (!bm)
76        return 0;
77    return GraphicsJNI::createBitmap(env, bm, false, NULL);
78}
79
80static WebIconDatabase* gIconDatabaseClient = new WebIconDatabase();
81
82bool WebIconDatabase::performImport()
83{
84    // We don't do do any old-style database importing.
85    return true;
86}
87
88// Called on the WebCore thread
89void WebIconDatabase::didImportIconURLForPageURL(const WTF::String& pageURL)
90{
91    // FIXME: After http://trac.webkit.org/changeset/81719 this method is called
92    // on the WebCore thread, so switching threads via this queue is superfluous
93    // and should be removed. http://b/4565022
94    mNotificationsMutex.lock();
95    mNotifications.append(pageURL);
96    if (!mDeliveryRequested) {
97        mDeliveryRequested = true;
98        JavaSharedClient::EnqueueFunctionPtr(DeliverNotifications, this);
99    }
100    mNotificationsMutex.unlock();
101}
102
103void WebIconDatabase::didImportIconDataForPageURL(const WTF::String& pageURL)
104{
105    // WebKit1 only has a single "icon did change" notification.
106    didImportIconURLForPageURL(pageURL);
107}
108
109void WebIconDatabase::didChangeIconForPageURL(const WTF::String& pageURL)
110{
111    // WebKit1 only has a single "icon did change" notification.
112    didImportIconURLForPageURL(pageURL);
113}
114
115void WebIconDatabase::didRemoveAllIcons()
116{
117}
118
119void WebIconDatabase::didFinishURLImport()
120{
121}
122
123// Called in the WebCore thread
124void WebIconDatabase::RegisterForIconNotification(WebIconDatabaseClient* client)
125{
126    WebIconDatabase* db = gIconDatabaseClient;
127    for (unsigned i = 0; i < db->mClients.size(); ++i) {
128        // Do not add the same client twice.
129        if (db->mClients[i] == client)
130            return;
131    }
132    gIconDatabaseClient->mClients.append(client);
133}
134
135// Called in the WebCore thread
136void WebIconDatabase::UnregisterForIconNotification(WebIconDatabaseClient* client)
137{
138    WebIconDatabase* db = gIconDatabaseClient;
139    for (unsigned i = 0; i < db->mClients.size(); ++i) {
140        if (db->mClients[i] == client) {
141            db->mClients.remove(i);
142            break;
143        }
144    }
145}
146
147// Called in the WebCore thread
148void WebIconDatabase::DeliverNotifications(void* v)
149{
150    ASSERT(v);
151    ((WebIconDatabase*)v)->deliverNotifications();
152}
153
154// Called in the WebCore thread
155void WebIconDatabase::deliverNotifications()
156{
157    ASSERT(mDeliveryRequested);
158
159    // Swap the notifications queue
160    Vector<WTF::String> queue;
161    mNotificationsMutex.lock();
162    queue.swap(mNotifications);
163    mDeliveryRequested = false;
164    mNotificationsMutex.unlock();
165
166    // Swap the clients queue
167    Vector<WebIconDatabaseClient*> clients;
168    clients.swap(mClients);
169
170    for (unsigned i = 0; i < queue.size(); ++i) {
171        for (unsigned j = 0; j < clients.size(); ++j) {
172            clients[j]->didAddIconForPageUrl(queue[i]);
173        }
174    }
175}
176
177static void Open(JNIEnv* env, jobject obj, jstring path)
178{
179    WebCore::IconDatabaseBase& iconDb = WebCore::iconDatabase();
180    if (iconDb.isOpen())
181        return;
182    iconDb.setEnabled(true);
183    iconDb.setClient(gIconDatabaseClient);
184    ALOG_ASSERT(path, "No path given to nativeOpen");
185    WTF::String pathStr = jstringToWtfString(env, path);
186    WTF::CString fullPath = WebCore::pathByAppendingComponent(pathStr,
187            WebCore::IconDatabase::defaultDatabaseFilename()).utf8();
188    mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP;
189    bool didSetPermissions = false;
190    if (access(fullPath.data(), F_OK) == 0) {
191        if (chmod(fullPath.data(), mode) == 0)
192            didSetPermissions = true;
193    } else {
194        int fd = open(fullPath.data(), O_CREAT, mode);
195        if (fd >= 0) {
196            close(fd);
197            didSetPermissions = true;
198        }
199    }
200    if (didSetPermissions) {
201        ALOGV("Opening WebIconDatabase file '%s'", pathStr.latin1().data());
202        bool res = iconDb.open(pathStr, WebCore::IconDatabase::defaultDatabaseFilename());
203        if (!res)
204            ALOGE("Open failed!");
205    } else
206        ALOGE("Failed to set permissions on '%s'", fullPath.data());
207}
208
209static void Close(JNIEnv* env, jobject obj)
210{
211    WebCore::iconDatabase().close();
212}
213
214static void RemoveAllIcons(JNIEnv* env, jobject obj)
215{
216    ALOGV("Removing all icons");
217    WebCore::iconDatabase().removeAllIcons();
218}
219
220static jobject IconForPageUrl(JNIEnv* env, jobject obj, jstring url)
221{
222    ALOG_ASSERT(url, "No url given to iconForPageUrl");
223    WTF::String urlStr = jstringToWtfString(env, url);
224
225    // FIXME: This method should not be used from outside WebCore and will be removed.
226    // http://trac.webkit.org/changeset/81484
227    WebCore::Image* icon = WebCore::iconDatabase().synchronousIconForPageURL(urlStr, WebCore::IntSize(16, 16));
228    ALOGV("Retrieving icon for '%s' %p", urlStr.latin1().data(), icon);
229    return webcoreImageToJavaBitmap(env, icon);
230}
231
232static void RetainIconForPageUrl(JNIEnv* env, jobject obj, jstring url)
233{
234    ALOG_ASSERT(url, "No url given to retainIconForPageUrl");
235    WTF::String urlStr = jstringToWtfString(env, url);
236
237    ALOGV("Retaining icon for '%s'", urlStr.latin1().data());
238    WebCore::iconDatabase().retainIconForPageURL(urlStr);
239}
240
241static void ReleaseIconForPageUrl(JNIEnv* env, jobject obj, jstring url)
242{
243    ALOG_ASSERT(url, "No url given to releaseIconForPageUrl");
244    WTF::String urlStr = jstringToWtfString(env, url);
245
246    ALOGV("Releasing icon for '%s'", urlStr.latin1().data());
247    WebCore::iconDatabase().releaseIconForPageURL(urlStr);
248}
249
250/*
251 * JNI registration
252 */
253static JNINativeMethod gWebIconDatabaseMethods[] = {
254    { "nativeOpen", "(Ljava/lang/String;)V",
255        (void*) Open },
256    { "nativeClose", "()V",
257        (void*) Close },
258    { "nativeRemoveAllIcons", "()V",
259        (void*) RemoveAllIcons },
260    { "nativeIconForPageUrl", "(Ljava/lang/String;)Landroid/graphics/Bitmap;",
261        (void*) IconForPageUrl },
262    { "nativeRetainIconForPageUrl", "(Ljava/lang/String;)V",
263        (void*) RetainIconForPageUrl },
264    { "nativeReleaseIconForPageUrl", "(Ljava/lang/String;)V",
265        (void*) ReleaseIconForPageUrl }
266};
267
268int registerWebIconDatabase(JNIEnv* env)
269{
270#ifndef NDEBUG
271    jclass webIconDatabase = env->FindClass("android/webkit/WebIconDatabaseClassic");
272    ALOG_ASSERT(webIconDatabase, "Unable to find class android.webkit.WebIconDatabaseClassic");
273    env->DeleteLocalRef(webIconDatabase);
274#endif
275
276    return jniRegisterNativeMethods(env, "android/webkit/WebIconDatabaseClassic",
277            gWebIconDatabaseMethods, NELEM(gWebIconDatabaseMethods));
278}
279
280}
281