Android A11Y: Fix TalkBack scrolling behavior

* Add CollectionInfo to scrollable nodes.
  Every scrollable node will get a CollectionInfo attached to signal
  the number of (possibly invisible) children.
  This is necessary as TalkBack on Android doesn't scroll to items not
  visible on screen if the number of further child nodes is not
  communicated to TalkBack.
* Return success of scroll to TalkBack.
  TalkBack needs the result of the scroll to decide if it should leave
  the current element or stay after a scroll was successful.
  Success of a scroll action is measured as the successful movement
  of the children of the scrolled element. This is a workaround for
  the Qt Accessibility API not returning the success of failure of a
  performed action.

Task-number: QTBUG-103499
Pick-to: 6.4 6.3 6.2 5.15
Change-Id: Ie2c51d0b77fb5030973a0f93c42e0db3082be45e
Reviewed-by: Assam Boudjelthia <assam.boudjelthia@qt.io>
This commit is contained in:
Jens Trillmann 2022-05-24 08:57:45 +02:00
parent 3c70919883
commit 56c4d183ec
2 changed files with 33 additions and 4 deletions

View File

@ -7,6 +7,7 @@ package org.qtproject.qt.android.accessibility;
import android.accessibilityservice.AccessibilityService;
import android.app.Activity;
import android.graphics.Rect;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
@ -15,6 +16,7 @@ import android.view.ViewParent;
import android.text.TextUtils;
import android.view.accessibility.*;
import android.view.accessibility.AccessibilityNodeInfo.CollectionInfo;
import android.view.MotionEvent;
import android.view.View.OnHoverListener;
@ -407,6 +409,13 @@ public class QtAccessibilityDelegate extends View.AccessibilityDelegate
int[] ids = QtNativeAccessibility.childIdListForAccessibleObject(virtualViewId);
for (int i = 0; i < ids.length; ++i)
node.addChild(m_view, ids[i]);
if (node.isScrollable()) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
node.setCollectionInfo(new CollectionInfo(ids.length, 1, false));
} else {
node.setCollectionInfo(CollectionInfo.obtain(ids.length, 1, false));
}
}
return node;
}

View File

@ -193,7 +193,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);
@ -201,7 +201,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);
}
@ -303,23 +303,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);
}