Revision control
++ // 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
++ // Qt-Security score:significant reason:default
++
++ #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
++ \brief Native interface to a core application on Android.
++
++ Accessed through QCoreApplication::nativeInterface().
++
++ \inmodule QtCore
++ \inheaderfile QCoreApplication
++ \ingroup native-interfaces
++ \ingroup native-interfaces-qcoreapplication
++ */
++ QT_DEFINE_NATIVE_INTERFACE(QAndroidApplication);
++
++ /*!
++ \fn QJniObject QNativeInterface::QAndroidApplication::context()
++
++ Returns the Android context as a \c QtJniTypes::Context. The context
++ is an \c Activity if the most recently started activity object is valid.
++ Otherwise, the context is a \c Service.
++
++ \since 6.2
++ */
++ QtJniTypes::Context QNativeInterface::QAndroidApplication::context()
++ {
++ return QtAndroidPrivate::context();
++ }
++
++ /*!
++ \fn bool QNativeInterface::QAndroidApplication::isActivityContext()
++
++ Returns \c true if QAndroidApplication::context() provides an \c Activity
++ context.
++
++ \since 6.2
++ */
++ bool QNativeInterface::QAndroidApplication::isActivityContext()
++ {
++ 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: Keep your app responsive}{Keep 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