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