Home | History | Annotate | Download | only in braille_display_private
      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 
     35 BrailleController::BrailleController() {
     36 }
     37 
     38 BrailleController::~BrailleController() {
     39 }
     40 
     41 // static
     42 BrailleController* BrailleController::GetInstance() {
     43   return BrailleControllerImpl::GetInstance();
     44 }
     45 
     46 // static
     47 BrailleControllerImpl* BrailleControllerImpl::GetInstance() {
     48   return Singleton<BrailleControllerImpl,
     49                    LeakySingletonTraits<BrailleControllerImpl> >::get();
     50 }
     51 
     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 
     60 BrailleControllerImpl::~BrailleControllerImpl() {
     61 }
     62 
     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 
     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 
     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 
    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 
    123 void BrailleControllerImpl::RemoveObserver(BrailleObserver* observer) {
    124   DCHECK_CURRENTLY_ON(BrowserThread::UI);
    125   observers_.RemoveObserver(observer);
    126 }
    127 
    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 
    139 void BrailleControllerImpl::PokeSocketDirForTesting() {
    140   OnSocketDirChangedOnIOThread();
    141 }
    142 
    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 
    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 
    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 
    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 
    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 
    228 void BrailleControllerImpl::ResetRetryConnectHorizon() {
    229   DCHECK_CURRENTLY_ON(BrowserThread::IO);
    230   retry_connect_horizon_ = Time::Now() + TimeDelta::FromMilliseconds(
    231       kConnectRetryTimeout);
    232 }
    233 
    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 
    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 
    262 scoped_ptr<BrlapiConnection> BrailleControllerImpl::CreateBrlapiConnection() {
    263   DCHECK(libbrlapi_loader_.loaded());
    264   return BrlapiConnection::Create(&libbrlapi_loader_);
    265 }
    266 
    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 
    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 
    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