summaryrefslogtreecommitdiff
path: root/remoting
diff options
context:
space:
mode:
Diffstat (limited to 'remoting')
-rw-r--r--remoting/client/jni/chromoting_jni_instance.cc13
-rw-r--r--remoting/client/jni/chromoting_jni_instance.h5
-rw-r--r--remoting/client/log_to_server.cc194
-rw-r--r--remoting/client/log_to_server.h90
-rw-r--r--remoting/client/server_log_entry.cc238
-rw-r--r--remoting/client/server_log_entry.h93
-rwxr-xr-xremoting/host/linux/linux_me2me_host.py14
-rw-r--r--remoting/host/setup/me2me_native_messaging_host_main.cc4
-rw-r--r--remoting/remoting_client.gypi3
-rw-r--r--remoting/remoting_srcs.gypi4
-rw-r--r--remoting/resources/remoting_strings.grd3
-rw-r--r--remoting/webapp/connection_stats.js2
-rw-r--r--remoting/webapp/host_controller.js36
-rw-r--r--remoting/webapp/host_dispatcher.js3
-rw-r--r--remoting/webapp/host_install_dialog.js79
-rw-r--r--remoting/webapp/host_screen.js13
-rw-r--r--remoting/webapp/host_setup_dialog.js76
17 files changed, 777 insertions, 93 deletions
diff --git a/remoting/client/jni/chromoting_jni_instance.cc b/remoting/client/jni/chromoting_jni_instance.cc
index 19e7897188..3f9bd22fcd 100644
--- a/remoting/client/jni/chromoting_jni_instance.cc
+++ b/remoting/client/jni/chromoting_jni_instance.cc
@@ -12,6 +12,8 @@
#include "remoting/client/audio_player.h"
#include "remoting/client/jni/android_keymap.h"
#include "remoting/client/jni/chromoting_jni_runtime.h"
+#include "remoting/client/log_to_server.h"
+#include "remoting/client/server_log_entry.h"
#include "remoting/client/software_video_renderer.h"
#include "remoting/jingle_glue/chromium_port_allocator.h"
#include "remoting/jingle_glue/chromium_socket_factory.h"
@@ -29,7 +31,7 @@ const int kXmppPort = 5222;
const bool kXmppUseTls = true;
// Interval at which to log performance statistics, if enabled.
-const int kPerfStatsIntervalMs = 10000;
+const int kPerfStatsIntervalMs = 60000;
}
@@ -215,6 +217,8 @@ void ChromotingJniInstance::OnConnectionState(
EnableStatsLogging(state == protocol::ConnectionToHost::CONNECTED);
+ log_to_server_->LogSessionStateChange(state, error);
+
if (create_pairing_ && state == protocol::ConnectionToHost::CONNECTED) {
protocol::PairingRequest request;
DCHECK(!device_name_.empty());
@@ -332,6 +336,10 @@ void ChromotingJniInstance::ConnectToHostOnNetworkThread() {
net::ClientSocketFactory::GetDefaultFactory(),
jni_runtime_->url_requester(), xmpp_config_));
+ log_to_server_.reset(new client::LogToServer(client::ServerLogEntry::ME2ME,
+ signaling_.get(),
+ "remoting@bot.talk.google.com"));
+
NetworkSettings network_settings(NetworkSettings::NAT_TRAVERSAL_FULL);
// Use Chrome's network stack to allocate ports for peer-to-peer channels.
@@ -358,6 +366,7 @@ void ChromotingJniInstance::DisconnectFromHostOnNetworkThread() {
// |client_| must be torn down before |signaling_|.
connection_.reset();
client_.reset();
+ log_to_server_.reset();
}
void ChromotingJniInstance::FetchSecret(
@@ -420,6 +429,8 @@ void ChromotingJniInstance::LogPerfStats() {
stats->video_paint_ms()->Average(),
stats->round_trip_ms()->Average());
+ log_to_server_->LogStatistics(stats);
+
jni_runtime_->network_task_runner()->PostDelayedTask(
FROM_HERE, base::Bind(&ChromotingJniInstance::LogPerfStats, this),
base::TimeDelta::FromMilliseconds(kPerfStatsIntervalMs));
diff --git a/remoting/client/jni/chromoting_jni_instance.h b/remoting/client/jni/chromoting_jni_instance.h
index edb8b4a808..08404978db 100644
--- a/remoting/client/jni/chromoting_jni_instance.h
+++ b/remoting/client/jni/chromoting_jni_instance.h
@@ -29,6 +29,10 @@ class ClipboardEvent;
class CursorShapeInfo;
} // namespace protocol
+namespace client {
+class LogToServer;
+}
+
class VideoRenderer;
// ClientUserInterface that indirectly makes and receives JNI calls.
@@ -148,6 +152,7 @@ class ChromotingJniInstance
scoped_ptr<ChromotingClient> client_;
XmppSignalStrategy::XmppServerConfig xmpp_config_;
scoped_ptr<XmppSignalStrategy> signaling_; // Must outlive client_
+ scoped_ptr<client::LogToServer> log_to_server_;
// Pass this the user's PIN once we have it. To be assigned and accessed on
// the UI thread, but must be posted to the network thread to call it.
diff --git a/remoting/client/log_to_server.cc b/remoting/client/log_to_server.cc
new file mode 100644
index 0000000000..c45ad386cb
--- /dev/null
+++ b/remoting/client/log_to_server.cc
@@ -0,0 +1,194 @@
+// Copyright 2014 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 "remoting/client/log_to_server.h"
+
+#include "base/macros.h"
+#include "base/rand_util.h"
+#include "remoting/base/constants.h"
+#include "remoting/client/chromoting_stats.h"
+#include "remoting/jingle_glue/iq_sender.h"
+#include "remoting/jingle_glue/signal_strategy.h"
+#include "third_party/libjingle/source/talk/xmllite/xmlelement.h"
+#include "third_party/libjingle/source/talk/xmpp/constants.h"
+
+using buzz::QName;
+using buzz::XmlElement;
+using remoting::protocol::ConnectionToHost;
+
+namespace {
+
+const char kSessionIdAlphabet[] =
+ "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
+const int kSessionIdLength = 20;
+
+const int kMaxSessionIdAgeDays = 1;
+
+bool IsStartOfSession(ConnectionToHost::State state) {
+ return state == ConnectionToHost::INITIALIZING ||
+ state == ConnectionToHost::CONNECTING ||
+ state == ConnectionToHost::AUTHENTICATED ||
+ state == ConnectionToHost::CONNECTED;
+}
+
+bool IsEndOfSession(ConnectionToHost::State state) {
+ return state == ConnectionToHost::FAILED ||
+ state == ConnectionToHost::CLOSED;
+}
+
+bool ShouldAddDuration(ConnectionToHost::State state) {
+ // Duration is added to log entries at the end of the session, as well as at
+ // some intermediate states where it is relevant (e.g. to determine how long
+ // it took for a session to become CONNECTED).
+ return IsEndOfSession(state) || state == ConnectionToHost::CONNECTED;
+}
+
+} // namespace
+
+namespace remoting {
+
+namespace client {
+
+LogToServer::LogToServer(ServerLogEntry::Mode mode,
+ SignalStrategy* signal_strategy,
+ const std::string& directory_bot_jid)
+ : mode_(mode),
+ signal_strategy_(signal_strategy),
+ directory_bot_jid_(directory_bot_jid) {
+ signal_strategy_->AddListener(this);
+}
+
+LogToServer::~LogToServer() {
+ signal_strategy_->RemoveListener(this);
+}
+
+void LogToServer::LogSessionStateChange(
+ protocol::ConnectionToHost::State state,
+ protocol::ErrorCode error) {
+ DCHECK(CalledOnValidThread());
+
+ scoped_ptr<ServerLogEntry> entry(
+ ServerLogEntry::MakeForSessionStateChange(state, error));
+ entry->AddClientFields();
+ entry->AddModeField(mode_);
+
+ MaybeExpireSessionId();
+ if (IsStartOfSession(state)) {
+ // Maybe set the session ID and start time.
+ if (session_id_.empty()) {
+ GenerateSessionId();
+ }
+ if (session_start_time_.is_null()) {
+ session_start_time_ = base::TimeTicks::Now();
+ }
+ }
+
+ if (!session_id_.empty()) {
+ entry->AddSessionId(session_id_);
+ }
+
+ // Maybe clear the session start time and log the session duration.
+ if (ShouldAddDuration(state) && !session_start_time_.is_null()) {
+ entry->AddSessionDuration(base::TimeTicks::Now() - session_start_time_);
+ }
+
+ if (IsEndOfSession(state)) {
+ session_start_time_ = base::TimeTicks();
+ session_id_.clear();
+ }
+
+ Log(*entry.get());
+}
+
+void LogToServer::LogStatistics(ChromotingStats* statistics) {
+ DCHECK(CalledOnValidThread());
+
+ MaybeExpireSessionId();
+
+ scoped_ptr<ServerLogEntry> entry(
+ ServerLogEntry::MakeForStatistics(statistics));
+ entry->AddClientFields();
+ entry->AddModeField(mode_);
+ entry->AddSessionId(session_id_);
+ Log(*entry.get());
+}
+
+void LogToServer::OnSignalStrategyStateChange(SignalStrategy::State state) {
+ DCHECK(CalledOnValidThread());
+
+ if (state == SignalStrategy::CONNECTED) {
+ iq_sender_.reset(new IqSender(signal_strategy_));
+ SendPendingEntries();
+ } else if (state == SignalStrategy::DISCONNECTED) {
+ iq_sender_.reset();
+ }
+}
+
+bool LogToServer::OnSignalStrategyIncomingStanza(
+ const buzz::XmlElement* stanza) {
+ return false;
+}
+
+void LogToServer::Log(const ServerLogEntry& entry) {
+ pending_entries_.push_back(entry);
+ SendPendingEntries();
+}
+
+void LogToServer::SendPendingEntries() {
+ if (iq_sender_ == NULL) {
+ return;
+ }
+ if (pending_entries_.empty()) {
+ return;
+ }
+ // Make one stanza containing all the pending entries.
+ scoped_ptr<XmlElement> stanza(ServerLogEntry::MakeStanza());
+ while (!pending_entries_.empty()) {
+ ServerLogEntry& entry = pending_entries_.front();
+ stanza->AddElement(entry.ToStanza().release());
+ pending_entries_.pop_front();
+ }
+ // Send the stanza to the server.
+ scoped_ptr<IqRequest> req = iq_sender_->SendIq(
+ buzz::STR_SET, directory_bot_jid_, stanza.Pass(),
+ IqSender::ReplyCallback());
+ // We ignore any response, so let the IqRequest be destroyed.
+ return;
+}
+
+void LogToServer::GenerateSessionId() {
+ session_id_.resize(kSessionIdLength);
+ for (int i = 0; i < kSessionIdLength; i++) {
+ const int alphabet_size = arraysize(kSessionIdAlphabet) - 1;
+ session_id_[i] = kSessionIdAlphabet[base::RandGenerator(alphabet_size)];
+ }
+ session_id_generation_time_ = base::TimeTicks::Now();
+}
+
+void LogToServer::MaybeExpireSessionId() {
+ if (session_id_.empty()) {
+ return;
+ }
+
+ base::TimeDelta max_age = base::TimeDelta::FromDays(kMaxSessionIdAgeDays);
+ if (base::TimeTicks::Now() - session_id_generation_time_ > max_age) {
+ // Log the old session ID.
+ scoped_ptr<ServerLogEntry> entry(
+ ServerLogEntry::MakeForSessionIdOld(session_id_));
+ entry->AddModeField(mode_);
+ Log(*entry.get());
+
+ // Generate a new session ID.
+ GenerateSessionId();
+
+ // Log the new session ID.
+ entry = ServerLogEntry::MakeForSessionIdNew(session_id_);
+ entry->AddModeField(mode_);
+ Log(*entry.get());
+ }
+}
+
+} // namespace client
+
+} // namespace remoting
diff --git a/remoting/client/log_to_server.h b/remoting/client/log_to_server.h
new file mode 100644
index 0000000000..35f3c96f04
--- /dev/null
+++ b/remoting/client/log_to_server.h
@@ -0,0 +1,90 @@
+// Copyright 2014 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.
+
+#ifndef REMOTING_CLIENT_LOG_TO_SERVER_H_
+#define REMOTING_CLIENT_LOG_TO_SERVER_H_
+
+#include <deque>
+#include <map>
+#include <string>
+
+#include "base/threading/non_thread_safe.h"
+#include "base/time/time.h"
+#include "remoting/client/server_log_entry.h"
+#include "remoting/jingle_glue/signal_strategy.h"
+#include "remoting/protocol/connection_to_host.h"
+#include "remoting/protocol/errors.h"
+
+namespace buzz {
+class XmlElement;
+} // namespace buzz
+
+namespace remoting {
+
+class ChromotingStats;
+class IqSender;
+
+// Temporary namespace to prevent conflict with the same-named class in
+// remoting/host when linking unittests.
+//
+// TODO(lambroslambrou): Remove this and factor out any shared code.
+namespace client {
+
+// LogToServer sends log entries to a server.
+// The contents of the log entries are described in server_log_entry.cc.
+// They do not contain any personally identifiable information.
+class LogToServer : public base::NonThreadSafe,
+ public SignalStrategy::Listener {
+ public:
+ LogToServer(ServerLogEntry::Mode mode,
+ SignalStrategy* signal_strategy,
+ const std::string& directory_bot_jid);
+ virtual ~LogToServer();
+
+ // Logs a session state change.
+ void LogSessionStateChange(protocol::ConnectionToHost::State state,
+ protocol::ErrorCode error);
+ void LogStatistics(remoting::ChromotingStats* statistics);
+
+ // SignalStrategy::Listener interface.
+ virtual void OnSignalStrategyStateChange(
+ SignalStrategy::State state) OVERRIDE;
+ virtual bool OnSignalStrategyIncomingStanza(
+ const buzz::XmlElement* stanza) OVERRIDE;
+
+ private:
+ void Log(const ServerLogEntry& entry);
+ void SendPendingEntries();
+
+ // Generates a new random session ID.
+ void GenerateSessionId();
+
+ // Expire the session ID if the maximum duration has been exceeded.
+ void MaybeExpireSessionId();
+
+ ServerLogEntry::Mode mode_;
+ SignalStrategy* signal_strategy_;
+ scoped_ptr<IqSender> iq_sender_;
+ std::string directory_bot_jid_;
+
+ std::deque<ServerLogEntry> pending_entries_;
+
+ // A randomly generated session ID to be attached to log messages. This
+ // is regenerated at the start of a new session.
+ std::string session_id_;
+
+ // Start time of the session.
+ base::TimeTicks session_start_time_;
+
+ // Time when the session ID was generated.
+ base::TimeTicks session_id_generation_time_;
+
+ DISALLOW_COPY_AND_ASSIGN(LogToServer);
+};
+
+} // namespace client
+
+} // namespace remoting
+
+#endif // REMOTING_CLIENT_LOG_TO_SERVER_H_
diff --git a/remoting/client/server_log_entry.cc b/remoting/client/server_log_entry.cc
new file mode 100644
index 0000000000..cd580d8863
--- /dev/null
+++ b/remoting/client/server_log_entry.cc
@@ -0,0 +1,238 @@
+// Copyright 2014 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 "remoting/client/server_log_entry.h"
+
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/rand_util.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/stringize_macros.h"
+#include "base/strings/stringprintf.h"
+#include "base/sys_info.h"
+#include "remoting/base/constants.h"
+#include "remoting/client/chromoting_stats.h"
+#include "remoting/protocol/connection_to_host.h"
+#include "remoting/protocol/errors.h"
+#include "third_party/libjingle/source/talk/xmllite/xmlelement.h"
+
+using base::StringPrintf;
+using base::SysInfo;
+using buzz::QName;
+using buzz::XmlElement;
+using remoting::protocol::ConnectionToHost;
+
+namespace remoting {
+
+namespace client {
+
+namespace {
+const char kLogCommand[] = "log";
+
+const char kLogEntry[] = "entry";
+
+const char kKeyEventName[] = "event-name";
+const char kValueEventNameSessionState[] = "session-state";
+const char kValueEventNameStatistics[] = "connection-statistics";
+const char kValueEventNameSessionIdOld[] = "session-id-old";
+const char kValueEventNameSessionIdNew[] = "session-id-new";
+
+const char kKeyRole[] = "role";
+const char kValueRoleClient[] = "client";
+
+const char kKeyMode[] = "mode";
+const char kValueModeIt2Me[] = "it2me";
+const char kValueModeMe2Me[] = "me2me";
+
+const char kKeySessionState[] = "session-state";
+const char kValueSessionStateConnected[] = "connected";
+const char kValueSessionStateClosed[] = "closed";
+
+const char kKeyOsName[] = "os-name";
+const char kKeyOsVersion[] = "os-version";
+const char kKeyAppVersion[] = "app-version";
+
+const char kKeyCpu[] = "cpu";
+
+} // namespace
+
+ServerLogEntry::ServerLogEntry() {
+}
+
+ServerLogEntry::~ServerLogEntry() {
+}
+
+// static
+scoped_ptr<buzz::XmlElement> ServerLogEntry::MakeStanza() {
+ return scoped_ptr<buzz::XmlElement>(
+ new XmlElement(QName(kChromotingXmlNamespace, kLogCommand)));
+}
+
+// static
+scoped_ptr<ServerLogEntry> ServerLogEntry::MakeForSessionStateChange(
+ protocol::ConnectionToHost::State state,
+ protocol::ErrorCode error) {
+ scoped_ptr<ServerLogEntry> entry(new ServerLogEntry());
+ entry->Set(kKeyRole, kValueRoleClient);
+ entry->Set(kKeyEventName, kValueEventNameSessionState);
+
+ entry->Set(kKeySessionState, GetValueSessionState(state));
+ if (error != protocol::OK) {
+ entry->Set("connection-error", GetValueError(error));
+ }
+
+ return entry.Pass();
+}
+
+// static
+scoped_ptr<ServerLogEntry> ServerLogEntry::MakeForStatistics(
+ ChromotingStats* statistics) {
+ scoped_ptr<ServerLogEntry> entry(new ServerLogEntry());
+ entry->Set(kKeyRole, kValueRoleClient);
+ entry->Set(kKeyEventName, kValueEventNameStatistics);
+
+ entry->Set("video-bandwidth",
+ StringPrintf("%.2f", statistics->video_bandwidth()->Rate()));
+ entry->Set("capture-latency",
+ StringPrintf("%.2f", statistics->video_capture_ms()->Average()));
+ entry->Set("encode-latency",
+ StringPrintf("%.2f", statistics->video_encode_ms()->Average()));
+ entry->Set("decode-latency",
+ StringPrintf("%.2f", statistics->video_decode_ms()->Average()));
+ entry->Set("render-latency",
+ StringPrintf("%.2f", statistics->video_frame_rate()->Rate()));
+ entry->Set("roundtrip-latency",
+ StringPrintf("%.2f", statistics->round_trip_ms()->Average()));
+
+ return entry.Pass();
+}
+
+// static
+scoped_ptr<ServerLogEntry> ServerLogEntry::MakeForSessionIdOld(
+ const std::string& session_id) {
+ scoped_ptr<ServerLogEntry> entry(new ServerLogEntry());
+ entry->Set(kKeyRole, kValueRoleClient);
+ entry->Set(kKeyEventName, kValueEventNameSessionIdOld);
+ entry->AddSessionId(session_id);
+ return entry.Pass();
+}
+
+// static
+scoped_ptr<ServerLogEntry> ServerLogEntry::MakeForSessionIdNew(
+ const std::string& session_id) {
+ scoped_ptr<ServerLogEntry> entry(new ServerLogEntry());
+ entry->Set(kKeyRole, kValueRoleClient);
+ entry->Set(kKeyEventName, kValueEventNameSessionIdNew);
+ entry->AddSessionId(session_id);
+ return entry.Pass();
+}
+
+void ServerLogEntry::AddClientFields() {
+ Set(kKeyOsName, SysInfo::OperatingSystemName());
+ Set(kKeyOsVersion, SysInfo::OperatingSystemVersion());
+ Set(kKeyAppVersion, STRINGIZE(VERSION));
+ Set(kKeyCpu, SysInfo::OperatingSystemArchitecture());
+};
+
+void ServerLogEntry::AddModeField(ServerLogEntry::Mode mode) {
+ Set(kKeyMode, GetValueMode(mode));
+}
+
+void ServerLogEntry::AddSessionId(const std::string& session_id) {
+ Set("session-id", session_id);
+}
+
+void ServerLogEntry::AddSessionDuration(base::TimeDelta duration) {
+ Set("session-duration", base::Int64ToString(duration.InSeconds()));
+}
+
+// static
+const char* ServerLogEntry::GetValueMode(ServerLogEntry::Mode mode) {
+ switch (mode) {
+ case IT2ME:
+ return kValueModeIt2Me;
+ case ME2ME:
+ return kValueModeMe2Me;
+ default:
+ NOTREACHED();
+ return NULL;
+ }
+}
+
+scoped_ptr<XmlElement> ServerLogEntry::ToStanza() const {
+ scoped_ptr<XmlElement> stanza(new XmlElement(QName(
+ kChromotingXmlNamespace, kLogEntry)));
+ ValuesMap::const_iterator iter;
+ for (iter = values_map_.begin(); iter != values_map_.end(); ++iter) {
+ stanza->AddAttr(QName(std::string(), iter->first), iter->second);
+ }
+ return stanza.Pass();
+}
+
+// static
+const char* ServerLogEntry::GetValueSessionState(
+ ConnectionToHost::State state) {
+ switch (state) {
+ // Where possible, these are the same strings that the webapp sends for the
+ // corresponding state - see remoting/webapp/server_log_entry.js.
+ case ConnectionToHost::INITIALIZING:
+ return "initializing";
+ case ConnectionToHost::CONNECTING:
+ return "connecting";
+ case ConnectionToHost::AUTHENTICATED:
+ return "authenticated";
+ case ConnectionToHost::CONNECTED:
+ return kValueSessionStateConnected;
+ case ConnectionToHost::FAILED:
+ return "connection-failed";
+ case ConnectionToHost::CLOSED:
+ return kValueSessionStateClosed;
+ default:
+ NOTREACHED();
+ return NULL;
+ }
+}
+
+// static
+const char* ServerLogEntry::GetValueError(protocol::ErrorCode error) {
+ switch (error) {
+ // Where possible, these are the same strings that the webapp sends for the
+ // corresponding error - see remoting/webapp/server_log_entry.js.
+ case protocol::OK:
+ return "none";
+ case protocol::PEER_IS_OFFLINE:
+ return "host-is-offline";
+ case protocol::SESSION_REJECTED:
+ return "session-rejected";
+ case protocol::INCOMPATIBLE_PROTOCOL:
+ return "incompatible-protocol";
+ case protocol::AUTHENTICATION_FAILED:
+ return "authentication-failed";
+ case protocol::CHANNEL_CONNECTION_ERROR:
+ return "channel-connection-error";
+ case protocol::SIGNALING_ERROR:
+ return "signaling-error";
+ case protocol::SIGNALING_TIMEOUT:
+ return "signaling-timeout";
+ case protocol::HOST_OVERLOAD:
+ return "host-overload";
+ case protocol::UNKNOWN_ERROR:
+ return "unknown-error";
+ default:
+ NOTREACHED();
+ return NULL;
+ }
+}
+
+void ServerLogEntry::AddEventName(const std::string& event_name) {
+ Set("event-name", event_name);
+}
+
+void ServerLogEntry::Set(const std::string& key, const std::string& value) {
+ values_map_[key] = value;
+}
+
+} // namespace client
+
+} // namespace remoting
diff --git a/remoting/client/server_log_entry.h b/remoting/client/server_log_entry.h
new file mode 100644
index 0000000000..9f861d2538
--- /dev/null
+++ b/remoting/client/server_log_entry.h
@@ -0,0 +1,93 @@
+// Copyright 2014 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.
+
+#ifndef REMOTING_CLIENT_SERVER_LOG_ENTRY_H_
+#define REMOTING_CLIENT_SERVER_LOG_ENTRY_H_
+
+#include <map>
+#include <string>
+
+#include "base/memory/scoped_ptr.h"
+#include "base/time/time.h"
+#include "remoting/protocol/connection_to_host.h"
+#include "remoting/protocol/errors.h"
+
+namespace buzz {
+class XmlElement;
+} // namespace buzz
+
+namespace remoting {
+
+class ChromotingStats;
+
+// Temporary namespace to prevent conflict with the same-named class in
+// remoting/host when linking unittests.
+//
+// TODO(lambroslambrou): Remove this and factor out any shared code.
+namespace client {
+
+class ServerLogEntry {
+ public:
+ // The mode of a connection.
+ enum Mode {
+ IT2ME,
+ ME2ME
+ };
+
+ // Constructs a log stanza. The caller should add one or more log entry
+ // stanzas as children of this stanza, before sending the log stanza to
+ // the remoting bot.
+ static scoped_ptr<buzz::XmlElement> MakeStanza();
+
+ // Constructs a log entry for a session state change.
+ static scoped_ptr<ServerLogEntry> MakeForSessionStateChange(
+ remoting::protocol::ConnectionToHost::State state,
+ remoting::protocol::ErrorCode error);
+
+ // Constructs a log entry for reporting statistics.
+ static scoped_ptr<ServerLogEntry> MakeForStatistics(
+ remoting::ChromotingStats* statistics);
+
+ // Constructs a log entry for reporting session ID is old.
+ static scoped_ptr<ServerLogEntry> MakeForSessionIdOld(
+ const std::string& session_id);
+
+ // Constructs a log entry for reporting session ID is old.
+ static scoped_ptr<ServerLogEntry> MakeForSessionIdNew(
+ const std::string& session_id);
+
+ ~ServerLogEntry();
+
+ // Adds fields describing the client to this log entry.
+ void AddClientFields();
+
+ // Adds a field describing the mode of a connection to this log entry.
+ void AddModeField(Mode mode);
+
+ void AddEventName(const std::string& event_name);
+ void AddSessionId(const std::string& session_id);
+ void AddSessionDuration(base::TimeDelta duration);
+
+ // Converts this object to an XML stanza.
+ scoped_ptr<buzz::XmlElement> ToStanza() const;
+
+ private:
+ typedef std::map<std::string, std::string> ValuesMap;
+
+ ServerLogEntry();
+ void Set(const std::string& key, const std::string& value);
+
+ static const char* GetValueSessionState(
+ remoting::protocol::ConnectionToHost::State state);
+ static const char* GetValueError(remoting::protocol::ErrorCode error);
+ static const char* GetValueMode(Mode mode);
+
+ ValuesMap values_map_;
+};
+
+} // namespace client
+
+} // namespace remoting
+
+#endif // REMOTING_CLIENT_SERVER_LOG_ENTRY_H_
diff --git a/remoting/host/linux/linux_me2me_host.py b/remoting/host/linux/linux_me2me_host.py
index af694064c3..2604849c7a 100755
--- a/remoting/host/linux/linux_me2me_host.py
+++ b/remoting/host/linux/linux_me2me_host.py
@@ -490,14 +490,12 @@ def get_daemon_pid():
uid = os.getuid()
this_pid = os.getpid()
- # Support new & old psutil API.
- if 'error' in dir(psutil):
- # Old API (0.x & 1.x).
- psutil.Error = psutil.error.Error
- psget = lambda x: x
- else:
- # New API (2.x+).
+ # Support new & old psutil API. This is the right way to check, according to
+ # http://grodola.blogspot.com/2014/01/psutil-20-porting.html
+ if psutil.version_info >= (2, 0):
psget = lambda x: x()
+ else:
+ psget = lambda x: x
for process in psutil.process_iter():
# Skip any processes that raise an exception, as processes may terminate
@@ -517,7 +515,7 @@ def get_daemon_pid():
continue
if cmdline[0] == sys.executable and cmdline[1] == sys.argv[0]:
return process.pid
- except psutil.Error:
+ except (psutil.NoSuchProcess, psutil.AccessDenied):
continue
return 0
diff --git a/remoting/host/setup/me2me_native_messaging_host_main.cc b/remoting/host/setup/me2me_native_messaging_host_main.cc
index 8cf6566b7a..d164bec28e 100644
--- a/remoting/host/setup/me2me_native_messaging_host_main.cc
+++ b/remoting/host/setup/me2me_native_messaging_host_main.cc
@@ -6,6 +6,7 @@
#include "base/at_exit.h"
#include "base/command_line.h"
+#include "base/i18n/icu_util.h"
#include "base/message_loop/message_loop.h"
#include "base/run_loop.h"
#include "base/strings/string_number_conversions.h"
@@ -65,6 +66,9 @@ int StartMe2MeNativeMessagingHost() {
base::mac::ScopedNSAutoreleasePool pool;
#endif // defined(OS_MACOSX)
+ // Required to find the ICU data file, used by some file_util routines.
+ base::i18n::InitializeICU();
+
#if defined(REMOTING_ENABLE_BREAKPAD)
// Initialize Breakpad as early as possible. On Mac the command-line needs to
// be initialized first, so that the preference for crash-reporting can be
diff --git a/remoting/remoting_client.gypi b/remoting/remoting_client.gypi
index d05130ef80..e9d5b32db0 100644
--- a/remoting/remoting_client.gypi
+++ b/remoting/remoting_client.gypi
@@ -38,6 +38,9 @@
'target_name': 'remoting_client',
'type': 'static_library',
'variables': { 'enable_wexit_time_destructors': 1, },
+ 'defines': [
+ 'VERSION=<(version_full)',
+ ],
'dependencies': [
'remoting_base',
'remoting_protocol',
diff --git a/remoting/remoting_srcs.gypi b/remoting/remoting_srcs.gypi
index f694c8db49..d9d663e1fa 100644
--- a/remoting/remoting_srcs.gypi
+++ b/remoting/remoting_srcs.gypi
@@ -210,6 +210,10 @@
'client/frame_producer.h',
'client/key_event_mapper.cc',
'client/key_event_mapper.h',
+ 'client/log_to_server.cc',
+ 'client/log_to_server.h',
+ 'client/server_log_entry.cc',
+ 'client/server_log_entry.h',
'client/software_video_renderer.cc',
'client/software_video_renderer.h',
'client/video_renderer.h',
diff --git a/remoting/resources/remoting_strings.grd b/remoting/resources/remoting_strings.grd
index 805a6d326a..b9d8bc267e 100644
--- a/remoting/resources/remoting_strings.grd
+++ b/remoting/resources/remoting_strings.grd
@@ -460,7 +460,8 @@ Remote computers with non US-English keyboards may receive incorrect text input.
For information about privacy, please see the Google Privacy Policy (http://goo.gl/SyrVzj) and the Chrome Privacy Policy (http://goo.gl/0uXE5d).
</message>
<message name="IDS_PLAY_STORE_CHANGES" desc="List of what's changed in this release of Chrome Remote Desktop for Android. [CHAR-LIMIT=500] [NAME=play_store_changes]">
-First release of Chrome Remote Desktop for Android.
+• Implemented full-screen / immersive mode.
+• Improved icon for hiding the action bar.
</message>
</if>
diff --git a/remoting/webapp/connection_stats.js b/remoting/webapp/connection_stats.js
index b9c77bf416..3f7404ead9 100644
--- a/remoting/webapp/connection_stats.js
+++ b/remoting/webapp/connection_stats.js
@@ -52,7 +52,7 @@ remoting.ConnectionStats.prototype.update = function(stats) {
/**
* @param {number} value
* @param {string} units
- * @returns {string} Formatted number.
+ * @return {string} Formatted number.
*/
function formatStatNumber(value, units) {
if (value != undefined) {
diff --git a/remoting/webapp/host_controller.js b/remoting/webapp/host_controller.js
index f8860c0e16..056cdd9eef 100644
--- a/remoting/webapp/host_controller.js
+++ b/remoting/webapp/host_controller.js
@@ -124,7 +124,19 @@ remoting.HostController.prototype.getConsent = function(onDone, onError) {
* @return {void}
*/
remoting.HostController.prototype.installHost = function(onDone, onError) {
- this.hostDispatcher_.installHost(onDone, onError);
+ /** @type {remoting.HostController} */
+ var that = this;
+
+ /** @param {remoting.HostController.AsyncResult} asyncResult */
+ var onHostInstalled = function(asyncResult) {
+ // Refresh the dispatcher after the host has been installed.
+ if (asyncResult == remoting.HostController.AsyncResult.OK) {
+ that.hostDispatcher_ = that.createDispatcher_();
+ }
+ onDone(asyncResult);
+ };
+
+ this.hostDispatcher_.installHost(onHostInstalled, onError);
};
/**
@@ -344,27 +356,7 @@ remoting.HostController.prototype.start = function(hostPin, consent, onDone,
onError);
}
- /** @param {remoting.HostController.AsyncResult} asyncResult */
- var onHostInstalled = function(asyncResult) {
- if (asyncResult == remoting.HostController.AsyncResult.OK) {
- // Now that the host is installed, we need to get a new dispatcher that
- // dispatches to the NM host instead of the NPAPI plugin.
- console.log('Recreating the host dispatcher.');
- that.hostDispatcher_ = that.createDispatcher_();
- that.hostDispatcher_.getHostName(startWithHostname, onError);
- } else if (asyncResult == remoting.HostController.AsyncResult.CANCELLED) {
- onError(remoting.Error.CANCELLED);
- } else {
- onError(remoting.Error.UNEXPECTED);
- }
- }
-
- // Perform the installation step here on Windows.
- if (navigator.platform == 'Win32') {
- this.installHost(onHostInstalled, onError);
- } else {
- this.hostDispatcher_.getHostName(startWithHostname, onError);
- }
+ this.hostDispatcher_.getHostName(startWithHostname, onError);
};
/**
diff --git a/remoting/webapp/host_dispatcher.js b/remoting/webapp/host_dispatcher.js
index 6861e38106..a5ea504ece 100644
--- a/remoting/webapp/host_dispatcher.js
+++ b/remoting/webapp/host_dispatcher.js
@@ -327,6 +327,9 @@ remoting.HostDispatcher.prototype.getUsageStatsConsent =
};
/**
+ * This function installs the chromoting host using the NPAPI plugin and should
+ * only be called on Windows.
+ *
* @param {function(remoting.HostController.AsyncResult):void} onDone
* @param {function(remoting.Error):void} onError
* @return {void}
diff --git a/remoting/webapp/host_install_dialog.js b/remoting/webapp/host_install_dialog.js
index 95fdd96f58..f94cca6a58 100644
--- a/remoting/webapp/host_install_dialog.js
+++ b/remoting/webapp/host_install_dialog.js
@@ -27,8 +27,8 @@ remoting.HostInstallDialog = function() {
this.continueInstallButton_.disabled = false;
this.cancelInstallButton_.disabled = false;
- /** @private*/
- this.onDoneHandler_ = function() {}
+ /** @param {remoting.HostController.AsyncResult} asyncResult @private*/
+ this.onDoneHandler_ = function(asyncResult) {}
/** @param {remoting.Error} error @private */
this.onErrorHandler_ = function(error) {}
@@ -49,42 +49,55 @@ remoting.HostInstallDialog.hostDownloadUrls = {
/**
* Starts downloading host components and shows installation prompt.
*
- * @param {function():void} onDone Callback called when user clicks Ok,
- * presumably after installing the host. The handler must verify that the
- * host has been installed and call tryAgain() otherwise.
+ * @param {remoting.HostController} hostController Used to install the host on
+ * Windows.
+ * @param {function(remoting.HostController.AsyncResult):void} onDone Callback
+ * called when user clicks Ok, presumably after installing the host. The
+ * handler must verify that the host has been installed and call tryAgain()
+ * otherwise.
* @param {function(remoting.Error):void} onError Callback called when user
- * clicks Cancel button or there is some other unexpected error.
- * @returns {void}
+ * clicks Cancel button or there is some other unexpected error.
+ * @return {void}
*/
-remoting.HostInstallDialog.prototype.show = function(onDone, onError) {
- this.continueInstallButton_.addEventListener(
- 'click', this.onOkClickedHandler_, false);
- this.cancelInstallButton_.addEventListener(
- 'click', this.onCancelClickedHandler_, false);
- remoting.setMode(remoting.AppMode.HOST_INSTALL_PROMPT);
-
- var hostPackageUrl =
- remoting.HostInstallDialog.hostDownloadUrls[navigator.platform];
- if (hostPackageUrl === undefined) {
- this.onErrorHandler_(remoting.Error.CANCELLED);
- return;
+remoting.HostInstallDialog.prototype.show = function(
+ hostController, onDone, onError) {
+ // On Windows, host installation is automatic (handled by the NPAPI plugin)
+ // and we don't show the dialog. On Mac and Linux, we show the dialog and the
+ // user is expected to manually install the host before clicking OK.
+ // TODO (weitaosu): Make host installation automatic for IT2Me (like Me2Me) on
+ // Windows. Currently hostController is always null for IT2Me.
+ if (navigator.platform == 'Win32' && hostController != null) {
+ hostController.installHost(onDone, onError);
+ } else {
+ this.continueInstallButton_.addEventListener(
+ 'click', this.onOkClickedHandler_, false);
+ this.cancelInstallButton_.addEventListener(
+ 'click', this.onCancelClickedHandler_, false);
+ remoting.setMode(remoting.AppMode.HOST_INSTALL_PROMPT);
+
+ var hostPackageUrl =
+ remoting.HostInstallDialog.hostDownloadUrls[navigator.platform];
+ if (hostPackageUrl === undefined) {
+ this.onErrorHandler_(remoting.Error.CANCELLED);
+ return;
+ }
+
+ // Start downloading the package.
+ window.location = hostPackageUrl;
+
+ /** @type {function(remoting.HostController.AsyncResult):void} */
+ this.onDoneHandler_ = onDone;
+
+ /** @type {function(remoting.Error):void} */
+ this.onErrorHandler_ = onError;
}
-
- // Start downloading the package.
- window.location = hostPackageUrl;
-
- /** @type {function():void} */
- this.onDoneHandler_ = onDone;
-
- /** @type {function(remoting.Error):void} */
- this.onErrorHandler_ = onError;
}
/**
- * onDone handler must call this method if it detects that the host components
- * are still unavailable. The same onDone and onError callbacks will be used
- * when user clicks Ok or Cancel.
- */
+ * In manual host installation, onDone handler must call this method if it
+ * detects that the host components are still unavailable. The same onDone
+ * and onError callbacks will be used when user clicks Ok or Cancel.
+ */
remoting.HostInstallDialog.prototype.tryAgain = function() {
this.retryInstallButton_.addEventListener(
'click', this.onRetryClickedHandler_.bind(this), false);
@@ -101,7 +114,7 @@ remoting.HostInstallDialog.prototype.onOkClicked_ = function() {
this.continueInstallButton_.disabled = true;
this.cancelInstallButton_.disabled = true;
- this.onDoneHandler_();
+ this.onDoneHandler_(remoting.HostController.AsyncResult.OK);
}
remoting.HostInstallDialog.prototype.onCancelClicked_ = function() {
diff --git a/remoting/webapp/host_screen.js b/remoting/webapp/host_screen.js
index 1bebc74e81..45e9975bbf 100644
--- a/remoting/webapp/host_screen.js
+++ b/remoting/webapp/host_screen.js
@@ -58,10 +58,19 @@ remoting.tryShare = function() {
// If we failed to initialize dispatcher then prompt the user to install the
// host.
if (hostInstallDialog == null) {
+ var onDone = function(asyncResult) {
+ // TODO (weitaosu): Ignore asyncResult for now because it is not set
+ // during manual host installation. We should fix it after switching
+ // to automatic host installation on Windows.
+ tryInitializeDispatcher();
+ };
+
hostInstallDialog = new remoting.HostInstallDialog();
- (/** @type {remoting.HostInstallDialog} */ hostInstallDialog)
- .show(tryInitializeDispatcher, onInstallPromptError);
+ (/** @type {remoting.HostInstallDialog} */ hostInstallDialog).show(
+ null,
+ onDone,
+ onInstallPromptError);
} else {
(/** @type {remoting.HostInstallDialog} */ hostInstallDialog).tryAgain();
}
diff --git a/remoting/webapp/host_setup_dialog.js b/remoting/webapp/host_setup_dialog.js
index f8c12bf385..13717a2cad 100644
--- a/remoting/webapp/host_setup_dialog.js
+++ b/remoting/webapp/host_setup_dialog.js
@@ -219,11 +219,8 @@ remoting.HostSetupDialog.prototype.showForStartWithToken_ =
state != remoting.HostController.State.NOT_INSTALLED &&
state != remoting.HostController.State.INSTALLING;
- // Skip the installation step when the host is already installed or when using
- // NPAPI plugin on Windows (because on Windows the plugin takes care of
- // installation).
- if (installed || (navigator.platform == 'Win32' &&
- this.hostController_.usingNpapiPlugin())) {
+ // Skip the installation step when the host is already installed.
+ if (installed) {
flow.shift();
}
@@ -276,6 +273,16 @@ remoting.HostSetupDialog.prototype.startNewFlow_ = function(sequence) {
};
/**
+ * @param {string} tag
+ * @private
+ */
+remoting.HostSetupDialog.prototype.showProcessingMessage_ = function(tag) {
+ var messageDiv = document.getElementById('host-setup-processing-message');
+ l10n.localizeElementFromTag(messageDiv, tag);
+ remoting.setMode(remoting.AppMode.HOST_SETUP_PROCESSING);
+}
+
+/**
* Updates current UI mode according to the current state of the setup
* flow and start the action corresponding to the current step (if
* any).
@@ -284,12 +291,6 @@ remoting.HostSetupDialog.prototype.startNewFlow_ = function(sequence) {
remoting.HostSetupDialog.prototype.updateState_ = function() {
remoting.updateLocalHostState();
- /** @param {string} tag */
- function showProcessingMessage(tag) {
- var messageDiv = document.getElementById('host-setup-processing-message');
- l10n.localizeElementFromTag(messageDiv, tag);
- remoting.setMode(remoting.AppMode.HOST_SETUP_PROCESSING);
- }
/** @param {string} tag1
* @param {string=} opt_tag2 */
function showDoneMessage(tag1, opt_tag2) {
@@ -318,13 +319,13 @@ remoting.HostSetupDialog.prototype.updateState_ = function() {
} else if (state == remoting.HostSetupFlow.State.INSTALL_HOST) {
this.installHost_();
} else if (state == remoting.HostSetupFlow.State.STARTING_HOST) {
- showProcessingMessage(/*i18n-content*/'HOST_SETUP_STARTING');
+ this.showProcessingMessage_(/*i18n-content*/'HOST_SETUP_STARTING');
this.startHost_();
} else if (state == remoting.HostSetupFlow.State.UPDATING_PIN) {
- showProcessingMessage(/*i18n-content*/'HOST_SETUP_UPDATING_PIN');
+ this.showProcessingMessage_(/*i18n-content*/'HOST_SETUP_UPDATING_PIN');
this.updatePin_();
} else if (state == remoting.HostSetupFlow.State.STOPPING_HOST) {
- showProcessingMessage(/*i18n-content*/'HOST_SETUP_STOPPING');
+ this.showProcessingMessage_(/*i18n-content*/'HOST_SETUP_STOPPING');
this.stopHost_();
} else if (state == remoting.HostSetupFlow.State.HOST_STARTED) {
// TODO(jamiewalch): Only display the second string if the computer's power
@@ -355,36 +356,61 @@ remoting.HostSetupDialog.prototype.installHost_ = function() {
/** @type {remoting.HostSetupFlow} */
var flow = this.flow_;
- var onDone = function() {
- that.hostController_.getLocalHostState(onHostState);
- };
-
/** @param {remoting.Error} error */
var onError = function(error) {
flow.switchToErrorState(error);
that.updateState_();
- }
+ };
+
+ /** @param {remoting.HostController.AsyncResult} asyncResult */
+ var onDone = function(asyncResult) {
+ if (asyncResult == remoting.HostController.AsyncResult.OK) {
+ that.hostController_.getLocalHostState(onHostState);
+ } else if (asyncResult == remoting.HostController.AsyncResult.CANCELLED) {
+ onError(remoting.Error.CANCELLED);
+ } else {
+ onError(remoting.Error.UNEXPECTED);
+ }
+ };
/** @param {remoting.HostController.State} state */
var onHostState = function(state) {
- // Verify if the host has been installed. If not then try to prompt the user
- // again.
var installed =
state != remoting.HostController.State.NOT_INSTALLED &&
state != remoting.HostController.State.INSTALLING;
- // On Windows we perform the host installation after showing the pin form.
- if (installed || navigator.platform == 'Win32') {
+ if (installed) {
that.flow_.switchToNextStep();
that.updateState_();
} else {
- hostInstallDialog.tryAgain();
+ // For Mac/Linux, prompt the user again if the host is not installed.
+ if (navigator.platform != 'Win32') {
+ hostInstallDialog.tryAgain();
+ } else {
+ // For Windows, report an error in the unlikely case that
+ // HostController.installHost reports AsyncResult.OK but the host is not
+ // installed.
+ console.error('The chromoting host is not installed.');
+ onError(remoting.Error.UNEXPECTED);
+ }
}
+ };
+
+ if (navigator.platform == 'Win32') {
+ // Currently we show two dialogs (each with a UAC prompt) when a user
+ // enables the host for the first time, one for installing the host (by the
+ // plugin) and the other for starting the host (by the native messaging
+ // host). We'd like to reduce it to one but don't have a good solution
+ // right now.
+ // We also show the same message on the two dialogs because. We don't want
+ // to confuse the user by saying "Installing Remote Desktop" because in
+ // their mind "Remote Deskto" (the webapp) has already been installed.
+ that.showProcessingMessage_(/*i18n-content*/'HOST_SETUP_STARTING');
}
/** @type {remoting.HostInstallDialog} */
var hostInstallDialog = new remoting.HostInstallDialog();
- hostInstallDialog.show(onDone, onError);
+ hostInstallDialog.show(this.hostController_, onDone, onError);
}
/**