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