diff options
Diffstat (limited to 'remoting')
-rw-r--r-- | remoting/client/jni/chromoting_jni_instance.cc | 13 | ||||
-rw-r--r-- | remoting/client/jni/chromoting_jni_instance.h | 5 | ||||
-rw-r--r-- | remoting/client/log_to_server.cc | 194 | ||||
-rw-r--r-- | remoting/client/log_to_server.h | 90 | ||||
-rw-r--r-- | remoting/client/server_log_entry.cc | 238 | ||||
-rw-r--r-- | remoting/client/server_log_entry.h | 93 | ||||
-rwxr-xr-x | remoting/host/linux/linux_me2me_host.py | 14 | ||||
-rw-r--r-- | remoting/host/setup/me2me_native_messaging_host_main.cc | 4 | ||||
-rw-r--r-- | remoting/remoting_client.gypi | 3 | ||||
-rw-r--r-- | remoting/remoting_srcs.gypi | 4 | ||||
-rw-r--r-- | remoting/resources/remoting_strings.grd | 3 | ||||
-rw-r--r-- | remoting/webapp/connection_stats.js | 2 | ||||
-rw-r--r-- | remoting/webapp/host_controller.js | 36 | ||||
-rw-r--r-- | remoting/webapp/host_dispatcher.js | 3 | ||||
-rw-r--r-- | remoting/webapp/host_install_dialog.js | 79 | ||||
-rw-r--r-- | remoting/webapp/host_screen.js | 13 | ||||
-rw-r--r-- | remoting/webapp/host_setup_dialog.js | 76 |
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); } /** |