* Copyright (c) 2026 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 <ani.h>
#include <array>
#include <cstdint>
#include <memory>
#include <optional>
#include <string>
#include <unordered_map>
#include <vector>
#include "base/log/log_wrapper.h"
#include "core/common/ace_engine.h"
#include "core/common/container.h"
#include "core/common/container_scope.h"
#include "core/common/event_manager.h"
#include "core/components_ng/manager/smart_gesture/smart_gesture_manager.h"
#include "core/pipeline/base/element_register.h"
#include "core/pipeline_ng/pipeline_context.h"
#include "frameworks/base/error/error_code.h"
namespace OHOS::Ace::Ani {
using OHOS::Ace::NG::SmartGestureManager;
using OHOS::Ace::NG::SmartGestureProposal;
namespace {
constexpr char SMART_GESTURE_NAMESPACE[] = "@ohos.arkui.smartGestureController.smartGestureController";
constexpr char RAW_PROPOSAL_INNER_CLASS[] =
"@ohos.arkui.smartGestureController.smartGestureController.SmartGestureProposalRawInner";
constexpr char SMART_GESTURE_ACTION_ENUM[] = "arkui.component.enums.SmartGestureAction";
constexpr char OPERATE_INTENTION_ENUM[] = "arkui.component.enums.OperateIntention";
constexpr char STS_INT_CLASS[] = "std.core.Int";
constexpr char STS_DOUBLE_CLASS[] = "std.core.Double";
constexpr char ACTION_KEY[] = "action";
constexpr char OPERATE_INTENTION_KEY[] = "operateIntention";
constexpr char NODE_ID_KEY[] = "nodeId";
constexpr char PAGE_COUNT_KEY[] = "pageCount";
constexpr char DISTANCE_KEY[] = "distance";
constexpr char IS_CONSUMED_KEY[] = "isConsumed";
constexpr char SELECTED_PROPOSAL_KEY[] = "selectedProposal";
constexpr char UI_CONTEXT_NOT_FOUND_MESSAGE[] = "UI execution context not found";
constexpr char MONITOR_STATE_CREATE_FAILED_MESSAGE[] = "Failed to create smart gesture monitor state.";
constexpr char MONITOR_CALLBACK_EXCEPTION_MESSAGE[] = "Smart gesture monitor callback threw an exception.";
constexpr char INVALID_MONITOR_RESULT_MESSAGE[] =
"Smart gesture monitor must return a valid GestureHandlingResolution.";
constexpr char INVALID_SELECTED_PROPOSAL_MESSAGE[] = "Smart gesture monitor returned an invalid selectedProposal.";
enum class JsSmartGestureAction : int32_t {
NONE = 0,
PAGE_FORWARD = 1,
SCROLL_FORWARD = 2,
SELECT = 3,
CLICK = 4,
BACK_PRESS = 5,
};
enum class JsOperateIntention : int32_t {
TAP = 0,
SLIDE_FORWARD = 1,
BACK_PRESS = 2,
};
enum class MonitorResolutionParseResult : uint32_t {
SUCCESS = 0,
INVALID_RESOLUTION,
INVALID_SELECTED_PROPOSAL,
};
OHOS::Ace::NG::SmartGestureHandlingResolution CreateRejectedResolution()
{
OHOS::Ace::NG::SmartGestureHandlingResolution resolution;
resolution.isConsumed = false;
return resolution;
}
OHOS::Ace::NG::SmartGestureHandlingResolution CreateAcceptedResolution()
{
OHOS::Ace::NG::SmartGestureHandlingResolution resolution;
resolution.isConsumed = true;
return resolution;
}
std::string AniStringToStdString(ani_env* env, ani_string aniStr)
{
CHECK_NULL_RETURN(env && aniStr, "");
ani_size strSize = 0;
if (ANI_OK != env->String_GetUTF8Size(aniStr, &strSize)) {
return "";
}
std::vector<char> buffer(strSize + 1);
ani_size written = 0;
if (ANI_OK != env->String_GetUTF8(aniStr, buffer.data(), strSize + 1, &written)) {
return "";
}
buffer[written] = '\0';
return std::string(buffer.data());
}
ani_object WrapStsError(ani_env* env, const std::string& msg)
{
CHECK_NULL_RETURN(env, nullptr);
ani_class cls;
ani_method ctor;
ani_status status = ANI_OK;
if ((status = env->FindClass("escompat.Error", &cls)) != ANI_OK) {
TAG_LOGE(AceLogTag::ACE_GESTURE, "FindClass escompat.Error failed, status=%{public}d", status);
return nullptr;
}
if ((status = env->Class_FindMethod(cls, "<ctor>", "C{std.core.String}C{escompat.ErrorOptions}:", &ctor)) !=
ANI_OK) {
TAG_LOGE(AceLogTag::ACE_GESTURE, "FindMethod Error ctor failed, status=%{public}d", status);
return nullptr;
}
ani_string aniMsg = nullptr;
if ((status = env->String_NewUTF8(msg.c_str(), msg.size(), &aniMsg)) != ANI_OK) {
TAG_LOGE(AceLogTag::ACE_GESTURE, "String_NewUTF8 failed, status=%{public}d", status);
return nullptr;
}
ani_ref undefRef = nullptr;
if ((status = env->GetUndefined(&undefRef)) != ANI_OK) {
TAG_LOGE(AceLogTag::ACE_GESTURE, "GetUndefined failed, status=%{public}d", status);
return nullptr;
}
ani_object errObj = nullptr;
if ((status = env->Object_New(cls, ctor, &errObj, aniMsg, undefRef)) != ANI_OK) {
TAG_LOGE(AceLogTag::ACE_GESTURE, "Object_New Error failed, status=%{public}d", status);
return nullptr;
}
return errObj;
}
ani_ref CreateStsError(ani_env* env, ani_int code, const std::string& msg)
{
CHECK_NULL_RETURN(env, nullptr);
ani_class cls;
ani_method ctor;
ani_status status = ANI_OK;
if ((status = env->FindClass("@ohos.base.BusinessError", &cls)) != ANI_OK) {
TAG_LOGE(AceLogTag::ACE_GESTURE, "FindClass BusinessError failed, status=%{public}d", status);
return nullptr;
}
if ((status = env->Class_FindMethod(cls, "<ctor>", "iC{escompat.Error}:", &ctor)) != ANI_OK) {
TAG_LOGE(AceLogTag::ACE_GESTURE, "FindMethod BusinessError ctor failed, status=%{public}d", status);
return nullptr;
}
ani_object error = WrapStsError(env, msg);
CHECK_NULL_RETURN(error, nullptr);
ani_object businessError = nullptr;
if ((status = env->Object_New(cls, ctor, &businessError, code, error)) != ANI_OK) {
TAG_LOGE(AceLogTag::ACE_GESTURE, "Object_New BusinessError failed, status=%{public}d", status);
return nullptr;
}
return reinterpret_cast<ani_ref>(businessError);
}
void AniThrow(ani_env* env, const std::string& msg, int32_t code)
{
CHECK_NULL_VOID(env);
auto errObj = CreateStsError(env, static_cast<ani_int>(code), msg);
CHECK_NULL_VOID(errObj);
if (ANI_OK != env->ThrowError(static_cast<ani_error>(errObj))) {
TAG_LOGE(AceLogTag::ACE_GESTURE, "ThrowError failed");
}
}
bool IsUndefinedOrNull(ani_env* env, ani_ref value)
{
CHECK_NULL_RETURN(env, true);
CHECK_NULL_RETURN(value, true);
ani_boolean isUndefined = ANI_FALSE;
if (ANI_OK == env->Reference_IsUndefined(value, &isUndefined) && isUndefined == ANI_TRUE) {
return true;
}
ani_boolean isNull = ANI_FALSE;
if (ANI_OK == env->Reference_IsNull(value, &isNull) && isNull == ANI_TRUE) {
return true;
}
return false;
}
bool IsStrictlyEqual(ani_env* env, ani_ref left, ani_ref right)
{
CHECK_NULL_RETURN(env && left && right, false);
ani_boolean isEquals = ANI_FALSE;
if (ANI_OK != env->Reference_StrictEquals(left, right, &isEquals)) {
return false;
}
return isEquals == ANI_TRUE;
}
void ResetUnhandledErrorIfNeeded(ani_env* env)
{
CHECK_NULL_VOID(env);
ani_boolean hasUnhandledError = ANI_FALSE;
if (ANI_OK != env->ExistUnhandledError(&hasUnhandledError) || hasUnhandledError != ANI_TRUE) {
return;
}
env->ResetError();
}
void DescribeUnhandledErrorIfNeeded(ani_env* env)
{
CHECK_NULL_VOID(env);
ani_boolean hasUnhandledError = ANI_FALSE;
if (ANI_OK != env->ExistUnhandledError(&hasUnhandledError) || hasUnhandledError != ANI_TRUE) {
return;
}
env->DescribeError();
}
bool GetPropertyRef(ani_env* env, ani_object object, const char* key, ani_ref& outRef)
{
CHECK_NULL_RETURN(env && object && key, false);
return env->Object_GetPropertyByName_Ref(object, key, &outRef) == ANI_OK;
}
ani_object CreateIntObject(ani_env* env, int32_t value)
{
CHECK_NULL_RETURN(env, nullptr);
ani_class cls = nullptr;
ani_method ctor = nullptr;
ani_object result = nullptr;
if (ANI_OK != env->FindClass(STS_INT_CLASS, &cls)) {
return nullptr;
}
if (ANI_OK != env->Class_FindMethod(cls, "<ctor>", "i:", &ctor)) {
return nullptr;
}
if (ANI_OK != env->Object_New(cls, ctor, &result, static_cast<ani_int>(value))) {
return nullptr;
}
return result;
}
ani_object CreateDoubleObject(ani_env* env, double value)
{
CHECK_NULL_RETURN(env, nullptr);
ani_class cls = nullptr;
ani_method ctor = nullptr;
ani_object result = nullptr;
if (ANI_OK != env->FindClass(STS_DOUBLE_CLASS, &cls)) {
return nullptr;
}
if (ANI_OK != env->Class_FindMethod(cls, "<ctor>", "d:", &ctor)) {
return nullptr;
}
if (ANI_OK != env->Object_New(cls, ctor, &result, static_cast<ani_double>(value))) {
return nullptr;
}
return result;
}
bool SetIntPropertyByRef(ani_env* env, ani_object object, const char* key, int32_t value)
{
CHECK_NULL_RETURN(env && object && key, false);
auto boxedValue = CreateIntObject(env, value);
CHECK_NULL_RETURN(boxedValue, false);
return env->Object_SetPropertyByName_Ref(object, key, static_cast<ani_ref>(boxedValue)) == ANI_OK;
}
bool SetDoublePropertyByRef(ani_env* env, ani_object object, const char* key, double value)
{
CHECK_NULL_RETURN(env && object && key, false);
auto boxedValue = CreateDoubleObject(env, value);
CHECK_NULL_RETURN(boxedValue, false);
return env->Object_SetPropertyByName_Ref(object, key, static_cast<ani_ref>(boxedValue)) == ANI_OK;
}
bool IsClassObject(ani_env* env, ani_object object, const char* classDescriptor)
{
CHECK_NULL_RETURN(env && object && classDescriptor, false);
ani_class objectClass = nullptr;
if (ANI_OK != env->FindClass(classDescriptor, &objectClass)) {
return false;
}
ani_boolean isInstance = ANI_FALSE;
if (ANI_OK != env->Object_InstanceOf(object, objectClass, &isInstance)) {
return false;
}
return isInstance == ANI_TRUE;
}
bool SetEnumPropertyByIndex(ani_env* env, ani_object object, const char* key, const char* enumName, ani_size index)
{
CHECK_NULL_RETURN(env && object && key && enumName, false);
ani_enum enumType = nullptr;
if (ANI_OK != env->FindEnum(enumName, &enumType)) {
return false;
}
ani_enum_item enumItem = nullptr;
if (ANI_OK != env->Enum_GetEnumItemByIndex(enumType, index, &enumItem)) {
return false;
}
if (ANI_OK != env->Object_SetPropertyByName_Ref(object, key, enumItem)) {
return false;
}
return true;
}
std::optional<int32_t> GetInt32FromRef(ani_env* env, ani_ref ref)
{
CHECK_NULL_RETURN(env && ref, std::nullopt);
if (IsUndefinedOrNull(env, ref)) {
return std::nullopt;
}
auto object = static_cast<ani_object>(ref);
if (IsClassObject(env, object, STS_INT_CLASS)) {
ani_int value = 0;
if (ANI_OK != env->Object_CallMethodByName_Int(object, "toInt", ":i", &value)) {
return std::nullopt;
}
return static_cast<int32_t>(value);
}
ani_int value = 0;
if (ANI_OK == env->EnumItem_GetValue_Int(static_cast<ani_enum_item>(ref), &value)) {
return static_cast<int32_t>(value);
}
return std::nullopt;
}
std::optional<double> GetDoubleFromRef(ani_env* env, ani_ref ref)
{
CHECK_NULL_RETURN(env && ref, std::nullopt);
if (IsUndefinedOrNull(env, ref)) {
return std::nullopt;
}
auto object = static_cast<ani_object>(ref);
if (!IsClassObject(env, object, STS_DOUBLE_CLASS)) {
return std::nullopt;
}
ani_double value = 0.0;
if (ANI_OK != env->Object_CallMethodByName_Double(object, "toDouble", ":d", &value)) {
return std::nullopt;
}
return static_cast<double>(value);
}
std::optional<int32_t> GetOptionalInt32PropertyByName(ani_env* env, ani_object object, const char* key)
{
CHECK_NULL_RETURN(env && object && key, std::nullopt);
ani_int value = 0;
if (ANI_OK == env->Object_GetPropertyByName_Int(object, key, &value)) {
return static_cast<int32_t>(value);
}
ani_ref ref = nullptr;
if (!GetPropertyRef(env, object, key, ref) || IsUndefinedOrNull(env, ref)) {
return std::nullopt;
}
return GetInt32FromRef(env, ref);
}
std::optional<double> GetOptionalDoublePropertyByName(ani_env* env, ani_object object, const char* key)
{
CHECK_NULL_RETURN(env && object && key, std::nullopt);
ani_double value = 0.0;
if (ANI_OK == env->Object_GetPropertyByName_Double(object, key, &value)) {
return static_cast<double>(value);
}
ani_ref ref = nullptr;
if (!GetPropertyRef(env, object, key, ref) || IsUndefinedOrNull(env, ref)) {
return std::nullopt;
}
return GetDoubleFromRef(env, ref);
}
std::optional<bool> GetOptionalBoolPropertyByName(ani_env* env, ani_object object, const char* key)
{
CHECK_NULL_RETURN(env && object && key, std::nullopt);
ani_boolean value = ANI_FALSE;
if (ANI_OK != env->Object_GetPropertyByName_Boolean(object, key, &value)) {
return std::nullopt;
}
return value == ANI_TRUE;
}
RefPtr<SmartGestureManager> GetManagerByInstanceId(int32_t instanceId)
{
auto container = AceEngine::Get().GetContainer(instanceId);
CHECK_NULL_RETURN(container, nullptr);
auto pipeline = AceType::DynamicCast<OHOS::Ace::NG::PipelineContext>(container->GetPipelineContext());
CHECK_NULL_RETURN(pipeline, nullptr);
auto eventManager = pipeline->GetEventManager();
CHECK_NULL_RETURN(eventManager, nullptr);
return eventManager->GetOrCreateSmartGestureManager();
}
bool TryGetCurrentInstanceId(int32_t& instanceId)
{
instanceId = Container::CurrentIdSafely();
return instanceId >= 0;
}
RefPtr<SmartGestureManager> GetRequiredManager(ani_env* env, int32_t instanceId)
{
auto manager = GetManagerByInstanceId(instanceId);
if (!manager) {
AniThrow(env, UI_CONTEXT_NOT_FOUND_MESSAGE, OHOS::Ace::ERROR_CODE_INTERNAL_ERROR);
}
return manager;
}
class SmartGestureCodec final {
public:
static ani_object EncodeRawProposal(ani_env* env, int32_t instanceId, const SmartGestureProposal& proposal)
{
CHECK_NULL_RETURN(env, nullptr);
auto object = CreateRawProposalObject(env);
CHECK_NULL_RETURN(object, nullptr);
CHECK_NULL_RETURN(SetEnumPropertyByIndex(env, object, ACTION_KEY, SMART_GESTURE_ACTION_ENUM,
static_cast<ani_size>(ToJsAction(proposal))), nullptr);
CHECK_NULL_RETURN(SetEnumPropertyByIndex(env, object, OPERATE_INTENTION_KEY, OPERATE_INTENTION_ENUM,
static_cast<ani_size>(ToJsOperateIntention(proposal.operateIntention))), nullptr);
auto targetNode = proposal.GetTargetNode();
if (targetNode) {
SetIntPropertyByRef(env, object, NODE_ID_KEY, targetNode->GetId());
}
if (proposal.scrollingConfig.has_value()) {
if (proposal.scrollingConfig->count.has_value()) {
SetIntPropertyByRef(env, object, PAGE_COUNT_KEY, proposal.scrollingConfig->count.value());
}
if (proposal.scrollingConfig->distance.has_value()) {
SetDoublePropertyByRef(env, object, DISTANCE_KEY, proposal.scrollingConfig->distance.value());
}
}
return object;
}
static bool DecodeRawProposal(ani_env* env, ani_object value, std::optional<SmartGestureProposal>& outProposal)
{
CHECK_NULL_RETURN(env && value, false);
auto action = ParseJsAction(env, value);
CHECK_NULL_RETURN(action.has_value(), false);
auto rawIntention = GetOptionalInt32PropertyByName(env, value, OPERATE_INTENTION_KEY);
auto intention = ToNativeOperateIntention(rawIntention, *action);
switch (*action) {
case JsSmartGestureAction::NONE:
outProposal = SmartGestureProposal(OHOS::Ace::NG::SmartGestureProposalType::NONE_ACTION, intention);
return true;
case JsSmartGestureAction::BACK_PRESS:
outProposal = SmartGestureProposal(OHOS::Ace::NG::SmartGestureProposalType::BACK_PRESS, intention);
return true;
case JsSmartGestureAction::CLICK:
case JsSmartGestureAction::SELECT:
case JsSmartGestureAction::PAGE_FORWARD:
case JsSmartGestureAction::SCROLL_FORWARD:
break;
}
auto frameNode = ParseFrameNode(env, value);
CHECK_NULL_RETURN(frameNode, false);
if (*action == JsSmartGestureAction::CLICK) {
outProposal = SmartGestureProposal(OHOS::Ace::NG::SmartGestureProposalType::CLICK, intention, frameNode);
return true;
}
if (*action == JsSmartGestureAction::SELECT) {
outProposal = SmartGestureProposal(OHOS::Ace::NG::SmartGestureProposalType::SELECT, intention, frameNode);
return true;
}
OHOS::Ace::NG::ScrollingConfig config;
config.count = GetOptionalInt32PropertyByName(env, value, PAGE_COUNT_KEY);
config.distance = GetOptionalDoublePropertyByName(env, value, DISTANCE_KEY);
CHECK_NULL_RETURN(config.HasValue(), false);
outProposal =
SmartGestureProposal(OHOS::Ace::NG::SmartGestureProposalType::SCROLL, intention, frameNode, config);
return true;
}
static MonitorResolutionParseResult DecodeRawResolution(
ani_env* env, ani_object value, OHOS::Ace::NG::SmartGestureHandlingResolution& resolution)
{
if (!env || !value) {
return MonitorResolutionParseResult::INVALID_RESOLUTION;
}
auto consumed = GetOptionalBoolPropertyByName(env, value, IS_CONSUMED_KEY);
if (!consumed.has_value()) {
return MonitorResolutionParseResult::INVALID_RESOLUTION;
}
resolution.isConsumed = consumed.value();
ani_ref selectedProposalRef = nullptr;
if (!GetPropertyRef(env, value, SELECTED_PROPOSAL_KEY, selectedProposalRef)) {
return MonitorResolutionParseResult::SUCCESS;
}
if (IsUndefinedOrNull(env, selectedProposalRef)) {
return MonitorResolutionParseResult::SUCCESS;
}
std::optional<SmartGestureProposal> parsedProposal;
if (!DecodeRawProposal(env, static_cast<ani_object>(selectedProposalRef), parsedProposal)) {
return MonitorResolutionParseResult::INVALID_SELECTED_PROPOSAL;
}
resolution.hasSelectedProposal = true;
resolution.selectedProposal = parsedProposal;
return MonitorResolutionParseResult::SUCCESS;
}
private:
static ani_object CreateRawProposalObject(ani_env* env)
{
CHECK_NULL_RETURN(env, nullptr);
ani_class cls;
ani_method ctor;
ani_object object = nullptr;
if (ANI_OK != env->FindClass(RAW_PROPOSAL_INNER_CLASS, &cls)) {
return nullptr;
}
if (ANI_OK != env->Class_FindMethod(cls, "<ctor>", ":", &ctor)) {
return nullptr;
}
if (ANI_OK != env->Object_New(cls, ctor, &object)) {
return nullptr;
}
return object;
}
static std::optional<JsSmartGestureAction> ParseJsAction(ani_env* env, ani_object object)
{
auto actionValue = GetOptionalInt32PropertyByName(env, object, ACTION_KEY);
CHECK_NULL_RETURN(actionValue.has_value(), std::nullopt);
switch (static_cast<JsSmartGestureAction>(actionValue.value())) {
case JsSmartGestureAction::NONE:
case JsSmartGestureAction::PAGE_FORWARD:
case JsSmartGestureAction::SCROLL_FORWARD:
case JsSmartGestureAction::SELECT:
case JsSmartGestureAction::CLICK:
case JsSmartGestureAction::BACK_PRESS:
return static_cast<JsSmartGestureAction>(actionValue.value());
default:
return std::nullopt;
}
}
static OHOS::Ace::RefPtr<OHOS::Ace::NG::FrameNode> ParseFrameNode(ani_env* env, ani_object object)
{
auto nodeId = GetOptionalInt32PropertyByName(env, object, NODE_ID_KEY);
CHECK_NULL_RETURN(nodeId.has_value(), nullptr);
auto node = OHOS::Ace::ElementRegister::GetInstance()->GetNodeById(static_cast<int32_t>(nodeId.value()));
CHECK_NULL_RETURN(node, nullptr);
return AceType::DynamicCast<OHOS::Ace::NG::FrameNode>(node);
}
static JsSmartGestureAction ToJsAction(const SmartGestureProposal& proposal)
{
switch (proposal.type) {
case OHOS::Ace::NG::SmartGestureProposalType::NONE_ACTION:
return JsSmartGestureAction::NONE;
case OHOS::Ace::NG::SmartGestureProposalType::CLICK:
return JsSmartGestureAction::CLICK;
case OHOS::Ace::NG::SmartGestureProposalType::SELECT:
return JsSmartGestureAction::SELECT;
case OHOS::Ace::NG::SmartGestureProposalType::BACK_PRESS:
return JsSmartGestureAction::BACK_PRESS;
case OHOS::Ace::NG::SmartGestureProposalType::SCROLL:
if (proposal.scrollingConfig.has_value() && proposal.scrollingConfig->count.has_value() &&
!proposal.scrollingConfig->distance.has_value()) {
return JsSmartGestureAction::PAGE_FORWARD;
}
return JsSmartGestureAction::SCROLL_FORWARD;
}
return JsSmartGestureAction::NONE;
}
static JsOperateIntention ToJsOperateIntention(OHOS::Ace::NG::SmartGestureOperateIntention intention)
{
switch (intention) {
case OHOS::Ace::NG::SmartGestureOperateIntention::TAP:
return JsOperateIntention::TAP;
case OHOS::Ace::NG::SmartGestureOperateIntention::SLIDE_FORWARD:
return JsOperateIntention::SLIDE_FORWARD;
case OHOS::Ace::NG::SmartGestureOperateIntention::BACK_PRESS:
return JsOperateIntention::BACK_PRESS;
}
return JsOperateIntention::TAP;
}
static OHOS::Ace::NG::SmartGestureOperateIntention ToNativeOperateIntention(
std::optional<int32_t> intention, JsSmartGestureAction action)
{
if (intention.has_value()) {
switch (static_cast<JsOperateIntention>(intention.value())) {
case JsOperateIntention::TAP:
return OHOS::Ace::NG::SmartGestureOperateIntention::TAP;
case JsOperateIntention::SLIDE_FORWARD:
return OHOS::Ace::NG::SmartGestureOperateIntention::SLIDE_FORWARD;
case JsOperateIntention::BACK_PRESS:
return OHOS::Ace::NG::SmartGestureOperateIntention::BACK_PRESS;
}
}
switch (action) {
case JsSmartGestureAction::BACK_PRESS:
return OHOS::Ace::NG::SmartGestureOperateIntention::BACK_PRESS;
case JsSmartGestureAction::PAGE_FORWARD:
case JsSmartGestureAction::SCROLL_FORWARD:
return OHOS::Ace::NG::SmartGestureOperateIntention::SLIDE_FORWARD;
case JsSmartGestureAction::NONE:
case JsSmartGestureAction::SELECT:
case JsSmartGestureAction::CLICK:
return OHOS::Ace::NG::SmartGestureOperateIntention::TAP;
}
return OHOS::Ace::NG::SmartGestureOperateIntention::TAP;
}
};
class SmartGestureMonitorState final {
public:
SmartGestureMonitorState(ani_env* env, int32_t instanceId) : env_(env), instanceId_(instanceId) {}
static std::shared_ptr<SmartGestureMonitorState> Create(ani_env* env, int32_t instanceId, ani_fn_object callback)
{
auto state = std::make_shared<SmartGestureMonitorState>(env, instanceId);
CHECK_NULL_RETURN(state && state->SetMonitorRef(callback), nullptr);
return state;
}
~SmartGestureMonitorState()
{
ResetMonitorRef();
}
OHOS::Ace::NG::SmartGestureHandlingResolution Invoke(const SmartGestureProposal& defaultProposal) const
{
auto resolution = CreateRejectedResolution();
CHECK_NULL_RETURN(env_, resolution);
ContainerScope scope(instanceId_);
ani_size nrRefs = 16;
if (ANI_OK != env_->CreateLocalScope(nrRefs) || !nrRefs) {
TAG_LOGW(AceLogTag::ACE_GESTURE, "CreateLocalScope failed when invoking smart gesture monitor");
return resolution;
}
auto callback = GetCallback();
if (!callback) {
env_->DestroyLocalScope();
return resolution;
}
ani_object proposalValue = SmartGestureCodec::EncodeRawProposal(env_, instanceId_, defaultProposal);
if (!proposalValue) {
env_->DestroyLocalScope();
return resolution;
}
std::array<ani_ref, 1> args = { proposalValue };
ani_ref fnReturnVal = nullptr;
if (ANI_OK != env_->FunctionalObject_Call(callback, args.size(), args.data(), &fnReturnVal)) {
DescribeUnhandledErrorIfNeeded(env_);
ResetUnhandledErrorIfNeeded(env_);
TAG_LOGW(AceLogTag::ACE_GESTURE, "%{public}s", MONITOR_CALLBACK_EXCEPTION_MESSAGE);
env_->DestroyLocalScope();
return resolution;
}
if (IsUndefinedOrNull(env_, fnReturnVal)) {
TAG_LOGW(AceLogTag::ACE_GESTURE, "%{public}s", INVALID_MONITOR_RESULT_MESSAGE);
env_->DestroyLocalScope();
return resolution;
}
OHOS::Ace::NG::SmartGestureHandlingResolution decodedResolution;
auto parseResult =
SmartGestureCodec::DecodeRawResolution(env_, static_cast<ani_object>(fnReturnVal), decodedResolution);
if (parseResult != MonitorResolutionParseResult::SUCCESS) {
auto detail = parseResult == MonitorResolutionParseResult::INVALID_SELECTED_PROPOSAL
? INVALID_SELECTED_PROPOSAL_MESSAGE
: INVALID_MONITOR_RESULT_MESSAGE;
TAG_LOGW(AceLogTag::ACE_GESTURE, "%{public}s", detail);
env_->DestroyLocalScope();
return resolution;
}
env_->DestroyLocalScope();
return decodedResolution;
}
bool MatchesCallback(ani_env* env, ani_fn_object callback) const
{
CHECK_NULL_RETURN(env_ != nullptr, false);
CHECK_NULL_RETURN(env == env_, false);
auto currentCallback = reinterpret_cast<ani_ref>(GetCallback());
CHECK_NULL_RETURN(currentCallback != nullptr, false);
return IsStrictlyEqual(env_, currentCallback, reinterpret_cast<ani_ref>(callback));
}
ani_env* GetEnv() const
{
return env_;
}
void Detach()
{
ResetMonitorRef();
env_ = nullptr;
instanceId_ = -1;
}
private:
bool SetMonitorRef(ani_fn_object callback)
{
CHECK_NULL_RETURN(env_ != nullptr && callback != nullptr, false);
return env_->GlobalReference_Create(reinterpret_cast<ani_ref>(callback), &monitorRef_) == ANI_OK;
}
void ResetMonitorRef()
{
if (env_ != nullptr && monitorRef_ != nullptr) {
env_->GlobalReference_Delete(monitorRef_);
}
monitorRef_ = nullptr;
}
ani_fn_object GetCallback() const
{
return reinterpret_cast<ani_fn_object>(monitorRef_);
}
ani_env* env_ = nullptr;
int32_t instanceId_ = -1;
ani_ref monitorRef_ = nullptr;
};
class SmartGestureMonitorRegistry final {
public:
static bool Register(ani_env* env, int32_t instanceId, ani_fn_object callback)
{
auto iter = states_.find(instanceId);
if (iter != states_.end()) {
for (const auto& state : iter->second) {
if (state && state->MatchesCallback(env, callback)) {
return true;
}
}
}
auto state = SmartGestureMonitorState::Create(env, instanceId, callback);
CHECK_NULL_RETURN(state, false);
states_[instanceId].emplace_back(std::move(state));
return true;
}
static bool Unregister(ani_env* env, int32_t instanceId, ani_fn_object callback)
{
std::shared_ptr<SmartGestureMonitorState> stateToDetach;
auto iter = states_.find(instanceId);
if (iter == states_.end() || iter->second.empty()) {
return false;
}
auto& stateStack = iter->second;
for (auto reverseIter = stateStack.rbegin(); reverseIter != stateStack.rend(); ++reverseIter) {
auto state = *reverseIter;
if (!state || !state->MatchesCallback(env, callback)) {
continue;
}
stateToDetach = state;
stateStack.erase(std::next(reverseIter).base());
break;
}
if (stateStack.empty()) {
states_.erase(iter);
}
if (stateToDetach) {
stateToDetach->Detach();
}
return stateToDetach != nullptr;
}
static bool Clear(int32_t instanceId)
{
std::vector<std::shared_ptr<SmartGestureMonitorState>> statesToDetach;
auto iter = states_.find(instanceId);
if (iter == states_.end() || iter->second.empty()) {
return false;
}
statesToDetach = std::move(iter->second);
states_.erase(iter);
for (const auto& state : statesToDetach) {
if (state) {
state->Detach();
}
}
return true;
}
static bool HasMonitor(int32_t instanceId)
{
auto iter = states_.find(instanceId);
return iter != states_.end() && !iter->second.empty();
}
static OHOS::Ace::NG::SmartGestureHandlingResolution Invoke(
int32_t instanceId, const SmartGestureProposal& defaultProposal)
{
auto iter = states_.find(instanceId);
if (iter == states_.end() || iter->second.empty()) {
return CreateAcceptedResolution();
}
auto states = iter->second;
for (auto stateIter = states.rbegin(); stateIter != states.rend(); ++stateIter) {
auto state = *stateIter;
CHECK_NULL_CONTINUE(state);
auto resolution = state->Invoke(defaultProposal);
if (!resolution.isConsumed) {
continue;
}
if (resolution.hasSelectedProposal) {
return resolution;
}
return resolution;
}
return CreateRejectedResolution();
}
private:
static std::unordered_map<int32_t, std::vector<std::shared_ptr<SmartGestureMonitorState>>> states_;
};
std::unordered_map<int32_t, std::vector<std::shared_ptr<SmartGestureMonitorState>>>
SmartGestureMonitorRegistry::states_;
void InstallRegistryBackedMonitor(const RefPtr<SmartGestureManager>& manager, int32_t instanceId)
{
CHECK_NULL_VOID(manager);
if (!SmartGestureMonitorRegistry::HasMonitor(instanceId)) {
manager->SetMonitor(SmartGestureManager::SmartGestureMonitor());
return;
}
manager->SetMonitor(
[instanceId](const SmartGestureProposal& defaultProposal) -> OHOS::Ace::NG::SmartGestureHandlingResolution {
return SmartGestureMonitorRegistry::Invoke(instanceId, defaultProposal);
});
}
void EnableSmartTapAndSlideGestures(ani_env* env, ani_boolean enabled)
{
int32_t instanceId = -1;
if (!TryGetCurrentInstanceId(instanceId)) {
return;
}
ContainerScope scope(instanceId);
auto manager = GetRequiredManager(env, instanceId);
CHECK_NULL_VOID(manager);
manager->SetSmartTapAndSlideGesturesEnabled(enabled == ANI_TRUE);
}
void RegisterMonitor(ani_env* env, ani_fn_object callback)
{
CHECK_NULL_VOID(env);
if (callback == nullptr) {
AniThrow(env, MONITOR_STATE_CREATE_FAILED_MESSAGE, OHOS::Ace::ERROR_CODE_PARAM_INVALID);
return;
}
int32_t instanceId = -1;
if (!TryGetCurrentInstanceId(instanceId)) {
return;
}
ContainerScope scope(instanceId);
auto manager = GetRequiredManager(env, instanceId);
CHECK_NULL_VOID(manager);
if (!SmartGestureMonitorRegistry::Register(env, instanceId, callback)) {
AniThrow(env, MONITOR_STATE_CREATE_FAILED_MESSAGE, OHOS::Ace::ERROR_CODE_INTERNAL_ERROR);
return;
}
InstallRegistryBackedMonitor(manager, instanceId);
}
void UnregisterMonitor(ani_env* env, ani_fn_object callback)
{
CHECK_NULL_VOID(env);
if (callback == nullptr) {
return;
}
int32_t instanceId = -1;
if (!TryGetCurrentInstanceId(instanceId)) {
return;
}
ContainerScope scope(instanceId);
auto manager = GetRequiredManager(env, instanceId);
CHECK_NULL_VOID(manager);
SmartGestureMonitorRegistry::Unregister(env, instanceId, callback);
InstallRegistryBackedMonitor(manager, instanceId);
}
void ClearMonitors(ani_env* env)
{
CHECK_NULL_VOID(env);
int32_t instanceId = -1;
if (!TryGetCurrentInstanceId(instanceId)) {
return;
}
ContainerScope scope(instanceId);
auto manager = GetRequiredManager(env, instanceId);
CHECK_NULL_VOID(manager);
SmartGestureMonitorRegistry::Clear(instanceId);
InstallRegistryBackedMonitor(manager, instanceId);
}
void RequestSelected(ani_env* env, ani_string id)
{
CHECK_NULL_VOID(env);
CHECK_NULL_VOID(id);
auto inspectorId = AniStringToStdString(env, id);
int32_t instanceId = -1;
if (!TryGetCurrentInstanceId(instanceId)) {
return;
}
ContainerScope scope(instanceId);
auto manager = GetRequiredManager(env, instanceId);
CHECK_NULL_VOID(manager);
manager->RequestSelected(inspectorId);
}
void ClearSelected(ani_env* env)
{
CHECK_NULL_VOID(env);
int32_t instanceId = -1;
if (!TryGetCurrentInstanceId(instanceId)) {
return;
}
ContainerScope scope(instanceId);
auto manager = GetRequiredManager(env, instanceId);
CHECK_NULL_VOID(manager);
manager->ClearSelected();
}
}
ani_status SmartGestureAniConstructor(ani_vm* vm, uint32_t* result)
{
if (vm == nullptr || result == nullptr) {
return ANI_ERROR;
}
ani_env* env = nullptr;
if (ANI_OK != vm->GetEnv(ANI_VERSION_1, &env)) {
return ANI_ERROR;
}
ani_namespace ns = nullptr;
if (ANI_OK != env->FindNamespace(SMART_GESTURE_NAMESPACE, &ns)) {
return ANI_ERROR;
}
std::array methods = {
ani_native_function {
"enableSmartTapAndSlideGestures", nullptr, reinterpret_cast<void*>(EnableSmartTapAndSlideGestures) },
ani_native_function { "registerMonitor", nullptr, reinterpret_cast<void*>(RegisterMonitor) },
ani_native_function { "unregisterMonitor", nullptr, reinterpret_cast<void*>(UnregisterMonitor) },
ani_native_function { "clearMonitors", nullptr, reinterpret_cast<void*>(ClearMonitors) },
ani_native_function { "requestSelected", nullptr, reinterpret_cast<void*>(RequestSelected) },
ani_native_function { "clearSelected", nullptr, reinterpret_cast<void*>(ClearSelected) },
};
if (ANI_OK != env->Namespace_BindNativeFunctions(ns, methods.data(), methods.size())) {
return ANI_ERROR;
}
*result = ANI_VERSION_1;
return ANI_OK;
}
}
ANI_EXPORT ani_status ANI_Constructor(ani_vm* vm, uint32_t* result)
{
return OHOS::Ace::Ani::SmartGestureAniConstructor(vm, result);
}