• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2022-2023 Huawei Device Co., Ltd.
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at
6  *
7  *     http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15 
16 #include "adapter/preview/entrance/event_dispatcher.h"
17 
18 #include <map>
19 
20 #include "adapter/preview/entrance/ace_container.h"
21 #include "adapter/preview/entrance/ace_view_preview.h"
22 #include "adapter/preview/entrance/editing/text_input_client_mgr.h"
23 #include "base/log/ace_trace.h"
24 #include "base/log/log.h"
25 #include "core/common/container_scope.h"
26 #include "core/event/key_event.h"
27 #include "core/event/touch_event.h"
28 
29 namespace OHOS::Ace::Platform {
30 namespace {
31 
32 const wchar_t UPPER_CASE_A = L'A';
33 const wchar_t LOWER_CASE_A = L'a';
34 const wchar_t CASE_0 = L'0';
35 const std::wstring NUM_SYMBOLS = L")!@#$%^&*(";
36 const std::map<MMI::KeyCode, wchar_t> PRINTABEL_SYMBOLS = {
37     { MMI::KeyCode::KEY_GRAVE, L'`' },
38     { MMI::KeyCode::KEY_MINUS, L'-' },
39     { MMI::KeyCode::KEY_EQUALS, L'=' },
40     { MMI::KeyCode::KEY_LEFT_BRACKET, L'[' },
41     { MMI::KeyCode::KEY_RIGHT_BRACKET, L']' },
42     { MMI::KeyCode::KEY_BACKSLASH, L'\\' },
43     { MMI::KeyCode::KEY_SEMICOLON, L';' },
44     { MMI::KeyCode::KEY_APOSTROPHE, L'\'' },
45     { MMI::KeyCode::KEY_COMMA, L',' },
46     { MMI::KeyCode::KEY_PERIOD, L'.' },
47     { MMI::KeyCode::KEY_SLASH, L'/' },
48     { MMI::KeyCode::KEY_SPACE, L' ' },
49     { MMI::KeyCode::KEY_NUMPAD_DIVIDE, L'/' },
50     { MMI::KeyCode::KEY_NUMPAD_MULTIPLY, L'*' },
51     { MMI::KeyCode::KEY_NUMPAD_SUBTRACT, L'-' },
52     { MMI::KeyCode::KEY_NUMPAD_ADD, L'+' },
53     { MMI::KeyCode::KEY_NUMPAD_DOT, L'.' },
54     { MMI::KeyCode::KEY_NUMPAD_COMMA, L',' },
55     { MMI::KeyCode::KEY_NUMPAD_EQUALS, L'=' },
56 };
57 
58 const std::map<MMI::KeyCode, wchar_t> SHIFT_PRINTABEL_SYMBOLS = {
59     { MMI::KeyCode::KEY_GRAVE, L'~' },
60     { MMI::KeyCode::KEY_MINUS, L'_' },
61     { MMI::KeyCode::KEY_EQUALS, L'+' },
62     { MMI::KeyCode::KEY_LEFT_BRACKET, L'{' },
63     { MMI::KeyCode::KEY_RIGHT_BRACKET, L'}' },
64     { MMI::KeyCode::KEY_BACKSLASH, L'|' },
65     { MMI::KeyCode::KEY_SEMICOLON, L':' },
66     { MMI::KeyCode::KEY_APOSTROPHE, L'\"' },
67     { MMI::KeyCode::KEY_COMMA, L'<' },
68     { MMI::KeyCode::KEY_PERIOD, L'>' },
69     { MMI::KeyCode::KEY_SLASH, L'?' },
70 };
71 
ConvertTouchEvent(const std::shared_ptr<MMI::PointerEvent> & pointerEvent,TouchEvent & event)72 void ConvertTouchEvent(const std::shared_ptr<MMI::PointerEvent>& pointerEvent, TouchEvent& event)
73 {
74     event.id = pointerEvent->id;
75     event.x = pointerEvent->x;
76     event.y = pointerEvent->y;
77     event.screenX = pointerEvent->screenX;
78     event.screenY = pointerEvent->screenY;
79     event.type = static_cast<TouchType>(static_cast<size_t>(pointerEvent->type));
80     event.pullType = static_cast<TouchType>(static_cast<size_t>(pointerEvent->pullType));
81     event.time = pointerEvent->time;
82     event.size = pointerEvent->size;
83     event.force = pointerEvent->force;
84     event.tiltX = pointerEvent->tiltX;
85     event.tiltY = pointerEvent->tiltY;
86     event.deviceId = pointerEvent->deviceId;
87     event.sourceType = static_cast<SourceType>(static_cast<int32_t>(pointerEvent->sourceType));
88     event.sourceTool = static_cast<SourceTool>(static_cast<int32_t>(pointerEvent->sourceTool));
89     event.pointerEvent = pointerEvent;
90     for (auto& item : pointerEvent->pointers) {
91         TouchPoint pointer;
92         pointer.id = item.id;
93         pointer.x = item.x;
94         pointer.y = item.y;
95         pointer.screenX = item.screenX;
96         pointer.screenY = item.screenY;
97         pointer.downTime = item.downTime;
98         pointer.size = item.size;
99         pointer.force = item.force;
100         pointer.tiltX = item.tiltX;
101         pointer.tiltY = item.tiltY;
102         pointer.sourceTool = static_cast<SourceTool>(static_cast<int32_t>(item.sourceTool));
103         pointer.isPressed = item.isPressed;
104         event.pointers.emplace_back(pointer);
105     }
106 }
107 
ConvertKeyEvent(const std::shared_ptr<MMI::KeyEvent> & keyEvent,KeyEvent & event)108 void ConvertKeyEvent(const std::shared_ptr<MMI::KeyEvent>& keyEvent, KeyEvent& event)
109 {
110     event.code = static_cast<KeyCode>(static_cast<int32_t>(keyEvent->code));
111     event.key = keyEvent->key;
112     event.action = static_cast<KeyAction>(static_cast<int32_t>(keyEvent->action));
113     for (auto& item : keyEvent->pressedCodes) {
114         event.pressedCodes.push_back(static_cast<KeyCode>(static_cast<int32_t>(item)));
115     }
116     event.repeatTime = keyEvent->repeatTime;
117     event.timeStamp = keyEvent->timeStamp;
118     event.metaKey = keyEvent->metaKey;
119     event.deviceId = keyEvent->deviceId;
120     event.sourceType = static_cast<SourceType>(static_cast<int32_t>(keyEvent->sourceType));
121     event.rawKeyEvent = keyEvent;
122     event.enableCapsLock = keyEvent->enableCapsLock_;
123 }
124 
125 } // namespace
126 
EventDispatcher()127 EventDispatcher::EventDispatcher() {}
128 
129 EventDispatcher::~EventDispatcher() = default;
130 
Initialize()131 void EventDispatcher::Initialize()
132 {
133     LOGI("Initialize event dispatcher");
134     // Initial the proxy of Input method
135     TextInputClientMgr::GetInstance().InitTextInputProxy();
136     // Register the idle event callback function.
137 #ifndef ENABLE_ROSEN_BACKEND
138     IdleCallback idleNoticeCallback = [](int64_t deadline) {
139         EventDispatcher::GetInstance().DispatchIdleEvent(deadline);
140     };
141     FlutterDesktopSetIdleCallback(controller_, idleNoticeCallback);
142 #else
143     // rosen process idle
144 #endif
145 }
146 
DispatchIdleEvent(int64_t deadline)147 void EventDispatcher::DispatchIdleEvent(int64_t deadline)
148 {
149     ACE_SCOPED_TRACE("DispatchIdleEvent");
150     auto container = AceContainer::GetContainerInstance(ACE_INSTANCE_ID);
151     if (!container) {
152         LOGE("container is null");
153         return;
154     }
155 
156     auto aceView = container->GetAceView();
157     if (!aceView) {
158         LOGE("aceView is null");
159         return;
160     }
161 
162     aceView->ProcessIdleEvent(deadline);
163 }
164 
GetMouseEventAction(int32_t action,OHOS::Ace::MouseEvent & mouseEvent)165 static void GetMouseEventAction(int32_t action, OHOS::Ace::MouseEvent& mouseEvent)
166 {
167     switch (action) {
168         case OHOS::MMI::PointerEvent::POINTER_ACTION_BUTTON_DOWN:
169             mouseEvent.action = MouseAction::PRESS;
170             break;
171         case OHOS::MMI::PointerEvent::POINTER_ACTION_BUTTON_UP:
172             mouseEvent.action = MouseAction::RELEASE;
173             break;
174         case OHOS::MMI::PointerEvent::POINTER_ACTION_ENTER_WINDOW:
175             mouseEvent.action = MouseAction::WINDOW_ENTER;
176             break;
177         case OHOS::MMI::PointerEvent::POINTER_ACTION_LEAVE_WINDOW:
178             mouseEvent.action = MouseAction::WINDOW_LEAVE;
179             break;
180         case OHOS::MMI::PointerEvent::POINTER_ACTION_MOVE:
181             mouseEvent.action = MouseAction::MOVE;
182             break;
183 #ifdef ENABLE_DRAG_FRAMEWORK
184         case OHOS::MMI::PointerEvent::POINTER_ACTION_PULL_DOWN:
185             events.action = MouseAction::PRESS;
186             break;
187         case OHOS::MMI::PointerEvent::POINTER_ACTION_PULL_MOVE:
188             events.action = MouseAction::MOVE;
189             break;
190         case OHOS::MMI::PointerEvent::POINTER_ACTION_PULL_UP:
191             events.action = MouseAction::RELEASE;
192             break;
193 #endif // ENABLE_DRAG_FRAMEWORK
194         default:
195             mouseEvent.action = MouseAction::NONE;
196             break;
197     }
198 }
199 
GetMouseEventButton(int32_t button,Ace::MouseEvent & mouseEvent)200 static void GetMouseEventButton(int32_t button, Ace::MouseEvent& mouseEvent)
201 {
202     switch (button) {
203         case MMI::PointerEvent::MOUSE_BUTTON_LEFT:
204             mouseEvent.button = MouseButton::LEFT_BUTTON;
205             break;
206         case MMI::PointerEvent::MOUSE_BUTTON_RIGHT:
207             mouseEvent.button = MouseButton::RIGHT_BUTTON;
208             break;
209         case MMI::PointerEvent::MOUSE_BUTTON_MIDDLE:
210             mouseEvent.button = MouseButton::MIDDLE_BUTTON;
211             break;
212         case MMI::PointerEvent::MOUSE_BUTTON_SIDE:
213             mouseEvent.button = MouseButton::BACK_BUTTON;
214             break;
215         case MMI::PointerEvent::MOUSE_BUTTON_EXTRA:
216             mouseEvent.button = MouseButton::SIDE_BUTTON;
217             break;
218         case MMI::PointerEvent::MOUSE_BUTTON_FORWARD:
219             mouseEvent.button = MouseButton::FORWARD_BUTTON;
220             break;
221         case MMI::PointerEvent::MOUSE_BUTTON_BACK:
222             mouseEvent.button = MouseButton::BACK_BUTTON;
223             break;
224         case MMI::PointerEvent::MOUSE_BUTTON_TASK:
225             mouseEvent.button = MouseButton::TASK_BUTTON;
226             break;
227         default:
228             mouseEvent.button = MouseButton::NONE_BUTTON;
229             break;
230     }
231 }
232 
ConvertMouseEvent(const std::shared_ptr<MMI::PointerEvent> & pointerEvent,Ace::MouseEvent & mouseEvent)233 static void ConvertMouseEvent(
234     const std::shared_ptr<MMI::PointerEvent>& pointerEvent, Ace::MouseEvent& mouseEvent)
235 {
236     mouseEvent.id = pointerEvent->id;
237     mouseEvent.x = pointerEvent->x;
238     mouseEvent.y = pointerEvent->y;
239     mouseEvent.screenX = pointerEvent->screenX;
240     mouseEvent.screenY = pointerEvent->screenY;
241     GetMouseEventAction(pointerEvent->pointerAction_, mouseEvent);
242     GetMouseEventButton(pointerEvent->buttonId_, mouseEvent);
243     mouseEvent.sourceType = SourceType::MOUSE;
244     mouseEvent.targetDisplayId = pointerEvent->targetDisplayId_;
245     mouseEvent.deviceId = pointerEvent->deviceId;
246     std::set<int32_t> buttonSet = pointerEvent->pressedButtons_;
247     if (pointerEvent->pressedButtons_.empty()) {
248         pointerEvent->pressedButtons_.insert(pointerEvent->buttonId_);
249     }
250     uint32_t buttons = 0;
251     if (buttonSet.end() != buttonSet.find(MMI::PointerEvent::MOUSE_BUTTON_LEFT)) {
252         buttons &= static_cast<uint32_t>(MouseButton::LEFT_BUTTON);
253     }
254     if (buttonSet.end() != buttonSet.find(MMI::PointerEvent::MOUSE_BUTTON_RIGHT)) {
255         buttons &= static_cast<uint32_t>(MouseButton::RIGHT_BUTTON);
256     }
257     if (buttonSet.end() != buttonSet.find(MMI::PointerEvent::MOUSE_BUTTON_MIDDLE)) {
258         buttons &= static_cast<uint32_t>(MouseButton::MIDDLE_BUTTON);
259     }
260     if (buttonSet.end() != buttonSet.find(MMI::PointerEvent::MOUSE_BUTTON_SIDE)) {
261         buttons &= static_cast<uint32_t>(MouseButton::SIDE_BUTTON);
262     }
263     if (buttonSet.end() != buttonSet.find(MMI::PointerEvent::MOUSE_BUTTON_EXTRA)) {
264         buttons &= static_cast<uint32_t>(MouseButton::EXTRA_BUTTON);
265     }
266     if (buttonSet.end() != buttonSet.find(MMI::PointerEvent::MOUSE_BUTTON_FORWARD)) {
267         buttons &= static_cast<uint32_t>(MouseButton::FORWARD_BUTTON);
268     }
269     if (buttonSet.end() != buttonSet.find(MMI::PointerEvent::MOUSE_BUTTON_BACK)) {
270         buttons &= static_cast<uint32_t>(MouseButton::BACK_BUTTON);
271     }
272     if (buttonSet.end() != buttonSet.find(MMI::PointerEvent::MOUSE_BUTTON_TASK)) {
273         buttons &= static_cast<uint32_t>(MouseButton::TASK_BUTTON);
274     }
275     mouseEvent.pressedButtons = static_cast<int32_t>(buttons);
276 }
277 
GetAxisEventAction(int32_t action,Ace::AxisEvent & event)278 static void GetAxisEventAction(int32_t action, Ace::AxisEvent& event)
279 {
280     switch (action) {
281         case MMI::PointerEvent::POINTER_ACTION_AXIS_BEGIN:
282             event.action = Ace::AxisAction::BEGIN;
283             break;
284         case MMI::PointerEvent::POINTER_ACTION_AXIS_UPDATE:
285             event.action = Ace::AxisAction::UPDATE;
286             break;
287         case MMI::PointerEvent::POINTER_ACTION_AXIS_END:
288             event.action = Ace::AxisAction::END;
289             break;
290         default:
291             event.action = Ace::AxisAction::NONE;
292             break;
293     }
294 }
295 
GetAxisValue(const std::shared_ptr<MMI::PointerEvent> & pointerEvent,MMI::PointerEvent::AxisType axis)296 static double GetAxisValue(const std::shared_ptr<MMI::PointerEvent>& pointerEvent, MMI::PointerEvent::AxisType axis)
297 {
298     double axisValue {};
299     if ((axis >= MMI::PointerEvent::AXIS_TYPE_UNKNOWN) && (axis < MMI::PointerEvent::AXIS_TYPE_MAX)) {
300         axisValue = pointerEvent->axisValues_[axis];
301     }
302     return axisValue;
303 }
304 
ConvertAxisEvent(const std::shared_ptr<MMI::PointerEvent> & pointerEvent,Ace::AxisEvent & event)305 static void ConvertAxisEvent(const std::shared_ptr<MMI::PointerEvent>& pointerEvent, Ace::AxisEvent& event)
306 {
307     event.id = pointerEvent->id;
308     event.x = pointerEvent->x;
309     event.y = pointerEvent->y;
310     event.screenX = pointerEvent->screenX;
311     event.screenY = pointerEvent->screenY;
312     event.horizontalAxis = GetAxisValue(pointerEvent, MMI::PointerEvent::AxisType::AXIS_TYPE_SCROLL_HORIZONTAL);
313     event.verticalAxis = GetAxisValue(pointerEvent, MMI::PointerEvent::AxisType::AXIS_TYPE_SCROLL_VERTICAL);
314     event.pinchAxisScale = GetAxisValue(pointerEvent, MMI::PointerEvent::AxisType::AXIS_TYPE_PINCH);
315     GetAxisEventAction(pointerEvent->pointerAction_, event);
316     event.sourceType = SourceType::MOUSE;
317     event.sourceTool = SourceTool::MOUSE;
318     event.pointerEvent = pointerEvent;
319 }
320 
DispatchTouchEvent(const std::shared_ptr<MMI::PointerEvent> & pointerEvent)321 bool EventDispatcher::DispatchTouchEvent(const std::shared_ptr<MMI::PointerEvent>& pointerEvent)
322 {
323     ACE_SCOPED_TRACE("DispatchTouchEvent");
324     LOGI("Dispatch touch event");
325     auto container = AceContainer::GetContainerInstance(ACE_INSTANCE_ID);
326     CHECK_NULL_RETURN(container, false);
327     auto aceView = container->GetAceView();
328     CHECK_NULL_RETURN(aceView, false);
329     if (pointerEvent->sourceType == MMI::PointerEvent::SOURCE_TYPE_MOUSE) {
330         if (pointerEvent->pointerAction_ >= MMI::PointerEvent::POINTER_ACTION_AXIS_BEGIN &&
331             pointerEvent->pointerAction_ <= MMI::PointerEvent::POINTER_ACTION_AXIS_END) {
332             OHOS::Ace::AxisEvent axisEvent;
333             ConvertAxisEvent(pointerEvent, axisEvent);
334             return aceView->HandleAxisEvent(axisEvent);
335         } else {
336             OHOS::Ace::MouseEvent mouseEvent;
337             ConvertMouseEvent(pointerEvent, mouseEvent);
338             return aceView->HandleMouseEvent(mouseEvent);
339         }
340     }
341 
342     TouchEvent touchEvent;
343     ConvertTouchEvent(pointerEvent, touchEvent);
344     return aceView->HandleTouchEvent(touchEvent);
345 }
346 
DispatchBackPressedEvent()347 bool EventDispatcher::DispatchBackPressedEvent()
348 {
349     ACE_SCOPED_TRACE("DispatchBackPressedEvent");
350     LOGI("Dispatch back pressed event");
351     auto container = AceContainer::GetContainerInstance(ACE_INSTANCE_ID);
352     CHECK_NULL_RETURN(container, false);
353     auto context = container->GetPipelineContext();
354     CHECK_NULL_RETURN(context, false);
355 
356     std::promise<bool> backPromise;
357     std::future<bool> backFuture = backPromise.get_future();
358     auto weak = AceType::WeakClaim(AceType::RawPtr(context));
359     container->GetTaskExecutor()->PostTask(
360         [weak, &backPromise]() {
361             auto context = weak.Upgrade();
362             if (context == nullptr) {
363                 LOGW("context is nullptr.");
364                 return;
365             }
366             bool canBack = false;
367             if (context->IsLastPage()) {
368                 LOGW("Can't back because this is the last page!");
369             } else {
370                 canBack = context->CallRouterBackToPopPage();
371             }
372             backPromise.set_value(canBack);
373         },
374         TaskExecutor::TaskType::PLATFORM);
375     return backFuture.get();
376 }
377 
DispatchInputMethodEvent(unsigned int codePoint)378 bool EventDispatcher::DispatchInputMethodEvent(unsigned int codePoint)
379 {
380     ACE_SCOPED_TRACE("DispatchInputMethodEvent");
381     LOGI("Dispatch input method event");
382     return TextInputClientMgr::GetInstance().AddCharacter(static_cast<wchar_t>(codePoint));
383 }
384 
DispatchKeyEvent(const std::shared_ptr<MMI::KeyEvent> & keyEvent)385 bool EventDispatcher::DispatchKeyEvent(const std::shared_ptr<MMI::KeyEvent>& keyEvent)
386 {
387     ACE_SCOPED_TRACE("DispatchKeyEvent");
388     LOGI("Dispatch key event");
389     if (HandleTextKeyEvent(keyEvent)) {
390         LOGI("The event is related to the input component and has been handled successfully.");
391         return true;
392     }
393     auto container = AceContainer::GetContainerInstance(ACE_INSTANCE_ID);
394     CHECK_NULL_RETURN(container, false);
395     auto aceView = container->GetAceView();
396     CHECK_NULL_RETURN(aceView, false);
397 
398     KeyEvent event;
399     ConvertKeyEvent(keyEvent, event);
400     return aceView->HandleKeyEvent(event);
401 }
402 
HandleTextKeyEvent(const std::shared_ptr<MMI::KeyEvent> & keyEvent)403 bool EventDispatcher::HandleTextKeyEvent(const std::shared_ptr<MMI::KeyEvent>& keyEvent)
404 {
405     // Only the keys involved in the input component are processed here, and the other keys will be forwarded.
406     if (!TextInputClientMgr::GetInstance().IsValidClientId()) {
407         return false;
408     }
409 
410     const static size_t maxKeySizes = 2;
411     wchar_t keyChar;
412     if (keyEvent->pressedCodes.size() == 1) {
413         auto iterCode = PRINTABEL_SYMBOLS.find(keyEvent->code);
414         if (iterCode != PRINTABEL_SYMBOLS.end()) {
415             keyChar = iterCode->second;
416         } else if (MMI::KeyCode::KEY_0 <= keyEvent->code && keyEvent->code <= MMI::KeyCode::KEY_9) {
417             keyChar = static_cast<wchar_t>(keyEvent->code) - static_cast<wchar_t>(MMI::KeyCode::KEY_0) + CASE_0;
418         } else if (MMI::KeyCode::KEY_NUMPAD_0 <= keyEvent->code && keyEvent->code <= MMI::KeyCode::KEY_NUMPAD_9) {
419             if (!keyEvent->enableNumLock_) {
420                 return true;
421             }
422             keyChar = static_cast<wchar_t>(keyEvent->code) - static_cast<wchar_t>(MMI::KeyCode::KEY_NUMPAD_0) + CASE_0;
423         } else if (MMI::KeyCode::KEY_A <= keyEvent->code && keyEvent->code <= MMI::KeyCode::KEY_Z) {
424             keyChar = static_cast<wchar_t>(keyEvent->code) - static_cast<wchar_t>(MMI::KeyCode::KEY_A);
425             keyChar += (keyEvent->enableCapsLock_ ? UPPER_CASE_A : LOWER_CASE_A);
426         } else {
427             return false;
428         }
429     } else if (keyEvent->pressedCodes.size() == maxKeySizes &&
430                keyEvent->pressedCodes[0] == MMI::KeyCode::KEY_SHIFT_LEFT) {
431         auto iterCode = SHIFT_PRINTABEL_SYMBOLS.find(keyEvent->code);
432         if (iterCode != SHIFT_PRINTABEL_SYMBOLS.end()) {
433             keyChar = iterCode->second;
434         } else if (MMI::KeyCode::KEY_A <= keyEvent->code && keyEvent->code <= MMI::KeyCode::KEY_Z) {
435             keyChar = static_cast<wchar_t>(keyEvent->code) - static_cast<wchar_t>(MMI::KeyCode::KEY_A);
436             keyChar += (keyEvent->enableCapsLock_ ? LOWER_CASE_A : UPPER_CASE_A);
437         } else if (MMI::KeyCode::KEY_0 <= keyEvent->code && keyEvent->code <= MMI::KeyCode::KEY_9) {
438             keyChar = NUM_SYMBOLS[static_cast<int32_t>(keyEvent->code) - static_cast<int32_t>(MMI::KeyCode::KEY_0)];
439         } else {
440             return false;
441         }
442     } else {
443         return false;
444     }
445     if (keyEvent->action != MMI::KeyAction::DOWN) {
446         return true;
447     }
448     return TextInputClientMgr::GetInstance().AddCharacter(keyChar);
449 }
450 
451 } // namespace OHOS::Ace::Platform
452