普通文本  |  329行  |  10.39 KB

// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <cmath>
#include <set>
#include <vector>

#include "base/bind.h"
#include "base/logging.h"
#include "base/message_loop/message_loop.h"
#include "base/message_loop/message_loop_proxy.h"
#include "base/threading/thread.h"
#include "base/threading/thread_restrictions.h"
#include "content/browser/gamepad/gamepad_data_fetcher.h"
#include "content/browser/gamepad/gamepad_platform_data_fetcher.h"
#include "content/browser/gamepad/gamepad_provider.h"
#include "content/browser/gamepad/gamepad_service.h"
#include "content/common/gamepad_hardware_buffer.h"
#include "content/common/gamepad_messages.h"
#include "content/common/gamepad_user_gesture.h"
#include "content/public/browser/browser_thread.h"

using blink::WebGamepad;
using blink::WebGamepads;

namespace content {

GamepadProvider::ClosureAndThread::ClosureAndThread(
    const base::Closure& c,
    const scoped_refptr<base::MessageLoopProxy>& m)
    : closure(c),
      message_loop(m) {
}

GamepadProvider::ClosureAndThread::~ClosureAndThread() {
}

GamepadProvider::GamepadProvider()
    : is_paused_(true),
      have_scheduled_do_poll_(false),
      devices_changed_(true),
      ever_had_user_gesture_(false) {
  Initialize(scoped_ptr<GamepadDataFetcher>());
}

GamepadProvider::GamepadProvider(scoped_ptr<GamepadDataFetcher> fetcher)
    : is_paused_(true),
      have_scheduled_do_poll_(false),
      devices_changed_(true),
      ever_had_user_gesture_(false) {
  Initialize(fetcher.Pass());
}

GamepadProvider::~GamepadProvider() {
  base::SystemMonitor* monitor = base::SystemMonitor::Get();
  if (monitor)
    monitor->RemoveDevicesChangedObserver(this);

  // Use Stop() to join the polling thread, as there may be pending callbacks
  // which dereference |polling_thread_|.
  polling_thread_->Stop();
  data_fetcher_.reset();
}

base::SharedMemoryHandle GamepadProvider::GetSharedMemoryHandleForProcess(
    base::ProcessHandle process) {
  base::SharedMemoryHandle renderer_handle;
  gamepad_shared_memory_.ShareToProcess(process, &renderer_handle);
  return renderer_handle;
}

void GamepadProvider::GetCurrentGamepadData(WebGamepads* data) {
  const WebGamepads& pads = SharedMemoryAsHardwareBuffer()->buffer;
  base::AutoLock lock(shared_memory_lock_);
  *data = pads;
}

void GamepadProvider::Pause() {
  {
    base::AutoLock lock(is_paused_lock_);
    is_paused_ = true;
  }
  base::MessageLoop* polling_loop = polling_thread_->message_loop();
  polling_loop->PostTask(
      FROM_HERE,
      base::Bind(&GamepadProvider::SendPauseHint, Unretained(this), true));
}

void GamepadProvider::Resume() {
  {
    base::AutoLock lock(is_paused_lock_);
    if (!is_paused_)
        return;
    is_paused_ = false;
  }

  base::MessageLoop* polling_loop = polling_thread_->message_loop();
  polling_loop->PostTask(
      FROM_HERE,
      base::Bind(&GamepadProvider::SendPauseHint, Unretained(this), false));
  polling_loop->PostTask(
      FROM_HERE,
      base::Bind(&GamepadProvider::ScheduleDoPoll, Unretained(this)));
}

void GamepadProvider::RegisterForUserGesture(const base::Closure& closure) {
  base::AutoLock lock(user_gesture_lock_);
  user_gesture_observers_.push_back(ClosureAndThread(
      closure, base::MessageLoop::current()->message_loop_proxy()));
}

void GamepadProvider::OnDevicesChanged(base::SystemMonitor::DeviceType type) {
  base::AutoLock lock(devices_changed_lock_);
  devices_changed_ = true;
}

void GamepadProvider::Initialize(scoped_ptr<GamepadDataFetcher> fetcher) {
  size_t data_size = sizeof(GamepadHardwareBuffer);
  base::SystemMonitor* monitor = base::SystemMonitor::Get();
  if (monitor)
    monitor->AddDevicesChangedObserver(this);
  bool res = gamepad_shared_memory_.CreateAndMapAnonymous(data_size);
  CHECK(res);
  GamepadHardwareBuffer* hwbuf = SharedMemoryAsHardwareBuffer();
  memset(hwbuf, 0, sizeof(GamepadHardwareBuffer));
  pad_states_.reset(new PadState[WebGamepads::itemsLengthCap]);

  polling_thread_.reset(new base::Thread("Gamepad polling thread"));
#if defined(OS_LINUX)
  // On Linux, the data fetcher needs to watch file descriptors, so the message
  // loop needs to be a libevent loop.
  const base::MessageLoop::Type kMessageLoopType = base::MessageLoop::TYPE_IO;
#elif defined(OS_ANDROID)
  // On Android, keeping a message loop of default type.
  const base::MessageLoop::Type kMessageLoopType =
      base::MessageLoop::TYPE_DEFAULT;
#else
  // On Mac, the data fetcher uses IOKit which depends on CFRunLoop, so the
  // message loop needs to be a UI-type loop. On Windows it must be a UI loop
  // to properly pump the MessageWindow that captures device state.
  const base::MessageLoop::Type kMessageLoopType = base::MessageLoop::TYPE_UI;
#endif
  polling_thread_->StartWithOptions(base::Thread::Options(kMessageLoopType, 0));

  polling_thread_->message_loop()->PostTask(
      FROM_HERE,
      base::Bind(&GamepadProvider::DoInitializePollingThread,
                 base::Unretained(this),
                 base::Passed(&fetcher)));
}

void GamepadProvider::DoInitializePollingThread(
    scoped_ptr<GamepadDataFetcher> fetcher) {
  DCHECK(base::MessageLoop::current() == polling_thread_->message_loop());
  DCHECK(!data_fetcher_.get());  // Should only initialize once.

  if (!fetcher)
    fetcher.reset(new GamepadPlatformDataFetcher);
  data_fetcher_ = fetcher.Pass();
}

void GamepadProvider::SendPauseHint(bool paused) {
  DCHECK(base::MessageLoop::current() == polling_thread_->message_loop());
  if (data_fetcher_)
    data_fetcher_->PauseHint(paused);
}

bool GamepadProvider::PadState::Match(const WebGamepad& pad) const {
  return connected_ == pad.connected &&
         axes_length_ == pad.axesLength &&
         buttons_length_ == pad.buttonsLength &&
         memcmp(id_, pad.id, arraysize(id_)) == 0 &&
         memcmp(mapping_, pad.mapping, arraysize(mapping_)) == 0;
}

void GamepadProvider::PadState::SetPad(const WebGamepad& pad) {
  connected_ = pad.connected;
  axes_length_ = pad.axesLength;
  buttons_length_ = pad.buttonsLength;
  memcpy(id_, pad.id, arraysize(id_));
  memcpy(mapping_, pad.mapping, arraysize(mapping_));
}

void GamepadProvider::PadState::SetDisconnected() {
  connected_ = false;
  axes_length_ = 0;
  buttons_length_ = 0;
  memset(id_, 0, arraysize(id_));
  memset(mapping_, 0, arraysize(mapping_));
}

void GamepadProvider::PadState::AsWebGamepad(WebGamepad* pad) {
  pad->connected = connected_;
  pad->axesLength = axes_length_;
  pad->buttonsLength = buttons_length_;
  memcpy(pad->id, id_, arraysize(id_));
  memcpy(pad->mapping, mapping_, arraysize(mapping_));
  memset(pad->axes, 0, arraysize(pad->axes));
  memset(pad->buttons, 0, arraysize(pad->buttons));
}

void GamepadProvider::DoPoll() {
  DCHECK(base::MessageLoop::current() == polling_thread_->message_loop());
  DCHECK(have_scheduled_do_poll_);
  have_scheduled_do_poll_ = false;

  bool changed;
  GamepadHardwareBuffer* hwbuf = SharedMemoryAsHardwareBuffer();

  ANNOTATE_BENIGN_RACE_SIZED(
      &hwbuf->buffer,
      sizeof(WebGamepads),
      "Racey reads are discarded");

  {
    base::AutoLock lock(devices_changed_lock_);
    changed = devices_changed_;
    devices_changed_ = false;
  }

  {
    base::AutoLock lock(shared_memory_lock_);

    // Acquire the SeqLock. There is only ever one writer to this data.
    // See gamepad_hardware_buffer.h.
    hwbuf->sequence.WriteBegin();
    data_fetcher_->GetGamepadData(&hwbuf->buffer, changed);
    hwbuf->sequence.WriteEnd();
  }

  if (ever_had_user_gesture_) {
    for (unsigned i = 0; i < WebGamepads::itemsLengthCap; ++i) {
      WebGamepad& pad = hwbuf->buffer.items[i];
      PadState& state = pad_states_.get()[i];
      if (pad.connected && !state.connected()) {
        OnGamepadConnectionChange(true, i, pad);
      } else if (!pad.connected && state.connected()) {
        OnGamepadConnectionChange(false, i, pad);
      } else if (pad.connected && state.connected() && !state.Match(pad)) {
        WebGamepad old_pad;
        state.AsWebGamepad(&old_pad);
        OnGamepadConnectionChange(false, i, old_pad);
        OnGamepadConnectionChange(true, i, pad);
      }
    }
  }

  CheckForUserGesture();

  // Schedule our next interval of polling.
  ScheduleDoPoll();
}

void GamepadProvider::ScheduleDoPoll() {
  DCHECK(base::MessageLoop::current() == polling_thread_->message_loop());
  if (have_scheduled_do_poll_)
    return;

  {
    base::AutoLock lock(is_paused_lock_);
    if (is_paused_)
      return;
  }

  base::MessageLoop::current()->PostDelayedTask(
      FROM_HERE,
      base::Bind(&GamepadProvider::DoPoll, Unretained(this)),
      base::TimeDelta::FromMilliseconds(kDesiredSamplingIntervalMs));
  have_scheduled_do_poll_ = true;
}

void GamepadProvider::OnGamepadConnectionChange(
    bool connected, int index, const WebGamepad& pad) {
  PadState& state = pad_states_.get()[index];
  if (connected)
    state.SetPad(pad);
  else
    state.SetDisconnected();

  BrowserThread::PostTask(
      BrowserThread::IO,
      FROM_HERE,
      base::Bind(&GamepadProvider::DispatchGamepadConnectionChange,
                 base::Unretained(this),
                 connected,
                 index,
                 pad));
}

void GamepadProvider::DispatchGamepadConnectionChange(
    bool connected, int index, const WebGamepad& pad) {
  if (connected)
    GamepadService::GetInstance()->OnGamepadConnected(index, pad);
  else
    GamepadService::GetInstance()->OnGamepadDisconnected(index, pad);
}

GamepadHardwareBuffer* GamepadProvider::SharedMemoryAsHardwareBuffer() {
  void* mem = gamepad_shared_memory_.memory();
  CHECK(mem);
  return static_cast<GamepadHardwareBuffer*>(mem);
}

void GamepadProvider::CheckForUserGesture() {
  base::AutoLock lock(user_gesture_lock_);
  if (user_gesture_observers_.empty() && ever_had_user_gesture_)
    return;

  bool had_gesture_before = ever_had_user_gesture_;
  const WebGamepads& pads = SharedMemoryAsHardwareBuffer()->buffer;
  if (GamepadsHaveUserGesture(pads)) {
    ever_had_user_gesture_ = true;
    for (size_t i = 0; i < user_gesture_observers_.size(); i++) {
      user_gesture_observers_[i].message_loop->PostTask(FROM_HERE,
          user_gesture_observers_[i].closure);
    }
    user_gesture_observers_.clear();
  }
  if (!had_gesture_before && ever_had_user_gesture_) {
    // Initialize pad_states_ for the first time.
    for (size_t i = 0; i < WebGamepads::itemsLengthCap; ++i) {
      pad_states_.get()[i].SetPad(pads.items[i]);
    }
  }
}

}  // namespace content