| // 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 "tools/android/forwarder2/device_controller.h" |
| |
| #include <utility> |
| |
| #include "base/basictypes.h" |
| #include "base/bind.h" |
| #include "base/callback_helpers.h" |
| #include "base/logging.h" |
| #include "base/memory/scoped_ptr.h" |
| #include "base/single_thread_task_runner.h" |
| #include "base/thread_task_runner_handle.h" |
| #include "tools/android/forwarder2/command.h" |
| #include "tools/android/forwarder2/device_listener.h" |
| #include "tools/android/forwarder2/socket.h" |
| #include "tools/android/forwarder2/util.h" |
| |
| namespace forwarder2 { |
| |
| // static |
| scoped_ptr<DeviceController> DeviceController::Create( |
| const std::string& adb_unix_socket, |
| int exit_notifier_fd) { |
| scoped_ptr<DeviceController> device_controller; |
| scoped_ptr<Socket> host_socket(new Socket()); |
| if (!host_socket->BindUnix(adb_unix_socket)) { |
| PLOG(ERROR) << "Could not BindAndListen DeviceController socket on port " |
| << adb_unix_socket << ": "; |
| return device_controller.Pass(); |
| } |
| LOG(INFO) << "Listening on Unix Domain Socket " << adb_unix_socket; |
| device_controller.reset( |
| new DeviceController(host_socket.Pass(), exit_notifier_fd)); |
| return device_controller.Pass(); |
| } |
| |
| DeviceController::~DeviceController() { |
| DCHECK(construction_task_runner_->RunsTasksOnCurrentThread()); |
| } |
| |
| void DeviceController::Start() { |
| AcceptHostCommandSoon(); |
| } |
| |
| DeviceController::DeviceController(scoped_ptr<Socket> host_socket, |
| int exit_notifier_fd) |
| : host_socket_(host_socket.Pass()), |
| exit_notifier_fd_(exit_notifier_fd), |
| construction_task_runner_(base::ThreadTaskRunnerHandle::Get()), |
| weak_ptr_factory_(this) { |
| host_socket_->AddEventFd(exit_notifier_fd); |
| } |
| |
| void DeviceController::AcceptHostCommandSoon() { |
| base::ThreadTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, base::Bind(&DeviceController::AcceptHostCommandInternal, |
| base::Unretained(this))); |
| } |
| |
| void DeviceController::AcceptHostCommandInternal() { |
| scoped_ptr<Socket> socket(new Socket); |
| if (!host_socket_->Accept(socket.get())) { |
| if (!host_socket_->DidReceiveEvent()) |
| PLOG(ERROR) << "Could not Accept DeviceController socket"; |
| else |
| LOG(INFO) << "Received exit notification"; |
| return; |
| } |
| base::ScopedClosureRunner accept_next_client( |
| base::Bind(&DeviceController::AcceptHostCommandSoon, |
| base::Unretained(this))); |
| // So that |socket| doesn't block on read if it has notifications. |
| socket->AddEventFd(exit_notifier_fd_); |
| int port; |
| command::Type command; |
| if (!ReadCommand(socket.get(), &port, &command)) { |
| LOG(ERROR) << "Invalid command received."; |
| return; |
| } |
| const ListenersMap::iterator listener_it = listeners_.find(port); |
| DeviceListener* const listener = listener_it == listeners_.end() |
| ? static_cast<DeviceListener*>(NULL) : listener_it->second.get(); |
| switch (command) { |
| case command::LISTEN: { |
| if (listener != NULL) { |
| LOG(WARNING) << "Already forwarding port " << port |
| << ". Attempting to restart the listener.\n"; |
| DeleteRefCountedValueInMapFromIterator(listener_it, &listeners_); |
| } |
| scoped_ptr<DeviceListener> new_listener( |
| DeviceListener::Create( |
| socket.Pass(), port, |
| base::Bind(&DeviceController::DeleteListenerOnError, |
| weak_ptr_factory_.GetWeakPtr()))); |
| if (!new_listener) |
| return; |
| new_listener->Start(); |
| // |port| can be zero, to allow dynamically allocated port, so instead, we |
| // call DeviceListener::listener_port() to retrieve the currently |
| // allocated port to this new listener. |
| const int listener_port = new_listener->listener_port(); |
| listeners_.insert( |
| std::make_pair(listener_port, |
| linked_ptr<DeviceListener>(new_listener.release()))); |
| LOG(INFO) << "Forwarding device port " << listener_port << " to host."; |
| break; |
| } |
| case command::DATA_CONNECTION: |
| if (listener == NULL) { |
| LOG(ERROR) << "Data Connection command received, but " |
| << "listener has not been set up yet for port " << port; |
| // After this point it is assumed that, once we close our Adb Data |
| // socket, the Adb forwarder command will propagate the closing of |
| // sockets all the way to the host side. |
| break; |
| } |
| listener->SetAdbDataSocket(socket.Pass()); |
| break; |
| case command::UNLISTEN: |
| LOG(INFO) << "Unmapping port " << port; |
| if (!listener) { |
| LOG(ERROR) << "No listener found for port " << port; |
| SendCommand(command::UNLISTEN_ERROR, port, socket.get()); |
| break; |
| } |
| DeleteRefCountedValueInMapFromIterator(listener_it, &listeners_); |
| SendCommand(command::UNLISTEN_SUCCESS, port, socket.get()); |
| break; |
| default: |
| // TODO(felipeg): add a KillAllListeners command. |
| LOG(ERROR) << "Invalid command received. Port: " << port |
| << " Command: " << command; |
| } |
| } |
| |
| // static |
| void DeviceController::DeleteListenerOnError( |
| const base::WeakPtr<DeviceController>& device_controller_ptr, |
| scoped_ptr<DeviceListener> device_listener) { |
| DeviceListener* const listener = device_listener.release(); |
| DeviceController* const controller = device_controller_ptr.get(); |
| if (!controller) { |
| // |listener| was already deleted by the controller that did have its |
| // ownership. |
| return; |
| } |
| DCHECK(controller->construction_task_runner_->RunsTasksOnCurrentThread()); |
| bool listener_did_exist = DeleteRefCountedValueInMap( |
| listener->listener_port(), &controller->listeners_); |
| DCHECK(listener_did_exist); |
| // Note that |listener| was deleted by DeleteRefCountedValueInMap(). |
| } |
| |
| } // namespace forwarder |