[Tizen] Remove to call key consumed event in ATSPI bridge
[platform/core/uifw/dali-adaptor.git] / dali / internal / accessibility / bridge / bridge-impl.cpp
1 /*
2  * Copyright (c) 2022 Samsung Electronics Co., Ltd.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  *
16  */
17
18 // CLASS HEADER
19
20 // EXTERNAL INCLUDES
21 #include <dali/devel-api/common/stage.h>
22 #include <dali/integration-api/debug.h>
23 #include <dali/public-api/actors/layer.h>
24 #include <iostream>
25 #include <unordered_map>
26
27 // INTERNAL INCLUDES
28 #include <dali/devel-api/adaptor-framework/environment-variable.h>
29 #include <dali/devel-api/adaptor-framework/window-devel.h>
30 #include <dali/internal/accessibility/bridge/accessibility-common.h>
31 #include <dali/internal/accessibility/bridge/bridge-accessible.h>
32 #include <dali/internal/accessibility/bridge/bridge-action.h>
33 #include <dali/internal/accessibility/bridge/bridge-collection.h>
34 #include <dali/internal/accessibility/bridge/bridge-component.h>
35 #include <dali/internal/accessibility/bridge/bridge-editable-text.h>
36 #include <dali/internal/accessibility/bridge/bridge-hypertext.h>
37 #include <dali/internal/accessibility/bridge/bridge-hyperlink.h>
38 #include <dali/internal/accessibility/bridge/bridge-object.h>
39 #include <dali/internal/accessibility/bridge/bridge-selection.h>
40 #include <dali/internal/accessibility/bridge/bridge-socket.h>
41 #include <dali/internal/accessibility/bridge/bridge-text.h>
42 #include <dali/internal/accessibility/bridge/bridge-value.h>
43 #include <dali/internal/accessibility/bridge/bridge-application.h>
44 #include <dali/internal/accessibility/bridge/dummy/dummy-atspi.h>
45 #include <dali/internal/adaptor/common/adaptor-impl.h>
46 #include <dali/internal/system/common/environment-variables.h>
47
48 using namespace Dali::Accessibility;
49
50 namespace // unnamed namespace
51 {
52
53 const int RETRY_INTERVAL = 1000;
54
55 } // unnamed namespace
56
57 /**
58  * @brief The BridgeImpl class is to implement some Bridge functions.
59  */
60 class BridgeImpl : public virtual BridgeBase,
61                    public BridgeAccessible,
62                    public BridgeObject,
63                    public BridgeComponent,
64                    public BridgeCollection,
65                    public BridgeAction,
66                    public BridgeValue,
67                    public BridgeText,
68                    public BridgeEditableText,
69                    public BridgeSelection,
70                    public BridgeApplication,
71                    public BridgeHypertext,
72                    public BridgeHyperlink,
73                    public BridgeSocket
74 {
75   DBus::DBusClient                                              mAccessibilityStatusClient;
76   DBus::DBusClient                                              mRegistryClient;
77   DBus::DBusClient                                              mDirectReadingClient;
78   bool                                                          mIsScreenReaderEnabled = false;
79   bool                                                          mIsEnabled             = false;
80   std::unordered_map<int32_t, std::function<void(std::string)>> mDirectReadingCallbacks;
81   Dali::Actor                                                   mHighlightedActor;
82   std::function<void(Dali::Actor)>                              mHighlightClearAction;
83   Dali::CallbackBase*                                           mIdleCallback          = NULL;
84   Dali::Timer                                                   mInitializeTimer;
85   Dali::Timer                                                   mReadIsEnabledTimer;
86   Dali::Timer                                                   mReadScreenReaderEnabledTimer;
87   Dali::Timer                                                   mForceUpTimer;
88   std::string                                                   mPreferredBusName;
89
90 public:
91   BridgeImpl()
92   {
93   }
94
95   /**
96    * @copydoc Dali::Accessibility::Bridge::Emit()
97    */
98   Consumed Emit(KeyEventType type, unsigned int keyCode, const std::string& keyName, unsigned int timeStamp, bool isText) override
99   {
100     if(!IsUp())
101     {
102       return Consumed::NO;
103     }
104
105     unsigned int keyType = 0;
106
107     switch(type)
108     {
109       case KeyEventType::KEY_PRESSED:
110       {
111         keyType = 0;
112         break;
113       }
114       case KeyEventType::KEY_RELEASED:
115       {
116         keyType = 1;
117         break;
118       }
119       default:
120       {
121         return Consumed::NO;
122       }
123     }
124
125     return Consumed::NO;
126   }
127
128   /**
129    * @copydoc Dali::Accessibility::Bridge::Pause()
130    */
131   void Pause() override
132   {
133     if(!IsUp())
134     {
135       return;
136     }
137
138     mDirectReadingClient.method<DBus::ValueOrError<void>(bool)>("PauseResume").asyncCall([](DBus::ValueOrError<void> msg) {
139       if(!msg)
140       {
141         LOG() << "Direct reading command failed (" << msg.getError().message << ")\n";
142       }
143     },
144                                                                                         true);
145   }
146
147   /**
148    * @copydoc Dali::Accessibility::Bridge::Resume()
149    */
150   void Resume() override
151   {
152     if(!IsUp())
153     {
154       return;
155     }
156
157     mDirectReadingClient.method<DBus::ValueOrError<void>(bool)>("PauseResume").asyncCall([](DBus::ValueOrError<void> msg) {
158       if(!msg)
159       {
160         LOG() << "Direct reading command failed (" << msg.getError().message << ")\n";
161       }
162     },
163                                                                                         false);
164   }
165
166   /**
167    * @copydoc Dali::Accessibility::Bridge::StopReading()
168    */
169   void StopReading(bool alsoNonDiscardable) override
170   {
171     if(!IsUp())
172     {
173       return;
174     }
175
176     mDirectReadingClient.method<DBus::ValueOrError<void>(bool)>("StopReading").asyncCall([](DBus::ValueOrError<void> msg) {
177       if(!msg)
178       {
179         LOG() << "Direct reading command failed (" << msg.getError().message << ")\n";
180       }
181     },
182                                                                                         alsoNonDiscardable);
183   }
184
185   /**
186    * @copydoc Dali::Accessibility::Bridge::Say()
187    */
188   void Say(const std::string& text, bool discardable, std::function<void(std::string)> callback) override
189   {
190     if(!IsUp())
191     {
192       return;
193     }
194
195     mDirectReadingClient.method<DBus::ValueOrError<std::string, bool, int32_t>(std::string, bool)>("ReadCommand").asyncCall([=](DBus::ValueOrError<std::string, bool, int32_t> msg) {
196       if(!msg)
197       {
198         LOG() << "Direct reading command failed (" << msg.getError().message << ")\n";
199       }
200       else if(callback)
201       {
202         mDirectReadingCallbacks.emplace(std::get<2>(msg), callback);
203       }
204     },
205                                                                                                                            text,
206                                                                                                                            discardable);
207   }
208
209   /**
210    * @copydoc Dali::Accessibility::Bridge::ForceDown()
211    */
212   void ForceDown() override
213   {
214     if(mData)
215     {
216       if(mData->mCurrentlyHighlightedActor && mData->mHighlightActor)
217       {
218         mData->mCurrentlyHighlightedActor.Remove(mData->mHighlightActor);
219       }
220       mData->mCurrentlyHighlightedActor = {};
221       mData->mHighlightActor            = {};
222
223       mDisabledSignal.Emit();
224       UnembedSocket(mApplication.GetAddress(), {AtspiDbusNameRegistry, "root"});
225       ReleaseBusName(mPreferredBusName);
226     }
227
228     mHighlightedActor     = {};
229     mHighlightClearAction = {};
230     BridgeAccessible::ForceDown();
231     mRegistryClient       = {};
232     mDirectReadingClient  = {};
233     mDirectReadingCallbacks.clear();
234     mApplication.mChildren.clear();
235     ClearTimer();
236   }
237
238   void ClearTimer()
239   {
240     if(mInitializeTimer)
241     {
242       mInitializeTimer.Stop();
243       mInitializeTimer.Reset();
244     }
245
246     if(mReadIsEnabledTimer)
247     {
248       mReadIsEnabledTimer.Stop();
249       mReadIsEnabledTimer.Reset();
250     }
251
252     if(mReadScreenReaderEnabledTimer)
253     {
254       mReadScreenReaderEnabledTimer.Stop();
255       mReadScreenReaderEnabledTimer.Reset();
256     }
257
258     if(mForceUpTimer)
259     {
260       mForceUpTimer.Stop();
261       mForceUpTimer.Reset();
262     }
263   }
264
265   /**
266    * @copydoc Dali::Accessibility::Bridge::Terminate()
267    */
268   void Terminate() override
269   {
270     if(mData)
271     {
272       // The ~Window() after this point cannot emit DESTROY, because Bridge is not available. So emit DESTROY here.
273       for(auto windowAccessible : mApplication.mChildren)
274       {
275         BridgeObject::Emit(windowAccessible, WindowEvent::DESTROY);
276       }
277       mData->mCurrentlyHighlightedActor = {};
278       mData->mHighlightActor            = {};
279     }
280     ForceDown();
281     if((NULL != mIdleCallback) && Dali::Adaptor::IsAvailable())
282     {
283       Dali::Adaptor::Get().RemoveIdle(mIdleCallback);
284     }
285     mAccessibilityStatusClient        = {};
286     mDbusServer                       = {};
287     mConnectionPtr                    = {};
288   }
289
290   bool ForceUpTimerCallback()
291   {
292     if(ForceUp() != ForceUpResult::FAILED)
293     {
294       return false;
295     }
296     return true;
297   }
298
299   /**
300    * @copydoc Dali::Accessibility::Bridge::ForceUp()
301    */
302   ForceUpResult ForceUp() override
303   {
304     auto forceUpResult = BridgeAccessible::ForceUp();
305     if(forceUpResult == ForceUpResult::ALREADY_UP)
306     {
307       return forceUpResult;
308     }
309     else if(forceUpResult == ForceUpResult::FAILED)
310     {
311       if(!mForceUpTimer)
312       {
313         mForceUpTimer = Dali::Timer::New(RETRY_INTERVAL);
314         mForceUpTimer.TickSignal().Connect(this, &BridgeImpl::ForceUpTimerCallback);
315         mForceUpTimer.Start();
316       }
317       return forceUpResult;
318     }
319
320     BridgeObject::RegisterInterfaces();
321     BridgeAccessible::RegisterInterfaces();
322     BridgeComponent::RegisterInterfaces();
323     BridgeCollection::RegisterInterfaces();
324     BridgeAction::RegisterInterfaces();
325     BridgeValue::RegisterInterfaces();
326     BridgeText::RegisterInterfaces();
327     BridgeEditableText::RegisterInterfaces();
328     BridgeSelection::RegisterInterfaces();
329     BridgeApplication::RegisterInterfaces();
330     BridgeHypertext::RegisterInterfaces();
331     BridgeHyperlink::RegisterInterfaces();
332     BridgeSocket::RegisterInterfaces();
333
334     RegisterOnBridge(&mApplication);
335
336     mRegistryClient      = {AtspiDbusNameRegistry, AtspiDbusPathDec, Accessible::GetInterfaceName(AtspiInterface::DEVICE_EVENT_CONTROLLER), mConnectionPtr};
337     mDirectReadingClient = DBus::DBusClient{DirectReadingDBusName, DirectReadingDBusPath, DirectReadingDBusInterface, mConnectionPtr};
338
339     mDirectReadingClient.addSignal<void(int32_t, std::string)>("ReadingStateChanged", [=](int32_t id, std::string readingState) {
340       auto it = mDirectReadingCallbacks.find(id);
341       if(it != mDirectReadingCallbacks.end())
342       {
343         it->second(readingState);
344         if(readingState != "ReadingPaused" && readingState != "ReadingResumed" && readingState != "ReadingStarted")
345         {
346           mDirectReadingCallbacks.erase(it);
347         }
348       }
349     });
350
351     RequestBusName(mPreferredBusName);
352
353     auto parentAddress = EmbedSocket(mApplication.GetAddress(), {AtspiDbusNameRegistry, "root"});
354     mApplication.mParent.SetAddress(std::move(parentAddress));
355     mEnabledSignal.Emit();
356
357     return ForceUpResult::JUST_STARTED;
358   }
359
360   /**
361    * @brief Sends a signal to dbus that the window is shown.
362    *
363    * @param[in] window The window to be shown
364    * @see Accessible::EmitShowing() and BridgeObject::EmitStateChanged()
365    */
366   void EmitShown(Dali::Window window)
367   {
368     auto windowAccessible = mApplication.GetWindowAccessible(window);
369     if(windowAccessible)
370     {
371       windowAccessible->EmitShowing(true);
372     }
373   }
374
375   /**
376    * @brief Sends a signal to dbus that the window is hidden.
377    *
378    * @param[in] window The window to be hidden
379    * @see Accessible::EmitShowing() and BridgeObject::EmitStateChanged()
380    */
381   void EmitHidden(Dali::Window window)
382   {
383     auto windowAccessible = mApplication.GetWindowAccessible(window);
384     if(windowAccessible)
385     {
386       windowAccessible->EmitShowing(false);
387     }
388   }
389
390   /**
391    * @brief Sends a signal to dbus that the window is activated.
392    *
393    * @param[in] window The window to be activated
394    * @see BridgeObject::Emit()
395    */
396   void EmitActivate(Dali::Window window)
397   {
398     auto windowAccessible = mApplication.GetWindowAccessible(window);
399     if(windowAccessible)
400     {
401       windowAccessible->Emit(WindowEvent::ACTIVATE, 0);
402     }
403   }
404
405   /**
406    * @brief Sends a signal to dbus that the window is deactivated.
407    *
408    * @param[in] window The window to be deactivated
409    * @see BridgeObject::Emit()
410    */
411   void EmitDeactivate(Dali::Window window)
412   {
413     auto windowAccessible = mApplication.GetWindowAccessible(window);
414     if(windowAccessible)
415     {
416       windowAccessible->Emit(WindowEvent::DEACTIVATE, 0);
417     }
418   }
419
420   /**
421    * @copydoc Dali::Accessibility::Bridge::WindowShown()
422    */
423   void WindowShown(Dali::Window window) override
424   {
425     if(IsUp())
426     {
427       EmitShown(window);
428     }
429   }
430
431   /**
432    * @copydoc Dali::Accessibility::Bridge::WindowHidden()
433    */
434   void WindowHidden(Dali::Window window) override
435   {
436     if(IsUp())
437     {
438       EmitHidden(window);
439     }
440   }
441
442   /**
443    * @copydoc Dali::Accessibility::Bridge::WindowFocused()
444    */
445   void WindowFocused(Dali::Window window) override
446   {
447     if(IsUp())
448     {
449       EmitActivate(window);
450     }
451   }
452
453   /**
454    * @copydoc Dali::Accessibility::Bridge::WindowUnfocused()
455    */
456   void WindowUnfocused(Dali::Window window) override
457   {
458     if(IsUp())
459     {
460       EmitDeactivate(window);
461     }
462   }
463
464   /**
465    * @copydoc Dali::Accessibility::Bridge::SuppressScreenReader()
466    */
467   void SuppressScreenReader(bool suppress) override
468   {
469     if(mIsScreenReaderSuppressed == suppress)
470     {
471       return;
472     }
473     mIsScreenReaderSuppressed = suppress;
474     ReadScreenReaderEnabledProperty();
475   }
476
477   void SwitchBridge()
478   {
479     if((!mIsScreenReaderSuppressed && mIsScreenReaderEnabled) || mIsEnabled)
480     {
481       ForceUp();
482     }
483     else
484     {
485       ForceDown();
486     }
487   }
488
489   bool ReadIsEnabledTimerCallback()
490   {
491     ReadIsEnabledProperty();
492     return false;
493   }
494
495   void ReadIsEnabledProperty()
496   {
497     mAccessibilityStatusClient.property<bool>("IsEnabled").asyncGet([this](DBus::ValueOrError<bool> msg) {
498       if(!msg)
499       {
500         DALI_LOG_ERROR("Get IsEnabled property error: %s\n", msg.getError().message.c_str());
501         if(msg.getError().errorType == DBus::ErrorType::INVALID_REPLY)
502         {
503           if(!mReadIsEnabledTimer)
504           {
505             mReadIsEnabledTimer = Dali::Timer::New(RETRY_INTERVAL);
506             mReadIsEnabledTimer.TickSignal().Connect(this, &BridgeImpl::ReadIsEnabledTimerCallback);
507           }
508           mReadIsEnabledTimer.Start();
509         }
510         return;
511       }
512
513       if(mReadIsEnabledTimer)
514       {
515         mReadIsEnabledTimer.Stop();
516         mReadIsEnabledTimer.Reset();
517       }
518
519       mIsEnabled = std::get<0>(msg);
520       SwitchBridge();
521     });
522   }
523
524   void ListenIsEnabledProperty()
525   {
526     mAccessibilityStatusClient.addPropertyChangedEvent<bool>("IsEnabled", [this](bool res) {
527       mIsEnabled = res;
528       SwitchBridge();
529     });
530   }
531
532   bool ReadScreenReaderEnabledTimerCallback()
533   {
534     ReadScreenReaderEnabledProperty();
535     return false;
536   }
537
538   void ReadScreenReaderEnabledProperty()
539   {
540     // can be true because of SuppressScreenReader before init
541     if (!mAccessibilityStatusClient)
542     {
543       return;
544     }
545
546     mAccessibilityStatusClient.property<bool>("ScreenReaderEnabled").asyncGet([this](DBus::ValueOrError<bool> msg) {
547       if(!msg)
548       {
549         DALI_LOG_ERROR("Get ScreenReaderEnabled property error: %s\n", msg.getError().message.c_str());
550         if(msg.getError().errorType == DBus::ErrorType::INVALID_REPLY)
551         {
552           if(!mReadScreenReaderEnabledTimer)
553           {
554             mReadScreenReaderEnabledTimer = Dali::Timer::New(RETRY_INTERVAL);
555             mReadScreenReaderEnabledTimer.TickSignal().Connect(this, &BridgeImpl::ReadScreenReaderEnabledTimerCallback);
556           }
557           mReadScreenReaderEnabledTimer.Start();
558         }
559         return;
560       }
561
562       if(mReadScreenReaderEnabledTimer)
563       {
564         mReadScreenReaderEnabledTimer.Stop();
565         mReadScreenReaderEnabledTimer.Reset();
566       }
567
568       mIsScreenReaderEnabled = std::get<0>(msg);
569       SwitchBridge();
570     });
571   }
572
573   void EmitScreenReaderEnabledSignal()
574   {
575     if (mIsScreenReaderEnabled)
576     {
577       mScreenReaderEnabledSignal.Emit();
578     }
579     else
580     {
581       mScreenReaderDisabledSignal.Emit();
582     }
583   }
584
585   void ListenScreenReaderEnabledProperty()
586   {
587     mAccessibilityStatusClient.addPropertyChangedEvent<bool>("ScreenReaderEnabled", [this](bool res) {
588       mIsScreenReaderEnabled = res;
589       EmitScreenReaderEnabledSignal();
590       SwitchBridge();
591     });
592   }
593
594   void ReadAndListenProperties()
595   {
596     ReadIsEnabledProperty();
597     ListenIsEnabledProperty();
598
599     ReadScreenReaderEnabledProperty();
600     ListenScreenReaderEnabledProperty();
601   }
602
603   bool InitializeAccessibilityStatusClient()
604   {
605     mAccessibilityStatusClient = DBus::DBusClient{A11yDbusName, A11yDbusPath, A11yDbusStatusInterface, DBus::ConnectionType::SESSION};
606
607     if (!mAccessibilityStatusClient)
608     {
609       DALI_LOG_ERROR("Accessibility Status DbusClient is not ready\n");
610       return false;
611     }
612
613     return true;
614   }
615
616   bool InitializeTimerCallback()
617   {
618     if ( InitializeAccessibilityStatusClient() )
619     {
620       ReadAndListenProperties();
621       return false;
622     }
623     return true;
624   }
625
626   bool OnIdleSignal()
627   {
628     if ( InitializeAccessibilityStatusClient() )
629     {
630       ReadAndListenProperties();
631       mIdleCallback = NULL;
632       return false;
633     }
634
635     if(!mInitializeTimer)
636     {
637       mInitializeTimer = Dali::Timer::New(RETRY_INTERVAL);
638       mInitializeTimer.TickSignal().Connect(this, &BridgeImpl::InitializeTimerCallback);
639     }
640     mInitializeTimer.Start();
641
642     mIdleCallback = NULL;
643     return false;
644   }
645
646   /**
647    * @copydoc Dali::Accessibility::Bridge::Initialize()
648    */
649   void Initialize() override
650   {
651     if ( InitializeAccessibilityStatusClient() )
652     {
653       ReadAndListenProperties();
654       return;
655     }
656
657     // Initialize failed. Try it again on Idle
658     if( Dali::Adaptor::IsAvailable() )
659     {
660       Dali::Adaptor& adaptor = Dali::Adaptor::Get();
661       if( NULL == mIdleCallback )
662       {
663         mIdleCallback = MakeCallback( this, &BridgeImpl::OnIdleSignal );
664         adaptor.AddIdle( mIdleCallback, true );
665       }
666     }
667   }
668
669   /**
670    * @copydoc Dali::Accessibility::Bridge::GetScreenReaderEnabled()
671    */
672   bool GetScreenReaderEnabled() override
673   {
674     return mIsScreenReaderEnabled;
675   }
676
677   /**
678    * @copydoc Dali::Accessibility::Bridge::IsEnabled()
679    */
680   bool IsEnabled() override
681   {
682     return mIsEnabled;
683   }
684
685   Address EmbedSocket(const Address& plug, const Address& socket) override
686   {
687     auto client = CreateSocketClient(socket);
688     auto reply  = client.method<Address(Address)>("Embed").call(plug);
689
690     if(!reply)
691     {
692       DALI_LOG_ERROR("Failed to embed socket %s: %s", socket.ToString().c_str(), reply.getError().message.c_str());
693       return {};
694     }
695
696     return std::get<0>(reply.getValues());
697   }
698
699   void EmbedAtkSocket(const Address& plug, const Address& socket) override
700   {
701     auto client = CreateSocketClient(socket);
702
703     client.method<void(std::string)>("Embedded").call(ATSPI_PREFIX_PATH + plug.GetPath());
704   }
705
706   void UnembedSocket(const Address& plug, const Address& socket) override
707   {
708     auto client = CreateSocketClient(socket);
709
710     client.method<void(Address)>("Unembed").call(plug);
711   }
712
713   void SetSocketOffset(ProxyAccessible* socket, std::int32_t x, std::int32_t y) override
714   {
715     AddCoalescableMessage(CoalescableMessages::SET_OFFSET, socket, 1.0f, [=]() {
716       auto client = CreateSocketClient(socket->GetAddress());
717
718       client.method<void(std::int32_t, std::int32_t)>("SetOffset").asyncCall([](DBus::ValueOrError<void>) {}, x, y);
719     });
720   }
721
722   void SetExtentsOffset(std::int32_t x, std::int32_t y) override
723   {
724     if(mData)
725     {
726       mData->mExtentsOffset = {x, y};
727     }
728   }
729
730   void SetPreferredBusName(std::string_view preferredBusName) override
731   {
732     if(preferredBusName == mPreferredBusName)
733     {
734       return;
735     }
736
737     std::string oldPreferredBusName = std::move(mPreferredBusName);
738     mPreferredBusName               = std::string{preferredBusName};
739
740     if(IsUp())
741     {
742       ReleaseBusName(oldPreferredBusName);
743       RequestBusName(mPreferredBusName);
744     }
745     // else: request/release will be handled by ForceUp/ForceDown, respectively
746   }
747
748 private:
749   DBus::DBusClient CreateSocketClient(const Address& socket)
750   {
751     return {socket.GetBus(), ATSPI_PREFIX_PATH + socket.GetPath(), Accessible::GetInterfaceName(AtspiInterface::SOCKET), mConnectionPtr};
752   }
753
754   void RequestBusName(const std::string& busName)
755   {
756     if(busName.empty())
757     {
758       return;
759     }
760
761     DBus::requestBusName(mConnectionPtr, busName);
762   }
763
764   void ReleaseBusName(const std::string& busName)
765   {
766     if(busName.empty())
767     {
768       return;
769     }
770
771     DBus::releaseBusName(mConnectionPtr, busName);
772   }
773 }; // BridgeImpl
774
775 namespace // unnamed namespace
776 {
777
778 bool INITIALIZED_BRIDGE = false;
779
780 /**
781  * @brief Creates BridgeImpl instance.
782  *
783  * @return The BridgeImpl instance
784  * @note This method is to check environment variable first. If ATSPI is disable using env, it returns dummy bridge instance.
785  */
786 std::shared_ptr<Bridge> CreateBridge()
787 {
788   INITIALIZED_BRIDGE = true;
789
790   try
791   {
792     /* check environment variable first */
793     const char* envAtspiDisabled = Dali::EnvironmentVariable::GetEnvironmentVariable(DALI_ENV_DISABLE_ATSPI);
794     if(envAtspiDisabled && std::atoi(envAtspiDisabled) != 0)
795     {
796       return Dali::Accessibility::DummyBridge::GetInstance();
797     }
798
799     return std::make_shared<BridgeImpl>();
800   }
801   catch(const std::exception&)
802   {
803     DALI_LOG_ERROR("Failed to initialize AT-SPI bridge");
804     return Dali::Accessibility::DummyBridge::GetInstance();
805   }
806 }
807
808 } // unnamed namespace
809
810 // Dali::Accessibility::Bridge class implementation
811
812 std::shared_ptr<Bridge> Bridge::GetCurrentBridge()
813 {
814   static std::shared_ptr<Bridge> bridge;
815
816   if(bridge)
817   {
818     return bridge;
819   }
820   else if(mAutoInitState == AutoInitState::ENABLED)
821   {
822     bridge = CreateBridge();
823
824     /* check environment variable for suppressing screen-reader */
825     const char* envSuppressScreenReader = Dali::EnvironmentVariable::GetEnvironmentVariable(DALI_ENV_SUPPRESS_SCREEN_READER);
826     if(envSuppressScreenReader && std::atoi(envSuppressScreenReader) != 0)
827     {
828       bridge->SuppressScreenReader(true);
829     }
830
831     return bridge;
832   }
833
834   return Dali::Accessibility::DummyBridge::GetInstance();
835 }
836
837 void Bridge::DisableAutoInit()
838 {
839   if(INITIALIZED_BRIDGE)
840   {
841     DALI_LOG_ERROR("Bridge::DisableAutoInit() called after bridge auto-initialization");
842   }
843
844   mAutoInitState = AutoInitState::DISABLED;
845 }
846
847 void Bridge::EnableAutoInit()
848 {
849   mAutoInitState = AutoInitState::ENABLED;
850
851   if(INITIALIZED_BRIDGE)
852   {
853     return;
854   }
855
856   auto rootLayer       = Dali::Stage::GetCurrent().GetRootLayer(); // A root layer of the default window.
857   auto window          = Dali::DevelWindow::Get(rootLayer);
858   auto applicationName = Dali::Internal::Adaptor::Adaptor::GetApplicationPackageName();
859
860   auto accessible = Accessibility::Accessible::Get(rootLayer);
861
862   auto bridge = Bridge::GetCurrentBridge();
863   bridge->AddTopLevelWindow(accessible);
864   bridge->SetApplicationName(applicationName);
865   bridge->Initialize();
866
867   if(window && window.IsVisible())
868   {
869     bridge->WindowShown(window);
870   }
871 }
872
873 std::string Bridge::MakeBusNameForWidget(std::string_view widgetInstanceId)
874 {
875   // The bus name should consist of dot-separated alphanumeric elements, e.g. "com.example.BusName123".
876   // Allowed characters in each element: "[A-Z][a-z][0-9]_", but no element may start with a digit.
877
878   static const char prefix[]   = "com.samsung.dali.widget_";
879   static const char underscore = '_';
880
881   std::stringstream tmp;
882
883   tmp << prefix;
884
885   for(char ch : widgetInstanceId)
886   {
887     tmp << (std::isalnum(ch) ? ch : underscore);
888   }
889
890   return tmp.str();
891 }