From: Igor Olshevskyi Date: Wed, 9 Aug 2017 04:57:03 +0000 (+0300) Subject: TizenRefApp-9082 [Call UI] Implement Screen Reader highlight logic for MainPage X-Git-Tag: submit/tizen_4.0/20170911.085536^2~9 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=7df53dad9dd42453662f08fa381e2ee953f7e662;p=profile%2Fwearable%2Fapps%2Fnative%2Fcall-ui.git TizenRefApp-9082 [Call UI] Implement Screen Reader highlight logic for MainPage Change-Id: I91c20c3a111762ef110ce1ec132fd124920b63b7 --- diff --git a/edc/accept_reject.edc b/edc/accept_reject.edc index 6938525..5b1d360 100644 --- a/edc/accept_reject.edc +++ b/edc/accept_reject.edc @@ -73,7 +73,6 @@ group { "elm/layout/callui/event_accept_reject"; rect { "bg"; scale; mouse; - repeat; desc { "default"; min: CU_ACCEPT_REJECT_ICON_BG_SIZE; fixed: 1 1; @@ -162,20 +161,8 @@ group { "elm/layout/callui/accept_reject_main"; // Control parts - swallow { "swl.accept.finger.event"; - scale; - mouse; - desc { "default"; - min: CU_ACCEPT_REJECT_ICON_BG_SIZE; - fixed: 1 1; - rel1 { relative: CU_ACCEPT_COMPONENT_REL; to: "bg"; } - rel2 { relative: CU_ACCEPT_COMPONENT_REL; to: "bg"; } - } - } swallow { "swl.accept.finger.guide.bg"; scale; - mouse; - repeat; desc { "default"; min: CU_ACCEPT_REJECT_ICON_BG_SIZE; fixed: 1 1; @@ -185,8 +172,6 @@ group { "elm/layout/callui/accept_reject_main"; } swallow { "swl.accept.finger.tracer"; scale; - mouse; - repeat; desc { "default"; min: CU_ACCEPT_REJECT_FINGER_TRACER_BG_SIZE; fixed: 1 1; @@ -196,8 +181,6 @@ group { "elm/layout/callui/accept_reject_main"; } swallow { "swl.accept.icon"; scale; - mouse; - repeat; desc { "default"; min: CU_ACCEPT_REJECT_ICON_SIZE; fixed: 1 1; @@ -205,20 +188,18 @@ group { "elm/layout/callui/accept_reject_main"; rel2 { relative: CU_ACCEPT_COMPONENT_REL; to: "bg"; } } } - swallow { "swl.reject.finger.event"; + swallow { "swl.accept.finger.event"; scale; mouse; desc { "default"; min: CU_ACCEPT_REJECT_ICON_BG_SIZE; fixed: 1 1; - rel1 { relative: CU_REJECT_COMPONENT_REL; to: "bg"; } - rel2 { relative: CU_REJECT_COMPONENT_REL; to: "bg"; } + rel1 { relative: CU_ACCEPT_COMPONENT_REL; to: "bg"; } + rel2 { relative: CU_ACCEPT_COMPONENT_REL; to: "bg"; } } } swallow { "swl.reject.finger.guide.bg"; scale; - mouse; - repeat; desc { "default"; min: CU_ACCEPT_REJECT_ICON_BG_SIZE; fixed: 1 1; @@ -228,8 +209,6 @@ group { "elm/layout/callui/accept_reject_main"; } swallow { "swl.reject.finger.tracer"; scale; - mouse; - repeat; desc { "default"; min: CU_ACCEPT_REJECT_FINGER_TRACER_BG_SIZE; fixed: 1 1; @@ -239,8 +218,6 @@ group { "elm/layout/callui/accept_reject_main"; } swallow { "swl.reject.icon"; scale; - mouse; - repeat; desc { "default"; min: CU_ACCEPT_REJECT_ICON_SIZE; fixed: 1 1; @@ -248,5 +225,15 @@ group { "elm/layout/callui/accept_reject_main"; rel2 { relative: CU_REJECT_COMPONENT_REL; to: "bg"; } } } + swallow { "swl.reject.finger.event"; + scale; + mouse; + desc { "default"; + min: CU_ACCEPT_REJECT_ICON_BG_SIZE; + fixed: 1 1; + rel1 { relative: CU_REJECT_COMPONENT_REL; to: "bg"; } + rel2 { relative: CU_REJECT_COMPONENT_REL; to: "bg"; } + } + } } } diff --git a/edc/call_info.edc b/edc/call_info.edc index ccd688e..86dc549 100644 --- a/edc/call_info.edc +++ b/edc/call_info.edc @@ -218,12 +218,17 @@ group { "elm/layout/callui/call_info"; CU_DOT("dot.first", 3, -3) CU_DOT("dot.second", 9, -9) CU_DOT("dot.third", 15, -15) + rect { "ao_text_info"; mouse; scale; desc { "default"; - rel1.to: "text_info"; - rel2.to: "text_info"; + fixed: 1 1; + align: 0.0 0.0; + min: 208 32; + max: 208 32; + rel1 { relative: 1.0 1.0; to_x: "left.pad"; to_y: "top.pad"; } + rel2 { relative: 0.0 1.0; to_x: "right.pad"; to_y: "top.pad"; } color: 0 0 0 0; } } diff --git a/edc/main_ly.edc b/edc/main_ly.edc index e87ac93..1e9dd3d 100644 --- a/edc/main_ly.edc +++ b/edc/main_ly.edc @@ -57,3 +57,7 @@ group { "elm/layout/callui/main"; } } } + +group { "elm/layout/callui/fake_access_object"; + data.item: "access_highlight" "on"; +} diff --git a/edc/more_option.edc b/edc/more_option.edc index 56a6577..f5640cf 100644 --- a/edc/more_option.edc +++ b/edc/more_option.edc @@ -78,6 +78,15 @@ group { "elm/layout/callui/more_option"; text.fit: 1 1; } } + rect { "ao_txt.status"; + mouse; + scale; + desc { "default"; + rel1.to: "txt.status"; + rel2.to: "txt.status"; + color: 0 0 0 0; + } + } swallow { "swl.slot.1"; scale; desc { "default"; diff --git a/edc/reject_msg.edc b/edc/reject_msg.edc index cc88b05..ef23efb 100644 --- a/edc/reject_msg.edc +++ b/edc/reject_msg.edc @@ -43,7 +43,7 @@ group { "elm/layout/callui/reject_msg"; } } image { "cue"; - repeat; + scale; desc { "default"; align: 0.5 1.0; fixed: 0 1; @@ -74,8 +74,6 @@ group { "elm/layout/callui/reject_msg"; } textblock { "reject_msg_text"; scale; - nomouse; - repeat; desc { "default"; rel1 { relative: 0.0 0.0; to: "reject_msg_text_zone"; } rel2 { relative: 1.0 1.0; to: "reject_msg_text_zone"; } @@ -88,6 +86,15 @@ group { "elm/layout/callui/reject_msg"; hid; } } + rect { "ao_cue"; + mouse; + scale; + desc { "default"; + rel1 { relative: 0.0 0.0; to: "reject_msg_text_zone"; } + rel2 { relative: 1.0 1.0; to_x: "reject_msg_text_zone"; } + color: 0 0 0 0; + } + } } } diff --git a/edc/volume_control.edc b/edc/volume_control.edc index e0ab578..21e173d 100644 --- a/edc/volume_control.edc +++ b/edc/volume_control.edc @@ -118,6 +118,18 @@ group { "elm/layout/callui/volume_control"; hid; } } + textblock { "ao_txt.value"; + scale; + mouse; + desc { "default"; + fixed: 1 1; + rel1.to: "txt.value"; + rel2.to: "txt.value"; + } + desc { "hide"; + hid; + } + } swallow { "swl.minus"; scale; desc { "default"; diff --git a/inc/config.h b/inc/config.h index 5d9d113..fd664e0 100644 --- a/inc/config.h +++ b/inc/config.h @@ -21,9 +21,11 @@ namespace callui { + constexpr auto PACKAGE = "w-call-ui"; + constexpr auto WINDOW_NAME = "org.tizen.call-ui"; constexpr auto BASE_SCALE = 1.3; } -#endif // __CALL_UI_CONFIG_H__ +#endif // __CALLUI_CONFIG_H__ diff --git a/inc/presenters/AcceptRejectPresenter.h b/inc/presenters/AcceptRejectPresenter.h index fd477e9..49ad4e1 100644 --- a/inc/presenters/AcceptRejectPresenter.h +++ b/inc/presenters/AcceptRejectPresenter.h @@ -52,6 +52,9 @@ namespace callui { void update(CallMask calls); + ucl::ElmWidget *getAcceptAo(); + ucl::ElmWidget *getRejectAo(); + private: friend class ucl::ReffedObj; AcceptRejectPresenter(ucl::IRefCountObj &rc, diff --git a/inc/presenters/AccessoryPresenter.h b/inc/presenters/AccessoryPresenter.h index 1cd131a..2a3d573 100644 --- a/inc/presenters/AccessoryPresenter.h +++ b/inc/presenters/AccessoryPresenter.h @@ -53,6 +53,16 @@ namespace callui { void hideVolumeControls(); ucl::Result update(const ICallManagerSRef &cm); + // Screen Reader + ucl::ElmWidget *getVolumBtn(); + ucl::ElmWidget *getBluetoothBtn(); + ucl::ElmWidget *getMuteBtn(); + ucl::ElmWidget *getAddContactBtn(); + ucl::ElmWidget *getVolumeControlLy(); + ucl::ElmWidget *getVolumeControlDecreaseBtn(); + ucl::ElmWidget *getVolumeControlIncreaseBtn(); + ucl::ElmWidget *getVolumeControlValueTxtAo(); + private: enum class ComponentsMode { UNDEFINED, @@ -90,6 +100,8 @@ namespace callui { void onVolumeControlEventCb(VolumeControlEvent event); Eina_Bool onRotaryEvent(Eext_Rotary_Event_Info *info); + bool checkPossibilityToModifyVolume(int volume, + bool needIncrease); void tryIncreaseVolume(); void tryDecreaseVolume(); @@ -113,6 +125,10 @@ namespace callui { ucl::Result setActiveCallCompomnents(); ucl::Result setEndCallCompomnents(const ICallManagerSRef &cm); + // Screen Reader + void registerVolumeControlAo(); + void onVolumeControlScreenReaderReadStart(ucl::Widget &widget, void *eventInfo); + private: ucl::LayoutSRef m_widget; ucl::StyledWidgetSRef m_volumeBtn; @@ -129,9 +145,9 @@ namespace callui { ComponentsMode m_mode; std::string m_unsavedPhoneNumber; NotiHandler m_exitHandler; + + bool m_isVcShowOnRotaryEvent; }; } - - #endif // __CALLUI_PRESENTERS_ACCESSORY_PRESENTER_H__ diff --git a/inc/presenters/AtspiHighlightHelper.h b/inc/presenters/AtspiHighlightHelper.h new file mode 100644 index 0000000..e6d127d --- /dev/null +++ b/inc/presenters/AtspiHighlightHelper.h @@ -0,0 +1,64 @@ +/* + * Copyright 2017 Samsung Electronics Co., Ltd + * + * Licensed under the Flora License, Version 1.1 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://floralicense.org/license/ + * + * 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 __CALLUI_PRESENTERS_ATSPI_HIGHLIGHT_HELPER_H__ +#define __CALLUI_PRESENTERS_ATSPI_HIGHLIGHT_HELPER_H__ + +#include "ucl/mvp/GuiPresenter.h" + +#include "types.h" + +namespace callui { + + class AtspiHighlightHelper final : public ucl::GuiPresenter { + public: + using RelationEventHandler = ucl::WeakDelegate; + + using GestureEventHandler = ucl::WeakDelegate; + public: + static AtspiHighlightHelperSRef newInstance(GuiPresenter &parent, + ucl::ElmWidget &rootWidget); + + void setRelationEventHandler(RelationEventHandler handler); + void setGestureEventHandler(GestureEventHandler handler); + void registerWidget(ucl::ElmWidget &widget); + bool handleGesture(Elm_Interface_Atspi_Accessible *widget, + const Elm_Atspi_Gesture_Info &info); + + private: + friend class ucl::ReffedObj; + AtspiHighlightHelper(ucl::IRefCountObj &rc); + virtual ~AtspiHighlightHelper(); + + ucl::Result prepare(GuiPresenter &parent, ucl::ElmWidget &rootWidget); + + void handleAtspiGesture(Elm_Interface_Atspi_Accessible *widget, + ucl::AtspiGestureEventInfo &e); + + private: + void onAtspiGesture(ucl::Widget &widget, void *eventInfo); + + private: + RelationEventHandler m_relationEventHandler; + GestureEventHandler m_gestureEventHandler; + }; +} + +#endif // __CALLUI_PRESENTERS_ATSPI_HIGHLIGHT_HELPER_H__ diff --git a/inc/presenters/CallInfoPresenter.h b/inc/presenters/CallInfoPresenter.h index c7bc82c..b2e51da 100644 --- a/inc/presenters/CallInfoPresenter.h +++ b/inc/presenters/CallInfoPresenter.h @@ -49,6 +49,11 @@ namespace callui { CallMode getMode() const; ucl::Result update(CallMode mode, const ICallManagerSRef &cm); + // Screen Reader + ucl::ElmWidget *getStatusTxtAo(); + ucl::ElmWidget *getMainTxtAo(); + ucl::ElmWidget *getSubTxtAo(); + private: friend class ucl::ReffedObj; CallInfoPresenter(ucl::IRefCountObj &rc, @@ -69,7 +74,6 @@ namespace callui { ucl::Result updateCallerId(); ucl::Result updateSubText(); ucl::Result updateMainTxt(); - std::string getNumberSubText(const ICallInfoSCRef &callInfo) const; std::string getIncomingCallSubText() const; std::string getOutgoingCallSubText() const; @@ -78,7 +82,12 @@ namespace callui { std::string generateMainTxt(const ICallInfoSCRef &callInfo); - void displayMainTxt(const ICallInfoSCRef &info, const std::string &text) const; + void displayMainTxt(const ICallInfoSCRef &info, const std::string &text); + + // Screen Reader + ucl::Result setMainTxtAccessObject(const std::string &text); + ucl::Result setSubTxtAccessObject(const std::string &text); + private: ucl::LayoutSRef m_widget; ucl::StyledWidgetSRef m_callerId; @@ -92,6 +101,11 @@ namespace callui { CallStatusPresenterSRef m_callStatus; bool m_isSubTxtEnable; bool m_needModifyCallStatus; + + // Screen Reader + ucl::ElmWidgetSRef m_statusAO; + ucl::ElmWidgetSRef m_mainTxtAO; + ucl::ElmWidgetSRef m_subTxtAO; }; } diff --git a/inc/presenters/CallStatusPresenter.h b/inc/presenters/CallStatusPresenter.h index 0f788b3..c1d5a30 100644 --- a/inc/presenters/CallStatusPresenter.h +++ b/inc/presenters/CallStatusPresenter.h @@ -47,6 +47,9 @@ namespace callui { public: virtual ~CallStatusPresenter(); + // Screen Reader + ucl::ElmWidget *getStatusTextAo(); + private: friend class ucl::ReffedObj; CallStatusPresenter(ucl::IRefCountObj &rc, @@ -65,6 +68,9 @@ namespace callui { Eina_Bool onCallDurationTimerCb(); Eina_Bool onBlinkingTimerCb(); + // Screen Reader + ucl::Result createStatusTxtAo(); + private: ucl::LayoutSRef m_ly; CallMode m_mode; @@ -73,6 +79,9 @@ namespace callui { Ecore_Timer *m_timer; struct tm m_duration; int m_blinkCount; + + // Screen Reader + ucl::ElmWidgetSRef m_statusTxtAo; }; } diff --git a/inc/presenters/MainPage.h b/inc/presenters/MainPage.h index 6366dea..03747c0 100644 --- a/inc/presenters/MainPage.h +++ b/inc/presenters/MainPage.h @@ -67,10 +67,12 @@ namespace callui { ucl::Result createRejectMsgPresenter( const IRejectMsgProviderSRef &provider); + ucl::Result createRejectMsgCue(); void RejectMsgStateCb(RejectMsgState state); void RejectMsgSelectCb(const IRejectMsgSRef &rm); - ucl::Result createBottomBtn(const ucl::ElmStyle &style); + ucl::Result createBottomBtn(const ucl::ElmStyle &style, + bool setVisible = true); void onBottomBtnClicked(ucl::Widget &widget, void *eventInfo); void startEndCallTimer(); @@ -80,8 +82,6 @@ namespace callui { void onPowerKeyUp(ucl::Widget &widget, void *eventInfo); void processKeyPress(); - bool detectMuteControlDisableState(); - ucl::Result updateDeviceState(CallMode prevMode, CallMode curMode); ucl::Result createWidget(); @@ -90,6 +90,25 @@ namespace callui { void onExitAppRequest(); + // Screen Reader + ucl::Result createRejectMsgCueAo(); + ucl::Result createAtspiHighlightHelper(); + void registerIncomingCallModeAo(); + void registerActiveCallModeAo(); + void registerEndCallModeAo(); + Elm_Interface_Atspi_Accessible *onIncomingCallModeAtspiHighlight( + Elm_Interface_Atspi_Accessible *widget, + Elm_Atspi_Relation_Type flowRelation); + Elm_Interface_Atspi_Accessible *onActiveCallModeAtspiHighlight( + Elm_Interface_Atspi_Accessible *widget, + Elm_Atspi_Relation_Type flowRelation); + Elm_Interface_Atspi_Accessible *onEndCallModeAtspiHighlight( + Elm_Interface_Atspi_Accessible *ao, + Elm_Atspi_Relation_Type flowRelation); + bool onIncomingModeAtspiGesture( + Elm_Interface_Atspi_Accessible *widget, + Elm_Atspi_Gesture_Type gestureType); + // Page virtual void onBackKey() final override; @@ -117,6 +136,10 @@ namespace callui { CallMode m_mode; Ecore_Timer *m_ecTimer; bool m_ecTimerBtnReq; + + // Screen Reader + ucl::ElmWidgetSRef m_rmCueAo; + AtspiHighlightHelperSRef m_atspiHelper; }; } diff --git a/inc/presenters/MoreOptionsPresenter.h b/inc/presenters/MoreOptionsPresenter.h index a4f4a13..e1b62dd 100644 --- a/inc/presenters/MoreOptionsPresenter.h +++ b/inc/presenters/MoreOptionsPresenter.h @@ -39,6 +39,7 @@ namespace callui { Builder &setNaviframe(const ucl::NaviframeSRef &navi); Builder &setParentWidget(const ucl::ElmWidgetSRef &parentWidget); MoreOptionsPresenterSRef build(ucl::GuiPresenter &parent) const; + private: ICallManagerSRef m_cm; ISoundManagerSRef m_sm; @@ -50,6 +51,9 @@ namespace callui { ucl::Widget &getWidget(); void update(); + // Screen Reader + ucl::ElmWidget *getCueAo(); + private: friend class ucl::ReffedObj; MoreOptionsPresenter(ucl::IRefCountObj &rc, @@ -61,10 +65,16 @@ namespace callui { ucl::Result prepare(ucl::GuiPresenter &parent, ucl::ElmWidget &parentWidget); + void updateComponents(); + ucl::Result createWidget(ucl::ElmWidget &parent); ucl::Result createPanel(); ucl::Result createPanelLayout(); - ucl::Result createButtons(); + + ucl::Result createSwapButton(); + ucl::Result createUnholdButton(); + ucl::Result createKeypadButton(); + ucl::StyledWidgetSRef createButton(const ucl::ElmStyle &style, const ucl::TString &txt, const ucl::WidgetEventHandler &handler); @@ -84,6 +94,9 @@ namespace callui { void onPanelInactivate(Evas_Object *obj, const char *emission, const char *source); + void onCueClicked(Evas_Object *obj, + const char *emission, + const char *source); ucl::Result startCallDurationTimer(); void stopCallDurationTimer(); @@ -95,6 +108,18 @@ namespace callui { void onPageExitRequest(Page &page); + // Screen Reader + ucl::Result createAccessObjects(); + ucl::Result createCueAo(); + ucl::Result createStatusTxtAo(); + ucl::Result createFakeAo(); + ucl::Result createAtspiHighlightHelper(); + Elm_Interface_Atspi_Accessible *onAtspiHighlight( + Elm_Interface_Atspi_Accessible *ao, + Elm_Atspi_Relation_Type flowRelation); + Eina_Bool onCueAoActionCb(Evas_Object *obj, + Elm_Access_Action_Info *actionInfo); + private: ucl::LayoutSRef m_widget; ucl::StyledWidgetSRef m_panel; @@ -102,16 +127,21 @@ namespace callui { ucl::StyledWidgetSRef m_btnSwap; ucl::StyledWidgetSRef m_btnUnhold; ucl::StyledWidgetSRef m_btnKeypad; - PageWRef m_keypad; + PageWRef m_keypad; ICallManagerSRef m_cm; ISoundManagerSRef m_sm; ucl::NaviframeSRef m_navi; - ICallInfoWCRef m_info; Ecore_Timer *m_timer; struct tm m_duration; + + // Screen Reader + AtspiHighlightHelperSRef m_atspiHelper; + ucl::ElmWidgetSRef m_fakeAo; + ucl::ElmWidgetSRef m_cueAo; + ucl::ElmWidgetSRef m_statusTxtAo; }; } diff --git a/inc/presenters/RejectMsgPresenter.h b/inc/presenters/RejectMsgPresenter.h index 31740fb..d04558a 100644 --- a/inc/presenters/RejectMsgPresenter.h +++ b/inc/presenters/RejectMsgPresenter.h @@ -52,6 +52,7 @@ namespace callui { RejectMsgState getState(); + void showPanel(); void hidePanel(); void setStateHandler(const RejectMsgStateHandler &handler); @@ -75,8 +76,8 @@ namespace callui { ucl::Result createPanelBg(); ucl::Result createPanelLy(); ucl::Result createGenlist(); - ucl::Result fillGenlist(); + ucl::Result fillGenlist(); ucl::Result addGenlistTitleItem(); ucl::Result addGenlistTextItem(const IRejectMsgSRef &rm); ucl::Result addGenlistBottomItem(); @@ -91,6 +92,18 @@ namespace callui { void onBackKey(Evas_Object *obj, void *eventInfo); + // Screen Reader + ucl::Result createAtspiHighlightHelper(); + void registerGenlistAtspiGestureCallbacks(); + Elm_Interface_Atspi_Accessible *getFirstAo(); + Elm_Interface_Atspi_Accessible *getLastAo(); + Eina_Bool onAtspiGesture( + Elm_Atspi_Gesture_Info gestureInfo, + Elm_Interface_Atspi_Accessible *ao); + Elm_Interface_Atspi_Accessible *onAtspiHighlight( + Elm_Interface_Atspi_Accessible *ao, + Elm_Atspi_Relation_Type flowRelation); + // Presenter virtual void onActivate() final override; @@ -109,6 +122,8 @@ namespace callui { RejectMsgSelectHandler m_selectHandler; RejectMsgState m_state; + // Screen Reader + AtspiHighlightHelperSRef m_atspiHelper; }; } diff --git a/inc/presenters/types.h b/inc/presenters/types.h index eea785e..e2dadb9 100644 --- a/inc/presenters/types.h +++ b/inc/presenters/types.h @@ -79,6 +79,8 @@ namespace callui { UCL_DECLARE_REF_ALIASES(DeviceStatePresenter); UCL_DECLARE_REF_ALIASES(MotionSensorPresenter); + UCL_DECLARE_REF_ALIASES(AtspiHighlightHelper); + using AcceptDialogHandler = ucl::WeakDelegate; using RejectMsgStateHandler = ucl::WeakDelegate; using RejectMsgSelectHandler = ucl::WeakDelegate; diff --git a/inc/resources.h b/inc/resources.h index a6983ee..cbc2b05 100644 --- a/inc/resources.h +++ b/inc/resources.h @@ -54,6 +54,27 @@ namespace callui { extern const ucl::TString STR_MORE_UNHOLD; extern const ucl::TString STR_MORE_TRANSFER; extern const ucl::TString STR_MORE_GEAR; + + // Screen Reader + extern const ucl::TString AO_STR_CALL; + extern const ucl::TString AO_STR_VOLUME; + extern const ucl::TString AO_STR_HEADSET; + extern const ucl::TString AO_STR_GEAR_SPK; + extern const ucl::TString AO_STR_MUTE; + extern const ucl::TString AO_STR_MORE_OPTIONS; + extern const ucl::TString AO_STR_END_CALL; + extern const ucl::TString AO_STR_CALLBACK; + extern const ucl::TString AO_STR_ADD_TO_CONTACTS; + extern const ucl::TString AO_STR_ROTATE_BEZEL_TO_ADJUST; + extern const ucl::TString AO_STR_DECREASE_VOLUME; + extern const ucl::TString AO_STR_INCREASE_VOLUME; + extern const ucl::TString AO_STR_ACCEPT_CALL; + extern const ucl::TString AO_STR_SWIPE_RIGHT_WITH_TWO_FINGERS_TO_ACCEPT; + extern const ucl::TString AO_STR_REJECT_CALL; + extern const ucl::TString AO_STR_SWIPE_LEFT_WITH_TWO_FINGERS_TO_REJECT; + extern const ucl::TString AO_STR_DECLINE_MESSAGES; + extern const ucl::TString AO_STR_SWIPE_UP_WITH_TWO_FINGERS_TO_SEND_A_DECLINE_MESSAGE; + } #endif // __CALLUI_RESOURCES_H__ diff --git a/inc/view/AcceptRejectWidget.h b/inc/view/AcceptRejectWidget.h index bd7865b..a304047 100644 --- a/inc/view/AcceptRejectWidget.h +++ b/inc/view/AcceptRejectWidget.h @@ -52,6 +52,10 @@ namespace callui { void deactivateRotary(); void setAcceptBtnType(AcceptButtonType type); + // Screen Reader + ucl::ElmWidget *getAcceptAo(); + ucl::ElmWidget *getRejectAo(); + private: friend class ucl::ReffedObj; AcceptRejectWidget(ucl::IRefCountObj &rc, @@ -125,6 +129,10 @@ namespace callui { void setAcceptUnpressedState(); void setRejectUnpressedState(); + // Screen Reader + ucl::Result registerAccessObjects( + ucl::ElmWidget &widget); + private: ucl::Layout *m_layout; NotiHandler m_accHandler; @@ -173,6 +181,10 @@ namespace callui { int m_rejBCAnimIndex; AcceptButtonType m_acceptBtnType; + + // Screen Reader + ucl::ElmWidgetSRef m_accAo; + ucl::ElmWidgetSRef m_rejAo; }; } diff --git a/inc/view/VolumeControl.h b/inc/view/VolumeControl.h index d8299d4..98fbccb 100644 --- a/inc/view/VolumeControl.h +++ b/inc/view/VolumeControl.h @@ -54,6 +54,11 @@ namespace callui { virtual void setValue(int value) override final; + // Screen Reader + ucl::ElmWidget *getDecreaseBtn(); + ucl::ElmWidget *getIncreaseBtn(); + ucl::ElmWidget *getValueTxtAo(); + private: friend class ucl::ReffedObj; VolumeControl(ucl::IRefCountObj &rc, @@ -70,10 +75,19 @@ namespace callui { void onDecreaseBtnClickedCb(ucl::Widget &widget, void *eventInfo); void onIncreaseBtnClickedCb(ucl::Widget &widget, void *eventInfo); + void onWidgetShowCb(Widget &widget, void *eventInfo); + void onWidgetHideCb(Widget &widget, void *eventInfo); + + // Screen Reader + void registerAccessObjectInformation(); + private: ucl::StyledWidget m_decreaseBtn; ucl::StyledWidget m_increaseBtn; VolumeControlEventHandler m_handler; + + // Screen Reader + ucl::ElmWidgetSRef m_valueTxtAo; }; } diff --git a/inc/view/helpers.h b/inc/view/helpers.h index d0d3395..4ebd3d7 100644 --- a/inc/view/helpers.h +++ b/inc/view/helpers.h @@ -19,23 +19,19 @@ #include -#include "types.h" - -namespace ucl { +#include "ucl/gui/ElmWidget.h" +#include "ucl/gui/Naviframe.h" +#include "ucl/gui/Layout.h" - class ElmWidget; - class Naviframe; -} +#include "types.h" -namespace callui { +namespace callui { namespace utils { ucl::Result createCircleSurface(ucl::Naviframe &navi); Eext_Circle_Surface *getCircleSurface(const ucl::ElmWidget &widget); - void addRotaryEventHandler(Eext_Rotary_Handler_Cb func, void *data); - - void delRotaryEventHandler(Eext_Rotary_Handler_Cb func, void *data); + ucl::ElmWidgetSRef createFakeAccessObject(ucl::ElmWidget &parent); Elm_Genlist_Item_Class createGenlistItemClass(const char *style, Elm_Gen_Item_Text_Get_Cb txtCb = nullptr, @@ -43,7 +39,27 @@ namespace callui { Elm_Gen_Item_State_Get_Cb stateCb = nullptr, Elm_Gen_Item_Del_Cb delCb = nullptr); + ucl::ElmWidgetSRef createAccessObject(ucl::ElmWidget &parent, + ucl::Widget &ly); + + ucl::ElmWidgetSRef createAccessObjectFromLyPart(ucl::ElmWidget &parent, + ucl::Widget &ly, + const ucl::EdjePart &lyPart); + + void destroyAccessObject(ucl::ElmWidget &ao); + +}} + +namespace callui { + + void addRotaryEventHandler(Eext_Rotary_Handler_Cb func, void *data); + + void delRotaryEventHandler(Eext_Rotary_Handler_Cb func, void *data); + ucl::LayoutTheme getImageTheme(const char *fileName); + + Elm_Atspi_Relation_Type getFlowRelation(Elm_Atspi_Gesture_Info gestureInfo); + } #endif // __CALLUI_VIEW_HELPERS_H__ diff --git a/src/presenters/AcceptDialog.cpp b/src/presenters/AcceptDialog.cpp index df1efbf..424ae39 100644 --- a/src/presenters/AcceptDialog.cpp +++ b/src/presenters/AcceptDialog.cpp @@ -54,7 +54,8 @@ namespace callui { { } - AcceptDialog::Builder &AcceptDialog::Builder::setHandler(AcceptDialogHandler handler) + AcceptDialog::Builder & + AcceptDialog::Builder::setHandler(AcceptDialogHandler handler) { m_handler = handler; return *this; @@ -110,11 +111,9 @@ namespace callui { if (!popupEo) { LOG_RETURN(RES_FAIL, "elm_popup_add() failed!"); } - evas_object_size_hint_weight_set(popupEo, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); - m_popup = makeShared(popupEo, true); m_popup->setStyle(style); - + m_popup->setWeight(EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); show(*m_popup); m_popup->addEventHandler(impl::POPUP_DISMISSED, WEAK_DELEGATE( @@ -141,15 +140,17 @@ namespace callui { if (!glEo) { LOG_RETURN(RES_FAIL, "elm_genlist_add() failed!"); } - evas_object_size_hint_weight_set(glEo, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); - evas_object_size_hint_align_set(glEo, EVAS_HINT_FILL, EVAS_HINT_FILL); elm_genlist_mode_set(glEo, ELM_LIST_COMPRESS); elm_genlist_homogeneous_set(glEo, EINA_TRUE); m_genlist = makeShared(glEo); + m_genlist->setWeight(EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); + m_genlist->setAlign(EVAS_HINT_FILL, EVAS_HINT_FILL); - Evas_Object *circleGlEo = eext_circle_object_genlist_add(glEo, getCircleSurface(*m_genlist)); - eext_circle_object_genlist_scroller_policy_set(circleGlEo, ELM_SCROLLER_POLICY_OFF, ELM_SCROLLER_POLICY_AUTO); + Evas_Object *circleGlEo = eext_circle_object_genlist_add(glEo, + utils::getCircleSurface(*m_genlist)); + eext_circle_object_genlist_scroller_policy_set(circleGlEo, + ELM_SCROLLER_POLICY_OFF, ELM_SCROLLER_POLICY_AUTO); eext_rotary_object_event_activated_set(circleGlEo, EINA_TRUE); FAIL_RETURN(fillGenlist(), "fillGenlist() failed!"); @@ -161,7 +162,8 @@ namespace callui { Result AcceptDialog::addGenlistTitleItem() { - static Elm_Genlist_Item_Class titleItc = createGenlistItemClass("title", + static Elm_Genlist_Item_Class titleItc = + utils::createGenlistItemClass("title", [](void *data, Evas_Object *obj, const char *part) -> char * { return strdup(STR_ANSWER_CALL.translate()); }); @@ -180,7 +182,8 @@ namespace callui { Result AcceptDialog::addGenlistTextItem(AcceptDialogEvent event) { - static Elm_Genlist_Item_Class textItc = createGenlistItemClass("1text.1icon", + static Elm_Genlist_Item_Class textItc = + utils::createGenlistItemClass("1text.1icon", [](void *data, Evas_Object *obj, const char *part) -> char * { switch (impl::asEvent(data)) { case AcceptDialogEvent::HOLD_AND_ACCEPT: @@ -206,8 +209,8 @@ namespace callui { Result AcceptDialog::addGenlistBottomItem() { - static Elm_Genlist_Item_Class paddingItc = createGenlistItemClass("1text.1icon"); - + static Elm_Genlist_Item_Class paddingItc = + utils::createGenlistItemClass("1text.1icon"); Elm_Object_Item *item = elm_genlist_item_append(*m_genlist, &paddingItc, nullptr, nullptr, diff --git a/src/presenters/AcceptRejectPresenter.cpp b/src/presenters/AcceptRejectPresenter.cpp index 831637b..19ebff1 100644 --- a/src/presenters/AcceptRejectPresenter.cpp +++ b/src/presenters/AcceptRejectPresenter.cpp @@ -278,5 +278,14 @@ namespace callui { AcceptButtonType::SIMPLE); } + // Screen Reader + ElmWidget *AcceptRejectPresenter::getAcceptAo() + { + return m_widget->getAcceptAo(); + } + ElmWidget *AcceptRejectPresenter::getRejectAo() + { + return m_widget->getRejectAo(); + } } diff --git a/src/presenters/AccessoryPresenter.cpp b/src/presenters/AccessoryPresenter.cpp index 23787f0..1774f0f 100644 --- a/src/presenters/AccessoryPresenter.cpp +++ b/src/presenters/AccessoryPresenter.cpp @@ -51,6 +51,8 @@ namespace callui { namespace { namespace impl { constexpr EdjeSignal SIGNAL_TURN_ON {"turn.on"}; constexpr EdjeSignal SIGNAL_TURN_OFF {"turn.off"}; + + constexpr SmartEvent EVENT_ACCESS_READ_START {"access,read,start"}; }}} namespace callui { @@ -112,7 +114,8 @@ namespace callui { m_vcTimer(nullptr), m_audioState(m_sm->getAudioState()), m_mode(ComponentsMode::UNDEFINED), - m_exitHandler(handler) + m_exitHandler(handler), + m_isVcShowOnRotaryEvent(false) { } @@ -125,13 +128,17 @@ namespace callui { Result AccessoryPresenter::prepare(GuiPresenter &parent, ElmWidget &parentWidget, const ICallManagerSRef &cm) { - FAIL_RETURN(GuiPresenter::prepare(parent), "Presenter::prepare() failed"); + FAIL_RETURN(GuiPresenter::prepare(parent), + "Presenter::prepare() failed"); - FAIL_RETURN(createWidget(parentWidget), "createWidget() failed"); + FAIL_RETURN(createWidget(parentWidget), + "createWidget() failed"); - FAIL_RETURN(createSlider(), "createSlider() failed"); + FAIL_RETURN(createSlider(), + "createSlider() failed"); - FAIL_RETURN(createVolumeControl(), "createVolumeControl() failed"); + FAIL_RETURN(createVolumeControl(), + "createVolumeControl() failed"); updateVolume(m_sm->getVolume()); @@ -139,7 +146,8 @@ namespace callui { updateMode(cm); - FAIL_RETURN(updateModeRelativeComponents(cm), "updateComponents() failed"); + FAIL_RETURN(updateModeRelativeComponents(cm), + "updateComponents() failed"); return RES_OK; } @@ -179,13 +187,10 @@ namespace callui { Result AccessoryPresenter::update(const ICallManagerSRef &cm) { - auto curMode = getCurrentMode(cm); - if (m_mode == curMode) { - LOG_RETURN(RES_OK, "Mode is the same. No need to update"); - } - m_mode = curMode; + updateMode(cm); - FAIL_RETURN(updateModeRelativeComponents(cm), "updateComponents() failed"); + FAIL_RETURN(updateModeRelativeComponents(cm), + "updateModeRelativeComponents() failed"); return RES_OK; } @@ -315,6 +320,8 @@ namespace callui { m_vc->resize(w, h); hide(*m_vc); + registerVolumeControlAo(); + return RES_OK; } @@ -332,6 +339,10 @@ namespace callui { asWeak(*this))); show(*m_volumeBtn); + // Screen Reader + elm_atspi_accessible_translation_domain_set(*m_volumeBtn, PACKAGE); + elm_atspi_accessible_name_set(*m_volumeBtn, AO_STR_VOLUME); + return RES_OK; } @@ -349,6 +360,10 @@ namespace callui { show(*m_muteBtn); + // Screen Reader + elm_atspi_accessible_translation_domain_set(*m_muteBtn, PACKAGE); + elm_atspi_accessible_name_set(*m_muteBtn, AO_STR_MUTE); + return RES_OK; } @@ -366,9 +381,20 @@ namespace callui { show(*m_bluetoothBtn); - (m_audioState == AudioStateType::BT) ? - m_bluetoothBtn->emit(impl::SIGNAL_TURN_ON) : - m_bluetoothBtn->emit(impl::SIGNAL_TURN_OFF); + // Screen Reader + elm_atspi_accessible_translation_domain_set(*m_bluetoothBtn, PACKAGE); + if (m_audioState == AudioStateType::BT) { + m_bluetoothBtn->emit(impl::SIGNAL_TURN_ON); + // Screen Reader + elm_atspi_accessible_name_set(*m_bluetoothBtn, + AO_STR_GEAR_SPK); + } else { + m_bluetoothBtn->emit(impl::SIGNAL_TURN_OFF); + // Screen Reader + elm_atspi_accessible_name_set(*m_bluetoothBtn, + AO_STR_HEADSET); + } + if (!m_sm->isBTSupported()) { disable(*m_bluetoothBtn); } @@ -389,6 +415,11 @@ namespace callui { asWeak(*this))); show(*m_addContactBtn); + // Screen Reader + elm_atspi_accessible_translation_domain_set(*m_addContactBtn, PACKAGE); + elm_atspi_accessible_name_set(*m_addContactBtn, + AO_STR_ADD_TO_CONTACTS); + return RES_OK; } @@ -401,6 +432,8 @@ namespace callui { show(*m_vc); startVCTimer(); + + elm_atspi_component_highlight_grab(*m_vc); } void AccessoryPresenter::onMuteBtnClicked(Widget &widget, void *eventInfo) @@ -531,7 +564,12 @@ namespace callui { { stopVCTimer(); - m_vcTimer = ecore_timer_add(CALL_VC_TIMER_INTERVAL, + auto timerInterval = CALL_VC_TIMER_INTERVAL; + if (elm_atspi_bridge_utils_is_screen_reader_enabled()) { + timerInterval = CALL_VC_SCREEN_READER_TIMER_INTERVAL; + } + + m_vcTimer = ecore_timer_add(timerInterval, CALLBACK_B(AccessoryPresenter::onVCTimerCb), this); } @@ -554,7 +592,8 @@ namespace callui { Eina_Bool AccessoryPresenter::onRotaryEvent(Eext_Rotary_Event_Info *info) { if (!isActive()) { - LOG_RETURN_VALUE(RES_OK, EINA_TRUE, "Presenter is not active. Ignore"); + LOG_RETURN_VALUE(RES_OK, EINA_TRUE, + "Presenter is not active. Ignore"); } if (m_vcTimer) { @@ -562,6 +601,15 @@ namespace callui { } else { show(*m_vc); startVCTimer(); + m_isVcShowOnRotaryEvent = true; + elm_atspi_component_highlight_grab(*m_vc); + } + + if (m_isVcShowOnRotaryEvent) { + m_isVcShowOnRotaryEvent = checkPossibilityToModifyVolume( + m_sm->getVolume(), + info->direction == + EEXT_ROTARY_DIRECTION_CLOCKWISE); } if (info->direction == EEXT_ROTARY_DIRECTION_CLOCKWISE) { @@ -598,22 +646,32 @@ namespace callui { } } + bool AccessoryPresenter::checkPossibilityToModifyVolume(int volume, bool needIncrease) + { + if (needIncrease) { + auto max = m_sm->getMaxVolume(); + return (max >= volume); + } else { + return (volume - 1 >= VOLUME_LEVEL_MIN); + } + return false; + } + void AccessoryPresenter::tryIncreaseVolume() { - auto max = m_sm->getMaxVolume(); auto cur = m_sm->getVolume(); - - if (max != cur) { - m_sm->setVolume(cur + 1); + if (checkPossibilityToModifyVolume(cur, true)) { + FAIL_RETURN_VOID(m_sm->setVolume(cur + 1), + "setVolume() failed"); } } void AccessoryPresenter::tryDecreaseVolume() { auto cur = m_sm->getVolume(); - - if (cur - 1 >= VOLUME_LEVEL_MIN) { - m_sm->setVolume(cur - 1); + if (checkPossibilityToModifyVolume(cur, false)) { + FAIL_RETURN_VOID(m_sm->setVolume(cur - 1), + "setVolume() failed"); } } @@ -635,9 +693,18 @@ namespace callui { updateVolume(m_sm->getVolume()); if (m_bluetoothBtn) { - (m_audioState == AudioStateType::BT) ? - m_bluetoothBtn->emit(impl::SIGNAL_TURN_ON) : - m_bluetoothBtn->emit(impl::SIGNAL_TURN_OFF); + + if (m_audioState == AudioStateType::BT) { + m_bluetoothBtn->emit(impl::SIGNAL_TURN_ON); + // Screen Reader + elm_atspi_accessible_name_set(*m_bluetoothBtn, + AO_STR_GEAR_SPK); + } else { + m_bluetoothBtn->emit(impl::SIGNAL_TURN_OFF); + // Screen Reader + elm_atspi_accessible_name_set(*m_bluetoothBtn, + AO_STR_HEADSET); + } } } } @@ -660,6 +727,15 @@ namespace callui { m_vc->setIncreaseBtnEnable(true); m_vc->setDecreaseBtnEnable(true); } + + // Screen Reader + if (m_vc->isVisible()) { + if (!m_isVcShowOnRotaryEvent) { + elm_atspi_bridge_utils_say(std::to_string(cur).c_str(), + EINA_FALSE, nullptr, nullptr); + } + m_isVcShowOnRotaryEvent = false; + } } void AccessoryPresenter::onVolumeLevelChanged(int value) @@ -684,5 +760,73 @@ namespace callui { isMuted ? m_muteBtn->emit(impl::SIGNAL_TURN_ON) : m_muteBtn->emit(impl::SIGNAL_TURN_OFF); } + + // Screen Reader + ElmWidget *AccessoryPresenter::getVolumBtn() + { + return m_volumeBtn.get(); + } + + ElmWidget *AccessoryPresenter::getBluetoothBtn() + { + return m_bluetoothBtn.get(); + } + + ElmWidget *AccessoryPresenter::getMuteBtn() + { + return m_muteBtn.get(); + } + + ElmWidget *AccessoryPresenter::getAddContactBtn() + { + return m_addContactBtn.get(); + } + + ElmWidget *AccessoryPresenter::getVolumeControlLy() + { + return m_vc.get(); + } + + ElmWidget *AccessoryPresenter::getVolumeControlDecreaseBtn() + { + return m_vc->getDecreaseBtn(); + } + + ElmWidget *AccessoryPresenter::getVolumeControlIncreaseBtn() + { + return m_vc->getIncreaseBtn(); + } + + ElmWidget *AccessoryPresenter::getVolumeControlValueTxtAo() + { + return m_vc->getValueTxtAo(); + } + + void AccessoryPresenter::registerVolumeControlAo() + { + auto decrBtn = m_vc->getDecreaseBtn(); + if (decrBtn) { + decrBtn->addEventHandler(impl::EVENT_ACCESS_READ_START, + WEAK_DELEGATE(AccessoryPresenter:: + onVolumeControlScreenReaderReadStart, + asWeak(*this))); + } + + auto incrBtn = m_vc->getIncreaseBtn(); + if (incrBtn) { + incrBtn->addEventHandler(impl::EVENT_ACCESS_READ_START, + WEAK_DELEGATE(AccessoryPresenter:: + onVolumeControlScreenReaderReadStart, + asWeak(*this))); + } + } + + void AccessoryPresenter::onVolumeControlScreenReaderReadStart( + Widget &widget, + void *eventInfo) + { + restartVCTimer(); + } + } diff --git a/src/presenters/AtspiHighlightHelper.cpp b/src/presenters/AtspiHighlightHelper.cpp new file mode 100644 index 0000000..b44c679 --- /dev/null +++ b/src/presenters/AtspiHighlightHelper.cpp @@ -0,0 +1,167 @@ +/* + * Copyright 2017 Samsung Electronics Co., Ltd + * + * Licensed under the Flora License, Version 1.1 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://floralicense.org/license/ + * + * 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 "presenters/AtspiHighlightHelper.h" + +#include "common.h" + +namespace callui { namespace { namespace impl { + + constexpr EoDataKey ATSPI_HELPER_DATA {"callui,atspi,highlight,helper"}; +}}} + +namespace callui { + + using ucl::AtspiGestureEventInfo; + + using ucl::ATSPI_ON_GESTURE; + + AtspiHighlightHelperSRef AtspiHighlightHelper::newInstance( + GuiPresenter &parent, ElmWidget &rootWidget) + { + auto result = makeShared(); + + FAIL_RETURN_VALUE(result->prepare(parent, rootWidget), {}, + "result->prepare() failed!"); + + return result; + } + + AtspiHighlightHelper::AtspiHighlightHelper(IRefCountObj &rc) : + GuiPresenter(rc) + { + } + + AtspiHighlightHelper::~AtspiHighlightHelper() + { + } + + Result AtspiHighlightHelper::prepare(GuiPresenter &parent, + ElmWidget &rootWidget) + { + FAIL_RETURN(GuiPresenter::prepare(parent), + "GuiPresenter::prepare() failed!"); + + registerWidget(rootWidget); + + return RES_OK; + } + + void AtspiHighlightHelper::setRelationEventHandler(RelationEventHandler handler) + { + m_relationEventHandler = handler; + } + + void AtspiHighlightHelper::setGestureEventHandler(GestureEventHandler handler) + { + m_gestureEventHandler = handler; + } + + void AtspiHighlightHelper::registerWidget(ElmWidget &widget) + { + DLOG("this [%p] widget [%p]", this, widget.getEo()); + + widget.addEventHandler(ATSPI_ON_GESTURE, WEAK_DELEGATE( + AtspiHighlightHelper::onAtspiGesture, asWeak(*this))); + } + + void AtspiHighlightHelper::handleAtspiGesture( + Elm_Interface_Atspi_Accessible *ao, + AtspiGestureEventInfo &e) + { + DLOG("this [%p] ao [%p]", this, ao); + + if (e.stopPropagation) { + DLOG("e.stopPropagation"); + return; + } + + if (!isActive()) { + DLOG("!isActive()"); + if (e.gestureInfo.type != ELM_ATSPI_GESTURE_ONE_FINGER_SINGLE_TAP) { + e.preventDefault = true; + } + return; + } + + e.stopPropagation = true; + + if (m_gestureEventHandler) { + DLOG("m_gestureEventHandler"); + if (m_gestureEventHandler(ao, e.gestureInfo.type)) { + e.preventDefault = true; + return; + } + } + + e.preventDefault = false; + + if (!m_relationEventHandler) { + DLOG("!m_relationEventHandler"); + return; + } + + const Elm_Atspi_Relation_Type relation = getFlowRelation(e.gestureInfo); + if (relation == ELM_ATSPI_RELATION_NULL) { + return; + } + + const auto relationObj = m_relationEventHandler(ao, relation); + if (!relationObj) { + return; + } + + auto &win = getWindow(); + auto atspiHelper = static_cast + (win.getData(impl::ATSPI_HELPER_DATA)); + + if (!atspiHelper) { + const auto obj = utils::createFakeAccessObject(win); + if (!obj) { + LOG_RETURN_VOID(RES_FAIL, "createFakeAccessObject() failed!"); + } + obj->setIsOwner(false); + atspiHelper = obj->getEo(); + win.setData(impl::ATSPI_HELPER_DATA, atspiHelper); + } + + if (ao == win) { + ao = atspiHelper; + elm_atspi_component_highlight_grab(ao); + } + + elm_atspi_accessible_relationships_clear(ao); + elm_atspi_accessible_relationship_append(ao, relation, relationObj); + } + + void AtspiHighlightHelper::onAtspiGesture( + Widget &widget, void *eventInfo) + { + handleAtspiGesture(widget, + *static_cast(eventInfo)); + } + + bool AtspiHighlightHelper::handleGesture(Elm_Interface_Atspi_Accessible *ao, + const Elm_Atspi_Gesture_Info &info) + { + AtspiGestureEventInfo eventInfo {}; + eventInfo.gestureInfo = info; + + handleAtspiGesture(ao, eventInfo); + + return eventInfo.preventDefault; + } +} diff --git a/src/presenters/CallInfoPresenter.cpp b/src/presenters/CallInfoPresenter.cpp index 9f787da..2a73963 100644 --- a/src/presenters/CallInfoPresenter.cpp +++ b/src/presenters/CallInfoPresenter.cpp @@ -78,6 +78,9 @@ namespace callui { namespace { namespace impl { "%s" ""}; + constexpr EdjePart PART_AO_MAIN_TXT {"ao_text_1line"}; + constexpr EdjePart PART_AO_SUB_TXT {"ao_text_2line"}; + int getTextWidth(ElmWidget &parent, const char *fontStyle, int fontSize, const std::string &text) { @@ -356,6 +359,8 @@ namespace callui { m_widget->setContent(*m_label, impl::PART_SWL_2LINE); + elm_atspi_accessible_can_highlight_set(*m_label, EINA_FALSE); + return RES_OK; } @@ -452,6 +457,9 @@ namespace callui { m_widget->emit(impl::SIGN_DEFAULT, impl::SRC_TXT_2LINE); } + FAIL_RETURN(setSubTxtAccessObject(subTxt), + "setSubTxtAccessObject() failed!"); + return RES_OK; } @@ -490,10 +498,13 @@ namespace callui { } void CallInfoPresenter::displayMainTxt(const ICallInfoSCRef &info, - const std::string &text) const + const std::string &text) { m_widget->setText(text.c_str(), impl::PART_TXT_MAIN); + FAIL_RETURN_VOID(setMainTxtAccessObject(text), + "setMainTxtAccessObject() failed!"); + if (m_mode == CallMode::INCOMING) { // Font size and color if (m_callerId) { @@ -646,4 +657,61 @@ namespace callui { return RES_OK; } + // Screen Reader + ElmWidget *CallInfoPresenter::getMainTxtAo() + { + return m_mainTxtAO.get(); + } + + ElmWidget *CallInfoPresenter::getSubTxtAo() + { + return m_subTxtAO.get(); + } + + ElmWidget *CallInfoPresenter::getStatusTxtAo() + { + return m_callStatus->getStatusTextAo(); + } + + Result CallInfoPresenter::setMainTxtAccessObject(const std::string &text) + { + if (!m_mainTxtAO) { + m_mainTxtAO = utils::createAccessObjectFromLyPart(*m_widget, + *m_widget, + impl::PART_AO_MAIN_TXT); + if (!m_mainTxtAO) { + LOG_RETURN(RES_FAIL, "createAccessObjectFromLyPart() failed!"); + } + + } + elm_atspi_accessible_reading_info_type_set(*m_mainTxtAO, + ELM_ACCESSIBLE_READING_INFO_TYPE_NAME); + elm_atspi_accessible_name_set(*m_mainTxtAO, text.c_str()); + + return RES_OK; + } + + Result CallInfoPresenter::setSubTxtAccessObject(const std::string &text) + { + if (!text.empty()) { + if (!m_subTxtAO) { + m_subTxtAO = utils::createAccessObjectFromLyPart(*m_widget, + *m_widget, + impl::PART_AO_SUB_TXT); + if (!m_subTxtAO) { + LOG_RETURN(RES_FAIL, "createAccessObjectFromLyPart() failed!"); + } + } + + elm_atspi_accessible_reading_info_type_set(*m_subTxtAO, + ELM_ACCESSIBLE_READING_INFO_TYPE_NAME); + elm_atspi_accessible_name_set(*m_subTxtAO, text.c_str()); + + } else if (m_subTxtAO) { + utils::destroyAccessObject(*m_subTxtAO); + m_subTxtAO.reset(); + } + + return RES_OK; + } } diff --git a/src/presenters/CallStatusPresenter.cpp b/src/presenters/CallStatusPresenter.cpp index 61e6d6e..d613e9c 100644 --- a/src/presenters/CallStatusPresenter.cpp +++ b/src/presenters/CallStatusPresenter.cpp @@ -35,6 +35,8 @@ namespace callui { namespace { namespace impl { constexpr EdjeSignal SIGN_DOT_LTR {"default:LTR"}; constexpr EdjeSignal SIGN_RESET {"reset"}; + constexpr EdjePart PART_AO_STATUS {"ao_text_info"}; + constexpr EdjeSignalSrc SIGN_SRC_DOT {"dot"}; }}} @@ -129,6 +131,7 @@ namespace callui { m_ly->emit(impl::SIGN_RESET, impl::SIGN_SRC_DOT); m_ly->setText("", impl::PART_TXT_TEXT_INFO); + createStatusTxtAo(); switch (m_mode) { case CallMode::INCOMING: return processIncomingMode(); @@ -190,7 +193,8 @@ namespace callui { if (const auto info = m_info.lock()) { m_duration = info->getDuration(); } - setCallDuration(m_duration, *m_ly, impl::PART_TXT_TEXT_INFO); + m_ly->setText(getCallDuration(m_duration), + impl::PART_TXT_TEXT_INFO); if (m_timer) { ecore_timer_del(m_timer); @@ -211,7 +215,8 @@ namespace callui { Eina_Bool CallStatusPresenter::onBlinkingTimerCb() { if ((m_blinkCount % 2) == 0) { - setCallDuration(m_duration, *m_ly, impl::PART_TXT_TEXT_INFO); + m_ly->setText(getCallDuration(m_duration), + impl::PART_TXT_TEXT_INFO); } else if ((m_blinkCount % 2) == 1) { m_ly->setText("", impl::PART_TXT_TEXT_INFO); } @@ -246,4 +251,38 @@ namespace callui { return RES_OK; } + // Screen Reader + Result CallStatusPresenter::createStatusTxtAo() + { + if (!m_statusTxtAo) { + m_statusTxtAo = utils::createAccessObjectFromLyPart(*m_ly, + *m_ly, + impl::PART_AO_STATUS); + if (!m_statusTxtAo) { + LOG_RETURN(RES_FAIL, "createAccessObjectFromLyPart() failed!"); + } + + elm_atspi_accessible_reading_info_type_set(*m_statusTxtAo, + ELM_ACCESSIBLE_READING_INFO_TYPE_NAME); + elm_atspi_accessible_name_cb_set(*m_statusTxtAo, + [](void *data, Evas_Object *obj) -> char * + { + auto self = static_cast(data); + if (!self) { + return nullptr; + } + auto txt = self->m_ly-> + getText(impl::PART_TXT_TEXT_INFO).getCStr(); + return (txt) ? strdup(txt) : nullptr; + }, + this); + } + return RES_OK; + } + + ElmWidget *CallStatusPresenter::getStatusTextAo() + { + return m_statusTxtAo.get(); + } + } diff --git a/src/presenters/Instance.cpp b/src/presenters/Instance.cpp index e77f8c4..accfc96 100644 --- a/src/presenters/Instance.cpp +++ b/src/presenters/Instance.cpp @@ -93,8 +93,10 @@ namespace callui { } m_win->getConformant().setContent(*m_navi); + elm_atspi_accessible_translation_domain_set(*m_win, PACKAGE); + elm_atspi_accessible_name_set(*m_win, AO_STR_CALL); - FAIL_RETURN(createCircleSurface(*m_navi), + FAIL_RETURN(utils::createCircleSurface(*m_navi), "createCircleSurface() failed!"); m_sysEventProvider.addEventHandler( diff --git a/src/presenters/MainPage.cpp b/src/presenters/MainPage.cpp index 1a491b4..689ddeb 100644 --- a/src/presenters/MainPage.cpp +++ b/src/presenters/MainPage.cpp @@ -37,6 +37,8 @@ #include "presenters/MoreOptionsPresenter.h" #include "presenters/DeviceStatePresenter.h" +#include "presenters/AtspiHighlightHelper.h" + #include "resources.h" #include "common.h" @@ -61,8 +63,9 @@ namespace callui { namespace { namespace impl { constexpr EdjePart PART_SWL_OVERLAY {"swl.overlay"}; constexpr EdjePart PART_SWL_MORE_OPTION {"swl.more_option"}; - constexpr EdjePart PART_TXT_REJECT_MSG {"reject_msg_text"}; + constexpr EdjePart PART_TXT_REJECT_MSG_CUE_AO {"ao_cue"}; + constexpr EdjePart PART_TXT_REJECT_MSG {"reject_msg_text"}; constexpr ElmStyle STYLE_BB_END_CALL {"callui/end_call"}; constexpr ElmStyle STYLE_BB_RECALL {"callui/call_back"}; }}} @@ -204,8 +207,6 @@ namespace callui { Result MainPage::processIncomingCallMode() { - m_bottomBtn.reset(); - m_moreOptionsPrs.reset(); m_accessoryPrs.reset(); auto call = m_cm->getIncomingCall(); @@ -223,19 +224,16 @@ namespace callui { if (!isUnknownCaller(*call->getInfo()) && (provider && provider->getMsgCount() > 0)) { - m_rmLy = Layout::Builder(). - setTheme(impl::LAYOUT_REJECT_MSG_WIDGET). - setIsOwner(true). - build(*m_widget); - if (!m_rmLy) { - LOG_RETURN(RES_FAIL, "Layout::build() failed!"); - } - m_rmLy->setText(STR_DECLINE_MESSAGES, impl::PART_TXT_REJECT_MSG); - - m_widget->setContent(*m_rmLy, impl::PART_SWL_REJECT_MSG); FAIL_RETURN(createRejectMsgPresenter(provider), "createRejectMsgPresenter() failed!"); + + FAIL_RETURN(createRejectMsgCue(), + "craeteRejectMsgCue() failed!"); + + if (createRejectMsgCueAo() != RES_OK) { + ELOG("createRejectMsgCueAo() failed!"); + } } if (m_indicator) { @@ -245,15 +243,24 @@ namespace callui { return RES_OK; } - Result MainPage::processEndCallMode() + Result MainPage::createRejectMsgCue() { - m_bottomBtn.reset(); - m_moreOptionsPrs.reset(); + m_rmLy = Layout::Builder(). + setTheme(impl::LAYOUT_REJECT_MSG_WIDGET). + setIsOwner(true). + build(*m_widget); + if (!m_rmLy) { + LOG_RETURN(RES_FAIL, "Layout::build() failed!"); + } - m_rmPrs.reset(); - m_acceptRejectPrs.reset(); - m_rmLy.reset(); + m_rmLy->setText(STR_DECLINE_MESSAGES, impl::PART_TXT_REJECT_MSG); + m_widget->setContent(*m_rmLy, impl::PART_SWL_REJECT_MSG); + return RES_OK; + } + + Result MainPage::processEndCallMode() + { if (m_indicator) { m_indicator->udapteIncomingCallMode(false); } @@ -261,6 +268,19 @@ namespace callui { FAIL_RETURN(createAccessoryPresenter(), "createAccessoryPresenter() failed"); + + auto end = m_cm->getEndCall(); + if (end) { + auto info = end->getInfo(); + if (info && info->getConferenceMemberCount() == 1) { + FAIL_RETURN(createBottomBtn(impl::STYLE_BB_RECALL, false), + "createBottomBtn() failed"); + hide(*m_bottomBtn); + } + } else { + ELOG("End call is NULL!"); + } + startEndCallTimer(); return RES_OK; @@ -271,6 +291,7 @@ namespace callui { m_rmPrs.reset(); m_acceptRejectPrs.reset(); m_rmLy.reset(); + m_rmCueAo.reset(); if (m_indicator) { m_indicator->udapteIncomingCallMode(false); @@ -363,7 +384,7 @@ namespace callui { } } - Result MainPage::createBottomBtn(const ElmStyle &style) + Result MainPage::createBottomBtn(const ElmStyle &style, bool setVisible) { m_bottomBtn = makeShared( elm_button_add(*m_widget), true); @@ -373,37 +394,42 @@ namespace callui { WEAK_DELEGATE(MainPage::onBottomBtnClicked, asWeak(*this))); - m_widget->setContent(*m_bottomBtn, impl::PART_SWL_BOTTOM_BTN); - show(*m_bottomBtn); + elm_atspi_accessible_translation_domain_set(*m_bottomBtn, PACKAGE); + if (style == impl::STYLE_BB_RECALL) { + elm_atspi_accessible_name_set(*m_bottomBtn, AO_STR_CALLBACK); + } else { + elm_atspi_accessible_name_set(*m_bottomBtn, AO_STR_END_CALL); + } + + if (setVisible) { + m_widget->setContent(*m_bottomBtn, impl::PART_SWL_BOTTOM_BTN); + show(*m_bottomBtn); + } else { + hide(*m_bottomBtn); + } return RES_OK; } Eina_Bool MainPage::onEndCallTimerCb() { - auto end = m_cm->getEndCall(); - if (!end) { - m_ecTimer = nullptr; - requestExit(); - LOG_RETURN_VALUE(RES_FAIL, ECORE_CALLBACK_CANCEL, "end is NULL"); - } - - auto info = end->getInfo(); - if (!m_bottomBtn && !m_ecTimerBtnReq) { - if (info && info->getConferenceMemberCount() == 1) { - if (createBottomBtn(impl::STYLE_BB_RECALL) != RES_OK) { - m_ecTimer = nullptr; - requestExit(); - LOG_RETURN_VALUE(RES_FAIL, ECORE_CALLBACK_CANCEL, - "createBottomBtn() failed!"); - } + if (!m_ecTimerBtnReq) { + if (!m_bottomBtn) { + m_ecTimer = nullptr; + requestExit(); + LOG_RETURN_VALUE(RES_FAIL, ECORE_CALLBACK_CANCEL, "bottom button is NULL!"); } + m_widget->setContent(*m_bottomBtn, impl::PART_SWL_BOTTOM_BTN); + show(*m_bottomBtn); + ecore_timer_interval_set(m_ecTimer, impl::CU_EXIT_APP_TIMEOUT); m_ecTimerBtnReq = true; + return ECORE_CALLBACK_RENEW; } else { m_ecTimer = nullptr; requestExit(); + return ECORE_CALLBACK_CANCEL; } } @@ -424,6 +450,15 @@ namespace callui { FAIL_RETURN_VOID(showWindow(), "showWindow failed!"); + m_acceptRejectPrs.reset(); + m_rmPrs.reset(); + m_moreOptionsPrs.reset(); + m_callInfoPrs.reset(); + + m_rmLy.reset(); + m_rmCueAo.reset(); + m_bottomBtn.reset(); + switch (m_mode) { case CallMode::INCOMING: FAIL_RETURN_VOID(processIncomingCallMode(), @@ -443,7 +478,9 @@ namespace callui { "createCallInfoPresenter() failed!"); FAIL_RETURN_VOID(updateDeviceState(prevMode, m_mode), - "createCallInfoPresenter() failed!"); + "updateDeviceState() failed!"); + + createAtspiHighlightHelper(); } Result MainPage::updateDeviceState(CallMode prevMode, CallMode curMode) @@ -464,13 +501,6 @@ namespace callui { return RES_OK; } - bool MainPage::detectMuteControlDisableState() - { - return (m_mode == CallMode::OUTGOING || - (m_mode == CallMode::DURING && - (m_cm->getAvailableCalls() == CALL_FLAG_HELD))); - } - Result MainPage::createWidget() { m_widget = Layout::Builder(). @@ -499,7 +529,8 @@ namespace callui { LOG_RETURN(RES_FAIL, "Indicator::build() failed!"); } - m_widget->setContent(m_indicator->getWidget(), impl::PART_SWL_INDICATOR); + m_widget->setContent(m_indicator->getWidget(), + impl::PART_SWL_INDICATOR); return RES_OK; } @@ -607,8 +638,13 @@ namespace callui { void MainPage::RejectMsgStateCb(RejectMsgState state) { - (state == RejectMsgState::HIDDEN) ? - show(*m_rmLy) : hide(*m_rmLy); + if (state == RejectMsgState::HIDDEN) { + show(*m_rmLy); + show(*m_rmCueAo); + } else { + hide(*m_rmLy); + hide(*m_rmCueAo); + } } void MainPage::RejectMsgSelectCb(const IRejectMsgSRef &rm) @@ -636,7 +672,8 @@ namespace callui { "RejectMessagePresenter::build() failed!"); } - m_widget->setContent(m_rmPrs->getWidget(), impl::PART_SWL_OVERLAY); + m_widget->setContent(m_rmPrs->getWidget(), + impl::PART_SWL_OVERLAY); return RES_OK; } @@ -669,4 +706,458 @@ namespace callui { } } + // Screen Reader + Result MainPage::createRejectMsgCueAo() + { + m_rmCueAo = utils::createAccessObjectFromLyPart(*m_widget, + *m_rmLy, + impl::PART_TXT_REJECT_MSG_CUE_AO); + if (!m_rmCueAo) { + LOG_RETURN(RES_FAIL, "createAccessObjectFromLyPart() failed!"); + } + elm_atspi_accessible_translation_domain_set(*m_rmCueAo, PACKAGE); + elm_atspi_accessible_reading_info_type_set(*m_rmCueAo, + ELM_ACCESSIBLE_READING_INFO_TYPE_NAME | + ELM_ACCESSIBLE_READING_INFO_TYPE_DESCRIPTION); + elm_atspi_accessible_name_set(*m_rmCueAo, AO_STR_DECLINE_MESSAGES); + elm_atspi_accessible_description_set(*m_rmCueAo, + AO_STR_SWIPE_UP_WITH_TWO_FINGERS_TO_SEND_A_DECLINE_MESSAGE); + + elm_atspi_accessible_gesture_cb_set(*m_rmCueAo, + [](void *data, Elm_Atspi_Gesture_Info gesture, Evas_Object *obj) { + // TODO: ELM_ATSPI_GESTURE_TWO_FINGERS_HOVER must be replaced + if (gesture.type == ELM_ATSPI_GESTURE_TWO_FINGERS_HOVER) { + auto page = (MainPage *)data; + page->m_rmPrs->showPanel(); + return EINA_TRUE; + } + + return EINA_FALSE; + }, this); + + return RES_OK; + } + + Result MainPage::createAtspiHighlightHelper() + { + m_atspiHelper = AtspiHighlightHelper::newInstance(*this, getWindow()); + if (!m_atspiHelper) { + LOG_RETURN(RES_FAIL, + "AtspiHighlightHelper::newInstance() failed!"); + } + + switch (m_mode) { + case CallMode::INCOMING: + registerIncomingCallModeAo(); + break; + case CallMode::OUTGOING: + case CallMode::DURING: + registerActiveCallModeAo(); + break; + case CallMode::END: + registerEndCallModeAo(); + default: + break; + } + return RES_OK; + } + + void MainPage::registerIncomingCallModeAo() + { + DLOG("ENTER"); + m_atspiHelper->setRelationEventHandler(WEAK_DELEGATE( + MainPage::onIncomingCallModeAtspiHighlight, asWeak(*this))); + + m_atspiHelper->setGestureEventHandler(WEAK_DELEGATE( + MainPage::onIncomingModeAtspiGesture, asWeak(*this))); + + auto acceptAO = m_acceptRejectPrs->getAcceptAo(); + if (!acceptAO) { + DLOG("acceptAO is NULL"); + } else { + m_atspiHelper->registerWidget(*acceptAO); + } + + auto rejectAo = m_acceptRejectPrs->getRejectAo(); + if (!rejectAo) { + DLOG("rejectAo is NULL"); + } else { + m_atspiHelper->registerWidget(*rejectAo); + } + + auto statusTxtAo = m_callInfoPrs->getStatusTxtAo(); + if (!statusTxtAo) { + DLOG("statusTxtAo is NULL"); + } else { + m_atspiHelper->registerWidget(*statusTxtAo); + } + + auto mainTxtAo = m_callInfoPrs->getMainTxtAo(); + if (!mainTxtAo) { + DLOG("mainTxtAo is NULL"); + } else { + m_atspiHelper->registerWidget(*mainTxtAo); + } + + auto subTxtAo = m_callInfoPrs->getSubTxtAo(); + if (!subTxtAo) { + DLOG("subTxtAo is NULL"); + } else { + m_atspiHelper->registerWidget(*subTxtAo); + } + + if (!m_rmCueAo) { + DLOG("m_rmCueAo is NULL"); + } else { + m_atspiHelper->registerWidget(*m_rmCueAo); + } + + DLOG("EXIT"); + } + + bool MainPage::onIncomingModeAtspiGesture(Elm_Interface_Atspi_Accessible *widget, + Elm_Atspi_Gesture_Type gestureType) + { + DLOG(" GESTURE TYPE [%d]", gestureType); + + if (m_rmCueAo && widget == *m_rmCueAo) { + if (gestureType == ELM_ATSPI_GESTURE_TWO_FINGERS_HOVER) { + m_rmPrs->showPanel(); + return true; + } + } + return false; + } + + Elm_Interface_Atspi_Accessible *MainPage::onIncomingCallModeAtspiHighlight( + Elm_Interface_Atspi_Accessible *ao, + Elm_Atspi_Relation_Type flowRelation) + { + DLOG("FlowRelation [%s]", + flowRelation == ELM_ATSPI_RELATION_FLOWS_FROM ? + "FROM" : "TO"); + + auto acceptAo = m_acceptRejectPrs->getAcceptAo(); + auto rejectAo = m_acceptRejectPrs->getRejectAo(); + auto statusTxtAo = m_callInfoPrs->getStatusTxtAo(); + auto mainTxtAo = m_callInfoPrs->getMainTxtAo(); + auto subTxtAo = m_callInfoPrs->getSubTxtAo(); + + if (ao == *acceptAo) { + if (flowRelation == ELM_ATSPI_RELATION_FLOWS_TO) { + return *statusTxtAo; + } + } else if (ao == *statusTxtAo) { + if (flowRelation == ELM_ATSPI_RELATION_FLOWS_TO) { + return *mainTxtAo; + } else if (flowRelation == ELM_ATSPI_RELATION_FLOWS_FROM) { + return *acceptAo; + } + } else if (ao == *mainTxtAo) { + if (flowRelation == ELM_ATSPI_RELATION_FLOWS_TO) { + return (subTxtAo) ? *subTxtAo : *rejectAo; + } else if (flowRelation == ELM_ATSPI_RELATION_FLOWS_FROM) { + return *statusTxtAo; + } + } else if (subTxtAo && ao == *subTxtAo) { + if (flowRelation == ELM_ATSPI_RELATION_FLOWS_TO) { + return *rejectAo; + } else if (flowRelation == ELM_ATSPI_RELATION_FLOWS_FROM) { + return *mainTxtAo; + } + } else if (ao == *rejectAo) { + if (flowRelation == ELM_ATSPI_RELATION_FLOWS_TO) { + return m_rmCueAo ? *m_rmCueAo : ao; + } else if (flowRelation == ELM_ATSPI_RELATION_FLOWS_FROM) { + return (subTxtAo) ? *subTxtAo : *mainTxtAo; + } + } else if (m_rmCueAo && ao == *m_rmCueAo) { + if (flowRelation == ELM_ATSPI_RELATION_FLOWS_FROM) { + return *rejectAo; + } + } else if (ao == getWindow()) { + return *acceptAo; + } else { + LOG_RETURN_VALUE(RES_FAIL, nullptr, "Unknown object!"); + } + + return ao; + } + + void MainPage::registerActiveCallModeAo() + { + DLOG("ENTER"); + m_atspiHelper->setRelationEventHandler(WEAK_DELEGATE( + MainPage::onActiveCallModeAtspiHighlight, asWeak(*this))); + + auto statusTxtAo = m_callInfoPrs->getStatusTxtAo(); + if (!statusTxtAo) { + DLOG("statusTxtAo is NULL"); + } else { + m_atspiHelper->registerWidget(*statusTxtAo); + } + + auto mainTxtAo = m_callInfoPrs->getMainTxtAo(); + if (!mainTxtAo) { + DLOG("mainTxtAo is NULL"); + } else { + m_atspiHelper->registerWidget(*mainTxtAo); + } + + auto subTxtAo = m_callInfoPrs->getSubTxtAo(); + if (!subTxtAo) { + DLOG("subTxtAo is NULL"); + } else { + m_atspiHelper->registerWidget(*subTxtAo); + } + + auto volumeBtnAo = m_accessoryPrs->getVolumBtn(); + if (!volumeBtnAo) { + DLOG("volumeBtnAo is NULL"); + } else { + m_atspiHelper->registerWidget(*volumeBtnAo); + } + + auto bluetoothBtnAo = m_accessoryPrs->getBluetoothBtn(); + if (!bluetoothBtnAo) { + DLOG("bluetoothBtnAo is NULL"); + } else { + m_atspiHelper->registerWidget(*bluetoothBtnAo); + } + + auto muteBtnAo = m_accessoryPrs->getMuteBtn(); + if (!muteBtnAo) { + DLOG("muteBtnAo is NULL"); + } else { + m_atspiHelper->registerWidget(*muteBtnAo); + } + + auto moreOptCueAo = m_moreOptionsPrs->getCueAo(); + if (!moreOptCueAo) { + DLOG("moreCueAo is NULL"); + } else { + m_atspiHelper->registerWidget(*moreOptCueAo); + } + + if (!m_bottomBtn) { + DLOG("m_bottomBtn is NULL"); + } else { + m_atspiHelper->registerWidget(*m_bottomBtn); + } + + auto vcLayout = m_accessoryPrs->getVolumeControlLy(); + if (!vcLayout) { + DLOG("vcLayout is NULL"); + } else { + m_atspiHelper->registerWidget(*vcLayout); + } + + auto vcDecrVolumeBtn = m_accessoryPrs->getVolumeControlDecreaseBtn(); + if (!vcDecrVolumeBtn) { + DLOG("vcDecrVolumeBtn is NULL"); + } else { + m_atspiHelper->registerWidget(*vcDecrVolumeBtn); + } + + auto vcIncrVolumeBtn = m_accessoryPrs->getVolumeControlIncreaseBtn(); + if (!vcIncrVolumeBtn) { + DLOG("vcIncrVolumeBtn is NULL"); + } else { + m_atspiHelper->registerWidget(*vcIncrVolumeBtn); + } + + auto vcVolumeValueAo = m_accessoryPrs->getVolumeControlValueTxtAo(); + if (!vcVolumeValueAo) { + DLOG("vcVolumeValueAo is NULL"); + } else { + m_atspiHelper->registerWidget(*vcVolumeValueAo); + } + + DLOG("EXIT"); + } + + Elm_Interface_Atspi_Accessible *MainPage::onActiveCallModeAtspiHighlight( + Elm_Interface_Atspi_Accessible *ao, + Elm_Atspi_Relation_Type flowRelation) + { + DLOG("FlowRelation [%s]", + flowRelation == ELM_ATSPI_RELATION_FLOWS_FROM ? + "FROM" : "TO"); + + auto statusTxtAo = m_callInfoPrs->getStatusTxtAo(); + auto mainTxtAo = m_callInfoPrs->getMainTxtAo(); + auto subTxtAo = m_callInfoPrs->getSubTxtAo(); + auto volumeBtnAo = m_accessoryPrs->getVolumBtn(); + auto bluetoothBtnAo = m_accessoryPrs->getBluetoothBtn(); + auto muteBtnAo = m_accessoryPrs->getMuteBtn(); + auto moreOptCueAo = m_moreOptionsPrs->getCueAo(); + auto vcLayout = m_accessoryPrs->getVolumeControlLy(); + auto vcDecrVolumeBtn = m_accessoryPrs->getVolumeControlDecreaseBtn(); + auto vcIncrVolumeBtn = m_accessoryPrs->getVolumeControlIncreaseBtn(); + auto vcVolumeValueAo = m_accessoryPrs->getVolumeControlValueTxtAo(); + + if (ao == *statusTxtAo) { + if (flowRelation == ELM_ATSPI_RELATION_FLOWS_TO) { + return *mainTxtAo; + } + } else if (ao == *mainTxtAo) { + if (flowRelation == ELM_ATSPI_RELATION_FLOWS_TO) { + if (subTxtAo) { + return *subTxtAo; + } else { + return *volumeBtnAo; + } + } else { + return *statusTxtAo; + } + } else if (subTxtAo && ao == *subTxtAo) { + if (flowRelation == ELM_ATSPI_RELATION_FLOWS_TO) { + return *volumeBtnAo; + } else { + return *mainTxtAo; + } + } else if (ao == *volumeBtnAo) { + if (flowRelation == ELM_ATSPI_RELATION_FLOWS_TO) { + return *bluetoothBtnAo; + } else { + if (subTxtAo) { + return *subTxtAo; + } else { + return *mainTxtAo; + } + } + } else if (ao == *bluetoothBtnAo) { + if (flowRelation == ELM_ATSPI_RELATION_FLOWS_TO) { + return *muteBtnAo; + } else { + return *volumeBtnAo; + } + } else if (ao == *muteBtnAo) { + if (flowRelation == ELM_ATSPI_RELATION_FLOWS_TO) { + return *moreOptCueAo; + } else { + return *bluetoothBtnAo; + } + } else if (ao == *moreOptCueAo) { + if (flowRelation == ELM_ATSPI_RELATION_FLOWS_TO) { + return *m_bottomBtn; + } else { + return *muteBtnAo; + } + } else if (ao == *m_bottomBtn) { + if (flowRelation == ELM_ATSPI_RELATION_FLOWS_FROM) { + return *moreOptCueAo; + } + } else if (ao == *vcLayout) { + if (flowRelation == ELM_ATSPI_RELATION_FLOWS_TO) { + return *vcDecrVolumeBtn; + } + } else if (ao == *vcDecrVolumeBtn) { + if (flowRelation == ELM_ATSPI_RELATION_FLOWS_TO) { + return *vcVolumeValueAo; + } else { + return *vcLayout; + } + } else if (ao == *vcVolumeValueAo) { + if (flowRelation == ELM_ATSPI_RELATION_FLOWS_TO) { + return *vcIncrVolumeBtn; + } else { + return *vcDecrVolumeBtn; + } + } else if (ao == *vcIncrVolumeBtn) { + if (flowRelation == ELM_ATSPI_RELATION_FLOWS_FROM) { + return *vcVolumeValueAo; + } + } else if (ao == getWindow()) { + return *statusTxtAo; + } else { + LOG_RETURN_VALUE(RES_FAIL, nullptr, "Unknown object!"); + } + + return ao; + } + + void MainPage::registerEndCallModeAo() + { + m_atspiHelper->setRelationEventHandler(WEAK_DELEGATE( + MainPage::onEndCallModeAtspiHighlight, asWeak(*this))); + + auto statusTxtAo = m_callInfoPrs->getStatusTxtAo(); + if (!statusTxtAo) { + DLOG("statusTxtAo is NULL"); + } else { + m_atspiHelper->registerWidget(*statusTxtAo); + } + + auto mainTxtAo = m_callInfoPrs->getMainTxtAo(); + if (!mainTxtAo) { + DLOG("mainTxtAo is NULL"); + } else { + m_atspiHelper->registerWidget(*mainTxtAo); + } + + auto addContactsBtnAo = m_accessoryPrs->getAddContactBtn(); + if (!addContactsBtnAo) { + DLOG("addContactsBtnAo is NULL"); + } else { + m_atspiHelper->registerWidget(*addContactsBtnAo); + } + + if (!m_bottomBtn) { + DLOG("m_bottomBtn is NULL"); + } else { + m_atspiHelper->registerWidget(*m_bottomBtn); + } + + DLOG("EXIT"); + } + + Elm_Interface_Atspi_Accessible *MainPage::onEndCallModeAtspiHighlight( + Elm_Interface_Atspi_Accessible *ao, + Elm_Atspi_Relation_Type flowRelation) + { + DLOG("FlowRelation [%s]", + flowRelation == ELM_ATSPI_RELATION_FLOWS_FROM ? + "FROM" : "TO"); + + auto statusTxtAo = m_callInfoPrs->getStatusTxtAo(); + auto mainTxtAo = m_callInfoPrs->getMainTxtAo(); + auto addContactsBtnAo = m_accessoryPrs->getAddContactBtn(); + + if (ao == *statusTxtAo) { + if (flowRelation == ELM_ATSPI_RELATION_FLOWS_TO) { + return *mainTxtAo; + } + } else if (ao == *mainTxtAo) { + if (flowRelation == ELM_ATSPI_RELATION_FLOWS_TO) { + if (addContactsBtnAo) { + return *addContactsBtnAo; + } else { + return *m_bottomBtn; + } + } else { + return *statusTxtAo; + } + } else if (addContactsBtnAo && ao == *addContactsBtnAo) { + if (flowRelation == ELM_ATSPI_RELATION_FLOWS_TO) { + return *m_bottomBtn; + } else { + return *mainTxtAo; + } + } else if (ao == *m_bottomBtn) { + if (flowRelation == ELM_ATSPI_RELATION_FLOWS_FROM) { + if (addContactsBtnAo) { + return *addContactsBtnAo; + } else { + return *mainTxtAo; + } + } + } else if (ao == getWindow()) { + return *statusTxtAo; + } else { + LOG_RETURN_VALUE(RES_FAIL, nullptr, "Unknown object!"); + } + + return ao; + } } diff --git a/src/presenters/MoreOptionsPresenter.cpp b/src/presenters/MoreOptionsPresenter.cpp index 6a98eac..13f6049 100644 --- a/src/presenters/MoreOptionsPresenter.cpp +++ b/src/presenters/MoreOptionsPresenter.cpp @@ -28,6 +28,7 @@ #include "model/ISoundManager.h" #include "presenters/KeypadPage.h" +#include "presenters/AtspiHighlightHelper.h" #include "resources.h" #include "common.h" @@ -54,6 +55,10 @@ namespace callui { namespace { namespace impl { constexpr EdjePart PART_SWL_SLOT2 {"swl.slot.2"}; constexpr EdjePart PART_TXT_STATUS {"txt.status"}; + constexpr EdjePart PART_AO_TXT_STATUS {"ao_txt.status"}; + + constexpr EdjePart PART_ACCESS {"access"}; + constexpr EdjeSignal SIGNAL_ODD {"odd"}; constexpr EdjeSignal SIGNAL_EVEN {"even"}; @@ -156,9 +161,11 @@ namespace callui { FAIL_RETURN(createPanelLayout(), "createPanelContent() failed!"); - FAIL_RETURN(createButtons(), "createButtons() failed!"); + FAIL_RETURN(createAccessObjects(), "createStaticAo() failed!"); - update(); + updateComponents(); + + FAIL_RETURN(createAtspiHighlightHelper(), "createAtspiHighlightHelper() failed!"); deactivateBy(m_widget.get()); @@ -181,6 +188,20 @@ namespace callui { return RES_OK; } + void MoreOptionsPresenter::onCueClicked(Evas_Object *obj, + const char *emission, + const char *source) + { + show(*m_fakeAo); + m_panel->setContent(*m_panelLy); + show(*m_panelLy); + + createStatusTxtAo(); + if (m_statusTxtAo) { + m_atspiHelper->registerWidget(*m_statusTxtAo); + } + } + Result MoreOptionsPresenter::createPanel() { auto *eo = elm_panel_add(*m_widget); @@ -201,6 +222,11 @@ namespace callui { elm_layout_signal_callback_add(*m_panel, "elm,state,inactive,finished", "elm", CALLBACK_A(MoreOptionsPresenter::onPanelInactivate), this); + setDeactivatorSink(m_panel); + + elm_layout_signal_callback_add(*m_panel, "cue,clicked", + "elm", CALLBACK_A(MoreOptionsPresenter::onCueClicked), this); + return RES_OK; } @@ -226,12 +252,19 @@ namespace callui { auto active = m_cm->getActiveCall(); auto held = m_cm->getHeldCall(); + FAIL_RETURN_VOID(createKeypadButton(), + "createKeypadButton() failed"); + if (held) { m_panelLy->emit(impl::SIGNAL_EVEN, impl::SIGNAL_SRC_MORE_OPTION); if (active) { + FAIL_RETURN_VOID(createSwapButton(), + "createSwapButton() failed"); setPanelContent(*m_btnSwap, impl::PART_SWL_SLOT1); } else { + FAIL_RETURN_VOID(createUnholdButton(), + "createUnholdButton() failed"); setPanelContent(*m_btnUnhold, impl::PART_SWL_SLOT1); } setPanelContent(*m_btnKeypad, impl::PART_SWL_SLOT2); @@ -242,9 +275,8 @@ namespace callui { } } - Result MoreOptionsPresenter::createButtons() + Result MoreOptionsPresenter::createSwapButton() { - // Swap m_btnSwap = createButton(impl::STYLE_BTN_SWAP, STR_MORE_SWAP, WEAK_DELEGATE(MoreOptionsPresenter::onSwapBtnClick, asWeak(*this))); @@ -252,7 +284,11 @@ namespace callui { LOG_RETURN(RES_FAIL, "Create Swap button failed!"); } - // Unhold + return RES_OK; + } + + Result MoreOptionsPresenter::createUnholdButton() + { m_btnUnhold = createButton(impl::STYLE_BTN_UNHOLD, STR_MORE_UNHOLD, WEAK_DELEGATE(MoreOptionsPresenter::onUnholdBtnClick, asWeak(*this))); @@ -260,7 +296,11 @@ namespace callui { LOG_RETURN(RES_FAIL, "Create Unhold button failed!"); } - // Keypad + return RES_OK; + } + + Result MoreOptionsPresenter::createKeypadButton() + { m_btnKeypad = createButton(impl::STYLE_BTN_KEYPAD, STR_MORE_KEYPAD, WEAK_DELEGATE(MoreOptionsPresenter::onKeypadBtnClick, asWeak(*this))); @@ -335,6 +375,11 @@ namespace callui { } void MoreOptionsPresenter::update() + { + updateComponents(); + } + + void MoreOptionsPresenter::updateComponents() { updateSlots(); updateStatusText(); @@ -350,6 +395,8 @@ namespace callui { const auto keepAliver = asShared(*this); sendDeactivate(*m_widget); activateBy(m_widget.get()); + + elm_atspi_component_highlight_grab(*m_fakeAo); } void MoreOptionsPresenter::onPanelInactivate(Evas_Object *obj, @@ -362,6 +409,13 @@ namespace callui { const auto keepAliver = asShared(*this); deactivateBy(m_widget.get()); sendActivate(*m_widget); + + m_panel->unsetContent(); + hide(*m_panelLy); + m_statusTxtAo.reset(); + + elm_atspi_accessible_can_highlight_set(*m_cueAo, EINA_TRUE); + hide(*m_fakeAo); } void MoreOptionsPresenter::onBackKey(Evas_Object *obj, void *eventInfo) @@ -391,7 +445,8 @@ namespace callui { } m_info = info; m_duration = info->getDuration(); - setCallDuration(m_duration, *m_panelLy, impl::PART_TXT_STATUS); + auto temp = getCallDuration(m_duration); + m_panelLy->setText(temp, impl::PART_TXT_STATUS); FAIL_RETURN_VOID(startCallDurationTimer(), "startTimer() failed!"); @@ -454,4 +509,198 @@ namespace callui { m_panelLy->setContent(widget, part); show(widget); } + + // Screen Reader + Result MoreOptionsPresenter::createAtspiHighlightHelper() + { + DLOG("ENTER"); + m_atspiHelper = AtspiHighlightHelper::newInstance(*this, getWindow()); + if (!m_atspiHelper) { + LOG_RETURN(RES_FAIL, + "AtspiHighlightHelper::newInstance() failed!"); + } + + m_atspiHelper->setRelationEventHandler(WEAK_DELEGATE( + MoreOptionsPresenter::onAtspiHighlight, asWeak(*this))); + + if (m_panelLy) { + m_atspiHelper->registerWidget(*m_fakeAo); + } + + if (m_panelLy) { + m_atspiHelper->registerWidget(*m_panelLy); + } + + if (m_btnSwap) { + m_atspiHelper->registerWidget(*m_btnSwap); + } + + if (m_btnUnhold) { + m_atspiHelper->registerWidget(*m_btnUnhold); + } + + if (m_btnKeypad) { + m_atspiHelper->registerWidget(*m_btnKeypad); + } + + if (m_statusTxtAo) { + m_atspiHelper->registerWidget(*m_statusTxtAo); + } + DLOG("EXIT"); + return RES_OK; + } + + Elm_Interface_Atspi_Accessible *MoreOptionsPresenter::onAtspiHighlight( + Elm_Interface_Atspi_Accessible *ao, + Elm_Atspi_Relation_Type flowRelation) + { + DLOG("FlowRelation [%s]", + flowRelation == ELM_ATSPI_RELATION_FLOWS_FROM ? + "FROM" : + "TO"); + + if (m_btnSwap && ao == *m_btnSwap) { + DLOG("Swap button"); + if (flowRelation == ELM_ATSPI_RELATION_FLOWS_TO) { + return *m_btnKeypad; + } + } else if (m_btnUnhold && ao == *m_btnUnhold) { + DLOG("Unhold button"); + if (flowRelation == ELM_ATSPI_RELATION_FLOWS_TO) { + return *m_btnKeypad; + } + } else if (ao == *m_btnKeypad) { + DLOG("Keypad button"); + if (flowRelation == ELM_ATSPI_RELATION_FLOWS_TO) { + return *m_statusTxtAo; + } else { + if (m_btnSwap) { + return *m_btnSwap; + } else if (m_btnUnhold) { + return *m_btnUnhold; + } + } + } else if (ao == *m_statusTxtAo) { + DLOG("Status text"); + if (flowRelation == ELM_ATSPI_RELATION_FLOWS_FROM) { + return *m_btnKeypad; + } + } else if (ao == getWindow() || ao == *m_fakeAo) { + DLOG("window or panelLy"); + if (m_btnSwap) { + return *m_btnSwap; + } else if (m_btnUnhold) { + return *m_btnUnhold; + } else { + return *m_btnKeypad; + } + } else { + LOG_RETURN_VALUE(RES_FAIL, nullptr, "Unknown object!"); + } + + return ao; + } + + Result MoreOptionsPresenter::createAccessObjects() + { + FAIL_RETURN(createCueAo(), "createCueAo() failed"); + FAIL_RETURN(createStatusTxtAo(), "createStatusTxtAo() failed"); + FAIL_RETURN(createFakeAo(), "createFakeAo() failed"); + + return RES_OK; + } + + Result MoreOptionsPresenter::createCueAo() + { + m_cueAo = utils::createAccessObjectFromLyPart(*m_widget, + *m_panel, + impl::PART_ACCESS); + if (!m_cueAo) { + LOG_RETURN(RES_FAIL, + "createAccessObjectFromLyPart() failed!"); + } + elm_access_action_cb_set(*m_cueAo, + ELM_ACCESS_ACTION_ACTIVATE, + CALLBACK_A(MoreOptionsPresenter::onCueAoActionCb), + this); + + elm_atspi_accessible_translation_domain_set(*m_cueAo, PACKAGE); + elm_atspi_accessible_reading_info_type_set(*m_cueAo, + ELM_ACCESSIBLE_READING_INFO_TYPE_NAME | + ELM_ACCESSIBLE_READING_INFO_TYPE_ROLE); + elm_atspi_accessible_name_set(*m_cueAo, AO_STR_MORE_OPTIONS); + elm_atspi_accessible_role_set(*m_cueAo, ELM_ATSPI_ROLE_PUSH_BUTTON); + + return RES_OK; + } + + ElmWidget *MoreOptionsPresenter::getCueAo() + { + return m_cueAo.get(); + } + + Result MoreOptionsPresenter::createFakeAo() + { + m_fakeAo = utils::createFakeAccessObject(*m_panel); + if (!m_fakeAo) { + LOG_RETURN(RES_FAIL, "createFakeAccessObject() failed!"); + } + + return RES_OK; + } + + Result MoreOptionsPresenter::createStatusTxtAo() + { + m_statusTxtAo = utils::createAccessObjectFromLyPart(*m_widget, + *m_panelLy, + impl::PART_AO_TXT_STATUS); + if (!m_statusTxtAo) { + LOG_RETURN(RES_FAIL, + "createAccessObjectFromLyPart() failed!"); + } + + elm_atspi_accessible_reading_info_type_set(*m_statusTxtAo, + ELM_ACCESSIBLE_READING_INFO_TYPE_NAME); + elm_atspi_accessible_name_cb_set(*m_statusTxtAo, + [](void *data, Evas_Object *obj) -> char * + { + auto self = static_cast(data); + if (!self) { + return nullptr; + } + auto txt = self->m_panelLy-> + getText(impl::PART_TXT_STATUS).getCStr(); + return (txt) ? strdup(txt) : nullptr; + }, + this); + + return RES_OK; + } + + Eina_Bool MoreOptionsPresenter::onCueAoActionCb(Evas_Object *obj, + Elm_Access_Action_Info *actionInfo) + { + switch (actionInfo->action_type) { + case ELM_ACCESS_ACTION_ACTIVATE: + + show(*m_fakeAo); + m_panel->setContent(*m_panelLy); + show(*m_panelLy); + createStatusTxtAo(); + if (m_statusTxtAo) { + m_atspiHelper->registerWidget(*m_statusTxtAo); + } + + elm_panel_toggle(*m_panel); + elm_atspi_component_highlight_clear(*m_cueAo); + elm_atspi_accessible_can_highlight_set(*m_cueAo, EINA_FALSE); + + return EINA_TRUE; + break; + default: + break; + } + + return EINA_FALSE; + } } diff --git a/src/presenters/RejectMsgPresenter.cpp b/src/presenters/RejectMsgPresenter.cpp index 7774321..f5e7c03 100644 --- a/src/presenters/RejectMsgPresenter.cpp +++ b/src/presenters/RejectMsgPresenter.cpp @@ -19,6 +19,8 @@ #include "model/IRejectMsgProvider.h" #include "model/IRejectMsg.h" +#include "presenters/AtspiHighlightHelper.h" + #include "resources.h" #include "common.h" @@ -174,8 +176,8 @@ namespace callui { FAIL_RETURN(createPanelLy(), "createPanelLy() failed!"); - FAIL_RETURN(createGenlist(), - "createGenlist() failed!"); + FAIL_RETURN(createAtspiHighlightHelper(), + "createScreenReaderRoute() failed!"); deactivateBy(m_widget.get()); @@ -212,30 +214,43 @@ namespace callui { { Elm_Panel_Scroll_Info *ev = static_cast(eventInfo); DLOG("pos x[%f] y[%f]", ev->rel_x, ev->rel_y); + auto prevState = m_state; if (ev->rel_y == 1.0) { m_state = RejectMsgState::SHOWN; // Prevent panel scrolling elm_object_scroll_freeze_push(m_panel->getEo()); - sendDeactivate(*m_widget); - activateBy(m_widget.get()); } else if (ev->rel_y == 0.0) { m_state = RejectMsgState::HIDDEN; - // Scroll genlist to top - elm_scroller_region_show(m_genlist->getEo(), 0, 0, 0, 0); + m_genlist.reset(); } else { + if (!m_genlist) { + FAIL_RETURN_VOID(createGenlist(), + "createGenlist() failed!"); + } + m_state = RejectMsgState::IN_TRANSITION; const auto alphaValue = static_cast(impl::ALPHA_CHANNEL_MAX * ev->rel_y); m_panelBg->setColor(0, alphaValue); - if (isActive()) { + } + + if (prevState != m_state) { + if (m_state == RejectMsgState::SHOWN) { + activateBy(m_widget.get()); + } else { deactivateBy(m_widget.get()); + } + + if (m_state == RejectMsgState::HIDDEN) { sendActivate(*m_widget); + } else { + sendDeactivate(*m_widget); } - } - if (m_stateHandler) { - m_stateHandler(m_state); + if (m_stateHandler) { + m_stateHandler(m_state); + } } } @@ -257,6 +272,8 @@ namespace callui { m_widget->setContent(*m_panel, impl::PART_SWL_RIGHT); + setDeactivatorSink(m_panel); + return RES_OK; } @@ -297,7 +314,8 @@ namespace callui { LOG_RETURN(RES_FAIL, "Layout::build failed!"); } // Circular surface - Eext_Circle_Surface *const circleSurf = eext_circle_surface_layout_add(*circlLy); + Eext_Circle_Surface *const circleSurf = + eext_circle_surface_layout_add(*circlLy); if (!circleSurf) { LOG_RETURN(RES_FAIL, "eext_circle_surface_layout_add() failed!"); } @@ -306,17 +324,21 @@ namespace callui { if (!glEo) { LOG_RETURN(RES_FAIL, "elm_genlist_add() failed!"); } - evas_object_size_hint_weight_set(glEo, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); - evas_object_size_hint_align_set(glEo, EVAS_HINT_FILL, EVAS_HINT_FILL); elm_genlist_mode_set(glEo, ELM_LIST_COMPRESS); elm_genlist_homogeneous_set(glEo, EINA_TRUE); - m_genlist = makeShared(glEo); - m_circleEo = eext_circle_object_genlist_add(m_genlist->getEo(), circleSurf); + m_genlist = makeShared(glEo, true); + m_genlist->setWeight(EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); + m_genlist->setAlign(EVAS_HINT_FILL, EVAS_HINT_FILL); + + m_circleEo = eext_circle_object_genlist_add(m_genlist->getEo(), + circleSurf); if (!m_circleEo) { LOG_RETURN(RES_FAIL, "elm_genlist_add() failed!"); } - eext_circle_object_genlist_scroller_policy_set(m_circleEo, ELM_SCROLLER_POLICY_OFF, ELM_SCROLLER_POLICY_AUTO); + eext_circle_object_genlist_scroller_policy_set(m_circleEo, + ELM_SCROLLER_POLICY_OFF, + ELM_SCROLLER_POLICY_AUTO); deactivateRotary(); FAIL_RETURN(fillGenlist(), "fillGenlist() failed!"); @@ -325,6 +347,8 @@ namespace callui { m_panelLy->setContent(*m_genlist, impl::PART_SWL_CONTENT); + registerGenlistAtspiGestureCallbacks(); + return RES_OK; } @@ -344,7 +368,7 @@ namespace callui { Result RejectMsgPresenter::addGenlistTitleItem() { - static auto titleItc = createGenlistItemClass("title", + static auto titleItc = utils::createGenlistItemClass("title", [](void *data, Evas_Object *obj, const char *part) -> char * { return strdup(STR_DECLINE_MESSAGES.translate()); }); @@ -364,7 +388,7 @@ namespace callui { Result RejectMsgPresenter::addGenlistTextItem(const IRejectMsgSRef &rm) { - static auto textItc = createGenlistItemClass("1text.1icon", + static auto textItc = utils::createGenlistItemClass("1text.1icon", [](void *data, Evas_Object *obj, const char *part) -> char * { if (!data) { LOG_RETURN_VALUE(RES_FAIL, nullptr, "Data is NULL"); @@ -400,7 +424,7 @@ namespace callui { Result RejectMsgPresenter::addGenlistBottomItem() { - static auto paddingItc = createGenlistItemClass("1text.1icon"); + static auto paddingItc = utils::createGenlistItemClass("1text.1icon"); auto *item = elm_genlist_item_append(*m_genlist, &paddingItc, nullptr, @@ -445,6 +469,15 @@ namespace callui { return m_state; } + void RejectMsgPresenter::showPanel() + { + DLOG(); + if (m_state != RejectMsgState::SHOWN) { + DLOG("Panel state [NOT SHOWN]"); + elm_panel_hidden_set(m_panel->getEo(), EINA_FALSE); + } + } + void RejectMsgPresenter::hidePanel() { DLOG(); @@ -493,4 +526,77 @@ namespace callui { } } + // Screen Reader + Elm_Interface_Atspi_Accessible *RejectMsgPresenter::getFirstAo() + { + return (m_genlist) ? + elm_genlist_first_item_get(*m_genlist) : + nullptr; + } + + Elm_Interface_Atspi_Accessible *RejectMsgPresenter::getLastAo() + { + return (m_genlist) ? + elm_genlist_last_item_get(*m_genlist) : + nullptr; + } + + Result RejectMsgPresenter::createAtspiHighlightHelper() + { + m_atspiHelper = AtspiHighlightHelper::newInstance(*this, getWindow()); + if (!m_atspiHelper) { + LOG_RETURN(RES_FAIL, "AtspiHighlightHelper::newInstance() failed!"); + } + + m_atspiHelper->setRelationEventHandler(WEAK_DELEGATE( + RejectMsgPresenter::onAtspiHighlight, asWeak(*this))); + + return RES_OK; + } + + void RejectMsgPresenter::registerGenlistAtspiGestureCallbacks() + { + auto item = getFirstAo(); + elm_atspi_accessible_gesture_cb_set(item, + CALLBACK_A(RejectMsgPresenter::onAtspiGesture), this); + + item = getLastAo(); + elm_atspi_accessible_gesture_cb_set(item, + CALLBACK_A(RejectMsgPresenter::onAtspiGesture), this); + } + + Elm_Interface_Atspi_Accessible *RejectMsgPresenter::onAtspiHighlight( + Elm_Interface_Atspi_Accessible *ao, + Elm_Atspi_Relation_Type flowRelation) + { + DLOG("ENTER"); + + DLOG("FlowRelation [%s]", + flowRelation == ELM_ATSPI_RELATION_FLOWS_FROM ? + "FROM" : + "TO"); + + if (ao == getFirstAo()) { + if (flowRelation == ELM_ATSPI_RELATION_FLOWS_TO) { + return nullptr; + } + } else if (ao == getLastAo()) { + if (flowRelation == ELM_ATSPI_RELATION_FLOWS_FROM) { + return nullptr; + } + } else if (ao == getWindow()) { + return getFirstAo(); + } else { + LOG_RETURN_VALUE(RES_FAIL, nullptr, "Unknown object!"); + } + return ao; + } + + Eina_Bool RejectMsgPresenter::onAtspiGesture( + Elm_Atspi_Gesture_Info gestureInfo, + Elm_Interface_Atspi_Accessible *ao) + { + return toEina( + m_atspiHelper->handleGesture(ao, gestureInfo)); + } } diff --git a/src/presenters/common.h b/src/presenters/common.h index d07af88..49f910e 100644 --- a/src/presenters/common.h +++ b/src/presenters/common.h @@ -29,6 +29,7 @@ namespace callui { constexpr auto CALL_VC_TIMER_INTERVAL = 1.5; + constexpr auto CALL_VC_SCREEN_READER_TIMER_INTERVAL = 5.0; constexpr auto VOLUME_LEVEL_MIN = 1; } diff --git a/src/presenters/helpers.cpp b/src/presenters/helpers.cpp index ef4ea02..adc682d 100644 --- a/src/presenters/helpers.cpp +++ b/src/presenters/helpers.cpp @@ -41,9 +41,7 @@ namespace callui { } } - void setCallDuration(const struct tm &time, - EdjeWidget &widget, - const EdjePart &part) + TString getCallDuration(const struct tm &time) { TString tmp; if (time.tm_hour > 0) { @@ -51,7 +49,7 @@ namespace callui { } else { tmp = himpl::STR_MM_SS_TIME.format(time.tm_min, time.tm_sec); } - widget.setText(tmp, part); + return tmp; } void tryUpdateCallDurationTime( @@ -62,7 +60,8 @@ namespace callui { { if ((compTime.tm_sec - curTime.tm_sec) != 0) { curTime = compTime; - setCallDuration(curTime, widget, part); + auto tmp = getCallDuration(curTime); + widget.setText(tmp, part); } } diff --git a/src/presenters/helpers.h b/src/presenters/helpers.h index cc46cf7..9a0081c 100644 --- a/src/presenters/helpers.h +++ b/src/presenters/helpers.h @@ -28,8 +28,7 @@ namespace callui { void replaceSubstringInString(std::string &str, const std::string &from, const std::string &to); - void setCallDuration(const struct tm &time, - ucl::EdjeWidget &widget, const ucl::EdjePart &part); + ucl::TString getCallDuration(const struct tm &time); void tryUpdateCallDurationTime( struct tm &curTime, struct tm &compTime, diff --git a/src/resources.cpp b/src/resources.cpp index e2258e7..fbac1f1 100644 --- a/src/resources.cpp +++ b/src/resources.cpp @@ -48,4 +48,27 @@ namespace callui { const ucl::TString STR_MORE_HOLD {"Hold"}; const ucl::TString STR_MORE_UNHOLD {"Unhold"}; const ucl::TString STR_MORE_GEAR {"Gear"}; + + // Screen Reader + const ucl::TString AO_STR_CALL {"Call"}; + const ucl::TString AO_STR_VOLUME {"Volume"}; + const ucl::TString AO_STR_HEADSET {"Headset"}; + const ucl::TString AO_STR_GEAR_SPK {"Gear speaker"}; + const ucl::TString AO_STR_MUTE {"Mute"}; + const ucl::TString AO_STR_MORE_OPTIONS {"More options"}; + const ucl::TString AO_STR_END_CALL {"End call"}; + const ucl::TString AO_STR_CALLBACK {"Callback"}; + const ucl::TString AO_STR_ADD_TO_CONTACTS {"Add to contacts"}; + const ucl::TString AO_STR_ROTATE_BEZEL_TO_ADJUST {"Rotate bezel to adjust"}; + const ucl::TString AO_STR_DECREASE_VOLUME {"Decrease volume"}; + const ucl::TString AO_STR_INCREASE_VOLUME {"Increase volume"}; + const ucl::TString AO_STR_ACCEPT_CALL {"Accept call"}; + const ucl::TString AO_STR_SWIPE_RIGHT_WITH_TWO_FINGERS_TO_ACCEPT + {"Swipe right with two fingers to accept"}; + const ucl::TString AO_STR_REJECT_CALL {"Reject call"}; + const ucl::TString AO_STR_SWIPE_LEFT_WITH_TWO_FINGERS_TO_REJECT + {"Swipe left with two fingers to reject"}; + const ucl::TString AO_STR_DECLINE_MESSAGES {"Decline messages"}; + const ucl::TString AO_STR_SWIPE_UP_WITH_TWO_FINGERS_TO_SEND_A_DECLINE_MESSAGE + {"Swipe up with two fingers to send a decline message"}; } diff --git a/src/view/AcceptRejectWidget.cpp b/src/view/AcceptRejectWidget.cpp index f8841a7..0049890 100644 --- a/src/view/AcceptRejectWidget.cpp +++ b/src/view/AcceptRejectWidget.cpp @@ -17,6 +17,7 @@ #include "view/AcceptRejectWidget.h" #include "common.h" +#include "resources.h" #define CU_COL_TRANSPARENT 0, 0, 0, 0 #define CU_COL_WHITE 255, 255, 255, 255 @@ -205,6 +206,9 @@ namespace callui { auto result = makeShared(layout, m_acceptHandler, m_rejectHandler, m_acceptBtnType); + FAIL_RETURN_VALUE(result->registerAccessObjects(parent), {}, + "registerScreenReaderObjects() failed!"); + result->bindToEo(); return result; @@ -1396,4 +1400,45 @@ namespace callui { setRejectUnpressedTransitions(); } + + // Screen Reader + ElmWidget *AcceptRejectWidget::getAcceptAo() + { + return m_accAo.get(); + } + + ElmWidget *AcceptRejectWidget::getRejectAo() + { + return m_rejAo.get(); + } + + Result AcceptRejectWidget::registerAccessObjects(ElmWidget &parent) + { + m_accAo = utils::createAccessObject(parent, *m_accEventLy); + if (!m_accAo) { + LOG_RETURN(RES_FAIL, "createAccessObject() failed!"); + } + elm_atspi_accessible_translation_domain_set(*m_accAo, PACKAGE); + elm_atspi_accessible_reading_info_type_set(*m_accAo, + ELM_ACCESSIBLE_READING_INFO_TYPE_NAME | + ELM_ACCESSIBLE_READING_INFO_TYPE_DESCRIPTION); + elm_atspi_accessible_name_set(*m_accAo, AO_STR_ACCEPT_CALL); + elm_atspi_accessible_description_set(*m_accAo, + AO_STR_SWIPE_RIGHT_WITH_TWO_FINGERS_TO_ACCEPT); + + m_rejAo = utils::createAccessObject(parent, *m_rejEventLy); + if (!m_rejAo) { + LOG_RETURN(RES_FAIL, "createAccessObject() failed!"); + } + elm_atspi_accessible_translation_domain_set(*m_rejAo, PACKAGE); + elm_atspi_accessible_reading_info_type_set(*m_rejAo, + ELM_ACCESSIBLE_READING_INFO_TYPE_NAME | + ELM_ACCESSIBLE_READING_INFO_TYPE_DESCRIPTION); + elm_atspi_accessible_name_set(*m_rejAo, AO_STR_REJECT_CALL); + elm_atspi_accessible_description_set(*m_rejAo, + AO_STR_SWIPE_LEFT_WITH_TWO_FINGERS_TO_REJECT); + + return RES_OK; + } + } diff --git a/src/view/Slider.cpp b/src/view/Slider.cpp index 9e22cd6..17205e3 100644 --- a/src/view/Slider.cpp +++ b/src/view/Slider.cpp @@ -88,10 +88,8 @@ namespace callui { { m_layout->setIsOwner(false); - evas_object_size_hint_align_set(m_slider, - EVAS_HINT_FILL, EVAS_HINT_FILL); - evas_object_size_hint_weight_set(m_slider, - EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); + m_slider.setAlign(EVAS_HINT_FILL, EVAS_HINT_FILL); + m_slider.setWeight(EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); m_layout->setContent(m_circleLy, impl::PART_SWL_SLIDER); show(m_circleLy); @@ -100,6 +98,14 @@ namespace callui { setMaxValue(maxValue); setValue(curValue); + + // Screen Reader + elm_atspi_accessible_role_set(m_slider, + ELM_ATSPI_ROLE_REDUNDANT_OBJECT); + elm_atspi_accessible_role_set(m_circleLy, + ELM_ATSPI_ROLE_REDUNDANT_OBJECT); + elm_atspi_accessible_role_set(*m_layout, + ELM_ATSPI_ROLE_REDUNDANT_OBJECT); } void Slider::setValue(int value) diff --git a/src/view/VolumeControl.cpp b/src/view/VolumeControl.cpp index 3cba34d..355aae1 100644 --- a/src/view/VolumeControl.cpp +++ b/src/view/VolumeControl.cpp @@ -17,6 +17,7 @@ #include "view/VolumeControl.h" #include "common.h" +#include "resources.h" namespace callui { namespace { namespace impl { @@ -33,6 +34,7 @@ namespace callui { namespace { namespace impl { constexpr EdjePart PART_TXT_INFO {"txt.info"}; constexpr EdjePart PART_TXT_VALUE {"txt.value"}; + constexpr EdjePart PART_TXT_VALUE_AO {"ao_txt.value"}; }}} @@ -110,8 +112,13 @@ namespace callui { { } - void VolumeControl::prepare(const ucl::TString &info, int curValue) + void VolumeControl::prepare(const TString &info, int curValue) { + m_layout->addEventHandler(WidgetEvent::SHOW, WEAK_DELEGATE( + VolumeControl::onWidgetShowCb, asWeak(*this))); + m_layout->addEventHandler(WidgetEvent::HIDE, WEAK_DELEGATE( + VolumeControl::onWidgetHideCb, asWeak(*this))); + m_decreaseBtn.setStyle(impl::STYLE_BTN_MINUS); m_decreaseBtn.addEventHandler(BTN_CLICKED, WEAK_DELEGATE( VolumeControl::onDecreaseBtnClickedCb, asWeak(*this))); @@ -126,6 +133,20 @@ namespace callui { setInfoText(info); setValue(curValue); + + registerAccessObjectInformation(); + } + + void VolumeControl::onWidgetShowCb(Widget &widget, void *eventInfo) + { + if (m_valueTxtAo) + show(*m_valueTxtAo); + } + + void VolumeControl::onWidgetHideCb(Widget &widget, void *eventInfo) + { + if (m_valueTxtAo) + hide(*m_valueTxtAo); } void VolumeControl::setInfoText(const TString &info) @@ -169,4 +190,65 @@ namespace callui { } } + // Screen Reader + ElmWidget *VolumeControl::getDecreaseBtn() + { + return &m_decreaseBtn; + } + + ElmWidget *VolumeControl::getIncreaseBtn() + { + return &m_increaseBtn; + } + + ElmWidget *VolumeControl::getValueTxtAo() + { + return m_valueTxtAo.get(); + } + + void VolumeControl::registerAccessObjectInformation() + { + elm_atspi_accessible_translation_domain_set(m_slider, PACKAGE); + elm_atspi_accessible_reading_info_type_set(m_slider, + ELM_ACCESSIBLE_READING_INFO_TYPE_NAME + | ELM_ACCESSIBLE_READING_INFO_TYPE_DESCRIPTION); + elm_atspi_accessible_name_set(m_slider, AO_STR_VOLUME); + elm_atspi_accessible_description_set(m_slider, + AO_STR_ROTATE_BEZEL_TO_ADJUST); + + elm_atspi_accessible_translation_domain_set(m_decreaseBtn, PACKAGE); + elm_atspi_accessible_name_set(m_decreaseBtn, + AO_STR_DECREASE_VOLUME); + + elm_atspi_accessible_translation_domain_set(m_increaseBtn, PACKAGE); + elm_atspi_accessible_name_set(m_increaseBtn, + AO_STR_INCREASE_VOLUME); + + m_valueTxtAo = utils::createAccessObjectFromLyPart( + *m_layout, + *m_layout, + impl::PART_TXT_VALUE_AO); + if (!m_valueTxtAo) { + ELOG("createAccessObjectFromLyPart() failed"); + } else { + elm_atspi_accessible_translation_domain_set(*m_valueTxtAo, + PACKAGE); + elm_atspi_accessible_reading_info_type_set(*m_valueTxtAo, + ELM_ACCESSIBLE_READING_INFO_TYPE_NAME); + elm_atspi_accessible_name_cb_set(*m_valueTxtAo, + [](void *data, Evas_Object *obj) -> char * + { + auto self = static_cast(data); + if (!self) { + return nullptr; + } + auto txt = self->m_layout-> + getText(impl::PART_TXT_VALUE).getCStr(); + return (txt) ? strdup(txt) : nullptr; + }, + this); + } + + elm_atspi_accessible_role_set(*m_layout, ELM_ATSPI_ROLE_TEXT); + } } diff --git a/src/view/helpers.cpp b/src/view/helpers.cpp index e4f8595..6f6a196 100644 --- a/src/view/helpers.cpp +++ b/src/view/helpers.cpp @@ -18,6 +18,7 @@ #include "ucl/gui/Window.h" #include "ucl/gui/Naviframe.h" +#include "ucl/gui/Layout.h" #include "common.h" @@ -26,9 +27,12 @@ namespace callui { namespace { namespace impl { using namespace ucl; constexpr EoDataKey CIRCLE_SURFACE {"callui,eext,circle,surface"}; + + constexpr LayoutTheme LAYOUT_FAKE_ACCESS_OBJECT + {"layout", "callui", "fake_access_object"}; }}} -namespace callui { +namespace callui { namespace utils { using namespace ucl; @@ -72,26 +76,33 @@ namespace callui { return sfc; } - void addRotaryEventHandler(Eext_Rotary_Handler_Cb func, void *data) - { - eext_rotary_event_handler_add(func, data); - } - - void delRotaryEventHandler(Eext_Rotary_Handler_Cb func, void *data) + ElmWidgetSRef createFakeAccessObject(ElmWidget &parent) { - std::vector backup; - while (true) { - void *const oldData = eext_rotary_event_handler_del(func); - if (!oldData || (oldData == data)) { - return; - } - backup.push_back(oldData); - } - for (auto i = backup.size(); i-- > 0; ) { - eext_rotary_event_handler_add(func, backup[i]); + const auto result = Layout::Builder(). + setTheme(impl::LAYOUT_FAKE_ACCESS_OBJECT). + setIsOwner(true). + setNeedBindToEo(true). + build(parent); + if (!result) { + LOG_RETURN_VALUE(RES_FAIL, {}, "Layout::build() failed!"); } + + result->setGeometry(0, 0, 1, 1); + show(*result); + + elm_atspi_accessible_reading_info_type_set(*result, 0); + + elm_atspi_accessible_gesture_cb_set(*result, + [](void *, Elm_Atspi_Gesture_Info, Evas_Object *) -> Eina_Bool + { + return EINA_TRUE; + }, + nullptr); + + return result; } + Elm_Genlist_Item_Class createGenlistItemClass(const char *style, Elm_Gen_Item_Text_Get_Cb txtCb, Elm_Gen_Item_Content_Get_Cb contentCb, @@ -108,10 +119,85 @@ namespace callui { return itc; } + ElmWidgetSRef createAccessObject(ElmWidget &parent, Widget &ly) + { + auto ao = elm_access_object_register(ly, parent); + if (!ao) { + LOG_RETURN_VALUE(RES_FAIL, {}, + "elm_access_object_register() failed!"); + } + + return makeShared(ao, true); + } + + ElmWidgetSRef createAccessObjectFromLyPart(ElmWidget &parent, + Widget &ly, + const EdjePart &lyPart) + { + auto po = const_cast( + edje_object_part_object_get( + elm_layout_edje_get(ly), lyPart)); + if (!po) { + LOG_RETURN_VALUE(RES_FAIL, {}, "Part object is NULL"); + } + + auto ao = elm_access_object_register(po, parent); + if (!ao) { + LOG_RETURN_VALUE(RES_FAIL, {}, + "elm_access_object_register() failed!"); + } + + return makeShared(ao, true); + } + + void destroyAccessObject(ElmWidget &ao) + { + elm_access_object_unregister(ao); + } + +}} + +namespace callui { + + void addRotaryEventHandler(Eext_Rotary_Handler_Cb func, void *data) + { + eext_rotary_event_handler_add(func, data); + } + + void delRotaryEventHandler(Eext_Rotary_Handler_Cb func, void *data) + { + std::vector backup; + while (true) { + void *const oldData = eext_rotary_event_handler_del(func); + if (!oldData || (oldData == data)) { + break; + } + backup.push_back(oldData); + } + for (auto i = backup.size(); i-- > 0; ) { + eext_rotary_event_handler_add(func, backup[i]); + } + } + LayoutTheme getImageTheme(const char *const fileName) { return {"layout", "callui_image", fileName}; } + Elm_Atspi_Relation_Type getFlowRelation(Elm_Atspi_Gesture_Info gestureInfo) + { + switch (gestureInfo.type) { + case ELM_ATSPI_GESTURE_ONE_FINGER_FLICK_RIGHT: + case ELM_ATSPI_GESTURE_ONE_FINGER_FLICK_DOWN: + return ELM_ATSPI_RELATION_FLOWS_TO; + case ELM_ATSPI_GESTURE_ONE_FINGER_FLICK_LEFT: + case ELM_ATSPI_GESTURE_ONE_FINGER_FLICK_UP: + return ELM_ATSPI_RELATION_FLOWS_FROM; + default: + break; + } + return ELM_ATSPI_RELATION_NULL; + } + }