diff --git a/BUILD.gn b/BUILD.gn
index 7a102d594bf570c401e4a55e5682fd014a56ab81..efab1e976b3374f1e23dd4d3371057eb572ce37a 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -141,7 +141,7 @@ group("unittests") {
}
# Compile all unittests targets if enabled.
- if (enable_unittests) {
+ if (enable_unittests && !is_win) {
public_deps += [
"//flutter/display_list:display_list_rendertests",
"//flutter/display_list:display_list_unittests",
@@ -152,9 +152,6 @@ group("unittests") {
"//flutter/runtime:no_dart_plugin_registrant_unittests",
"//flutter/runtime:runtime_unittests",
"//flutter/shell/common:shell_unittests",
- "//flutter/shell/platform/embedder:embedder_a11y_unittests",
- "//flutter/shell/platform/embedder:embedder_proctable_unittests",
- "//flutter/shell/platform/embedder:embedder_unittests",
"//flutter/testing:testing_unittests",
"//flutter/testing/dart",
"//flutter/testing/smoke_test_failure",
@@ -162,6 +159,21 @@ group("unittests") {
"//flutter/third_party/txt:txt_unittests",
]
+ # ohos does not support vulkan
+ if (!is_ohos) {
+ public_deps += [
+ "//flutter/shell/platform/embedder:embedder_a11y_unittests",
+ "//flutter/shell/platform/embedder:embedder_proctable_unittests",
+ "//flutter/shell/platform/embedder:embedder_unittests",
+ ]
+ }
+
+ if (is_ohos) {
+ public_deps += [
+ "//flutter/shell/platform/ohos:flutter_ohos_unittests",
+ ]
+ }
+
# The accessibility library only supports Mac and Windows at the moment.
if (is_mac || is_win) {
public_deps +=
@@ -181,7 +193,7 @@ group("unittests") {
[ "//flutter/shell/platform/darwin:flutter_channels_unittests" ]
}
- if (!is_win && !is_fuchsia) {
+ if (!is_win && !is_fuchsia && !is_ohos) {
public_deps += [
"//flutter/shell/platform/android/external_view_embedder:android_external_view_embedder_unittests",
"//flutter/shell/platform/android/jni:jni_unittests",
diff --git a/COMMITTERS.md b/COMMITTERS.md
new file mode 100644
index 0000000000000000000000000000000000000000..c6fed253df75e3930ffb6eda8e6caaa81c0c4488
--- /dev/null
+++ b/COMMITTERS.md
@@ -0,0 +1,8 @@
+## Committers列表
+
+### 以下是此项目的committer人员
+不区分先后顺序
+
+- [zjzhu](https://gitee.com/appproject)
+- [aibin](https://gitee.com/binai)
+- [lihui](https://gitee.com/lihui868)
\ No newline at end of file
diff --git a/OAT.xml b/OAT.xml
index a1e1a7950dadf713a2dde6ce10a619cc69c1ada6..1f5876088898c9ce3f7c46c84ba1023bf5801f27 100644
--- a/OAT.xml
+++ b/OAT.xml
@@ -137,8 +137,12 @@ used to filter file path.
desc="Apache-2.0 文档类文件" />
+
+
diff --git a/README.OpenSource b/README.OpenSource
index d0011eeaca00e7fc9a203f1c48151d3c2b060271..f67241e17a8791d11d8da8230d9381e006b13a5d 100644
--- a/README.OpenSource
+++ b/README.OpenSource
@@ -1,7 +1,7 @@
[
{
"Name": "engine",
- "License": "BSD 3-Clause License",
+ "License": "BSD-3-Clause License",
"License File": "LICENSE",
"Version Number": "3.7.12",
"Owner": "aibin@openvalley.net",
diff --git a/README.md b/README.md
index 53e171f7932d4487e9ce5deb2d44058ab7403aeb..16a40eda07f0aac888170c266e273952ecfe3eb0 100644
--- a/README.md
+++ b/README.md
@@ -83,6 +83,8 @@ Flutter Engine
5. 由于windows和mac、linux对换行符处理方式不同,在应用dart补丁时会造成dart vm snapshot hash结果不同,可通过以下方法获取当前snapshot hash值
+6. MediaQuery组件暂不支持displayFeatureType和displayFeatureState信息
+
```shell
python xxx/src/third_party/dart/tools/make_version.py --format='{{SNAPSHOT_HASH}}'
```
diff --git a/attachment/repos/angle.patch b/attachment/repos/angle.patch
index e4a0952dd4dce8a288dd5aaead27e49bc803628b..837d591f23ed85f2ab797186e1ea5ba54b4f9b58 100644
--- a/attachment/repos/angle.patch
+++ b/attachment/repos/angle.patch
@@ -1,8 +1,19 @@
diff --git a/BUILD.gn b/BUILD.gn
-index efaf09033..73af49870 100644
+index efaf090339..cc0398c0e4 100644
--- a/BUILD.gn
+++ b/BUILD.gn
-@@ -83,7 +83,7 @@ if (angle_build_all) {
+@@ -11,7 +11,9 @@ if (angle_use_wayland) {
+ import("//build_overrides/wayland.gni")
+ }
+ if (angle_has_build) {
+- import("//build/config/linux/pkg_config.gni")
++ if (!is_ohos) {
++ import("//build/config/linux/pkg_config.gni")
++ }
+ import("//build/config/sanitizers/sanitizers.gni")
+ import("//build/config/ui.gni")
+ import("//testing/libfuzzer/fuzzer_test.gni")
+@@ -83,7 +85,7 @@ if (angle_build_all) {
":translator_fuzzer",
":xxhash_fuzzer",
"$angle_root/samples:angle_samples",
@@ -11,18 +22,43 @@ index efaf09033..73af49870 100644
]
if (angle_enable_cl) {
deps += [ "$angle_root/src/libOpenCL:angle_cl" ]
-@@ -486,7 +486,8 @@ angle_static_library("angle_image_util") {
- sources = libangle_image_util_sources
+@@ -141,7 +143,7 @@ config("internal_config") {
+ if (angle_is_winuwp) {
+ defines += [ "ANGLE_IS_WINUWP" ]
+ }
+- } else if (is_linux || is_chromeos) {
++ } else if (is_linux || is_chromeos || is_ohos) {
+ defines += [ "ANGLE_IS_LINUX" ]
+ }
+
+@@ -443,7 +445,7 @@ angle_static_library("angle_common") {
+ all_dependent_configs = [ ":angle_disable_pool_alloc" ]
+ }
+
+- if (is_linux || is_chromeos) {
++ if (is_linux || is_chromeos || is_ohos) {
+ libs = [ "dl" ]
+ }
+
+@@ -487,6 +489,7 @@ angle_static_library("angle_image_util") {
public_configs += [ ":angle_image_util_config" ]
public_deps = [ ":angle_image_util_headers" ]
--
-+
+
+ angle_has_astc_encoder = false
if (angle_has_astc_encoder) {
public_deps += [ "third_party/astc-encoder:astcenc" ]
include_dirs = [ "third_party/astc-encoder/src/Source/" ]
+@@ -561,7 +564,7 @@ angle_static_library("angle_gpu_info_util") {
+ libs += [ "dxgi.lib" ]
+ }
+
+- if (is_linux || is_chromeos) {
++ if (is_linux || is_chromeos || is_ohos) {
+ sources += libangle_gpu_info_util_linux_sources
+
+ if (angle_use_x11 && angle_has_build) {
diff --git a/gni/angle.gni b/gni/angle.gni
-index b8086660a..d82d056d6 100644
+index b8086660a1..d82d056d65 100644
--- a/gni/angle.gni
+++ b/gni/angle.gni
@@ -60,8 +60,7 @@ if (angle_has_build) {
@@ -65,7 +101,7 @@ index b8086660a..d82d056d6 100644
angle_build_vulkan_system_info = angle_has_build && !angle_is_winuwp
diff --git a/include/EGL/eglplatform.h b/include/EGL/eglplatform.h
-index 9ebaf00a9..fe111d115 100644
+index 9ebaf00a9b..fe111d115d 100644
--- a/include/EGL/eglplatform.h
+++ b/include/EGL/eglplatform.h
@@ -117,13 +117,19 @@ typedef khronos_uintptr_t EGLNativeWindowType;
@@ -95,7 +131,7 @@ index 9ebaf00a9..fe111d115 100644
#elif defined(__APPLE__)
diff --git a/src/common/angle_version.h b/src/common/angle_version.h
-index d9d7e8929..a97e3844b 100644
+index d9d7e8929d..a97e3844ba 100644
--- a/src/common/angle_version.h
+++ b/src/common/angle_version.h
@@ -14,8 +14,12 @@
@@ -112,7 +148,7 @@ index d9d7e8929..a97e3844b 100644
#define ANGLE_STRINGIFY(x) #x
#define ANGLE_MACRO_STRINGIFY(x) ANGLE_STRINGIFY(x)
diff --git a/src/compiler/translator/BuildSPIRV.h b/src/compiler/translator/BuildSPIRV.h
-index d67bd812b..00d22d025 100644
+index d67bd812ba..00d22d025d 100644
--- a/src/compiler/translator/BuildSPIRV.h
+++ b/src/compiler/translator/BuildSPIRV.h
@@ -110,6 +110,8 @@ struct SpirvType
@@ -124,3 +160,38 @@ index d67bd812b..00d22d025 100644
struct SpirvIdAndIdList
{
spirv::IdRef id;
+diff --git a/src/libANGLE/renderer/gl/BUILD.gn b/src/libANGLE/renderer/gl/BUILD.gn
+index c675c26074..6cba114ef8 100644
+--- a/src/libANGLE/renderer/gl/BUILD.gn
++++ b/src/libANGLE/renderer/gl/BUILD.gn
+@@ -125,7 +125,7 @@ if (angle_use_x11) {
+ ]
+ }
+
+-if (is_android || is_linux || is_chromeos) {
++if (is_android || is_linux || is_chromeos || is_ohos) {
+ _gl_backend_sources += [
+ "egl/ContextEGL.cpp",
+ "egl/ContextEGL.h",
+@@ -264,7 +264,7 @@ angle_source_set("angle_gl_backend") {
+ "Xext",
+ ]
+ }
+- if (is_android || is_linux || is_chromeos) {
++ if (is_android || is_linux || is_chromeos || is_ohos) {
+ deps += [ "$angle_root/src/common/linux:angle_dma_buf" ]
+ }
+ if (is_apple) {
+diff --git a/src/libGLESv2.gni b/src/libGLESv2.gni
+index 723d7f99f2..dab8b887ce 100644
+--- a/src/libGLESv2.gni
++++ b/src/libGLESv2.gni
+@@ -87,7 +87,7 @@ xxhash_sources = [
+ "src/common/third_party/xxhash/xxhash.h",
+ ]
+
+-if (is_linux || is_chromeos || is_android || is_fuchsia) {
++if (is_linux || is_chromeos || is_android || is_fuchsia || is_ohos) {
+ libangle_common_sources += [
+ "src/common/system_utils_linux.cpp",
+ "src/common/system_utils_posix.cpp",
diff --git a/attachment/repos/build.patch b/attachment/repos/build.patch
index bbe17f9809dd6752ee533c0b15c1ec6c84574e57..84c50014e1c32ce13efa69e053f54a61fca03a3e 100644
--- a/attachment/repos/build.patch
+++ b/attachment/repos/build.patch
@@ -102,16 +102,16 @@ index b8d0f47..a3d1ed9 100644
@@ -313,7 +334,7 @@ if (!is_clang && using_sanitizer) {
is_clang = true
}
-
+
-use_flutter_cxx = is_clang && (is_linux || is_android || is_mac || is_ios)
+use_flutter_cxx = is_clang && (is_linux || is_android || is_mac || is_ios || is_ohos )
-
+
if (is_msan && !is_linux) {
assert(false, "Memory sanitizer is only available on Linux.")
@@ -365,7 +386,9 @@ if (is_posix) {
]
}
-
+
-if (is_linux) {
+if (is_ohos || (is_linux && host_os == "mac")) {
+ _native_compiler_configs += [ "//build/config/ohos:sdk" ]
@@ -160,7 +160,7 @@ index b8d0f47..a3d1ed9 100644
if (is_clang) {
host_toolchain = "//build/toolchain/linux:clang_$host_cpu"
diff --git a/build/config/compiler/BUILD.gn b/build/config/compiler/BUILD.gn
-index ba20010..17e0487 100644
+index ba20010..5f27c8c 100644
--- a/build/config/compiler/BUILD.gn
+++ b/build/config/compiler/BUILD.gn
@@ -191,10 +191,16 @@ config("compiler") {
@@ -185,7 +185,7 @@ index ba20010..17e0487 100644
} else if (current_cpu == "x86") {
cflags += [ "-m32" ]
@@ -315,7 +321,7 @@ config("compiler") {
-
+
# Linux/Android common flags setup.
# ---------------------------------
- if (is_linux || is_android) {
@@ -193,8 +193,8 @@ index ba20010..17e0487 100644
cflags += [
"-fPIC",
"-pipe", # Use pipes for communicating between sub-processes. Faster.
-@@ -334,7 +340,16 @@ config("compiler") {
-
+@@ -334,7 +340,28 @@ config("compiler") {
+
# Linux-specific compiler flags setup.
# ------------------------------------
- if (is_linux) {
@@ -202,7 +202,19 @@ index ba20010..17e0487 100644
+ cflags += [ "-pthread" ]
+ ldflags += [ "-pthread" ]
+
-+ if (current_cpu == "arm64") {
++ if (current_cpu == "x86") {
++ cflags += [ "--target=x86-linux-ohos" ]
++ ldflags += [ "--target=x86-linux-ohos" ]
++ cflags += [ "-DBORINGSSL_CLANG_SUPPORTS_DOT_ARCH" ]
++ } else if (current_cpu == "x64") {
++ cflags += [ "--target=x86_64-linux-ohos" ]
++ ldflags += [ "--target=x86_64-linux-ohos" ]
++ cflags += [ "-DBORINGSSL_CLANG_SUPPORTS_DOT_ARCH" ]
++ } else if (current_cpu == "arm") {
++ cflags += [ "--target=aarch-linux-ohos" ]
++ ldflags += [ "--target=aarch-linux-ohos" ]
++ cflags += [ "-DBORINGSSL_CLANG_SUPPORTS_DOT_ARCH" ]
++ } else if (current_cpu == "arm64") {
+ cflags += [ "--target=aarch64-linux-ohos" ]
+ ldflags += [ "--target=aarch64-linux-ohos" ]
+ cflags += [ "-DBORINGSSL_CLANG_SUPPORTS_DOT_ARCH" ]
@@ -210,8 +222,8 @@ index ba20010..17e0487 100644
+ } else if (is_linux) {
cflags += [ "-pthread" ]
ldflags += [ "-pthread" ]
-
-@@ -521,9 +536,13 @@ config("runtime_library") {
+
+@@ -521,9 +548,13 @@ config("runtime_library") {
ldflags += [ "-nostdlib++" ]
}
include_dirs = [
@@ -224,9 +236,9 @@ index ba20010..17e0487 100644
+ include_dirs += [ "//third_party/libcxx/include" ]
+ }
}
-
+
# Android standard library setup.
-@@ -860,17 +879,35 @@ config("optimize") {
+@@ -860,12 +891,14 @@ config("optimize") {
cflags = [ "-Oz" ] + common_optimize_on_cflags # Favor size over speed.
} else if (is_wasm) {
cflags = [ "-Oz" ]
@@ -235,34 +247,13 @@ index ba20010..17e0487 100644
} else {
cflags = [ "-O2" ] + common_optimize_on_cflags
}
-
+
lto_flags = []
- if (enable_lto && (is_ios || is_android || is_fuchsia || is_wasm)) {
+ if (enable_lto && (is_ios || is_android || is_fuchsia || is_wasm || is_ohos)) {
lto_flags += [ "-flto" ]
}
-
-+ if (is_ohos) {
-+ cflags += [
-+ "-mcpu=tsv110",
-+ "-fwhole-program-vtables",
-+ ]
-+ }
-+
- ldflags = common_optimize_on_ldflags + lto_flags
- cflags += lto_flags
-+
-+ if (is_ohos) {
-+ ldflags += [
-+ "-Wl,--lto-O2",
-+ "-Wl,--plugin-opt=-mcpu=tsv110",
-+ "-Wl,-mllvm",
-+ "-Wl,-wholeprogramdevirt-check=fallback",
-+ ]
-+ }
- }
-
- # Turn off optimizations.
+
diff --git a/build/config/ohos/BUILD.gn b/build/config/ohos/BUILD.gn
new file mode 100644
index 0000000..b2675d2
@@ -300,10 +291,10 @@ index 0000000..b2675d2
+}
diff --git a/build/config/ohos/config.gni b/build/config/ohos/config.gni
new file mode 100644
-index 0000000..0022d70
+index 0000000..add7249
--- /dev/null
+++ b/build/config/ohos/config.gni
-@@ -0,0 +1,13 @@
+@@ -0,0 +1,32 @@
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
@@ -311,7 +302,26 @@ index 0000000..0022d70
+# This file contains common system config stuff for the Ohos build.
+
+if (is_ohos) {
-+ if (current_cpu == "arm64") {
++ # Defines host_os
++ if (host_os == "linux") {
++ ohos_host_os = "linux"
++ } else if (host_os == "mac") {
++ ohos_host_os = "darwin"
++ } else if (host_os == "win") {
++ ohos_host_os = "win"
++ } else {
++ assert(false, "Need Ohos toolchain support for your build OS.")
++ }
++
++
++ # ABI ------------------------------------------------------------------------
++ if (current_cpu == "x86") {
++ ohos_app_abi = "x86"
++ } else if (current_cpu == "x64") {
++ ohos_app_abi = "x86_64"
++ } else if (current_cpu == "arm") {
++ ohos_app_abi = "armeabi-v7a"
++ } else if (current_cpu == "arm64") {
+ ohos_app_abi = "arm64-v8a"
+ } else {
+ assert(false, "Unknown Ohos ABI: " + current_cpu)
@@ -864,7 +874,7 @@ index cad6cbc..c17950b 100644
+++ b/build/secondary/third_party/glfw/BUILD.gn
@@ -47,6 +47,34 @@ source_set("glfw") {
]
-
+
defines = [ "_GLFW_WIN32" ]
+ } else if (is_ohos) {
+ sources += [
@@ -914,6 +924,6 @@ index 65b1623..a89742a 100644
+ readelf = "${toolchain_bin}/llvm-readelf"
+ nm = "${toolchain_bin}/llvm-nm"
+ strip = "${toolchain_bin}/llvm-strip"
-
+
target_triple_flags = "--target=${custom_target_triple}"
sysroot_flags = "--sysroot ${custom_sysroot}"
diff --git a/attachment/repos/dart.patch0 b/attachment/repos/dart.patch0
index 04ca043cec6ffd71361c290ec56a2728f68b4704..8094cc2e317d80592662f557836994999c0b947c 100644
--- a/attachment/repos/dart.patch0
+++ b/attachment/repos/dart.patch0
@@ -1,4 +1,4 @@
-diff --git a/pkg/dds/lib/dds.dart b/pkg/dds/lib/dds.dart
+diff --git a/pkg/dds/lib/dds.dart b/pkg/dds/lib/dds.dart
index 177a9265649..c7038c56ad7 100644
--- a/pkg/dds/lib/dds.dart
+++ b/pkg/dds/lib/dds.dart
@@ -75,6 +75,60 @@ index 33be98fc0aa..eda146a0580 100644
String? get authCode => _authCode;
String? _authCode;
+diff --git a/pkg/vm/lib/transformations/ffi/abi.dart b/pkg/vm/lib/transformations/ffi/abi.dart
+index cc2d86bafd7..e703419eed7 100644
+--- a/pkg/vm/lib/transformations/ffi/abi.dart
++++ b/pkg/vm/lib/transformations/ffi/abi.dart
+@@ -40,6 +40,7 @@ enum _OS {
+ linux,
+ macos,
+ windows,
++ ohos,
+ }
+
+ /// An application binary interface (ABI).
+@@ -71,6 +72,9 @@ class Abi {
+ /// The application binary interface for Fuchsia on the X64 architecture.
+ static const fuchsiaX64 = _fuchsiaX64;
+
++ /// The application binary interface for Ohos on the X64 architecture.
++ static const ohosX64 = _ohosX64;
++
+ /// The application binary interface for iOS on the Arm architecture.
+ static const iosArm = _iosArm;
+
+@@ -131,6 +135,7 @@ class Abi {
+ androidX64,
+ fuchsiaArm64,
+ fuchsiaX64,
++ ohosX64,
+ iosArm,
+ iosArm64,
+ iosX64,
+@@ -176,6 +181,7 @@ class Abi {
+ static const _androidX64 = Abi._(_Architecture.x64, _OS.android);
+ static const _fuchsiaArm64 = Abi._(_Architecture.arm64, _OS.fuchsia);
+ static const _fuchsiaX64 = Abi._(_Architecture.x64, _OS.fuchsia);
++ static const _ohosX64 = Abi._(_Architecture.x64, _OS.ohos);
+ static const _iosArm = Abi._(_Architecture.arm, _OS.ios);
+ static const _iosArm64 = Abi._(_Architecture.arm64, _OS.ios);
+ static const _iosX64 = Abi._(_Architecture.x64, _OS.ios);
+@@ -200,6 +206,7 @@ const Map abiNames = {
+ Abi.androidX64: 'androidX64',
+ Abi.fuchsiaArm64: 'fuchsiaArm64',
+ Abi.fuchsiaX64: 'fuchsiaX64',
++ Abi.ohosX64: 'ohosX64',
+ Abi.iosArm: 'iosArm',
+ Abi.iosArm64: 'iosArm64',
+ Abi.iosX64: 'iosX64',
+@@ -239,6 +246,7 @@ const Map> nonSizeAlignment = {
+ // _wordSize64
+ Abi.androidArm64: _wordSize64,
+ Abi.androidX64: _wordSize64,
++ Abi.ohosX64: _wordSize64,
+ Abi.fuchsiaArm64: _wordSize64,
+ Abi.fuchsiaX64: _wordSize64,
+ Abi.iosArm64: _wordSize64,
diff --git a/runtime/BUILD.gn b/runtime/BUILD.gn
index 13bfd0a724b..042b6a45f7e 100644
--- a/runtime/BUILD.gn
@@ -5575,27 +5629,28 @@ index 66fa5a8d564..cc5eb834b3d 100644
#if DART_TARGET_OS_MACOS_IOS
#define DART_TARGET_OS_NAME IOS
diff --git a/runtime/vm/compiler/ffi/abi.h b/runtime/vm/compiler/ffi/abi.h
-index b693ef85255..54b15f48890 100644
+index b693ef85255..6a3f42bd949 100644
--- a/runtime/vm/compiler/ffi/abi.h
+++ b/runtime/vm/compiler/ffi/abi.h
-@@ -24,6 +24,8 @@ enum class Abi {
+@@ -24,6 +24,9 @@ enum class Abi {
kAndroidArm64,
kAndroidIA32,
kAndroidX64,
+ kOhosArm,
+ kOhosArm64,
++ kOhosX64,
kFuchsiaArm64,
kFuchsiaX64,
kIOSArm,
-@@ -49,9 +51,9 @@ const int64_t num_abis = static_cast(Abi::kWindowsX64) + 1;
+@@ -49,9 +52,9 @@ const int64_t num_abis = static_cast(Abi::kWindowsX64) + 1;
// - runtime/vm/compiler/frontend/kernel_to_il.cc
static_assert(static_cast(Abi::kAndroidArm) == 0,
"Enum value unexpected.");
-static_assert(static_cast(Abi::kWindowsX64) == 19,
-+static_assert(static_cast(Abi::kWindowsX64) == 21,
++static_assert(static_cast(Abi::kWindowsX64) == 22,
"Enum value unexpected.");
-static_assert(num_abis == 20, "Enum value unexpected.");
-+static_assert(num_abis == 22, "Enum value unexpected.");
++static_assert(num_abis == 23, "Enum value unexpected.");
// The target ABI. Defines sizes and alignment of native types.
Abi TargetAbi();
@@ -7619,6 +7674,166 @@ index b7e5be3eab5..8a8f873f6c5 100644
bool _isAllowedOrigin(String origin) {
Uri uri;
+diff --git a/sdk/lib/ffi/abi.dart b/sdk/lib/ffi/abi.dart
+index e6f08b37d25..5af605a8764 100644
+--- a/sdk/lib/ffi/abi.dart
++++ b/sdk/lib/ffi/abi.dart
+@@ -34,6 +34,9 @@ class Abi {
+ /// The application binary interface for Fuchsia on the X64 architecture.
+ static const fuchsiaX64 = _fuchsiaX64;
+
++ /// The application binary interface for Ohos on the X64 architecture.
++ static const ohosX64 = _ohosX64;
++
+ /// The application binary interface for iOS on the Arm architecture.
+ static const iosArm = _iosArm;
+
+@@ -94,6 +97,7 @@ class Abi {
+ androidX64,
+ fuchsiaArm64,
+ fuchsiaX64,
++ ohosX64,
+ iosArm,
+ iosArm64,
+ iosX64,
+@@ -136,6 +140,7 @@ class Abi {
+ static const _androidX64 = Abi._(_Architecture.x64, _OS.android);
+ static const _fuchsiaArm64 = Abi._(_Architecture.arm64, _OS.fuchsia);
+ static const _fuchsiaX64 = Abi._(_Architecture.x64, _OS.fuchsia);
++ static const _ohosX64 = Abi._(_Architecture.x64, _OS.ohos);
+ static const _iosArm = Abi._(_Architecture.arm, _OS.ios);
+ static const _iosArm64 = Abi._(_Architecture.arm64, _OS.ios);
+ static const _iosX64 = Abi._(_Architecture.x64, _OS.ios);
+@@ -170,4 +175,5 @@ enum _OS {
+ linux,
+ macos,
+ windows,
++ ohos,
+ }
+diff --git a/sdk/lib/ffi/c_type.dart b/sdk/lib/ffi/c_type.dart
+index ee358c2f79d..1f9e629beb9 100644
+--- a/sdk/lib/ffi/c_type.dart
++++ b/sdk/lib/ffi/c_type.dart
+@@ -29,6 +29,7 @@ part of dart.ffi;
+ Abi.androidX64: Int8(),
+ Abi.fuchsiaArm64: Uint8(),
+ Abi.fuchsiaX64: Int8(),
++ Abi.ohosX64: Int8(),
+ Abi.iosArm: Int8(),
+ Abi.iosArm64: Int8(),
+ Abi.iosX64: Int8(),
+@@ -66,6 +67,7 @@ class Char extends AbiSpecificInteger {
+ Abi.androidX64: Int8(),
+ Abi.fuchsiaArm64: Int8(),
+ Abi.fuchsiaX64: Int8(),
++ Abi.ohosX64: Int8(),
+ Abi.iosArm: Int8(),
+ Abi.iosArm64: Int8(),
+ Abi.iosX64: Int8(),
+@@ -103,6 +105,7 @@ class SignedChar extends AbiSpecificInteger {
+ Abi.androidX64: Uint8(),
+ Abi.fuchsiaArm64: Uint8(),
+ Abi.fuchsiaX64: Uint8(),
++ Abi.ohosX64: Uint8(),
+ Abi.iosArm: Uint8(),
+ Abi.iosArm64: Uint8(),
+ Abi.iosX64: Uint8(),
+@@ -140,6 +143,7 @@ class UnsignedChar extends AbiSpecificInteger {
+ Abi.androidX64: Int16(),
+ Abi.fuchsiaArm64: Int16(),
+ Abi.fuchsiaX64: Int16(),
++ Abi.ohosX64: Int16(),
+ Abi.iosArm: Int16(),
+ Abi.iosArm64: Int16(),
+ Abi.iosX64: Int16(),
+@@ -177,6 +181,7 @@ class Short extends AbiSpecificInteger {
+ Abi.androidX64: Uint16(),
+ Abi.fuchsiaArm64: Uint16(),
+ Abi.fuchsiaX64: Uint16(),
++ Abi.ohosX64: Uint16(),
+ Abi.iosArm: Uint16(),
+ Abi.iosArm64: Uint16(),
+ Abi.iosX64: Uint16(),
+@@ -214,6 +219,7 @@ class UnsignedShort extends AbiSpecificInteger {
+ Abi.androidX64: Int32(),
+ Abi.fuchsiaArm64: Int32(),
+ Abi.fuchsiaX64: Int32(),
++ Abi.ohosX64: Int32(),
+ Abi.iosArm: Int32(),
+ Abi.iosArm64: Int32(),
+ Abi.iosX64: Int32(),
+@@ -251,6 +257,7 @@ class Int extends AbiSpecificInteger {
+ Abi.androidX64: Uint32(),
+ Abi.fuchsiaArm64: Uint32(),
+ Abi.fuchsiaX64: Uint32(),
++ Abi.ohosX64: Uint32(),
+ Abi.iosArm: Uint32(),
+ Abi.iosArm64: Uint32(),
+ Abi.iosX64: Uint32(),
+@@ -289,6 +296,7 @@ class UnsignedInt extends AbiSpecificInteger {
+ Abi.androidX64: Int64(),
+ Abi.fuchsiaArm64: Int64(),
+ Abi.fuchsiaX64: Int64(),
++ Abi.ohosX64: Int64(),
+ Abi.iosArm: Int32(),
+ Abi.iosArm64: Int64(),
+ Abi.iosX64: Int64(),
+@@ -327,6 +335,7 @@ class Long extends AbiSpecificInteger {
+ Abi.androidX64: Uint64(),
+ Abi.fuchsiaArm64: Uint64(),
+ Abi.fuchsiaX64: Uint64(),
++ Abi.ohosX64: Uint64(),
+ Abi.iosArm: Uint32(),
+ Abi.iosArm64: Uint64(),
+ Abi.iosX64: Uint64(),
+@@ -364,6 +373,7 @@ class UnsignedLong extends AbiSpecificInteger {
+ Abi.androidX64: Int64(),
+ Abi.fuchsiaArm64: Int64(),
+ Abi.fuchsiaX64: Int64(),
++ Abi.ohosX64: Int64(),
+ Abi.iosArm: Int64(),
+ Abi.iosArm64: Int64(),
+ Abi.iosX64: Int64(),
+@@ -401,6 +411,7 @@ class LongLong extends AbiSpecificInteger {
+ Abi.androidX64: Uint64(),
+ Abi.fuchsiaArm64: Uint64(),
+ Abi.fuchsiaX64: Uint64(),
++ Abi.ohosX64: Uint64(),
+ Abi.iosArm: Uint64(),
+ Abi.iosArm64: Uint64(),
+ Abi.iosX64: Uint64(),
+@@ -433,6 +444,7 @@ class UnsignedLongLong extends AbiSpecificInteger {
+ Abi.androidX64: Int64(),
+ Abi.fuchsiaArm64: Int64(),
+ Abi.fuchsiaX64: Int64(),
++ Abi.ohosX64: Int64(),
+ Abi.iosArm: Int32(),
+ Abi.iosArm64: Int64(),
+ Abi.iosX64: Int64(),
+@@ -466,6 +478,7 @@ class IntPtr extends AbiSpecificInteger {
+ Abi.androidX64: Uint64(),
+ Abi.fuchsiaArm64: Uint64(),
+ Abi.fuchsiaX64: Uint64(),
++ Abi.ohosX64: Uint64(),
+ Abi.iosArm: Uint32(),
+ Abi.iosArm64: Uint64(),
+ Abi.iosX64: Uint64(),
+@@ -499,6 +512,7 @@ class UintPtr extends AbiSpecificInteger {
+ Abi.androidX64: Uint64(),
+ Abi.fuchsiaArm64: Uint64(),
+ Abi.fuchsiaX64: Uint64(),
++ Abi.ohosX64: Uint64(),
+ Abi.iosArm: Uint32(),
+ Abi.iosArm64: Uint64(),
+ Abi.iosX64: Uint64(),
+@@ -535,6 +549,7 @@ class Size extends AbiSpecificInteger {
+ Abi.androidX64: Uint32(),
+ Abi.fuchsiaArm64: Uint32(),
+ Abi.fuchsiaX64: Int32(),
++ Abi.ohosX64: Int32(),
+ Abi.iosArm: Int32(),
+ Abi.iosArm64: Int32(),
+ Abi.iosX64: Int32(),
diff --git a/sdk_args.gni b/sdk_args.gni
index 4b2a1e4ea63..a864344ee8d 100644
--- a/sdk_args.gni
diff --git a/attachment/repos/swiftshader.patch b/attachment/repos/swiftshader.patch
new file mode 100644
index 0000000000000000000000000000000000000000..4aec1116344c4d142cf0b154553fabdce16760f9
--- /dev/null
+++ b/attachment/repos/swiftshader.patch
@@ -0,0 +1,98 @@
+diff --git a/src/Reactor/BUILD.gn b/src/Reactor/BUILD.gn
+index 676f99f1a..36a581f10 100644
+--- a/src/Reactor/BUILD.gn
++++ b/src/Reactor/BUILD.gn
+@@ -95,7 +95,7 @@ if (supports_subzero) {
+ if (is_win) {
+ include_dirs +=
+ [ "../../third_party/llvm-subzero/build/Windows/include/" ]
+- } else if (is_linux || is_chromeos) {
++ } else if (is_linux || is_chromeos || is_ohos) {
+ include_dirs += [ "../../third_party/llvm-subzero/build/Linux/include/" ]
+ } else if (is_fuchsia) {
+ include_dirs +=
+@@ -328,7 +328,7 @@ if (supports_llvm) {
+ "$llvm_dir/configs/common/include/",
+ ]
+
+- if (is_linux || is_chromeos) {
++ if (is_linux || is_chromeos || is_ohos) {
+ include_dirs += [ "$llvm_dir/configs/linux/include/" ]
+ } else if (is_fuchsia) {
+ include_dirs += [ "$llvm_dir/configs/fuchsia/include/" ]
+diff --git a/src/Reactor/reactor.gni b/src/Reactor/reactor.gni
+index 04fad6f81..324003e35 100644
+--- a/src/Reactor/reactor.gni
++++ b/src/Reactor/reactor.gni
+@@ -14,7 +14,7 @@ declare_args() {
+ }
+
+ declare_args() {
+- supports_llvm = is_linux || is_chromeos || is_fuchsia || is_win || is_android
++ supports_llvm = is_linux || is_chromeos || is_fuchsia || is_win || is_android || is_ohos
+ # LLVM uses C++17 features which require macOS 10.12, while Chrome's minimum platform for x86 is 10.11.
+ # Don't build LLVM on Mac, unless we have to. This only happens on ARM64 devices, which launched with 11.0.
+ # TODO(b/174843857): Remove check for !supports_subzero once Chrome supports macOS 10.12
+diff --git a/src/System/BUILD.gn b/src/System/BUILD.gn
+index 6f61d9f76..bed58a57b 100644
+--- a/src/System/BUILD.gn
++++ b/src/System/BUILD.gn
+@@ -29,7 +29,7 @@ swiftshader_source_set("System_headers") {
+ "SwiftConfig.hpp",
+ "Timer.hpp",
+ ]
+- if (is_linux || is_chromeos || is_android) {
++ if (is_linux || is_chromeos || is_android || is_ohos) {
+ sources += [
+ "Linux/MemFd.hpp",
+ ]
+@@ -48,7 +48,7 @@ swiftshader_source_set("System") {
+ "SwiftConfig.cpp",
+ "Timer.cpp",
+ ]
+- if (is_linux || is_chromeos || is_android) {
++ if (is_linux || is_chromeos || is_android || is_ohos) {
+ sources += [
+ "Linux/MemFd.cpp",
+ ]
+diff --git a/third_party/llvm-10.0/BUILD.gn b/third_party/llvm-10.0/BUILD.gn
+index 6c40b2c00..6076f2bea 100644
+--- a/third_party/llvm-10.0/BUILD.gn
++++ b/third_party/llvm-10.0/BUILD.gn
+@@ -100,7 +100,7 @@ llvm_include_dirs = [
+ "configs/common/lib/Transforms/InstCombine/",
+ ]
+
+-if (is_linux || is_chromeos) {
++if (is_linux || is_chromeos || is_ohos) {
+ llvm_include_dirs += [ "configs/linux/include/" ]
+ } else if (is_fuchsia) {
+ llvm_include_dirs += [ "configs/fuchsia/include/" ]
+diff --git a/third_party/marl/src/thread.cpp b/third_party/marl/src/thread.cpp
+index 30897f020..a6c3031c2 100644
+--- a/third_party/marl/src/thread.cpp
++++ b/third_party/marl/src/thread.cpp
+@@ -154,14 +154,6 @@ Thread::Affinity Thread::Affinity::all(
+ auto thread = pthread_self();
+ cpu_set_t cpuset;
+ CPU_ZERO(&cpuset);
+- if (pthread_getaffinity_np(thread, sizeof(cpu_set_t), &cpuset) == 0) {
+- int count = CPU_COUNT(&cpuset);
+- for (int i = 0; i < count; i++) {
+- Core core;
+- core.pthread.index = static_cast(i);
+- affinity.cores.emplace_back(std::move(core));
+- }
+- }
+ #elif defined(__FreeBSD__)
+ auto thread = pthread_self();
+ cpuset_t cpuset;
+@@ -385,8 +377,6 @@ class Thread::Impl {
+ for (size_t i = 0; i < count; i++) {
+ CPU_SET(affinity[i].pthread.index, &cpuset);
+ }
+- auto thread = pthread_self();
+- pthread_setaffinity_np(thread, sizeof(cpu_set_t), &cpuset);
+ #elif defined(__FreeBSD__)
+ cpuset_t cpuset;
+ CPU_ZERO(&cpuset);
diff --git a/attachment/scripts/config.json b/attachment/scripts/config.json
index 9092b1a9391fc583a49e8d2837226597c069ae72..62ec1a4f35c30a76bb80f93234e2f02164c9622b 100644
--- a/attachment/scripts/config.json
+++ b/attachment/scripts/config.json
@@ -70,5 +70,11 @@
"target": "third_party/boringssl",
"type": "patch",
"file_path": "flutter/attachment/repos/boringssl.patch"
+ },
+ {
+ "name": "swiftshader",
+ "target": "third_party/swiftshader",
+ "type": "patch",
+ "file_path": "flutter/attachment/repos/swiftshader.patch"
}
]
diff --git a/attachment/scripts/config_pre.json b/attachment/scripts/config_pre.json
index 9092b1a9391fc583a49e8d2837226597c069ae72..62ec1a4f35c30a76bb80f93234e2f02164c9622b 100644
--- a/attachment/scripts/config_pre.json
+++ b/attachment/scripts/config_pre.json
@@ -70,5 +70,11 @@
"target": "third_party/boringssl",
"type": "patch",
"file_path": "flutter/attachment/repos/boringssl.patch"
+ },
+ {
+ "name": "swiftshader",
+ "target": "third_party/swiftshader",
+ "type": "patch",
+ "file_path": "flutter/attachment/repos/swiftshader.patch"
}
]
diff --git a/attachment/scripts/ohos.py b/attachment/scripts/ohos.py
index 49f44d38657caa856a78706731ed16738aaf13d7..d60c405775fcb9c163d2784360cc1b7097be03d3 100644
--- a/attachment/scripts/ohos.py
+++ b/attachment/scripts/ohos.py
@@ -28,6 +28,12 @@ from datetime import datetime
SUPPORT_BUILD_NAMES = ("clean", "config", "har", "compile", "zip", "zip2", "upload")
SUPPORT_BUILD_TYPES = ("debug", "profile", "release")
+SUPPORT_ABIS = {
+ "x86": "x86",
+ "x64": "x86_64",
+ "arm": "armeabi-v7a",
+ "arm64": "arm64-v8a",
+}
DIR_ROOT = os.path.abspath(os.path.join(sys.argv[0], os.pardir))
OS_NAME = platform.system().lower()
IS_WINDOWS = OS_NAME.startswith("win")
@@ -61,14 +67,12 @@ class BuildInfo:
buildType="release",
targetOS="ohos",
targetArch="arm64",
- targetTriple="arm64-%s-ohos" % OS_NAME,
- abi="arm64-v8a",
):
self.buildType = buildType
self.targetOS = targetOS
self.targetArch = targetArch
- self.targetTriple = targetTriple
- self.abi = abi
+ self.targetTriple = "%s-%s-%s" % (targetArch, OS_NAME, targetOS)
+ self.abi = SUPPORT_ABIS[targetArch]
def __repr__(self):
return "BuildInfo(buildType=%s)" % (self.buildType)
@@ -328,11 +332,14 @@ def addParseParam(parser):
help='Extra param to src/flutter/tools/gn. Such as: -g "\\--enable-unittests"',
)
parser.add_argument(
- "--ohos_api_int", type=int, choices=[11, 12], default=12, help="Ohos api int."
+ "--ohos_api_int", type=int, default=13, help="Ohos api int. Deprecated."
)
parser.add_argument(
"--har-unstripped", action="store_true", help="Use so.unstripped or not."
)
+ parser.add_argument(
+ "--ohos-cpu", type=str, choices=['x64', 'x86', 'arm64', 'arm'], default="arm64"
+ )
def updateCode(args):
@@ -365,7 +372,7 @@ def buildByNameAndType(args):
for buildType in SUPPORT_BUILD_TYPES:
if not buildType in buildTypes:
continue
- buildInfo = BuildInfo(buildType=buildType)
+ buildInfo = BuildInfo(buildType=buildType, targetArch=args.ohos_cpu)
for buildName in SUPPORT_BUILD_NAMES:
if not buildName in buildNames:
continue
diff --git a/attachment/scripts/ohos_create_flutter_har.py b/attachment/scripts/ohos_create_flutter_har.py
index bf21568bdfe8b10c5f4c6e61513f67daccb386c3..cc481911fe2dfe8a7faf5db65e6d39e3ad93c2c2 100644
--- a/attachment/scripts/ohos_create_flutter_har.py
+++ b/attachment/scripts/ohos_create_flutter_har.py
@@ -24,76 +24,12 @@ import subprocess
import sys
-HAR_CONFIG_TEMPLATE = """
-{
- "app": {
- "signingConfigs": [],
- "products": [
- {
- "name": "default",
- "signingConfig": "default",
- "compileSdkVersion": "%s",
- "compatibleSdkVersion": "%s",
- "runtimeOS": "HarmonyOS",
- }
- ],
- "buildModeSet": [
- {
- "name": "debug",
- },
- {
- "name": "release"
- },
- {
- "name": "profile"
- },
- ]
- },
- "modules": [
- {
- "name": "flutter",
- "srcPath": "./flutter"
- }
- ]
-}
-"""
-
-
-HVIGOR_CONFIG = """
-{
- "modelVersion": "5.0.0",
- "dependencies": {
- }
-}
-"""
-
-
def runGitCommand(command):
result = subprocess.run(command, capture_output=True, text=True, shell=True)
if result.returncode != 0:
raise Exception(f"Git command failed: {result.stderr}")
return result.stdout.strip()
-# 更新har的配置文件,指定编译使用的api版本
-def updateConfig(buildDir, apiInt):
- apiVersionMap = {
- 11: "4.1.0(11)",
- 12: "5.0.0(12)",
- }
- apiStr = apiVersionMap[apiInt]
- with open(
- os.path.join(buildDir, "build-profile.json5"), "w", encoding="utf-8"
- ) as file:
- file.write(HAR_CONFIG_TEMPLATE % (apiStr, apiStr))
-
- if apiInt != 11:
- with open(
- os.path.join(buildDir, "hvigor", "hvigor-config.json5"),
- "w",
- encoding="utf-8",
- ) as file:
- file.write(HVIGOR_CONFIG)
-
# 自动更新flutter.har的版本号,把日期加到末尾。如: 1.0.0-20240731
def updateVersion(buildDir):
@@ -137,7 +73,6 @@ def runCommand(command, checkCode=True, timeout=None):
# 编译har文件,通过hvigorw的命令行参数指定编译类型(debug/release/profile)
def buildHar(buildDir, apiInt, buildType):
- updateConfig(buildDir, apiInt)
updateVersion(buildDir)
hvigorwCommand = "hvigorw" if apiInt != 11 else (".%shvigorw" % os.sep)
runCommand(
@@ -160,7 +95,7 @@ def main():
parser.add_argument("--native_lib", action="append", help="Native code library.")
parser.add_argument("--ohos_abi", help="Native code ABI.")
parser.add_argument(
- "--ohos_api_int", type=int, choices=[11, 12], help="Ohos api int."
+ "--ohos_api_int", type=int, default=13, help="Ohos api int. Deprecated."
)
options = parser.parse_args()
# copy source code
diff --git a/fml/BUILD.gn b/fml/BUILD.gn
index 03d286857c2d7308c91dca9f445e59e484069b50..bc70dd0f79a47a5b4df373ce29a00f62158a833d 100644
--- a/fml/BUILD.gn
+++ b/fml/BUILD.gn
@@ -364,6 +364,9 @@ if (enable_unittests) {
"time/time_delta_unittest.cc",
"time/time_point_unittest.cc",
"time/time_unittest.cc",
+ "platform/ohos/timerfd_unittests.cpp",
+ "platform/ohos/paths_ohos_unittests.cpp",
+ "platform/ohos/napi_util_unittests.cpp",
]
if (is_mac) {
diff --git a/fml/platform/ohos/message_loop_ohos.cc b/fml/platform/ohos/message_loop_ohos.cc
index 5e528abe9991ce6d8d7a6b48d4ca7be19db9532a..c918e9e66ccc861f3218867f7bc9b90fdd76148a 100644
--- a/fml/platform/ohos/message_loop_ohos.cc
+++ b/fml/platform/ohos/message_loop_ohos.cc
@@ -33,7 +33,7 @@ void MessageLoopOhos::OnPollCallback(uv_poll_t* handle,
int status,
int events) {
if (status < 0) {
- FML_DLOG(ERROR) << "Poll error:" << uv_strerror(status);
+ FML_LOG(ERROR) << "Poll error:" << uv_strerror(status);
return;
}
diff --git a/fml/platform/ohos/napi_util_unittests.cpp b/fml/platform/ohos/napi_util_unittests.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..a5e67eb19f777e1800dc2b32cacbc27256398165
--- /dev/null
+++ b/fml/platform/ohos/napi_util_unittests.cpp
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2024 Huawei Device Co., Ltd.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "flutter/fml/platform/ohos/napi_util.h"
+#include "gtest/gtest.h"
+
+namespace fml {
+namespace {
+TEST(NapiUtilTest, NapiUtil01) {
+ napi_env env = nullptr;
+ napi_value value = nullptr;
+ EXPECT_EQ(napi::NapiIsNull(env, value), true);
+}
+
+TEST(NapiUtilTest, NapiUtil02) {
+ napi_env env = nullptr;
+ napi_value value = nullptr;
+ std::string str = "test";
+ EXPECT_EQ(napi::GetString(env, value, str), napi::ERROR_TYPE);
+}
+
+TEST(NapiUtilTest, NapiUtil03) {
+ napi_env env = nullptr;
+ napi_value value = nullptr;
+ std::vector vectorStr = {};
+ EXPECT_NE(napi::GetArrayString(env, value, vectorStr), napi::SUCCESS);
+}
+
+TEST(NapiUtilTest, NapiUtil04) {
+ napi_env env = nullptr;
+ napi_value value = nullptr;
+ void** message = nullptr;
+ size_t lenth = 0;
+ EXPECT_NE(napi::GetArrayBuffer(env, value, message, &lenth), napi::SUCCESS);
+}
+
+TEST(NapiUtilTest, NapiUtil05) {
+ napi_env env = nullptr;
+ napi_value value = nullptr;
+ EXPECT_EQ(napi::NapiIsType(env, value, napi_number), false);
+}
+
+TEST(NapiUtilTest, NapiUtil06) {
+ napi_env env = nullptr;
+ napi_ref ref_napi_obj = nullptr;
+ EXPECT_GT(napi::InvokeJsMethod(env, ref_napi_obj, nullptr, 0, nullptr), 0);
+}
+
+} // namespace
+} // namespace fml
\ No newline at end of file
diff --git a/fml/platform/ohos/paths_ohos_unittests.cpp b/fml/platform/ohos/paths_ohos_unittests.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..2267050aad4fcb5ece960c3593c2a31457bedec8
--- /dev/null
+++ b/fml/platform/ohos/paths_ohos_unittests.cpp
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2024 Huawei Device Co., Ltd.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "flutter/fml/platform/ohos/paths_ohos.h"
+#include "gtest/gtest.h"
+
+namespace fml {
+namespace {
+TEST(PathsTest, PathsOhosTest01) {
+ fml::paths::InitializeOhosCachesPath("test");
+ fml::UniqueFD resultFD = fml::paths::GetCachesDirectory();
+ EXPECT_FALSE(resultFD.is_valid());
+}
+
+
+} // namespace
+} // namespace fml
\ No newline at end of file
diff --git a/fml/platform/ohos/timerfd_unittests.cpp b/fml/platform/ohos/timerfd_unittests.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..6da8a3f2f74cda75708b49593684aa15bb501dba
--- /dev/null
+++ b/fml/platform/ohos/timerfd_unittests.cpp
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2024 Huawei Device Co., Ltd.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include
+#include
+#include
+#include
+#include "flutter/fml/platform/ohos/timerfd.h"
+
+#include "gtest/gtest.h"
+
+using fml::TimePoint;
+namespace fml {
+namespace {
+
+class MockFileDescriptor {
+public:
+ MockFileDescriptor()
+ {
+ pipe(mFdArray);
+ }
+
+ ~MockFileDescriptor()
+ {
+ close(mFdArray[0]);
+ close(mFdArray[1]);
+ }
+
+ int GetReadFd() const { return mFdArray[0]; }
+ int GetWriteFd() const { return mFdArray[1]; }
+
+ void WriteFireCount(uint64_t count)
+ {
+ write(mFdArray[1], &count, sizeof(count));
+ }
+
+private:
+ int mFdArray[2];
+};
+TEST(TimerfdTest, TimerRearmTest) {
+ TimePoint timeP;
+ EXPECT_EQ(TimerRearm(0, timeP), false);
+}
+
+TEST(TimerDrainTest, ReadsFireCountSuccessfully) {
+ MockFileDescriptor mockFd;
+ uint64_t expected_count = 1;
+ mockFd.WriteFireCount(expected_count);
+ EXPECT_TRUE(TimerDrain(mockFd.GetReadFd()));
+}
+
+TEST(TimerfdTest, TimerFdCreateTest) {
+ EXPECT_TRUE(timerfd_create(0, 0));
+}
+
+TEST(TimerfdTest, TimerFdSettimeTest) {
+ EXPECT_EQ(timerfd_settime(0, 0, nullptr, nullptr), -1);
+}
+
+} // namespace
+} // namespace fml
\ No newline at end of file
diff --git a/lib/ui/window/platform_configuration.cc b/lib/ui/window/platform_configuration.cc
index 5972d4aee99239553df92eb69c069d6c293b26ac..07afbfd75d4fd7ae32d0bdbcc42674cb4c8afd32 100644
--- a/lib/ui/window/platform_configuration.cc
+++ b/lib/ui/window/platform_configuration.cc
@@ -6,13 +6,13 @@
#include
+#include "flutter/fml/trace_event.h"
#include "flutter/lib/ui/compositing/scene.h"
#include "flutter/lib/ui/ui_dart_state.h"
#include "flutter/lib/ui/window/platform_message_response_dart.h"
#include "flutter/lib/ui/window/platform_message_response_dart_port.h"
#include "flutter/lib/ui/window/viewport_metrics.h"
#include "flutter/lib/ui/window/window.h"
-#include "flutter/fml/trace_event.h"
#include "third_party/tonic/converter/dart_converter.h"
#include "third_party/tonic/dart_args.h"
#include "third_party/tonic/dart_library_natives.h"
@@ -145,9 +145,6 @@ void PlatformConfiguration::DispatchPlatformMessage(
std::shared_ptr dart_state =
dispatch_platform_message_.dart_state().lock();
- FML_DLOG(INFO)
- << "DispatchPlatformMessage channel: "
- << message->channel();
if (!dart_state) {
FML_DLOG(WARNING)
<< "Dropping platform message for lack of DartState on channel: "
@@ -181,9 +178,7 @@ void PlatformConfiguration::DispatchSemanticsAction(int32_t id,
fml::MallocMapping args) {
std::shared_ptr dart_state =
dispatch_semantics_action_.dart_state().lock();
- FML_DLOG(INFO)
- << "DispatchSemanticsAction : "
- << id ;
+
if (!dart_state) {
return;
}
@@ -195,7 +190,6 @@ void PlatformConfiguration::DispatchSemanticsAction(int32_t id,
if (Dart_IsError(args_handle)) {
return;
}
-
tonic::CheckAndHandleError(tonic::DartInvoke(
dispatch_semantics_action_.Get(),
{tonic::ToDart(id), tonic::ToDart(static_cast(action)),
@@ -361,7 +355,6 @@ Dart_Handle PlatformConfigurationNativeApi::SendPortPlatformMessage(
void PlatformConfigurationNativeApi::RespondToPlatformMessage(
int response_id,
const tonic::DartByteData& data) {
- FML_DLOG(INFO)<<"PlatformConfigurationNativeApi::RespondToPlatformMessage:" << response_id;
if (Dart_IsNull(data.dart_handle())) {
UIDartState::Current()
->platform_configuration()
diff --git a/shell/common/BUILD.gn b/shell/common/BUILD.gn
index 7fa0c1b23ae702ec61d968210891c412fdc6f482..2f8b67095e0e5ba42ac68e68c4f625f00f6b1b01 100644
--- a/shell/common/BUILD.gn
+++ b/shell/common/BUILD.gn
@@ -185,6 +185,10 @@ if (enable_unittests) {
"fixtures/shelltest_screenshot.png",
"fixtures/hello_loop_2.gif",
]
+
+ if (is_ohos) {
+ dest = "/data/flutter_unittests/common"
+ }
}
shell_host_executable("shell_benchmarks") {
@@ -284,16 +288,12 @@ if (enable_unittests) {
testonly = true
sources = [
- "animator_unittests.cc",
"canvas_spy_unittests.cc",
"context_options_unittests.cc",
"engine_unittests.cc",
- "input_events_unittests.cc",
- "persistent_cache_unittests.cc",
"pipeline_unittests.cc",
"rasterizer_unittests.cc",
"resource_cache_limit_calculator_unittests.cc",
- "shell_unittests.cc",
"switches_unittests.cc",
"variable_refresh_rate_display_unittests.cc",
"vsync_waiter_unittests.cc",
@@ -310,6 +310,15 @@ if (enable_unittests) {
"//third_party/googletest:gmock",
]
+ if (!is_ohos) {
+ sources += [
+ "animator_unittests.cc",
+ "input_events_unittests.cc",
+ "persistent_cache_unittests.cc",
+ "shell_unittests.cc",
+ ]
+ }
+
if (is_fuchsia) {
sources += [ "shell_fuchsia_unittests.cc" ]
@@ -319,7 +328,7 @@ if (enable_unittests) {
"$fuchsia_sdk_root/pkg:fidl_cpp",
"$fuchsia_sdk_root/pkg:sys_cpp",
]
- } else {
+ } else if (!is_ohos) {
# TODO(63837): This test is hard-coded to use a TestGLSurface so it cannot run on fuchsia.
sources += [ "shell_io_manager_unittests.cc" ]
diff --git a/shell/config.gni b/shell/config.gni
index 85dd360cbc036d5c4276303906ecb56fbcab19ac..4db04f8b5c834f51ceed3be23d295e548416cde7 100644
--- a/shell/config.gni
+++ b/shell/config.gni
@@ -17,6 +17,6 @@ declare_args() {
test_enable_metal = shell_enable_metal
# The Vulkan unittests are combined with the GL unittests.
- test_enable_vulkan = is_fuchsia || shell_enable_gl
+ test_enable_vulkan = (is_fuchsia || shell_enable_gl) && !is_ohos
test_enable_software = shell_enable_software
}
diff --git a/shell/gpu/gpu_surface_vulkan.cc b/shell/gpu/gpu_surface_vulkan.cc
index ba8383c1343cf63020cb05440406d3b285fe206a..7743289f593248bb944aa77fbe40634c7ea11f1d 100644
--- a/shell/gpu/gpu_surface_vulkan.cc
+++ b/shell/gpu/gpu_surface_vulkan.cc
@@ -98,6 +98,7 @@ sk_sp GPUSurfaceVulkan::CreateSurfaceFromVulkanImage(
const VkImage image,
const VkFormat format,
const SkISize& size) {
+#ifdef SK_VULKAN
GrVkImageInfo image_info = {
.fImage = image,
.fImageTiling = VK_IMAGE_TILING_OPTIMAL,
@@ -126,6 +127,9 @@ sk_sp GPUSurfaceVulkan::CreateSurfaceFromVulkanImage(
SkColorSpace::MakeSRGB(), // color space
&surface_properties // surface properties
);
+#else
+ return nullptr;
+#endif // SK_VULKAN
}
SkColorType GPUSurfaceVulkan::ColorTypeFromFormat(const VkFormat format) {
diff --git a/shell/platform/embedder/BUILD.gn b/shell/platform/embedder/BUILD.gn
index 56335f45afd715add78c6f571d03c3d1239f4886..b4bd71a775873e98bc3bf9ac15f38bbe42a528a5 100644
--- a/shell/platform/embedder/BUILD.gn
+++ b/shell/platform/embedder/BUILD.gn
@@ -217,7 +217,7 @@ test_fixtures("fixtures") {
]
}
-if (enable_unittests) {
+if (enable_unittests && !is_ohos) {
source_set("embedder_unittests_library") {
testonly = true
diff --git a/shell/platform/ohos/BUILD.gn b/shell/platform/ohos/BUILD.gn
index c8e36f5796a88e0230a7095e12d56d603d38c046..84548cee193d2a885072d0b358a0806dd746d674 100644
--- a/shell/platform/ohos/BUILD.gn
+++ b/shell/platform/ohos/BUILD.gn
@@ -84,7 +84,13 @@ source_set("flutter_ohos_sources") {
"ohos_surface_gl_skia.h",
"types.h",
"ohos_logging.h",
+ "platform_view_ohos_delegate.h",
+ "./accessibility/ohos_accessibility_bridge.h",
"./accessibility/ohos_accessibility_features.h",
+ "./accessibility/ohos_accessibility_manager.h",
+ "./accessibility/native_accessibility_channel.h",
+ "./accessibility/ohos_accessibility_ddl.h",
+ "./utils/ddl_utils.h"
]
#configs += [ "//flutter/shell/platform/ohos/config:gtk" ]
@@ -115,7 +121,12 @@ source_set("flutter_ohos_sources") {
"ohos_image_generator.cpp",
"ohos_external_texture_gl.cpp",
"./surface/ohos_snapshot_surface_producer.cpp",
+ "platform_view_ohos_delegate.cpp",
+ "./accessibility/ohos_accessibility_bridge.cpp",
"./accessibility/ohos_accessibility_features.cpp",
+ "./accessibility/ohos_accessibility_manager.cpp",
+ "./accessibility/native_accessibility_channel.cpp",
+ "./accessibility/ohos_accessibility_ddl.cpp"
]
# Set flag to stop headers being directly included (library users should not do this)
@@ -153,6 +164,28 @@ source_set("flutter_ohos_sources") {
}
+# accessibility-relevant source set
+source_set("flutter_ohos_accessibility_sources") {
+ public = _public_headers + [
+ "./compatibility/ohos_accessibility_interface.h"
+ ]
+
+ sources = []
+
+ # Set flag to stop headers being directly included (library users should not do this)
+ defines = [
+ "FLUTTER_LINUX_COMPILATION",
+ "FLUTTER_ENGINE_NO_PROTOTYPES",
+ "OHOS_PLATFORM" ,
+ "__MUSL__",
+ ]
+
+ deps = []
+
+ public_deps = []
+ ldflags = ["-lace_ndk.z"]
+}
+
source_set("flutter_ohos_src") {
configs += [
#"//flutter/shell/platform/ohos/config:gtk",
@@ -176,10 +209,22 @@ executable("flutter_ohos_unittests") {
sources = [
#"testing/mock_texture_registrar.cc",
+ "testing/ohos_assert_provider_unittests.cpp",
+ "testing/ohos_context_gl_skia_uinttests.cpp",
+ "testing/ohos_display_uinttests.cpp",
+ "testing/ohos_egl_surface_unittests.cpp",
+ "testing/ohos_environment_gl_unittests.cpp",
+ "testing/ohos_external_texture_gl_unittests.cpp",
+ "testing/ohos_surface_gl_skia_unittests.cpp",
+ "testing/ohos_surface_software_unittests.cpp",
+ "testing/ohos_touch_processor_unittests.cpp",
+ "testing/ohos_xcomponent_adapter_unittests.cpp",
+ "testing/vsync_waiter_ohos_unittests.cpp",
]
public_configs = [ "//flutter:config" ]
+ cflags = ["-Wno-deprecated-declarations"]
defines = [
"FLUTTER_ENGINE_NO_PROTOTYPES",
@@ -197,6 +242,15 @@ executable("flutter_ohos_unittests") {
"//flutter/shell/platform/embedder:embedder_test_utils",
"//flutter/testing",
]
+
+ ldflags = ["-lace_ndk.z"]
+ ldflags += ["-lnative_window"]
+ ldflags += ["-lnative_vsync"]
+ ldflags += ["-limage_ndk.z"]
+ ldflags += ["-lrawfile.z"]
+ ldflags += ["-lnative_image"]
+ ldflags += ["-lpixelmap_ndk.z"]
+ ldflags += ["-lqos"]
}
shared_library("flutter_shell_native") {
@@ -205,26 +259,18 @@ shared_library("flutter_shell_native") {
ldflags = ["--rtlib=compiler-rt",
"-fuse-ld=lld",
-# "-static-libstdc++",
-# "-Wl", "--build-id=sha1",
-# "-Wl,","--warn-shared-textrel",
-# "-Wl,","--fatal-warnings -lunwind",
-# "-Wl,","--no-undefined -Qunused-arguments",
-# "-Wl,","-z,noexecstack"
]
- #ldflags = [ "-Wl,-rpath,\$ORIGIN" ]
- #ldflags += ["-L{$OHOS_NDK_LIB}","-lnative_window"]
ldflags += ["-lnative_window"]
ldflags += ["-lnative_vsync"]
ldflags += ["-lace_napi.z"]
ldflags += ["-lace_ndk.z"]
ldflags += ["-lhilog_ndk.z"]
+ ldflags += ["-ldeviceinfo_ndk.z"]
ldflags += ["-luv"]
ldflags += ["-lrawfile.z"]
ldflags += ["-lEGL"]
ldflags += ["-lGLESv3"]
- # ldflags += ["-lGLESv2"]
ldflags += ["-limage_ndk.z"]
ldflags += ["-lnative_image"]
@@ -236,45 +282,19 @@ shared_library("flutter_shell_native") {
public_configs = [ "//flutter:config" ]
}
-#copy("publish_headers_ohos") {
-# sources = _public_headers
-# outputs = [ "$root_out_dir/flutter_ohos/{{source_file_part}}" ]
-#}
-
-#zip_bundle("flutter_gtk") {
-# prefix = "$full_target_platform_name/"
-# if (flutter_runtime_mode != "debug" ||
-# (flutter_runtime_mode == "debug" && target_cpu != "x64")) {
-# prefix = "$full_target_platform_name-$flutter_runtime_mode/"
-# }
-# output = "${prefix}${full_target_platform_name}-flutter-gtk.zip"
-# deps = [
-# ":flutter_ohos_gtk",
-# ":publish_headers_ohos",
-# "//third_party/dart/runtime/bin:gen_snapshot",
-# ]
-# sources = get_target_outputs(":publish_headers_ohos")
-# tmp_files = []
-# foreach(source, sources) {
-# tmp_files += [
-# {
-# source = source
-# destination = rebase_path(source, "$root_build_dir")
-# },
-# ]
-# }
-# tmp_files += [
-# {
-# source = "$root_build_dir/libflutter_${host_os}_gtk.so"
-# destination = "libflutter_${host_os}_gtk.so"
-# },
-# {
-# source = "$root_build_dir/gen_snapshot"
-# destination = "gen_snapshot"
-# },
-# ]
-# files = tmp_files
-#}
+# accessibility-relevant .so
+shared_library("flutter_shell_native_accessibility") {
+ output_name = "flutter_accessibility"
+ deps = [ ":flutter_ohos_accessibility_sources" ]
+
+ ldflags = ["--rtlib=compiler-rt",
+ "-fuse-ld=lld",
+ ]
+
+ ldflags += ["-lace_ndk.z"]
+
+ public_configs = [ "//flutter:config" ]
+}
declare_args() {
embedding_artifact_id = "flutter_embedding_$flutter_runtime_mode"
@@ -296,8 +316,10 @@ action("ohos_har") {
if (stripped_symbols) {
engine_library = "libflutter.so"
+ accessibility_library = "libflutter_accessibility.so"
} else {
engine_library = "so.unstripped/libflutter.so"
+ accessibility_library = "libflutter_accessibility.so"
}
inputs = [
@@ -321,6 +343,8 @@ action("ohos_har") {
rebase_path("flutter.har", root_build_dir, root_build_dir),
"--native_lib",
rebase_path("$engine_library", root_build_dir, root_build_dir),
+ "--native_lib",
+ rebase_path("$accessibility_library", root_build_dir, root_build_dir),
"--ohos_abi",
ohos_app_abi,
"--ohos_api_int",
@@ -328,7 +352,8 @@ action("ohos_har") {
]
deps = [
- ":flutter_shell_native"
+ ":flutter_shell_native",
+ ":flutter_shell_native_accessibility"
]
if (flutter_runtime_mode == "profile") {
diff --git a/shell/platform/ohos/accessibility/native_accessibility_channel.cpp b/shell/platform/ohos/accessibility/native_accessibility_channel.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..1d3e8a7a3aa374ae7bbfcaea280754673fbfa46d
--- /dev/null
+++ b/shell/platform/ohos/accessibility/native_accessibility_channel.cpp
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2024 Huawei Device Co., Ltd.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "native_accessibility_channel.h"
+#include "flutter/shell/platform/ohos/ohos_shell_holder.h"
+#include "flutter/shell/platform/ohos/accessibility/ohos_accessibility_bridge.h"
+
+namespace flutter {
+
+ NativeAccessibilityChannel::NativeAccessibilityChannel() {}
+
+ NativeAccessibilityChannel::~NativeAccessibilityChannel() {}
+
+ /**
+ * 通知flutter框架ohos平台无障碍屏幕朗读已开启
+ */
+ void NativeAccessibilityChannel::OnOhosAccessibilityEnabled(int64_t shellHolderId)
+ {
+ FML_DLOG(INFO) << "NativeAccessibilityChannel -> OnOhosAccessibilityEnabled";
+ this->SetSemanticsEnabled(shellHolderId, true);
+ }
+
+ /**
+ * 通知flutter框架ohos平台无障碍屏幕朗读未开启
+ */
+ void NativeAccessibilityChannel::OnOhosAccessibilityDisabled(int64_t shellHolderId)
+ {
+ FML_DLOG(INFO) << "NativeAccessibilityChannel -> OnOhosAccessibilityDisabled";
+ this->SetSemanticsEnabled(shellHolderId, false);
+ }
+
+ /**
+ * Native无障碍通道传递语义感知,若开启则实时更新语义树信息
+ */
+ void NativeAccessibilityChannel::SetSemanticsEnabled(int64_t shellHolderId,
+ bool enabled)
+ {
+ auto ohos_shell_holder =
+ reinterpret_cast(shellHolderId);
+ ohos_shell_holder->GetPlatformView()->SetSemanticsEnabled(enabled);
+ }
+
+ /**
+ * Native无障碍通道设置无障碍特征类型,如:无障碍导航、字体加粗等
+ */
+ void NativeAccessibilityChannel::SetAccessibilityFeatures(int64_t shellHolderId,
+ int32_t flags)
+ {
+ auto ohos_shell_holder =
+ reinterpret_cast(shellHolderId);
+ ohos_shell_holder->GetPlatformView()->SetAccessibilityFeatures(flags);
+ }
+
+ /**
+ * Native无障碍通道分发flutter屏幕语义动作,如:点击、滑动等
+ */
+ void NativeAccessibilityChannel::DispatchSemanticsAction(
+ int64_t shellHolderId,
+ int32_t id,
+ flutter::SemanticsAction action,
+ fml::MallocMapping args)
+ {
+ auto ohos_shell_holder =
+ reinterpret_cast(shellHolderId);
+ ohos_shell_holder->GetPlatformView()->PlatformView::DispatchSemanticsAction(id, action, fml::MallocMapping());
+ }
+
+ /**
+ * 更新flutter无障碍相关语义信息
+ */
+ void NativeAccessibilityChannel::UpdateSemantics(
+ flutter::SemanticsNodeUpdates update,
+ flutter::CustomAccessibilityActionUpdates actions)
+ {
+ auto ohos_a11y_bridge = OhosAccessibilityBridge::GetInstance();
+ ohos_a11y_bridge->updateSemantics(update, actions);
+ }
+
+ /**
+ * 设置无障碍消息处理器,通过无障碍通道发送处理dart侧传递的相关信息
+ */
+ void NativeAccessibilityChannel::SetAccessibilityMessageHandler(
+ std::shared_ptr handler)
+ {
+ this->handler = handler;
+ }
+
+ /**
+ * 利用通道内部类AccessibilityMessageHandler处理主动播报事件
+ */
+ void NativeAccessibilityChannel::AccessibilityMessageHandler::Announce(
+ std::unique_ptr& message)
+ {
+ auto ohos_a11y_bridge = OhosAccessibilityBridge::GetInstance();
+ ohos_a11y_bridge->Announce(message);
+ }
+}
\ No newline at end of file
diff --git a/shell/platform/ohos/accessibility/native_accessibility_channel.h b/shell/platform/ohos/accessibility/native_accessibility_channel.h
new file mode 100644
index 0000000000000000000000000000000000000000..29d6e895c459d9ab18537c3e57330d0774021bd2
--- /dev/null
+++ b/shell/platform/ohos/accessibility/native_accessibility_channel.h
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2024 Huawei Device Co., Ltd.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef OHOS_NATIVE_ACCESSIBILITY_CHANNEL_H
+#define OHOS_NATIVE_ACCESSIBILITY_CHANNEL_H
+#include
+#include "flutter/fml/mapping.h"
+#include "flutter/lib/ui/semantics/semantics_node.h"
+#include "flutter/lib/ui/semantics/custom_accessibility_action.h"
+namespace flutter {
+
+class NativeAccessibilityChannel {
+ public:
+ NativeAccessibilityChannel();
+ ~NativeAccessibilityChannel();
+
+ void OnOhosAccessibilityEnabled(int64_t shellHolderId);
+
+ void OnOhosAccessibilityDisabled(int64_t shellHolderId);
+
+ void SetSemanticsEnabled(int64_t shellHolderId, bool enabled);
+
+ void SetAccessibilityFeatures(int64_t shellHolderId, int32_t flags);
+
+ void DispatchSemanticsAction(int64_t shellHolderId,
+ int32_t id,
+ flutter::SemanticsAction action,
+ fml::MallocMapping args);
+
+ void UpdateSemantics(flutter::SemanticsNodeUpdates update,
+ flutter::CustomAccessibilityActionUpdates actions);
+
+ class AccessibilityMessageHandler {
+ public:
+ void Announce(std::unique_ptr& message);
+
+ void OnTap(int32_t nodeId);
+
+ void OnLongPress(int32_t nodeId);
+
+ void OnTooltip(std::unique_ptr& message);
+ };
+
+ void SetAccessibilityMessageHandler(
+ std::shared_ptr handler);
+
+ private:
+ std::shared_ptr handler;
+};
+}
+
+#endif
\ No newline at end of file
diff --git a/shell/platform/ohos/accessibility/ohos_accessibility_bridge.cpp b/shell/platform/ohos/accessibility/ohos_accessibility_bridge.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..5bda8bfaea0e5a4f3de851e011763c650b5243fb
--- /dev/null
+++ b/shell/platform/ohos/accessibility/ohos_accessibility_bridge.cpp
@@ -0,0 +1,2470 @@
+/*
+ * Copyright (C) 2024 Huawei Device Co., Ltd.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "ohos_accessibility_bridge.h"
+#include
+#include
+#include
+#include
+#include "flutter/fml/logging.h"
+#include "flutter/shell/platform/ohos/ohos_logging.h"
+#include "flutter/shell/common/platform_view.h"
+#include "flutter/shell/platform/embedder/embedder.h"
+#include "flutter/shell/platform/ohos/ohos_shell_holder.h"
+#include "third_party/skia/include/core/SkMatrix.h"
+#include "third_party/skia/include/core/SkScalar.h"
+
+#define OHOS_API_VERSION 13
+#define ARKUI_SUCCEED_CODE 0
+#define ARKUI_FAILED_CODE -1
+#define ARKUI_BAD_PARAM_CODE -2
+#define ARKUI_OOM_CODE -3
+#define ARKUI_ACCESSIBILITY_CALL_CHECK(X) \
+ do { \
+ int32_t RET = X; \
+ if (RET != ARKUI_SUCCEED_CODE) { \
+ LOGE("Failed arkui a11y function call, error code:%{public}d", RET); \
+ } \
+ } while (false) \
+
+namespace flutter {
+
+OhosAccessibilityBridge* OhosAccessibilityBridge::bridgeInstance = nullptr;
+
+OhosAccessibilityBridge* OhosAccessibilityBridge::GetInstance()
+{
+ if(!bridgeInstance) {
+ bridgeInstance = new OhosAccessibilityBridge();
+ }
+ return bridgeInstance;
+}
+
+void OhosAccessibilityBridge::DestroyInstance() {
+ delete bridgeInstance;
+ bridgeInstance = nullptr;
+}
+
+OhosAccessibilityBridge::OhosAccessibilityBridge() {}
+
+/**
+ * 监听当前ohos平台是否开启无障碍屏幕朗读服务
+ */
+void OhosAccessibilityBridge::OnOhosAccessibilityStateChange(
+ int64_t shellHolderId,
+ bool ohosAccessibilityEnabled)
+{
+ native_shell_holder_id_ = shellHolderId;
+ nativeAccessibilityChannel_ = std::make_shared();
+ accessibilityFeatures_ = std::make_shared();
+
+ if (ohosAccessibilityEnabled) {
+ nativeAccessibilityChannel_->OnOhosAccessibilityEnabled(native_shell_holder_id_);
+ } else {
+ accessibilityFeatures_->SetAccessibleNavigation(false, native_shell_holder_id_);
+ nativeAccessibilityChannel_->OnOhosAccessibilityDisabled(native_shell_holder_id_);
+ }
+}
+
+void OhosAccessibilityBridge::SetNativeShellHolderId(int64_t id)
+{
+ this->native_shell_holder_id_ = id;
+}
+
+/**
+ * 从dart侧传递到c++侧的flutter无障碍语义树节点更新过程
+ */
+void OhosAccessibilityBridge::updateSemantics(
+ flutter::SemanticsNodeUpdates update,
+ flutter::CustomAccessibilityActionUpdates actions)
+{
+ FML_DLOG(INFO) << ("OhosAccessibilityBridge::UpdateSemantics()");
+
+ // 当flutter页面更新时,自动请求id=0节点组件获焦(滑动组件除外)
+ if (IS_FLUTTER_NAVIGATE) {
+ RequestFocusWhenPageUpdate();
+ IS_FLUTTER_NAVIGATE = false;
+ }
+
+ /** 获取并分析每个语义节点的更新属性 */
+ for (auto& item : update) {
+ // 获取当前更新的节点node
+ const flutter::SemanticsNode& node = item.second;
+
+ // set struct SemanticsNodeExtent
+ auto nodeEx = SetAndGetSemanticsNodeExtent(node);
+
+ //print semantics node and flags info for debugging
+ GetSemanticsNodeDebugInfo(node);
+ GetSemanticsFlagsDebugInfo(node);
+
+ /**
+ * 构建flutter无障碍语义节点树
+ * NOTE: 若使用g_flutterSemanticsTree.insert({node.id, node})方式
+ * 来添加新增的语义节点会导致已有key值自动忽略,不会更新原有key对应的value
+ */
+ g_flutterSemanticsTree[node.id] = node;
+
+ if(!IsNodeVisible(node)) {
+ continue;
+ }
+
+ // 获取当前flutter节点的全部子节点数量,并构建父子节点id映射关系
+ int32_t childNodeCount = node.childrenInTraversalOrder.size();
+ for (int32_t i = 0; i < childNodeCount; i++) {
+ g_parentChildIdVec.emplace_back(
+ std::make_pair(node.id, node.childrenInTraversalOrder[i]));
+ FML_DLOG(INFO) << "UpdateSemantics parentChildIdMap -> (" << node.id
+ << ", " << node.childrenInTraversalOrder[i] << ")";
+ }
+
+ // 当滑动节点产生滑动,并执行滑动处理
+ if (HasScrolled(node)) {
+ LOGD("UpdateSemantics -> has scrolled");
+ if (OH_GetSdkApiVersion() >= 13) {
+ ArkUI_AccessibilityElementInfo* (*OH_ArkUI_CreateAccessibilityElementInfo)(void) =
+ OhosAccessibilityDDL::DLLoadCreateElemInfoFunc("OH_ArkUI_CreateAccessibilityElementInfo");
+ if (OH_ArkUI_CreateAccessibilityElementInfo == nullptr) {
+ LOGE("OH_ArkUI_CreateAccessibilityElementInfo is null, %{public}s", dlerror());
+ }
+ ArkUI_AccessibilityElementInfo* _elementInfo = OH_ArkUI_CreateAccessibilityElementInfo();
+
+ FlutterNodeToElementInfoById(_elementInfo, static_cast(node.id));
+ FlutterScrollExecution(node, _elementInfo);
+
+ void (*OH_ArkUI_DestoryAccessibilityElementInfo)(ArkUI_AccessibilityElementInfo*) =
+ OhosAccessibilityDDL::DLLoadDestroyElemFunc("OH_ArkUI_DestoryAccessibilityElementInfo");
+ if (OH_ArkUI_DestoryAccessibilityElementInfo == nullptr) {
+ LOGE("OH_ArkUI_DestoryAccessibilityElementInfo is null, %{public}s", dlerror());
+ }
+ OH_ArkUI_DestoryAccessibilityElementInfo(_elementInfo);
+ _elementInfo = nullptr;
+ }
+ }
+ // 判断是否触发liveRegion活动区,当前节点是否活跃
+ if (node.HasFlag(FLAGS_::kIsLiveRegion)) {
+ FML_DLOG(INFO) << "UpdateSemantics -> LiveRegion, node.id=" << node.id;
+ FlutterPageUpdate(ArkUI_AccessibilityEventType::
+ ARKUI_ACCESSIBILITY_NATIVE_EVENT_TYPE_PAGE_CONTENT_UPDATE);
+ }
+ }
+
+ // 遍历更新的actions,并将所有的actions的id添加进actionMap
+ for (const auto& item : actions) {
+ const flutter::CustomAccessibilityAction action = item.second;
+ GetCustomActionDebugInfo(action);
+ g_actions_mp[action.id] = action;
+ }
+ // 打印flutter语义树的不同节点的属性信息
+ for (const auto& item : g_flutterSemanticsTree) {
+ FML_DLOG(INFO) << "g_flutterSemanticsTree -> {" << item.first << ", "
+ << item.second.id << "}";
+ }
+ for (const auto& item : g_parentChildIdVec) {
+ FML_DLOG(INFO) << "g_parentChildIdVec -> (" << item.first << ", "
+ << item.second << ")";
+ }
+ //打印按层次遍历排序的flutter语义树节点id数组
+ std::vector levelOrderTraversalTree = GetLevelOrderTraversalTree(0);
+ for (const auto& item: levelOrderTraversalTree) {
+ FML_DLOG(INFO) << "LevelOrderTraversalTree: { " << item << " }";
+ }
+
+ FML_DLOG(INFO) << "=== UpdateSemantics is end ===";
+}
+
+/**
+ * flutter可滑动组件的滑动逻辑处理实现
+ */
+void OhosAccessibilityBridge::FlutterScrollExecution(
+ flutter::SemanticsNode node,
+ ArkUI_AccessibilityElementInfo* elementInfoFromList)
+{
+ if (OH_GetSdkApiVersion() >= 13) {
+ double nodePosition = node.scrollPosition;
+ double nodeScrollExtentMax = node.scrollExtentMax;
+ double nodeScrollExtentMin = node.scrollExtentMin;
+ double infinity = std::numeric_limits::infinity();
+
+ // 设置flutter可滑动的最大范围值
+ if (nodeScrollExtentMax == infinity) {
+ nodeScrollExtentMax = SCROLL_EXTENT_FOR_INFINITY;
+ if (nodePosition > SCROLL_POSITION_CAP_FOR_INFINITY) {
+ nodePosition = SCROLL_POSITION_CAP_FOR_INFINITY;
+ }
+ }
+ if (nodeScrollExtentMin == infinity) {
+ nodeScrollExtentMax += SCROLL_EXTENT_FOR_INFINITY;
+ if (nodePosition < -SCROLL_POSITION_CAP_FOR_INFINITY) {
+ nodePosition = -SCROLL_POSITION_CAP_FOR_INFINITY;
+ }
+ nodePosition += SCROLL_EXTENT_FOR_INFINITY;
+ } else {
+ nodeScrollExtentMax -= node.scrollExtentMin;
+ nodePosition -= node.scrollExtentMin;
+ }
+
+ if (node.HasAction(ACTIONS_::kScrollUp) ||
+ node.HasAction(ACTIONS_::kScrollDown)) {
+ } else if (node.HasAction(ACTIONS_::kScrollLeft) ||
+ node.HasAction(ACTIONS_::kScrollRight)) {
+ }
+
+ // 当可滑动组件存在滑动子节点
+ if (node.scrollChildren > 0) {
+ // 配置当前滑动组件的子节点总数
+ int32_t itemCount = node.scrollChildren;
+ int32_t (*OH_ArkUI_AccessibilityElementInfoSetItemCount)(ArkUI_AccessibilityElementInfo*, int32_t) =
+ OhosAccessibilityDDL::DLLoadSetElemIntFunc("OH_ArkUI_AccessibilityElementInfoSetItemCount");
+ if (OH_ArkUI_AccessibilityElementInfoSetItemCount == nullptr) {
+ LOGE("OH_ArkUI_AccessibilityElementInfoSetItemCount is null, %{public}s", dlerror());
+ }
+ ARKUI_ACCESSIBILITY_CALL_CHECK(OH_ArkUI_AccessibilityElementInfoSetItemCount(elementInfoFromList, itemCount));
+
+ // 设置当前页面可见的起始滑动index
+ int32_t startItemIndex = node.scrollIndex;
+ int32_t (*OH_ArkUI_AccessibilityElementInfoSetStartItemIndex)(ArkUI_AccessibilityElementInfo*, int32_t) =
+ OhosAccessibilityDDL::DLLoadSetElemIntFunc("OH_ArkUI_AccessibilityElementInfoSetStartItemIndex");
+ if (OH_ArkUI_AccessibilityElementInfoSetStartItemIndex == nullptr) {
+ LOGE("OH_ArkUI_AccessibilityElementInfoSetStartItemIndex is null, %{public}s", dlerror());
+ }
+ ARKUI_ACCESSIBILITY_CALL_CHECK(OH_ArkUI_AccessibilityElementInfoSetStartItemIndex(elementInfoFromList, startItemIndex));
+
+ // 计算当前滑动位置页面的可见子滑动节点数量
+ int visibleChildren = 0;
+ // handle hidden children at the beginning and end of the list.
+ for (const auto& childId : node.childrenInHitTestOrder) {
+ auto childNode = GetFlutterSemanticsNode(childId);
+ if (!childNode.HasFlag(FLAGS_::kIsHidden)) {
+ visibleChildren += 1;
+ }
+ }
+ // 当可见滑动子节点数量超过滑动组件总子节点数量
+ if (node.scrollIndex + visibleChildren > node.scrollChildren) {
+ FML_DLOG(WARNING)
+ << "FlutterScrollExecution -> Scroll index is out of bounds";
+ }
+ // 当滑动击中子节点数量为0
+ if (!node.childrenInHitTestOrder.size()) {
+ FML_DLOG(WARNING) << "FlutterScrollExecution -> Had scrollChildren but no "
+ "childrenInHitTestOrder";
+ }
+ // 设置当前页面可见的末尾滑动index
+ int32_t endItemIndex = node.scrollIndex + visibleChildren - 1;
+ int32_t (*OH_ArkUI_AccessibilityElementInfoSetEndItemIndex)(ArkUI_AccessibilityElementInfo*, int32_t) =
+ OhosAccessibilityDDL::DLLoadSetElemIntFunc("OH_ArkUI_AccessibilityElementInfoSetEndItemIndex");
+ if (OH_ArkUI_AccessibilityElementInfoSetEndItemIndex == nullptr) {
+ LOGE("OH_ArkUI_AccessibilityElementInfoSetEndItemIndex is null, %{public}s", dlerror());
+ }
+ ARKUI_ACCESSIBILITY_CALL_CHECK(
+ OH_ArkUI_AccessibilityElementInfoSetEndItemIndex(elementInfoFromList, endItemIndex)
+ );
+ }
+ }
+}
+
+/**
+ * 当页面内容/状态更新事件,在页面转换、切换、调整大小时发送页面状态更新事件
+ */
+void OhosAccessibilityBridge::FlutterPageUpdate(
+ ArkUI_AccessibilityEventType eventType)
+{
+ if (provider_ == nullptr) {
+ FML_DLOG(ERROR) << "PageStateUpdate ->"
+ "AccessibilityProvider = nullptr";
+ return;
+ }
+ if (OH_GetSdkApiVersion() >= 13) {
+ ArkUI_AccessibilityEventInfo* (*OH_ArkUI_CreateAccessibilityEventInfo)(void) =
+ OhosAccessibilityDDL::DLLoadCreateEventInfoFunc("OH_ArkUI_CreateAccessibilityEventInfo");
+ if (OH_ArkUI_CreateAccessibilityEventInfo == nullptr) {
+ LOGE("OH_ArkUI_CreateAccessibilityEventInfo is null, %{public}s", dlerror());
+ }
+ ArkUI_AccessibilityEventInfo* pageUpdateEventInfo = OH_ArkUI_CreateAccessibilityEventInfo();
+
+ int32_t (*OH_ArkUI_AccessibilityEventSetEventType)(ArkUI_AccessibilityEventInfo*, ArkUI_AccessibilityEventType) =
+ OhosAccessibilityDDL::DLLoadSetEventFunc("OH_ArkUI_AccessibilityEventSetEventType");
+ if (OH_ArkUI_AccessibilityEventSetEventType == nullptr) {
+ LOGE("OH_ArkUI_AccessibilityEventSetEventType is null, %{public}s", dlerror());
+ }
+ ARKUI_ACCESSIBILITY_CALL_CHECK(
+ OH_ArkUI_AccessibilityEventSetEventType(pageUpdateEventInfo, eventType)
+ );
+
+ auto callback = [](int32_t errorCode) {
+ FML_DLOG(WARNING) << "PageStateUpdate callback-> errorCode =" << errorCode;
+ };
+
+ void (*OH_ArkUI_SendAccessibilityAsyncEvent)(ArkUI_AccessibilityProvider*, ArkUI_AccessibilityEventInfo*, void (*callback)(int32_t)) =
+ OhosAccessibilityDDL::DLLoadSendAsyncEventFunc("OH_ArkUI_SendAccessibilityAsyncEvent");
+ if (OH_ArkUI_SendAccessibilityAsyncEvent == nullptr) {
+ LOGE("OH_ArkUI_SendAccessibilityAsyncEvent is null, %{public}s", dlerror());
+ }
+ OH_ArkUI_SendAccessibilityAsyncEvent(provider_, pageUpdateEventInfo, callback);
+
+ void (*OH_ArkUI_DestoryAccessibilityEventInfo)(ArkUI_AccessibilityEventInfo*) =
+ OhosAccessibilityDDL::DLLoadDestroyEventFunc("OH_ArkUI_DestoryAccessibilityEventInfo");
+ if (OH_ArkUI_DestoryAccessibilityEventInfo == nullptr) {
+ LOGE("OH_ArkUI_DestoryAccessibilityEventInfo is null, %{public}s", dlerror());
+ }
+ OH_ArkUI_DestoryAccessibilityEventInfo(pageUpdateEventInfo);
+ pageUpdateEventInfo = nullptr;
+ }
+}
+
+/**
+ * 特定节点的焦点请求 (当页面更新时自动请求id=0节点获焦)
+ */
+void OhosAccessibilityBridge::RequestFocusWhenPageUpdate()
+{
+ if (provider_ == nullptr) {
+ FML_DLOG(ERROR) << "RequestFocusWhenPageUpdate ->"
+ "AccessibilityProvider = nullptr";
+ return;
+ }
+ if (OH_GetSdkApiVersion() >= 13) {
+ ArkUI_AccessibilityEventInfo* (*OH_ArkUI_CreateAccessibilityEventInfo)(void) =
+ OhosAccessibilityDDL::DLLoadCreateEventInfoFunc("OH_ArkUI_CreateAccessibilityEventInfo");
+ if (OH_ArkUI_CreateAccessibilityEventInfo == nullptr) {
+ LOGE("OH_ArkUI_CreateAccessibilityEventInfo is null, %{public}s", dlerror());
+ }
+ ArkUI_AccessibilityEventInfo* reqFocusEventInfo = OH_ArkUI_CreateAccessibilityEventInfo();
+
+ ArkUI_AccessibilityElementInfo* (*OH_ArkUI_CreateAccessibilityElementInfo)(void) =
+ OhosAccessibilityDDL::DLLoadCreateElemInfoFunc("OH_ArkUI_CreateAccessibilityElementInfo");
+ if (OH_ArkUI_CreateAccessibilityElementInfo == nullptr) {
+ LOGE("OH_ArkUI_CreateAccessibilityElementInfo is null, %{public}s", dlerror());
+ }
+ ArkUI_AccessibilityElementInfo* elementInfo = OH_ArkUI_CreateAccessibilityElementInfo();
+
+ int32_t (*OH_ArkUI_AccessibilityEventSetEventType)(ArkUI_AccessibilityEventInfo*, ArkUI_AccessibilityEventType) =
+ OhosAccessibilityDDL::DLLoadSetEventFunc("OH_ArkUI_AccessibilityEventSetEventType");
+ if (OH_ArkUI_AccessibilityEventSetEventType == nullptr) {
+ LOGE("OH_ArkUI_AccessibilityEventSetEventType is null, %{public}s", dlerror());
+ }
+ ARKUI_ACCESSIBILITY_CALL_CHECK(
+ OH_ArkUI_AccessibilityEventSetEventType(
+ reqFocusEventInfo,
+ ArkUI_AccessibilityEventType::
+ ARKUI_ACCESSIBILITY_NATIVE_EVENT_TYPE_REQUEST_ACCESSIBILITY_FOCUS)
+ );
+
+ int32_t requestFocusId = 0;
+ int32_t (*OH_ArkUI_AccessibilityEventSetRequestFocusId)(ArkUI_AccessibilityEventInfo*, int32_t) =
+ OhosAccessibilityDDL::DLLoadSetReqFocusFunc("OH_ArkUI_AccessibilityEventSetRequestFocusId");
+ if (OH_ArkUI_AccessibilityEventSetRequestFocusId == nullptr) {
+ LOGE("OH_ArkUI_AccessibilityEventSetRequestFocusId is null, %{public}s", dlerror());
+ }
+ ARKUI_ACCESSIBILITY_CALL_CHECK(
+ OH_ArkUI_AccessibilityEventSetRequestFocusId(reqFocusEventInfo,
+ requestFocusId)
+ );
+
+ int32_t (*OH_ArkUI_AccessibilityEventSetElementInfo)(ArkUI_AccessibilityEventInfo*, ArkUI_AccessibilityElementInfo*) =
+ OhosAccessibilityDDL::DLLoadSetEventElemFunc("OH_ArkUI_AccessibilityEventSetElementInfo");
+ if (OH_ArkUI_AccessibilityEventSetElementInfo == nullptr) {
+ LOGE("OH_ArkUI_AccessibilityEventSetElementInfo is null, %{public}s", dlerror());
+ }
+ ARKUI_ACCESSIBILITY_CALL_CHECK(
+ OH_ArkUI_AccessibilityEventSetElementInfo(reqFocusEventInfo, elementInfo)
+ );
+
+ auto callback = [](int32_t errorCode) {
+ FML_DLOG(WARNING) << "PageStateUpdate callback-> errorCode =" << errorCode;
+ };
+
+ void (*OH_ArkUI_SendAccessibilityAsyncEvent)(ArkUI_AccessibilityProvider*, ArkUI_AccessibilityEventInfo*, void (*callback)(int32_t)) =
+ OhosAccessibilityDDL::DLLoadSendAsyncEventFunc("OH_ArkUI_SendAccessibilityAsyncEvent");
+ if (OH_ArkUI_SendAccessibilityAsyncEvent == nullptr) {
+ LOGE("OH_ArkUI_SendAccessibilityAsyncEvent is null, %{public}s", dlerror());
+ }
+ OH_ArkUI_SendAccessibilityAsyncEvent(provider_, reqFocusEventInfo, callback);
+
+ void (*OH_ArkUI_DestoryAccessibilityEventInfo)(ArkUI_AccessibilityEventInfo*) =
+ OhosAccessibilityDDL::DLLoadDestroyEventFunc("OH_ArkUI_DestoryAccessibilityEventInfo");
+ if (OH_ArkUI_DestoryAccessibilityEventInfo == nullptr) {
+ LOGE("OH_ArkUI_DestoryAccessibilityEventInfo is null, %{public}s", dlerror());
+ }
+ OH_ArkUI_DestoryAccessibilityEventInfo(reqFocusEventInfo);
+ reqFocusEventInfo = nullptr;
+
+ void (*OH_ArkUI_DestoryAccessibilityElementInfo)(ArkUI_AccessibilityElementInfo*) =
+ OhosAccessibilityDDL::DLLoadDestroyElemFunc("OH_ArkUI_DestoryAccessibilityElementInfo");
+ if (OH_ArkUI_DestoryAccessibilityElementInfo == nullptr) {
+ LOGE("OH_ArkUI_DestoryAccessibilityElementInfo is null, %{public}s", dlerror());
+ }
+ OH_ArkUI_DestoryAccessibilityElementInfo(elementInfo);
+ elementInfo = nullptr;
+ }
+}
+
+/**
+ * 主动播报特定文本
+ */
+void OhosAccessibilityBridge::Announce(std::unique_ptr& message)
+{
+ if (provider_ == nullptr) {
+ FML_DLOG(ERROR) << "announce ->"
+ "AccessibilityProvider = nullptr";
+ return;
+ }
+ if (OH_GetSdkApiVersion() >= 13) {
+ // 创建并设置屏幕朗读事件
+ ArkUI_AccessibilityEventInfo* (*OH_ArkUI_CreateAccessibilityEventInfo)(void) =
+ OhosAccessibilityDDL::DLLoadCreateEventInfoFunc("OH_ArkUI_CreateAccessibilityEventInfo");
+ if (OH_ArkUI_CreateAccessibilityEventInfo == nullptr) {
+ LOGE("OH_ArkUI_CreateAccessibilityEventInfo is null, %{public}s", dlerror());
+ }
+ ArkUI_AccessibilityEventInfo* announceEventInfo = OH_ArkUI_CreateAccessibilityEventInfo();
+
+
+ int32_t (*OH_ArkUI_AccessibilityEventSetEventType)(ArkUI_AccessibilityEventInfo*, ArkUI_AccessibilityEventType) =
+ OhosAccessibilityDDL::DLLoadSetEventFunc("OH_ArkUI_AccessibilityEventSetEventType");
+ if (OH_ArkUI_AccessibilityEventSetEventType == nullptr) {
+ LOGE("OH_ArkUI_AccessibilityEventSetEventType is null, %{public}s", dlerror());
+ }
+ ARKUI_ACCESSIBILITY_CALL_CHECK(
+ OH_ArkUI_AccessibilityEventSetEventType(
+ announceEventInfo,
+ ArkUI_AccessibilityEventType::ARKUI_ACCESSIBILITY_NATIVE_EVENT_TYPE_ANNOUNCE_FOR_ACCESSIBILITY)
+ );
+
+ int32_t (*OH_ArkUI_AccessibilityEventSetTextAnnouncedForAccessibility)(ArkUI_AccessibilityEventInfo*, const char*) =
+ OhosAccessibilityDDL::DLLoadSetEventStringFunc("OH_ArkUI_AccessibilityEventSetTextAnnouncedForAccessibility");
+ if (OH_ArkUI_AccessibilityEventSetTextAnnouncedForAccessibility == nullptr) {
+ LOGE("OH_ArkUI_AccessibilityEventSetTextAnnouncedForAccessibility is null, %{public}s", dlerror());
+ }
+ ARKUI_ACCESSIBILITY_CALL_CHECK(
+ OH_ArkUI_AccessibilityEventSetTextAnnouncedForAccessibility(
+ announceEventInfo, message.get())
+ );
+ FML_DLOG(INFO) << ("announce -> message: ") << (message.get());
+
+ auto callback = [](int32_t errorCode) {
+ FML_DLOG(WARNING) << "announce callback-> errorCode =" << errorCode;
+ };
+
+ void (*OH_ArkUI_SendAccessibilityAsyncEvent)(ArkUI_AccessibilityProvider*, ArkUI_AccessibilityEventInfo*, void (*callback)(int32_t)) =
+ OhosAccessibilityDDL::DLLoadSendAsyncEventFunc("OH_ArkUI_SendAccessibilityAsyncEvent");
+ if (OH_ArkUI_SendAccessibilityAsyncEvent == nullptr) {
+ LOGE("OH_ArkUI_SendAccessibilityAsyncEvent is null, %{public}s", dlerror());
+ }
+ OH_ArkUI_SendAccessibilityAsyncEvent(provider_, announceEventInfo, callback);
+
+ void (*OH_ArkUI_DestoryAccessibilityEventInfo)(ArkUI_AccessibilityEventInfo*) =
+ OhosAccessibilityDDL::DLLoadDestroyEventFunc("OH_ArkUI_DestoryAccessibilityEventInfo");
+ if (OH_ArkUI_DestoryAccessibilityEventInfo == nullptr) {
+ LOGE("OH_ArkUI_DestoryAccessibilityEventInfo is null, %{public}s", dlerror());
+ }
+ OH_ArkUI_DestoryAccessibilityEventInfo(announceEventInfo);
+ announceEventInfo = nullptr;
+ }
+}
+
+//获取根节点
+flutter::SemanticsNode OhosAccessibilityBridge::GetFlutterRootSemanticsNode()
+{
+ if (!g_flutterSemanticsTree.size()) {
+ FML_DLOG(ERROR)
+ << "GetFlutterRootSemanticsNode -> g_flutterSemanticsTree.size()=0";
+ return {};
+ }
+ if (g_flutterSemanticsTree.find(0) == g_flutterSemanticsTree.end()) {
+ FML_DLOG(ERROR) << "GetFlutterRootSemanticsNode -> g_flutterSemanticsTree "
+ "has no keys = 0";
+ return {};
+ }
+ return g_flutterSemanticsTree.at(0);
+}
+
+/**
+ * 根据nodeid获取或创建flutter语义节点
+ */
+flutter::SemanticsNode OhosAccessibilityBridge::GetFlutterSemanticsNode(
+ int32_t id)
+{
+ flutter::SemanticsNode node;
+ if (g_flutterSemanticsTree.count(id) > 0) {
+ return g_flutterSemanticsTree.at(id);
+ FML_DLOG(INFO) << "GetFlutterSemanticsNode get node.id=" << id;
+ } else {
+ FML_DLOG(ERROR) << "GetFlutterSemanticsNode g_flutterSemanticsTree = null" << id;
+ return {};
+ }
+}
+
+/**
+ * flutter的语义节点初始化配置给arkui创建的elementInfos
+ */
+void OhosAccessibilityBridge::FlutterTreeToArkuiTree(
+ ArkUI_AccessibilityElementInfoList* elementInfoList)
+{
+ if (g_flutterSemanticsTree.size() == 0) {
+ FML_DLOG(ERROR) << "OhosAccessibilityBridge::FlutterTreeToArkuiTree "
+ "g_flutterSemanticsTree.size() = 0";
+ return;
+ }
+ if (OH_GetSdkApiVersion() >= 13) {
+ // 将flutter语义节点树传递给arkui的无障碍elementinfo
+ for (const auto& item : g_flutterSemanticsTree) {
+ flutter::SemanticsNode flutterNode = item.second;
+
+ // 创建elementinfo,系统自动加入到elementinfolist
+ ArkUI_AccessibilityElementInfo* (*OH_ArkUI_AddAndGetAccessibilityElementInfo)(ArkUI_AccessibilityElementInfoList*) =
+ OhosAccessibilityDDL::DLLoadGetElemFunc("OH_ArkUI_AddAndGetAccessibilityElementInfo");
+ if (OH_ArkUI_AddAndGetAccessibilityElementInfo == nullptr) {
+ LOGE("OH_ArkUI_AddAndGetAccessibilityElementInfo is null, %{public}s", dlerror());
+ }
+ ArkUI_AccessibilityElementInfo* elementInfo =
+ OH_ArkUI_AddAndGetAccessibilityElementInfo(elementInfoList);
+ if (elementInfo == nullptr) {
+ FML_DLOG(INFO) << "OhosAccessibilityBridge::FlutterTreeToArkuiTree "
+ "elementInfo is null";
+ return;
+ }
+ // 设置elementinfo的屏幕坐标范围
+ int32_t left = static_cast(flutterNode.rect.fLeft);
+ int32_t top = static_cast(flutterNode.rect.fTop);
+ int32_t right = static_cast(flutterNode.rect.fRight);
+ int32_t bottom = static_cast(flutterNode.rect.fBottom);
+ ArkUI_AccessibleRect rect = {left, top, right, bottom};
+
+ int32_t (*OH_ArkUI_AccessibilityElementInfoSetScreenRect)(ArkUI_AccessibilityElementInfo*, ArkUI_AccessibleRect*) =
+ OhosAccessibilityDDL::DLLoadSetElemSreenRectFunc("OH_ArkUI_AccessibilityElementInfoSetScreenRect");
+ if (OH_ArkUI_AccessibilityElementInfoSetScreenRect == nullptr) {
+ LOGE("OH_ArkUI_AccessibilityElementInfoSetScreenRect is null, %{public}s", dlerror());
+ }
+ ARKUI_ACCESSIBILITY_CALL_CHECK(
+ OH_ArkUI_AccessibilityElementInfoSetScreenRect(elementInfo, &rect)
+ );
+
+ // 设置elementinfo的action类型
+ std::string widget_type = GetNodeComponentType(flutterNode);
+ FlutterSetElementInfoOperationActions(elementInfo, widget_type);
+
+ // 设置elementid
+ int32_t (*OH_ArkUI_AccessibilityElementInfoSetElementId)(ArkUI_AccessibilityElementInfo*, int32_t) =
+ OhosAccessibilityDDL::DLLoadSetElemIntFunc("OH_ArkUI_AccessibilityElementInfoSetElementId");
+ if (OH_ArkUI_AccessibilityElementInfoSetElementId == nullptr) {
+ LOGE("OH_ArkUI_AccessibilityElementInfoSetElementId is null, %{public}s", dlerror());
+ }
+ ARKUI_ACCESSIBILITY_CALL_CHECK(
+ OH_ArkUI_AccessibilityElementInfoSetElementId(elementInfo, flutterNode.id)
+ );
+
+ // 设置父节点id
+ int32_t parentId = GetParentId(flutterNode.id);
+ int32_t (*OH_ArkUI_AccessibilityElementInfoSetParentId)(ArkUI_AccessibilityElementInfo*, int32_t) =
+ OhosAccessibilityDDL::DLLoadSetElemIntFunc("OH_ArkUI_AccessibilityElementInfoSetParentId");
+ if (OH_ArkUI_AccessibilityElementInfoSetParentId == nullptr) {
+ LOGE("OH_ArkUI_AccessibilityElementInfoSetParentId is null, %{public}s", dlerror());
+ }
+ if (flutterNode.id == 0) {
+ ARKUI_ACCESSIBILITY_CALL_CHECK(
+ OH_ArkUI_AccessibilityElementInfoSetParentId(elementInfo, ARKUI_ACCESSIBILITY_ROOT_PARENT_ID)
+ );
+ } else {
+ ARKUI_ACCESSIBILITY_CALL_CHECK(OH_ArkUI_AccessibilityElementInfoSetParentId(elementInfo, parentId));
+ }
+
+ // 设置孩子节点
+ int32_t childCount = flutterNode.childrenInTraversalOrder.size();
+ auto childrenIdsVec = flutterNode.childrenInTraversalOrder;
+ std::sort(childrenIdsVec.begin(), childrenIdsVec.end());
+ int64_t childNodeIds[childCount];
+ for (int32_t i = 0; i < childCount; i++) {
+ childNodeIds[i] = static_cast(childrenIdsVec[i]);
+ FML_DLOG(INFO) << "FlutterTreeToArkuiTree flutterNode.id= "
+ << flutterNode.id << " childCount= " << childCount
+ << " childNodeId=" << childNodeIds[i];
+ }
+ int32_t (*OH_ArkUI_AccessibilityElementInfoSetChildNodeIds)(ArkUI_AccessibilityElementInfo*, int32_t, int64_t*) =
+ OhosAccessibilityDDL::DLLoadSetElemChildFunc("OH_ArkUI_AccessibilityElementInfoSetChildNodeIds");
+ if (OH_ArkUI_AccessibilityElementInfoSetChildNodeIds == nullptr) {
+ LOGE("OH_ArkUI_AccessibilityElementInfoSetChildNodeIds is null, %{public}s", dlerror());
+ }
+ ARKUI_ACCESSIBILITY_CALL_CHECK(
+ OH_ArkUI_AccessibilityElementInfoSetChildNodeIds(elementInfo, childCount, childNodeIds)
+ );
+
+ // 配置常用属性
+ int32_t (*OH_ArkUI_AccessibilityElementInfoSetEnabled)(ArkUI_AccessibilityElementInfo*, bool) =
+ OhosAccessibilityDDL::DLLoadSetElemBoolFunc("OH_ArkUI_AccessibilityElementInfoSetEnabled");
+ if (OH_ArkUI_AccessibilityElementInfoSetEnabled == nullptr) {
+ LOGE("OH_ArkUI_AccessibilityElementInfoSetEnabled is null, %{public}s", dlerror());
+ }
+ ARKUI_ACCESSIBILITY_CALL_CHECK(OH_ArkUI_AccessibilityElementInfoSetEnabled(elementInfo, true));
+
+ int32_t (*OH_ArkUI_AccessibilityElementInfoSetClickable)(ArkUI_AccessibilityElementInfo*, bool) =
+ OhosAccessibilityDDL::DLLoadSetElemBoolFunc("OH_ArkUI_AccessibilityElementInfoSetClickable");
+ if (OH_ArkUI_AccessibilityElementInfoSetClickable == nullptr) {
+ LOGE("OH_ArkUI_AccessibilityElementInfoSetClickable is null, %{public}s", dlerror());
+ }
+ ARKUI_ACCESSIBILITY_CALL_CHECK(OH_ArkUI_AccessibilityElementInfoSetClickable(elementInfo, true));
+ int32_t (*OH_ArkUI_AccessibilityElementInfoSetCheckable)(ArkUI_AccessibilityElementInfo*, bool) =
+ OhosAccessibilityDDL::DLLoadSetElemBoolFunc("OH_ArkUI_AccessibilityElementInfoSetCheckable");
+ if (OH_ArkUI_AccessibilityElementInfoSetCheckable == nullptr) {
+ LOGE("OH_ArkUI_AccessibilityElementInfoSetCheckable is null, %{public}s", dlerror());
+ }
+ ARKUI_ACCESSIBILITY_CALL_CHECK(OH_ArkUI_AccessibilityElementInfoSetCheckable(elementInfo, true));
+
+ int32_t (*OH_ArkUI_AccessibilityElementInfoSetFocusable)(ArkUI_AccessibilityElementInfo*, bool) =
+ OhosAccessibilityDDL::DLLoadSetElemBoolFunc("OH_ArkUI_AccessibilityElementInfoSetFocusable");
+ if (OH_ArkUI_AccessibilityElementInfoSetFocusable == nullptr) {
+ LOGE("OH_ArkUI_AccessibilityElementInfoSetFocusable is null, %{public}s", dlerror());
+ }
+ ARKUI_ACCESSIBILITY_CALL_CHECK(OH_ArkUI_AccessibilityElementInfoSetFocusable(elementInfo, true));
+
+ int32_t (*OH_ArkUI_AccessibilityElementInfoSetVisible)(ArkUI_AccessibilityElementInfo*, bool) =
+ OhosAccessibilityDDL::DLLoadSetElemBoolFunc("OH_ArkUI_AccessibilityElementInfoSetVisible");
+ if (OH_ArkUI_AccessibilityElementInfoSetVisible == nullptr) {
+ LOGE("OH_ArkUI_AccessibilityElementInfoSetVisible is null, %{public}s", dlerror());
+ }
+ ARKUI_ACCESSIBILITY_CALL_CHECK(OH_ArkUI_AccessibilityElementInfoSetVisible(elementInfo, true));
+
+ // 设置组件类型
+ std::string componentTypeName = GetNodeComponentType(flutterNode);
+ int32_t (*OH_ArkUI_AccessibilityElementInfoSetComponentType)(ArkUI_AccessibilityElementInfo*, const char*) =
+ OhosAccessibilityDDL::DLLoadSetElemStringFunc("OH_ArkUI_AccessibilityElementInfoSetComponentType");
+ if (OH_ArkUI_AccessibilityElementInfoSetComponentType == nullptr) {
+ LOGE("OH_ArkUI_AccessibilityElementInfoSetComponentType is null, %{public}s", dlerror());
+ }
+ ARKUI_ACCESSIBILITY_CALL_CHECK(
+ OH_ArkUI_AccessibilityElementInfoSetComponentType(elementInfo, componentTypeName.c_str())
+ );
+
+ std::string contents = componentTypeName + "_content";
+ int32_t (*OH_ArkUI_AccessibilityElementInfoSetContents)(ArkUI_AccessibilityElementInfo*, const char*) =
+ OhosAccessibilityDDL::DLLoadSetElemStringFunc("OH_ArkUI_AccessibilityElementInfoSetContents");
+ if (OH_ArkUI_AccessibilityElementInfoSetContents == nullptr) {
+ LOGE("OH_ArkUI_AccessibilityElementInfoSetContents is null, %{public}s", dlerror());
+ }
+ ARKUI_ACCESSIBILITY_CALL_CHECK(
+ OH_ArkUI_AccessibilityElementInfoSetContents(elementInfo, contents.c_str())
+ );
+
+ // 设置无障碍相关属性
+ int32_t (*OH_ArkUI_AccessibilityElementInfoSetAccessibilityText)(ArkUI_AccessibilityElementInfo*, const char*) =
+ OhosAccessibilityDDL::DLLoadSetElemStringFunc("OH_ArkUI_AccessibilityElementInfoSetAccessibilityText");
+ if (OH_ArkUI_AccessibilityElementInfoSetAccessibilityText == nullptr) {
+ LOGE("OH_ArkUI_AccessibilityElementInfoSetAccessibilityText is null, %{public}s", dlerror());
+ }
+ ARKUI_ACCESSIBILITY_CALL_CHECK(
+ OH_ArkUI_AccessibilityElementInfoSetAccessibilityText(elementInfo, flutterNode.label.c_str())
+ );
+
+ int32_t (*OH_ArkUI_AccessibilityElementInfoSetAccessibilityLevel)(ArkUI_AccessibilityElementInfo*, const char*) =
+ OhosAccessibilityDDL::DLLoadSetElemStringFunc("OH_ArkUI_AccessibilityElementInfoSetAccessibilityLevel");
+ if (OH_ArkUI_AccessibilityElementInfoSetAccessibilityLevel == nullptr) {
+ LOGE("OH_ArkUI_AccessibilityElementInfoSetAccessibilityLevel is null, %{public}s", dlerror());
+ }
+ ARKUI_ACCESSIBILITY_CALL_CHECK(OH_ArkUI_AccessibilityElementInfoSetAccessibilityLevel(elementInfo, "yes"));
+
+ int32_t (*OH_ArkUI_AccessibilityElementInfoSetAccessibilityGroup)(ArkUI_AccessibilityElementInfo*, bool) =
+ OhosAccessibilityDDL::DLLoadSetElemBoolFunc("OH_ArkUI_AccessibilityElementInfoSetAccessibilityGroup");
+ if (OH_ArkUI_AccessibilityElementInfoSetAccessibilityGroup == nullptr) {
+ LOGE("OH_ArkUI_AccessibilityElementInfoSetAccessibilityGroup is null, %{public}s", dlerror());
+ }
+ ARKUI_ACCESSIBILITY_CALL_CHECK(OH_ArkUI_AccessibilityElementInfoSetAccessibilityGroup(elementInfo, false));
+ }
+ }
+}
+
+/**
+ * 获取当前elementid的父节点id
+ */
+int32_t OhosAccessibilityBridge::GetParentId(int64_t elementId)
+{
+ if (!g_parentChildIdVec.size()) {
+ FML_DLOG(INFO) << "OhosAccessibilityBridge::GetParentId parentChildIdMap.size()=0";
+ return ARKUI_ACCESSIBILITY_ROOT_PARENT_ID;
+ }
+ if (elementId == -1) {
+ return ARKUI_ACCESSIBILITY_ROOT_PARENT_ID;
+ }
+ int32_t childElementId = static_cast(elementId);
+ for (const auto& item : g_parentChildIdVec) {
+ if (item.second == childElementId) {
+ return item.first;
+ }
+ }
+ return RET_ERROR_STATE_CODE;
+}
+
+/**
+ * 设置并获取xcomponet上渲染的组件的屏幕绝对坐标rect
+ */
+void OhosAccessibilityBridge::SetAbsoluteScreenRect(int32_t flutterNodeId,
+ float left,
+ float top,
+ float right,
+ float bottom)
+{
+ g_screenRectMap[flutterNodeId] =
+ std::make_pair(std::make_pair(left, top), std::make_pair(right, bottom));
+ FML_DLOG(INFO) << "SetAbsoluteScreenRect -> insert { " << flutterNodeId
+ << ", <" << left << ", " << top << ", " << right << ", "
+ << bottom << "> } is succeed";
+}
+
+std::pair, std::pair>
+OhosAccessibilityBridge::GetAbsoluteScreenRect(int32_t flutterNodeId)
+{
+ if (!g_screenRectMap.empty() && g_screenRectMap.count(flutterNodeId) > 0) {
+ return g_screenRectMap.at(flutterNodeId);
+ } else {
+ FML_DLOG(ERROR) << "GetAbsoluteScreenRect -> flutterNodeId="
+ << flutterNodeId << " is not found !";
+ return {};
+ }
+}
+
+/**
+ * flutter无障碍语义树的子节点相对坐标系转化为屏幕绝对坐标的映射算法
+ * 目前暂未考虑旋转、透视场景,不影响屏幕朗读功能
+ */
+void OhosAccessibilityBridge::ConvertChildRelativeRectToScreenRect(
+ flutter::SemanticsNode currNode)
+{
+ // 获取当前flutter节点的相对rect
+ auto currLeft = static_cast(currNode.rect.fLeft);
+ auto currTop = static_cast(currNode.rect.fTop);
+ auto currRight = static_cast(currNode.rect.fRight);
+ auto currBottom = static_cast(currNode.rect.fBottom);
+
+ // 获取当前flutter节点的缩放、平移、透视等矩阵坐标转换
+ SkMatrix transform = currNode.transform.asM33();
+ auto _kMScaleX = transform.get(SkMatrix::kMScaleX);
+ auto _kMTransX = transform.get(SkMatrix::kMTransX);
+ auto _kMScaleY = transform.get(SkMatrix::kMScaleY);
+ auto _kMTransY = transform.get(SkMatrix::kMTransY);
+ /** 以下矩阵坐标变换参数(如:旋转/错切、透视)场景目前暂不考虑
+ * NOTE: SkMatrix::kMSkewX, SkMatrix::kMSkewY,
+ * SkMatrix::kMPersp0, SkMatrix::kMPersp1, SkMatrix::kMPersp2
+ */
+
+ // 获取当前flutter节点的父节点的相对rect
+ int32_t parentId = GetParentId(currNode.id);
+ auto parentNode = GetFlutterSemanticsNode(parentId);
+ auto parentRight = parentNode.rect.fRight;
+ auto parentBottom = parentNode.rect.fBottom;
+
+ // 获取当前flutter节点的父节点的绝对坐标
+ auto _rectPairs = GetAbsoluteScreenRect(parentNode.id);
+ auto realParentLeft = _rectPairs.first.first;
+ auto realParentTop = _rectPairs.first.second;
+ auto realParentRight = _rectPairs.second.first;
+ auto realParentBottom = _rectPairs.second.second;
+
+ // 获取root节点的绝对坐标, 即xcomponent屏幕长宽
+ auto _rootRect = GetAbsoluteScreenRect(0);
+ auto rootHeight = _rootRect.second.second;
+
+ // 真实缩放系数
+ float realScaleFactor = realParentRight / parentRight * 1.0;
+ float newLeft;
+ float newTop;
+ float newRight;
+ float newBottom;
+
+ if (_kMScaleX > 1 && _kMScaleY > 1) {
+ // 子节点相对父节点进行变化(缩放、 平移)
+ newLeft = currLeft + _kMTransX * _kMScaleX;
+ newTop = currTop + _kMTransY * _kMScaleY;
+ newRight = currRight * _kMScaleX;
+ newBottom = currBottom * _kMScaleY;
+ // 更新当前flutter节点currNode的相对坐标 -> 屏幕绝对坐标
+ SetAbsoluteScreenRect(currNode.id, newLeft, newTop, newRight, newBottom);
+ } else {
+ // 若当前节点的相对坐标与父亲节点的相对坐标值相同,则直接继承坐标值
+ if (currRight == parentRight && currBottom == parentBottom) {
+ newLeft = realParentLeft;
+ newTop = realParentTop;
+ newRight = realParentRight;
+ newBottom = realParentBottom;
+ } else {
+ // 子节点的屏幕绝对坐标转换,包括offset偏移值计算、缩放系数变换
+ newLeft = (currLeft + _kMTransX) * realScaleFactor + realParentLeft;
+ newTop = (currTop + _kMTransY) * realScaleFactor + realParentTop;
+ newRight = (currLeft + _kMTransX + currRight) * realScaleFactor + realParentLeft;
+ newBottom = (currTop + _kMTransY + currBottom) * realScaleFactor + realParentTop;
+ }
+ // 若子节点rect超过父节点则跳过显示(单个屏幕显示不下,滑动再重新显示)
+ const bool IS_OVER_SCREEN_AREA = newLeft < realParentLeft ||
+ newTop < realParentTop ||
+ newRight > realParentRight ||
+ newBottom > realParentBottom ||
+ newLeft >= newRight ||
+ newTop >= newBottom;
+ if (IS_OVER_SCREEN_AREA) {
+ FML_DLOG(ERROR) << "ConvertChildRelativeRectToScreenRect childRect is "
+ "bigger than parentRect -> { nodeId: "
+ << currNode.id << ", (" << newLeft << ", " << newTop
+ << ", " << newRight << ", " << newBottom << ")}";
+ // 防止滑动场景下绿框坐标超出屏幕范围,进行正则化处理
+ newTop -= rootHeight;
+ newBottom -= rootHeight;
+ SetAbsoluteScreenRect(currNode.id, newLeft, newTop, newRight, newBottom);
+ } else {
+ SetAbsoluteScreenRect(currNode.id, newLeft, newTop, newRight, newBottom);
+ }
+ }
+ FML_DLOG(INFO) << "ConvertChildRelativeRectToScreenRect -> { nodeId: "
+ << currNode.id << ", (" << newLeft << ", " << newTop << ", "
+ << newRight << ", " << newBottom << ")}";
+}
+
+/**
+ * 实现对特定id的flutter节点到arkui的elementinfo节点转化
+ */
+void OhosAccessibilityBridge::FlutterNodeToElementInfoById(
+ ArkUI_AccessibilityElementInfo* elementInfoFromList,
+ int64_t elementId)
+{
+ if (elementInfoFromList == nullptr) {
+ FML_DLOG(INFO) << "OhosAccessibilityBridge::FlutterNodeToElementInfoById "
+ "elementInfoFromList is null";
+ return;
+ }
+ FML_DLOG(INFO) << "FlutterNodeToElementInfoById elementId = " << elementId;
+
+ if (OH_GetSdkApiVersion() >= 13) {
+ // 当elementId = -1或0时,创建root节点
+ if (elementId == 0 || elementId == -1) {
+ // 获取flutter的root节点
+ flutter::SemanticsNode flutterNode = GetFlutterSemanticsNode(static_cast(0));
+
+ // 设置elementinfo的屏幕坐标范围
+ int32_t left = static_cast(flutterNode.rect.fLeft);
+ int32_t top = static_cast(flutterNode.rect.fTop);
+ int32_t right = static_cast(flutterNode.rect.fRight);
+ int32_t bottom = static_cast(flutterNode.rect.fBottom);
+ ArkUI_AccessibleRect rect = {left, top, right, bottom};
+ int32_t (*OH_ArkUI_AccessibilityElementInfoSetScreenRect)(ArkUI_AccessibilityElementInfo*, ArkUI_AccessibleRect*) =
+ OhosAccessibilityDDL::DLLoadSetElemSreenRectFunc("OH_ArkUI_AccessibilityElementInfoSetScreenRect");
+ if (OH_ArkUI_AccessibilityElementInfoSetScreenRect == nullptr) {
+ LOGE("OH_ArkUI_AccessibilityElementInfoSetScreenRect is null, %{public}s", dlerror());
+ }
+ ARKUI_ACCESSIBILITY_CALL_CHECK(
+ OH_ArkUI_AccessibilityElementInfoSetScreenRect(elementInfoFromList, &rect)
+ );
+
+ // 设置root节点的屏幕绝对坐标rect
+ SetAbsoluteScreenRect(0, left, top, right, bottom);
+
+ // 设置elementinfo的action类型
+ std::string widget_type = "root";
+ FlutterSetElementInfoOperationActions(elementInfoFromList, widget_type);
+
+ // 根据flutternode信息配置对应的elementinfo
+ int32_t (*OH_ArkUI_AccessibilityElementInfoSetElementId)(ArkUI_AccessibilityElementInfo*, int32_t) =
+ OhosAccessibilityDDL::DLLoadSetElemIntFunc("OH_ArkUI_AccessibilityElementInfoSetElementId");
+ if (OH_ArkUI_AccessibilityElementInfoSetElementId == nullptr) {
+ LOGE("OH_ArkUI_AccessibilityElementInfoSetElementId is null, %{public}s", dlerror());
+ }
+ ARKUI_ACCESSIBILITY_CALL_CHECK(OH_ArkUI_AccessibilityElementInfoSetElementId(elementInfoFromList, 0));
+
+ // NOTE: arkui无障碍子系统强制设置root的父节点id = -2100000 (严禁更改)
+ int32_t (*OH_ArkUI_AccessibilityElementInfoSetParentId)(ArkUI_AccessibilityElementInfo*, int32_t) =
+ OhosAccessibilityDDL::DLLoadSetElemIntFunc("OH_ArkUI_AccessibilityElementInfoSetParentId");
+ if (OH_ArkUI_AccessibilityElementInfoSetParentId == nullptr) {
+ LOGE("OH_ArkUI_AccessibilityElementInfoSetParentId is null, %{public}s", dlerror());
+ }
+ ARKUI_ACCESSIBILITY_CALL_CHECK(
+ OH_ArkUI_AccessibilityElementInfoSetParentId(elementInfoFromList, ARKUI_ACCESSIBILITY_ROOT_PARENT_ID)
+ );
+
+ // 设置无障碍播报文本
+ int32_t (*OH_ArkUI_AccessibilityElementInfoSetAccessibilityText)(ArkUI_AccessibilityElementInfo*, const char*) =
+ OhosAccessibilityDDL::DLLoadSetElemStringFunc("OH_ArkUI_AccessibilityElementInfoSetAccessibilityText");
+ if (OH_ArkUI_AccessibilityElementInfoSetAccessibilityText == nullptr) {
+ LOGE("OH_ArkUI_AccessibilityElementInfoSetAccessibilityText is null, %{public}s", dlerror());
+ }
+ ARKUI_ACCESSIBILITY_CALL_CHECK(
+ OH_ArkUI_AccessibilityElementInfoSetAccessibilityText(
+ elementInfoFromList, flutterNode.label.empty() ? flutterNode.hint.c_str() : flutterNode.label.c_str())
+ );
+
+ int32_t (*OH_ArkUI_AccessibilityElementInfoSetAccessibilityLevel)(ArkUI_AccessibilityElementInfo*, const char*) =
+ OhosAccessibilityDDL::DLLoadSetElemStringFunc("OH_ArkUI_AccessibilityElementInfoSetAccessibilityLevel");
+ if (OH_ArkUI_AccessibilityElementInfoSetAccessibilityLevel == nullptr) {
+ LOGE("OH_ArkUI_AccessibilityElementInfoSetAccessibilityLevel is null, %{public}s", dlerror());
+ }
+ ARKUI_ACCESSIBILITY_CALL_CHECK(
+ OH_ArkUI_AccessibilityElementInfoSetAccessibilityLevel(elementInfoFromList, "yes")
+ );
+
+ int32_t (*OH_ArkUI_AccessibilityElementInfoSetAccessibilityGroup)(ArkUI_AccessibilityElementInfo*, bool) =
+ OhosAccessibilityDDL::DLLoadSetElemBoolFunc("OH_ArkUI_AccessibilityElementInfoSetAccessibilityGroup");
+ if (OH_ArkUI_AccessibilityElementInfoSetAccessibilityGroup == nullptr) {
+ LOGE("OH_ArkUI_AccessibilityElementInfoSetAccessibilityGroup is null, %{public}s", dlerror());
+ }
+ ARKUI_ACCESSIBILITY_CALL_CHECK(
+ OH_ArkUI_AccessibilityElementInfoSetAccessibilityGroup(elementInfoFromList, false)
+ );
+
+ // 配置child节点信息
+ int32_t childCount = flutterNode.childrenInTraversalOrder.size();
+ auto childrenIdsVec = flutterNode.childrenInTraversalOrder;
+ std::sort(childrenIdsVec.begin(), childrenIdsVec.end());
+ int64_t childNodeIds[childCount];
+ for (int32_t i = 0; i < childCount; i++) {
+ childNodeIds[i] = static_cast(childrenIdsVec[i]);
+ FML_DLOG(INFO)
+ << "FlutterNodeToElementInfoById -> elementid=0 childCount="
+ << childCount << " childNodeIds=" << childNodeIds[i];
+ }
+ int32_t (*OH_ArkUI_AccessibilityElementInfoSetChildNodeIds)(ArkUI_AccessibilityElementInfo*, int32_t, int64_t*) =
+ OhosAccessibilityDDL::DLLoadSetElemChildFunc("OH_ArkUI_AccessibilityElementInfoSetChildNodeIds");
+ if (OH_ArkUI_AccessibilityElementInfoSetChildNodeIds == nullptr) {
+ LOGE("OH_ArkUI_AccessibilityElementInfoSetChildNodeIds is null, %{public}s", dlerror());
+ }
+ ARKUI_ACCESSIBILITY_CALL_CHECK(
+ OH_ArkUI_AccessibilityElementInfoSetChildNodeIds(elementInfoFromList, childCount, childNodeIds)
+ );
+
+ // 配置root节点常用属性
+ int32_t (*OH_ArkUI_AccessibilityElementInfoSetEnabled)(ArkUI_AccessibilityElementInfo*, bool) =
+ OhosAccessibilityDDL::DLLoadSetElemBoolFunc("OH_ArkUI_AccessibilityElementInfoSetEnabled");
+ if (OH_ArkUI_AccessibilityElementInfoSetEnabled == nullptr) {
+ LOGE("OH_ArkUI_AccessibilityElementInfoSetEnabled is null, %{public}s", dlerror());
+ }
+ ARKUI_ACCESSIBILITY_CALL_CHECK(OH_ArkUI_AccessibilityElementInfoSetEnabled(elementInfoFromList, true));
+
+ int32_t (*OH_ArkUI_AccessibilityElementInfoSetClickable)(ArkUI_AccessibilityElementInfo*, bool) =
+ OhosAccessibilityDDL::DLLoadSetElemBoolFunc("OH_ArkUI_AccessibilityElementInfoSetClickable");
+ if (OH_ArkUI_AccessibilityElementInfoSetClickable == nullptr) {
+ LOGE("OH_ArkUI_AccessibilityElementInfoSetClickable is null, %{public}s", dlerror());
+ }
+ ARKUI_ACCESSIBILITY_CALL_CHECK(OH_ArkUI_AccessibilityElementInfoSetClickable(elementInfoFromList, true));
+
+ int32_t (*OH_ArkUI_AccessibilityElementInfoSetFocusable)(ArkUI_AccessibilityElementInfo*, bool) =
+ OhosAccessibilityDDL::DLLoadSetElemBoolFunc("OH_ArkUI_AccessibilityElementInfoSetFocusable");
+ if (OH_ArkUI_AccessibilityElementInfoSetFocusable == nullptr) {
+ LOGE("OH_ArkUI_AccessibilityElementInfoSetFocusable is null, %{public}s", dlerror());
+ }
+ ARKUI_ACCESSIBILITY_CALL_CHECK(OH_ArkUI_AccessibilityElementInfoSetFocusable(elementInfoFromList, true));
+
+ int32_t (*OH_ArkUI_AccessibilityElementInfoSetVisible)(ArkUI_AccessibilityElementInfo*, bool) =
+ OhosAccessibilityDDL::DLLoadSetElemBoolFunc("OH_ArkUI_AccessibilityElementInfoSetVisible");
+ if (OH_ArkUI_AccessibilityElementInfoSetVisible == nullptr) {
+ LOGE("OH_ArkUI_AccessibilityElementInfoSetVisible is null, %{public}s", dlerror());
+ }
+ ARKUI_ACCESSIBILITY_CALL_CHECK(OH_ArkUI_AccessibilityElementInfoSetVisible(elementInfoFromList, true));
+
+ int32_t (*OH_ArkUI_AccessibilityElementInfoSetComponentType)(ArkUI_AccessibilityElementInfo*, const char*) =
+ OhosAccessibilityDDL::DLLoadSetElemStringFunc("OH_ArkUI_AccessibilityElementInfoSetComponentType");
+ if (OH_ArkUI_AccessibilityElementInfoSetComponentType == nullptr) {
+ LOGE("OH_ArkUI_AccessibilityElementInfoSetComponentType is null, %{public}s", dlerror());
+ }
+ ARKUI_ACCESSIBILITY_CALL_CHECK(OH_ArkUI_AccessibilityElementInfoSetComponentType(elementInfoFromList, "root"));
+
+ int32_t (*OH_ArkUI_AccessibilityElementInfoSetContents)(ArkUI_AccessibilityElementInfo*, const char*) =
+ OhosAccessibilityDDL::DLLoadSetElemStringFunc("OH_ArkUI_AccessibilityElementInfoSetContents");
+ if (OH_ArkUI_AccessibilityElementInfoSetContents == nullptr) {
+ LOGE("OH_ArkUI_AccessibilityElementInfoSetContents is null, %{public}s", dlerror());
+ }
+ ARKUI_ACCESSIBILITY_CALL_CHECK(OH_ArkUI_AccessibilityElementInfoSetContents(elementInfoFromList, flutterNode.label.c_str()));
+ } else {
+ //当elementId >= 1时,根据flutter节点信息配置elementinfo无障碍属性
+ FlutterSetElementInfoProperties(elementInfoFromList, elementId);
+ }
+ }
+ FML_DLOG(INFO) << "=== OhosAccessibilityBridge::FlutterNodeToElementInfoById is end ===";
+}
+
+/**
+ * 判断源字符串是否包含目标字符串
+ */
+bool OhosAccessibilityBridge::Contains(const std::string source,
+ const std::string target)
+{
+ return source.find(target) != std::string::npos;
+}
+
+/**
+ * 配置arkui节点的可操作动作类型
+ */
+void OhosAccessibilityBridge::FlutterSetElementInfoOperationActions(
+ ArkUI_AccessibilityElementInfo* elementInfoFromList,
+ std::string widget_type)
+{
+ if (OH_GetSdkApiVersion() >= 13) {
+ int32_t (*OH_ArkUI_AccessibilityElementInfoSetOperationActions)(ArkUI_AccessibilityElementInfo*, int32_t, ArkUI_AccessibleAction*) =
+ OhosAccessibilityDDL::DLLoadSetElemOperActionsFunc("OH_ArkUI_AccessibilityElementInfoSetOperationActions");
+ if (OH_ArkUI_AccessibilityElementInfoSetOperationActions == nullptr) {
+ LOGE("OH_ArkUI_AccessibilityElementInfoSetOperationActions is null, %{public}s", dlerror());
+ }
+ if (widget_type == "textfield") {
+ // set elementinfo action types
+ int32_t actionTypeNum = 10;
+ ArkUI_AccessibleAction actions[actionTypeNum];
+
+ actions[0].actionType = ArkUI_Accessibility_ActionType::
+ ARKUI_ACCESSIBILITY_NATIVE_ACTION_TYPE_GAIN_ACCESSIBILITY_FOCUS;
+ actions[0].description = "获取焦点";
+
+ actions[1].actionType = ArkUI_Accessibility_ActionType::
+ ARKUI_ACCESSIBILITY_NATIVE_ACTION_TYPE_CLEAR_ACCESSIBILITY_FOCUS;
+ actions[1].description = "清除焦点";
+
+ actions[2].actionType = ArkUI_Accessibility_ActionType::
+ ARKUI_ACCESSIBILITY_NATIVE_ACTION_TYPE_CLICK;
+ actions[2].description = "点击操作";
+
+ actions[3].actionType = ArkUI_Accessibility_ActionType::
+ ARKUI_ACCESSIBILITY_NATIVE_ACTION_TYPE_LONG_CLICK;
+ actions[3].description = "长按操作";
+
+ actions[4].actionType = ArkUI_Accessibility_ActionType::
+ ARKUI_ACCESSIBILITY_NATIVE_ACTION_TYPE_COPY;
+ actions[4].description = "文本复制";
+
+ actions[5].actionType = ArkUI_Accessibility_ActionType::
+ ARKUI_ACCESSIBILITY_NATIVE_ACTION_TYPE_PASTE;
+ actions[5].description = "文本粘贴";
+
+ actions[6].actionType = ArkUI_Accessibility_ActionType::
+ ARKUI_ACCESSIBILITY_NATIVE_ACTION_TYPE_CUT;
+ actions[6].description = "文本剪切";
+
+ actions[7].actionType = ArkUI_Accessibility_ActionType::
+ ARKUI_ACCESSIBILITY_NATIVE_ACTION_TYPE_SELECT_TEXT;
+ actions[7].description = "文本选择";
+
+ actions[8].actionType = ArkUI_Accessibility_ActionType::
+ ARKUI_ACCESSIBILITY_NATIVE_ACTION_TYPE_SET_TEXT;
+ actions[8].description = "文本内容设置";
+
+ actions[9].actionType = ArkUI_Accessibility_ActionType::
+ ARKUI_ACCESSIBILITY_NATIVE_ACTION_TYPE_SET_CURSOR_POSITION;
+ actions[9].description = "光标位置设置";
+
+ ARKUI_ACCESSIBILITY_CALL_CHECK(
+ OH_ArkUI_AccessibilityElementInfoSetOperationActions(elementInfoFromList, actionTypeNum, actions)
+ );
+ } else if (widget_type == "scrollable") {
+ // if node is a scrollable component
+ int32_t actionTypeNum = 5;
+ ArkUI_AccessibleAction actions[actionTypeNum];
+
+ actions[0].actionType = ArkUI_Accessibility_ActionType::
+ ARKUI_ACCESSIBILITY_NATIVE_ACTION_TYPE_GAIN_ACCESSIBILITY_FOCUS;
+ actions[0].description = "获取焦点";
+
+ actions[1].actionType = ArkUI_Accessibility_ActionType::
+ ARKUI_ACCESSIBILITY_NATIVE_ACTION_TYPE_CLEAR_ACCESSIBILITY_FOCUS;
+ actions[1].description = "清除焦点";
+
+ actions[2].actionType = ArkUI_Accessibility_ActionType::
+ ARKUI_ACCESSIBILITY_NATIVE_ACTION_TYPE_CLICK;
+ actions[2].description = "点击动作";
+
+ actions[3].actionType = ArkUI_Accessibility_ActionType::
+ ARKUI_ACCESSIBILITY_NATIVE_ACTION_TYPE_SCROLL_FORWARD;
+ actions[3].description = "向上滑动";
+
+ actions[4].actionType = ArkUI_Accessibility_ActionType::
+ ARKUI_ACCESSIBILITY_NATIVE_ACTION_TYPE_SCROLL_BACKWARD;
+ actions[4].description = "向下滑动";
+
+ ARKUI_ACCESSIBILITY_CALL_CHECK(
+ OH_ArkUI_AccessibilityElementInfoSetOperationActions(elementInfoFromList, actionTypeNum, actions)
+ );
+ } else {
+ // set common component action types
+ int32_t actionTypeNum = 3;
+ ArkUI_AccessibleAction actions[actionTypeNum];
+
+ actions[0].actionType = ArkUI_Accessibility_ActionType::
+ ARKUI_ACCESSIBILITY_NATIVE_ACTION_TYPE_GAIN_ACCESSIBILITY_FOCUS;
+ actions[0].description = "获取焦点";
+
+ actions[1].actionType = ArkUI_Accessibility_ActionType::
+ ARKUI_ACCESSIBILITY_NATIVE_ACTION_TYPE_CLEAR_ACCESSIBILITY_FOCUS;
+ actions[1].description = "清除焦点";
+
+ actions[2].actionType = ArkUI_Accessibility_ActionType::
+ ARKUI_ACCESSIBILITY_NATIVE_ACTION_TYPE_CLICK;
+ actions[2].description = "点击动作";
+
+ ARKUI_ACCESSIBILITY_CALL_CHECK(
+ OH_ArkUI_AccessibilityElementInfoSetOperationActions(elementInfoFromList, actionTypeNum, actions)
+ );
+ }
+ }
+}
+
+/**
+ * 根据flutter节点信息配置elementinfo无障碍属性
+ */
+void OhosAccessibilityBridge::FlutterSetElementInfoProperties(
+ ArkUI_AccessibilityElementInfo* elementInfoFromList,
+ int64_t elementId)
+{
+ if (OH_GetSdkApiVersion() >= 13) {
+ flutter::SemanticsNode flutterNode = GetFlutterSemanticsNode(static_cast(elementId));
+
+ // set elementinfo id
+ int32_t (*OH_ArkUI_AccessibilityElementInfoSetElementId)(ArkUI_AccessibilityElementInfo*, int32_t) =
+ OhosAccessibilityDDL::DLLoadSetElemIntFunc("OH_ArkUI_AccessibilityElementInfoSetElementId");
+ if (OH_ArkUI_AccessibilityElementInfoSetElementId == nullptr) {
+ LOGE("OH_ArkUI_AccessibilityElementInfoSetElementId is null, %{public}s", dlerror());
+ }
+ ARKUI_ACCESSIBILITY_CALL_CHECK(
+ OH_ArkUI_AccessibilityElementInfoSetElementId(elementInfoFromList, flutterNode.id)
+ );
+
+ // convert relative rect to absolute rect
+ ConvertChildRelativeRectToScreenRect(flutterNode);
+ auto rectPairs = GetAbsoluteScreenRect(flutterNode.id);
+ // set screen rect in xcomponent
+ int32_t left = rectPairs.first.first;
+ int32_t top = rectPairs.first.second;
+ int32_t right = rectPairs.second.first;
+ int32_t bottom = rectPairs.second.second;
+ ArkUI_AccessibleRect rect = {left, top, right, bottom};
+ int32_t (*OH_ArkUI_AccessibilityElementInfoSetScreenRect)(ArkUI_AccessibilityElementInfo*, ArkUI_AccessibleRect*) =
+ OhosAccessibilityDDL::DLLoadSetElemSreenRectFunc("OH_ArkUI_AccessibilityElementInfoSetScreenRect");
+ if (OH_ArkUI_AccessibilityElementInfoSetScreenRect == nullptr) {
+ LOGE("OH_ArkUI_AccessibilityElementInfoSetScreenRect is null, %{public}s", dlerror());
+ }
+ ARKUI_ACCESSIBILITY_CALL_CHECK(
+ OH_ArkUI_AccessibilityElementInfoSetScreenRect(elementInfoFromList, &rect)
+ );
+ FML_DLOG(INFO) << "FlutterNodeToElementInfoById -> node.id= "
+ << flutterNode.id << " SceenRect = (" << left << ", " << top
+ << ", " << right << ", " << bottom << ")";
+
+ // 配置arkui的elementinfo可操作动作属性
+ if (IsTextField(flutterNode)) {
+ // 若当前flutter节点为文本输入框组件
+ std::string widget_type = "textfield";
+ FlutterSetElementInfoOperationActions(elementInfoFromList, widget_type);
+ } else if (IsScrollableWidget(flutterNode) || IsNodeScrollable(flutterNode)) {
+ // 若当前flutter节点为可滑动组件类型
+ std::string widget_type = "scrollable";
+ FlutterSetElementInfoOperationActions(elementInfoFromList, widget_type);
+ } else {
+ // 若当前flutter节点为通用组件
+ std::string widget_type = "common";
+ FlutterSetElementInfoOperationActions(elementInfoFromList, widget_type);
+ }
+
+ // set current elementinfo parent id
+ int32_t parentId = GetParentId(elementId);
+ int32_t (*OH_ArkUI_AccessibilityElementInfoSetParentId)(ArkUI_AccessibilityElementInfo*, int32_t) =
+ OhosAccessibilityDDL::DLLoadSetElemIntFunc("OH_ArkUI_AccessibilityElementInfoSetParentId");
+ if (OH_ArkUI_AccessibilityElementInfoSetParentId == nullptr) {
+ LOGE("OH_ArkUI_AccessibilityElementInfoSetParentId is null, %{public}s", dlerror());
+ }
+ ARKUI_ACCESSIBILITY_CALL_CHECK(
+ OH_ArkUI_AccessibilityElementInfoSetParentId(elementInfoFromList, parentId)
+ );
+ FML_DLOG(INFO) << "FlutterNodeToElementInfoById GetParentId = " << parentId;
+
+ // set accessibility text for announcing
+ std::string text = flutterNode.label;
+ int32_t (*OH_ArkUI_AccessibilityElementInfoSetAccessibilityText)(ArkUI_AccessibilityElementInfo*, const char*) =
+ OhosAccessibilityDDL::DLLoadSetElemStringFunc("OH_ArkUI_AccessibilityElementInfoSetAccessibilityText");
+ if (OH_ArkUI_AccessibilityElementInfoSetAccessibilityText == nullptr) {
+ LOGE("OH_ArkUI_AccessibilityElementInfoSetAccessibilityText is null, %{public}s", dlerror());
+ }
+ ARKUI_ACCESSIBILITY_CALL_CHECK(
+ OH_ArkUI_AccessibilityElementInfoSetAccessibilityText(elementInfoFromList, text.c_str())
+ );
+ FML_DLOG(INFO) << "FlutterNodeToElementInfoById SetAccessibilityText = "
+ << text;
+
+ //set contents (same as AccessibilityText)
+ int32_t (*OH_ArkUI_AccessibilityElementInfoSetContents)(ArkUI_AccessibilityElementInfo*, const char*) =
+ OhosAccessibilityDDL::DLLoadSetElemStringFunc("OH_ArkUI_AccessibilityElementInfoSetContents");
+ if (OH_ArkUI_AccessibilityElementInfoSetContents == nullptr) {
+ LOGE("OH_ArkUI_AccessibilityElementInfoSetContents is null, %{public}s", dlerror());
+ }
+ ARKUI_ACCESSIBILITY_CALL_CHECK(
+ OH_ArkUI_AccessibilityElementInfoSetContents(elementInfoFromList, text.c_str())
+ );
+
+ std::string hint = flutterNode.hint;
+ int32_t (*OH_ArkUI_AccessibilityElementInfoSetHintText)(ArkUI_AccessibilityElementInfo*, const char*) =
+ OhosAccessibilityDDL::DLLoadSetElemStringFunc("OH_ArkUI_AccessibilityElementInfoSetHintText");
+ if (OH_ArkUI_AccessibilityElementInfoSetHintText == nullptr) {
+ LOGE("OH_ArkUI_AccessibilityElementInfoSetHintText is null, %{public}s", dlerror());
+ }
+ ARKUI_ACCESSIBILITY_CALL_CHECK(
+ OH_ArkUI_AccessibilityElementInfoSetHintText(elementInfoFromList, hint.c_str())
+ );
+
+ // set chidren elementinfo ids
+ int32_t childCount = flutterNode.childrenInTraversalOrder.size();
+ auto childrenIdsVec = flutterNode.childrenInTraversalOrder;
+ std::sort(childrenIdsVec.begin(), childrenIdsVec.end());
+ int64_t childNodeIds[childCount];
+ for (int32_t i = 0; i < childCount; i++) {
+ childNodeIds[i] = static_cast(childrenIdsVec[i]);
+ FML_DLOG(INFO) << "FlutterNodeToElementInfoById -> elementid=" << elementId
+ << " childCount=" << childCount
+ << " childNodeIds=" << childNodeIds[i];
+ }
+ int32_t (*OH_ArkUI_AccessibilityElementInfoSetChildNodeIds)(ArkUI_AccessibilityElementInfo*, int32_t, int64_t*) =
+ OhosAccessibilityDDL::DLLoadSetElemChildFunc("OH_ArkUI_AccessibilityElementInfoSetChildNodeIds");
+ if (OH_ArkUI_AccessibilityElementInfoSetChildNodeIds == nullptr) {
+ LOGE("OH_ArkUI_AccessibilityElementInfoSetChildNodeIds is null, %{public}s", dlerror());
+ }
+ ARKUI_ACCESSIBILITY_CALL_CHECK(
+ OH_ArkUI_AccessibilityElementInfoSetChildNodeIds(elementInfoFromList, childCount, childNodeIds)
+ );
+
+ /**
+ * 根据当前flutter节点的SemanticsFlags特性,配置对应的elmentinfo属性
+ */
+ // 判断当前节点组件是否enabled
+ if (IsNodeEnabled(flutterNode)) {
+ int32_t (*OH_ArkUI_AccessibilityElementInfoSetEnabled)(ArkUI_AccessibilityElementInfo*, bool) =
+ OhosAccessibilityDDL::DLLoadSetElemBoolFunc("OH_ArkUI_AccessibilityElementInfoSetEnabled");
+ if (OH_ArkUI_AccessibilityElementInfoSetEnabled == nullptr) {
+ LOGE("OH_ArkUI_AccessibilityElementInfoSetEnabled is null, %{public}s", dlerror());
+ }
+ ARKUI_ACCESSIBILITY_CALL_CHECK(
+ OH_ArkUI_AccessibilityElementInfoSetEnabled(elementInfoFromList, true)
+ );
+ FML_DLOG(INFO) << "flutterNode.id=" << flutterNode.id
+ << " OH_ArkUI_AccessibilityElementInfoSetEnabled -> true";
+ }
+ // 判断当前节点是否可点击
+ if (IsNodeClickable(flutterNode)) {
+ int32_t (*OH_ArkUI_AccessibilityElementInfoSetClickable)(ArkUI_AccessibilityElementInfo*, bool) =
+ OhosAccessibilityDDL::DLLoadSetElemBoolFunc("OH_ArkUI_AccessibilityElementInfoSetClickable");
+ if (OH_ArkUI_AccessibilityElementInfoSetClickable == nullptr) {
+ LOGE("OH_ArkUI_AccessibilityElementInfoSetClickable is null, %{public}s", dlerror());
+ }
+ ARKUI_ACCESSIBILITY_CALL_CHECK(
+ OH_ArkUI_AccessibilityElementInfoSetClickable(elementInfoFromList, true)
+ );
+ FML_DLOG(INFO) << "flutterNode.id=" << flutterNode.id
+ << " OH_ArkUI_AccessibilityElementInfoSetClickable -> true";
+ }
+ // 判断当前节点是否可获焦点
+ if (IsNodeFocusable(flutterNode)) {
+ int32_t (*OH_ArkUI_AccessibilityElementInfoSetFocusable)(ArkUI_AccessibilityElementInfo*, bool) =
+ OhosAccessibilityDDL::DLLoadSetElemBoolFunc("OH_ArkUI_AccessibilityElementInfoSetFocusable");
+ if (OH_ArkUI_AccessibilityElementInfoSetFocusable == nullptr) {
+ LOGE("OH_ArkUI_AccessibilityElementInfoSetFocusable is null, %{public}s", dlerror());
+ }
+ ARKUI_ACCESSIBILITY_CALL_CHECK(
+ OH_ArkUI_AccessibilityElementInfoSetFocusable(elementInfoFromList, true)
+ );
+ FML_DLOG(INFO) << "flutterNode.id=" << flutterNode.id
+ << " OH_ArkUI_AccessibilityElementInfoSetFocusable -> true";
+ }
+ // 判断当前节点是否为密码输入框
+ if (IsNodePassword(flutterNode)) {
+ int32_t (*OH_ArkUI_AccessibilityElementInfoSetIsPassword)(ArkUI_AccessibilityElementInfo*, bool) =
+ OhosAccessibilityDDL::DLLoadSetElemBoolFunc("OH_ArkUI_AccessibilityElementInfoSetIsPassword");
+ if (OH_ArkUI_AccessibilityElementInfoSetIsPassword == nullptr) {
+ LOGE("OH_ArkUI_AccessibilityElementInfoSetIsPassword is null, %{public}s", dlerror());
+ }
+ ARKUI_ACCESSIBILITY_CALL_CHECK(
+ OH_ArkUI_AccessibilityElementInfoSetIsPassword(elementInfoFromList, true)
+ );
+ FML_DLOG(INFO) << "flutterNode.id=" << flutterNode.id
+ << " OH_ArkUI_AccessibilityElementInfoSetIsPassword -> true";
+ }
+ // 判断当前节点是否具备checkable状态 (如:checkbox, radio button)
+ if (IsNodeCheckable(flutterNode)) {
+ int32_t (*OH_ArkUI_AccessibilityElementInfoSetCheckable)(ArkUI_AccessibilityElementInfo*, bool) =
+ OhosAccessibilityDDL::DLLoadSetElemBoolFunc("OH_ArkUI_AccessibilityElementInfoSetCheckable");
+ if (OH_ArkUI_AccessibilityElementInfoSetCheckable == nullptr) {
+ LOGE("OH_ArkUI_AccessibilityElementInfoSetCheckable is null, %{public}s", dlerror());
+ }
+ ARKUI_ACCESSIBILITY_CALL_CHECK(
+ OH_ArkUI_AccessibilityElementInfoSetCheckable(elementInfoFromList, true)
+ );
+ FML_DLOG(INFO) << "flutterNode.id=" << flutterNode.id
+ << " OH_ArkUI_AccessibilityElementInfoSetCheckable -> true";
+ }
+ // 判断当前节点(check box/radio button)是否checked/unchecked
+ if (IsNodeChecked(flutterNode)) {
+ int32_t (*OH_ArkUI_AccessibilityElementInfoSetChecked)(ArkUI_AccessibilityElementInfo*, bool) =
+ OhosAccessibilityDDL::DLLoadSetElemBoolFunc("OH_ArkUI_AccessibilityElementInfoSetChecked");
+ if (OH_ArkUI_AccessibilityElementInfoSetChecked == nullptr) {
+ LOGE("OH_ArkUI_AccessibilityElementInfoSetChecked is null, %{public}s", dlerror());
+ }
+ ARKUI_ACCESSIBILITY_CALL_CHECK(
+ OH_ArkUI_AccessibilityElementInfoSetChecked(elementInfoFromList, true)
+ );
+ FML_DLOG(INFO) << "flutterNode.id=" << flutterNode.id
+ << " OH_ArkUI_AccessibilityElementInfoSetChecked -> true";
+ }
+ // 判断当前节点组件是否可显示
+ if (IsNodeVisible(flutterNode)) {
+ int32_t (*OH_ArkUI_AccessibilityElementInfoSetVisible)(ArkUI_AccessibilityElementInfo*, bool) =
+ OhosAccessibilityDDL::DLLoadSetElemBoolFunc("OH_ArkUI_AccessibilityElementInfoSetVisible");
+ if (OH_ArkUI_AccessibilityElementInfoSetVisible == nullptr) {
+ LOGE("OH_ArkUI_AccessibilityElementInfoSetVisible is null, %{public}s", dlerror());
+ }
+ ARKUI_ACCESSIBILITY_CALL_CHECK(
+ OH_ArkUI_AccessibilityElementInfoSetVisible(elementInfoFromList, true)
+ );
+ FML_DLOG(INFO) << "flutterNode.id=" << flutterNode.id
+ << " OH_ArkUI_AccessibilityElementInfoSetVisible -> true";
+ }
+ // 判断当前节点组件是否选中
+ if (IsNodeSelected(flutterNode)) {
+ int32_t (*OH_ArkUI_AccessibilityElementInfoSetSelected)(ArkUI_AccessibilityElementInfo*, bool) =
+ OhosAccessibilityDDL::DLLoadSetElemBoolFunc("OH_ArkUI_AccessibilityElementInfoSetSelected");
+ if (OH_ArkUI_AccessibilityElementInfoSetSelected == nullptr) {
+ LOGE("OH_ArkUI_AccessibilityElementInfoSetSelected is null, %{public}s", dlerror());
+ }
+ OH_ArkUI_AccessibilityElementInfoSetSelected(elementInfoFromList, true);
+ FML_DLOG(INFO) << "flutterNode.id=" << flutterNode.id
+ << " OH_ArkUI_AccessibilityElementInfoSetSelected -> true";
+ }
+ // 判断当前节点组件是否可滑动
+ if (IsNodeScrollable(flutterNode)) {
+ int32_t (*OH_ArkUI_AccessibilityElementInfoSetScrollable)(ArkUI_AccessibilityElementInfo*, bool) =
+ OhosAccessibilityDDL::DLLoadSetElemBoolFunc("OH_ArkUI_AccessibilityElementInfoSetScrollable");
+ if (OH_ArkUI_AccessibilityElementInfoSetScrollable == nullptr) {
+ LOGE("OH_ArkUI_AccessibilityElementInfoSetScrollable is null, %{public}s", dlerror());
+ }
+ ARKUI_ACCESSIBILITY_CALL_CHECK(
+ OH_ArkUI_AccessibilityElementInfoSetScrollable(elementInfoFromList, true)
+ );
+ FML_DLOG(INFO) << "flutterNode.id=" << flutterNode.id
+ << " OH_ArkUI_AccessibilityElementInfoSetScrollable -> true";
+ }
+ // 判断当前节点组件是否可编辑(文本输入框)
+ if (IsTextField(flutterNode)) {
+ int32_t (*OH_ArkUI_AccessibilityElementInfoSetEditable)(ArkUI_AccessibilityElementInfo*, bool) =
+ OhosAccessibilityDDL::DLLoadSetElemBoolFunc("OH_ArkUI_AccessibilityElementInfoSetEditable");
+ if (OH_ArkUI_AccessibilityElementInfoSetEditable == nullptr) {
+ LOGE("OH_ArkUI_AccessibilityElementInfoSetEditable is null, %{public}s", dlerror());
+ }
+ ARKUI_ACCESSIBILITY_CALL_CHECK(
+ OH_ArkUI_AccessibilityElementInfoSetEditable(elementInfoFromList, true)
+ );
+ FML_DLOG(INFO) << "flutterNode.id=" << flutterNode.id
+ << " OH_ArkUI_AccessibilityElementInfoSetEditable -> true";
+ }
+ // 判断当前节点组件是否为滑动条
+ if (IsSlider(flutterNode)) {
+ FML_DLOG(INFO) << "flutterNode.id=" << flutterNode.id
+ << " OH_ArkUI_AccessibilityElementInfoSetRangeInfo -> true";
+ }
+ // 判断当前节点组件是否支持长按
+ if (IsNodeHasLongPress(flutterNode)) {
+ int32_t (*OH_ArkUI_AccessibilityElementInfoSetLongClickable)(ArkUI_AccessibilityElementInfo*, bool) =
+ OhosAccessibilityDDL::DLLoadSetElemBoolFunc("OH_ArkUI_AccessibilityElementInfoSetLongClickable");
+ if (OH_ArkUI_AccessibilityElementInfoSetLongClickable == nullptr) {
+ LOGE("OH_ArkUI_AccessibilityElementInfoSetLongClickable is null, %{public}s", dlerror());
+ }
+ ARKUI_ACCESSIBILITY_CALL_CHECK(
+ OH_ArkUI_AccessibilityElementInfoSetLongClickable(elementInfoFromList, true)
+ );
+ FML_DLOG(INFO)
+ << "flutterNode.id=" << flutterNode.id
+ << " OH_ArkUI_AccessibilityElementInfoSetLongClickable -> true";
+ }
+
+ // 获取当前节点的组件类型
+ std::string componentTypeName = GetNodeComponentType(flutterNode);
+ FML_DLOG(INFO) << "FlutterNodeToElementInfoById componentTypeName = "
+ << componentTypeName;
+ // flutter节点对应elementinfo所属的组件类型(如:root, button,text等)
+ int32_t (*OH_ArkUI_AccessibilityElementInfoSetComponentType)(ArkUI_AccessibilityElementInfo*, const char*) =
+ OhosAccessibilityDDL::DLLoadSetElemStringFunc("OH_ArkUI_AccessibilityElementInfoSetComponentType");
+ if (OH_ArkUI_AccessibilityElementInfoSetComponentType == nullptr) {
+ LOGE("OH_ArkUI_AccessibilityElementInfoSetComponentType is null, %{public}s", dlerror());
+ }
+ if (elementId == 0) {
+ ARKUI_ACCESSIBILITY_CALL_CHECK(
+ OH_ArkUI_AccessibilityElementInfoSetComponentType(elementInfoFromList, "root")
+ );
+ } else {
+ ARKUI_ACCESSIBILITY_CALL_CHECK(
+ OH_ArkUI_AccessibilityElementInfoSetComponentType(elementInfoFromList,
+ componentTypeName.c_str())
+ );
+ }
+ FML_DLOG(INFO) << "FlutterNodeToElementInfoById SetComponentType: "
+ << componentTypeName;
+
+ /**
+ * 无障碍重要性,用于控制某个组件是否可被无障碍辅助服务所识别。支持的值为(默认值:“auto”):
+ * “auto”:根据组件不同会转换为“yes”或者“no”
+ * “yes”:当前组件可被无障碍辅助服务所识别
+ * “no”:当前组件不可被无障碍辅助服务所识别
+ * “no-hide-descendants”:当前组件及其所有子组件不可被无障碍辅助服务所识别
+ */
+ int32_t (*OH_ArkUI_AccessibilityElementInfoSetAccessibilityLevel)(ArkUI_AccessibilityElementInfo*, const char*) =
+ OhosAccessibilityDDL::DLLoadSetElemStringFunc("OH_ArkUI_AccessibilityElementInfoSetAccessibilityLevel");
+ if (OH_ArkUI_AccessibilityElementInfoSetAccessibilityLevel == nullptr) {
+ LOGE("OH_ArkUI_AccessibilityElementInfoSetAccessibilityLevel is null, %{public}s", dlerror());
+ }
+ ARKUI_ACCESSIBILITY_CALL_CHECK(
+ OH_ArkUI_AccessibilityElementInfoSetAccessibilityLevel(elementInfoFromList, "yes");
+ );
+ // 无障碍组,设置为true时表示该组件及其所有子组件为一整个可以选中的组件,无障碍服务将不再关注其子组件内容。默认值:false
+ int32_t (*OH_ArkUI_AccessibilityElementInfoSetAccessibilityGroup)(ArkUI_AccessibilityElementInfo*, bool) =
+ OhosAccessibilityDDL::DLLoadSetElemBoolFunc("OH_ArkUI_AccessibilityElementInfoSetAccessibilityGroup");
+ if (OH_ArkUI_AccessibilityElementInfoSetAccessibilityGroup == nullptr) {
+ LOGE("OH_ArkUI_AccessibilityElementInfoSetAccessibilityGroup is null, %{public}s", dlerror());
+ }
+ ARKUI_ACCESSIBILITY_CALL_CHECK(
+ OH_ArkUI_AccessibilityElementInfoSetAccessibilityGroup(elementInfoFromList, false);
+ );
+ }
+}
+
+/**
+ * 将flutter无障碍语义树的转化为层次遍历顺序存储,
+ * 并按该顺序构建arkui语义树,以实现DevEco Testing
+ * UIViewer、Hypium自动化测试工具对flutter组件树的可视化
+ */
+std::vector OhosAccessibilityBridge::GetLevelOrderTraversalTree(int32_t rootId)
+{
+ std::vector levelOrderTraversalTree;
+ std::queue semanticsQue;
+
+ auto root = GetFlutterSemanticsNode(rootId);
+ semanticsQue.push(root);
+
+ while (!semanticsQue.empty()) {
+ uint32_t queSize = semanticsQue.size();
+ for (uint32_t i=0; i(currNode.id));
+
+ std::sort(currNode.childrenInTraversalOrder.begin(),
+ currNode.childrenInTraversalOrder.end());
+ for (const auto& childId: currNode.childrenInTraversalOrder) {
+ auto childNode = GetFlutterSemanticsNode(childId);
+ semanticsQue.push(childNode);
+ }
+ }
+ }
+ return levelOrderTraversalTree;
+}
+
+/**
+ * 创建并配置完整arkui无障碍语义树
+ */
+void OhosAccessibilityBridge::BuildArkUISemanticsTree(
+ int64_t elementId,
+ ArkUI_AccessibilityElementInfo* elementInfoFromList,
+ ArkUI_AccessibilityElementInfoList* elementList)
+{
+ if (OH_GetSdkApiVersion() >= 13) {
+ //配置root节点信息
+ FlutterNodeToElementInfoById(elementInfoFromList, elementId);
+ //获取flutter无障碍语义树的节点总数
+ auto levelOrderTreeVec = GetLevelOrderTraversalTree(0);
+ int64_t elementInfoCount = levelOrderTreeVec.size();
+ //创建并配置节点id >= 1的全部节点
+ for (int64_t i = 1; i < elementInfoCount; i++) {
+ int64_t levelOrderId = levelOrderTreeVec[i];
+ auto newNode = GetFlutterSemanticsNode(levelOrderId);
+ //当节点为隐藏状态时,自动规避
+ if (IsNodeVisible(newNode)) {
+ ArkUI_AccessibilityElementInfo* (*OH_ArkUI_AddAndGetAccessibilityElementInfo)(ArkUI_AccessibilityElementInfoList*) =
+ OhosAccessibilityDDL::DLLoadGetElemFunc("OH_ArkUI_AddAndGetAccessibilityElementInfo");
+ if (OH_ArkUI_AddAndGetAccessibilityElementInfo == nullptr) {
+ LOGE("OH_ArkUI_AddAndGetAccessibilityElementInfo is null, %{public}s", dlerror());
+ }
+ ArkUI_AccessibilityElementInfo* newElementInfo =
+ OH_ArkUI_AddAndGetAccessibilityElementInfo(elementList);
+ //配置当前子节点信息
+ FlutterNodeToElementInfoById(newElementInfo, levelOrderId);
+ }
+ }
+ }
+}
+
+/**
+ * Called to obtain element information based on a specified node.
+ * NOTE:该arkui接口需要在系统无障碍服务开启时,才能触发调用
+ */
+int32_t OhosAccessibilityBridge::FindAccessibilityNodeInfosById(
+ int64_t elementId,
+ ArkUI_AccessibilitySearchMode mode,
+ int32_t requestId,
+ ArkUI_AccessibilityElementInfoList* elementList)
+{
+ FML_DLOG(INFO)
+ << "#### FindAccessibilityNodeInfosById input-params ####: elementId = "
+ << elementId << " mode=" << mode << " requestId=" << requestId
+ << " elementList= " << elementList;
+
+ if (g_flutterSemanticsTree.size() == 0) {
+ FML_DLOG(INFO)
+ << "FindAccessibilityNodeInfosById g_flutterSemanticsTree is null";
+ return ARKUI_ACCESSIBILITY_NATIVE_RESULT_FAILED;
+ }
+ if (elementList == nullptr) {
+ FML_DLOG(INFO) << "FindAccessibilityNodeInfosById elementList is null";
+ return ARKUI_ACCESSIBILITY_NATIVE_RESULT_FAILED;
+ }
+ // 开启无障碍导航功能
+ if(elementId == -1 || elementId == 0) {
+ accessibilityFeatures_->SetAccessibleNavigation(true, native_shell_holder_id_);
+ }
+
+ if (OH_GetSdkApiVersion() >= 13) {
+ // 从elementinfolist中获取elementinfo
+ ArkUI_AccessibilityElementInfo* (*OH_ArkUI_AddAndGetAccessibilityElementInfo)(ArkUI_AccessibilityElementInfoList*) =
+ OhosAccessibilityDDL::DLLoadGetElemFunc("OH_ArkUI_AddAndGetAccessibilityElementInfo");
+ if (OH_ArkUI_AddAndGetAccessibilityElementInfo == nullptr) {
+ LOGE("OH_ArkUI_AddAndGetAccessibilityElementInfo is null, %{public}s", dlerror());
+ }
+ ArkUI_AccessibilityElementInfo* elementInfoFromList =
+ OH_ArkUI_AddAndGetAccessibilityElementInfo(elementList);
+ if (elementInfoFromList == nullptr) {
+ FML_DLOG(INFO) << "FindAccessibilityNodeInfosById elementInfoFromList is null";
+ return ARKUI_ACCESSIBILITY_NATIVE_RESULT_FAILED;
+ }
+
+ // 过滤非当前屏幕显示的语义节点创建、配置,防止溢出屏幕坐标绘制bug以及优化性能开销
+ auto flutterNode = GetFlutterSemanticsNode(static_cast(elementId));
+
+ if (mode == ArkUI_AccessibilitySearchMode::ARKUI_ACCESSIBILITY_NATIVE_SEARCH_MODE_PREFETCH_CURRENT) {
+ /** Search for current nodes. (mode = 0) */
+ BuildArkUISemanticsTree(elementId, elementInfoFromList, elementList);
+ } else if (mode ==ArkUI_AccessibilitySearchMode::ARKUI_ACCESSIBILITY_NATIVE_SEARCH_MODE_PREFETCH_PREDECESSORS) {
+ /** Search for parent nodes. (mode = 1) */
+ if (IsNodeVisible(flutterNode)) {
+ FlutterNodeToElementInfoById(elementInfoFromList, elementId);
+ }
+ } else if (mode ==ArkUI_AccessibilitySearchMode::ARKUI_ACCESSIBILITY_NATIVE_SEARCH_MODE_PREFETCH_SIBLINGS) {
+ /** Search for sibling nodes. (mode = 2) */
+ if (IsNodeVisible(flutterNode)) {
+ FlutterNodeToElementInfoById(elementInfoFromList, elementId);
+ }
+ } else if (mode ==ArkUI_AccessibilitySearchMode::ARKUI_ACCESSIBILITY_NATIVE_SEARCH_MODE_PREFETCH_CHILDREN) {
+ /** Search for child nodes at the next level. (mode = 4) */
+ if (IsNodeVisible(flutterNode)) {
+ FlutterNodeToElementInfoById(elementInfoFromList, elementId);
+ }
+ } else if (mode ==ArkUI_AccessibilitySearchMode::ARKUI_ACCESSIBILITY_NATIVE_SEARCH_MODE_PREFETCH_RECURSIVE_CHILDREN) {
+ /** Search for all child nodes. (mode = 8) */
+ BuildArkUISemanticsTree(elementId, elementInfoFromList, elementList);
+ } else {
+ FlutterNodeToElementInfoById(elementInfoFromList, elementId);
+ }
+ }
+ FML_DLOG(INFO) << "--- FindAccessibilityNodeInfosById is end ---";
+ return ARKUI_ACCESSIBILITY_NATIVE_RESULT_SUCCESSFUL;
+}
+
+/**
+ * 解析flutter语义动作,并通过NativAccessibilityChannel分发
+ */
+void OhosAccessibilityBridge::DispatchSemanticsAction(
+ int32_t id,
+ flutter::SemanticsAction action,
+ fml::MallocMapping args)
+{
+ nativeAccessibilityChannel_->DispatchSemanticsAction(native_shell_holder_id_,
+ id,
+ action,
+ fml::MallocMapping());
+}
+
+/**
+ * 执行语义动作解析,当FindAccessibilityNodeInfosById找到相应的elementinfo时才会触发该回调函数
+ */
+int32_t OhosAccessibilityBridge::ExecuteAccessibilityAction(
+ int64_t elementId,
+ ArkUI_Accessibility_ActionType action,
+ ArkUI_AccessibilityActionArguments* actionArguments,
+ int32_t requestId)
+{
+ FML_DLOG(INFO) << "ExecuteAccessibilityAction input-params-> elementId="
+ << elementId << " action=" << action
+ << " requestId=" << requestId
+ << " *actionArguments=" << actionArguments;
+
+ if (actionArguments == nullptr) {
+ FML_DLOG(ERROR) << "OhosAccessibilityBridge::ExecuteAccessibilityAction "
+ "actionArguments = null";
+ return ARKUI_ACCESSIBILITY_NATIVE_RESULT_FAILED;
+ }
+
+ // 获取当前elementid对应的flutter语义节点
+ auto flutterNode = GetFlutterSemanticsNode(static_cast(elementId));
+
+ // 根据当前elementid和无障碍动作类型,发送无障碍事件
+ switch (action) {
+ /** Response to a click. 16 */
+ case ArkUI_Accessibility_ActionType::ARKUI_ACCESSIBILITY_NATIVE_ACTION_TYPE_CLICK: {
+ /** Click event, sent after the UI component responds. 1 */
+ auto clickEventType = ArkUI_AccessibilityEventType::
+ ARKUI_ACCESSIBILITY_NATIVE_EVENT_TYPE_CLICKED;
+ Flutter_SendAccessibilityAsyncEvent(elementId, clickEventType);
+ FML_DLOG(INFO) << "ExecuteAccessibilityAction -> action: click(" << action
+ << ")" << " event: click(" << clickEventType << ")";
+ // 解析arkui的屏幕点击 -> flutter对应节点的屏幕点击
+ auto flutterTapAction = ArkuiActionsToFlutterActions(action);
+ DispatchSemanticsAction(static_cast(elementId), flutterTapAction,
+ {});
+ break;
+ }
+ /** Response to a long click. 32 */
+ case ArkUI_Accessibility_ActionType::ARKUI_ACCESSIBILITY_NATIVE_ACTION_TYPE_LONG_CLICK: {
+ /** Long click event, sent after the UI component responds. 2 */
+ auto longClickEventType = ArkUI_AccessibilityEventType::
+ ARKUI_ACCESSIBILITY_NATIVE_EVENT_TYPE_LONG_CLICKED;
+ Flutter_SendAccessibilityAsyncEvent(elementId, longClickEventType);
+ FML_DLOG(INFO) << "ExecuteAccessibilityAction -> action: longclick("
+ << action << ")" << " event: longclick("
+ << longClickEventType << ")";
+ // 解析arkui的屏幕动作 -> flutter对应节点的屏幕动作
+ auto flutterLongPressAction = ArkuiActionsToFlutterActions(action);
+ DispatchSemanticsAction(static_cast(elementId),
+ flutterLongPressAction, {});
+ break;
+ }
+ /** Accessibility focus acquisition. 64 */
+ case ArkUI_Accessibility_ActionType::ARKUI_ACCESSIBILITY_NATIVE_ACTION_TYPE_GAIN_ACCESSIBILITY_FOCUS: {
+ // 解析arkui的获焦 -> flutter对应节点的获焦
+ auto flutterGainFocusAction = ArkuiActionsToFlutterActions(action);
+ DispatchSemanticsAction(static_cast(elementId),
+ flutterGainFocusAction, {});
+ // Accessibility focus event, sent after the UI component responds. 32768
+ auto focusEventType = ArkUI_AccessibilityEventType::
+ ARKUI_ACCESSIBILITY_NATIVE_EVENT_TYPE_ACCESSIBILITY_FOCUSED;
+ Flutter_SendAccessibilityAsyncEvent(elementId, focusEventType);
+ FML_DLOG(INFO) << "ExecuteAccessibilityAction -> action: focus(" << action
+ << ")" << " event: focus(" << focusEventType << ")";
+ if (flutterNode.HasAction(ACTIONS_::kIncrease) ||
+ flutterNode.HasAction(ACTIONS_::kDecrease)) {
+ Flutter_SendAccessibilityAsyncEvent(
+ elementId, ArkUI_AccessibilityEventType::
+ ARKUI_ACCESSIBILITY_NATIVE_EVENT_TYPE_SELECTED);
+ }
+ break;
+ }
+ /** Accessibility focus clearance. 128 */
+ case ArkUI_Accessibility_ActionType::ARKUI_ACCESSIBILITY_NATIVE_ACTION_TYPE_CLEAR_ACCESSIBILITY_FOCUS: {
+ // 解析arkui的失焦 -> flutter对应节点的失焦
+ auto flutterLoseFocusAction = ArkuiActionsToFlutterActions(action);
+ DispatchSemanticsAction(static_cast(elementId),
+ flutterLoseFocusAction, {});
+ /** Accessibility focus cleared event, sent after the UI component
+ * responds. 65536 */
+ auto clearFocusEventType = ArkUI_AccessibilityEventType::
+ ARKUI_ACCESSIBILITY_NATIVE_EVENT_TYPE_ACCESSIBILITY_FOCUS_CLEARED;
+ Flutter_SendAccessibilityAsyncEvent(elementId, clearFocusEventType);
+ FML_DLOG(INFO) << "ExecuteAccessibilityAction -> action: clearfocus("
+ << action << ")" << " event: clearfocus("
+ << clearFocusEventType << ")";
+ break;
+ }
+ /** Forward scroll action. 256 */
+ case ArkUI_Accessibility_ActionType::ARKUI_ACCESSIBILITY_NATIVE_ACTION_TYPE_SCROLL_FORWARD: {
+ // flutter scroll forward with different situations
+ if (flutterNode.HasAction(ACTIONS_::kScrollUp)) {
+ auto flutterScrollUpAction = ArkuiActionsToFlutterActions(action);
+ DispatchSemanticsAction(static_cast(elementId),
+ flutterScrollUpAction, {});
+ } else if (flutterNode.HasAction(ACTIONS_::kScrollLeft)) {
+ DispatchSemanticsAction(static_cast(elementId),
+ ACTIONS_::kScrollLeft, {});
+ } else if (flutterNode.HasAction(ACTIONS_::kIncrease)) {
+ flutterNode.value = flutterNode.increasedValue;
+ flutterNode.valueAttributes = flutterNode.increasedValueAttributes;
+
+ Flutter_SendAccessibilityAsyncEvent(
+ elementId, ArkUI_AccessibilityEventType::
+ ARKUI_ACCESSIBILITY_NATIVE_EVENT_TYPE_SELECTED);
+ DispatchSemanticsAction(static_cast(elementId),
+ ACTIONS_::kIncrease, {});
+ } else {
+ }
+ std::string currComponetType = GetNodeComponentType(flutterNode);
+ if (currComponetType == "ListView") {
+ /** Scrolled event, sent when a scrollable component experiences a
+ * scroll event. 4096 */
+ ArkUI_AccessibilityEventType scrollEventType1 =
+ ArkUI_AccessibilityEventType::
+ ARKUI_ACCESSIBILITY_NATIVE_EVENT_TYPE_SCROLLED;
+ Flutter_SendAccessibilityAsyncEvent(elementId, scrollEventType1);
+ FML_DLOG(INFO)
+ << "ExecuteAccessibilityAction -> action: scroll forward(" << action
+ << ")" << " event: scroll forward(" << scrollEventType1 << ")";
+ }
+ break;
+ }
+ /** Backward scroll action. 512 */
+ case ArkUI_Accessibility_ActionType::ARKUI_ACCESSIBILITY_NATIVE_ACTION_TYPE_SCROLL_BACKWARD: {
+ // flutter scroll down with different situations
+ if (flutterNode.HasAction(ACTIONS_::kScrollDown)) {
+ auto flutterScrollDownAction = ArkuiActionsToFlutterActions(action);
+ DispatchSemanticsAction(static_cast(elementId),
+ flutterScrollDownAction, {});
+ } else if (flutterNode.HasAction(ACTIONS_::kScrollRight)) {
+ DispatchSemanticsAction(static_cast(elementId),
+ ACTIONS_::kScrollRight, {});
+ } else if (flutterNode.HasAction(ACTIONS_::kDecrease)) {
+ flutterNode.value = flutterNode.decreasedValue;
+ flutterNode.valueAttributes = flutterNode.decreasedValueAttributes;
+
+ Flutter_SendAccessibilityAsyncEvent(
+ elementId, ArkUI_AccessibilityEventType::
+ ARKUI_ACCESSIBILITY_NATIVE_EVENT_TYPE_SELECTED);
+ DispatchSemanticsAction(static_cast(elementId),
+ ACTIONS_::kDecrease, {});
+ } else {
+ }
+ std::string currComponetType = GetNodeComponentType(flutterNode);
+ if (currComponetType == "ListView") {
+ /** Scrolled event, sent when a scrollable component experiences a
+ * scroll event. 4096 */
+ ArkUI_AccessibilityEventType scrollBackwardEventType =
+ ArkUI_AccessibilityEventType::
+ ARKUI_ACCESSIBILITY_NATIVE_EVENT_TYPE_SCROLLED;
+ Flutter_SendAccessibilityAsyncEvent(elementId, scrollBackwardEventType);
+ FML_DLOG(INFO)
+ << "ExecuteAccessibilityAction -> action: scroll backward("
+ << action << ")" << " event: scroll backward("
+ << scrollBackwardEventType << ")";
+ }
+ break;
+ }
+ /** Copy action for text content. 1024 */
+ case ArkUI_Accessibility_ActionType::ARKUI_ACCESSIBILITY_NATIVE_ACTION_TYPE_COPY: {
+ FML_DLOG(INFO) << "ExecuteAccessibilityAction -> action: copy(" << action
+ << ")";
+ DispatchSemanticsAction(static_cast(elementId), ACTIONS_::kCopy, {});
+ break;
+ }
+ /** Paste action for text content. 2048 */
+ case ArkUI_Accessibility_ActionType::ARKUI_ACCESSIBILITY_NATIVE_ACTION_TYPE_PASTE: {
+ FML_DLOG(INFO) << "ExecuteAccessibilityAction -> action: paste(" << action
+ << ")";
+ DispatchSemanticsAction(static_cast(elementId), ACTIONS_::kPaste, {});
+ break;
+ }
+ /** Cut action for text content. 4096 */
+ case ArkUI_Accessibility_ActionType::ARKUI_ACCESSIBILITY_NATIVE_ACTION_TYPE_CUT: {
+ FML_DLOG(INFO) << "ExecuteAccessibilityAction -> action: cut(" << action << ")";
+ DispatchSemanticsAction(static_cast(elementId), ACTIONS_::kCut, {});
+ break;
+ }
+ /** Text selection action, requiring the setting of selectTextBegin,
+ * TextEnd, and TextInForward parameters to select a text
+ * segment in the text box. 8192 */
+ case ArkUI_Accessibility_ActionType::ARKUI_ACCESSIBILITY_NATIVE_ACTION_TYPE_SELECT_TEXT: {
+ FML_DLOG(INFO) << "ExecuteAccessibilityAction -> action: select text("
+ << action << ")";
+ // 输入框文本选择操作
+ PerformSelectText(flutterNode, action, actionArguments);
+ break;
+ }
+ /** Text content setting action. 16384 */
+ case ArkUI_Accessibility_ActionType::ARKUI_ACCESSIBILITY_NATIVE_ACTION_TYPE_SET_TEXT: {
+ FML_DLOG(INFO) << "ExecuteAccessibilityAction -> action: set text("
+ << action << ")";
+ // 输入框设置文本
+ PerformSetText(flutterNode, action, actionArguments);
+ break;
+ }
+ /** Cursor position setting action. 1048576 */
+ case ArkUI_Accessibility_ActionType::ARKUI_ACCESSIBILITY_NATIVE_ACTION_TYPE_SET_CURSOR_POSITION: {
+ FML_DLOG(INFO)
+ << "ExecuteAccessibilityAction -> action: set cursor position("
+ << action << ")";
+ break;
+ }
+ /** Invalid action. 0 */
+ case ArkUI_Accessibility_ActionType::ARKUI_ACCESSIBILITY_NATIVE_ACTION_TYPE_INVALID: {
+ /** Invalid event. 0 */
+ ArkUI_AccessibilityEventType invalidEventType =
+ ArkUI_AccessibilityEventType::
+ ARKUI_ACCESSIBILITY_NATIVE_EVENT_TYPE_INVALID;
+ Flutter_SendAccessibilityAsyncEvent(elementId, invalidEventType);
+ FML_DLOG(ERROR) << "ExecuteAccessibilityAction -> action: invalid("
+ << action << ")" << " event: innvalid("
+ << invalidEventType << ")";
+ break;
+ }
+ default: {
+ /** custom semantics action */
+ }
+ }
+ FML_DLOG(INFO) << "--- ExecuteAccessibilityAction is end ---";
+ return ARKUI_ACCESSIBILITY_NATIVE_RESULT_SUCCESSFUL;
+}
+
+/**
+ * Called to obtain element information based on a specified node and text
+ * content.
+ */
+int32_t OhosAccessibilityBridge::FindAccessibilityNodeInfosByText(
+ int64_t elementId,
+ const char* text,
+ int32_t requestId,
+ ArkUI_AccessibilityElementInfoList* elementList)
+{
+ FML_DLOG(INFO) << "=== FindAccessibilityNodeInfosByText() ===";
+ return 0;
+}
+int32_t OhosAccessibilityBridge::FindFocusedAccessibilityNode(
+ int64_t elementId,
+ ArkUI_AccessibilityFocusType focusType,
+ int32_t requestId,
+ ArkUI_AccessibilityElementInfo* elementinfo)
+{
+ FML_DLOG(INFO) << "=== FindFocusedAccessibilityNode() ===";
+ return 0;
+}
+int32_t OhosAccessibilityBridge::FindNextFocusAccessibilityNode(
+ int64_t elementId,
+ ArkUI_AccessibilityFocusMoveDirection direction,
+ int32_t requestId,
+ ArkUI_AccessibilityElementInfo* elementList) {
+ FML_DLOG(INFO) << "=== FindNextFocusAccessibilityNode() ===";
+ return 0;
+}
+
+int32_t OhosAccessibilityBridge::ClearFocusedFocusAccessibilityNode()
+{
+ FML_DLOG(INFO) << "=== ClearFocusedFocusAccessibilityNode() ===";
+ return 0;
+}
+int32_t OhosAccessibilityBridge::GetAccessibilityNodeCursorPosition(
+ int64_t elementId,
+ int32_t requestId,
+ int32_t* index)
+{
+ FML_DLOG(INFO) << "=== GetAccessibilityNodeCursorPosition() ===";
+ return 0;
+}
+
+/**
+ * 将arkui的action类型转化为flutter的action类型
+ */
+flutter::SemanticsAction OhosAccessibilityBridge::ArkuiActionsToFlutterActions(
+ ArkUI_Accessibility_ActionType arkui_action)
+{
+ switch (arkui_action) {
+ case ArkUI_Accessibility_ActionType::
+ ARKUI_ACCESSIBILITY_NATIVE_ACTION_TYPE_CLICK:
+ return ACTIONS_::kTap;
+
+ case ArkUI_Accessibility_ActionType::
+ ARKUI_ACCESSIBILITY_NATIVE_ACTION_TYPE_LONG_CLICK:
+ return ACTIONS_::kLongPress;
+
+ case ArkUI_Accessibility_ActionType::
+ ARKUI_ACCESSIBILITY_NATIVE_ACTION_TYPE_SCROLL_FORWARD:
+ return ACTIONS_::kScrollUp;
+
+ case ArkUI_Accessibility_ActionType::
+ ARKUI_ACCESSIBILITY_NATIVE_ACTION_TYPE_SCROLL_BACKWARD:
+ return ACTIONS_::kScrollDown;
+
+ case ArkUI_Accessibility_ActionType::
+ ARKUI_ACCESSIBILITY_NATIVE_ACTION_TYPE_COPY:
+ return ACTIONS_::kCopy;
+
+ case ArkUI_Accessibility_ActionType::
+ ARKUI_ACCESSIBILITY_NATIVE_ACTION_TYPE_CUT:
+ return ACTIONS_::kCut;
+
+ case ArkUI_Accessibility_ActionType::
+ ARKUI_ACCESSIBILITY_NATIVE_ACTION_TYPE_GAIN_ACCESSIBILITY_FOCUS:
+ return ACTIONS_::kDidGainAccessibilityFocus;
+
+ case ArkUI_Accessibility_ActionType::
+ ARKUI_ACCESSIBILITY_NATIVE_ACTION_TYPE_CLEAR_ACCESSIBILITY_FOCUS:
+ return ACTIONS_::kDidLoseAccessibilityFocus;
+
+ // Text selection action, requiring the setting of selectTextBegin,
+ // TextEnd, and TextInForward parameters to select a text
+ // segment in the text box. */
+ case ArkUI_Accessibility_ActionType::
+ ARKUI_ACCESSIBILITY_NATIVE_ACTION_TYPE_SELECT_TEXT:
+ return ACTIONS_::kSetSelection;
+
+ case ArkUI_Accessibility_ActionType::
+ ARKUI_ACCESSIBILITY_NATIVE_ACTION_TYPE_SET_TEXT:
+ return ACTIONS_::kSetText;
+
+ default:
+ // might not match to the valid action in arkui
+ return ACTIONS_::kCustomAction;
+ }
+}
+
+/**
+ * 自定义无障碍异步事件发送
+ */
+void OhosAccessibilityBridge::Flutter_SendAccessibilityAsyncEvent(
+ int64_t elementId,
+ ArkUI_AccessibilityEventType eventType)
+{
+ if (provider_ == nullptr) {
+ FML_DLOG(ERROR) << "Flutter_SendAccessibilityAsyncEvent "
+ "AccessibilityProvider = nullptr";
+ return;
+ }
+ if (OH_GetSdkApiVersion() >= 13) {
+ // 1.创建eventInfo对象
+ ArkUI_AccessibilityEventInfo* (*OH_ArkUI_CreateAccessibilityEventInfo)(void) =
+ OhosAccessibilityDDL::DLLoadCreateEventInfoFunc("OH_ArkUI_CreateAccessibilityEventInfo");
+ if (OH_ArkUI_CreateAccessibilityEventInfo == nullptr) {
+ LOGE("OH_ArkUI_CreateAccessibilityEventInfo is null, %{public}s", dlerror());
+ }
+ ArkUI_AccessibilityEventInfo* eventInfo = OH_ArkUI_CreateAccessibilityEventInfo();
+ if (eventInfo == nullptr) {
+ FML_DLOG(ERROR) << "Flutter_SendAccessibilityAsyncEvent "
+ "OH_ArkUI_CreateAccessibilityEventInfo eventInfo = null";
+ return;
+ }
+
+ // 2.创建的elementinfo并根据对应id的flutternode进行属性初始化
+ ArkUI_AccessibilityElementInfo* (*OH_ArkUI_CreateAccessibilityElementInfo)(void) =
+ OhosAccessibilityDDL::DLLoadCreateElemInfoFunc("OH_ArkUI_CreateAccessibilityElementInfo");
+ if (OH_ArkUI_CreateAccessibilityElementInfo == nullptr) {
+ LOGE("OH_ArkUI_CreateAccessibilityElementInfo is null, %{public}s", dlerror());
+ }
+ ArkUI_AccessibilityElementInfo* _elementInfo = OH_ArkUI_CreateAccessibilityElementInfo();
+ FlutterNodeToElementInfoById(_elementInfo, elementId);
+ // 若为获焦事件,则设置当前elementinfo获焦
+ int32_t (*OH_ArkUI_AccessibilityElementInfoSetAccessibilityFocused)(ArkUI_AccessibilityElementInfo*, bool) =
+ OhosAccessibilityDDL::DLLoadSetElemBoolFunc("OH_ArkUI_AccessibilityElementInfoSetAccessibilityFocused");
+ if (OH_ArkUI_AccessibilityElementInfoSetAccessibilityFocused == nullptr) {
+ LOGE("OH_ArkUI_AccessibilityElementInfoSetAccessibilityFocused is null, %{public}s", dlerror());
+ }
+ if (eventType == ArkUI_AccessibilityEventType::ARKUI_ACCESSIBILITY_NATIVE_EVENT_TYPE_ACCESSIBILITY_FOCUSED) {
+ ARKUI_ACCESSIBILITY_CALL_CHECK(
+ OH_ArkUI_AccessibilityElementInfoSetAccessibilityFocused(_elementInfo, true)
+ );
+ }
+
+ // 3.设置发送事件,如配置获焦、失焦、点击、滑动事件
+ int32_t (*OH_ArkUI_AccessibilityEventSetEventType)(ArkUI_AccessibilityEventInfo*, ArkUI_AccessibilityEventType) =
+ OhosAccessibilityDDL::DLLoadSetEventFunc("OH_ArkUI_AccessibilityEventSetEventType");
+ if (OH_ArkUI_AccessibilityEventSetEventType == nullptr) {
+ LOGE("OH_ArkUI_AccessibilityEventSetEventType is null, %{public}s", dlerror());
+ }
+ ARKUI_ACCESSIBILITY_CALL_CHECK(OH_ArkUI_AccessibilityEventSetEventType(eventInfo, eventType));
+
+ // 4.将eventinfo事件和当前elementinfo进行绑定
+ int32_t (*OH_ArkUI_AccessibilityEventSetElementInfo)(ArkUI_AccessibilityEventInfo*, ArkUI_AccessibilityElementInfo*) =
+ OhosAccessibilityDDL::DLLoadSetEventElemFunc("OH_ArkUI_AccessibilityEventSetElementInfo");
+ if (OH_ArkUI_AccessibilityEventSetElementInfo == nullptr) {
+ LOGE("OH_ArkUI_AccessibilityEventSetElementInfo is null, %{public}s", dlerror());
+ }
+ ARKUI_ACCESSIBILITY_CALL_CHECK(OH_ArkUI_AccessibilityEventSetElementInfo(eventInfo, _elementInfo));
+
+ // 5.调用接口发送到ohos侧
+ auto callback = [](int32_t errorCode) {
+ FML_DLOG(INFO)
+ << "Flutter_SendAccessibilityAsyncEvent callback-> errorCode ="
+ << errorCode;
+ };
+
+ // 6.发送event到OH侧
+ void (*OH_ArkUI_SendAccessibilityAsyncEvent)(ArkUI_AccessibilityProvider*, ArkUI_AccessibilityEventInfo*, void (*callback)(int32_t)) =
+ OhosAccessibilityDDL::DLLoadSendAsyncEventFunc("OH_ArkUI_SendAccessibilityAsyncEvent");
+ if (OH_ArkUI_SendAccessibilityAsyncEvent == nullptr) {
+ LOGE("OH_ArkUI_SendAccessibilityAsyncEvent is null, %{public}s", dlerror());
+ }
+ OH_ArkUI_SendAccessibilityAsyncEvent(provider_, eventInfo, callback);
+
+ // 7.销毁新创建的elementinfo, eventinfo
+ void (*OH_ArkUI_DestoryAccessibilityElementInfo)(ArkUI_AccessibilityElementInfo*) =
+ OhosAccessibilityDDL::DLLoadDestroyElemFunc("OH_ArkUI_DestoryAccessibilityElementInfo");
+ if (OH_ArkUI_DestoryAccessibilityElementInfo == nullptr) {
+ LOGE("OH_ArkUI_DestoryAccessibilityElementInfo is null, %{public}s", dlerror());
+ }
+ OH_ArkUI_DestoryAccessibilityElementInfo(_elementInfo);
+ _elementInfo = nullptr;
+
+ void (*OH_ArkUI_DestoryAccessibilityEventInfo)(ArkUI_AccessibilityEventInfo*) =
+ OhosAccessibilityDDL::DLLoadDestroyEventFunc("OH_ArkUI_DestoryAccessibilityEventInfo");
+ if (OH_ArkUI_DestoryAccessibilityEventInfo == nullptr) {
+ LOGE("OH_ArkUI_DestoryAccessibilityEventInfo is null, %{public}s", dlerror());
+ }
+ OH_ArkUI_DestoryAccessibilityEventInfo(eventInfo);
+ eventInfo = nullptr;
+ }
+
+ FML_DLOG(INFO) << "OhosAccessibilityBridge::Flutter_SendAccessibilityAsyncEvent is end";
+ return;
+}
+
+/**
+ * 判断当前语义节点是否获焦
+ */
+bool OhosAccessibilityBridge::IsNodeFocusable(
+ const flutter::SemanticsNode& node)
+{
+ if (node.HasFlag(FLAGS_::kScopesRoute)) {
+ return false;
+ }
+ if (node.HasFlag(FLAGS_::kIsFocusable)) {
+ return true;
+ }
+ // Always consider platform views focusable.
+ if (node.IsPlatformViewNode()) {
+ return true;
+ }
+ // Always consider actionable nodes focusable.
+ if (node.actions != 0) {
+ return true;
+ }
+ if ((node.flags & FOCUSABLE_FLAGS) != 0) {
+ return true;
+ }
+ if ((node.actions & ~FOCUSABLE_FLAGS) != 0) {
+ return true;
+ }
+ // Consider text nodes focusable.
+ return !node.label.empty() || !node.value.empty() || !node.hint.empty();
+}
+
+void OhosAccessibilityBridge::PerformSetText(
+ flutter::SemanticsNode flutterNode,
+ ArkUI_Accessibility_ActionType action,
+ ArkUI_AccessibilityActionArguments* actionArguments) {}
+
+void OhosAccessibilityBridge::PerformSelectText(
+ flutter::SemanticsNode flutterNode,
+ ArkUI_Accessibility_ActionType action,
+ ArkUI_AccessibilityActionArguments* actionArguments) {}
+
+/**
+ * 获取当前flutter节点的组件类型,并映射为arkui组件
+ */
+std::string OhosAccessibilityBridge::GetNodeComponentType(
+ const flutter::SemanticsNode& node)
+{
+ if (node.HasFlag(FLAGS_::kIsButton)) {
+ return "Button";
+ }
+ if (node.HasFlag(FLAGS_::kIsTextField)) {
+ return "TextField";
+ }
+ if (node.HasFlag(FLAGS_::kIsMultiline)) {
+ return "TextArea";
+ }
+ if (node.HasFlag(FLAGS_::kIsLink)) {
+ return "Link";
+ }
+ if (node.HasFlag(FLAGS_::kIsSlider) || node.HasAction(ACTIONS_::kIncrease) ||
+ node.HasAction(ACTIONS_::kDecrease)) {
+ return "Slider";
+ }
+ if (node.HasFlag(FLAGS_::kIsHeader)) {
+ return "Header";
+ }
+ if (node.HasFlag(FLAGS_::kIsImage)) {
+ return "Image";
+ }
+ if (node.HasFlag(FLAGS_::kHasCheckedState)) {
+ if (node.HasFlag(FLAGS_::kIsInMutuallyExclusiveGroup)) {
+ // arkui没有RadioButton,这里透传为RadioButton
+ return "RadioButton";
+ } else {
+ return "Checkbox";
+ }
+ }
+ if (node.HasFlag(FLAGS_::kHasToggledState)) {
+ return "Switch";
+ }
+ if (node.HasAction(ACTIONS_::kIncrease) ||
+ node.HasAction(ACTIONS_::kDecrease)) {
+ return "SeekBar";
+ }
+ if (node.HasFlag(FLAGS_::kHasImplicitScrolling)) {
+ if (node.HasAction(ACTIONS_::kScrollLeft) ||
+ node.HasAction(ACTIONS_::kScrollRight)) {
+ return "HorizontalScrollView";
+ } else {
+ return "ScrollView";
+ }
+ }
+ if ((!node.label.empty() || !node.tooltip.empty() || !node.hint.empty())) {
+ return "Text";
+ }
+ return "Widget";
+}
+
+/**
+ * 判断当前节点是否为textfield文本框
+ */
+bool OhosAccessibilityBridge::IsTextField(flutter::SemanticsNode flutterNode)
+{
+ return flutterNode.HasFlag(FLAGS_::kIsTextField);
+}
+/**
+ * 判断当前节点是否为滑动条slider类型
+ */
+bool OhosAccessibilityBridge::IsSlider(flutter::SemanticsNode flutterNode)
+{
+ return flutterNode.HasFlag(FLAGS_::kIsSlider);
+}
+/**
+ * 判断当前flutter节点组件是否可点击
+ */
+bool OhosAccessibilityBridge::IsNodeClickable(
+ flutter::SemanticsNode flutterNode)
+{
+ return flutterNode.HasAction(ACTIONS_::kTap);
+ flutterNode.HasFlag(FLAGS_::kHasCheckedState) ||
+ flutterNode.HasFlag(FLAGS_::kIsButton) ||
+ flutterNode.HasFlag(FLAGS_::kIsTextField) ||
+ flutterNode.HasFlag(FLAGS_::kIsImage) ||
+ flutterNode.HasFlag(FLAGS_::kIsLiveRegion) ||
+ flutterNode.HasFlag(FLAGS_::kIsMultiline) ||
+ flutterNode.HasFlag(FLAGS_::kIsLink) ||
+ flutterNode.HasFlag(FLAGS_::kIsSlider) ||
+ flutterNode.HasFlag(FLAGS_::kIsKeyboardKey) ||
+ flutterNode.HasFlag(FLAGS_::kHasToggledState) ||
+ flutterNode.HasFlag(FLAGS_::kHasImplicitScrolling);
+}
+/**
+ * 判断当前flutter节点组件是否可显示
+ */
+bool OhosAccessibilityBridge::IsNodeVisible(
+ flutter::SemanticsNode flutterNode)
+{
+ return flutterNode.HasFlag(FLAGS_::kIsHidden) ? false : true;
+}
+/**
+ * 判断当前flutter节点组件是否具备checkable属性
+ */
+bool OhosAccessibilityBridge::IsNodeCheckable(
+ flutter::SemanticsNode flutterNode)
+{
+ return flutterNode.HasFlag(FLAGS_::kHasCheckedState) ||
+ flutterNode.HasFlag(FLAGS_::kHasToggledState);
+}
+/**
+ * 判断当前flutter节点组件是否checked/unchecked(checkbox、radio button)
+ */
+bool OhosAccessibilityBridge::IsNodeChecked(
+ flutter::SemanticsNode flutterNode)
+{
+ return flutterNode.HasFlag(FLAGS_::kIsChecked) ||
+ flutterNode.HasFlag(FLAGS_::kIsToggled);
+}
+/**
+ * 判断当前flutter节点组件是否选中
+ */
+bool OhosAccessibilityBridge::IsNodeSelected(
+ flutter::SemanticsNode flutterNode)
+{
+ return flutterNode.HasFlag(FLAGS_::kIsSelected);
+}
+/**
+ * 判断当前flutter节点组件是否为密码输入框
+ */
+bool OhosAccessibilityBridge::IsNodePassword(
+ flutter::SemanticsNode flutterNode)
+{
+ return flutterNode.HasFlag(FLAGS_::kIsTextField) &&
+ flutterNode.HasFlag(FLAGS_::kIsObscured);
+}
+/**
+ * 判断当前flutter节点组件是否支持长按功能
+ */
+bool OhosAccessibilityBridge::IsNodeHasLongPress(
+ flutter::SemanticsNode flutterNode)
+{
+ return flutterNode.HasAction(ACTIONS_::kLongPress);
+}
+/**
+ * 判断当前flutter节点是否enabled
+ */
+bool OhosAccessibilityBridge::IsNodeEnabled(
+ flutter::SemanticsNode flutterNode)
+{
+ return !flutterNode.HasFlag(FLAGS_::kHasEnabledState) ||
+ flutterNode.HasFlag(FLAGS_::kIsEnabled);
+}
+/**
+ * 判断当前节点是否已经滑动
+ */
+bool OhosAccessibilityBridge::HasScrolled(
+ const flutter::SemanticsNode& flutterNode)
+{
+ return flutterNode.scrollPosition != std::nan("");
+}
+/**
+ * 判断是否可滑动
+ */
+bool OhosAccessibilityBridge::IsNodeScrollable(
+ flutter::SemanticsNode flutterNode)
+{
+ return flutterNode.HasAction(ACTIONS_::kScrollLeft) ||
+ flutterNode.HasAction(ACTIONS_::kScrollRight) ||
+ flutterNode.HasAction(ACTIONS_::kScrollUp) ||
+ flutterNode.HasAction(ACTIONS_::kScrollDown);
+}
+/**
+ * 判断当前节点组件是否是滑动组件,如: listview, gridview等
+ */
+bool OhosAccessibilityBridge::IsScrollableWidget(
+ flutter::SemanticsNode flutterNode)
+{
+ return flutterNode.HasFlag(FLAGS_::kHasImplicitScrolling);
+}
+
+void OhosAccessibilityBridge::AddRouteNodes(
+ std::vector edges,
+ flutter::SemanticsNode node)
+{
+ if (node.HasFlag(FLAGS_::kScopesRoute)) {
+ edges.emplace_back(node);
+ }
+ for (auto& childNodeId : node.childrenInTraversalOrder) {
+ auto childNode = GetFlutterSemanticsNode(childNodeId);
+ AddRouteNodes(edges, childNode);
+ }
+}
+
+std::string OhosAccessibilityBridge::GetRouteName(flutter::SemanticsNode node)
+{
+ if (node.HasFlag(FLAGS_::kNamesRoute) && !node.label.empty()) {
+ return node.label;
+ }
+ for (auto& childNodeId : node.childrenInTraversalOrder) {
+ auto childNode = GetFlutterSemanticsNode(childNodeId);
+ std::string newName = GetRouteName(childNode);
+ if (!newName.empty()) {
+ return newName;
+ }
+ }
+ return "";
+}
+
+void OhosAccessibilityBridge::onWindowNameChange(flutter::SemanticsNode route)
+{
+ std::string routeName = GetRouteName(route);
+ if (routeName.empty()) {
+ routeName = " ";
+ }
+ Flutter_SendAccessibilityAsyncEvent(
+ static_cast(route.id),
+ ArkUI_AccessibilityEventType::ARKUI_ACCESSIBILITY_NATIVE_EVENT_TYPE_PAGE_CONTENT_UPDATE);
+}
+
+void OhosAccessibilityBridge::removeSemanticsNode(
+ flutter::SemanticsNode nodeToBeRemoved)
+{
+ if (!g_flutterSemanticsTree.size()) {
+ FML_DLOG(ERROR) << "OhosAccessibilityBridge::removeSemanticsNode -> "
+ "g_flutterSemanticsTree.szie()=0";
+ return;
+ }
+ if (g_flutterSemanticsTree.find(nodeToBeRemoved.id) ==
+ g_flutterSemanticsTree.end()) {
+ FML_DLOG(INFO) << "Attempted to remove a node that is not in the tree.";
+ }
+ int32_t nodeToBeRemovedParentId = GetParentId(nodeToBeRemoved.id);
+ for (auto it = g_parentChildIdVec.begin(); it != g_parentChildIdVec.end(); it++) {
+ if (it->first == nodeToBeRemovedParentId &&
+ it->second == nodeToBeRemoved.id) {
+ g_parentChildIdVec.erase(it);
+ }
+ }
+}
+
+/**
+ * when the system accessibility service is shut down,
+ * clear all the flutter semantics-relevant caches like maps, vectors
+ */
+void OhosAccessibilityBridge::ClearFlutterSemanticsCaches()
+{
+ g_flutterSemanticsTree.clear();
+ g_parentChildIdVec.clear();
+ g_screenRectMap.clear();
+ g_actions_mp.clear();
+ g_flutterNavigationVec.clear();
+}
+
+/**
+ * extent common struct SemanticsNode to
+ * derived struct SemanticsNodeExtent
+ */
+SemanticsNodeExtent OhosAccessibilityBridge::SetAndGetSemanticsNodeExtent(
+ flutter::SemanticsNode node)
+{
+ SemanticsNodeExtent nodeEx = SemanticsNodeExtent();
+ nodeEx.id = std::move(node.id);
+ nodeEx.flags = std::move(node.flags);
+ nodeEx.actions = std::move(node.actions);
+ nodeEx.maxValueLength = std::move(node.maxValueLength);
+ nodeEx.currentValueLength = std::move(node.currentValueLength);
+ nodeEx.textSelectionBase = std::move(node.textSelectionBase);
+ nodeEx.textSelectionExtent = std::move(node.textSelectionExtent);
+ nodeEx.platformViewId = std::move(node.platformViewId);
+ nodeEx.scrollChildren = std::move(node.scrollChildren);
+ nodeEx.scrollIndex = std::move(node.scrollIndex);
+ nodeEx.scrollPosition = std::move(node.scrollPosition);
+ nodeEx.scrollExtentMax = std::move(node.scrollExtentMax);
+ nodeEx.scrollExtentMin = std::move(node.scrollExtentMin);
+ nodeEx.elevation = std::move(node.elevation);
+ nodeEx.thickness = std::move(node.thickness);
+ nodeEx.label = std::move(node.label);
+ nodeEx.labelAttributes = std::move(node.labelAttributes);
+ nodeEx.hint = std::move(node.hint);
+ nodeEx.hintAttributes = std::move(node.hintAttributes);
+ nodeEx.value = std::move(node.value);
+ nodeEx.valueAttributes = std::move(node.valueAttributes);
+ nodeEx.increasedValue = std::move(node.increasedValue);
+ nodeEx.increasedValueAttributes = std::move(node.increasedValueAttributes);
+ nodeEx.decreasedValue = std::move(node.decreasedValue);
+ nodeEx.decreasedValueAttributes = std::move(node.decreasedValueAttributes);
+ nodeEx.tooltip = std::move(node.tooltip);
+ nodeEx.textDirection = std::move(node.textDirection);
+
+ nodeEx.rect = std::move(node.rect);
+ nodeEx.transform = std::move(node.transform);
+ nodeEx.childrenInTraversalOrder = std::move(node.childrenInTraversalOrder);
+ nodeEx.childrenInHitTestOrder = std::move(node.childrenInHitTestOrder);
+ nodeEx.customAccessibilityActions =
+ std::move(node.customAccessibilityActions);
+ return nodeEx;
+}
+
+void OhosAccessibilityBridge::GetSemanticsNodeDebugInfo(
+ flutter::SemanticsNode node)
+{
+ FML_DLOG(INFO) << "-------------------SemanticsNode------------------";
+ SkMatrix _transform = node.transform.asM33();
+ FML_DLOG(INFO) << "node.id=" << node.id;
+ FML_DLOG(INFO) << "node.label=" << node.label;
+ FML_DLOG(INFO) << "node.tooltip=" << node.tooltip;
+ FML_DLOG(INFO) << "node.hint=" << node.hint;
+ FML_DLOG(INFO) << "node.flags=" << node.flags;
+ FML_DLOG(INFO) << "node.actions=" << node.actions;
+ FML_DLOG(INFO) << "node.rect= {" << node.rect.fLeft << ", " << node.rect.fTop
+ << ", " << node.rect.fRight << ", " << node.rect.fBottom
+ << "}";
+ FML_DLOG(INFO) << "node.transform -> kMScaleX="
+ << _transform.get(SkMatrix::kMScaleX);
+ FML_DLOG(INFO) << "node.transform -> kMSkewX="
+ << _transform.get(SkMatrix::kMSkewX);
+ FML_DLOG(INFO) << "node.transform -> kMTransX="
+ << _transform.get(SkMatrix::kMTransX);
+ FML_DLOG(INFO) << "node.transform -> kMSkewY="
+ << _transform.get(SkMatrix::kMSkewY);
+ FML_DLOG(INFO) << "node.transform -> kMScaleY="
+ << _transform.get(SkMatrix::kMScaleY);
+ FML_DLOG(INFO) << "node.transform -> kMTransY="
+ << _transform.get(SkMatrix::kMTransY);
+ FML_DLOG(INFO) << "node.transform -> kMPersp0="
+ << _transform.get(SkMatrix::kMPersp0);
+ FML_DLOG(INFO) << "node.transform -> kMPersp1="
+ << _transform.get(SkMatrix::kMPersp1);
+ FML_DLOG(INFO) << "node.transform -> kMPersp2="
+ << _transform.get(SkMatrix::kMPersp2);
+ FML_DLOG(INFO) << "node.maxValueLength=" << node.maxValueLength;
+ FML_DLOG(INFO) << "node.currentValueLength=" << node.currentValueLength;
+ FML_DLOG(INFO) << "node.textSelectionBase=" << node.textSelectionBase;
+ FML_DLOG(INFO) << "node.textSelectionExtent=" << node.textSelectionExtent;
+ FML_DLOG(INFO) << "node.textSelectionBase=" << node.textSelectionBase;
+ FML_DLOG(INFO) << "node.platformViewId=" << node.platformViewId;
+ FML_DLOG(INFO) << "node.scrollChildren=" << node.scrollChildren;
+ FML_DLOG(INFO) << "node.scrollIndex=" << node.scrollIndex;
+ FML_DLOG(INFO) << "node.scrollPosition=" << node.scrollPosition;
+ FML_DLOG(INFO) << "node.scrollIndex=" << node.scrollIndex;
+ FML_DLOG(INFO) << "node.scrollPosition=" << node.scrollPosition;
+ FML_DLOG(INFO) << "node.scrollExtentMax=" << node.scrollExtentMax;
+ FML_DLOG(INFO) << "node.scrollExtentMin=" << node.scrollExtentMin;
+ FML_DLOG(INFO) << "node.elevation=" << node.elevation;
+ FML_DLOG(INFO) << "node.thickness=" << node.thickness;
+ FML_DLOG(INFO) << "node.textDirection=" << node.textDirection;
+ FML_DLOG(INFO) << "node.childrenInTraversalOrder.size()="
+ << node.childrenInTraversalOrder.size();
+ for (uint32_t i = 0; i < node.childrenInTraversalOrder.size(); i++) {
+ FML_DLOG(INFO) << "node.childrenInTraversalOrder[" << i
+ << "]=" << node.childrenInTraversalOrder[i];
+ }
+ FML_DLOG(INFO) << "node.childrenInHitTestOrder.size()="
+ << node.childrenInHitTestOrder.size();
+ for (uint32_t i = 0; i < node.childrenInHitTestOrder.size(); i++) {
+ FML_DLOG(INFO) << "node.childrenInHitTestOrder[" << i
+ << "]=" << node.childrenInHitTestOrder[i];
+ }
+ FML_DLOG(INFO) << "node.customAccessibilityActions.size()="
+ << node.customAccessibilityActions.size();
+ for (uint32_t i = 0; i < node.customAccessibilityActions.size(); i++) {
+ FML_DLOG(INFO) << "node.customAccessibilityActions[" << i
+ << "]=" << node.customAccessibilityActions[i];
+ }
+ FML_DLOG(INFO) << "------------------SemanticsNode-----------------";
+}
+
+void OhosAccessibilityBridge::GetSemanticsFlagsDebugInfo(
+ flutter::SemanticsNode node)
+{
+ FML_DLOG(INFO) << "----------------SemanticsFlags-------------------------";
+ FML_DLOG(INFO) << "node.id=" << node.id;
+ FML_DLOG(INFO) << "node.label=" << node.label;
+ FML_DLOG(INFO) << "kHasCheckedState: "
+ << node.HasFlag(FLAGS_::kHasCheckedState);
+ FML_DLOG(INFO) << "kIsChecked:" << node.HasFlag(FLAGS_::kIsChecked);
+ FML_DLOG(INFO) << "kIsSelected:" << node.HasFlag(FLAGS_::kIsSelected);
+ FML_DLOG(INFO) << "kIsButton:" << node.HasFlag(FLAGS_::kIsButton);
+ FML_DLOG(INFO) << "kIsTextField:" << node.HasFlag(FLAGS_::kIsTextField);
+ FML_DLOG(INFO) << "kIsFocused:" << node.HasFlag(FLAGS_::kIsFocused);
+ FML_DLOG(INFO) << "kHasEnabledState:"
+ << node.HasFlag(FLAGS_::kHasEnabledState);
+ FML_DLOG(INFO) << "kIsEnabled:" << node.HasFlag(FLAGS_::kIsEnabled);
+ FML_DLOG(INFO) << "kIsInMutuallyExclusiveGroup:"
+ << node.HasFlag(FLAGS_::kIsInMutuallyExclusiveGroup);
+ FML_DLOG(INFO) << "kIsHeader:" << node.HasFlag(FLAGS_::kIsHeader);
+ FML_DLOG(INFO) << "kIsObscured:" << node.HasFlag(FLAGS_::kIsObscured);
+ FML_DLOG(INFO) << "kScopesRoute:" << node.HasFlag(FLAGS_::kScopesRoute);
+ FML_DLOG(INFO) << "kNamesRoute:" << node.HasFlag(FLAGS_::kNamesRoute);
+ FML_DLOG(INFO) << "kIsHidden:" << node.HasFlag(FLAGS_::kIsHidden);
+ FML_DLOG(INFO) << "kIsImage:" << node.HasFlag(FLAGS_::kIsImage);
+ FML_DLOG(INFO) << "kIsLiveRegion:" << node.HasFlag(FLAGS_::kIsLiveRegion);
+ FML_DLOG(INFO) << "kHasToggledState:"
+ << node.HasFlag(FLAGS_::kHasToggledState);
+ FML_DLOG(INFO) << "kIsToggled:" << node.HasFlag(FLAGS_::kIsToggled);
+ FML_DLOG(INFO) << "kHasImplicitScrolling:"
+ << node.HasFlag(FLAGS_::kHasImplicitScrolling);
+ FML_DLOG(INFO) << "kIsMultiline:" << node.HasFlag(FLAGS_::kIsMultiline);
+ FML_DLOG(INFO) << "kIsReadOnly:" << node.HasFlag(FLAGS_::kIsReadOnly);
+ FML_DLOG(INFO) << "kIsFocusable:" << node.HasFlag(FLAGS_::kIsFocusable);
+ FML_DLOG(INFO) << "kIsLink:" << node.HasFlag(FLAGS_::kIsLink);
+ FML_DLOG(INFO) << "kIsSlider:" << node.HasFlag(FLAGS_::kIsSlider);
+ FML_DLOG(INFO) << "kIsKeyboardKey:" << node.HasFlag(FLAGS_::kIsKeyboardKey);
+ FML_DLOG(INFO) << "kIsCheckStateMixed:"
+ << node.HasFlag(FLAGS_::kIsCheckStateMixed);
+ FML_DLOG(INFO) << "----------------SemanticsFlags--------------------";
+ }
+
+ void OhosAccessibilityBridge::GetCustomActionDebugInfo(
+ flutter::CustomAccessibilityAction customAccessibilityAction)
+ {
+ FML_DLOG(INFO) << "--------------CustomAccessibilityAction------------";
+ FML_DLOG(INFO) << "customAccessibilityAction.id="
+ << customAccessibilityAction.id;
+ FML_DLOG(INFO) << "customAccessibilityAction.overrideId="
+ << customAccessibilityAction.overrideId;
+ FML_DLOG(INFO) << "customAccessibilityAction.label="
+ << customAccessibilityAction.label;
+ FML_DLOG(INFO) << "customAccessibilityAction.hint="
+ << customAccessibilityAction.hint;
+ FML_DLOG(INFO) << "------------CustomAccessibilityAction--------------";
+}
+} // namespace flutter
diff --git a/shell/platform/ohos/accessibility/ohos_accessibility_bridge.h b/shell/platform/ohos/accessibility/ohos_accessibility_bridge.h
new file mode 100644
index 0000000000000000000000000000000000000000..5f03fdc839f5ec12e06b19a5cadd4e57f21eeb61
--- /dev/null
+++ b/shell/platform/ohos/accessibility/ohos_accessibility_bridge.h
@@ -0,0 +1,342 @@
+/*
+ * Copyright (C) 2024 Huawei Device Co., Ltd.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef OHOS_ACCESSIBILITY_BRIDGE_H
+#define OHOS_ACCESSIBILITY_BRIDGE_H
+#include
+#include
+#include