1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome/browser/extensions/api/braille_display_private/braille_controller_brlapi.h"
12 #include "base/bind.h"
13 #include "base/bind_helpers.h"
14 #include "base/stl_util.h"
15 #include "base/time/time.h"
16 #include "chrome/browser/extensions/api/braille_display_private/brlapi_connection.h"
17 #include "chrome/browser/extensions/api/braille_display_private/brlapi_keycode_map.h"
18 #include "content/public/browser/browser_thread.h"
20 namespace extensions
{
21 using content::BrowserThread
;
23 using base::TimeDelta
;
25 namespace braille_display_private
{
28 // Delay between detecting a directory update and trying to connect
30 const int64 kConnectionDelayMs
= 500;
31 // How long to periodically retry connecting after a brltty restart.
32 // Some displays are slow to connect.
33 const int64 kConnectRetryTimeout
= 20000;
36 BrailleController::BrailleController() {
39 BrailleController::~BrailleController() {
43 BrailleController
* BrailleController::GetInstance() {
44 return BrailleControllerImpl::GetInstance();
48 BrailleControllerImpl
* BrailleControllerImpl::GetInstance() {
49 return base::Singleton
<
50 BrailleControllerImpl
,
51 base::LeakySingletonTraits
<BrailleControllerImpl
>>::get();
54 BrailleControllerImpl::BrailleControllerImpl()
55 : started_connecting_(false),
56 connect_scheduled_(false) {
57 create_brlapi_connection_function_
= base::Bind(
58 &BrailleControllerImpl::CreateBrlapiConnection
,
59 base::Unretained(this));
62 BrailleControllerImpl::~BrailleControllerImpl() {
65 void BrailleControllerImpl::TryLoadLibBrlApi() {
66 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
67 if (libbrlapi_loader_
.loaded())
69 // These versions of libbrlapi work the same for the functions we
70 // are using. (0.6.0 adds brlapi_writeWText).
71 static const char* const kSupportedVersions
[] = {
75 for (size_t i
= 0; i
< arraysize(kSupportedVersions
); ++i
) {
76 if (libbrlapi_loader_
.Load(kSupportedVersions
[i
]))
79 LOG(WARNING
) << "Couldn't load libbrlapi: " << strerror(errno
);
82 scoped_ptr
<DisplayState
> BrailleControllerImpl::GetDisplayState() {
83 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
85 scoped_ptr
<DisplayState
> display_state(new DisplayState
);
86 if (connection_
.get() && connection_
->Connected()) {
88 if (!connection_
->GetDisplaySize(&size
)) {
90 } else if (size
> 0) { // size == 0 means no display present.
91 display_state
->available
= true;
92 display_state
->text_cell_count
.reset(new int(size
));
95 return display_state
.Pass();
98 void BrailleControllerImpl::WriteDots(const std::vector
<char>& cells
) {
99 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
100 if (connection_
&& connection_
->Connected()) {
102 if (!connection_
->GetDisplaySize(&size
)) {
105 std::vector
<unsigned char> sizedCells(size
);
106 std::memcpy(&sizedCells
[0], vector_as_array(&cells
),
107 std::min(cells
.size(), size
));
108 if (size
> cells
.size())
109 std::fill(sizedCells
.begin() + cells
.size(), sizedCells
.end(), 0);
110 if (!connection_
->WriteDots(&sizedCells
[0]))
115 void BrailleControllerImpl::AddObserver(BrailleObserver
* observer
) {
116 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
117 if (!BrowserThread::PostTask(BrowserThread::IO
, FROM_HERE
,
119 &BrailleControllerImpl::StartConnecting
,
120 base::Unretained(this)))) {
123 observers_
.AddObserver(observer
);
126 void BrailleControllerImpl::RemoveObserver(BrailleObserver
* observer
) {
127 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
128 observers_
.RemoveObserver(observer
);
131 void BrailleControllerImpl::SetCreateBrlapiConnectionForTesting(
132 const CreateBrlapiConnectionFunction
& function
) {
133 if (function
.is_null()) {
134 create_brlapi_connection_function_
= base::Bind(
135 &BrailleControllerImpl::CreateBrlapiConnection
,
136 base::Unretained(this));
138 create_brlapi_connection_function_
= function
;
142 void BrailleControllerImpl::PokeSocketDirForTesting() {
143 OnSocketDirChangedOnIOThread();
146 void BrailleControllerImpl::StartConnecting() {
147 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
148 if (started_connecting_
)
150 started_connecting_
= true;
152 if (!libbrlapi_loader_
.loaded()) {
155 // Only try to connect after we've started to watch the
156 // socket directory. This is necessary to avoid a race condition
157 // and because we don't retry to connect after errors that will
158 // persist until there's a change to the socket directory (i.e.
160 BrowserThread::PostTaskAndReply(
161 BrowserThread::FILE, FROM_HERE
,
163 &BrailleControllerImpl::StartWatchingSocketDirOnFileThread
,
164 base::Unretained(this)),
166 &BrailleControllerImpl::TryToConnect
,
167 base::Unretained(this)));
168 ResetRetryConnectHorizon();
171 void BrailleControllerImpl::StartWatchingSocketDirOnFileThread() {
172 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
173 base::FilePath
brlapi_dir(BRLAPI_SOCKETPATH
);
174 if (!file_path_watcher_
.Watch(
175 brlapi_dir
, false, base::Bind(
176 &BrailleControllerImpl::OnSocketDirChangedOnFileThread
,
177 base::Unretained(this)))) {
178 LOG(WARNING
) << "Couldn't watch brlapi directory " << BRLAPI_SOCKETPATH
;
182 void BrailleControllerImpl::OnSocketDirChangedOnFileThread(
183 const base::FilePath
& path
, bool error
) {
184 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
186 LOG(ERROR
) << "Error watching brlapi directory: " << path
.value();
189 BrowserThread::PostTask(
190 BrowserThread::IO
, FROM_HERE
, base::Bind(
191 &BrailleControllerImpl::OnSocketDirChangedOnIOThread
,
192 base::Unretained(this)));
195 void BrailleControllerImpl::OnSocketDirChangedOnIOThread() {
196 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
197 VLOG(1) << "BrlAPI directory changed";
198 // Every directory change resets the max retry time to the appropriate delay
200 ResetRetryConnectHorizon();
201 // Try after an initial delay to give the driver a chance to connect.
202 ScheduleTryToConnect();
205 void BrailleControllerImpl::TryToConnect() {
206 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
207 DCHECK(libbrlapi_loader_
.loaded());
208 connect_scheduled_
= false;
209 if (!connection_
.get())
210 connection_
= create_brlapi_connection_function_
.Run();
211 if (connection_
.get() && !connection_
->Connected()) {
212 VLOG(1) << "Trying to connect to brlapi";
213 BrlapiConnection::ConnectResult result
= connection_
->Connect(base::Bind(
214 &BrailleControllerImpl::DispatchKeys
,
215 base::Unretained(this)));
217 case BrlapiConnection::CONNECT_SUCCESS
:
218 DispatchOnDisplayStateChanged(GetDisplayState());
220 case BrlapiConnection::CONNECT_ERROR_NO_RETRY
:
222 case BrlapiConnection::CONNECT_ERROR_RETRY
:
223 ScheduleTryToConnect();
231 void BrailleControllerImpl::ResetRetryConnectHorizon() {
232 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
233 retry_connect_horizon_
= Time::Now() + TimeDelta::FromMilliseconds(
234 kConnectRetryTimeout
);
237 void BrailleControllerImpl::ScheduleTryToConnect() {
238 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
239 TimeDelta
delay(TimeDelta::FromMilliseconds(kConnectionDelayMs
));
240 // Don't reschedule if there's already a connect scheduled or
241 // the next attempt would fall outside of the retry limit.
242 if (connect_scheduled_
)
244 if (Time::Now() + delay
> retry_connect_horizon_
) {
245 VLOG(1) << "Stopping to retry to connect to brlapi";
248 VLOG(1) << "Scheduling connection retry to brlapi";
249 connect_scheduled_
= true;
250 BrowserThread::PostDelayedTask(BrowserThread::IO
, FROM_HERE
,
252 &BrailleControllerImpl::TryToConnect
,
253 base::Unretained(this)),
257 void BrailleControllerImpl::Disconnect() {
258 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
259 if (!connection_
|| !connection_
->Connected())
261 connection_
->Disconnect();
262 DispatchOnDisplayStateChanged(scoped_ptr
<DisplayState
>(new DisplayState()));
265 scoped_ptr
<BrlapiConnection
> BrailleControllerImpl::CreateBrlapiConnection() {
266 DCHECK(libbrlapi_loader_
.loaded());
267 return BrlapiConnection::Create(&libbrlapi_loader_
);
270 void BrailleControllerImpl::DispatchKeys() {
271 DCHECK(connection_
.get());
272 brlapi_keyCode_t code
;
274 int result
= connection_
->ReadKey(&code
);
275 if (result
< 0) { // Error.
276 brlapi_error_t
* err
= connection_
->BrlapiError();
277 if (err
->brlerrno
== BRLAPI_ERROR_LIBCERR
&& err
->libcerrno
== EINTR
)
279 // Disconnect on other errors.
280 VLOG(1) << "BrlAPI error: " << connection_
->BrlapiStrError();
283 } else if (result
== 0) { // No more data.
286 scoped_ptr
<KeyEvent
> event
= BrlapiKeyCodeToEvent(code
);
288 DispatchKeyEvent(event
.Pass());
292 void BrailleControllerImpl::DispatchKeyEvent(scoped_ptr
<KeyEvent
> event
) {
293 if (!BrowserThread::CurrentlyOn(BrowserThread::UI
)) {
294 BrowserThread::PostTask(BrowserThread::UI
, FROM_HERE
,
296 &BrailleControllerImpl::DispatchKeyEvent
,
297 base::Unretained(this),
298 base::Passed(&event
)));
301 VLOG(1) << "Dispatching key event: " << *event
->ToValue();
302 FOR_EACH_OBSERVER(BrailleObserver
, observers_
, OnBrailleKeyEvent(*event
));
305 void BrailleControllerImpl::DispatchOnDisplayStateChanged(
306 scoped_ptr
<DisplayState
> new_state
) {
307 if (!BrowserThread::CurrentlyOn(BrowserThread::UI
)) {
308 if (!BrowserThread::PostTask(
309 BrowserThread::UI
, FROM_HERE
,
310 base::Bind(&BrailleControllerImpl::DispatchOnDisplayStateChanged
,
311 base::Unretained(this),
312 base::Passed(&new_state
)))) {
317 FOR_EACH_OBSERVER(BrailleObserver
, observers_
,
318 OnBrailleDisplayStateChanged(*new_state
));
321 } // namespace braille_display_private
323 } // namespace extensions