/**************************************************************************** ** ** 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 #include #include #include #include #include #include #include #include #include #include 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(pixelData), bitmapInfo.width * bitmapInfo.height); // AndroidBitmap_unlockPixels(env, icon); // return true; //} typedef QMap WebViews; Q_GLOBAL_STATIC(WebViews, g_webViews) QAndroidWebViewPrivate::QAndroidWebViewPrivate(QObject *p) : QAbstractWebView(p) , m_id(reinterpret_cast(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(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("destroy"); } QString QAndroidWebViewPrivate::httpUserAgent() const { return QString( m_viewController.callObjectMethod("getUserAgent").toString()); } void QAndroidWebViewPrivate::setHttpUserAgent(const QString &userAgent) { m_viewController.callMethod("setUserAgent", "(Ljava/lang/String;)V", QJNIObjectPrivate::fromString(userAgent).object()); Q_EMIT httpUserAgentChanged(userAgent); } QUrl QAndroidWebViewPrivate::url() const { return QUrl::fromUserInput(m_viewController.callObjectMethod("getUrl").toString()); } void QAndroidWebViewPrivate::setUrl(const QUrl &url) { m_viewController.callMethod("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("loadData", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V", htmlString.object(), mimeTypeString.object(), 0) : m_viewController.callMethod("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("canGoBack"); } void QAndroidWebViewPrivate::goBack() { m_viewController.callMethod("goBack"); } bool QAndroidWebViewPrivate::canGoForward() const { return m_viewController.callMethod("canGoForward"); } void QAndroidWebViewPrivate::goForward() { m_viewController.callMethod("goForward"); } void QAndroidWebViewPrivate::reload() { m_viewController.callMethod("reload"); } QString QAndroidWebViewPrivate::title() const { return m_viewController.callObjectMethod("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("runJavaScript", "(Ljava/lang/String;J)V", static_cast(QJNIObjectPrivate::fromString(script).object()), callbackId); } void QAndroidWebViewPrivate::setVisible(bool visible) { m_window->setVisible(visible); } int QAndroidWebViewPrivate::loadProgress() const { return m_viewController.callMethod("getProgress"); } bool QAndroidWebViewPrivate::isLoading() const { return m_viewController.callMethod("isLoading"); } void QAndroidWebViewPrivate::setParentView(QObject *view) { m_window->setParent(qobject_cast(view)); } QObject *QAndroidWebViewPrivate::parentView() const { return m_window->parent(); } void QAndroidWebViewPrivate::stop() { m_viewController.callMethod("stopLoading"); } //void QAndroidWebViewPrivate::initialize() //{ // // TODO: //} void QAndroidWebViewPrivate::onApplicationStateChanged(Qt::ApplicationState state) { if (QtAndroidPrivate::androidSdkVersion() < 11) return; if (state == Qt::ApplicationActive) m_viewController.callMethod("onResume"); else m_viewController.callMethod("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(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_6) != 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(c_onPageFinished)}, {"c_onPageStarted", "(JLjava/lang/String;Landroid/graphics/Bitmap;)V", reinterpret_cast(c_onPageStarted)}, {"c_onProgressChanged", "(JI)V", reinterpret_cast(c_onProgressChanged)}, {"c_onReceivedIcon", "(JLandroid/graphics/Bitmap;)V", reinterpret_cast(c_onReceivedIcon)}, {"c_onReceivedTitle", "(JLjava/lang/String;)V", reinterpret_cast(c_onReceivedTitle)}, {"c_onRunJavaScriptResult", "(JJLjava/lang/String;)V", reinterpret_cast(c_onRunJavaScriptResult)}, {"c_onReceivedError", "(JILjava/lang/String;Ljava/lang/String;)V", reinterpret_cast(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_6; }