• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.
4 
5 #include "chrome/browser/extensions/api/braille_display_private/braille_controller_brlapi.h"
6 
7 #include <algorithm>
8 #include <cerrno>
9 #include <cstring>
10 #include <vector>
11 
12 #include "base/bind.h"
13 #include "base/bind_helpers.h"
14 #include "base/time/time.h"
15 #include "chrome/browser/extensions/api/braille_display_private/brlapi_connection.h"
16 #include "chrome/browser/extensions/api/braille_display_private/brlapi_keycode_map.h"
17 #include "content/public/browser/browser_thread.h"
18 
19 namespace extensions {
20 using content::BrowserThread;
21 using base::Time;
22 using base::TimeDelta;
23 namespace api {
24 namespace braille_display_private {
25 
26 namespace {
27 // Delay between detecting a directory update and trying to connect
28 // to the brlapi.
29 const int64 kConnectionDelayMs = 500;
30 // How long to periodically retry connecting after a brltty restart.
31 // Some displays are slow to connect.
32 const int64 kConnectRetryTimeout = 20000;
33 }  // namespace
34 
BrailleController()35 BrailleController::BrailleController() {
36 }
37 
~BrailleController()38 BrailleController::~BrailleController() {
39 }
40 
41 // static
GetInstance()42 BrailleController* BrailleController::GetInstance() {
43   return BrailleControllerImpl::GetInstance();
44 }
45 
46 // static
GetInstance()47 BrailleControllerImpl* BrailleControllerImpl::GetInstance() {
48   return Singleton<BrailleControllerImpl,
49                    LeakySingletonTraits<BrailleControllerImpl> >::get();
50 }
51 
BrailleControllerImpl()52 BrailleControllerImpl::BrailleControllerImpl()
53     : started_connecting_(false),
54       connect_scheduled_(false) {
55   create_brlapi_connection_function_ = base::Bind(
56       &BrailleControllerImpl::CreateBrlapiConnection,
57       base::Unretained(this));
58 }
59 
~BrailleControllerImpl()60 BrailleControllerImpl::~BrailleControllerImpl() {
61 }
62 
TryLoadLibBrlApi()63 void BrailleControllerImpl::TryLoadLibBrlApi() {
64   DCHECK_CURRENTLY_ON(BrowserThread::IO);
65   if (libbrlapi_loader_.loaded())
66     return;
67   // These versions of libbrlapi work the same for the functions we
68   // are using.  (0.6.0 adds brlapi_writeWText).
69   static const char* kSupportedVersions[] = {
70     "libbrlapi.so.0.5",
71     "libbrlapi.so.0.6"
72   };
73   for (size_t i = 0; i < arraysize(kSupportedVersions); ++i) {
74     if (libbrlapi_loader_.Load(kSupportedVersions[i]))
75       return;
76   }
77   LOG(WARNING) << "Couldn't load libbrlapi: " << strerror(errno);
78 }
79 
GetDisplayState()80 scoped_ptr<DisplayState> BrailleControllerImpl::GetDisplayState() {
81   DCHECK_CURRENTLY_ON(BrowserThread::IO);
82   StartConnecting();
83   scoped_ptr<DisplayState> display_state(new DisplayState);
84   if (connection_.get() && connection_->Connected()) {
85     size_t size;
86     if (!connection_->GetDisplaySize(&size)) {
87       Disconnect();
88     } else if (size > 0) {  // size == 0 means no display present.
89       display_state->available = true;
90       display_state->text_cell_count.reset(new int(size));
91     }
92   }
93   return display_state.Pass();
94 }
95 
WriteDots(const std::string & cells)96 void BrailleControllerImpl::WriteDots(const std::string& cells) {
97   DCHECK_CURRENTLY_ON(BrowserThread::IO);
98   if (connection_ && connection_->Connected()) {
99     size_t size;
100     if (!connection_->GetDisplaySize(&size)) {
101       Disconnect();
102     }
103     std::vector<unsigned char> sizedCells(size);
104     std::memcpy(&sizedCells[0], cells.data(), std::min(cells.size(), size));
105     if (size > cells.size())
106       std::fill(sizedCells.begin() + cells.size(), sizedCells.end(), 0);
107     if (!connection_->WriteDots(&sizedCells[0]))
108       Disconnect();
109   }
110 }
111 
AddObserver(BrailleObserver * observer)112 void BrailleControllerImpl::AddObserver(BrailleObserver* observer) {
113   DCHECK_CURRENTLY_ON(BrowserThread::UI);
114   if (!BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
115                                base::Bind(
116                                    &BrailleControllerImpl::StartConnecting,
117                                    base::Unretained(this)))) {
118     NOTREACHED();
119   }
120   observers_.AddObserver(observer);
121 }
122 
RemoveObserver(BrailleObserver * observer)123 void BrailleControllerImpl::RemoveObserver(BrailleObserver* observer) {
124   DCHECK_CURRENTLY_ON(BrowserThread::UI);
125   observers_.RemoveObserver(observer);
126 }
127 
SetCreateBrlapiConnectionForTesting(const CreateBrlapiConnectionFunction & function)128 void BrailleControllerImpl::SetCreateBrlapiConnectionForTesting(
129     const CreateBrlapiConnectionFunction& function) {
130   if (function.is_null()) {
131     create_brlapi_connection_function_ = base::Bind(
132         &BrailleControllerImpl::CreateBrlapiConnection,
133         base::Unretained(this));
134   } else {
135     create_brlapi_connection_function_ = function;
136   }
137 }
138 
PokeSocketDirForTesting()139 void BrailleControllerImpl::PokeSocketDirForTesting() {
140   OnSocketDirChangedOnIOThread();
141 }
142 
StartConnecting()143 void BrailleControllerImpl::StartConnecting() {
144   DCHECK_CURRENTLY_ON(BrowserThread::IO);
145   if (started_connecting_)
146     return;
147   started_connecting_ = true;
148   TryLoadLibBrlApi();
149   if (!libbrlapi_loader_.loaded()) {
150     return;
151   }
152   // Only try to connect after we've started to watch the
153   // socket directory.  This is necessary to avoid a race condition
154   // and because we don't retry to connect after errors that will
155   // persist until there's a change to the socket directory (i.e.
156   // ENOENT).
157   BrowserThread::PostTaskAndReply(
158       BrowserThread::FILE, FROM_HERE,
159       base::Bind(
160           &BrailleControllerImpl::StartWatchingSocketDirOnFileThread,
161           base::Unretained(this)),
162       base::Bind(
163           &BrailleControllerImpl::TryToConnect,
164           base::Unretained(this)));
165   ResetRetryConnectHorizon();
166 }
167 
StartWatchingSocketDirOnFileThread()168 void BrailleControllerImpl::StartWatchingSocketDirOnFileThread() {
169   DCHECK_CURRENTLY_ON(BrowserThread::FILE);
170   base::FilePath brlapi_dir(BRLAPI_SOCKETPATH);
171   if (!file_path_watcher_.Watch(
172           brlapi_dir, false, base::Bind(
173               &BrailleControllerImpl::OnSocketDirChangedOnFileThread,
174               base::Unretained(this)))) {
175     LOG(WARNING) << "Couldn't watch brlapi directory " << BRLAPI_SOCKETPATH;
176   }
177 }
178 
OnSocketDirChangedOnFileThread(const base::FilePath & path,bool error)179 void BrailleControllerImpl::OnSocketDirChangedOnFileThread(
180     const base::FilePath& path, bool error) {
181   DCHECK_CURRENTLY_ON(BrowserThread::FILE);
182   if (error) {
183     LOG(ERROR) << "Error watching brlapi directory: " << path.value();
184     return;
185   }
186   BrowserThread::PostTask(
187       BrowserThread::IO, FROM_HERE, base::Bind(
188           &BrailleControllerImpl::OnSocketDirChangedOnIOThread,
189           base::Unretained(this)));
190 }
191 
OnSocketDirChangedOnIOThread()192 void BrailleControllerImpl::OnSocketDirChangedOnIOThread() {
193   DCHECK_CURRENTLY_ON(BrowserThread::IO);
194   VLOG(1) << "BrlAPI directory changed";
195   // Every directory change resets the max retry time to the appropriate delay
196   // into the future.
197   ResetRetryConnectHorizon();
198   // Try after an initial delay to give the driver a chance to connect.
199   ScheduleTryToConnect();
200 }
201 
TryToConnect()202 void BrailleControllerImpl::TryToConnect() {
203   DCHECK_CURRENTLY_ON(BrowserThread::IO);
204   DCHECK(libbrlapi_loader_.loaded());
205   connect_scheduled_ = false;
206   if (!connection_.get())
207     connection_ = create_brlapi_connection_function_.Run();
208   if (connection_.get() && !connection_->Connected()) {
209     VLOG(1) << "Trying to connect to brlapi";
210     BrlapiConnection::ConnectResult result = connection_->Connect(base::Bind(
211         &BrailleControllerImpl::DispatchKeys,
212         base::Unretained(this)));
213     switch (result) {
214       case BrlapiConnection::CONNECT_SUCCESS:
215         DispatchOnDisplayStateChanged(GetDisplayState());
216         break;
217       case BrlapiConnection::CONNECT_ERROR_NO_RETRY:
218         break;
219       case BrlapiConnection::CONNECT_ERROR_RETRY:
220         ScheduleTryToConnect();
221         break;
222       default:
223         NOTREACHED();
224     }
225   }
226 }
227 
ResetRetryConnectHorizon()228 void BrailleControllerImpl::ResetRetryConnectHorizon() {
229   DCHECK_CURRENTLY_ON(BrowserThread::IO);
230   retry_connect_horizon_ = Time::Now() + TimeDelta::FromMilliseconds(
231       kConnectRetryTimeout);
232 }
233 
ScheduleTryToConnect()234 void BrailleControllerImpl::ScheduleTryToConnect() {
235   DCHECK_CURRENTLY_ON(BrowserThread::IO);
236   TimeDelta delay(TimeDelta::FromMilliseconds(kConnectionDelayMs));
237   // Don't reschedule if there's already a connect scheduled or
238   // the next attempt would fall outside of the retry limit.
239   if (connect_scheduled_)
240     return;
241   if (Time::Now() + delay > retry_connect_horizon_) {
242     VLOG(1) << "Stopping to retry to connect to brlapi";
243     return;
244   }
245   VLOG(1) << "Scheduling connection retry to brlapi";
246   connect_scheduled_ = true;
247   BrowserThread::PostDelayedTask(BrowserThread::IO, FROM_HERE,
248                                  base::Bind(
249                                      &BrailleControllerImpl::TryToConnect,
250                                      base::Unretained(this)),
251                                  delay);
252 }
253 
Disconnect()254 void BrailleControllerImpl::Disconnect() {
255   DCHECK_CURRENTLY_ON(BrowserThread::IO);
256   if (!connection_ || !connection_->Connected())
257     return;
258   connection_->Disconnect();
259   DispatchOnDisplayStateChanged(scoped_ptr<DisplayState>(new DisplayState()));
260 }
261 
CreateBrlapiConnection()262 scoped_ptr<BrlapiConnection> BrailleControllerImpl::CreateBrlapiConnection() {
263   DCHECK(libbrlapi_loader_.loaded());
264   return BrlapiConnection::Create(&libbrlapi_loader_);
265 }
266 
DispatchKeys()267 void BrailleControllerImpl::DispatchKeys() {
268   DCHECK(connection_.get());
269   brlapi_keyCode_t code;
270   while (true) {
271     int result = connection_->ReadKey(&code);
272     if (result < 0) {  // Error.
273       brlapi_error_t* err = connection_->BrlapiError();
274       if (err->brlerrno == BRLAPI_ERROR_LIBCERR && err->libcerrno == EINTR)
275         continue;
276       // Disconnect on other errors.
277       VLOG(1) << "BrlAPI error: " << connection_->BrlapiStrError();
278       Disconnect();
279       return;
280     } else if (result == 0) { // No more data.
281       return;
282     }
283     scoped_ptr<KeyEvent> event = BrlapiKeyCodeToEvent(code);
284     if (event)
285       DispatchKeyEvent(event.Pass());
286   }
287 }
288 
DispatchKeyEvent(scoped_ptr<KeyEvent> event)289 void BrailleControllerImpl::DispatchKeyEvent(scoped_ptr<KeyEvent> event) {
290   if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) {
291     BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
292                             base::Bind(
293                                 &BrailleControllerImpl::DispatchKeyEvent,
294                                 base::Unretained(this),
295                                 base::Passed(&event)));
296     return;
297   }
298   VLOG(1) << "Dispatching key event: " << *event->ToValue();
299   FOR_EACH_OBSERVER(BrailleObserver, observers_, OnBrailleKeyEvent(*event));
300 }
301 
DispatchOnDisplayStateChanged(scoped_ptr<DisplayState> new_state)302 void BrailleControllerImpl::DispatchOnDisplayStateChanged(
303     scoped_ptr<DisplayState> new_state) {
304   if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) {
305     if (!BrowserThread::PostTask(
306             BrowserThread::UI, FROM_HERE,
307             base::Bind(&BrailleControllerImpl::DispatchOnDisplayStateChanged,
308                        base::Unretained(this),
309                        base::Passed(&new_state)))) {
310       NOTREACHED();
311     }
312     return;
313   }
314   FOR_EACH_OBSERVER(BrailleObserver, observers_,
315                     OnBrailleDisplayStateChanged(*new_state));
316 }
317 
318 }  // namespace braille_display_private
319 }  // namespace api
320 }  // namespace extensions
321