diff options
Diffstat (limited to 'src/corelib/platform/android/qandroidnativeinterface.cpp')
-rw-r--r-- | src/corelib/platform/android/qandroidnativeinterface.cpp | 246 |
1 files changed, 202 insertions, 44 deletions
diff --git a/src/corelib/platform/android/qandroidnativeinterface.cpp b/src/corelib/platform/android/qandroidnativeinterface.cpp index 74d21c5d4c..fc3a09c78b 100644 --- a/src/corelib/platform/android/qandroidnativeinterface.cpp +++ b/src/corelib/platform/android/qandroidnativeinterface.cpp @@ -1,47 +1,36 @@ -/**************************************************************************** -** -** Copyright (C) 2021 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtCore module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** 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 https://www.qt.io/terms-conditions. For further -** information use the contact form at https://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.LGPL3 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-3.0.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 (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#include <QtCore/qcoreapplication.h> +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include <QtCore/qcoreapplication_platform.h> + +#include <QtCore/private/qnativeinterface_p.h> #include <QtCore/private/qjnihelpers_p.h> +#include <QtCore/qjniobject.h> +#if QT_CONFIG(future) && !defined(QT_NO_QOBJECT) +#include <QtCore/qfuture.h> +#include <QtCore/qfuturewatcher.h> +#include <QtCore/qpromise.h> +#include <QtCore/qtimer.h> +#include <QtCore/qthreadpool.h> +#include <deque> +#include <memory> +#endif QT_BEGIN_NAMESPACE +#if QT_CONFIG(future) && !defined(QT_NO_QOBJECT) +static const char qtNativeClassName[] = "org/qtproject/qt/android/QtNative"; + +struct PendingRunnable { + std::function<QVariant()> function; + std::shared_ptr<QPromise<QVariant>> promise; +}; + +using PendingRunnables = std::deque<PendingRunnable>; +Q_GLOBAL_STATIC(PendingRunnables, g_pendingRunnables); +Q_CONSTINIT static QBasicMutex g_pendingRunnablesMutex; +#endif + /*! \class QNativeInterface::QAndroidApplication \since 6.2 @@ -57,14 +46,14 @@ QT_BEGIN_NAMESPACE QT_DEFINE_NATIVE_INTERFACE(QAndroidApplication); /*! - \fn jobject QNativeInterface::QAndroidApplication::context() + \fn QJniObject QNativeInterface::QAndroidApplication::context() - Returns the Android context as a \c jobject. The context is an \c Activity + Returns the Android context as a \c QJniObject. The context is an \c Activity if the main activity object is valid. Otherwise, the context is a \c Service. \since 6.2 */ -jobject QNativeInterface::QAndroidApplication::context() +QtJniTypes::Context QNativeInterface::QAndroidApplication::context() { return QtAndroidPrivate::context(); } @@ -79,7 +68,176 @@ jobject QNativeInterface::QAndroidApplication::context() */ bool QNativeInterface::QAndroidApplication::isActivityContext() { - return QtAndroidPrivate::activity(); + return QtAndroidPrivate::activity().isValid(); +} + +/*! + \fn int QNativeInterface::QAndroidApplication::sdkVersion() + + Returns the Android SDK version. This is also known as the API level. + + \since 6.2 +*/ +int QNativeInterface::QAndroidApplication::sdkVersion() +{ + return QtAndroidPrivate::androidSdkVersion(); +} + +/*! + \fn void QNativeInterface::QAndroidApplication::hideSplashScreen(int duration) + + Hides the splash screen by using a fade effect for the given \a duration. + If \a duration is not provided (default is 0) the splash screen is hidden + immedetiately after the app starts. + + \since 6.2 +*/ +void QNativeInterface::QAndroidApplication::hideSplashScreen(int duration) +{ + QtAndroidPrivate::activity().callMethod<void>("hideSplashScreen", duration); +} + +/*! + Posts the function \a runnable to the Android thread. The function will be + queued and executed on the Android UI thread. If the call is made on the + Android UI thread \a runnable will be executed immediately. If the Android + app is paused or the main Activity is null, \c runnable is added to the + Android main thread's queue. + + This call returns a QFuture<QVariant> which allows doing both synchronous + and asynchronous calls, and can handle any return type. However, to get + a result back from the QFuture::result(), QVariant::value() should be used. + + If the \a runnable execution takes longer than the period of \a timeout, + the blocking calls \l QFuture::waitForFinished() and \l QFuture::result() + are ended once \a timeout has elapsed. However, if \a runnable has already + started execution, it won't be cancelled. + + The following example shows how to run an asynchronous call that expects + a return type: + + \code + auto task = QNativeInterface::QAndroidApplication::runOnAndroidMainThread([=]() { + QJniObject surfaceView; + if (!surfaceView.isValid()) + qDebug() << "SurfaceView object is not valid yet"; + + surfaceView = QJniObject("android/view/SurfaceView", + "(Landroid/content/Context;)V", + QNativeInterface::QAndroidApplication::context()); + + return QVariant::fromValue(surfaceView); + }).then([](QFuture<QVariant> future) { + auto surfaceView = future.result().value<QJniObject>(); + if (surfaceView.isValid()) + qDebug() << "Retrieved SurfaceView object is valid"; + }); + \endcode + + The following example shows how to run a synchronous call with a void + return type: + + \code + QNativeInterface::QAndroidApplication::runOnAndroidMainThread([]() { + QJniObject activity = QNativeInterface::QAndroidApplication::context(); + // Hide system ui elements or go full screen + activity.callObjectMethod("getWindow", "()Landroid/view/Window;") + .callObjectMethod("getDecorView", "()Landroid/view/View;") + .callMethod<void>("setSystemUiVisibility", "(I)V", 0xffffffff); + }).waitForFinished(); + \endcode + + \note Becareful about the type of operations you do on the Android's main + thread, as any long operation can block the app's UI rendering and input + handling. If the function is expected to have long execution time, it's + also good to use a \l QDeadlineTimer in your \a runnable to manage + the execution and make sure it doesn't block the UI thread. Usually, + any operation longer than 5 seconds might block the app's UI. For more + information, see \l {Android: Keeping your app responsive}{Keeping your app responsive}. + + \since 6.2 +*/ +#if QT_CONFIG(future) && !defined(QT_NO_QOBJECT) +QFuture<QVariant> QNativeInterface::QAndroidApplication::runOnAndroidMainThread( + const std::function<QVariant()> &runnable, + const QDeadlineTimer timeout) +{ + auto promise = std::make_shared<QPromise<QVariant>>(); + QFuture<QVariant> future = promise->future(); + promise->start(); + + if (!timeout.isForever()) { + QThreadPool::globalInstance()->start([=]() mutable { + QEventLoop loop; + QTimer::singleShot(timeout.remainingTime(), &loop, [&]() { + future.cancel(); + promise->finish(); + loop.quit(); + }); + + QFutureWatcher<QVariant> watcher; + QObject::connect(&watcher, &QFutureWatcher<QVariant>::finished, &loop, [&]() { + loop.quit(); + }); + QObject::connect(&watcher, &QFutureWatcher<QVariant>::canceled, &loop, [&]() { + loop.quit(); + }); + watcher.setFuture(future); + + // we're going to sleep, make sure we don't block + // QThreadPool::globalInstance(): + + QThreadPool::globalInstance()->releaseThread(); + const auto sg = qScopeGuard([] { + QThreadPool::globalInstance()->reserveThread(); + }); + loop.exec(); + }); + } + + QMutexLocker locker(&g_pendingRunnablesMutex); +#ifdef __cpp_aggregate_paren_init + g_pendingRunnables->emplace_back(runnable, std::move(promise)); +#else + g_pendingRunnables->push_back({runnable, std::move(promise)}); +#endif + locker.unlock(); + + QJniObject::callStaticMethod<void>(qtNativeClassName, + "runPendingCppRunnablesOnAndroidThread", + "()V"); + return future; +} + +// function called from Java from Android UI thread +static void runPendingCppRunnables(JNIEnv */*env*/, jobject /*obj*/) +{ + // run all posted runnables + for (;;) { + QMutexLocker locker(&g_pendingRunnablesMutex); + if (g_pendingRunnables->empty()) + break; + + PendingRunnable r = std::move(g_pendingRunnables->front()); + g_pendingRunnables->pop_front(); + locker.unlock(); + + // run the runnable outside the sync block! + if (!r.promise->isCanceled()) + r.promise->addResult(r.function()); + r.promise->finish(); + } +} +#endif + +bool QtAndroidPrivate::registerNativeInterfaceNatives(QJniEnvironment &env) +{ +#if QT_CONFIG(future) && !defined(QT_NO_QOBJECT) + const JNINativeMethod methods = {"runPendingCppRunnables", "()V", (void *)runPendingCppRunnables}; + return env.registerNativeMethods(qtNativeClassName, &methods, 1); +#else + return true; +#endif } QT_END_NAMESPACE |