| 
							- /*
 -   ==============================================================================
 - 
 -    This file is part of the JUCE library.
 -    Copyright (c) 2022 - Raw Material Software Limited
 - 
 -    JUCE is an open source library subject to commercial or open-source
 -    licensing.
 - 
 -    By using JUCE, you agree to the terms of both the JUCE 7 End-User License
 -    Agreement and JUCE Privacy Policy.
 - 
 -    End User License Agreement: www.juce.com/juce-7-licence
 -    Privacy Policy: www.juce.com/juce-privacy-policy
 - 
 -    Or: You may also use this code under the terms of the GPL v3 (see
 -    www.gnu.org/licenses).
 - 
 -    JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
 -    EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
 -    DISCLAIMED.
 - 
 -   ==============================================================================
 - */
 - 
 - namespace juce
 - {
 - 
 - #define JUCE_NATIVE_ACCESSIBILITY_INCLUDED 1
 - 
 - #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
 -  METHOD (setSource,                "setSource",                "(Landroid/view/View;I)V") \
 -  METHOD (addChild,                 "addChild",                 "(Landroid/view/View;I)V") \
 -  METHOD (setParent,                "setParent",                "(Landroid/view/View;)V") \
 -  METHOD (setVirtualParent,         "setParent",                "(Landroid/view/View;I)V") \
 -  METHOD (setBoundsInScreen,        "setBoundsInScreen",        "(Landroid/graphics/Rect;)V") \
 -  METHOD (setBoundsInParent,        "setBoundsInParent",        "(Landroid/graphics/Rect;)V") \
 -  METHOD (setPackageName,           "setPackageName",           "(Ljava/lang/CharSequence;)V") \
 -  METHOD (setClassName,             "setClassName",             "(Ljava/lang/CharSequence;)V") \
 -  METHOD (setContentDescription,    "setContentDescription",    "(Ljava/lang/CharSequence;)V") \
 -  METHOD (setCheckable,             "setCheckable",             "(Z)V") \
 -  METHOD (setChecked,               "setChecked",               "(Z)V") \
 -  METHOD (setClickable,             "setClickable",             "(Z)V") \
 -  METHOD (setEnabled,               "setEnabled",               "(Z)V") \
 -  METHOD (setFocusable,             "setFocusable",             "(Z)V") \
 -  METHOD (setFocused,               "setFocused",               "(Z)V") \
 -  METHOD (setPassword,              "setPassword",              "(Z)V") \
 -  METHOD (setSelected,              "setSelected",              "(Z)V") \
 -  METHOD (setVisibleToUser,         "setVisibleToUser",         "(Z)V") \
 -  METHOD (setAccessibilityFocused,  "setAccessibilityFocused",  "(Z)V") \
 -  METHOD (setText,                  "setText",                  "(Ljava/lang/CharSequence;)V") \
 -  METHOD (setMovementGranularities, "setMovementGranularities", "(I)V") \
 -  METHOD (addAction,                "addAction",                "(I)V") \
 - 
 -  DECLARE_JNI_CLASS (AndroidAccessibilityNodeInfo, "android/view/accessibility/AccessibilityNodeInfo")
 - #undef JNI_CLASS_MEMBERS
 - 
 - #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
 -  METHOD (setCollectionInfo, "setCollectionInfo", "(Landroid/view/accessibility/AccessibilityNodeInfo$CollectionInfo;)V") \
 -  METHOD (setCollectionItemInfo, "setCollectionItemInfo", "(Landroid/view/accessibility/AccessibilityNodeInfo$CollectionItemInfo;)V")
 - 
 -  DECLARE_JNI_CLASS_WITH_MIN_SDK (AndroidAccessibilityNodeInfo19, "android/view/accessibility/AccessibilityNodeInfo", 19)
 - #undef JNI_CLASS_MEMBERS
 - 
 - #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
 -  STATICMETHOD (obtain, "obtain", "(IIZ)Landroid/view/accessibility/AccessibilityNodeInfo$CollectionInfo;")
 - 
 -  DECLARE_JNI_CLASS_WITH_MIN_SDK (AndroidAccessibilityNodeInfoCollectionInfo, "android/view/accessibility/AccessibilityNodeInfo$CollectionInfo", 19)
 - #undef JNI_CLASS_MEMBERS
 - 
 - #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
 -  STATICMETHOD (obtain, "obtain", "(IIIIZ)Landroid/view/accessibility/AccessibilityNodeInfo$CollectionItemInfo;")
 - 
 -  DECLARE_JNI_CLASS_WITH_MIN_SDK (AndroidAccessibilityNodeInfoCollectionItemInfo, "android/view/accessibility/AccessibilityNodeInfo$CollectionItemInfo", 19)
 - #undef JNI_CLASS_MEMBERS
 - 
 - #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
 -  STATICMETHOD (obtain, "obtain", "(I)Landroid/view/accessibility/AccessibilityEvent;") \
 -  METHOD (setPackageName, "setPackageName", "(Ljava/lang/CharSequence;)V") \
 -  METHOD (setSource, "setSource","(Landroid/view/View;I)V") \
 -  METHOD (setAction, "setAction", "(I)V") \
 -  METHOD (setFromIndex, "setFromIndex", "(I)V") \
 -  METHOD (setToIndex, "setToIndex", "(I)V") \
 - 
 -  DECLARE_JNI_CLASS (AndroidAccessibilityEvent, "android/view/accessibility/AccessibilityEvent")
 - #undef JNI_CLASS_MEMBERS
 - 
 - #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
 -  METHOD (isEnabled, "isEnabled", "()Z") \
 - 
 -  DECLARE_JNI_CLASS (AndroidAccessibilityManager, "android/view/accessibility/AccessibilityManager")
 - #undef JNI_CLASS_MEMBERS
 - 
 - namespace
 - {
 -     constexpr int HOST_VIEW_ID = -1;
 - 
 -     constexpr int TYPE_VIEW_CLICKED                                = 0x00000001,
 -                   TYPE_VIEW_SELECTED                               = 0x00000004,
 -                   TYPE_VIEW_ACCESSIBILITY_FOCUSED                  = 0x00008000,
 -                   TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED            = 0x00010000,
 -                   TYPE_WINDOW_CONTENT_CHANGED                      = 0x00000800,
 -                   TYPE_VIEW_TEXT_SELECTION_CHANGED                 = 0x00002000,
 -                   TYPE_VIEW_TEXT_CHANGED                           = 0x00000010,
 -                   TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY = 0x00020000;
 - 
 -     constexpr int CONTENT_CHANGE_TYPE_SUBTREE             = 0x00000001,
 -                   CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION = 0x00000004;
 - 
 -     constexpr int ACTION_ACCESSIBILITY_FOCUS              = 0x00000040,
 -                   ACTION_CLEAR_ACCESSIBILITY_FOCUS        = 0x00000080,
 -                   ACTION_CLEAR_FOCUS                      = 0x00000002,
 -                   ACTION_CLEAR_SELECTION                  = 0x00000008,
 -                   ACTION_CLICK                            = 0x00000010,
 -                   ACTION_COLLAPSE                         = 0x00080000,
 -                   ACTION_EXPAND                           = 0x00040000,
 -                   ACTION_FOCUS                            = 0x00000001,
 -                   ACTION_NEXT_AT_MOVEMENT_GRANULARITY     = 0x00000100,
 -                   ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY = 0x00000200,
 -                   ACTION_SCROLL_BACKWARD                  = 0x00002000,
 -                   ACTION_SCROLL_FORWARD                   = 0x00001000,
 -                   ACTION_SELECT                           = 0x00000004,
 -                   ACTION_SET_SELECTION                    = 0x00020000,
 -                   ACTION_SET_TEXT                         = 0x00200000;
 - 
 -     constexpr int MOVEMENT_GRANULARITY_CHARACTER = 0x00000001,
 -                   MOVEMENT_GRANULARITY_LINE      = 0x00000004,
 -                   MOVEMENT_GRANULARITY_PAGE      = 0x00000010,
 -                   MOVEMENT_GRANULARITY_PARAGRAPH = 0x00000008,
 -                   MOVEMENT_GRANULARITY_WORD      = 0x00000002,
 -                   ALL_GRANULARITIES = MOVEMENT_GRANULARITY_CHARACTER
 -                                     | MOVEMENT_GRANULARITY_LINE
 -                                     | MOVEMENT_GRANULARITY_PAGE
 -                                     | MOVEMENT_GRANULARITY_PARAGRAPH
 -                                     | MOVEMENT_GRANULARITY_WORD;
 - 
 -     constexpr int ACCESSIBILITY_LIVE_REGION_POLITE = 0x00000001;
 - }
 - 
 - static jmethodID nodeInfoSetEditable                     = nullptr;
 - static jmethodID nodeInfoSetTextSelection                = nullptr;
 - static jmethodID nodeInfoSetLiveRegion                   = nullptr;
 - static jmethodID accessibilityEventSetContentChangeTypes = nullptr;
 - 
 - static void loadSDKDependentMethods()
 - {
 -     static bool hasChecked = false;
 - 
 -     if (! hasChecked)
 -     {
 -         hasChecked = true;
 - 
 -         auto* env = getEnv();
 -         const auto sdkVersion = getAndroidSDKVersion();
 - 
 -         if (sdkVersion >= 18)
 -         {
 -             nodeInfoSetEditable      = env->GetMethodID (AndroidAccessibilityNodeInfo, "setEditable",      "(Z)V");
 -             nodeInfoSetTextSelection = env->GetMethodID (AndroidAccessibilityNodeInfo, "setTextSelection", "(II)V");
 -         }
 - 
 -         if (sdkVersion >= 19)
 -         {
 -             nodeInfoSetLiveRegion                   = env->GetMethodID (AndroidAccessibilityNodeInfo, "setLiveRegion",         "(I)V");
 -             accessibilityEventSetContentChangeTypes = env->GetMethodID (AndroidAccessibilityEvent,    "setContentChangeTypes", "(I)V");
 -         }
 -     }
 - }
 - 
 - static constexpr auto getClassName (AccessibilityRole role)
 - {
 -     switch (role)
 -     {
 -         case AccessibilityRole::editableText:  return "android.widget.EditText";
 -         case AccessibilityRole::toggleButton:  return "android.widget.CheckBox";
 -         case AccessibilityRole::radioButton:   return "android.widget.RadioButton";
 -         case AccessibilityRole::image:         return "android.widget.ImageView";
 -         case AccessibilityRole::popupMenu:     return "android.widget.PopupMenu";
 -         case AccessibilityRole::comboBox:      return "android.widget.Spinner";
 -         case AccessibilityRole::tree:          return "android.widget.ExpandableListView";
 -         case AccessibilityRole::progressBar:   return "android.widget.ProgressBar";
 - 
 -         case AccessibilityRole::scrollBar:
 -         case AccessibilityRole::slider:        return "android.widget.SeekBar";
 - 
 -         case AccessibilityRole::hyperlink:
 -         case AccessibilityRole::button:        return "android.widget.Button";
 - 
 -         case AccessibilityRole::label:
 -         case AccessibilityRole::staticText:    return "android.widget.TextView";
 - 
 -         case AccessibilityRole::tooltip:
 -         case AccessibilityRole::splashScreen:
 -         case AccessibilityRole::dialogWindow:  return "android.widget.PopupWindow";
 - 
 -         // If we don't supply a custom class type, then TalkBack will use the node's CollectionInfo
 -         // to make a sensible decision about how to describe the container
 -         case AccessibilityRole::list:
 -         case AccessibilityRole::table:
 - 
 -         case AccessibilityRole::column:
 -         case AccessibilityRole::row:
 -         case AccessibilityRole::cell:
 -         case AccessibilityRole::menuItem:
 -         case AccessibilityRole::menuBar:
 -         case AccessibilityRole::listItem:
 -         case AccessibilityRole::treeItem:
 -         case AccessibilityRole::window:
 -         case AccessibilityRole::tableHeader:
 -         case AccessibilityRole::unspecified:
 -         case AccessibilityRole::group:
 -         case AccessibilityRole::ignored:       break;
 -     }
 - 
 -     return "android.view.View";
 - }
 - 
 - static jobject getSourceView (const AccessibilityHandler& handler)
 - {
 -     if (auto* peer = handler.getComponent().getPeer())
 -         return (jobject) peer->getNativeHandle();
 - 
 -     return nullptr;
 - }
 - 
 - static jobject makeAndroidRect (Rectangle<int> r)
 - {
 -     return getEnv()->NewObject (AndroidRect,
 -                                 AndroidRect.constructor,
 -                                 r.getX(),
 -                                 r.getY(),
 -                                 r.getRight(),
 -                                 r.getBottom());
 - }
 - 
 - static jobject makeAndroidPoint (Point<int> p)
 - {
 -     return getEnv()->NewObject (AndroidPoint,
 -                                 AndroidPoint.create,
 -                                 p.getX(),
 -                                 p.getY());
 - }
 - 
 - //==============================================================================
 - class AccessibilityNativeHandle
 - {
 - public:
 -     static AccessibilityHandler* getAccessibilityHandlerForVirtualViewId (int virtualViewId)
 -     {
 -         auto iter = virtualViewIdMap.find (virtualViewId);
 - 
 -         if (iter != virtualViewIdMap.end())
 -             return iter->second;
 - 
 -         return nullptr;
 -     }
 - 
 -     explicit AccessibilityNativeHandle (AccessibilityHandler& h)
 -         : accessibilityHandler (h),
 -           virtualViewId (getVirtualViewIdForHandler (accessibilityHandler))
 -     {
 -         loadSDKDependentMethods();
 - 
 -         if (virtualViewId != HOST_VIEW_ID)
 -             virtualViewIdMap[virtualViewId] = &accessibilityHandler;
 -     }
 - 
 -     ~AccessibilityNativeHandle()
 -     {
 -         if (virtualViewId != HOST_VIEW_ID)
 -             virtualViewIdMap.erase (virtualViewId);
 - 
 -         if (nativeChildViewId.has_value())
 -             virtualViewIdMap.erase (*nativeChildViewId);
 -     }
 - 
 -     int getVirtualViewId() const noexcept  { return virtualViewId; }
 - 
 -     jobject getNativeView (int viewId) const
 -     {
 -         if (nativeChildViewId == viewId)
 -             return static_cast<jobject> (AccessibilityHandler::getNativeChildForComponent (accessibilityHandler.getComponent()));
 - 
 -         return nullptr;
 -     }
 - 
 -     void populateNodeInfo (jobject info, int infoVirtualViewId)
 -     {
 -         if (   AccessibilityHandler::getNativeChildForComponent (accessibilityHandler.getComponent()) != nullptr
 -             && ! nativeChildViewId.has_value())
 -         {
 -             nativeChildViewId.emplace (getNextVirtualViewId());
 -             virtualViewIdMap[*nativeChildViewId] = &accessibilityHandler;
 -         }
 - 
 -         const ScopedValueSetter<bool> svs (inPopulateNodeInfo, true);
 - 
 -         const auto sourceView = getSourceView (accessibilityHandler);
 - 
 -         if (sourceView == nullptr)
 -             return;
 - 
 -         auto* env = getEnv();
 -         auto appContext = getAppContext();
 - 
 -         if (appContext.get() == nullptr)
 -             return;
 - 
 -         {
 -             for (auto* child : accessibilityHandler.getChildren())
 -                 env->CallVoidMethod (info, AndroidAccessibilityNodeInfo.addChild,
 -                                      sourceView, child->getNativeImplementation()->getVirtualViewId());
 - 
 -             if (nativeChildViewId.has_value() && nativeChildViewId != infoVirtualViewId)
 -                 env->CallVoidMethod (info, AndroidAccessibilityNodeInfo.addChild,
 -                                      sourceView, *nativeChildViewId);
 - 
 -             if (auto* parent = accessibilityHandler.getParent())
 -                 env->CallVoidMethod (info, AndroidAccessibilityNodeInfo.setVirtualParent,
 -                                      sourceView, parent->getNativeImplementation()->getVirtualViewId());
 -         }
 - 
 -         {
 -             const auto scale = Desktop::getInstance().getDisplays().getPrimaryDisplay()->scale;
 - 
 -             LocalRef<jobject> screenBounds (makeAndroidRect (accessibilityHandler.getComponent().getScreenBounds() * scale));
 - 
 -             env->CallVoidMethod (info, AndroidAccessibilityNodeInfo.setBoundsInScreen, screenBounds.get());
 - 
 -             LocalRef<jobject> boundsInParent (makeAndroidRect (accessibilityHandler.getComponent().getBoundsInParent() * scale));
 - 
 -             env->CallVoidMethod (info, AndroidAccessibilityNodeInfo.setBoundsInParent, boundsInParent.get());
 -         }
 - 
 -         const auto state = accessibilityHandler.getCurrentState();
 - 
 -         env->CallVoidMethod (info,
 -                              AndroidAccessibilityNodeInfo.setEnabled,
 -                              ! state.isIgnored());
 -         env->CallVoidMethod (info,
 -                              AndroidAccessibilityNodeInfo.setVisibleToUser,
 -                              true);
 -         env->CallVoidMethod (info,
 -                              AndroidAccessibilityNodeInfo.setPackageName,
 -                              env->CallObjectMethod (appContext.get(),
 -                                                     AndroidContext.getPackageName));
 -         env->CallVoidMethod (info,
 -                              AndroidAccessibilityNodeInfo.setSource,
 -                              sourceView,
 -                              virtualViewId);
 -         env->CallVoidMethod (info,
 -                              AndroidAccessibilityNodeInfo.setClassName,
 -                              javaString (getClassName (accessibilityHandler.getRole())).get());
 -         env->CallVoidMethod (info,
 -                              AndroidAccessibilityNodeInfo.setContentDescription,
 -                              getDescriptionString().get());
 - 
 -         if (state.isFocusable())
 -         {
 -             env->CallVoidMethod (info, AndroidAccessibilityNodeInfo.setFocusable, true);
 - 
 -             const auto& component = accessibilityHandler.getComponent();
 - 
 -             if (component.getWantsKeyboardFocus())
 -             {
 -                 const auto hasKeyboardFocus = component.hasKeyboardFocus (false);
 - 
 -                 env->CallVoidMethod (info,
 -                                      AndroidAccessibilityNodeInfo.setFocused,
 -                                      hasKeyboardFocus);
 -                 env->CallVoidMethod (info,
 -                                      AndroidAccessibilityNodeInfo.addAction,
 -                                      hasKeyboardFocus ? ACTION_CLEAR_FOCUS : ACTION_FOCUS);
 -             }
 - 
 -             const auto isAccessibleFocused = accessibilityHandler.hasFocus (false);
 - 
 -             env->CallVoidMethod (info,
 -                                  AndroidAccessibilityNodeInfo.setAccessibilityFocused,
 -                                  isAccessibleFocused);
 - 
 -             env->CallVoidMethod (info,
 -                                  AndroidAccessibilityNodeInfo.addAction,
 -                                  isAccessibleFocused ? ACTION_CLEAR_ACCESSIBILITY_FOCUS
 -                                                      : ACTION_ACCESSIBILITY_FOCUS);
 -         }
 - 
 -         if (state.isCheckable())
 -         {
 -             env->CallVoidMethod (info,
 -                                  AndroidAccessibilityNodeInfo.setCheckable,
 -                                  true);
 -             env->CallVoidMethod (info,
 -                                  AndroidAccessibilityNodeInfo.setChecked,
 -                                  state.isChecked());
 -         }
 - 
 -         if (state.isSelectable() || state.isMultiSelectable())
 -         {
 -             const auto isSelected = state.isSelected();
 - 
 -             env->CallVoidMethod (info,
 -                                  AndroidAccessibilityNodeInfo.setSelected,
 -                                  isSelected);
 -             env->CallVoidMethod (info,
 -                                  AndroidAccessibilityNodeInfo.addAction,
 -                                  isSelected ? ACTION_CLEAR_SELECTION : ACTION_SELECT);
 -         }
 - 
 -         if ((accessibilityHandler.getCurrentState().isCheckable() && accessibilityHandler.getActions().contains (AccessibilityActionType::toggle))
 -             || accessibilityHandler.getActions().contains (AccessibilityActionType::press))
 -         {
 -             env->CallVoidMethod (info,
 -                                  AndroidAccessibilityNodeInfo.setClickable,
 -                                  true);
 -             env->CallVoidMethod (info,
 -                                  AndroidAccessibilityNodeInfo.addAction,
 -                                  ACTION_CLICK);
 -         }
 - 
 -         if (accessibilityHandler.getActions().contains (AccessibilityActionType::showMenu)
 -             && state.isExpandable())
 -         {
 -             env->CallVoidMethod (info,
 -                                  AndroidAccessibilityNodeInfo.addAction,
 -                                  state.isExpanded() ? ACTION_COLLAPSE : ACTION_EXPAND);
 -         }
 - 
 -         if (auto* textInterface = accessibilityHandler.getTextInterface())
 -         {
 -             env->CallVoidMethod (info,
 -                                  AndroidAccessibilityNodeInfo.setText,
 -                                  javaString (textInterface->getAllText()).get());
 - 
 -             const auto isReadOnly = textInterface->isReadOnly();
 - 
 -             env->CallVoidMethod (info,
 -                                  AndroidAccessibilityNodeInfo.setPassword,
 -                                  textInterface->isDisplayingProtectedText());
 - 
 -             if (nodeInfoSetEditable != nullptr)
 -                 env->CallVoidMethod (info, nodeInfoSetEditable, ! isReadOnly);
 - 
 -             const auto selection = textInterface->getSelection();
 - 
 -             if (nodeInfoSetTextSelection != nullptr && ! selection.isEmpty())
 -                 env->CallVoidMethod (info,
 -                                      nodeInfoSetTextSelection,
 -                                      selection.getStart(), selection.getEnd());
 - 
 -             if (nodeInfoSetLiveRegion != nullptr && accessibilityHandler.hasFocus (false))
 -                 env->CallVoidMethod (info,
 -                                      nodeInfoSetLiveRegion,
 -                                      ACCESSIBILITY_LIVE_REGION_POLITE);
 - 
 -             env->CallVoidMethod (info,
 -                                  AndroidAccessibilityNodeInfo.setMovementGranularities,
 -                                  ALL_GRANULARITIES);
 - 
 -             env->CallVoidMethod (info,
 -                                  AndroidAccessibilityNodeInfo.addAction,
 -                                  ACTION_NEXT_AT_MOVEMENT_GRANULARITY);
 -             env->CallVoidMethod (info,
 -                                  AndroidAccessibilityNodeInfo.addAction,
 -                                  ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY);
 -             env->CallVoidMethod (info,
 -                                  AndroidAccessibilityNodeInfo.addAction,
 -                                  ACTION_SET_SELECTION);
 - 
 -             if (! isReadOnly)
 -                 env->CallVoidMethod (info, AndroidAccessibilityNodeInfo.addAction, ACTION_SET_TEXT);
 -         }
 - 
 -         if (auto* valueInterface = accessibilityHandler.getValueInterface())
 -         {
 -             if (! valueInterface->isReadOnly())
 -             {
 -                 const auto range = valueInterface->getRange();
 - 
 -                 if (range.isValid())
 -                 {
 -                     env->CallVoidMethod (info,
 -                                          AndroidAccessibilityNodeInfo.addAction,
 -                                          ACTION_SCROLL_FORWARD);
 -                     env->CallVoidMethod (info,
 -                                          AndroidAccessibilityNodeInfo.addAction,
 -                                          ACTION_SCROLL_BACKWARD);
 -                 }
 -             }
 -         }
 - 
 -         if (getAndroidSDKVersion() >= 19)
 -         {
 -             if (auto* tableInterface = accessibilityHandler.getTableInterface())
 -             {
 -                 const auto rows    = tableInterface->getNumRows();
 -                 const auto columns = tableInterface->getNumColumns();
 -                 const LocalRef<jobject> collectionInfo { env->CallStaticObjectMethod (AndroidAccessibilityNodeInfoCollectionInfo,
 -                                                                                       AndroidAccessibilityNodeInfoCollectionInfo.obtain,
 -                                                                                       (jint) rows,
 -                                                                                       (jint) columns,
 -                                                                                       (jboolean) false) };
 -                 env->CallVoidMethod (info, AndroidAccessibilityNodeInfo19.setCollectionInfo, collectionInfo.get());
 -             }
 - 
 -             if (auto* enclosingTableHandler = detail::AccessibilityHelpers::getEnclosingHandlerWithInterface (&accessibilityHandler, &AccessibilityHandler::getTableInterface))
 -             {
 -                 auto* interface = enclosingTableHandler->getTableInterface();
 -                 jassert (interface != nullptr);
 -                 const auto rowSpan    = interface->getRowSpan    (accessibilityHandler);
 -                 const auto columnSpan = interface->getColumnSpan (accessibilityHandler);
 - 
 -                 enum class IsHeader { no, yes };
 - 
 -                 const auto addCellInfo = [env, &info] (AccessibilityTableInterface::Span rows, AccessibilityTableInterface::Span columns, IsHeader header)
 -                 {
 -                     const LocalRef<jobject> collectionItemInfo { env->CallStaticObjectMethod (AndroidAccessibilityNodeInfoCollectionItemInfo,
 -                                                                                               AndroidAccessibilityNodeInfoCollectionItemInfo.obtain,
 -                                                                                               (jint) rows.begin,
 -                                                                                               (jint) rows.num,
 -                                                                                               (jint) columns.begin,
 -                                                                                               (jint) columns.num,
 -                                                                                               (jboolean) (header == IsHeader::yes)) };
 -                     env->CallVoidMethod (info, AndroidAccessibilityNodeInfo19.setCollectionItemInfo, collectionItemInfo.get());
 -                 };
 - 
 -                 if (rowSpan.hasValue() && columnSpan.hasValue())
 -                 {
 -                     addCellInfo (*rowSpan, *columnSpan, IsHeader::no);
 -                 }
 -                 else
 -                 {
 -                     if (auto* tableHeader = interface->getHeaderHandler())
 -                     {
 -                         if (accessibilityHandler.getParent() == tableHeader)
 -                         {
 -                             const auto children = tableHeader->getChildren();
 -                             const auto column = std::distance (children.cbegin(), std::find (children.cbegin(), children.cend(), &accessibilityHandler));
 - 
 -                             // Talkback will only treat a row as a column header if its row index is zero
 -                             // https://github.com/google/talkback/blob/acd0bc7631a3dfbcf183789c7557596a45319e1f/utils/src/main/java/CollectionState.java#L853
 -                             addCellInfo ({ 0, 1 }, { (int) column, 1 }, IsHeader::yes);
 -                         }
 -                     }
 -                 }
 -             }
 -         }
 -     }
 - 
 -     bool performAction (int action, jobject arguments)
 -     {
 -         switch (action)
 -         {
 -             case ACTION_ACCESSIBILITY_FOCUS:
 -             {
 -                 const WeakReference<Component> safeComponent (&accessibilityHandler.getComponent());
 - 
 -                 accessibilityHandler.getActions().invoke (AccessibilityActionType::focus);
 - 
 -                 if (safeComponent != nullptr)
 -                     accessibilityHandler.grabFocus();
 - 
 -                 return true;
 -             }
 - 
 -             case ACTION_CLEAR_ACCESSIBILITY_FOCUS:
 -             {
 -                 accessibilityHandler.giveAwayFocus();
 -                 return true;
 -             }
 - 
 -             case ACTION_FOCUS:
 -             case ACTION_CLEAR_FOCUS:
 -             {
 -                 auto& component = accessibilityHandler.getComponent();
 - 
 -                 if (component.getWantsKeyboardFocus())
 -                 {
 -                     const auto hasFocus = component.hasKeyboardFocus (false);
 - 
 -                     if (hasFocus && action == ACTION_CLEAR_FOCUS)
 -                         component.giveAwayKeyboardFocus();
 -                     else if (! hasFocus && action == ACTION_FOCUS)
 -                         component.grabKeyboardFocus();
 - 
 -                     return true;
 -                 }
 - 
 -                 break;
 -             }
 - 
 -             case ACTION_CLICK:
 -             {
 -                 // Invoking the action may delete this handler
 -                 const WeakReference<AccessibilityNativeHandle> savedHandle { this };
 - 
 -                 if ((accessibilityHandler.getCurrentState().isCheckable() && accessibilityHandler.getActions().invoke (AccessibilityActionType::toggle))
 -                     || accessibilityHandler.getActions().invoke (AccessibilityActionType::press))
 -                 {
 -                     if (savedHandle != nullptr)
 -                         sendAccessibilityEventImpl (accessibilityHandler, TYPE_VIEW_CLICKED, 0);
 - 
 -                     return true;
 -                 }
 - 
 -                 break;
 -             }
 - 
 -             case ACTION_SELECT:
 -             case ACTION_CLEAR_SELECTION:
 -             {
 -                 const auto state = accessibilityHandler.getCurrentState();
 - 
 -                 if (state.isSelectable() || state.isMultiSelectable())
 -                 {
 -                     const auto isSelected = state.isSelected();
 - 
 -                     if ((isSelected && action == ACTION_CLEAR_SELECTION)
 -                         || (! isSelected && action == ACTION_SELECT))
 -                     {
 -                         return accessibilityHandler.getActions().invoke (AccessibilityActionType::toggle);
 -                     }
 - 
 -                 }
 - 
 -                 break;
 -             }
 - 
 -             case ACTION_EXPAND:
 -             case ACTION_COLLAPSE:
 -             {
 -                 const auto state = accessibilityHandler.getCurrentState();
 - 
 -                 if (state.isExpandable())
 -                 {
 -                     const auto isExpanded = state.isExpanded();
 - 
 -                     if ((isExpanded && action == ACTION_COLLAPSE)
 -                         || (! isExpanded && action == ACTION_EXPAND))
 -                     {
 -                         return accessibilityHandler.getActions().invoke (AccessibilityActionType::showMenu);
 -                     }
 -                 }
 - 
 -                 break;
 -             }
 - 
 -             case ACTION_NEXT_AT_MOVEMENT_GRANULARITY:      return moveCursor (arguments, true);
 -             case ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY:  return moveCursor (arguments, false);
 - 
 -             case ACTION_SET_SELECTION:
 -             {
 -                 if (auto* textInterface = accessibilityHandler.getTextInterface())
 -                 {
 -                     auto* env = getEnv();
 - 
 -                     const auto selection = [&]() -> Range<int>
 -                     {
 -                         const auto selectionStartKey = javaString ("ACTION_ARGUMENT_SELECTION_START_INT");
 -                         const auto selectionEndKey   = javaString ("ACTION_ARGUMENT_SELECTION_END_INT");
 - 
 -                         const auto hasKey = [&env, &arguments] (const auto& key)
 -                         {
 -                             return env->CallBooleanMethod (arguments, AndroidBundle.containsKey, key.get());
 -                         };
 - 
 -                         if (hasKey (selectionStartKey) && hasKey (selectionEndKey))
 -                         {
 -                             const auto getKey = [&env, &arguments] (const auto& key)
 -                             {
 -                                 return env->CallIntMethod (arguments, AndroidBundle.getInt, key.get());
 -                             };
 - 
 -                             const auto start = getKey (selectionStartKey);
 -                             const auto end   = getKey (selectionEndKey);
 - 
 -                             return Range<int>::between (start, end);
 -                         }
 - 
 -                         return {};
 -                     }();
 - 
 -                     textInterface->setSelection (selection);
 - 
 -                     return true;
 -                 }
 - 
 -                 break;
 -             }
 - 
 -             case ACTION_SET_TEXT:
 -             {
 -                 if (auto* textInterface = accessibilityHandler.getTextInterface())
 -                 {
 -                     if (! textInterface->isReadOnly())
 -                     {
 -                         const auto charSequenceKey = javaString ("ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE");
 - 
 -                         auto* env = getEnv();
 - 
 -                         const auto text = [&]() -> String
 -                         {
 -                             if (env->CallBooleanMethod (arguments, AndroidBundle.containsKey, charSequenceKey.get()))
 -                             {
 -                                 LocalRef<jobject> charSequence (env->CallObjectMethod (arguments,
 -                                                                                        AndroidBundle.getCharSequence,
 -                                                                                        charSequenceKey.get()));
 -                                 LocalRef<jstring> textStringRef ((jstring) env->CallObjectMethod (charSequence,
 -                                                                                                   JavaCharSequence.toString));
 - 
 -                                 return juceString (textStringRef.get());
 -                             }
 - 
 -                             return {};
 -                         }();
 - 
 -                         textInterface->setText (text);
 -                     }
 -                 }
 - 
 -                 break;
 -             }
 - 
 -             case ACTION_SCROLL_BACKWARD:
 -             case ACTION_SCROLL_FORWARD:
 -             {
 -                 if (auto* valueInterface = accessibilityHandler.getValueInterface())
 -                 {
 -                     if (! valueInterface->isReadOnly())
 -                     {
 -                         const auto range = valueInterface->getRange();
 - 
 -                         if (range.isValid())
 -                         {
 -                             const auto interval = action == ACTION_SCROLL_BACKWARD ? -range.getInterval()
 -                                                                                    : range.getInterval();
 -                             valueInterface->setValue (jlimit (range.getMinimumValue(),
 -                                                               range.getMaximumValue(),
 -                                                               valueInterface->getCurrentValue() + interval));
 - 
 -                             // required for Android to announce the new value
 -                             sendAccessibilityEventImpl (accessibilityHandler, TYPE_VIEW_SELECTED, 0);
 -                             return true;
 -                         }
 -                     }
 -                 }
 - 
 -                 break;
 -             }
 -         }
 - 
 -         return false;
 -     }
 - 
 -     bool isInPopulateNodeInfo() const noexcept  { return inPopulateNodeInfo; }
 - 
 -     static bool areAnyAccessibilityClientsActive()
 -     {
 -         auto* env = getEnv();
 -         auto appContext = getAppContext();
 - 
 -         if (appContext.get() != nullptr)
 -         {
 -             LocalRef<jobject> accessibilityManager (env->CallObjectMethod (appContext.get(), AndroidContext.getSystemService,
 -                                                                            javaString ("accessibility").get()));
 - 
 -             if (accessibilityManager != nullptr)
 -                 return env->CallBooleanMethod (accessibilityManager.get(), AndroidAccessibilityManager.isEnabled);
 -         }
 - 
 -         return false;
 -     }
 - 
 -     template <typename ModificationCallback>
 -     static void sendAccessibilityEventExtendedImpl (const AccessibilityHandler& handler,
 -                                                     int eventType,
 -                                                     ModificationCallback&& modificationCallback)
 -     {
 -         if (! areAnyAccessibilityClientsActive())
 -             return;
 - 
 -         if (const auto sourceView = getSourceView (handler))
 -         {
 -             const auto* nativeImpl = handler.getNativeImplementation();
 - 
 -             if (nativeImpl == nullptr || nativeImpl->isInPopulateNodeInfo())
 -                 return;
 - 
 -             auto* env = getEnv();
 -             auto appContext = getAppContext();
 - 
 -             if (appContext.get() == nullptr)
 -                 return;
 - 
 -             LocalRef<jobject> event (env->CallStaticObjectMethod (AndroidAccessibilityEvent,
 -                                                                   AndroidAccessibilityEvent.obtain,
 -                                                                   eventType));
 - 
 -             env->CallVoidMethod (event,
 -                                  AndroidAccessibilityEvent.setPackageName,
 -                                  env->CallObjectMethod (appContext.get(),
 -                                                         AndroidContext.getPackageName));
 - 
 -             env->CallVoidMethod (event,
 -                                  AndroidAccessibilityEvent.setSource,
 -                                  sourceView,
 -                                  nativeImpl->getVirtualViewId());
 - 
 -             modificationCallback (event);
 - 
 -             env->CallBooleanMethod (sourceView,
 -                                     AndroidViewGroup.requestSendAccessibilityEvent,
 -                                     sourceView,
 -                                     event.get());
 -         }
 -     }
 - 
 -     static void sendAccessibilityEventImpl (const AccessibilityHandler& handler, int eventType, int contentChangeTypes)
 -     {
 -         sendAccessibilityEventExtendedImpl (handler, eventType, [contentChangeTypes] (auto event)
 -         {
 -             if (contentChangeTypes != 0 && accessibilityEventSetContentChangeTypes != nullptr)
 -                 getEnv()->CallVoidMethod (event,
 -                                           accessibilityEventSetContentChangeTypes,
 -                                           contentChangeTypes);
 -         });
 -     }
 - 
 - private:
 -     static std::unordered_map<int, AccessibilityHandler*> virtualViewIdMap;
 - 
 -     static int getNextVirtualViewId()
 -     {
 -         static int counter = 0;
 - 
 -         return counter++;
 -     }
 - 
 -     static int getVirtualViewIdForHandler (const AccessibilityHandler& handler)
 -     {
 -         if (handler.getComponent().isOnDesktop())
 -             return HOST_VIEW_ID;
 - 
 -         return getNextVirtualViewId();
 -     }
 - 
 -     LocalRef<jstring> getDescriptionString() const
 -     {
 -         const auto valueString = [this]() -> String
 -         {
 -             if (auto* textInterface = accessibilityHandler.getTextInterface())
 -                 return textInterface->getAllText();
 - 
 -             if (auto* valueInterface = accessibilityHandler.getValueInterface())
 -                 return valueInterface->getCurrentValueAsString();
 - 
 -             return {};
 -         }();
 - 
 -         StringArray strings (accessibilityHandler.getTitle(),
 -                              valueString,
 -                              accessibilityHandler.getDescription(),
 -                              accessibilityHandler.getHelp());
 - 
 -         strings.removeEmptyStrings();
 - 
 -         return javaString (strings.joinIntoString (","));
 -     }
 - 
 -     bool moveCursor (jobject arguments, bool forwards)
 -     {
 -         using ATH = AccessibilityTextHelpers;
 - 
 -         auto* textInterface = accessibilityHandler.getTextInterface();
 - 
 -         if (textInterface == nullptr)
 -             return false;
 - 
 -         const auto granularityKey = javaString ("ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT");
 -         const auto extendSelectionKey = javaString ("ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN");
 - 
 -         auto* env = getEnv();
 - 
 -         const auto boundaryType = [&]
 -         {
 -             const auto granularity = env->CallIntMethod (arguments, AndroidBundle.getInt, granularityKey.get());
 - 
 -             using BoundaryType = ATH::BoundaryType;
 - 
 -             switch (granularity)
 -             {
 -                 case MOVEMENT_GRANULARITY_CHARACTER:  return BoundaryType::character;
 -                 case MOVEMENT_GRANULARITY_WORD:       return BoundaryType::word;
 -                 case MOVEMENT_GRANULARITY_LINE:       return BoundaryType::line;
 -                 case MOVEMENT_GRANULARITY_PARAGRAPH:
 -                 case MOVEMENT_GRANULARITY_PAGE:       return BoundaryType::document;
 -             }
 - 
 -             jassertfalse;
 -             return BoundaryType::character;
 -         }();
 - 
 -         const auto direction = forwards
 -                              ? ATH::Direction::forwards
 -                              : ATH::Direction::backwards;
 - 
 -         const auto extend = env->CallBooleanMethod (arguments, AndroidBundle.getBoolean, extendSelectionKey.get())
 -                           ? ATH::ExtendSelection::yes
 -                           : ATH::ExtendSelection::no;
 - 
 -         const auto oldSelection = textInterface->getSelection();
 -         const auto newSelection = ATH::findNewSelectionRangeAndroid (*textInterface, boundaryType, extend, direction);
 -         textInterface->setSelection (newSelection);
 - 
 -         // Required for Android to read back the text that the cursor moved over
 -         sendAccessibilityEventExtendedImpl (accessibilityHandler, TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY, [&] (auto event)
 -         {
 -             env->CallVoidMethod (event,
 -                                  AndroidAccessibilityEvent.setAction,
 -                                  forwards ? ACTION_NEXT_AT_MOVEMENT_GRANULARITY : ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY);
 - 
 -             env->CallVoidMethod (event,
 -                                  AndroidAccessibilityEvent.setFromIndex,
 -                                  oldSelection.getStart() != newSelection.getStart() ? oldSelection.getStart()
 -                                                                                     : oldSelection.getEnd());
 - 
 -             env->CallVoidMethod (event,
 -                                  AndroidAccessibilityEvent.setToIndex,
 -                                  oldSelection.getStart() != newSelection.getStart() ? newSelection.getStart()
 -                                                                                     : newSelection.getEnd());
 -         });
 - 
 -         return true;
 -     }
 - 
 -     AccessibilityHandler& accessibilityHandler;
 -     const int virtualViewId;
 -     std::optional<int> nativeChildViewId;
 -     bool inPopulateNodeInfo = false;
 - 
 -     //==============================================================================
 -     JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AccessibilityNativeHandle)
 - 
 -     JUCE_DECLARE_WEAK_REFERENCEABLE (AccessibilityNativeHandle)
 - };
 - 
 - std::unordered_map<int, AccessibilityHandler*> AccessibilityNativeHandle::virtualViewIdMap;
 - 
 - class AccessibilityHandler::AccessibilityNativeImpl : public AccessibilityNativeHandle
 - {
 - public:
 -     using AccessibilityNativeHandle::AccessibilityNativeHandle;
 - };
 - 
 - //==============================================================================
 - AccessibilityNativeHandle* AccessibilityHandler::getNativeImplementation() const
 - {
 -     return nativeImpl.get();
 - }
 - 
 - void detail::AccessibilityHelpers::notifyAccessibilityEvent (const AccessibilityHandler& handler,
 -                                                              Event eventType)
 - {
 -     if (eventType == Event::elementCreated
 -         || eventType == Event::elementDestroyed
 -         || eventType == Event::elementMovedOrResized)
 -     {
 -         if (auto* parent = handler.getParent())
 -             AccessibilityNativeHandle::sendAccessibilityEventImpl (*parent, TYPE_WINDOW_CONTENT_CHANGED, CONTENT_CHANGE_TYPE_SUBTREE);
 - 
 -         return;
 -     }
 - 
 -     auto notification = [&handler, eventType]
 -     {
 -         switch (eventType)
 -         {
 -             case Event::focusChanged:
 -                 return handler.hasFocus (false) ? TYPE_VIEW_ACCESSIBILITY_FOCUSED
 -                                                 : TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED;
 - 
 -             case Event::elementCreated:
 -             case Event::elementDestroyed:
 -             case Event::elementMovedOrResized:
 -             case Event::windowOpened:
 -             case Event::windowClosed:
 -                 break;
 -         }
 - 
 -         return 0;
 -     }();
 - 
 -     if (notification != 0)
 -         AccessibilityNativeHandle::sendAccessibilityEventImpl (handler, notification, 0);
 - }
 - 
 - void AccessibilityHandler::notifyAccessibilityEvent (AccessibilityEvent eventType) const
 - {
 -     auto notification = [eventType]
 -     {
 -         switch (eventType)
 -         {
 -             case AccessibilityEvent::textSelectionChanged:  return TYPE_VIEW_TEXT_SELECTION_CHANGED;
 -             case AccessibilityEvent::textChanged:           return TYPE_VIEW_TEXT_CHANGED;
 - 
 -             case AccessibilityEvent::titleChanged:
 -             case AccessibilityEvent::structureChanged:      return TYPE_WINDOW_CONTENT_CHANGED;
 - 
 -             case AccessibilityEvent::rowSelectionChanged:
 -             case AccessibilityEvent::valueChanged:          break;
 -         }
 - 
 -         return 0;
 -     }();
 - 
 -     if (notification == 0)
 -         return;
 - 
 -     const auto contentChangeTypes = [eventType]
 -     {
 -         if (eventType == AccessibilityEvent::titleChanged)      return CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION;
 -         if (eventType == AccessibilityEvent::structureChanged)  return CONTENT_CHANGE_TYPE_SUBTREE;
 - 
 -         return 0;
 -     }();
 - 
 -     AccessibilityNativeHandle::sendAccessibilityEventImpl (*this, notification, contentChangeTypes);
 - }
 - 
 - void AccessibilityHandler::postAnnouncement (const String& announcementString,
 -                                              AnnouncementPriority)
 - {
 -     if (! AccessibilityNativeHandle::areAnyAccessibilityClientsActive())
 -         return;
 - 
 -     const auto rootView = []
 -     {
 -         LocalRef<jobject> activity (getMainActivity());
 - 
 -         if (activity != nullptr)
 -         {
 -             auto* env = getEnv();
 - 
 -             LocalRef<jobject> mainWindow (env->CallObjectMethod (activity.get(), AndroidActivity.getWindow));
 -             LocalRef<jobject> decorView (env->CallObjectMethod (mainWindow.get(), AndroidWindow.getDecorView));
 - 
 -             return LocalRef<jobject> (env->CallObjectMethod (decorView.get(), AndroidView.getRootView));
 -         }
 - 
 -         return LocalRef<jobject>();
 -     }();
 - 
 -     if (rootView != nullptr)
 -         getEnv()->CallVoidMethod (rootView.get(),
 -                                   AndroidView.announceForAccessibility,
 -                                   javaString (announcementString).get());
 - }
 - 
 - } // namespace juce
 
 
  |