diff options
Diffstat (limited to 'src/plugins/platforms/android/androidjniaccessibility.cpp')
-rw-r--r-- | src/plugins/platforms/android/androidjniaccessibility.cpp | 117 |
1 files changed, 84 insertions, 33 deletions
diff --git a/src/plugins/platforms/android/androidjniaccessibility.cpp b/src/plugins/platforms/android/androidjniaccessibility.cpp index 632eb34b92..adad9dde98 100644 --- a/src/plugins/platforms/android/androidjniaccessibility.cpp +++ b/src/plugins/platforms/android/androidjniaccessibility.cpp @@ -1,12 +1,11 @@ /**************************************************************************** ** -** Copyright (C) 2021 The Qt Company Ltd. +** Copyright (C) 2016 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the plugins of the Qt Toolkit. ** -** $QT_BEGIN_LICENSE:COMM$ -** +** $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 @@ -15,28 +14,30 @@ ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** -** $QT_END_LICENSE$ -** -** -** -** -** -** -** -** -** -** -** -** -** -** -** -** +** 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 "androiddeadlockprotector.h" #include "androidjniaccessibility.h" #include "androidjnimain.h" #include "qandroidplatformintegration.h" @@ -70,6 +71,7 @@ namespace QtAndroidAccessibility static jmethodID m_setEnabledMethodID = 0; static jmethodID m_setFocusableMethodID = 0; static jmethodID m_setFocusedMethodID = 0; + static jmethodID m_setHeadingMethodID = 0; static jmethodID m_setScrollableMethodID = 0; static jmethodID m_setTextSelectionMethodID = 0; static jmethodID m_setVisibleToUserMethodID = 0; @@ -81,7 +83,7 @@ namespace QtAndroidAccessibility // Because of that almost every method here is split into two parts. // The _helper part is executed in the context of m_accessibilityContext // on the main thread. The other part is executed in Java thread. - static QObject *m_accessibilityContext = nullptr; + static QPointer<QObject> m_accessibilityContext = nullptr; // This method is called from the Qt main thread, and normally a // QGuiApplication instance will be used as a parent. @@ -95,7 +97,21 @@ namespace QtAndroidAccessibility template <typename Func, typename Ret> void runInObjectContext(QObject *context, Func &&func, Ret *retVal) { - QMetaObject::invokeMethod(context, func, Qt::BlockingQueuedConnection, retVal); + AndroidDeadlockProtector protector; + if (!protector.acquire()) { + __android_log_print(ANDROID_LOG_WARN, m_qtTag, + "Could not run accessibility call in object context, accessing " + "main thread could lead to deadlock"); + return; + } + + if (!QtAndroid::blockEventLoopsWhenSuspended() + || QGuiApplication::applicationState() != Qt::ApplicationSuspended) { + QMetaObject::invokeMethod(context, func, Qt::BlockingQueuedConnection, retVal); + } else { + __android_log_print(ANDROID_LOG_WARN, m_qtTag, + "Could not run accessibility call in object context, event loop suspended."); + } } void initialize() @@ -133,9 +149,9 @@ namespace QtAndroidAccessibility return iface; } - void notifyLocationChange() + void notifyLocationChange(uint accessibilityObjectId) { - QtAndroid::notifyAccessibilityLocationChange(); + QtAndroid::notifyAccessibilityLocationChange(accessibilityObjectId); } static int parentId_helper(int objectId); // forward declaration @@ -159,6 +175,11 @@ namespace QtAndroidAccessibility QtAndroid::notifyValueChanged(accessibilityObjectId, value); } + void notifyScrolledEvent(uint accessiblityObjectId) + { + QtAndroid::notifyScrolledEvent(accessiblityObjectId); + } + static QVarLengthArray<int, 8> childIdListForAccessibleObject_helper(int objectId) { QAccessibleInterface *iface = interfaceFromId(objectId); @@ -216,7 +237,7 @@ namespace QtAndroidAccessibility return result; } - static QRect screenRect_helper(int objectId) + static QRect screenRect_helper(int objectId, bool clip = true) { QRect rect; QAccessibleInterface *iface = interfaceFromId(objectId); @@ -224,7 +245,7 @@ namespace QtAndroidAccessibility rect = QHighDpi::toNativePixels(iface->rect(), iface->window()); } // If the widget is not fully in-bound in its parent then we have to clip the rectangle to draw - if (iface && iface->parent() && iface->parent()->isValid()) { + if (clip && iface && iface->parent() && iface->parent()->isValid()) { const auto parentRect = QHighDpi::toNativePixels(iface->parent()->rect(), iface->parent()->window()); rect = rect.intersected(parentRect); } @@ -326,23 +347,43 @@ namespace QtAndroidAccessibility static jboolean scrollForward(JNIEnv */*env*/, jobject /*thiz*/, jint objectId) { bool result = false; + + const auto& ids = childIdListForAccessibleObject_helper(objectId); + if (ids.isEmpty()) + return false; + + const int firstChildId = ids.first(); + const QRect oldPosition = screenRect_helper(firstChildId, false); + if (m_accessibilityContext) { runInObjectContext(m_accessibilityContext, [objectId]() { return scroll_helper(objectId, QAccessibleActionInterface::increaseAction()); }, &result); } - return result; + + // Don't check for position change if the call was not successful + return result && oldPosition != screenRect_helper(firstChildId, false); } static jboolean scrollBackward(JNIEnv */*env*/, jobject /*thiz*/, jint objectId) { bool result = false; + + const auto& ids = childIdListForAccessibleObject_helper(objectId); + if (ids.isEmpty()) + return false; + + const int firstChildId = ids.first(); + const QRect oldPosition = screenRect_helper(firstChildId, false); + if (m_accessibilityContext) { runInObjectContext(m_accessibilityContext, [objectId]() { return scroll_helper(objectId, QAccessibleActionInterface::decreaseAction()); }, &result); } - return result; + + // Don't check for position change if the call was not successful + return result && oldPosition != screenRect_helper(firstChildId, false); } @@ -433,10 +474,13 @@ if (!clazz) { \ desc = iface->text(QAccessible::Value); hasValue = !desc.isEmpty(); } - if (!hasValue) { - if (!desc.isEmpty()) - desc.append(QChar(QChar::Space)); - desc.append(textFromValue(iface)); + if (!hasValue && iface->valueInterface()) { + const QString valueStr = textFromValue(iface); + if (!valueStr.isEmpty()) { + if (!desc.isEmpty()) + desc.append(QChar(QChar::Space)); + desc.append(valueStr); + } } } return desc; @@ -464,6 +508,7 @@ if (!clazz) { \ { bool valid = false; QAccessible::State state; + QAccessible::Role role; QStringList actions; QString description; bool hasTextSelection = false; @@ -478,6 +523,7 @@ if (!clazz) { \ if (iface && iface->isValid()) { info.valid = true; info.state = iface->state(); + info.role = iface->role(); info.actions = QAccessibleBridgeUtils::effectiveActionNames(iface); info.description = descriptionForInterface(iface); QAccessibleTextInterface *textIface = iface->textInterface(); @@ -521,9 +567,11 @@ if (!clazz) { \ env->CallVoidMethod(node, m_setEnabledMethodID, !info.state.disabled); env->CallVoidMethod(node, m_setFocusableMethodID, (bool)info.state.focusable); env->CallVoidMethod(node, m_setFocusedMethodID, (bool)info.state.focused); + if (m_setHeadingMethodID) + env->CallVoidMethod(node, m_setHeadingMethodID, info.role == QAccessible::Heading); env->CallVoidMethod(node, m_setVisibleToUserMethodID, !info.state.invisible); env->CallVoidMethod(node, m_setScrollableMethodID, hasIncreaseAction || hasDecreaseAction); - env->CallVoidMethod(node, m_setClickableMethodID, hasClickableAction); + env->CallVoidMethod(node, m_setClickableMethodID, hasClickableAction || info.role == QAccessible::Link); // Add ACTION_CLICK if (hasClickableAction) @@ -587,6 +635,9 @@ if (!clazz) { \ GET_AND_CHECK_STATIC_METHOD(m_setEnabledMethodID, nodeInfoClass, "setEnabled", "(Z)V"); GET_AND_CHECK_STATIC_METHOD(m_setFocusableMethodID, nodeInfoClass, "setFocusable", "(Z)V"); GET_AND_CHECK_STATIC_METHOD(m_setFocusedMethodID, nodeInfoClass, "setFocused", "(Z)V"); + if (QtAndroidPrivate::androidSdkVersion() >= 28) { + GET_AND_CHECK_STATIC_METHOD(m_setHeadingMethodID, nodeInfoClass, "setHeading", "(Z)V"); + } GET_AND_CHECK_STATIC_METHOD(m_setScrollableMethodID, nodeInfoClass, "setScrollable", "(Z)V"); GET_AND_CHECK_STATIC_METHOD(m_setVisibleToUserMethodID, nodeInfoClass, "setVisibleToUser", "(Z)V"); GET_AND_CHECK_STATIC_METHOD(m_setTextSelectionMethodID, nodeInfoClass, "setTextSelection", "(II)V"); |