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