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 Singleton
<BrailleControllerImpl
,
50 LeakySingletonTraits
<BrailleControllerImpl
> >::get();
53 BrailleControllerImpl::BrailleControllerImpl()
54 : started_connecting_(false),
55 connect_scheduled_(false) {
56 create_brlapi_connection_function_
= base::Bind(
57 &BrailleControllerImpl::CreateBrlapiConnection
,
58 base::Unretained(this));
61 BrailleControllerImpl::~BrailleControllerImpl() {
64 void BrailleControllerImpl::TryLoadLibBrlApi() {
65 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
66 if (libbrlapi_loader_
.loaded())
68 // These versions of libbrlapi work the same for the functions we
69 // are using. (0.6.0 adds brlapi_writeWText).
70 static const char* const kSupportedVersions
[] = {
74 for (size_t i
= 0; i
< arraysize(kSupportedVersions
); ++i
) {
75 if (libbrlapi_loader_
.Load(kSupportedVersions
[i
]))
78 LOG(WARNING
) << "Couldn't load libbrlapi: " << strerror(errno
);
81 scoped_ptr
<DisplayState
> BrailleControllerImpl::GetDisplayState() {
82 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
84 scoped_ptr
<DisplayState
> display_state(new DisplayState
);
85 if (connection_
.get() && connection_
->Connected()) {
87 if (!connection_
->GetDisplaySize(&size
)) {
89 } else if (size
> 0) { // size == 0 means no display present.
90 display_state
->available
= true;
91 display_state
->text_cell_count
.reset(new int(size
));
94 return display_state
.Pass();
97 void BrailleControllerImpl::WriteDots(const std::vector
<char>& cells
) {
98 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
99 if (connection_
&& connection_
->Connected()) {
101 if (!connection_
->GetDisplaySize(&size
)) {
104 std::vector
<unsigned char> sizedCells(size
);
105 std::memcpy(&sizedCells
[0], vector_as_array(&cells
),
106 std::min(cells
.size(), size
));
107 if (size
> cells
.size())
108 std::fill(sizedCells
.begin() + cells
.size(), sizedCells
.end(), 0);
109 if (!connection_
->WriteDots(&sizedCells
[0]))
114 void BrailleControllerImpl::AddObserver(BrailleObserver
* observer
) {
115 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
116 if (!BrowserThread::PostTask(BrowserThread::IO
, FROM_HERE
,
118 &BrailleControllerImpl::StartConnecting
,
119 base::Unretained(this)))) {
122 observers_
.AddObserver(observer
);
125 void BrailleControllerImpl::RemoveObserver(BrailleObserver
* observer
) {
126 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
127 observers_
.RemoveObserver(observer
);
130 void BrailleControllerImpl::SetCreateBrlapiConnectionForTesting(
131 const CreateBrlapiConnectionFunction
& function
) {
132 if (function
.is_null()) {
133 create_brlapi_connection_function_
= base::Bind(
134 &BrailleControllerImpl::CreateBrlapiConnection
,
135 base::Unretained(this));
137 create_brlapi_connection_function_
= function
;
141 void BrailleControllerImpl::PokeSocketDirForTesting() {
142 OnSocketDirChangedOnIOThread();
145 void BrailleControllerImpl::StartConnecting() {
146 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
147 if (started_connecting_
)
149 started_connecting_
= true;
151 if (!libbrlapi_loader_
.loaded()) {
154 // Only try to connect after we've started to watch the
155 // socket directory. This is necessary to avoid a race condition
156 // and because we don't retry to connect after errors that will
157 // persist until there's a change to the socket directory (i.e.
159 BrowserThread::PostTaskAndReply(
160 BrowserThread::FILE, FROM_HERE
,
162 &BrailleControllerImpl::StartWatchingSocketDirOnFileThread
,
163 base::Unretained(this)),
165 &BrailleControllerImpl::TryToConnect
,
166 base::Unretained(this)));
167 ResetRetryConnectHorizon();
170 void BrailleControllerImpl::StartWatchingSocketDirOnFileThread() {
171 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
172 base::FilePath
brlapi_dir(BRLAPI_SOCKETPATH
);
173 if (!file_path_watcher_
.Watch(
174 brlapi_dir
, false, base::Bind(
175 &BrailleControllerImpl::OnSocketDirChangedOnFileThread
,
176 base::Unretained(this)))) {
177 LOG(WARNING
) << "Couldn't watch brlapi directory " << BRLAPI_SOCKETPATH
;
181 void BrailleControllerImpl::OnSocketDirChangedOnFileThread(
182 const base::FilePath
& path
, bool error
) {
183 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
185 LOG(ERROR
) << "Error watching brlapi directory: " << path
.value();
188 BrowserThread::PostTask(
189 BrowserThread::IO
, FROM_HERE
, base::Bind(
190 &BrailleControllerImpl::OnSocketDirChangedOnIOThread
,
191 base::Unretained(this)));
194 void BrailleControllerImpl::OnSocketDirChangedOnIOThread() {
195 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
196 VLOG(1) << "BrlAPI directory changed";
197 // Every directory change resets the max retry time to the appropriate delay
199 ResetRetryConnectHorizon();
200 // Try after an initial delay to give the driver a chance to connect.
201 ScheduleTryToConnect();
204 void BrailleControllerImpl::TryToConnect() {
205 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
206 DCHECK(libbrlapi_loader_
.loaded());
207 connect_scheduled_
= false;
208 if (!connection_
.get())
209 connection_
= create_brlapi_connection_function_
.Run();
210 if (connection_
.get() && !connection_
->Connected()) {
211 VLOG(1) << "Trying to connect to brlapi";
212 BrlapiConnection::ConnectResult result
= connection_
->Connect(base::Bind(
213 &BrailleControllerImpl::DispatchKeys
,
214 base::Unretained(this)));
216 case BrlapiConnection::CONNECT_SUCCESS
:
217 DispatchOnDisplayStateChanged(GetDisplayState());
219 case BrlapiConnection::CONNECT_ERROR_NO_RETRY
:
221 case BrlapiConnection::CONNECT_ERROR_RETRY
:
222 ScheduleTryToConnect();
230 void BrailleControllerImpl::ResetRetryConnectHorizon() {
231 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
232 retry_connect_horizon_
= Time::Now() + TimeDelta::FromMilliseconds(
233 kConnectRetryTimeout
);
236 void BrailleControllerImpl::ScheduleTryToConnect() {
237 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
238 TimeDelta
delay(TimeDelta::FromMilliseconds(kConnectionDelayMs
));
239 // Don't reschedule if there's already a connect scheduled or
240 // the next attempt would fall outside of the retry limit.
241 if (connect_scheduled_
)
243 if (Time::Now() + delay
> retry_connect_horizon_
) {
244 VLOG(1) << "Stopping to retry to connect to brlapi";
247 VLOG(1) << "Scheduling connection retry to brlapi";
248 connect_scheduled_
= true;
249 BrowserThread::PostDelayedTask(BrowserThread::IO
, FROM_HERE
,
251 &BrailleControllerImpl::TryToConnect
,
252 base::Unretained(this)),
256 void BrailleControllerImpl::Disconnect() {
257 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
258 if (!connection_
|| !connection_
->Connected())
260 connection_
->Disconnect();
261 DispatchOnDisplayStateChanged(scoped_ptr
<DisplayState
>(new DisplayState()));
264 scoped_ptr
<BrlapiConnection
> BrailleControllerImpl::CreateBrlapiConnection() {
265 DCHECK(libbrlapi_loader_
.loaded());
266 return BrlapiConnection::Create(&libbrlapi_loader_
);
269 void BrailleControllerImpl::DispatchKeys() {
270 DCHECK(connection_
.get());
271 brlapi_keyCode_t code
;
273 int result
= connection_
->ReadKey(&code
);
274 if (result
< 0) { // Error.
275 brlapi_error_t
* err
= connection_
->BrlapiError();
276 if (err
->brlerrno
== BRLAPI_ERROR_LIBCERR
&& err
->libcerrno
== EINTR
)
278 // Disconnect on other errors.
279 VLOG(1) << "BrlAPI error: " << connection_
->BrlapiStrError();
282 } else if (result
== 0) { // No more data.
285 scoped_ptr
<KeyEvent
> event
= BrlapiKeyCodeToEvent(code
);
287 DispatchKeyEvent(event
.Pass());
291 void BrailleControllerImpl::DispatchKeyEvent(scoped_ptr
<KeyEvent
> event
) {
292 if (!BrowserThread::CurrentlyOn(BrowserThread::UI
)) {
293 BrowserThread::PostTask(BrowserThread::UI
, FROM_HERE
,
295 &BrailleControllerImpl::DispatchKeyEvent
,
296 base::Unretained(this),
297 base::Passed(&event
)));
300 VLOG(1) << "Dispatching key event: " << *event
->ToValue();
301 FOR_EACH_OBSERVER(BrailleObserver
, observers_
, OnBrailleKeyEvent(*event
));
304 void BrailleControllerImpl::DispatchOnDisplayStateChanged(
305 scoped_ptr
<DisplayState
> new_state
) {
306 if (!BrowserThread::CurrentlyOn(BrowserThread::UI
)) {
307 if (!BrowserThread::PostTask(
308 BrowserThread::UI
, FROM_HERE
,
309 base::Bind(&BrailleControllerImpl::DispatchOnDisplayStateChanged
,
310 base::Unretained(this),
311 base::Passed(&new_state
)))) {
316 FOR_EACH_OBSERVER(BrailleObserver
, observers_
,
317 OnBrailleDisplayStateChanged(*new_state
));
320 } // namespace braille_display_private
322 } // namespace extensions