diff options
Diffstat (limited to 'src/plugins/android/qandroidwebview.cpp')
-rw-r--r-- | src/plugins/android/qandroidwebview.cpp | 437 |
1 files changed, 437 insertions, 0 deletions
diff --git a/src/plugins/android/qandroidwebview.cpp b/src/plugins/android/qandroidwebview.cpp new file mode 100644 index 0000000..94e123c --- /dev/null +++ b/src/plugins/android/qandroidwebview.cpp @@ -0,0 +1,437 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtWebView module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qandroidwebview_p.h" +#include <private/qwebview_p.h> +#include <private/qwebviewloadrequest_p.h> +#include <QtCore/private/qjnihelpers_p.h> +#include <QtCore/private/qjni_p.h> + +#include <QtCore/qmap.h> +#include <android/bitmap.h> +#include <QtGui/qguiapplication.h> +#include <QtCore/qjsondocument.h> +#include <QtCore/qjsonobject.h> +#include <QtCore/qurl.h> +#include <QtCore/qdebug.h> + +QT_BEGIN_NAMESPACE + +static const char qtAndroidWebViewControllerClass[] = "org/qtproject/qt5/android/view/QtAndroidWebViewController"; + +//static bool favIcon(JNIEnv *env, jobject icon, QImage *image) +//{ +// // TODO: +// AndroidBitmapInfo bitmapInfo; +// if (AndroidBitmap_getInfo(env, icon, &bitmapInfo) != ANDROID_BITMAP_RESULT_SUCCESS) +// return false; + +// void *pixelData; +// if (AndroidBitmap_lockPixels(env, icon, &pixelData) != ANDROID_BITMAP_RESULT_SUCCESS) +// return false; + +// *image = QImage::fromData(static_cast<const uchar *>(pixelData), bitmapInfo.width * bitmapInfo.height); +// AndroidBitmap_unlockPixels(env, icon); + +// return true; +//} + +typedef QMap<quintptr, QAndroidWebViewPrivate *> WebViews; +Q_GLOBAL_STATIC(WebViews, g_webViews) + +QAndroidWebViewPrivate::QAndroidWebViewPrivate(QObject *p) + : QAbstractWebView(p) + , m_id(reinterpret_cast<quintptr>(this)) + , m_callbackId(0) + , m_window(0) +{ + m_viewController = QJNIObjectPrivate(qtAndroidWebViewControllerClass, + "(Landroid/app/Activity;J)V", + QtAndroidPrivate::activity(), + m_id); + m_webView = m_viewController.callObjectMethod("getWebView", + "()Landroid/webkit/WebView;"); + + m_window = QWindow::fromWinId(reinterpret_cast<WId>(m_webView.object())); + g_webViews->insert(m_id, this); + connect(qApp, &QGuiApplication::applicationStateChanged, + this, &QAndroidWebViewPrivate::onApplicationStateChanged); +} + +QAndroidWebViewPrivate::~QAndroidWebViewPrivate() +{ + g_webViews->take(m_id); + if (m_window != 0) { + m_window->setVisible(false); + m_window->setParent(0); + delete m_window; + } + + m_viewController.callMethod<void>("destroy"); +} + +QUrl QAndroidWebViewPrivate::url() const +{ + return QUrl::fromUserInput(m_viewController.callObjectMethod<jstring>("getUrl").toString()); +} + +void QAndroidWebViewPrivate::setUrl(const QUrl &url) +{ + m_viewController.callMethod<void>("loadUrl", + "(Ljava/lang/String;)V", + QJNIObjectPrivate::fromString(url.toString()).object()); +} + +void QAndroidWebViewPrivate::loadHtml(const QString &html, const QUrl &baseUrl) +{ + const QJNIObjectPrivate &htmlString = QJNIObjectPrivate::fromString(html); + const QJNIObjectPrivate &mimeTypeString = QJNIObjectPrivate::fromString(QLatin1String("text/html;charset=UTF-8")); + + baseUrl.isEmpty() ? m_viewController.callMethod<void>("loadData", + "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V", + htmlString.object(), + mimeTypeString.object(), + 0) + + : m_viewController.callMethod<void>("loadDataWithBaseURL", + "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V", + QJNIObjectPrivate::fromString(baseUrl.toString()).object(), + htmlString.object(), + mimeTypeString.object(), + 0, + 0); +} + +bool QAndroidWebViewPrivate::canGoBack() const +{ + return m_viewController.callMethod<jboolean>("canGoBack"); +} + +void QAndroidWebViewPrivate::goBack() +{ + m_viewController.callMethod<void>("goBack"); +} + +bool QAndroidWebViewPrivate::canGoForward() const +{ + return m_viewController.callMethod<jboolean>("canGoForward"); +} + +void QAndroidWebViewPrivate::goForward() +{ + m_viewController.callMethod<void>("goForward"); +} + +void QAndroidWebViewPrivate::reload() +{ + m_viewController.callMethod<void>("reload"); +} + +QString QAndroidWebViewPrivate::title() const +{ + return m_viewController.callObjectMethod<jstring>("getTitle").toString(); +} + +void QAndroidWebViewPrivate::setGeometry(const QRect &geometry) +{ + if (m_window == 0) + return; + + m_window->setGeometry(geometry); +} + +void QAndroidWebViewPrivate::setVisibility(QWindow::Visibility visibility) +{ + m_window->setVisibility(visibility); +} + +void QAndroidWebViewPrivate::runJavaScriptPrivate(const QString &script, + int callbackId) +{ + if (QtAndroidPrivate::androidSdkVersion() < 19) { + qWarning("runJavaScript() requires API level 19 or higher."); + if (callbackId == -1) + return; + + // Emit signal here to remove the callback. + Q_EMIT javaScriptResult(callbackId, QVariant()); + } + + m_viewController.callMethod<void>("runJavaScript", + "(Ljava/lang/String;J)V", + static_cast<jstring>(QJNIObjectPrivate::fromString(script).object()), + callbackId); +} + +void QAndroidWebViewPrivate::setVisible(bool visible) +{ + m_window->setVisible(visible); +} + +int QAndroidWebViewPrivate::loadProgress() const +{ + return m_viewController.callMethod<jint>("getProgress"); +} + +bool QAndroidWebViewPrivate::isLoading() const +{ + return m_viewController.callMethod<jboolean>("isLoading"); +} + +void QAndroidWebViewPrivate::setParentView(QObject *view) +{ + m_window->setParent(qobject_cast<QWindow *>(view)); +} + +QObject *QAndroidWebViewPrivate::parentView() const +{ + return m_window->parent(); +} + +void QAndroidWebViewPrivate::stop() +{ + m_viewController.callMethod<void>("stopLoading"); +} + +//void QAndroidWebViewPrivate::initialize() +//{ +// // TODO: +//} + +void QAndroidWebViewPrivate::onApplicationStateChanged(Qt::ApplicationState state) +{ + if (QtAndroidPrivate::androidSdkVersion() < 11) + return; + + if (state == Qt::ApplicationActive) + m_viewController.callMethod<void>("onResume"); + else + m_viewController.callMethod<void>("onPause"); +} + +QT_END_NAMESPACE + +static void c_onRunJavaScriptResult(JNIEnv *env, + jobject thiz, + jlong id, + jlong callbackId, + jstring result) +{ + Q_UNUSED(env) + Q_UNUSED(thiz) + + const WebViews &wv = (*g_webViews); + QAndroidWebViewPrivate *wc = static_cast<QAndroidWebViewPrivate *>(wv[id]); + if (!wc) + return; + + const QString &resultString = QJNIObjectPrivate(result).toString(); + + // The result string is in JSON format, lets parse it to see what we got. + QJsonValue jsonValue; + const QByteArray &jsonData = "{ \"data\": " + resultString.toUtf8() + " }"; + QJsonParseError error; + const QJsonDocument &jsonDoc = QJsonDocument::fromJson(jsonData, &error); + if (error.error == QJsonParseError::NoError && jsonDoc.isObject()) { + const QJsonObject &object = jsonDoc.object(); + jsonValue = object.value(QStringLiteral("data")); + } + + Q_EMIT wc->javaScriptResult(int(callbackId), + jsonValue.isNull() ? resultString + : jsonValue.toVariant()); +} + +static void c_onPageFinished(JNIEnv *env, + jobject thiz, + jlong id, + jstring url) +{ + Q_UNUSED(env) + Q_UNUSED(thiz) + const WebViews &wv = (*g_webViews); + QAndroidWebViewPrivate *wc = wv[id]; + if (!wc) + return; + + QWebViewLoadRequestPrivate loadRequest(QUrl(QJNIObjectPrivate(url).toString()), + QWebView::LoadSucceededStatus, + QString()); + Q_EMIT wc->loadingChanged(loadRequest); +} + +static void c_onPageStarted(JNIEnv *env, + jobject thiz, + jlong id, + jstring url, + jobject icon) +{ + Q_UNUSED(env) + Q_UNUSED(thiz) + Q_UNUSED(icon) + const WebViews &wv = (*g_webViews); + QAndroidWebViewPrivate *wc = wv[id]; + if (!wc) + return; + QWebViewLoadRequestPrivate loadRequest(QUrl(QJNIObjectPrivate(url).toString()), + QWebView::LoadStartedStatus, + QString()); + Q_EMIT wc->loadingChanged(loadRequest); + +// if (!icon) +// return; + +// QImage image; +// if (favIcon(env, icon, &image)) +// Q_EMIT wc->iconChanged(image); +} + +static void c_onProgressChanged(JNIEnv *env, + jobject thiz, + jlong id, + jint newProgress) +{ + Q_UNUSED(env) + Q_UNUSED(thiz) + const WebViews &wv = (*g_webViews); + QAndroidWebViewPrivate *wc = wv[id]; + if (!wc) + return; + + Q_EMIT wc->loadProgressChanged(newProgress); +} + +static void c_onReceivedIcon(JNIEnv *env, + jobject thiz, + jlong id, + jobject icon) +{ + Q_UNUSED(env) + Q_UNUSED(thiz) + Q_UNUSED(id) + Q_UNUSED(icon) + + const WebViews &wv = (*g_webViews); + QAndroidWebViewPrivate *wc = wv[id]; + if (!wc) + return; + + if (!icon) + return; + +// QImage image; +// if (favIcon(env, icon, &image)) +// Q_EMIT wc->iconChanged(image); +} + +static void c_onReceivedTitle(JNIEnv *env, + jobject thiz, + jlong id, + jstring title) +{ + Q_UNUSED(env) + Q_UNUSED(thiz) + const WebViews &wv = (*g_webViews); + QAndroidWebViewPrivate *wc = wv[id]; + if (!wc) + return; + + const QString &qTitle = QJNIObjectPrivate(title).toString(); + Q_EMIT wc->titleChanged(qTitle); +} + +static void c_onReceivedError(JNIEnv *env, + jobject thiz, + jlong id, + jint errorCode, + jstring description, + jstring url) +{ + Q_UNUSED(env) + Q_UNUSED(thiz) + Q_UNUSED(errorCode) + + const WebViews &wv = (*g_webViews); + QAndroidWebViewPrivate *wc = wv[id]; + if (!wc) + return; + QWebViewLoadRequestPrivate loadRequest(QUrl(QJNIObjectPrivate(url).toString()), + QWebView::LoadFailedStatus, + QJNIObjectPrivate(description).toString()); + Q_EMIT wc->loadingChanged(loadRequest); +} + +JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* /*reserved*/) +{ + static bool initialized = false; + if (initialized) + return JNI_VERSION_1_6; + initialized = true; + + typedef union { + JNIEnv *nativeEnvironment; + void *venv; + } UnionJNIEnvToVoid; + + UnionJNIEnvToVoid uenv; + uenv.venv = NULL; + + if (vm->GetEnv(&uenv.venv, JNI_VERSION_1_4) != JNI_OK) + return JNI_ERR; + + JNIEnv *env = uenv.nativeEnvironment; + + jclass clazz = QJNIEnvironmentPrivate::findClass(qtAndroidWebViewControllerClass, env); + if (!clazz) + return JNI_ERR; + + JNINativeMethod methods[] = { + {"c_onPageFinished", "(JLjava/lang/String;)V", reinterpret_cast<void *>(c_onPageFinished)}, + {"c_onPageStarted", "(JLjava/lang/String;Landroid/graphics/Bitmap;)V", reinterpret_cast<void *>(c_onPageStarted)}, + {"c_onProgressChanged", "(JI)V", reinterpret_cast<void *>(c_onProgressChanged)}, + {"c_onReceivedIcon", "(JLandroid/graphics/Bitmap;)V", reinterpret_cast<void *>(c_onReceivedIcon)}, + {"c_onReceivedTitle", "(JLjava/lang/String;)V", reinterpret_cast<void *>(c_onReceivedTitle)}, + {"c_onRunJavaScriptResult", "(JJLjava/lang/String;)V", reinterpret_cast<void *>(c_onRunJavaScriptResult)}, + {"c_onReceivedError", "(JILjava/lang/String;Ljava/lang/String;)V", reinterpret_cast<void *>(c_onReceivedError)} + }; + + const int nMethods = sizeof(methods) / sizeof(methods[0]); + + if (env->RegisterNatives(clazz, methods, nMethods) != JNI_OK) + return JNI_ERR; + + return JNI_VERSION_1_4; +} |