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