Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "remoting/host/chromoting_host.h" | 5 #include "remoting/host/chromoting_host.h" |
| 6 | 6 |
| 7 #include "base/bind.h" | 7 #include "base/bind.h" |
| 8 #include "base/callback.h" | 8 #include "base/callback.h" |
| 9 #include "base/logging.h" | 9 #include "base/logging.h" |
| 10 #include "base/message_loop_proxy.h" | 10 #include "base/message_loop_proxy.h" |
| 11 #include "build/build_config.h" | 11 #include "build/build_config.h" |
| 12 #include "remoting/base/constants.h" | 12 #include "remoting/base/constants.h" |
| 13 #include "remoting/base/encoder.h" | |
| 14 #include "remoting/base/encoder_row_based.h" | |
| 15 #include "remoting/base/encoder_vp8.h" | |
| 16 #include "remoting/codec/audio_encoder.h" | |
| 17 #include "remoting/codec/audio_encoder_verbatim.h" | |
| 18 #include "remoting/host/audio_scheduler.h" | |
| 19 #include "remoting/host/chromoting_host_context.h" | 13 #include "remoting/host/chromoting_host_context.h" |
| 20 #include "remoting/host/desktop_environment.h" | 14 #include "remoting/host/desktop_environment.h" |
| 15 #include "remoting/host/desktop_environment_factory.h" | |
| 21 #include "remoting/host/event_executor.h" | 16 #include "remoting/host/event_executor.h" |
| 22 #include "remoting/host/host_config.h" | 17 #include "remoting/host/host_config.h" |
| 23 #include "remoting/host/screen_recorder.h" | |
| 24 #include "remoting/protocol/connection_to_client.h" | 18 #include "remoting/protocol/connection_to_client.h" |
| 25 #include "remoting/protocol/client_stub.h" | 19 #include "remoting/protocol/client_stub.h" |
| 26 #include "remoting/protocol/host_stub.h" | 20 #include "remoting/protocol/host_stub.h" |
| 27 #include "remoting/protocol/input_stub.h" | 21 #include "remoting/protocol/input_stub.h" |
| 28 #include "remoting/protocol/session_config.h" | 22 #include "remoting/protocol/session_config.h" |
| 29 | 23 |
| 30 using remoting::protocol::ConnectionToClient; | 24 using remoting::protocol::ConnectionToClient; |
| 31 using remoting::protocol::InputStub; | 25 using remoting::protocol::InputStub; |
| 32 | 26 |
| 33 namespace remoting { | 27 namespace remoting { |
| (...skipping 24 matching lines...) Expand all Loading... | |
| 58 | 52 |
| 59 // Don't use initial delay unless the last request was an error. | 53 // Don't use initial delay unless the last request was an error. |
| 60 false, | 54 false, |
| 61 }; | 55 }; |
| 62 | 56 |
| 63 } // namespace | 57 } // namespace |
| 64 | 58 |
| 65 ChromotingHost::ChromotingHost( | 59 ChromotingHost::ChromotingHost( |
| 66 ChromotingHostContext* context, | 60 ChromotingHostContext* context, |
| 67 SignalStrategy* signal_strategy, | 61 SignalStrategy* signal_strategy, |
| 68 DesktopEnvironment* environment, | 62 DesktopEnvironmentFactory* desktop_environment_factory, |
| 69 scoped_ptr<protocol::SessionManager> session_manager) | 63 scoped_ptr<protocol::SessionManager> session_manager) |
| 70 : context_(context), | 64 : context_(context), |
| 71 desktop_environment_(environment), | 65 desktop_environment_factory_(desktop_environment_factory), |
| 72 session_manager_(session_manager.Pass()), | 66 session_manager_(session_manager.Pass()), |
| 73 signal_strategy_(signal_strategy), | 67 signal_strategy_(signal_strategy), |
| 74 stopping_recorders_(0), | |
| 75 state_(kInitial), | 68 state_(kInitial), |
| 76 protocol_config_(protocol::CandidateSessionConfig::CreateDefault()), | 69 protocol_config_(protocol::CandidateSessionConfig::CreateDefault()), |
| 77 login_backoff_(&kDefaultBackoffPolicy), | 70 login_backoff_(&kDefaultBackoffPolicy), |
| 78 authenticating_client_(false), | 71 authenticating_client_(false), |
| 79 reject_authenticating_client_(false) { | 72 reject_authenticating_client_(false) { |
| 80 DCHECK(context_); | 73 DCHECK(context_); |
| 81 DCHECK(signal_strategy); | 74 DCHECK(signal_strategy); |
| 82 DCHECK(desktop_environment_); | |
| 83 DCHECK(context_->network_task_runner()->BelongsToCurrentThread()); | 75 DCHECK(context_->network_task_runner()->BelongsToCurrentThread()); |
| 84 | 76 |
| 85 if (!desktop_environment_->audio_capturer()) { | 77 #if !defined(OS_WIN) |
|
simonmorris
2012/09/04 18:47:21
Add a HasAudioCapturer() method to DesktopEnvironm
Wez
2012/09/04 20:29:30
Good idea, although I'd suggest SupportsAudioCaptu
alexeypa (please no reviews)
2012/09/05 22:53:29
Done.
alexeypa (please no reviews)
2012/09/05 22:53:29
Done.
| |
| 86 // Disable audio by replacing our list of supported audio configurations | 78 // Disable audio by replacing our list of supported audio configurations |
| 87 // with the NONE config. | 79 // with the NONE config. |
| 88 protocol_config_->mutable_audio_configs()->clear(); | 80 // AudioCapturer::Create() is currently only implemented for Windows. |
| 89 protocol_config_->mutable_audio_configs()->push_back( | 81 protocol_config_->mutable_audio_configs()->clear(); |
| 90 protocol::ChannelConfig(protocol::ChannelConfig::TRANSPORT_NONE, | 82 protocol_config_->mutable_audio_configs()->push_back( |
| 91 protocol::kDefaultStreamVersion, | 83 protocol::ChannelConfig(protocol::ChannelConfig::TRANSPORT_NONE, |
| 92 protocol::ChannelConfig::CODEC_VERBATIM)); | 84 protocol::kDefaultStreamVersion, |
| 93 } | 85 protocol::ChannelConfig::CODEC_VERBATIM)); |
| 86 #endif // !defined(OS_WIN) | |
| 94 } | 87 } |
| 95 | 88 |
| 96 ChromotingHost::~ChromotingHost() { | 89 ChromotingHost::~ChromotingHost() { |
| 97 DCHECK(clients_.empty()); | 90 DCHECK(clients_.empty()); |
| 98 } | 91 } |
| 99 | 92 |
| 100 void ChromotingHost::Start() { | 93 void ChromotingHost::Start() { |
| 101 DCHECK(context_->network_task_runner()->BelongsToCurrentThread()); | 94 DCHECK(context_->network_task_runner()->BelongsToCurrentThread()); |
| 102 | 95 |
| 103 LOG(INFO) << "Starting host"; | 96 LOG(INFO) << "Starting host"; |
| (...skipping 28 matching lines...) Expand all Loading... | |
| 132 // We are already stopping. Just save the task. | 125 // We are already stopping. Just save the task. |
| 133 if (!shutdown_task.is_null()) | 126 if (!shutdown_task.is_null()) |
| 134 shutdown_tasks_.push_back(shutdown_task); | 127 shutdown_tasks_.push_back(shutdown_task); |
| 135 break; | 128 break; |
| 136 | 129 |
| 137 case kStarted: | 130 case kStarted: |
| 138 if (!shutdown_task.is_null()) | 131 if (!shutdown_task.is_null()) |
| 139 shutdown_tasks_.push_back(shutdown_task); | 132 shutdown_tasks_.push_back(shutdown_task); |
| 140 state_ = kStopping; | 133 state_ = kStopping; |
| 141 | 134 |
| 142 // Disconnect all of the clients, implicitly stopping the ScreenRecorder. | 135 // Disconnect all of the clients. |
| 143 while (!clients_.empty()) { | 136 while (!clients_.empty()) { |
| 144 clients_.front()->Disconnect(); | 137 clients_.front()->Disconnect(); |
| 145 } | 138 } |
| 146 DCHECK(!recorder_.get()); | |
| 147 DCHECK(!audio_scheduler_.get()); | |
| 148 | 139 |
| 149 // Destroy session manager. | 140 // Destroy session manager. |
| 150 session_manager_.reset(); | 141 session_manager_.reset(); |
| 151 | 142 |
| 152 if (!stopping_recorders_) | 143 // Run the remaining shutdown tasks. |
| 153 ShutdownFinish(); | 144 ShutdownFinish(); |
| 154 break; | 145 break; |
| 155 } | 146 } |
| 156 } | 147 } |
| 157 | 148 |
| 158 void ChromotingHost::AddStatusObserver(HostStatusObserver* observer) { | 149 void ChromotingHost::AddStatusObserver(HostStatusObserver* observer) { |
| 159 DCHECK(context_->network_task_runner()->BelongsToCurrentThread()); | 150 DCHECK(context_->network_task_runner()->BelongsToCurrentThread()); |
| 160 status_observers_.AddObserver(observer); | 151 status_observers_.AddObserver(observer); |
| 161 } | 152 } |
| 162 | 153 |
| 163 void ChromotingHost::RemoveStatusObserver(HostStatusObserver* observer) { | 154 void ChromotingHost::RemoveStatusObserver(HostStatusObserver* observer) { |
| (...skipping 30 matching lines...) Expand all Loading... | |
| 194 ClientList clients_copy(clients_); | 185 ClientList clients_copy(clients_); |
| 195 for (ClientList::const_iterator other_client = clients_copy.begin(); | 186 for (ClientList::const_iterator other_client = clients_copy.begin(); |
| 196 other_client != clients_copy.end(); ++other_client) { | 187 other_client != clients_copy.end(); ++other_client) { |
| 197 if ((*other_client) != client) { | 188 if ((*other_client) != client) { |
| 198 (*other_client)->Disconnect(); | 189 (*other_client)->Disconnect(); |
| 199 } | 190 } |
| 200 } | 191 } |
| 201 | 192 |
| 202 // Disconnects above must have destroyed all other clients and |recorder_|. | 193 // Disconnects above must have destroyed all other clients and |recorder_|. |
| 203 DCHECK_EQ(clients_.size(), 1U); | 194 DCHECK_EQ(clients_.size(), 1U); |
| 204 DCHECK(!recorder_.get()); | |
| 205 DCHECK(!audio_scheduler_.get()); | |
| 206 | 195 |
| 207 // Notify observers that there is at least one authenticated client. | 196 // Notify observers that there is at least one authenticated client. |
| 208 const std::string& jid = client->client_jid(); | 197 const std::string& jid = client->client_jid(); |
| 209 | 198 |
| 210 reject_authenticating_client_ = false; | 199 reject_authenticating_client_ = false; |
| 211 | 200 |
| 212 authenticating_client_ = true; | 201 authenticating_client_ = true; |
| 213 FOR_EACH_OBSERVER(HostStatusObserver, status_observers_, | 202 FOR_EACH_OBSERVER(HostStatusObserver, status_observers_, |
| 214 OnClientAuthenticated(jid)); | 203 OnClientAuthenticated(jid)); |
| 215 authenticating_client_ = false; | 204 authenticating_client_ = false; |
| 216 | 205 |
| 217 if (reject_authenticating_client_) { | 206 if (reject_authenticating_client_) { |
| 218 client->Disconnect(); | 207 client->Disconnect(); |
| 219 } | 208 } |
| 220 } | 209 } |
| 221 | 210 |
| 222 void ChromotingHost::OnSessionChannelsConnected(ClientSession* client) { | 211 void ChromotingHost::OnSessionChannelsConnected(ClientSession* client) { |
| 223 DCHECK(context_->network_task_runner()->BelongsToCurrentThread()); | 212 DCHECK(context_->network_task_runner()->BelongsToCurrentThread()); |
| 224 | 213 |
| 225 // Then we create a ScreenRecorder passing the message loops that | 214 // Notify observers. |
| 226 // it should run on. | |
| 227 Encoder* encoder = CreateEncoder(client->connection()->session()->config()); | |
| 228 | |
| 229 recorder_ = new ScreenRecorder(context_->capture_task_runner(), | |
| 230 context_->encode_task_runner(), | |
| 231 context_->network_task_runner(), | |
| 232 desktop_environment_->capturer(), | |
| 233 encoder); | |
| 234 if (client->connection()->session()->config().is_audio_enabled()) { | |
| 235 scoped_ptr<AudioEncoder> audio_encoder = | |
| 236 CreateAudioEncoder(client->connection()->session()->config()); | |
| 237 audio_scheduler_ = new AudioScheduler( | |
| 238 context_->capture_task_runner(), | |
| 239 context_->network_task_runner(), | |
| 240 desktop_environment_->audio_capturer(), | |
| 241 audio_encoder.Pass(), | |
| 242 client->connection()->audio_stub()); | |
| 243 } | |
| 244 | |
| 245 // Immediately add the connection and start the session. | |
| 246 recorder_->AddConnection(client->connection()); | |
| 247 recorder_->Start(); | |
| 248 desktop_environment_->OnSessionStarted(client->CreateClipboardProxy()); | |
| 249 | |
| 250 FOR_EACH_OBSERVER(HostStatusObserver, status_observers_, | 215 FOR_EACH_OBSERVER(HostStatusObserver, status_observers_, |
| 251 OnClientConnected(client->client_jid())); | 216 OnClientConnected(client->client_jid())); |
| 252 } | 217 } |
| 253 | 218 |
| 254 void ChromotingHost::OnSessionAuthenticationFailed(ClientSession* client) { | 219 void ChromotingHost::OnSessionAuthenticationFailed(ClientSession* client) { |
| 255 DCHECK(context_->network_task_runner()->BelongsToCurrentThread()); | 220 DCHECK(context_->network_task_runner()->BelongsToCurrentThread()); |
| 256 | 221 |
| 257 // Notify observers. | 222 // Notify observers. |
| 258 FOR_EACH_OBSERVER(HostStatusObserver, status_observers_, | 223 FOR_EACH_OBSERVER(HostStatusObserver, status_observers_, |
| 259 OnAccessDenied(client->client_jid())); | 224 OnAccessDenied(client->client_jid())); |
| 260 } | 225 } |
| 261 | 226 |
| 262 void ChromotingHost::OnSessionClosed(ClientSession* client) { | 227 void ChromotingHost::OnSessionClosed(ClientSession* client) { |
| 263 DCHECK(context_->network_task_runner()->BelongsToCurrentThread()); | 228 DCHECK(context_->network_task_runner()->BelongsToCurrentThread()); |
| 264 | 229 |
| 265 scoped_ptr<ClientSession> client_destroyer(client); | |
| 266 | |
| 267 ClientList::iterator it = std::find(clients_.begin(), clients_.end(), client); | 230 ClientList::iterator it = std::find(clients_.begin(), clients_.end(), client); |
| 268 CHECK(it != clients_.end()); | 231 CHECK(it != clients_.end()); |
| 269 clients_.erase(it); | 232 clients_.erase(it); |
| 270 | 233 |
| 271 if (recorder_.get()) { | |
| 272 recorder_->RemoveConnection(client->connection()); | |
| 273 } | |
| 274 | |
| 275 if (audio_scheduler_.get()) { | |
| 276 audio_scheduler_->OnClientDisconnected(); | |
| 277 StopAudioScheduler(); | |
| 278 } | |
| 279 | |
| 280 if (client->is_authenticated()) { | 234 if (client->is_authenticated()) { |
| 281 FOR_EACH_OBSERVER(HostStatusObserver, status_observers_, | 235 FOR_EACH_OBSERVER(HostStatusObserver, status_observers_, |
| 282 OnClientDisconnected(client->client_jid())); | 236 OnClientDisconnected(client->client_jid())); |
| 237 } | |
| 283 | 238 |
| 284 // TODO(sergeyu): This teardown logic belongs to ClientSession | 239 client->StopAndDelete(); |
| 285 // class. It should start/stop screen recorder or tell the host | |
| 286 // when to do it. | |
| 287 if (recorder_.get()) { | |
| 288 // Currently we don't allow more than one simultaneous connection, | |
| 289 // so we need to shutdown recorder when a client disconnects. | |
| 290 StopScreenRecorder(); | |
| 291 } | |
| 292 desktop_environment_->OnSessionFinished(); | |
| 293 } | |
| 294 } | 240 } |
| 295 | 241 |
| 296 void ChromotingHost::OnSessionSequenceNumber(ClientSession* session, | 242 void ChromotingHost::OnSessionSequenceNumber(ClientSession* session, |
| 297 int64 sequence_number) { | 243 int64 sequence_number) { |
| 298 DCHECK(context_->network_task_runner()->BelongsToCurrentThread()); | 244 DCHECK(context_->network_task_runner()->BelongsToCurrentThread()); |
| 299 if (recorder_.get()) | |
| 300 recorder_->UpdateSequenceNumber(sequence_number); | |
| 301 } | 245 } |
| 302 | 246 |
| 303 void ChromotingHost::OnSessionRouteChange( | 247 void ChromotingHost::OnSessionRouteChange( |
| 304 ClientSession* session, | 248 ClientSession* session, |
| 305 const std::string& channel_name, | 249 const std::string& channel_name, |
| 306 const protocol::TransportRoute& route) { | 250 const protocol::TransportRoute& route) { |
| 307 DCHECK(context_->network_task_runner()->BelongsToCurrentThread()); | 251 DCHECK(context_->network_task_runner()->BelongsToCurrentThread()); |
| 308 FOR_EACH_OBSERVER(HostStatusObserver, status_observers_, | 252 FOR_EACH_OBSERVER(HostStatusObserver, status_observers_, |
| 309 OnClientRouteChange(session->client_jid(), channel_name, | 253 OnClientRouteChange(session->client_jid(), channel_name, |
| 310 route)); | 254 route)); |
| (...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 344 *response = protocol::SessionManager::INCOMPATIBLE; | 288 *response = protocol::SessionManager::INCOMPATIBLE; |
| 345 return; | 289 return; |
| 346 } | 290 } |
| 347 | 291 |
| 348 session->set_config(config); | 292 session->set_config(config); |
| 349 | 293 |
| 350 *response = protocol::SessionManager::ACCEPT; | 294 *response = protocol::SessionManager::ACCEPT; |
| 351 | 295 |
| 352 LOG(INFO) << "Client connected: " << session->jid(); | 296 LOG(INFO) << "Client connected: " << session->jid(); |
| 353 | 297 |
| 298 // Create the desktop integration implementation for the client to use. | |
| 299 scoped_ptr<DesktopEnvironment> desktop_environment = | |
| 300 desktop_environment_factory_->Create(context_); | |
| 301 | |
| 354 // Create a client object. | 302 // Create a client object. |
| 355 scoped_ptr<protocol::ConnectionToClient> connection( | 303 scoped_ptr<protocol::ConnectionToClient> connection( |
| 356 new protocol::ConnectionToClient(session)); | 304 new protocol::ConnectionToClient(session)); |
| 357 ClientSession* client = new ClientSession( | 305 ClientSession* client = new ClientSession( |
| 358 this, | 306 this, |
| 307 context_->capture_task_runner(), | |
| 308 context_->encode_task_runner(), | |
| 309 context_->network_task_runner(), | |
| 359 connection.Pass(), | 310 connection.Pass(), |
| 360 desktop_environment_->event_executor(), | 311 desktop_environment.Pass(), |
| 361 desktop_environment_->event_executor(), | |
| 362 desktop_environment_->capturer(), | |
| 363 max_session_duration_); | 312 max_session_duration_); |
| 364 clients_.push_back(client); | 313 clients_.push_back(client); |
| 365 } | 314 } |
| 366 | 315 |
| 367 void ChromotingHost::set_protocol_config( | 316 void ChromotingHost::set_protocol_config( |
| 368 protocol::CandidateSessionConfig* config) { | 317 protocol::CandidateSessionConfig* config) { |
| 369 DCHECK(context_->network_task_runner()->BelongsToCurrentThread()); | 318 DCHECK(context_->network_task_runner()->BelongsToCurrentThread()); |
| 370 DCHECK(config); | 319 DCHECK(config); |
| 371 DCHECK_EQ(state_, kInitial); | 320 DCHECK_EQ(state_, kInitial); |
| 372 protocol_config_.reset(config); | 321 protocol_config_.reset(config); |
| (...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 413 } | 362 } |
| 414 } | 363 } |
| 415 | 364 |
| 416 void ChromotingHost::SetUiStrings(const UiStrings& ui_strings) { | 365 void ChromotingHost::SetUiStrings(const UiStrings& ui_strings) { |
| 417 DCHECK(context_->network_task_runner()->BelongsToCurrentThread()); | 366 DCHECK(context_->network_task_runner()->BelongsToCurrentThread()); |
| 418 DCHECK_EQ(state_, kInitial); | 367 DCHECK_EQ(state_, kInitial); |
| 419 | 368 |
| 420 ui_strings_ = ui_strings; | 369 ui_strings_ = ui_strings; |
| 421 } | 370 } |
| 422 | 371 |
| 423 // TODO(sergeyu): Move this to SessionManager? | |
| 424 // static | |
| 425 Encoder* ChromotingHost::CreateEncoder(const protocol::SessionConfig& config) { | |
| 426 const protocol::ChannelConfig& video_config = config.video_config(); | |
| 427 | |
| 428 if (video_config.codec == protocol::ChannelConfig::CODEC_VERBATIM) { | |
| 429 return EncoderRowBased::CreateVerbatimEncoder(); | |
| 430 } else if (video_config.codec == protocol::ChannelConfig::CODEC_ZIP) { | |
| 431 return EncoderRowBased::CreateZlibEncoder(); | |
| 432 } else if (video_config.codec == protocol::ChannelConfig::CODEC_VP8) { | |
| 433 return new remoting::EncoderVp8(); | |
| 434 } | |
| 435 | |
| 436 return NULL; | |
| 437 } | |
| 438 | |
| 439 // static | |
| 440 scoped_ptr<AudioEncoder> ChromotingHost::CreateAudioEncoder( | |
| 441 const protocol::SessionConfig& config) { | |
| 442 const protocol::ChannelConfig& audio_config = config.audio_config(); | |
| 443 | |
| 444 if (audio_config.codec == protocol::ChannelConfig::CODEC_VERBATIM) { | |
| 445 return scoped_ptr<AudioEncoder>(new AudioEncoderVerbatim()); | |
| 446 } | |
| 447 | |
| 448 NOTIMPLEMENTED(); | |
| 449 return scoped_ptr<AudioEncoder>(NULL); | |
| 450 } | |
| 451 | |
| 452 void ChromotingHost::StopScreenRecorder() { | |
| 453 DCHECK(context_->network_task_runner()->BelongsToCurrentThread()); | |
| 454 DCHECK(recorder_.get()); | |
| 455 | |
| 456 ++stopping_recorders_; | |
| 457 scoped_refptr<ScreenRecorder> recorder = recorder_; | |
| 458 recorder_ = NULL; | |
| 459 recorder->Stop(base::Bind(&ChromotingHost::OnRecorderStopped, this)); | |
| 460 } | |
| 461 | |
| 462 void ChromotingHost::StopAudioScheduler() { | |
| 463 DCHECK(context_->network_task_runner()->BelongsToCurrentThread()); | |
| 464 DCHECK(audio_scheduler_.get()); | |
| 465 | |
| 466 ++stopping_recorders_; | |
| 467 scoped_refptr<AudioScheduler> recorder = audio_scheduler_; | |
| 468 audio_scheduler_ = NULL; | |
| 469 recorder->Stop(base::Bind(&ChromotingHost::OnRecorderStopped, this)); | |
| 470 } | |
| 471 | |
| 472 void ChromotingHost::OnRecorderStopped() { | |
| 473 if (!context_->network_task_runner()->BelongsToCurrentThread()) { | |
| 474 context_->network_task_runner()->PostTask( | |
| 475 FROM_HERE, base::Bind(&ChromotingHost::OnRecorderStopped, this)); | |
| 476 return; | |
| 477 } | |
| 478 | |
| 479 --stopping_recorders_; | |
| 480 DCHECK_GE(stopping_recorders_, 0); | |
| 481 | |
| 482 if (!stopping_recorders_ && state_ == kStopping) | |
| 483 ShutdownFinish(); | |
| 484 } | |
| 485 | |
| 486 void ChromotingHost::ShutdownFinish() { | 372 void ChromotingHost::ShutdownFinish() { |
| 487 DCHECK(context_->network_task_runner()->BelongsToCurrentThread()); | 373 DCHECK(context_->network_task_runner()->BelongsToCurrentThread()); |
| 488 DCHECK(!stopping_recorders_); | |
| 489 | 374 |
| 490 state_ = kStopped; | 375 state_ = kStopped; |
| 491 | 376 |
| 492 // Keep reference to |this|, so that we don't get destroyed while | 377 // Keep reference to |this|, so that we don't get destroyed while |
| 493 // sending notifications. | 378 // sending notifications. |
| 494 scoped_refptr<ChromotingHost> self(this); | 379 scoped_refptr<ChromotingHost> self(this); |
| 495 | 380 |
| 496 // Notify observers. | 381 // Notify observers. |
| 497 FOR_EACH_OBSERVER(HostStatusObserver, status_observers_, | 382 FOR_EACH_OBSERVER(HostStatusObserver, status_observers_, |
| 498 OnShutdown()); | 383 OnShutdown()); |
| 499 | 384 |
| 500 for (std::vector<base::Closure>::iterator it = shutdown_tasks_.begin(); | 385 for (std::vector<base::Closure>::iterator it = shutdown_tasks_.begin(); |
| 501 it != shutdown_tasks_.end(); ++it) { | 386 it != shutdown_tasks_.end(); ++it) { |
| 502 it->Run(); | 387 it->Run(); |
| 503 } | 388 } |
| 504 shutdown_tasks_.clear(); | 389 shutdown_tasks_.clear(); |
| 505 } | 390 } |
| 506 | 391 |
| 507 } // namespace remoting | 392 } // namespace remoting |
| OLD | NEW |