chromium/content/browser/accessibility/accessibility_tree_formatter_android.cc

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

#include "content/browser/accessibility/accessibility_tree_formatter_android.h"

#include <string>

#include "base/android/jni_android.h"
#include "base/android/jni_string.h"
#include "base/files/file_path.h"
#include "base/json/json_writer.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "content/browser/accessibility/accessibility_tree_formatter_blink.h"
#include "content/browser/accessibility/browser_accessibility_android.h"
#include "content/public/browser/ax_inspect_factory.h"
#include "ui/accessibility/ax_role_properties.h"

using base::StringPrintf;

namespace content {

namespace {
// clang-format off
const char* const BOOL_ATTRIBUTES[] = {
    "checkable",
    "checked",
    "clickable",
    "collapsed",
    "collection",
    "collection_item",
    "content_invalid",
    "disabled",
    "editable_text",
    "expanded",
    "focusable",
    "focused",
    "has_character_locations",
    "has_image",
    "has_non_empty_value",
    "heading",
    "hierarchical",
    "invisible",
    "link",
    "multiline",
    "multiselectable",
    "password",
    "range",
    "selected",
    "interesting",
    "table_header"
};

const char* const STRING_ATTRIBUTES[] = {
    "name",
    "hint",
    "state_description",
};

const char* const INT_ATTRIBUTES[] = {
    "item_index",
    "item_count",
    "row_count",
    "column_count",
    "row_index",
    "row_span",
    "column_index",
    "column_span",
    "input_type",
    "live_region_type",
    "range_min",
    "range_max",
    "range_current_value",
    "text_change_added_count",
    "text_change_removed_count",
};

const char* const ACTION_ATTRIBUTES[] = {
    "action_expand",
    "action_collapse",
};
// clang-format on
}  // namespace

AccessibilityTreeFormatterAndroid::AccessibilityTreeFormatterAndroid() {}

AccessibilityTreeFormatterAndroid::~AccessibilityTreeFormatterAndroid() {}

base::Value::Dict AccessibilityTreeFormatterAndroid::BuildTree(
    ui::AXPlatformNodeDelegate* root) const {
  if (!root) {
    return base::Value::Dict();
  }

  // XXX: Android formatter should walk native Android tree (not internal one).
  base::Value::Dict dict;
  RecursiveBuildTree(*root, &dict);
  return dict;
}

base::Value::Dict AccessibilityTreeFormatterAndroid::BuildTreeForSelector(
    const AXTreeSelector& selector) const {
  NOTREACHED_IN_MIGRATION();
  return base::Value::Dict();
}

base::Value::Dict AccessibilityTreeFormatterAndroid::BuildNode(
    ui::AXPlatformNodeDelegate* node) const {
  CHECK(node);
  base::Value::Dict dict;
  AddProperties(*node, &dict);
  return dict;
}

void AccessibilityTreeFormatterAndroid::AddDefaultFilters(
    std::vector<AXPropertyFilter>* property_filters) {
  AddPropertyFilter(property_filters, "hint=*");
  AddPropertyFilter(property_filters, "interesting", AXPropertyFilter::DENY);
  AddPropertyFilter(property_filters, "has_character_locations",
                    AXPropertyFilter::DENY);
  AddPropertyFilter(property_filters, "has_image", AXPropertyFilter::DENY);
}

void AccessibilityTreeFormatterAndroid::RecursiveBuildTree(
    const ui::AXPlatformNodeDelegate& node,
    base::Value::Dict* dict) const {
  if (!ShouldDumpNode(node))
    return;

  AddProperties(node, dict);
  if (!ShouldDumpChildren(node))
    return;

  base::Value::List children;

  const BrowserAccessibilityAndroid* android_node =
      static_cast<const BrowserAccessibilityAndroid*>(&node);

  for (size_t i = 0; i < android_node->PlatformChildCount(); ++i) {
    ui::BrowserAccessibility* child_node = android_node->PlatformGetChild(i);
    CHECK(child_node);
    base::Value::Dict child_dict;
    RecursiveBuildTree(*child_node, &child_dict);
    children.Append(std::move(child_dict));
  }
  dict->Set(kChildrenDictAttr, std::move(children));
}

void AccessibilityTreeFormatterAndroid::AddProperties(
    const ui::AXPlatformNodeDelegate& node,
    base::Value::Dict* dict) const {
  dict->Set("id", node.GetId());

  const BrowserAccessibilityAndroid* android_node =
      static_cast<const BrowserAccessibilityAndroid*>(&node);

  // Class name.
  dict->Set("class", android_node->GetClassName());

  // Bool attributes.
  dict->Set("checkable", android_node->IsCheckable());
  dict->Set("checked", android_node->IsChecked());
  dict->Set("clickable", android_node->IsClickable());
  dict->Set("collapsed", android_node->IsCollapsed());
  dict->Set("collection", android_node->IsCollection());
  dict->Set("collection_item", android_node->IsCollectionItem());
  dict->Set("content_invalid", android_node->IsContentInvalid());
  dict->Set("disabled", !android_node->IsEnabled());
  dict->Set("editable_text", android_node->IsTextField());
  dict->Set("expanded", android_node->IsExpanded());
  dict->Set("focusable", android_node->IsFocusable());
  dict->Set("focused", android_node->IsFocused());
  dict->Set("has_character_locations", android_node->HasCharacterLocations());
  dict->Set("has_image", android_node->HasImage());
  dict->Set("has_non_empty_value", android_node->HasNonEmptyValue());
  dict->Set("heading", android_node->IsHeading());
  dict->Set("hierarchical", android_node->IsHierarchical());
  dict->Set("invisible", !android_node->IsVisibleToUser());
  dict->Set("link", ui::IsLink(android_node->GetRole()));
  dict->Set("multiline", android_node->IsMultiLine());
  dict->Set("multiselectable", android_node->IsMultiselectable());
  dict->Set("range", android_node->GetData().IsRangeValueSupported());
  dict->Set("password", android_node->IsPasswordField());
  dict->Set("selected", android_node->IsSelected());
  dict->Set("interesting", android_node->IsInterestingOnAndroid());
  dict->Set("table_header", android_node->IsTableHeader());

  // String attributes.
  dict->Set("name", android_node->GetTextContentUTF16());
  dict->Set("hint", android_node->GetHint());
  dict->Set("role_description", android_node->GetRoleDescription());
  dict->Set("state_description", android_node->GetStateDescription());

  // Int attributes.
  dict->Set("item_index", android_node->GetItemIndex());
  dict->Set("item_count", android_node->GetItemCount());
  dict->Set("row_count", android_node->RowCount());
  dict->Set("column_count", android_node->ColumnCount());
  dict->Set("row_index", android_node->RowIndex());
  dict->Set("row_span", android_node->RowSpan());
  dict->Set("column_index", android_node->ColumnIndex());
  dict->Set("column_span", android_node->ColumnSpan());
  dict->Set("input_type", android_node->AndroidInputType());
  dict->Set("live_region_type", android_node->AndroidLiveRegionType());
  dict->Set("range_min", static_cast<int>(android_node->RangeMin()));
  dict->Set("range_max", static_cast<int>(android_node->RangeMax()));
  dict->Set("range_current_value",
            static_cast<int>(android_node->RangeCurrentValue()));
  dict->Set("text_change_added_count", android_node->GetTextChangeAddedCount());
  dict->Set("text_change_removed_count",
            android_node->GetTextChangeRemovedCount());

  // Actions.
  dict->Set("action_expand", android_node->IsCollapsed());
  dict->Set("action_collapse", android_node->IsExpanded());
}

std::string AccessibilityTreeFormatterAndroid::ProcessTreeForOutput(
    const base::Value::Dict& dict) const {
  const std::string* error_value = dict.FindString("error");
  if (error_value)
    return *error_value;

  std::string line;
  if (show_ids()) {
    int id_value = dict.FindInt("id").value_or(0);
    WriteAttribute(true, base::NumberToString(id_value), &line);
  }

  const std::string* class_value = dict.FindString("class");
  if (class_value) {
    WriteAttribute(true, *class_value, &line);
  }

  const std::string* role_description = dict.FindString("role_description");
  if (role_description && !role_description->empty()) {
    WriteAttribute(
        true, StringPrintf("role_description='%s'", role_description->c_str()),
        &line);
  }

  for (const char* attribute_name : BOOL_ATTRIBUTES) {
    std::optional<bool> value = dict.FindBool(attribute_name);
    if (value && *value)
      WriteAttribute(true, attribute_name, &line);
  }

  for (const char* attribute_name : STRING_ATTRIBUTES) {
    const std::string* value = dict.FindString(attribute_name);
    if (!value || value->empty())
      continue;
    WriteAttribute(
        true, StringPrintf("%s='%s'", attribute_name, value->c_str()), &line);
  }

  for (const char* attribute_name : INT_ATTRIBUTES) {
    int value = dict.FindInt(attribute_name).value_or(0);
    if (value == 0)
      continue;
    WriteAttribute(true, StringPrintf("%s=%d", attribute_name, value), &line);
  }

  for (const char* attribute_name : ACTION_ATTRIBUTES) {
    if (dict.FindBool(attribute_name).value_or(false)) {
      WriteAttribute(false /* Exclude actions by default */, attribute_name,
                     &line);
    }
  }

  return line;
}

}  // namespace content