chromium/services/accessibility/android/ax_tree_source_android.cc

// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "services/accessibility/android/ax_tree_source_android.h"

#include <memory>
#include <stack>
#include <string>
#include <utility>

#include "base/check.h"
#include "base/containers/adapters.h"
#include "base/dcheck_is_on.h"
#include "base/memory/raw_ptr.h"
#include "services/accessibility/android/accessibility_node_info_data_wrapper.h"
#include "services/accessibility/android/accessibility_window_info_data_wrapper.h"
#include "services/accessibility/android/android_accessibility_util.h"
#include "services/accessibility/android/auto_complete_handler.h"
#include "services/accessibility/android/drawer_layout_handler.h"
#include "services/accessibility/android/pane_title_handler.h"
#include "services/accessibility/android/public/mojom/accessibility_helper.mojom-shared.h"
#include "ui/accessibility/ax_enums.mojom.h"
#include "ui/accessibility/ax_tree_source_checker.h"
#include "ui/gfx/geometry/rect.h"

namespace ax::android {

using AXBooleanProperty = mojom::AccessibilityBooleanProperty;
using AXEventData = mojom::AccessibilityEventData;
using AXEventType = mojom::AccessibilityEventType;
using AXIntProperty = mojom::AccessibilityIntProperty;
using AXIntListProperty = mojom::AccessibilityIntListProperty;
using AXNodeInfoData = mojom::AccessibilityNodeInfoData;
using AXWindowBooleanProperty = mojom::AccessibilityWindowBooleanProperty;
using AXWindowInfoData = mojom::AccessibilityWindowInfoData;
using AXWindowIntListProperty = mojom::AccessibilityWindowIntListProperty;

// TODO(hirokisato): Enable AXTreeAndroidSerializer's |crash_on_error| once
// Android becomes able to send reliable trees.
AXTreeSourceAndroid::AXTreeSourceAndroid(
    Delegate* delegate,
    std::unique_ptr<SerializationDelegate> serialization_delegate,
    aura::Window* window)
    : current_tree_serializer_(
          new AXTreeAndroidSerializer(this, DCHECK_IS_ON())),
      is_notification_(false),
      is_input_method_window_(false),
      window_(window),
      delegate_(delegate),
      serialization_delegate_(std::move(serialization_delegate)) {
  CHECK(serialization_delegate_);
  serialization_delegate_->BindTree(this);
}

AXTreeSourceAndroid::~AXTreeSourceAndroid() {
  Reset();
}

void AXTreeSourceAndroid::NotifyAccessibilityEvent(AXEventData* event_data) {
  root_id_.reset();
  DCHECK(event_data);

  NotifyAccessibilityEventInternal(*event_data);

  // Clear maps in order to prevent invalid access from dead pointers.
  tree_map_.clear();
  parent_map_.clear();
  computed_bounds_.clear();
}

void AXTreeSourceAndroid::NotifyActionResult(const ui::AXActionData& data,
                                             bool result) {
  GetAutomationEventRouter()->DispatchActionResult(data, result);
}

void AXTreeSourceAndroid::NotifyGetTextLocationDataResult(
    const ui::AXActionData& data,
    const std::optional<gfx::Rect>& rect) {
  GetAutomationEventRouter()->DispatchGetTextLocationDataResult(data, rect);
}

bool AXTreeSourceAndroid::UseFullFocusMode() const {
  return delegate_->UseFullFocusMode();
}

void AXTreeSourceAndroid::InvalidateTree() {
  current_tree_serializer_->Reset();
}

bool AXTreeSourceAndroid::IsRootOfNodeTree(int32_t id) const {
  const auto& node_it = tree_map_.find(id);
  if (node_it == tree_map_.end()) {
    return false;
  }

  if (!node_it->second->IsNode()) {
    return false;
  }

  const auto& parent_it = parent_map_.find(id);
  if (parent_it == parent_map_.end()) {
    return true;
  }

  const auto& parent_tree_it = tree_map_.find(parent_it->second);
  CHECK(parent_tree_it != tree_map_.end());
  return !parent_tree_it->second->IsNode();
}

void AXTreeSourceAndroid::SetVirtualNode(
    int32_t parent_id,
    std::unique_ptr<AccessibilityInfoDataWrapper> child) {
  auto* parent_node = GetFromId(parent_id);
  // TODO support any node as a parent, not limiting to a window.
  CHECK(parent_node);
  CHECK(parent_node->GetWindow());

  int32_t node_id = child->GetId();
  tree_map_[node_id] = std::move(child);
  parent_map_[node_id] = parent_node->GetId();
  static_cast<AccessibilityWindowInfoDataWrapper*>(parent_node)
      ->AddVirtualChild(node_id);
}

AccessibilityInfoDataWrapper* AXTreeSourceAndroid::GetFirstImportantAncestor(
    AccessibilityInfoDataWrapper* info_data) const {
  AccessibilityInfoDataWrapper* parent = GetParent(info_data);
  while (parent && parent->IsNode() && !parent->IsImportantInAndroid()) {
    parent = GetParent(parent);
  }
  return parent;
}

AccessibilityInfoDataWrapper*
AXTreeSourceAndroid::GetFirstAccessibilityFocusableAncestor(
    AccessibilityInfoDataWrapper* info_data) const {
  AccessibilityInfoDataWrapper* node = info_data;
  while (node && !node->IsAccessibilityFocusableContainer()) {
    node = GetParent(node);
  }
  return node;
}

bool AXTreeSourceAndroid::GetTreeData(ui::AXTreeData* data) const {
  data->tree_id = ax_tree_id();
  if (android_focused_id_.has_value()) {
    data->focus_id = *android_focused_id_;
  }
  return true;
}

AccessibilityInfoDataWrapper* AXTreeSourceAndroid::GetRoot() const {
  return root_id_.has_value() ? GetFromId(*root_id_) : nullptr;
}

AccessibilityInfoDataWrapper* AXTreeSourceAndroid::GetFromId(int32_t id) const {
  auto it = tree_map_.find(id);
  if (it == tree_map_.end()) {
    return nullptr;
  }
  return it->second.get();
}

AccessibilityInfoDataWrapper* AXTreeSourceAndroid::GetParent(
    AccessibilityInfoDataWrapper* info_data) const {
  if (!info_data) {
    return nullptr;
  }
  auto it = parent_map_.find(info_data->GetId());
  if (it != parent_map_.end()) {
    return GetFromId(it->second);
  }
  return nullptr;
}

void AXTreeSourceAndroid::SerializeNode(AccessibilityInfoDataWrapper* info_data,
                                        ui::AXNodeData* out_data) const {
  if (!info_data) {
    return;
  }

  info_data->Serialize(out_data);

  const auto& itr = hooks_.find(info_data->GetId());
  if (itr != hooks_.end()) {
    itr->second->PostSerializeNode(out_data);
  }
}

void AXTreeSourceAndroid::BuildNodeMap(const AXEventData& event_data) {
  // Prepare the wrapper objects of mojom data from Android.
  CHECK(event_data.window_data);
  root_id_ = event_data.window_data->at(0)->window_id;
  for (const auto& window_ptr : *event_data.window_data) {
    int32_t window_id = window_ptr->window_id;
    int32_t root_node_id = window_ptr->root_node_id;
    AXWindowInfoData* window = window_ptr.get();
    if (root_node_id) {
      parent_map_[root_node_id] = window_id;
    }

    tree_map_[window_id] =
        std::make_unique<AccessibilityWindowInfoDataWrapper>(this, window);

    std::vector<int32_t> children;
    if (GetProperty(window->int_list_properties,
                    AXWindowIntListProperty::CHILD_WINDOW_IDS, &children)) {
      for (const int32_t child : children) {
        DCHECK(child != root_id_);
        parent_map_[child] = window_id;
      }
    }
  }

  for (const auto& node_ptr : event_data.node_data) {
    int32_t node_id = node_ptr->id;
    AXNodeInfoData* node = node_ptr.get();
    tree_map_[node_id] =
        std::make_unique<AccessibilityNodeInfoDataWrapper>(this, node);

    std::vector<int32_t> children;
    if (GetProperty(node_ptr.get()->int_list_properties,
                    AXIntListProperty::CHILD_NODE_IDS, &children)) {
      for (const int32_t child : children) {
        parent_map_[child] = node_id;
      }
    }
  }
}

void AXTreeSourceAndroid::NotifyAccessibilityEventInternal(
    const AXEventData& event_data) {
  if (window_id_ != event_data.window_id) {
    // Root window id is changed. Resetting variables that depends on id.
    android_focused_id_.reset();
    hooks_.clear();
    window_id_ = event_data.window_id;
  }
  is_notification_ = event_data.notification_key.has_value();
  if (is_notification_) {
    notification_key_ = event_data.notification_key;
  }
  is_input_method_window_ = event_data.is_input_method_window;

  BuildNodeMap(event_data);

  // Compute each node's bounds, based on its descendants.
  // Assuming |nodeData| is in pre-order, compute cached bounds in post-order to
  // avoid an O(n^2) amount of work as the computed bounds uses descendant
  // bounds.
  for (int i = event_data.node_data.size() - 1; i >= 0; --i) {
    int32_t id = event_data.node_data[i]->id;
    computed_bounds_[id] = ComputeEnclosingBounds(tree_map_[id].get());
  }
  for (int i = event_data.window_data->size() - 1; i >= 0; --i) {
    int32_t id = event_data.window_data->at(i)->window_id;
    computed_bounds_[id] = ComputeEnclosingBounds(tree_map_[id].get());
  }

  if (!UpdateAndroidFocusedId(event_data)) {
    // Exit this function if the focused node doesn't exist nor isn't visible.
    return;
  }

  AccessibilityInfoDataWrapper* const source_node =
      GetFromId(event_data.source_id);

  std::vector<int32_t> update_ids = ProcessHooksOnEvent(event_data);

  // Prep the event and send it to automation.
  AccessibilityInfoDataWrapper* focused_node =
      android_focused_id_.has_value() ? GetFromId(*android_focused_id_)
                                      : nullptr;
  std::vector<ui::AXEvent> events;
  const std::optional<ax::mojom::Event> event_type =
      ToAXEvent(event_data.event_type, source_node, focused_node);
  if (event_type) {
    ui::AXEvent event;
    event.event_type = event_type.value();
    event.id = event_data.source_id;

    int event_from_action;
    if (GetProperty(event_data.int_properties,
                    ax::android::mojom::AccessibilityEventIntProperty::ACTION,
                    &event_from_action)) {
      event.event_from = ax::mojom::EventFrom::kAction;

      event.event_from_action = ConvertToChromeAction(
          static_cast<mojom::AccessibilityActionType>(event_from_action));
    }

    events.push_back(std::move(event));
  }

  if (event_data.event_type == AXEventType::WINDOW_STATE_CHANGED) {
    // On event type of WINDOW_STATE_CHANGED, update the entire tree so that
    // window location is correctly calculated.
    update_ids.push_back(*root_id_);
  } else if (!UseFullFocusMode()) {
    update_ids.push_back(event_data.source_id);
  } else {
    // Otherwise, update subtree under the event source.
    // If the event source is ignored, it's possible that the name is used by
    // ancestor.
    AccessibilityInfoDataWrapper* parent =
        GetFirstAccessibilityFocusableAncestor(source_node);
    update_ids.push_back(parent ? parent->GetId() : event_data.source_id);
  }

  for (const int32_t update_id : update_ids) {
    current_tree_serializer_->MarkSubtreeDirty(update_id);
  }

  // Serialize updates in the reverse order of |update_ids|.
  // Updates from Android event first as this contains the entire tree
  // information, including focus.
  std::vector<ui::AXTreeUpdate> updates;
  for (const int32_t update_id : base::Reversed(update_ids)) {
    AccessibilityInfoDataWrapper* update_root = GetFromId(update_id);
    if (!update_root) {
      LOG(ERROR) << "Update root node doesn't exist, id=" << update_id;
      continue;
    }
    ui::AXTreeUpdate update;
    if (!current_tree_serializer_->SerializeChanges(update_root, &update)) {
      std::string error_string;
      ui::AXTreeSourceChecker<AccessibilityInfoDataWrapper*> checker(this);
      checker.CheckAndGetErrorString(&error_string);

      LOG(ERROR) << "Unable to serialize accessibility event\n"
                 << "Error: " << error_string << "\n"
                 << "Update: " << update.ToString();
    } else {
      updates.push_back(std::move(update));
    }
  }

  GetAutomationEventRouter()->DispatchAccessibilityEvents(
      ax_tree_id(), std::move(updates), gfx::Point(), std::move(events));
}

extensions::AutomationEventRouterInterface*
AXTreeSourceAndroid::GetAutomationEventRouter() const {
  if (automation_event_router_for_test_) {
    return automation_event_router_for_test_;
  }

  return extensions::AutomationEventRouter::GetInstance();
}

gfx::Rect AXTreeSourceAndroid::ComputeEnclosingBounds(
    AccessibilityInfoDataWrapper* info_data) const {
  DCHECK(info_data);
  gfx::Rect computed_bounds;
  // Exit early if the node or window is invisible.
  if (!info_data->IsVisibleToUser()) {
    return computed_bounds;
  }

  ComputeEnclosingBoundsInternal(info_data, &computed_bounds);
  return computed_bounds;
}

void AXTreeSourceAndroid::ComputeEnclosingBoundsInternal(
    AccessibilityInfoDataWrapper* info_data,
    gfx::Rect* computed_bounds) const {
  DCHECK(computed_bounds);
  auto cached_bounds = computed_bounds_.find(info_data->GetId());
  if (cached_bounds != computed_bounds_.end()) {
    computed_bounds->Union(cached_bounds->second);
    return;
  }

  if (!info_data->IsVisibleToUser()) {
    return;
  }
  // Only consider nodes that can possibly be accessibility focused.
  if (info_data->IsFocusableInFullFocusMode()) {
    computed_bounds->Union(info_data->GetBounds());
  }

  // NOTE: |AXTreeSourceAndroid::GetChildren| depends on ComputeEnclosingBounds.
  // To get children, directly call wrapper's GetChildren here.
  std::vector<raw_ptr<AccessibilityInfoDataWrapper, VectorExperimental>>
      children;
  info_data->GetChildren(&children);
  for (AccessibilityInfoDataWrapper* child : children) {
    ComputeEnclosingBoundsInternal(child, computed_bounds);
  }
  return;
}

AccessibilityInfoDataWrapper*
AXTreeSourceAndroid::FindFirstFocusableNodeInFullFocusMode(
    AccessibilityInfoDataWrapper* info_data) const {
  if (!info_data) {
    return nullptr;
  }

  if (info_data->IsVisibleToUser() && info_data->IsFocusableInFullFocusMode()) {
    return info_data;
  }

  for (AccessibilityInfoDataWrapper* child : GetChildren(info_data)) {
    AccessibilityInfoDataWrapper* candidate =
        FindFirstFocusableNodeInFullFocusMode(child);
    if (candidate) {
      return candidate;
    }
  }

  return nullptr;
}

bool AXTreeSourceAndroid::UpdateAndroidFocusedId(
    const AXEventData& event_data) {
  AccessibilityInfoDataWrapper* source_node = GetFromId(event_data.source_id);
  if (source_node) {
    AccessibilityInfoDataWrapper* source_window =
        GetFromId(source_node->GetWindowId());
    if (!source_window ||
        !GetBooleanProperty(source_window->GetWindow(),
                            AXWindowBooleanProperty::FOCUSED)) {
      // Don't update focus in this task for events from non-focused window.
      return true;
    }
  }

  if (event_data.event_type == AXEventType::VIEW_FOCUSED) {
    if (source_node && source_node->IsVisibleToUser() &&
        GetBooleanProperty(source_node->GetNode(),
                           AXBooleanProperty::FOCUSED)) {
      // Sometimes Android sets focus on unfocusable node, e.g. ListView.
      AccessibilityInfoDataWrapper* adjusted_node =
          UseFullFocusMode()
              ? FindFirstFocusableNodeInFullFocusMode(source_node)
              : source_node;
      if (adjusted_node) {
        android_focused_id_ = adjusted_node->GetId();
      }
    }
  } else if (event_data.event_type == AXEventType::VIEW_ACCESSIBILITY_FOCUSED &&
             UseFullFocusMode()) {
    if (source_node && source_node->IsVisibleToUser()) {
      android_focused_id_ = source_node->GetId();
    }
  } else if (event_data.event_type ==
                 AXEventType::VIEW_ACCESSIBILITY_FOCUS_CLEARED &&
             UseFullFocusMode()) {
    int event_from_action;
    GetProperty(event_data.int_properties,
                mojom::AccessibilityEventIntProperty::ACTION,
                &event_from_action);
    const mojom::AccessibilityActionType action =
        static_cast<mojom::AccessibilityActionType>(event_from_action);
    if (action != mojom::AccessibilityActionType::FOCUS &&
        action != mojom::AccessibilityActionType::ACCESSIBILITY_FOCUS) {
      android_focused_id_.reset();
    }
  } else if (event_data.event_type == AXEventType::VIEW_SELECTED) {
    // In Android, VIEW_SELECTED event is dispatched in the two cases below:
    // 1. Changing a value in ProgressBar or TimePicker in Android P.
    // 2. Selecting an item in the context of an AdapterView.
    if (!source_node || !source_node->IsNode()) {
      return false;
    }

    AXNodeInfoData* node_info = source_node->GetNode();
    DCHECK(node_info);

    bool is_range_change = !node_info->range_info.is_null();
    if (!is_range_change) {
      AccessibilityInfoDataWrapper* selected_node =
          GetSelectedNodeInfoFromAdapterViewEvent(event_data, source_node);
      if (!selected_node || !selected_node->IsVisibleToUser()) {
        return false;
      }

      android_focused_id_ = selected_node->GetId();
    }
  } else if (event_data.event_type == AXEventType::WINDOW_STATE_CHANGED) {
    // When accessibility window changed, a11y event of WINDOW_CONTENT_CHANGED
    // is fired from Android multiple times.
    // The event of WINDOW_STATE_CHANGED is fired only once for each window
    // change and use it as a trigger to move the a11y focus to the first node.
    AccessibilityInfoDataWrapper* new_focus = nullptr;

    // If the current window has ever been visited in the current task, try
    // focus on the last focus node in this window.
    // We do it for WINDOW_STATE_CHANGED event from a window or a root node.
    bool from_root_or_window = (source_node && !source_node->IsNode()) ||
                               IsRootOfNodeTree(event_data.source_id);
    if (from_root_or_window) {
      auto itr = window_id_to_last_focus_node_id_.find(event_data.window_id);
      if (itr != window_id_to_last_focus_node_id_.end()) {
        new_focus = GetFromId(itr->second);
      }
    } else if (UseFullFocusMode()) {
      // Otherwise, try focus on the first focusable node.
      new_focus = FindFirstFocusableNodeInFullFocusMode(
          GetFromId(event_data.source_id));
    }

    if (new_focus) {
      android_focused_id_ = new_focus->GetId();
    }
  }

  if (!android_focused_id_ || !GetFromId(*android_focused_id_)) {
    // Because we only handle events from the focused window, let's reset the
    // focus to the root window.
    AccessibilityInfoDataWrapper* root = GetRoot();
    CHECK(root);
    android_focused_id_ = root_id_;
  }

  if (android_focused_id_.has_value()) {
    window_id_to_last_focus_node_id_[event_data.window_id] =
        *android_focused_id_;
  } else {
    window_id_to_last_focus_node_id_.erase(event_data.window_id);
  }

  AccessibilityInfoDataWrapper* focused_node =
      android_focused_id_.has_value() ? GetFromId(*android_focused_id_)
                                      : nullptr;

  // Ensure that the focused node correctly gets focus.
  while (focused_node && focused_node->IsIgnored()) {
    AccessibilityInfoDataWrapper* parent = GetParent(focused_node);
    if (parent) {
      android_focused_id_ = parent->GetId();
      focused_node = parent;
    } else {
      // Unable to find the appropriate focus. Removing the focused node.
      android_focused_id_.reset();
      focused_node = nullptr;
      break;
    }
  }

  return true;
}

std::vector<int32_t> AXTreeSourceAndroid::ProcessHooksOnEvent(
    const AXEventData& event_data) {
  base::EraseIf(hooks_, [this](const auto& it) {
    return it.second->ShouldDestroy(this);
  });

  std::vector<int32_t> serialization_needed_ids;
  for (const auto& modifier : hooks_) {
    if (modifier.second->PreDispatchEvent(this, event_data)) {
      serialization_needed_ids.push_back(modifier.first);
    }
  }

  // Add new hook implementations if necessary.
  auto drawer_layout_hook =
      DrawerLayoutHandler::CreateIfNecessary(this, event_data);
  if (drawer_layout_hook.has_value()) {
    hooks_.insert(std::move(*drawer_layout_hook));
  }

  auto auto_complete_hooks =
      AutoCompleteHandler::CreateIfNecessary(this, event_data);
  for (auto& modifier : auto_complete_hooks) {
    if (hooks_.count(modifier.first) == 0) {
      hooks_.insert(std::move(modifier));
    }
  }

  auto pane_title_hook = PaneTitleHandler::CreateIfNecessary(this, event_data);
  if (pane_title_hook) {
    hooks_.insert(std::move(*pane_title_hook));
  }

  return serialization_needed_ids;
}

void AXTreeSourceAndroid::Reset() {
  tree_map_.clear();
  parent_map_.clear();
  computed_bounds_.clear();
  current_tree_serializer_ = std::make_unique<AXTreeAndroidSerializer>(this);
  root_id_.reset();
  window_id_.reset();
  android_focused_id_.reset();
  extensions::AutomationEventRouterInterface* router =
      GetAutomationEventRouter();
  if (!router) {
    return;
  }

  router->DispatchTreeDestroyedEvent(ax_tree_id());
}

bool AXTreeSourceAndroid::NeedReorder(
    AccessibilityInfoDataWrapper* left,
    AccessibilityInfoDataWrapper* right) const {
  auto left_bounds = ComputeEnclosingBounds(left);
  auto right_bounds = ComputeEnclosingBounds(right);
  return !CompareBounds(left_bounds, right_bounds) &&
         CompareBounds(left->GetBounds(), right->GetBounds());
}

bool AXTreeSourceAndroid::CompareBounds(const gfx::Rect& left,
                                        const gfx::Rect& right) const {
  if (left.IsEmpty() || right.IsEmpty()) {
    return true;
  }

  // Non-intersecting vertical check.
  if (left.bottom() <= right.y()) {
    return true;
  }

  if (left.y() >= right.bottom()) {
    return false;
  }

  // Vertically overlapping. Left one comes first.
  // TODO consider right-to-left
  int left_difference = left.x() - right.x();
  if (left_difference != 0) {
    return left_difference < 0;
  }

  // Top to bottom.
  int top_difference = left.y() - right.y();
  if (top_difference != 0) {
    return top_difference < 0;
  }

  // Larger to smaller.
  int height_difference = left.height() - right.height();
  if (height_difference != 0) {
    return height_difference > 0;
  }

  int width_difference = left.width() - right.width();
  if (width_difference != 0) {
    return width_difference > 0;
  }

  // The rects are equal. Respect the original order.
  return true;
}

int32_t AXTreeSourceAndroid::GetId(
    AccessibilityInfoDataWrapper* info_data) const {
  if (!info_data) {
    return ui::kInvalidAXNodeID;
  }
  return info_data->GetId();
}

void AXTreeSourceAndroid::CacheChildrenIfNeeded(
    AccessibilityInfoDataWrapper* info_data) {
  ComputeAndCacheChildren(info_data);
}

size_t AXTreeSourceAndroid::GetChildCount(
    AccessibilityInfoDataWrapper* info_data) const {
  if (!info_data) {
    return 0;
  }
  DCHECK(info_data->cached_children_);
  return info_data->cached_children_->size();
}

AccessibilityInfoDataWrapper* AXTreeSourceAndroid::ChildAt(
    AccessibilityInfoDataWrapper* info_data,
    size_t i) const {
  DCHECK(info_data->cached_children_);
  DCHECK(i >= 0 && i < info_data->cached_children_->size());
  return (*info_data->cached_children_)[i];
}

// We don't need to handle cache clearing here, because each
// AccessibilityInfoDataWrapper is created during
// AXTreeSourceAndroid::NotifyAccessibilityEvent(), and destructed at the end of
// it that method.
void AXTreeSourceAndroid::ClearChildCache(
    AccessibilityInfoDataWrapper* info_data) {}

bool AXTreeSourceAndroid::IsIgnored(
    AccessibilityInfoDataWrapper* info_data) const {
  return false;
}

bool AXTreeSourceAndroid::IsEqual(
    AccessibilityInfoDataWrapper* info_data1,
    AccessibilityInfoDataWrapper* info_data2) const {
  if (!info_data1 || !info_data2) {
    return false;
  }
  return info_data1->GetId() == info_data2->GetId();
}

AccessibilityInfoDataWrapper* AXTreeSourceAndroid::GetNull() const {
  return nullptr;
}

void AXTreeSourceAndroid::PerformAction(const ui::AXActionData& data) {
  delegate_->OnAction(data);
}

std::vector<raw_ptr<AccessibilityInfoDataWrapper, VectorExperimental>>&
AXTreeSourceAndroid::GetChildren(
    AccessibilityInfoDataWrapper* info_data) const {
  DCHECK(info_data);
  ComputeAndCacheChildren(info_data);
  return info_data->cached_children_.value();
}

void AXTreeSourceAndroid::ComputeAndCacheChildren(
    AccessibilityInfoDataWrapper* info_data) const {
  if (info_data->cached_children_) {
    return;
  }

  std::vector<raw_ptr<AccessibilityInfoDataWrapper, VectorExperimental>>&
      children = info_data->cached_children_.emplace();

  info_data->GetChildren(&children);
  if (children.size() < 2) {
    return;
  }

  // We sort output nodes only in full focus mode.
  if (!UseFullFocusMode() || info_data->IsWebNode()) {
    return;
  }

  // Also don't sort for virtual nodes (e.g. WebView).
  for (const AccessibilityInfoDataWrapper* child : children) {
    if (child->IsWebNode()) {
      return;
    }
  }

  // This is a kind of bubble sort, but we reorder nodes only when the original
  // node bounds (which is from node->GetBounds()) and child enclosing bounds
  // (which is from ComputeEnclosingBounds()) are different.
  // This algorithm takes O(N^2) time, but we practically don't expect that
  // there's a node that contains hundreds of child nodes that require
  // reordering.
  //
  // The concept here is taken from Android accessibility's similar logic in
  // com.google.android.accessibility.utils.traversal.ReorderedChildrenIterator.
  //
  // Note that NeedReorder method is not transitive, so we cannot sort with it.
  // For example, consider bounds below:
  //   a = (0,11)-(10x10)
  //   b = (20,5)-(10x10)
  //   c = (40,0)-(10x10)
  // Here, NeedReorder(a, b) = false, NeedReorder(b, c) = false, but
  // NeedReorder(a, c) = true.

  for (int i = children.size() - 2; i >= 0; i--) {
    auto original_bounds = children.at(i)->GetBounds();
    auto enclosing_bounds = ComputeEnclosingBounds(children.at(i));
    if (original_bounds == enclosing_bounds) {
      continue;
    }

    // move the current node to be visited later if necessary.
    for (size_t j = i; j + 1 < children.size() &&
                       NeedReorder(children.at(j), children.at(j + 1));
         j++) {
      std::swap(children.at(j), children.at(j + 1));
    }
  }
}

}  // namespace ax::android