diff options
Diffstat (limited to 'src/plugins/platforms/android')
9 files changed, 154 insertions, 50 deletions
diff --git a/src/plugins/platforms/android/androidjniaccessibility.cpp b/src/plugins/platforms/android/androidjniaccessibility.cpp index b5783bb67a..0f0cd1e16f 100644 --- a/src/plugins/platforms/android/androidjniaccessibility.cpp +++ b/src/plugins/platforms/android/androidjniaccessibility.cpp @@ -96,7 +96,13 @@ namespace QtAndroidAccessibility template <typename Func, typename Ret> void runInObjectContext(QObject *context, Func &&func, Ret *retVal) { - QMetaObject::invokeMethod(context, func, Qt::BlockingQueuedConnection, retVal); + 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() @@ -160,6 +166,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); @@ -217,7 +228,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); @@ -225,7 +236,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); } @@ -327,23 +338,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); } diff --git a/src/plugins/platforms/android/androidjniaccessibility.h b/src/plugins/platforms/android/androidjniaccessibility.h index 99d151bc3f..212131ff62 100644 --- a/src/plugins/platforms/android/androidjniaccessibility.h +++ b/src/plugins/platforms/android/androidjniaccessibility.h @@ -55,6 +55,7 @@ namespace QtAndroidAccessibility void notifyObjectHide(uint accessibilityObjectId); void notifyObjectFocus(uint accessibilityObjectId); void notifyValueChanged(uint accessibilityObjectId); + void notifyScrolledEvent(uint accessibilityObjectId); void createAccessibilityContextObject(QObject *parent); } diff --git a/src/plugins/platforms/android/androidjniinput.cpp b/src/plugins/platforms/android/androidjniinput.cpp index 1abd23485e..bb77752734 100644 --- a/src/plugins/platforms/android/androidjniinput.cpp +++ b/src/plugins/platforms/android/androidjniinput.cpp @@ -121,6 +121,11 @@ namespace QtAndroidInput return m_softwareKeyboardRect; } + int getSelectHandleWidth() + { + return QJNIObjectPrivate::callStaticMethod<jint>(applicationClass(), "getSelectHandleWidth"); + } + void updateHandles(int mode, QPoint editMenuPos, uint32_t editButtons, QPoint cursor, QPoint anchor, bool rtl) { QJNIObjectPrivate::callStaticMethod<void>(applicationClass(), "updateHandles", "(IIIIIIIIZ)V", diff --git a/src/plugins/platforms/android/androidjniinput.h b/src/plugins/platforms/android/androidjniinput.h index cc3070c4aa..4b2aef07ca 100644 --- a/src/plugins/platforms/android/androidjniinput.h +++ b/src/plugins/platforms/android/androidjniinput.h @@ -60,6 +60,8 @@ namespace QtAndroidInput // cursor/selection handles void updateHandles(int handleCount, QPoint editMenuPos = QPoint(), uint32_t editButtons = 0, QPoint cursor = QPoint(), QPoint anchor = QPoint(), bool rtl = false); + int getSelectHandleWidth(); + bool registerNatives(JNIEnv *env); } diff --git a/src/plugins/platforms/android/androidjnimain.cpp b/src/plugins/platforms/android/androidjnimain.cpp index ad141a925c..577a7905b9 100644 --- a/src/plugins/platforms/android/androidjnimain.cpp +++ b/src/plugins/platforms/android/androidjnimain.cpp @@ -250,6 +250,12 @@ namespace QtAndroid value); } + void notifyScrolledEvent(uint accessibilityObjectId) + { + QJNIObjectPrivate::callStaticMethod<void>(m_applicationClass, "notifyScrolledEvent", "(I)V", + accessibilityObjectId); + } + void notifyQtAndroidPluginRunning(bool running) { QJNIObjectPrivate::callStaticMethod<void>(m_applicationClass, "notifyQtAndroidPluginRunning","(Z)V", running); @@ -677,30 +683,23 @@ static void setDisplayMetrics(JNIEnv */*env*/, jclass /*clazz*/, jdouble xdpi, jdouble ydpi, jdouble scaledDensity, jdouble density, jfloat refreshRate) { - // Android does not give us the correct screen size for immersive mode, but - // the surface does have the right size - - widthPixels = qMax(widthPixels, desktopWidthPixels); - heightPixels = qMax(heightPixels, desktopHeightPixels); - m_desktopWidthPixels = desktopWidthPixels; m_desktopHeightPixels = desktopHeightPixels; m_scaledDensity = scaledDensity; m_density = density; + const QSize screenSize(widthPixels, heightPixels); + // available geometry always starts from top left + const QRect availableGeometry(0, 0, desktopWidthPixels, desktopHeightPixels); + const QSize physicalSize(qRound(double(widthPixels) / xdpi * 25.4), + qRound(double(heightPixels) / ydpi * 25.4)); + QMutexLocker lock(&m_platformMutex); if (!m_androidPlatformIntegration) { - QAndroidPlatformIntegration::setDefaultDisplayMetrics(desktopWidthPixels, - desktopHeightPixels, - qRound(double(widthPixels) / xdpi * 25.4), - qRound(double(heightPixels) / ydpi * 25.4), - widthPixels, - heightPixels); + QAndroidPlatformIntegration::setDefaultDisplayMetrics( + availableGeometry.width(), availableGeometry.height(), physicalSize.width(), + physicalSize.height(), screenSize.width(), screenSize.height()); } else { - const QSize physicalSize(qRound(double(widthPixels) / xdpi * 25.4), - qRound(double(heightPixels) / ydpi * 25.4)); - const QSize screenSize(widthPixels, heightPixels); - const QRect availableGeometry(0, 0, desktopWidthPixels, desktopHeightPixels); m_androidPlatformIntegration->setScreenSizeParameters(physicalSize, screenSize, availableGeometry); m_androidPlatformIntegration->setRefreshRate(refreshRate); diff --git a/src/plugins/platforms/android/androidjnimain.h b/src/plugins/platforms/android/androidjnimain.h index 9aaaa667c2..cc2839c20e 100644 --- a/src/plugins/platforms/android/androidjnimain.h +++ b/src/plugins/platforms/android/androidjnimain.h @@ -99,6 +99,7 @@ namespace QtAndroid void notifyObjectHide(uint accessibilityObjectId, uint parentObjectId); void notifyObjectFocus(uint accessibilityObjectId); void notifyValueChanged(uint accessibilityObjectId, jstring value); + void notifyScrolledEvent(uint accessibilityObjectId); void notifyQtAndroidPluginRunning(bool running); const char *classErrorMsgFmt(); diff --git a/src/plugins/platforms/android/qandroidassetsfileenginehandler.cpp b/src/plugins/platforms/android/qandroidassetsfileenginehandler.cpp index 07776a4a76..316859b2a6 100644 --- a/src/plugins/platforms/android/qandroidassetsfileenginehandler.cpp +++ b/src/plugins/platforms/android/qandroidassetsfileenginehandler.cpp @@ -86,6 +86,7 @@ struct AssetItem { } Type type = Type::File; QString name; + qint64 size = -1; }; using AssetItemList = QVector<AssetItem>; @@ -261,7 +262,7 @@ public: bool open(QIODevice::OpenMode openMode) override { - if (m_isFolder || (openMode & QIODevice::WriteOnly)) + if (!m_assetInfo || m_assetInfo->type != AssetItem::Type::File || (openMode & QIODevice::WriteOnly)) return false; close(); m_assetFile = AAssetManager_open(m_assetManager, m_fileName.toUtf8(), AASSET_MODE_BUFFER); @@ -275,14 +276,13 @@ public: m_assetFile = 0; return true; } - m_isFolder = false; return false; } qint64 size() const override { - if (m_assetFile) - return AAsset_getLength(m_assetFile); + if (m_assetInfo) + return m_assetInfo->size; return -1; } @@ -326,10 +326,12 @@ public: { FileFlags commonFlags(ReadOwnerPerm|ReadUserPerm|ReadGroupPerm|ReadOtherPerm|ExistsFlag); FileFlags flags; - if (m_assetFile) - flags = FileType | commonFlags; - else if (m_isFolder) - flags = DirectoryType | commonFlags; + if (m_assetInfo) { + if (m_assetInfo->type == AssetItem::Type::File) + flags = FileType | commonFlags; + else if (m_assetInfo->type == AssetItem::Type::Folder) + flags = DirectoryType | commonFlags; + } return type & flags; } @@ -364,21 +366,43 @@ public: return; close(); m_fileName = cleanedAssetPath(file); - switch (FolderIterator::fileType(m_fileName)) { - case AssetItem::Type::File: - open(QIODevice::ReadOnly); - break; - case AssetItem::Type::Folder: - m_isFolder = true; - break; - case AssetItem::Type::Invalid: - break; + + { + QMutexLocker lock(&m_assetsInfoCacheMutex); + QSharedPointer<AssetItem> *assetInfoPtr = m_assetsInfoCache.object(m_fileName); + if (assetInfoPtr) { + m_assetInfo = *assetInfoPtr; + return; + } } + + QSharedPointer<AssetItem> *newAssetInfoPtr = new QSharedPointer<AssetItem>(new AssetItem); + + m_assetInfo = *newAssetInfoPtr; + m_assetInfo->name = m_fileName; + m_assetInfo->type = AssetItem::Type::Invalid; + + m_assetFile = AAssetManager_open(m_assetManager, m_fileName.toUtf8(), AASSET_MODE_BUFFER); + + if (m_assetFile) { + m_assetInfo->type = AssetItem::Type::File; + m_assetInfo->size = AAsset_getLength(m_assetFile); + } else { + auto *assetDir = AAssetManager_openDir(m_assetManager, m_fileName.toUtf8()); + if (assetDir) { + if (AAssetDir_getNextFileName(assetDir)) + m_assetInfo->type = AssetItem::Type::Folder; + AAssetDir_close(assetDir); + } + } + + QMutexLocker lock(&m_assetsInfoCacheMutex); + m_assetsInfoCache.insert(m_fileName, newAssetInfoPtr); } Iterator *beginEntryList(QDir::Filters filters, const QStringList &filterNames) override { - if (m_isFolder) + if (m_assetInfo && m_assetInfo->type == AssetItem::Type::Folder) return new AndroidAbstractFileEngineIterator(filters, filterNames, m_fileName); return nullptr; } @@ -388,9 +412,14 @@ private: AAssetManager *m_assetManager = nullptr; // initialize with a name that can't be used as a file name QString m_fileName = QLatin1String("."); - bool m_isFolder = false; + QSharedPointer<AssetItem> m_assetInfo; + + static QCache<QString, QSharedPointer<AssetItem>> m_assetsInfoCache; + static QMutex m_assetsInfoCacheMutex; }; +QCache<QString, QSharedPointer<AssetItem>> AndroidAbstractFileEngine::m_assetsInfoCache(std::max(200, qEnvironmentVariableIntValue("QT_ANDROID_MAX_FILEINFO_ASSETS_CACHE_SIZE"))); +QMutex AndroidAbstractFileEngine::m_assetsInfoCacheMutex; AndroidAssetsFileEngineHandler::AndroidAssetsFileEngineHandler() { diff --git a/src/plugins/platforms/android/qandroidinputcontext.cpp b/src/plugins/platforms/android/qandroidinputcontext.cpp index 3b7f29ec1a..8a44482d44 100644 --- a/src/plugins/platforms/android/qandroidinputcontext.cpp +++ b/src/plugins/platforms/android/qandroidinputcontext.cpp @@ -514,7 +514,12 @@ QAndroidInputContext::QAndroidInputContext() auto im = qGuiApp->inputMethod(); if (!im->inputItemClipRectangle().contains(im->anchorRectangle()) || !im->inputItemClipRectangle().contains(im->cursorRectangle())) { - m_handleMode = Hidden; + // Undoes the hidden request if the only reason for the hidden is that + // X of the cursorRectangle or X of the anchorRectangle is less than 0. + const int rectX = im->inputItemClipRectangle().x(); + if (im->cursorRectangle().x() > rectX && im->anchorRectangle().x() > rectX) + m_handleMode = Hidden; + updateSelectionHandles(); } }); @@ -623,13 +628,13 @@ void QAndroidInputContext::updateSelectionHandles() if (noHandles) return; + QWindow *window = qGuiApp->focusWindow(); auto im = qGuiApp->inputMethod(); - if (!m_focusObject || ((m_handleMode & 0xff) == Hidden)) { + if (!m_focusObject || ((m_handleMode & 0xff) == Hidden) || !window) { // Hide the handles QtAndroidInput::updateHandles(Hidden); return; } - QWindow *window = qGuiApp->focusWindow(); double pixelDensity = window ? QHighDpiScaling::factor(window) : QHighDpiScaling::factor(QtAndroid::androidPlatformIntegration()->screen()); @@ -647,14 +652,25 @@ void QAndroidInputContext::updateSelectionHandles() } auto curRect = im->cursorRectangle(); - QPoint cursorPoint(window->mapToGlobal(QPoint(curRect.x() + (curRect.width() / 2), curRect.y() + curRect.height()))); - QPoint editMenuPoint(cursorPoint.x(), cursorPoint.y()); + QPoint cursorPointGlobal = window->mapToGlobal(QPoint(curRect.x() + (curRect.width() / 2), curRect.y() + curRect.height())); + QPoint cursorPoint(curRect.center().x(), curRect.bottom()); + int x = curRect.x(); + int y = curRect.y(); + + // Use x and y for the editMenuPoint from the cursorPointGlobal when the cursor is in the Dialog + if (cursorPointGlobal != cursorPoint) { + x = cursorPointGlobal.x(); + y = cursorPointGlobal.y(); + } + + QPoint editMenuPoint(x, y); m_handleMode &= ShowEditPopup; m_handleMode |= ShowCursor; uint32_t buttons = EditContext::PasteButton; if (!query.value(Qt::ImSurroundingText).toString().isEmpty()) buttons |= EditContext::SelectAllButton; - QtAndroidInput::updateHandles(m_handleMode, editMenuPoint * pixelDensity, buttons, cursorPoint * pixelDensity); + QtAndroidInput::updateHandles(m_handleMode, editMenuPoint * pixelDensity, buttons, + cursorPointGlobal * pixelDensity); // The VK is hidden, reset the timer if (m_hideCursorHandleTimer.isActive()) m_hideCursorHandleTimer.start(); @@ -667,12 +683,30 @@ void QAndroidInputContext::updateSelectionHandles() if (cpos > anchor) std::swap(leftRect, rightRect); + // Move the left or right select handle to the center from the screen edge + // the select handle is close to or over the screen edge. Otherwise, the + // select handle might go out of the screen and it would be impossible to drag. QPoint leftPoint(window->mapToGlobal(leftRect.bottomLeft().toPoint())); - QPoint righPoint(window->mapToGlobal(rightRect.bottomRight().toPoint())); - QPoint editPoint(window->mapToGlobal(leftRect.united(rightRect) - .topLeft().toPoint())); + QPoint rightPoint(window->mapToGlobal(rightRect.bottomRight().toPoint())); + static int m_selectHandleWidth = 0; + // For comparison, get the width of the handle. + // Only half of the width will protrude from the cursor on each side + if (m_selectHandleWidth == 0) + m_selectHandleWidth = QtAndroidInput::getSelectHandleWidth() / 2; + + int rightSideOfScreen = QtAndroid::androidPlatformIntegration()->screen()->availableGeometry().right(); + + // Check if handle will fit the screen on left side. If not, then move it closer to the center + if (leftPoint.x() <= m_selectHandleWidth) + leftPoint.setX(m_selectHandleWidth / pixelDensity); + + // Check if handle will fit the screen on right side. If not, then move it closer to the center + if (rightPoint.x() >= (rightSideOfScreen / pixelDensity) - m_selectHandleWidth) + rightPoint.setX((rightSideOfScreen / pixelDensity) - (m_selectHandleWidth / pixelDensity)); + + QPoint editPoint(window->mapToGlobal(leftRect.united(rightRect).topLeft().toPoint())); QtAndroidInput::updateHandles(m_handleMode, editPoint * pixelDensity, EditContext::AllButtons, - leftPoint * pixelDensity, righPoint * pixelDensity, + leftPoint * pixelDensity, rightPoint * pixelDensity, query.value(Qt::ImCurrentSelection).toString().isRightToLeft()); m_hideCursorHandleTimer.stop(); } diff --git a/src/plugins/platforms/android/qandroidplatformaccessibility.cpp b/src/plugins/platforms/android/qandroidplatformaccessibility.cpp index bdbf709b3d..b29190d3e9 100644 --- a/src/plugins/platforms/android/qandroidplatformaccessibility.cpp +++ b/src/plugins/platforms/android/qandroidplatformaccessibility.cpp @@ -68,6 +68,8 @@ void QAndroidPlatformAccessibility::notifyAccessibilityUpdate(QAccessibleEvent * QtAndroidAccessibility::notifyObjectFocus(event->uniqueId()); } else if (event->type() == QAccessible::ValueChanged) { QtAndroidAccessibility::notifyValueChanged(event->uniqueId()); + } else if (event->type() == QAccessible::ScrollingEnd) { + QtAndroidAccessibility::notifyScrolledEvent(event->uniqueId()); } } |