Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(75)

Side by Side Diff: remoting/host/setup/daemon_controller_win.cc

Issue 23606019: Refactor the daemon controller so that the callbacks are called on the caller thread. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Created 7 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
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
3 // found in the LICENSE file.
4
5 #include "remoting/host/setup/daemon_controller.h"
6
7 #include "base/basictypes.h"
8 #include "base/bind.h"
9 #include "base/bind_helpers.h"
10 #include "base/compiler_specific.h"
11 #include "base/file_util.h"
12 #include "base/files/file_path.h"
13 #include "base/json/json_reader.h"
14 #include "base/json/json_writer.h"
15 #include "base/logging.h"
16 #include "base/strings/string16.h"
17 #include "base/strings/utf_string_conversions.h"
18 #include "base/threading/thread.h"
19 #include "base/time/time.h"
20 #include "base/timer/timer.h"
21 #include "base/values.h"
22 #include "base/win/scoped_bstr.h"
23 #include "base/win/scoped_comptr.h"
24 #include "base/win/windows_version.h"
25 #include "remoting/base/scoped_sc_handle_win.h"
26 #include "remoting/host/branding.h"
27 // chromoting_lib.h contains MIDL-generated declarations.
28 #include "remoting/host/chromoting_lib.h"
29 #include "remoting/host/setup/daemon_installer_win.h"
30 #include "remoting/host/usage_stats_consent.h"
31
32 using base::win::ScopedBstr;
33 using base::win::ScopedComPtr;
34
35 namespace remoting {
36
37 namespace {
38
39 // ProgID of the daemon controller.
40 const wchar_t kDaemonController[] =
41 L"ChromotingElevatedController.ElevatedController";
42
43 // The COM elevation moniker for the Elevated Controller.
44 const wchar_t kDaemonControllerElevationMoniker[] =
45 L"Elevation:Administrator!new:"
46 L"ChromotingElevatedController.ElevatedController";
47
48 // Name of the Daemon Controller's worker thread.
49 const char kDaemonControllerThreadName[] = "Daemon Controller thread";
50
51 // The maximum duration of keeping a reference to a privileged instance of
52 // the Daemon Controller. This effectively reduces number of UAC prompts a user
53 // sees.
54 const int kPrivilegedTimeoutSec = 5 * 60;
55
56 // The maximum duration of keeping a reference to an unprivileged instance of
57 // the Daemon Controller. This interval should not be too long. If upgrade
58 // happens while there is a live reference to a Daemon Controller instance
59 // the old binary still can be used. So dropping the references often makes sure
60 // that the old binary will go away sooner.
61 const int kUnprivilegedTimeoutSec = 60;
62
63 class DaemonControllerWin : public remoting::DaemonController {
64 public:
65 DaemonControllerWin();
66 virtual ~DaemonControllerWin();
67
68 virtual State GetState() OVERRIDE;
69 virtual void GetConfig(const GetConfigCallback& callback) OVERRIDE;
70 virtual void SetConfigAndStart(
71 scoped_ptr<base::DictionaryValue> config,
72 bool consent,
73 const CompletionCallback& done) OVERRIDE;
74 virtual void UpdateConfig(scoped_ptr<base::DictionaryValue> config,
75 const CompletionCallback& done_callback) OVERRIDE;
76 virtual void Stop(const CompletionCallback& done_callback) OVERRIDE;
77 virtual void SetWindow(void* window_handle) OVERRIDE;
78 virtual void GetVersion(const GetVersionCallback& done_callback) OVERRIDE;
79 virtual void GetUsageStatsConsent(
80 const GetUsageStatsConsentCallback& done) OVERRIDE;
81
82 private:
83 // Activates an unprivileged instance of the daemon controller and caches it.
84 HRESULT ActivateController();
85
86 // Activates an instance of the daemon controller and caches it. If COM
87 // Elevation is supported (Vista+) the activated instance is elevated,
88 // otherwise it is activated under credentials of the caller.
89 HRESULT ActivateElevatedController();
90
91 // Releases the cached instance of the controller.
92 void ReleaseController();
93
94 // Procedes with the daemon configuration if the installation succeeded,
95 // otherwise reports the error.
96 void OnInstallationComplete(scoped_ptr<base::DictionaryValue> config,
97 bool consent,
98 const CompletionCallback& done,
99 HRESULT result);
100
101 // Opens the Chromoting service returning its handle in |service_out|.
102 DWORD OpenService(ScopedScHandle* service_out);
103
104 // Converts a config dictionary to a scoped BSTR.
105 static void ConfigToString(const base::DictionaryValue& config,
106 ScopedBstr* out);
107
108 // Converts a Windows service status code to a Daemon state.
109 static State ConvertToDaemonState(DWORD service_state);
110
111 // Converts HRESULT to the AsyncResult.
112 static AsyncResult HResultToAsyncResult(HRESULT hr);
113
114 // The functions that actually do the work. They should be called in
115 // the context of |worker_thread_|;
116 void DoGetConfig(const GetConfigCallback& callback);
117 void DoInstallAsNeededAndStart(scoped_ptr<base::DictionaryValue> config,
118 bool consent,
119 const CompletionCallback& done_callback);
120 void DoSetConfigAndStart(scoped_ptr<base::DictionaryValue> config,
121 bool consent,
122 const CompletionCallback& done);
123 void DoUpdateConfig(scoped_ptr<base::DictionaryValue> config,
124 const CompletionCallback& done_callback);
125 void DoStop(const CompletionCallback& done_callback);
126 void DoSetWindow(void* window_handle);
127 void DoGetVersion(const GetVersionCallback& callback);
128 void DoGetUsageStatsConsent(
129 const GetUsageStatsConsentCallback& done);
130
131 // |control_| and |control2_| hold references to an instance of the daemon
132 // controller to prevent a UAC prompt on every operation.
133 ScopedComPtr<IDaemonControl> control_;
134 ScopedComPtr<IDaemonControl2> control2_;
135
136 // True if |control_| holds a reference to an elevated instance of the daemon
137 // controller.
138 bool control_is_elevated_;
139
140 // This timer is used to release |control_| after a timeout.
141 scoped_ptr<base::OneShotTimer<DaemonControllerWin> > release_timer_;
142
143 // Handle of the plugin window.
144 HWND window_handle_;
145
146 // The worker thread used for servicing long running operations.
147 base::Thread worker_thread_;
148
149 scoped_ptr<DaemonInstallerWin> installer_;
150
151 DISALLOW_COPY_AND_ASSIGN(DaemonControllerWin);
152 };
153
154 DaemonControllerWin::DaemonControllerWin()
155 : control_is_elevated_(false),
156 window_handle_(NULL),
157 worker_thread_(kDaemonControllerThreadName) {
158 worker_thread_.init_com_with_mta(false);
159 if (!worker_thread_.Start()) {
160 LOG(FATAL) << "Failed to start the Daemon Controller worker thread.";
161 }
162 }
163
164 DaemonControllerWin::~DaemonControllerWin() {
165 // Clean up resources allocated on the worker thread.
166 worker_thread_.message_loop_proxy()->PostTask(
167 FROM_HERE,
168 base::Bind(&DaemonControllerWin::ReleaseController,
169 base::Unretained(this)));
170 worker_thread_.Stop();
171 }
172
173 remoting::DaemonController::State DaemonControllerWin::GetState() {
174 if (base::win::GetVersion() < base::win::VERSION_XP) {
175 return STATE_NOT_IMPLEMENTED;
176 }
177 // TODO(alexeypa): Make the thread alertable, so we can switch to APC
178 // notifications rather than polling.
179 ScopedScHandle service;
180 DWORD error = OpenService(&service);
181
182 switch (error) {
183 case ERROR_SUCCESS: {
184 SERVICE_STATUS status;
185 if (::QueryServiceStatus(service, &status)) {
186 return ConvertToDaemonState(status.dwCurrentState);
187 } else {
188 LOG_GETLASTERROR(ERROR)
189 << "Failed to query the state of the '" << kWindowsServiceName
190 << "' service";
191 return STATE_UNKNOWN;
192 }
193 break;
194 }
195 case ERROR_SERVICE_DOES_NOT_EXIST:
196 return STATE_NOT_INSTALLED;
197 default:
198 return STATE_UNKNOWN;
199 }
200 }
201
202 void DaemonControllerWin::GetConfig(const GetConfigCallback& callback) {
203 worker_thread_.message_loop_proxy()->PostTask(
204 FROM_HERE,
205 base::Bind(&DaemonControllerWin::DoGetConfig,
206 base::Unretained(this), callback));
207 }
208
209 void DaemonControllerWin::SetConfigAndStart(
210 scoped_ptr<base::DictionaryValue> config,
211 bool consent,
212 const CompletionCallback& done) {
213 worker_thread_.message_loop_proxy()->PostTask(
214 FROM_HERE, base::Bind(
215 &DaemonControllerWin::DoInstallAsNeededAndStart,
216 base::Unretained(this), base::Passed(&config), consent, done));
217 }
218
219 void DaemonControllerWin::UpdateConfig(
220 scoped_ptr<base::DictionaryValue> config,
221 const CompletionCallback& done_callback) {
222 worker_thread_.message_loop_proxy()->PostTask(
223 FROM_HERE, base::Bind(
224 &DaemonControllerWin::DoUpdateConfig,
225 base::Unretained(this), base::Passed(&config), done_callback));
226 }
227
228 void DaemonControllerWin::Stop(const CompletionCallback& done_callback) {
229 worker_thread_.message_loop_proxy()->PostTask(
230 FROM_HERE, base::Bind(
231 &DaemonControllerWin::DoStop, base::Unretained(this),
232 done_callback));
233 }
234
235 void DaemonControllerWin::SetWindow(void* window_handle) {
236 worker_thread_.message_loop_proxy()->PostTask(
237 FROM_HERE, base::Bind(
238 &DaemonControllerWin::DoSetWindow, base::Unretained(this),
239 window_handle));
240 }
241
242 void DaemonControllerWin::GetVersion(const GetVersionCallback& callback) {
243 worker_thread_.message_loop_proxy()->PostTask(
244 FROM_HERE,
245 base::Bind(&DaemonControllerWin::DoGetVersion,
246 base::Unretained(this), callback));
247 }
248
249 void DaemonControllerWin::GetUsageStatsConsent(
250 const GetUsageStatsConsentCallback& done) {
251 worker_thread_.message_loop_proxy()->PostTask(
252 FROM_HERE,
253 base::Bind(&DaemonControllerWin::DoGetUsageStatsConsent,
254 base::Unretained(this), done));
255 }
256
257 HRESULT DaemonControllerWin::ActivateController() {
258 DCHECK(worker_thread_.message_loop_proxy()->BelongsToCurrentThread());
259
260 if (control_.get() == NULL) {
261 CLSID class_id;
262 HRESULT hr = CLSIDFromProgID(kDaemonController, &class_id);
263 if (FAILED(hr)) {
264 return hr;
265 }
266
267 hr = CoCreateInstance(class_id, NULL, CLSCTX_LOCAL_SERVER,
268 IID_IDaemonControl, control_.ReceiveVoid());
269 if (FAILED(hr)) {
270 return hr;
271 }
272
273 // Ignore the error. IID_IDaemonControl2 is optional.
274 control_.QueryInterface(IID_IDaemonControl2, control2_.ReceiveVoid());
275
276 // Release |control_| upon expiration of the timeout.
277 release_timer_.reset(new base::OneShotTimer<DaemonControllerWin>());
278 release_timer_->Start(FROM_HERE,
279 base::TimeDelta::FromSeconds(kUnprivilegedTimeoutSec),
280 this,
281 &DaemonControllerWin::ReleaseController);
282 }
283
284 return S_OK;
285 }
286
287 HRESULT DaemonControllerWin::ActivateElevatedController() {
288 DCHECK(worker_thread_.message_loop_proxy()->BelongsToCurrentThread());
289
290 // The COM elevation is supported on Vista and above.
291 if (base::win::GetVersion() < base::win::VERSION_VISTA) {
292 return ActivateController();
293 }
294
295 // Release an unprivileged instance of the daemon controller if any.
296 if (!control_is_elevated_) {
297 ReleaseController();
298 }
299
300 if (control_.get() == NULL) {
301 BIND_OPTS3 bind_options;
302 memset(&bind_options, 0, sizeof(bind_options));
303 bind_options.cbStruct = sizeof(bind_options);
304 bind_options.hwnd = GetTopLevelWindow(window_handle_);
305 bind_options.dwClassContext = CLSCTX_LOCAL_SERVER;
306
307 HRESULT hr = ::CoGetObject(
308 kDaemonControllerElevationMoniker,
309 &bind_options,
310 IID_IDaemonControl,
311 control_.ReceiveVoid());
312 if (FAILED(hr)) {
313 return hr;
314 }
315
316 // Ignore the error. IID_IDaemonControl2 is optional.
317 control_.QueryInterface(IID_IDaemonControl2, control2_.ReceiveVoid());
318
319 // Note that we hold a reference to an elevated instance now.
320 control_is_elevated_ = true;
321
322 // Release |control_| upon expiration of the timeout.
323 release_timer_.reset(new base::OneShotTimer<DaemonControllerWin>());
324 release_timer_->Start(FROM_HERE,
325 base::TimeDelta::FromSeconds(kPrivilegedTimeoutSec),
326 this,
327 &DaemonControllerWin::ReleaseController);
328 }
329
330 return S_OK;
331 }
332
333 void DaemonControllerWin::ReleaseController() {
334 DCHECK(worker_thread_.message_loop_proxy()->BelongsToCurrentThread());
335
336 control_.Release();
337 control2_.Release();
338 release_timer_.reset();
339 control_is_elevated_ = false;
340 }
341
342 void DaemonControllerWin::OnInstallationComplete(
343 scoped_ptr<base::DictionaryValue> config,
344 bool consent,
345 const CompletionCallback& done,
346 HRESULT result) {
347 DCHECK(worker_thread_.message_loop_proxy()->BelongsToCurrentThread());
348
349 if (SUCCEEDED(result)) {
350 DoSetConfigAndStart(config.Pass(), consent, done);
351 } else {
352 LOG(ERROR) << "Failed to install the Chromoting Host "
353 << "(error: 0x" << std::hex << result << std::dec << ").";
354 done.Run(HResultToAsyncResult(result));
355 }
356
357 DCHECK(installer_.get() != NULL);
358 installer_.reset();
359 }
360
361 DWORD DaemonControllerWin::OpenService(ScopedScHandle* service_out) {
362 // Open the service and query its current state.
363 ScopedScHandle scmanager(
364 ::OpenSCManagerW(NULL, SERVICES_ACTIVE_DATABASE,
365 SC_MANAGER_CONNECT | SC_MANAGER_ENUMERATE_SERVICE));
366 if (!scmanager.IsValid()) {
367 DWORD error = GetLastError();
368 LOG_GETLASTERROR(ERROR)
369 << "Failed to connect to the service control manager";
370 return error;
371 }
372
373 ScopedScHandle service(
374 ::OpenServiceW(scmanager, kWindowsServiceName, SERVICE_QUERY_STATUS));
375 if (!service.IsValid()) {
376 DWORD error = GetLastError();
377 if (error != ERROR_SERVICE_DOES_NOT_EXIST) {
378 LOG_GETLASTERROR(ERROR)
379 << "Failed to open to the '" << kWindowsServiceName << "' service";
380 }
381 return error;
382 }
383
384 service_out->Set(service.Take());
385 return ERROR_SUCCESS;
386 }
387
388 // static
389 void DaemonControllerWin::ConfigToString(const base::DictionaryValue& config,
390 ScopedBstr* out) {
391 std::string config_str;
392 base::JSONWriter::Write(&config, &config_str);
393 ScopedBstr config_scoped_bstr(UTF8ToUTF16(config_str).c_str());
394 out->Swap(config_scoped_bstr);
395 }
396
397 // static
398 remoting::DaemonController::State DaemonControllerWin::ConvertToDaemonState(
399 DWORD service_state) {
400 switch (service_state) {
401 case SERVICE_RUNNING:
402 return STATE_STARTED;
403
404 case SERVICE_CONTINUE_PENDING:
405 case SERVICE_START_PENDING:
406 return STATE_STARTING;
407 break;
408
409 case SERVICE_PAUSE_PENDING:
410 case SERVICE_STOP_PENDING:
411 return STATE_STOPPING;
412 break;
413
414 case SERVICE_PAUSED:
415 case SERVICE_STOPPED:
416 return STATE_STOPPED;
417 break;
418
419 default:
420 NOTREACHED();
421 return STATE_UNKNOWN;
422 }
423 }
424
425 // static
426 DaemonController::AsyncResult DaemonControllerWin::HResultToAsyncResult(
427 HRESULT hr) {
428 if (SUCCEEDED(hr)) {
429 return RESULT_OK;
430 } else if (hr == HRESULT_FROM_WIN32(ERROR_CANCELLED)) {
431 return RESULT_CANCELLED;
432 } else {
433 // TODO(sergeyu): Report other errors to the webapp once it knows
434 // how to handle them.
435 return RESULT_FAILED;
436 }
437 }
438
439 void DaemonControllerWin::DoGetConfig(const GetConfigCallback& callback) {
440 DCHECK(worker_thread_.message_loop_proxy()->BelongsToCurrentThread());
441
442 scoped_ptr<base::DictionaryValue> dictionary_null;
443
444 // Configure and start the Daemon Controller if it is installed already.
445 HRESULT hr = ActivateController();
446 if (FAILED(hr)) {
447 callback.Run(dictionary_null.Pass());
448 return;
449 }
450
451 // Get the host configuration.
452 ScopedBstr host_config;
453 hr = control_->GetConfig(host_config.Receive());
454 if (FAILED(hr)) {
455 callback.Run(dictionary_null.Pass());
456 return;
457 }
458
459 // Parse the string into a dictionary.
460 string16 file_content(static_cast<BSTR>(host_config), host_config.Length());
461 scoped_ptr<base::Value> config(
462 base::JSONReader::Read(UTF16ToUTF8(file_content),
463 base::JSON_ALLOW_TRAILING_COMMAS));
464
465 base::DictionaryValue* dictionary;
466 if (config.get() == NULL || !config->GetAsDictionary(&dictionary)) {
467 callback.Run(dictionary_null.Pass());
468 return;
469 }
470 // Release |config|, because dictionary points to the same object.
471 config.release();
472
473 callback.Run(scoped_ptr<base::DictionaryValue>(dictionary));
474 }
475
476 void DaemonControllerWin::DoInstallAsNeededAndStart(
477 scoped_ptr<base::DictionaryValue> config,
478 bool consent,
479 const CompletionCallback& done) {
480 DCHECK(worker_thread_.message_loop_proxy()->BelongsToCurrentThread());
481
482 // Configure and start the Daemon Controller if it is installed already.
483 HRESULT hr = ActivateElevatedController();
484 if (SUCCEEDED(hr)) {
485 DoSetConfigAndStart(config.Pass(), consent, done);
486 return;
487 }
488
489 // Otherwise, install it if its COM registration entry is missing.
490 if (hr == CO_E_CLASSSTRING) {
491 scoped_ptr<DaemonInstallerWin> installer = DaemonInstallerWin::Create(
492 GetTopLevelWindow(window_handle_),
493 base::Bind(&DaemonControllerWin::OnInstallationComplete,
494 base::Unretained(this),
495 base::Passed(&config),
496 consent,
497 done));
498 if (installer.get()) {
499 DCHECK(!installer_.get());
500 installer_ = installer.Pass();
501 installer_->Install();
502 }
503 } else {
504 LOG(ERROR) << "Failed to initiate the Chromoting Host installation "
505 << "(error: 0x" << std::hex << hr << std::dec << ").";
506 done.Run(HResultToAsyncResult(hr));
507 }
508 }
509
510 void DaemonControllerWin::DoSetConfigAndStart(
511 scoped_ptr<base::DictionaryValue> config,
512 bool consent,
513 const CompletionCallback& done) {
514 DCHECK(worker_thread_.message_loop_proxy()->BelongsToCurrentThread());
515
516 HRESULT hr = ActivateElevatedController();
517 if (FAILED(hr)) {
518 done.Run(HResultToAsyncResult(hr));
519 return;
520 }
521
522 // Record the user's consent.
523 if (control2_.get()) {
524 hr = control2_->SetUsageStatsConsent(consent);
525 if (FAILED(hr)) {
526 done.Run(HResultToAsyncResult(hr));
527 return;
528 }
529 }
530
531 // Set the configuration.
532 ScopedBstr config_str(NULL);
533 ConfigToString(*config, &config_str);
534 if (config_str == NULL) {
535 done.Run(HResultToAsyncResult(E_OUTOFMEMORY));
536 return;
537 }
538
539 hr = control_->SetOwnerWindow(
540 reinterpret_cast<LONG_PTR>(GetTopLevelWindow(window_handle_)));
541 if (FAILED(hr)) {
542 done.Run(HResultToAsyncResult(hr));
543 return;
544 }
545
546 hr = control_->SetConfig(config_str);
547 if (FAILED(hr)) {
548 done.Run(HResultToAsyncResult(hr));
549 return;
550 }
551
552 // Start daemon.
553 hr = control_->StartDaemon();
554 done.Run(HResultToAsyncResult(hr));
555 }
556
557 void DaemonControllerWin::DoUpdateConfig(
558 scoped_ptr<base::DictionaryValue> config,
559 const CompletionCallback& done_callback) {
560 DCHECK(worker_thread_.message_loop_proxy()->BelongsToCurrentThread());
561
562 HRESULT hr = ActivateElevatedController();
563 if (FAILED(hr)) {
564 done_callback.Run(HResultToAsyncResult(hr));
565 return;
566 }
567
568 // Update the configuration.
569 ScopedBstr config_str(NULL);
570 ConfigToString(*config, &config_str);
571 if (config_str == NULL) {
572 done_callback.Run(HResultToAsyncResult(E_OUTOFMEMORY));
573 return;
574 }
575
576 // Make sure that the PIN confirmation dialog is focused properly.
577 hr = control_->SetOwnerWindow(
578 reinterpret_cast<LONG_PTR>(GetTopLevelWindow(window_handle_)));
579 if (FAILED(hr)) {
580 done_callback.Run(HResultToAsyncResult(hr));
581 return;
582 }
583
584 hr = control_->UpdateConfig(config_str);
585 done_callback.Run(HResultToAsyncResult(hr));
586 }
587
588 void DaemonControllerWin::DoStop(const CompletionCallback& done_callback) {
589 DCHECK(worker_thread_.message_loop_proxy()->BelongsToCurrentThread());
590
591 HRESULT hr = ActivateElevatedController();
592 if (FAILED(hr)) {
593 done_callback.Run(HResultToAsyncResult(hr));
594 return;
595 }
596
597 hr = control_->StopDaemon();
598 done_callback.Run(HResultToAsyncResult(hr));
599 }
600
601 void DaemonControllerWin::DoSetWindow(void* window_handle) {
602 window_handle_ = reinterpret_cast<HWND>(window_handle);
603 }
604
605 void DaemonControllerWin::DoGetVersion(const GetVersionCallback& callback) {
606 DCHECK(worker_thread_.message_loop_proxy()->BelongsToCurrentThread());
607
608 std::string version_null;
609
610 // Configure and start the Daemon Controller if it is installed already.
611 HRESULT hr = ActivateController();
612 if (FAILED(hr)) {
613 callback.Run(version_null);
614 return;
615 }
616
617 // Get the version string.
618 ScopedBstr version;
619 hr = control_->GetVersion(version.Receive());
620 if (FAILED(hr)) {
621 callback.Run(version_null);
622 return;
623 }
624
625 callback.Run(UTF16ToUTF8(
626 string16(static_cast<BSTR>(version), version.Length())));
627 }
628
629 void DaemonControllerWin::DoGetUsageStatsConsent(
630 const GetUsageStatsConsentCallback& done) {
631 DCHECK(worker_thread_.message_loop_proxy()->BelongsToCurrentThread());
632
633 // Activate the Daemon Controller and see if it supports |IDaemonControl2|.
634 HRESULT hr = ActivateController();
635 if (FAILED(hr)) {
636 // The host is not installed yet. Assume that the user didn't consent to
637 // collecting crash dumps.
638 done.Run(true, false, false);
639 return;
640 }
641
642 if (control2_.get() == NULL) {
643 // The host is installed and does not support crash dump reporting.
644 done.Run(false, false, false);
645 return;
646 }
647
648 // Get the recorded user's consent.
649 BOOL allowed;
650 BOOL set_by_policy;
651 hr = control2_->GetUsageStatsConsent(&allowed, &set_by_policy);
652 if (FAILED(hr)) {
653 // If the user's consent is not recorded yet, assume that the user didn't
654 // consent to collecting crash dumps.
655 done.Run(true, false, false);
656 return;
657 }
658
659 done.Run(true, !!allowed, !!set_by_policy);
660 }
661
662 } // namespace
663
664 scoped_ptr<DaemonController> remoting::DaemonController::Create() {
665 return scoped_ptr<DaemonController>(new DaemonControllerWin());
666 }
667
668 } // namespace remoting
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698