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