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