Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions flutter/shell/platform/tizen/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,7 @@ executable("flutter_tizen_unittests") {
"flutter_project_bundle_unittests.cc",
"flutter_tizen_engine_unittest.cc",
"flutter_tizen_texture_registrar_unittests.cc",
"flutter_tizen_view_unittests.cc",
]

ldflags = [ "-Wl,--unresolved-symbols=ignore-in-shared-libs" ]
Expand Down
18 changes: 15 additions & 3 deletions flutter/shell/platform/tizen/flutter_tizen.cc
Original file line number Diff line number Diff line change
Expand Up @@ -167,15 +167,27 @@ void FlutterDesktopEngineNotifyLowMemoryWarning(
}

void FlutterDesktopEngineNotifyAppIsInactive(FlutterDesktopEngineRef engine) {
EngineFromHandle(engine)->lifecycle_channel()->AppIsInactive();
flutter::FlutterTizenEngine* tizen_engine = EngineFromHandle(engine);
tizen_engine->lifecycle_channel()->AppIsInactive();
if (tizen_engine->view()) {
tizen_engine->view()->OnFocus(kUnfocused);
}
}

void FlutterDesktopEngineNotifyAppIsResumed(FlutterDesktopEngineRef engine) {
EngineFromHandle(engine)->lifecycle_channel()->AppIsResumed();
flutter::FlutterTizenEngine* tizen_engine = EngineFromHandle(engine);
tizen_engine->lifecycle_channel()->AppIsResumed();
if (tizen_engine->view()) {
tizen_engine->view()->OnFocus(kFocused);
Comment thread
JSUYA marked this conversation as resolved.
}
}

void FlutterDesktopEngineNotifyAppIsPaused(FlutterDesktopEngineRef engine) {
EngineFromHandle(engine)->lifecycle_channel()->AppIsPaused();
flutter::FlutterTizenEngine* tizen_engine = EngineFromHandle(engine);
tizen_engine->lifecycle_channel()->AppIsPaused();
if (tizen_engine->view()) {
tizen_engine->view()->OnFocus(kUnfocused);
}
Comment thread
JSUYA marked this conversation as resolved.
}

void FlutterDesktopEngineNotifyAppIsDetached(FlutterDesktopEngineRef engine) {
Expand Down
14 changes: 14 additions & 0 deletions flutter/shell/platform/tizen/flutter_tizen_engine.cc
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,13 @@ bool FlutterTizenEngine::RunEngine() {
auto* engine = static_cast<FlutterTizenEngine*>(user_data);
engine->OnUpdateSemantics(update);
};
args.view_focus_change_request_callback =
[](const FlutterViewFocusChangeRequest* request, void* user_data) {
auto* engine = static_cast<FlutterTizenEngine*>(user_data);
if (engine->view()) {
engine->view()->OnFocusChangeRequest(*request);
}
};

if (IsHeaded() && dynamic_cast<TizenRendererEgl*>(renderer_.get())) {
vsync_waiter_ = std::make_unique<TizenVsyncWaiter>(this);
Expand Down Expand Up @@ -341,6 +348,13 @@ void FlutterTizenEngine::SendPointerEvent(const FlutterPointerEvent& event) {
embedder_api_.SendPointerEvent(engine_, &event, 1);
}

void FlutterTizenEngine::SendViewFocusEvent(
const FlutterViewFocusEvent& event) {
if (engine_) {
embedder_api_.SendViewFocusEvent(engine_, &event);
}
}

void FlutterTizenEngine::SendWindowMetrics(int32_t x,
int32_t y,
int32_t width,
Expand Down
3 changes: 3 additions & 0 deletions flutter/shell/platform/tizen/flutter_tizen_engine.h
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,9 @@ class FlutterTizenEngine {
// Informs the engine of an incoming pointer event.
void SendPointerEvent(const FlutterPointerEvent& event);

// Informs the engine of a native view focus change.
void SendViewFocusEvent(const FlutterViewFocusEvent& event);

// Sends a window metrics update to the Flutter engine using current window
// dimensions in physical
void SendWindowMetrics(int32_t x,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ TEST_F(FlutterTizenEngineTest, RunDoesExpectedInitialization) {
EXPECT_STREQ(args->icu_data_path, "/foo/icudtl.dat");
EXPECT_EQ(args->dart_entrypoint_argc, 0);
EXPECT_NE(args->platform_message_callback, nullptr);
EXPECT_NE(args->view_focus_change_request_callback, nullptr);
EXPECT_NE(args->custom_task_runners, nullptr);
EXPECT_EQ(args->custom_task_runners->platform_task_runner,
args->custom_task_runners->ui_task_runner);
Expand Down
31 changes: 31 additions & 0 deletions flutter/shell/platform/tizen/flutter_tizen_view.cc
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,37 @@ void FlutterTizenView::OnRotate(int32_t degree) {
SendWindowMetrics(geometry.left, geometry.top, width, height, 0.0);
}

void FlutterTizenView::OnFocus(FlutterViewFocusState focus_state) {
if (focus_state == kFocused) {
auto* window = dynamic_cast<TizenWindow*>(tizen_view_.get());
if (window && !window->focusable()) {
return;
}
}
if (focus_state == last_focus_state_) {
return;
}
last_focus_state_ = focus_state;
FlutterViewFocusEvent event = {};
event.struct_size = sizeof(event);
event.view_id = view_id_;
event.state = focus_state;
event.direction = FlutterViewFocusDirection::kUndefined;
engine_->SendViewFocusEvent(event);
}

void FlutterTizenView::OnFocusChangeRequest(
const FlutterViewFocusChangeRequest& request) {
if (request.view_id != view_id_ || request.state != kFocused) {
return;
}
if (auto* window = dynamic_cast<TizenWindow*>(tizen_view_.get())) {
if (window->focusable()) {
window->ActivateWindow();
}
}
}

FlutterTizenView::PointerState* FlutterTizenView::GetOrCreatePointerState(
FlutterPointerDeviceKind device_kind,
int32_t device_id) {
Expand Down
9 changes: 9 additions & 0 deletions flutter/shell/platform/tizen/flutter_tizen_view.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ class FlutterTizenView : public TizenViewEventHandlerDelegate {

void OnRotate(int32_t degree) override;

void OnFocus(FlutterViewFocusState focus_state);

void OnFocusChangeRequest(const FlutterViewFocusChangeRequest& request);

void OnPointerMove(double x,
double y,
size_t timestamp,
Expand Down Expand Up @@ -166,6 +170,11 @@ class FlutterTizenView : public TizenViewEventHandlerDelegate {
// Keeps track of pointer states.
std::unordered_map<int32_t, std::unique_ptr<PointerState>> pointer_states_;

// The focus state most recently sent to the engine. Views start out
// unfocused. Used to avoid sending duplicate focus events when multiple
// lifecycle transitions map to the same focus state.
FlutterViewFocusState last_focus_state_ = kUnfocused;

// The plugin registrar managing internal plugins.
std::unique_ptr<PluginRegistrar> internal_plugin_registrar_;

Expand Down
118 changes: 118 additions & 0 deletions flutter/shell/platform/tizen/flutter_tizen_view_unittests.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
// Copyright 2026 Samsung Electronics Co., Ltd. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "flutter/shell/platform/tizen/flutter_tizen_view.h"

#include <Ecore.h>

#include <vector>

#include "flutter/shell/platform/embedder/test_utils/proc_table_replacement.h"
#include "flutter/shell/platform/tizen/testing/engine_modifier.h"
#include "flutter/shell/platform/tizen/tizen_window.h"
#include "gtest/gtest.h"

namespace flutter {
namespace testing {
namespace {

class TestTizenView : public TizenWindow {
public:
TestTizenView() : TizenWindow({}, false, true, false) {
input_method_context_ = std::make_unique<TizenInputMethodContext>(0);
}

void* GetRenderTarget() override { return nullptr; }
void* GetNativeHandle() override { return nullptr; }
uintptr_t GetWindowId() override { return 0; }
TizenGeometry GetGeometry() override { return {0, 0, 100, 100}; }
bool SetGeometry(TizenGeometry geometry) override { return true; }
int32_t GetDpi() override { return 160; }
uint32_t GetResourceId() override { return 0; }
void UpdateFlutterCursor(const std::string& kind) override {}
void Show() override {}
int32_t GetRotation() override { return 0; }
void SetPreferredOrientations(const std::vector<int>& rotations) override {}
void* GetRenderTargetDisplay() override { return nullptr; }
TizenGeometry GetScreenGeometry() override { return GetGeometry(); }
void BindKeys(const std::vector<std::string>& keys) override {}
void ActivateWindow() override { activated = true; }
void RaiseWindow() override {}
void LowerWindow() override {}

void SetFocusable(bool focusable) { focusable_ = focusable; }

bool activated = false;
};

TEST(FlutterTizenViewTest, SendsAndRequestsViewFocus) {
ecore_init();
{
FlutterDesktopEngineProperties properties = {};
properties.assets_path = "/foo/flutter_assets";
properties.icu_data_path = "/foo/icudtl.dat";
properties.aot_library_path = "/foo/libapp.so";

FlutterProjectBundle project(properties);
auto engine = std::make_unique<FlutterTizenEngine>(project);
std::vector<FlutterViewFocusEvent> sent_events;
EngineModifier modifier(engine.get());
modifier.SetEngine(reinterpret_cast<FLUTTER_API_SYMBOL(FlutterEngine)>(1));
modifier.embedder_api().SendViewFocusEvent = MOCK_ENGINE_PROC(
SendViewFocusEvent,
([&sent_events](auto engine, const FlutterViewFocusEvent* event) {
sent_events.push_back(*event);
return kSuccess;
}));
modifier.embedder_api().Shutdown = [](auto engine) { return kSuccess; };

auto tizen_view = std::make_unique<TestTizenView>();
TestTizenView* tizen_view_ptr = tizen_view.get();
FlutterTizenView view(kImplicitViewId, std::move(tizen_view),
std::move(engine), kEVulkan);

view.OnFocus(kFocused);
view.OnFocus(kFocused);
ASSERT_EQ(sent_events.size(), 1u);
EXPECT_EQ(sent_events[0].struct_size, sizeof(FlutterViewFocusEvent));
EXPECT_EQ(sent_events[0].view_id, kImplicitViewId);
EXPECT_EQ(sent_events[0].state, kFocused);
EXPECT_EQ(sent_events[0].direction, kUndefined);

view.OnFocus(kUnfocused);
view.OnFocus(kUnfocused);
ASSERT_EQ(sent_events.size(), 2u);
EXPECT_EQ(sent_events[1].state, kUnfocused);

FlutterViewFocusChangeRequest request = {
.struct_size = sizeof(FlutterViewFocusChangeRequest),
.view_id = kImplicitViewId,
.state = kUnfocused,
.direction = kUndefined,
};
view.OnFocusChangeRequest(request);
EXPECT_FALSE(tizen_view_ptr->activated);

request.state = kFocused;
request.view_id = kImplicitViewId + 1;
view.OnFocusChangeRequest(request);
EXPECT_FALSE(tizen_view_ptr->activated);

request.view_id = kImplicitViewId;
view.OnFocusChangeRequest(request);
EXPECT_TRUE(tizen_view_ptr->activated);

tizen_view_ptr->activated = false;
tizen_view_ptr->SetFocusable(false);
view.OnFocus(kFocused);
EXPECT_EQ(sent_events.size(), 2u);
view.OnFocusChangeRequest(request);
EXPECT_FALSE(tizen_view_ptr->activated);
}
ecore_shutdown();
}

} // namespace
} // namespace testing
} // namespace flutter
4 changes: 4 additions & 0 deletions flutter/shell/platform/tizen/testing/engine_modifier.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ class EngineModifier {
// engine unless overwritten again.
FlutterEngineProcTable& embedder_api() { return engine_->embedder_api_; }

void SetEngine(FLUTTER_API_SYMBOL(FlutterEngine) engine) {
engine_->engine_ = engine;
}

private:
FlutterTizenEngine* engine_;
};
Expand Down
2 changes: 2 additions & 0 deletions flutter/shell/platform/tizen/tizen_window.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ class TizenWindow : public TizenViewBase {

virtual void LowerWindow() = 0;

bool focusable() { return focusable_; }

protected:
explicit TizenWindow(TizenGeometry geometry,
bool transparent,
Expand Down
Loading