Android A11Y: handle valueChanged events

Before this patch Android A11Y implementation was missing
ValueChanged event handling. As a result, no update was given
when the element's value was changed.

Handling these events allows us to announce value changes on such
objects like Slider, SpinBox, etc...
This is a universal method of value-change announcement, so it
supports all sorts of A11Y gestures.

On the Java side a new function was introduced to announce the
values, because we need to use the actual element's *value*,
not its accessible name or description.

Task-number: QTBUG-93396
Pick-to: 6.3 6.2 5.15
Change-Id: Ic44abd5f01b9b6f5468962131466edaf6a49d498
Reviewed-by: Assam Boudjelthia <assam.boudjelthia@qt.io>
Reviewed-by: Rami Potinkara <rami.potinkara@qt.io>
This commit is contained in:
Ivan Solovev 2022-02-10 11:53:24 +01:00
parent e5ba838045
commit b238f83380
10 changed files with 100 additions and 3 deletions

View File

@ -921,6 +921,13 @@ public class QtActivityDelegate
m_accessibilityDelegate.notifyObjectFocus(viewId);
}
public void notifyValueChanged(int viewId, String value)
{
if (m_accessibilityDelegate == null)
return;
m_accessibilityDelegate.notifyValueChanged(viewId, value);
}
public void notifyQtAndroidPluginRunning(boolean running)
{
m_isPluginRunning = running;

View File

@ -998,6 +998,18 @@ public class QtNative
});
}
private static void notifyValueChanged(int viewId, String value)
{
runAction(new Runnable() {
@Override
public void run() {
if (m_activityDelegate != null) {
m_activityDelegate.notifyValueChanged(viewId, value);
}
}
});
}
public static void notifyQtAndroidPluginRunning(final boolean running)
{
m_activityDelegate.notifyQtAndroidPluginRunning(running);

View File

@ -225,6 +225,41 @@ public class QtAccessibilityDelegate extends View.AccessibilityDelegate
AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED);
}
public void notifyValueChanged(int viewId, String value)
{
// Send a TYPE_ANNOUNCEMENT event with the new value
if ((viewId == INVALID_ID) || !m_manager.isEnabled()) {
Log.w(TAG, "notifyValueChanged() for invalid view");
return;
}
final ViewGroup group = (ViewGroup)m_view.getParent();
if (group == null) {
Log.w(TAG, "Could not announce value because ViewGroup was null.");
return;
}
final AccessibilityEvent event =
AccessibilityEvent.obtain(AccessibilityEvent.TYPE_ANNOUNCEMENT);
event.setEnabled(true);
event.setClassName(m_view.getClass().getName() + DEFAULT_CLASS_NAME);
event.setContentDescription(value);
if (event.getText().isEmpty() && TextUtils.isEmpty(event.getContentDescription())) {
Log.w(TAG, "No value to announce for " + event.getClassName());
return;
}
event.setPackageName(m_view.getContext().getPackageName());
event.setSource(m_view, viewId);
if (!group.requestSendAccessibilityEvent(m_view, event))
Log.w(TAG, "Failed to send value change announcement for " + event.getClassName());
}
public boolean sendEventForVirtualViewId(int virtualViewId, int eventType)
{
if ((virtualViewId == INVALID_ID) || !m_manager.isEnabled()) {

View File

@ -55,4 +55,5 @@ class QtNativeAccessibility
static native boolean scrollBackward(int objectId);
static native boolean populateNode(int objectId, AccessibilityNodeInfo node);
static native String valueForAccessibleObject(int objectId);
}

View File

@ -1148,6 +1148,11 @@ public class QtActivity extends Activity
QtNative.activityDelegate().notifyObjectFocus(viewId);
}
public void notifyValueChanged(int viewId, String value)
{
QtNative.activityDelegate().notifyValueChanged(viewId, value);
}
public boolean isKeyboardVisible()
{
return QtNative.activityDelegate().isKeyboardVisible();

View File

@ -149,6 +149,14 @@ namespace QtAndroidAccessibility
QtAndroid::notifyObjectFocus(accessibilityObjectId);
}
static jstring jvalueForAccessibleObject(int objectId); // forward declaration
void notifyValueChanged(uint accessibilityObjectId)
{
jstring value = jvalueForAccessibleObject(accessibilityObjectId);
QtAndroid::notifyValueChanged(accessibilityObjectId, value);
}
static QVarLengthArray<int, 8> childIdListForAccessibleObject_helper(int objectId)
{
QAccessibleInterface *iface = interfaceFromId(objectId);
@ -345,6 +353,27 @@ if (!clazz) { \
//__android_log_print(ANDROID_LOG_FATAL, m_qtTag, m_methodErrorMsg, METHOD_NAME, METHOD_SIGNATURE);
static QString textFromValue(QAccessibleInterface *iface)
{
QString valueStr;
QAccessibleValueInterface *valueIface = iface->valueInterface();
if (valueIface) {
// TODO: fix double-to-string conversion
valueStr = valueIface->currentValue().toString();
}
return valueStr;
}
static jstring jvalueForAccessibleObject(int objectId)
{
QAccessibleInterface *iface = interfaceFromId(objectId);
const QString value = textFromValue(iface);
QJniEnvironment env;
jstring jstr = env->NewString((jchar*)value.constData(), (jsize)value.size());
if (env.checkAndClearExceptions())
__android_log_print(ANDROID_LOG_WARN, m_qtTag, "Failed to create jstring");
return jstr;
}
static QString descriptionForInterface(QAccessibleInterface *iface)
{
@ -356,9 +385,7 @@ if (!clazz) { \
if (desc.isEmpty()) {
desc = iface->text(QAccessible::Value);
if (desc.isEmpty()) {
if (QAccessibleValueInterface *valueIface = iface->valueInterface()) {
desc= valueIface->currentValue().toString();
}
desc = textFromValue(iface);
}
}
}

View File

@ -54,6 +54,7 @@ namespace QtAndroidAccessibility
void notifyLocationChange();
void notifyObjectHide(uint accessibilityObjectId);
void notifyObjectFocus(uint accessibilityObjectId);
void notifyValueChanged(uint accessibilityObjectId);
void createAccessibilityContextObject(QObject *parent);
}

View File

@ -226,6 +226,12 @@ namespace QtAndroid
QJniObject::callStaticMethod<void>(m_applicationClass, "notifyObjectFocus","(I)V", accessibilityObjectId);
}
void notifyValueChanged(uint accessibilityObjectId, jstring value)
{
QJniObject::callStaticMethod<void>(m_applicationClass, "notifyValueChanged",
"(ILjava/lang/String;)V", accessibilityObjectId, value);
}
void notifyQtAndroidPluginRunning(bool running)
{
QJniObject::callStaticMethod<void>(m_applicationClass, "notifyQtAndroidPluginRunning","(Z)V", running);

View File

@ -103,6 +103,7 @@ namespace QtAndroid
void notifyAccessibilityLocationChange();
void notifyObjectHide(uint accessibilityObjectId, uint parentObjectId);
void notifyObjectFocus(uint accessibilityObjectId);
void notifyValueChanged(uint accessibilityObjectId, jstring value);
void notifyQtAndroidPluginRunning(bool running);
const char *classErrorMsgFmt();

View File

@ -66,6 +66,8 @@ void QAndroidPlatformAccessibility::notifyAccessibilityUpdate(QAccessibleEvent *
QtAndroidAccessibility::notifyObjectHide(event->uniqueId());
} else if (event->type() == QAccessible::Focus) {
QtAndroidAccessibility::notifyObjectFocus(event->uniqueId());
} else if (event->type() == QAccessible::ValueChanged) {
QtAndroidAccessibility::notifyValueChanged(event->uniqueId());
}
}