Pin Chrome's shortcut to the Win10 Start menu on install and OS upgrade.
[chromium-blink-merge.git] / chrome / browser / extensions / api / braille_display_private / braille_controller_brlapi.cc
blob0ead4f57968133d329acee3cb9e9afd326343df2
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 Singleton<BrailleControllerImpl,
50 LeakySingletonTraits<BrailleControllerImpl> >::get();
53 BrailleControllerImpl::BrailleControllerImpl()
54 : started_connecting_(false),
55 connect_scheduled_(false) {
56 create_brlapi_connection_function_ = base::Bind(
57 &BrailleControllerImpl::CreateBrlapiConnection,
58 base::Unretained(this));
61 BrailleControllerImpl::~BrailleControllerImpl() {
64 void BrailleControllerImpl::TryLoadLibBrlApi() {
65 DCHECK_CURRENTLY_ON(BrowserThread::IO);
66 if (libbrlapi_loader_.loaded())
67 return;
68 // These versions of libbrlapi work the same for the functions we
69 // are using. (0.6.0 adds brlapi_writeWText).
70 static const char* const kSupportedVersions[] = {
71 "libbrlapi.so.0.5",
72 "libbrlapi.so.0.6"
74 for (size_t i = 0; i < arraysize(kSupportedVersions); ++i) {
75 if (libbrlapi_loader_.Load(kSupportedVersions[i]))
76 return;
78 LOG(WARNING) << "Couldn't load libbrlapi: " << strerror(errno);
81 scoped_ptr<DisplayState> BrailleControllerImpl::GetDisplayState() {
82 DCHECK_CURRENTLY_ON(BrowserThread::IO);
83 StartConnecting();
84 scoped_ptr<DisplayState> display_state(new DisplayState);
85 if (connection_.get() && connection_->Connected()) {
86 size_t size;
87 if (!connection_->GetDisplaySize(&size)) {
88 Disconnect();
89 } else if (size > 0) { // size == 0 means no display present.
90 display_state->available = true;
91 display_state->text_cell_count.reset(new int(size));
94 return display_state.Pass();
97 void BrailleControllerImpl::WriteDots(const std::vector<char>& cells) {
98 DCHECK_CURRENTLY_ON(BrowserThread::IO);
99 if (connection_ && connection_->Connected()) {
100 size_t size;
101 if (!connection_->GetDisplaySize(&size)) {
102 Disconnect();
104 std::vector<unsigned char> sizedCells(size);
105 std::memcpy(&sizedCells[0], vector_as_array(&cells),
106 std::min(cells.size(), size));
107 if (size > cells.size())
108 std::fill(sizedCells.begin() + cells.size(), sizedCells.end(), 0);
109 if (!connection_->WriteDots(&sizedCells[0]))
110 Disconnect();
114 void BrailleControllerImpl::AddObserver(BrailleObserver* observer) {
115 DCHECK_CURRENTLY_ON(BrowserThread::UI);
116 if (!BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
117 base::Bind(
118 &BrailleControllerImpl::StartConnecting,
119 base::Unretained(this)))) {
120 NOTREACHED();
122 observers_.AddObserver(observer);
125 void BrailleControllerImpl::RemoveObserver(BrailleObserver* observer) {
126 DCHECK_CURRENTLY_ON(BrowserThread::UI);
127 observers_.RemoveObserver(observer);
130 void BrailleControllerImpl::SetCreateBrlapiConnectionForTesting(
131 const CreateBrlapiConnectionFunction& function) {
132 if (function.is_null()) {
133 create_brlapi_connection_function_ = base::Bind(
134 &BrailleControllerImpl::CreateBrlapiConnection,
135 base::Unretained(this));
136 } else {
137 create_brlapi_connection_function_ = function;
141 void BrailleControllerImpl::PokeSocketDirForTesting() {
142 OnSocketDirChangedOnIOThread();
145 void BrailleControllerImpl::StartConnecting() {
146 DCHECK_CURRENTLY_ON(BrowserThread::IO);
147 if (started_connecting_)
148 return;
149 started_connecting_ = true;
150 TryLoadLibBrlApi();
151 if (!libbrlapi_loader_.loaded()) {
152 return;
154 // Only try to connect after we've started to watch the
155 // socket directory. This is necessary to avoid a race condition
156 // and because we don't retry to connect after errors that will
157 // persist until there's a change to the socket directory (i.e.
158 // ENOENT).
159 BrowserThread::PostTaskAndReply(
160 BrowserThread::FILE, FROM_HERE,
161 base::Bind(
162 &BrailleControllerImpl::StartWatchingSocketDirOnFileThread,
163 base::Unretained(this)),
164 base::Bind(
165 &BrailleControllerImpl::TryToConnect,
166 base::Unretained(this)));
167 ResetRetryConnectHorizon();
170 void BrailleControllerImpl::StartWatchingSocketDirOnFileThread() {
171 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
172 base::FilePath brlapi_dir(BRLAPI_SOCKETPATH);
173 if (!file_path_watcher_.Watch(
174 brlapi_dir, false, base::Bind(
175 &BrailleControllerImpl::OnSocketDirChangedOnFileThread,
176 base::Unretained(this)))) {
177 LOG(WARNING) << "Couldn't watch brlapi directory " << BRLAPI_SOCKETPATH;
181 void BrailleControllerImpl::OnSocketDirChangedOnFileThread(
182 const base::FilePath& path, bool error) {
183 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
184 if (error) {
185 LOG(ERROR) << "Error watching brlapi directory: " << path.value();
186 return;
188 BrowserThread::PostTask(
189 BrowserThread::IO, FROM_HERE, base::Bind(
190 &BrailleControllerImpl::OnSocketDirChangedOnIOThread,
191 base::Unretained(this)));
194 void BrailleControllerImpl::OnSocketDirChangedOnIOThread() {
195 DCHECK_CURRENTLY_ON(BrowserThread::IO);
196 VLOG(1) << "BrlAPI directory changed";
197 // Every directory change resets the max retry time to the appropriate delay
198 // into the future.
199 ResetRetryConnectHorizon();
200 // Try after an initial delay to give the driver a chance to connect.
201 ScheduleTryToConnect();
204 void BrailleControllerImpl::TryToConnect() {
205 DCHECK_CURRENTLY_ON(BrowserThread::IO);
206 DCHECK(libbrlapi_loader_.loaded());
207 connect_scheduled_ = false;
208 if (!connection_.get())
209 connection_ = create_brlapi_connection_function_.Run();
210 if (connection_.get() && !connection_->Connected()) {
211 VLOG(1) << "Trying to connect to brlapi";
212 BrlapiConnection::ConnectResult result = connection_->Connect(base::Bind(
213 &BrailleControllerImpl::DispatchKeys,
214 base::Unretained(this)));
215 switch (result) {
216 case BrlapiConnection::CONNECT_SUCCESS:
217 DispatchOnDisplayStateChanged(GetDisplayState());
218 break;
219 case BrlapiConnection::CONNECT_ERROR_NO_RETRY:
220 break;
221 case BrlapiConnection::CONNECT_ERROR_RETRY:
222 ScheduleTryToConnect();
223 break;
224 default:
225 NOTREACHED();
230 void BrailleControllerImpl::ResetRetryConnectHorizon() {
231 DCHECK_CURRENTLY_ON(BrowserThread::IO);
232 retry_connect_horizon_ = Time::Now() + TimeDelta::FromMilliseconds(
233 kConnectRetryTimeout);
236 void BrailleControllerImpl::ScheduleTryToConnect() {
237 DCHECK_CURRENTLY_ON(BrowserThread::IO);
238 TimeDelta delay(TimeDelta::FromMilliseconds(kConnectionDelayMs));
239 // Don't reschedule if there's already a connect scheduled or
240 // the next attempt would fall outside of the retry limit.
241 if (connect_scheduled_)
242 return;
243 if (Time::Now() + delay > retry_connect_horizon_) {
244 VLOG(1) << "Stopping to retry to connect to brlapi";
245 return;
247 VLOG(1) << "Scheduling connection retry to brlapi";
248 connect_scheduled_ = true;
249 BrowserThread::PostDelayedTask(BrowserThread::IO, FROM_HERE,
250 base::Bind(
251 &BrailleControllerImpl::TryToConnect,
252 base::Unretained(this)),
253 delay);
256 void BrailleControllerImpl::Disconnect() {
257 DCHECK_CURRENTLY_ON(BrowserThread::IO);
258 if (!connection_ || !connection_->Connected())
259 return;
260 connection_->Disconnect();
261 DispatchOnDisplayStateChanged(scoped_ptr<DisplayState>(new DisplayState()));
264 scoped_ptr<BrlapiConnection> BrailleControllerImpl::CreateBrlapiConnection() {
265 DCHECK(libbrlapi_loader_.loaded());
266 return BrlapiConnection::Create(&libbrlapi_loader_);
269 void BrailleControllerImpl::DispatchKeys() {
270 DCHECK(connection_.get());
271 brlapi_keyCode_t code;
272 while (true) {
273 int result = connection_->ReadKey(&code);
274 if (result < 0) { // Error.
275 brlapi_error_t* err = connection_->BrlapiError();
276 if (err->brlerrno == BRLAPI_ERROR_LIBCERR && err->libcerrno == EINTR)
277 continue;
278 // Disconnect on other errors.
279 VLOG(1) << "BrlAPI error: " << connection_->BrlapiStrError();
280 Disconnect();
281 return;
282 } else if (result == 0) { // No more data.
283 return;
285 scoped_ptr<KeyEvent> event = BrlapiKeyCodeToEvent(code);
286 if (event)
287 DispatchKeyEvent(event.Pass());
291 void BrailleControllerImpl::DispatchKeyEvent(scoped_ptr<KeyEvent> event) {
292 if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) {
293 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
294 base::Bind(
295 &BrailleControllerImpl::DispatchKeyEvent,
296 base::Unretained(this),
297 base::Passed(&event)));
298 return;
300 VLOG(1) << "Dispatching key event: " << *event->ToValue();
301 FOR_EACH_OBSERVER(BrailleObserver, observers_, OnBrailleKeyEvent(*event));
304 void BrailleControllerImpl::DispatchOnDisplayStateChanged(
305 scoped_ptr<DisplayState> new_state) {
306 if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) {
307 if (!BrowserThread::PostTask(
308 BrowserThread::UI, FROM_HERE,
309 base::Bind(&BrailleControllerImpl::DispatchOnDisplayStateChanged,
310 base::Unretained(this),
311 base::Passed(&new_state)))) {
312 NOTREACHED();
314 return;
316 FOR_EACH_OBSERVER(BrailleObserver, observers_,
317 OnBrailleDisplayStateChanged(*new_state));
320 } // namespace braille_display_private
321 } // namespace api
322 } // namespace extensions