From b238f83380dcaa2830999a8f413f4b648db80beb Mon Sep 17 00:00:00 2001 From: Ivan Solovev Date: Thu, 10 Feb 2022 11:53:24 +0100 Subject: [PATCH] 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 Reviewed-by: Rami Potinkara --- .../qt/android/QtActivityDelegate.java | 7 ++++ .../org/qtproject/qt/android/QtNative.java | 12 +++++++ .../QtAccessibilityDelegate.java | 35 +++++++++++++++++++ .../accessibility/QtNativeAccessibility.java | 1 + .../qt/android/bindings/QtActivity.java | 5 +++ .../android/androidjniaccessibility.cpp | 33 +++++++++++++++-- .../android/androidjniaccessibility.h | 1 + .../platforms/android/androidjnimain.cpp | 6 ++++ .../platforms/android/androidjnimain.h | 1 + .../android/qandroidplatformaccessibility.cpp | 2 ++ 10 files changed, 100 insertions(+), 3 deletions(-) diff --git a/src/android/jar/src/org/qtproject/qt/android/QtActivityDelegate.java b/src/android/jar/src/org/qtproject/qt/android/QtActivityDelegate.java index 83f86b0d97..fe76d731fd 100644 --- a/src/android/jar/src/org/qtproject/qt/android/QtActivityDelegate.java +++ b/src/android/jar/src/org/qtproject/qt/android/QtActivityDelegate.java @@ -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; diff --git a/src/android/jar/src/org/qtproject/qt/android/QtNative.java b/src/android/jar/src/org/qtproject/qt/android/QtNative.java index ccf18ca0a6..c7eb71bb50 100644 --- a/src/android/jar/src/org/qtproject/qt/android/QtNative.java +++ b/src/android/jar/src/org/qtproject/qt/android/QtNative.java @@ -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); diff --git a/src/android/jar/src/org/qtproject/qt/android/accessibility/QtAccessibilityDelegate.java b/src/android/jar/src/org/qtproject/qt/android/accessibility/QtAccessibilityDelegate.java index 4d72e9a680..c5f5fb92e6 100644 --- a/src/android/jar/src/org/qtproject/qt/android/accessibility/QtAccessibilityDelegate.java +++ b/src/android/jar/src/org/qtproject/qt/android/accessibility/QtAccessibilityDelegate.java @@ -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()) { diff --git a/src/android/jar/src/org/qtproject/qt/android/accessibility/QtNativeAccessibility.java b/src/android/jar/src/org/qtproject/qt/android/accessibility/QtNativeAccessibility.java index aa6f7c5a67..c574291708 100644 --- a/src/android/jar/src/org/qtproject/qt/android/accessibility/QtNativeAccessibility.java +++ b/src/android/jar/src/org/qtproject/qt/android/accessibility/QtNativeAccessibility.java @@ -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); } diff --git a/src/android/java/src/org/qtproject/qt/android/bindings/QtActivity.java b/src/android/java/src/org/qtproject/qt/android/bindings/QtActivity.java index e4073948de..0242f05693 100644 --- a/src/android/java/src/org/qtproject/qt/android/bindings/QtActivity.java +++ b/src/android/java/src/org/qtproject/qt/android/bindings/QtActivity.java @@ -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(); diff --git a/src/plugins/platforms/android/androidjniaccessibility.cpp b/src/plugins/platforms/android/androidjniaccessibility.cpp index bf492a8bd2..97d5692a47 100644 --- a/src/plugins/platforms/android/androidjniaccessibility.cpp +++ b/src/plugins/platforms/android/androidjniaccessibility.cpp @@ -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 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); } } } diff --git a/src/plugins/platforms/android/androidjniaccessibility.h b/src/plugins/platforms/android/androidjniaccessibility.h index a9044a32b1..a6e2edee5e 100644 --- a/src/plugins/platforms/android/androidjniaccessibility.h +++ b/src/plugins/platforms/android/androidjniaccessibility.h @@ -54,6 +54,7 @@ namespace QtAndroidAccessibility void notifyLocationChange(); void notifyObjectHide(uint accessibilityObjectId); void notifyObjectFocus(uint accessibilityObjectId); + void notifyValueChanged(uint accessibilityObjectId); void createAccessibilityContextObject(QObject *parent); } diff --git a/src/plugins/platforms/android/androidjnimain.cpp b/src/plugins/platforms/android/androidjnimain.cpp index 9816dfb93e..b62624c7b5 100644 --- a/src/plugins/platforms/android/androidjnimain.cpp +++ b/src/plugins/platforms/android/androidjnimain.cpp @@ -226,6 +226,12 @@ namespace QtAndroid QJniObject::callStaticMethod(m_applicationClass, "notifyObjectFocus","(I)V", accessibilityObjectId); } + void notifyValueChanged(uint accessibilityObjectId, jstring value) + { + QJniObject::callStaticMethod(m_applicationClass, "notifyValueChanged", + "(ILjava/lang/String;)V", accessibilityObjectId, value); + } + void notifyQtAndroidPluginRunning(bool running) { QJniObject::callStaticMethod(m_applicationClass, "notifyQtAndroidPluginRunning","(Z)V", running); diff --git a/src/plugins/platforms/android/androidjnimain.h b/src/plugins/platforms/android/androidjnimain.h index 5ebf8c5048..d8e40f8b7f 100644 --- a/src/plugins/platforms/android/androidjnimain.h +++ b/src/plugins/platforms/android/androidjnimain.h @@ -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(); diff --git a/src/plugins/platforms/android/qandroidplatformaccessibility.cpp b/src/plugins/platforms/android/qandroidplatformaccessibility.cpp index 75333d3f08..706d1bb4fa 100644 --- a/src/plugins/platforms/android/qandroidplatformaccessibility.cpp +++ b/src/plugins/platforms/android/qandroidplatformaccessibility.cpp @@ -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()); } }