[Cronet] Delay StartNetLog and StopNetLog until native request context is initialized
[chromium-blink-merge.git] / chrome / test / chromedriver / session_commands.cc
blob9795623d7ca25a4bae65ff56eb8b35db9ede4f61
1 // Copyright (c) 2013 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.
5 #include "chrome/test/chromedriver/session_commands.h"
7 #include <list>
9 #include "base/bind.h"
10 #include "base/callback.h"
11 #include "base/files/file_util.h"
12 #include "base/logging.h" // For CHECK macros.
13 #include "base/memory/ref_counted.h"
14 #include "base/message_loop/message_loop_proxy.h"
15 #include "base/synchronization/lock.h"
16 #include "base/synchronization/waitable_event.h"
17 #include "base/values.h"
18 #include "chrome/test/chromedriver/basic_types.h"
19 #include "chrome/test/chromedriver/capabilities.h"
20 #include "chrome/test/chromedriver/chrome/automation_extension.h"
21 #include "chrome/test/chromedriver/chrome/browser_info.h"
22 #include "chrome/test/chromedriver/chrome/chrome.h"
23 #include "chrome/test/chromedriver/chrome/chrome_android_impl.h"
24 #include "chrome/test/chromedriver/chrome/chrome_desktop_impl.h"
25 #include "chrome/test/chromedriver/chrome/device_manager.h"
26 #include "chrome/test/chromedriver/chrome/devtools_event_listener.h"
27 #include "chrome/test/chromedriver/chrome/geoposition.h"
28 #include "chrome/test/chromedriver/chrome/status.h"
29 #include "chrome/test/chromedriver/chrome/web_view.h"
30 #include "chrome/test/chromedriver/chrome_launcher.h"
31 #include "chrome/test/chromedriver/command_listener.h"
32 #include "chrome/test/chromedriver/logging.h"
33 #include "chrome/test/chromedriver/net/url_request_context_getter.h"
34 #include "chrome/test/chromedriver/session.h"
35 #include "chrome/test/chromedriver/util.h"
36 #include "chrome/test/chromedriver/version.h"
38 namespace {
40 const char kWindowHandlePrefix[] = "CDwindow-";
42 std::string WebViewIdToWindowHandle(const std::string& web_view_id) {
43 return kWindowHandlePrefix + web_view_id;
46 bool WindowHandleToWebViewId(const std::string& window_handle,
47 std::string* web_view_id) {
48 if (window_handle.find(kWindowHandlePrefix) != 0u)
49 return false;
50 *web_view_id = window_handle.substr(
51 std::string(kWindowHandlePrefix).length());
52 return true;
55 } // namespace
57 InitSessionParams::InitSessionParams(
58 scoped_refptr<URLRequestContextGetter> context_getter,
59 const SyncWebSocketFactory& socket_factory,
60 DeviceManager* device_manager,
61 PortServer* port_server,
62 PortManager* port_manager)
63 : context_getter(context_getter),
64 socket_factory(socket_factory),
65 device_manager(device_manager),
66 port_server(port_server),
67 port_manager(port_manager) {}
69 InitSessionParams::~InitSessionParams() {}
71 namespace {
73 scoped_ptr<base::DictionaryValue> CreateCapabilities(Chrome* chrome) {
74 scoped_ptr<base::DictionaryValue> caps(new base::DictionaryValue());
75 caps->SetString("browserName", "chrome");
76 caps->SetString("version", chrome->GetBrowserInfo()->browser_version);
77 caps->SetString("chrome.chromedriverVersion", kChromeDriverVersion);
78 caps->SetString("platform", chrome->GetOperatingSystemName());
79 caps->SetBoolean("javascriptEnabled", true);
80 caps->SetBoolean("takesScreenshot", true);
81 caps->SetBoolean("takesHeapSnapshot", true);
82 caps->SetBoolean("handlesAlerts", true);
83 caps->SetBoolean("databaseEnabled", false);
84 caps->SetBoolean("locationContextEnabled", true);
85 caps->SetBoolean("mobileEmulationEnabled",
86 chrome->IsMobileEmulationEnabled());
87 caps->SetBoolean("applicationCacheEnabled", false);
88 caps->SetBoolean("browserConnectionEnabled", false);
89 caps->SetBoolean("cssSelectorsEnabled", true);
90 caps->SetBoolean("webStorageEnabled", true);
91 caps->SetBoolean("rotatable", false);
92 caps->SetBoolean("acceptSslCerts", true);
93 caps->SetBoolean("nativeEvents", true);
94 scoped_ptr<base::DictionaryValue> chrome_caps(new base::DictionaryValue());
96 ChromeDesktopImpl* desktop = NULL;
97 Status status = chrome->GetAsDesktop(&desktop);
98 if (status.IsOk()) {
99 chrome_caps->SetString(
100 "userDataDir",
101 desktop->command().GetSwitchValueNative("user-data-dir"));
104 caps->Set("chrome", chrome_caps.release());
105 return caps.Pass();
108 Status CheckSessionCreated(Session* session) {
109 WebView* web_view = NULL;
110 Status status = session->GetTargetWindow(&web_view);
111 if (status.IsError())
112 return Status(kSessionNotCreatedException, status);
114 status = web_view->ConnectIfNecessary();
115 if (status.IsError())
116 return Status(kSessionNotCreatedException, status);
118 base::ListValue args;
119 scoped_ptr<base::Value> result(new base::FundamentalValue(0));
120 status = web_view->CallFunction(session->GetCurrentFrameId(),
121 "function(s) { return 1; }", args, &result);
122 if (status.IsError())
123 return Status(kSessionNotCreatedException, status);
125 int response;
126 if (!result->GetAsInteger(&response) || response != 1) {
127 return Status(kSessionNotCreatedException,
128 "unexpected response from browser");
131 return Status(kOk);
134 Status InitSessionHelper(
135 const InitSessionParams& bound_params,
136 Session* session,
137 const base::DictionaryValue& params,
138 scoped_ptr<base::Value>* value) {
139 session->driver_log.reset(
140 new WebDriverLog(WebDriverLog::kDriverType, Log::kAll));
141 const base::DictionaryValue* desired_caps;
142 if (!params.GetDictionary("desiredCapabilities", &desired_caps))
143 return Status(kUnknownError, "cannot find dict 'desiredCapabilities'");
145 Capabilities capabilities;
146 Status status = capabilities.Parse(*desired_caps);
147 if (status.IsError())
148 return status;
150 Log::Level driver_level = Log::kWarning;
151 if (capabilities.logging_prefs.count(WebDriverLog::kDriverType))
152 driver_level = capabilities.logging_prefs[WebDriverLog::kDriverType];
153 session->driver_log->set_min_level(driver_level);
155 // Create Log's and DevToolsEventListener's for ones that are DevTools-based.
156 // Session will own the Log's, Chrome will own the listeners.
157 // Also create |CommandListener|s for the appropriate logs.
158 ScopedVector<DevToolsEventListener> devtools_event_listeners;
159 ScopedVector<CommandListener> command_listeners;
160 status = CreateLogs(capabilities,
161 session,
162 &session->devtools_logs,
163 &devtools_event_listeners,
164 &command_listeners);
165 if (status.IsError())
166 return status;
168 // |session| will own the |CommandListener|s.
169 session->command_listeners.swap(command_listeners);
171 status = LaunchChrome(bound_params.context_getter.get(),
172 bound_params.socket_factory,
173 bound_params.device_manager,
174 bound_params.port_server,
175 bound_params.port_manager,
176 capabilities,
177 &devtools_event_listeners,
178 &session->chrome);
179 if (status.IsError())
180 return status;
182 std::list<std::string> web_view_ids;
183 status = session->chrome->GetWebViewIds(&web_view_ids);
184 if (status.IsError() || web_view_ids.empty()) {
185 return status.IsError() ? status :
186 Status(kUnknownError, "unable to discover open window in chrome");
189 session->window = web_view_ids.front();
190 session->detach = capabilities.detach;
191 session->force_devtools_screenshot = capabilities.force_devtools_screenshot;
192 session->capabilities = CreateCapabilities(session->chrome.get());
193 value->reset(session->capabilities->DeepCopy());
194 return CheckSessionCreated(session);
197 } // namespace
199 Status ExecuteInitSession(
200 const InitSessionParams& bound_params,
201 Session* session,
202 const base::DictionaryValue& params,
203 scoped_ptr<base::Value>* value) {
204 Status status = InitSessionHelper(bound_params, session, params, value);
205 if (status.IsError()) {
206 session->quit = true;
207 if (session->chrome != NULL)
208 session->chrome->Quit();
210 return status;
213 Status ExecuteQuit(
214 bool allow_detach,
215 Session* session,
216 const base::DictionaryValue& params,
217 scoped_ptr<base::Value>* value) {
218 session->quit = true;
219 if (allow_detach && session->detach)
220 return Status(kOk);
221 else
222 return session->chrome->Quit();
225 Status ExecuteGetSessionCapabilities(
226 Session* session,
227 const base::DictionaryValue& params,
228 scoped_ptr<base::Value>* value) {
229 value->reset(session->capabilities->DeepCopy());
230 return Status(kOk);
233 Status ExecuteGetCurrentWindowHandle(
234 Session* session,
235 const base::DictionaryValue& params,
236 scoped_ptr<base::Value>* value) {
237 WebView* web_view = NULL;
238 Status status = session->GetTargetWindow(&web_view);
239 if (status.IsError())
240 return status;
242 value->reset(
243 new base::StringValue(WebViewIdToWindowHandle(web_view->GetId())));
244 return Status(kOk);
247 Status ExecuteLaunchApp(
248 Session* session,
249 const base::DictionaryValue& params,
250 scoped_ptr<base::Value>* value) {
251 std::string id;
252 if (!params.GetString("id", &id))
253 return Status(kUnknownError, "'id' must be a string");
255 ChromeDesktopImpl* desktop = NULL;
256 Status status = session->chrome->GetAsDesktop(&desktop);
257 if (status.IsError())
258 return status;
260 AutomationExtension* extension = NULL;
261 status = desktop->GetAutomationExtension(&extension);
262 if (status.IsError())
263 return status;
265 return extension->LaunchApp(id);
268 Status ExecuteClose(
269 Session* session,
270 const base::DictionaryValue& params,
271 scoped_ptr<base::Value>* value) {
272 std::list<std::string> web_view_ids;
273 Status status = session->chrome->GetWebViewIds(&web_view_ids);
274 if (status.IsError())
275 return status;
276 bool is_last_web_view = web_view_ids.size() == 1u;
277 web_view_ids.clear();
279 WebView* web_view = NULL;
280 status = session->GetTargetWindow(&web_view);
281 if (status.IsError())
282 return status;
284 status = session->chrome->CloseWebView(web_view->GetId());
285 if (status.IsError())
286 return status;
288 status = session->chrome->GetWebViewIds(&web_view_ids);
289 if ((status.code() == kChromeNotReachable && is_last_web_view) ||
290 (status.IsOk() && web_view_ids.empty())) {
291 // If no window is open, close is the equivalent of calling "quit".
292 session->quit = true;
293 return session->chrome->Quit();
296 return status;
299 Status ExecuteGetWindowHandles(
300 Session* session,
301 const base::DictionaryValue& params,
302 scoped_ptr<base::Value>* value) {
303 std::list<std::string> web_view_ids;
304 Status status = session->chrome->GetWebViewIds(&web_view_ids);
305 if (status.IsError())
306 return status;
307 scoped_ptr<base::ListValue> window_ids(new base::ListValue());
308 for (std::list<std::string>::const_iterator it = web_view_ids.begin();
309 it != web_view_ids.end(); ++it) {
310 window_ids->AppendString(WebViewIdToWindowHandle(*it));
312 value->reset(window_ids.release());
313 return Status(kOk);
316 Status ExecuteSwitchToWindow(
317 Session* session,
318 const base::DictionaryValue& params,
319 scoped_ptr<base::Value>* value) {
320 std::string name;
321 if (!params.GetString("name", &name) || name.empty())
322 return Status(kUnknownError, "'name' must be a nonempty string");
324 std::list<std::string> web_view_ids;
325 Status status = session->chrome->GetWebViewIds(&web_view_ids);
326 if (status.IsError())
327 return status;
329 std::string web_view_id;
330 bool found = false;
331 if (WindowHandleToWebViewId(name, &web_view_id)) {
332 // Check if any web_view matches |web_view_id|.
333 for (std::list<std::string>::const_iterator it = web_view_ids.begin();
334 it != web_view_ids.end(); ++it) {
335 if (*it == web_view_id) {
336 found = true;
337 break;
340 } else {
341 // Check if any of the tab window names match |name|.
342 const char* kGetWindowNameScript = "function() { return window.name; }";
343 base::ListValue args;
344 for (std::list<std::string>::const_iterator it = web_view_ids.begin();
345 it != web_view_ids.end(); ++it) {
346 scoped_ptr<base::Value> result;
347 WebView* web_view;
348 status = session->chrome->GetWebViewById(*it, &web_view);
349 if (status.IsError())
350 return status;
351 status = web_view->ConnectIfNecessary();
352 if (status.IsError())
353 return status;
354 status = web_view->CallFunction(
355 std::string(), kGetWindowNameScript, args, &result);
356 if (status.IsError())
357 return status;
358 std::string window_name;
359 if (!result->GetAsString(&window_name))
360 return Status(kUnknownError, "failed to get window name");
361 if (window_name == name) {
362 web_view_id = *it;
363 found = true;
364 break;
369 if (!found)
370 return Status(kNoSuchWindow);
372 if (session->overridden_geoposition) {
373 WebView* web_view;
374 status = session->chrome->GetWebViewById(web_view_id, &web_view);
375 if (status.IsError())
376 return status;
377 status = web_view->ConnectIfNecessary();
378 if (status.IsError())
379 return status;
380 status = web_view->OverrideGeolocation(*session->overridden_geoposition);
381 if (status.IsError())
382 return status;
385 if (session->overridden_network_conditions) {
386 WebView* web_view;
387 status = session->chrome->GetWebViewById(web_view_id, &web_view);
388 if (status.IsError())
389 return status;
390 status = web_view->ConnectIfNecessary();
391 if (status.IsError())
392 return status;
393 status = web_view->OverrideNetworkConditions(
394 *session->overridden_network_conditions);
395 if (status.IsError())
396 return status;
399 session->window = web_view_id;
400 session->SwitchToTopFrame();
401 session->mouse_position = WebPoint(0, 0);
402 return Status(kOk);
405 Status ExecuteSetTimeout(
406 Session* session,
407 const base::DictionaryValue& params,
408 scoped_ptr<base::Value>* value) {
409 double ms_double;
410 if (!params.GetDouble("ms", &ms_double))
411 return Status(kUnknownError, "'ms' must be a double");
412 std::string type;
413 if (!params.GetString("type", &type))
414 return Status(kUnknownError, "'type' must be a string");
416 base::TimeDelta timeout =
417 base::TimeDelta::FromMilliseconds(static_cast<int>(ms_double));
418 // TODO(frankf): implicit and script timeout should be cleared
419 // if negative timeout is specified.
420 if (type == "implicit") {
421 session->implicit_wait = timeout;
422 } else if (type == "script") {
423 session->script_timeout = timeout;
424 } else if (type == "page load") {
425 session->page_load_timeout =
426 ((timeout < base::TimeDelta()) ? Session::kDefaultPageLoadTimeout
427 : timeout);
428 } else {
429 return Status(kUnknownError, "unknown type of timeout:" + type);
431 return Status(kOk);
434 Status ExecuteSetScriptTimeout(
435 Session* session,
436 const base::DictionaryValue& params,
437 scoped_ptr<base::Value>* value) {
438 double ms;
439 if (!params.GetDouble("ms", &ms) || ms < 0)
440 return Status(kUnknownError, "'ms' must be a non-negative number");
441 session->script_timeout =
442 base::TimeDelta::FromMilliseconds(static_cast<int>(ms));
443 return Status(kOk);
446 Status ExecuteImplicitlyWait(
447 Session* session,
448 const base::DictionaryValue& params,
449 scoped_ptr<base::Value>* value) {
450 double ms;
451 if (!params.GetDouble("ms", &ms) || ms < 0)
452 return Status(kUnknownError, "'ms' must be a non-negative number");
453 session->implicit_wait =
454 base::TimeDelta::FromMilliseconds(static_cast<int>(ms));
455 return Status(kOk);
458 Status ExecuteIsLoading(
459 Session* session,
460 const base::DictionaryValue& params,
461 scoped_ptr<base::Value>* value) {
462 WebView* web_view = NULL;
463 Status status = session->GetTargetWindow(&web_view);
464 if (status.IsError())
465 return status;
467 status = web_view->ConnectIfNecessary();
468 if (status.IsError())
469 return status;
471 bool is_pending;
472 status = web_view->IsPendingNavigation(
473 session->GetCurrentFrameId(), &is_pending);
474 if (status.IsError())
475 return status;
476 value->reset(new base::FundamentalValue(is_pending));
477 return Status(kOk);
480 Status ExecuteGetLocation(
481 Session* session,
482 const base::DictionaryValue& params,
483 scoped_ptr<base::Value>* value) {
484 if (!session->overridden_geoposition) {
485 return Status(kUnknownError,
486 "Location must be set before it can be retrieved");
488 base::DictionaryValue location;
489 location.SetDouble("latitude", session->overridden_geoposition->latitude);
490 location.SetDouble("longitude", session->overridden_geoposition->longitude);
491 location.SetDouble("accuracy", session->overridden_geoposition->accuracy);
492 // Set a dummy altitude to make WebDriver clients happy.
493 // https://code.google.com/p/chromedriver/issues/detail?id=281
494 location.SetDouble("altitude", 0);
495 value->reset(location.DeepCopy());
496 return Status(kOk);
499 Status ExecuteGetNetworkConditions(
500 Session* session,
501 const base::DictionaryValue& params,
502 scoped_ptr<base::Value>* value) {
503 if (!session->overridden_network_conditions) {
504 return Status(kUnknownError,
505 "network conditions must be set before it can be retrieved");
507 base::DictionaryValue conditions;
508 conditions.SetBoolean("offline",
509 session->overridden_network_conditions->offline);
510 conditions.SetInteger("latency",
511 session->overridden_network_conditions->latency);
512 conditions.SetInteger(
513 "download_throughput",
514 session->overridden_network_conditions->download_throughput);
515 conditions.SetInteger(
516 "upload_throughput",
517 session->overridden_network_conditions->upload_throughput);
518 value->reset(conditions.DeepCopy());
519 return Status(kOk);
522 Status ExecuteGetWindowPosition(
523 Session* session,
524 const base::DictionaryValue& params,
525 scoped_ptr<base::Value>* value) {
526 ChromeDesktopImpl* desktop = NULL;
527 Status status = session->chrome->GetAsDesktop(&desktop);
528 if (status.IsError())
529 return status;
531 AutomationExtension* extension = NULL;
532 status = desktop->GetAutomationExtension(&extension);
533 if (status.IsError())
534 return status;
536 int x, y;
537 status = extension->GetWindowPosition(&x, &y);
538 if (status.IsError())
539 return status;
541 base::DictionaryValue position;
542 position.SetInteger("x", x);
543 position.SetInteger("y", y);
544 value->reset(position.DeepCopy());
545 return Status(kOk);
548 Status ExecuteSetWindowPosition(
549 Session* session,
550 const base::DictionaryValue& params,
551 scoped_ptr<base::Value>* value) {
552 double x = 0;
553 double y = 0;
554 if (!params.GetDouble("x", &x) || !params.GetDouble("y", &y))
555 return Status(kUnknownError, "missing or invalid 'x' or 'y'");
557 ChromeDesktopImpl* desktop = NULL;
558 Status status = session->chrome->GetAsDesktop(&desktop);
559 if (status.IsError())
560 return status;
562 AutomationExtension* extension = NULL;
563 status = desktop->GetAutomationExtension(&extension);
564 if (status.IsError())
565 return status;
567 return extension->SetWindowPosition(static_cast<int>(x), static_cast<int>(y));
570 Status ExecuteGetWindowSize(
571 Session* session,
572 const base::DictionaryValue& params,
573 scoped_ptr<base::Value>* value) {
574 ChromeDesktopImpl* desktop = NULL;
575 Status status = session->chrome->GetAsDesktop(&desktop);
576 if (status.IsError())
577 return status;
579 AutomationExtension* extension = NULL;
580 status = desktop->GetAutomationExtension(&extension);
581 if (status.IsError())
582 return status;
584 int width, height;
585 status = extension->GetWindowSize(&width, &height);
586 if (status.IsError())
587 return status;
589 base::DictionaryValue size;
590 size.SetInteger("width", width);
591 size.SetInteger("height", height);
592 value->reset(size.DeepCopy());
593 return Status(kOk);
596 Status ExecuteSetWindowSize(
597 Session* session,
598 const base::DictionaryValue& params,
599 scoped_ptr<base::Value>* value) {
600 double width = 0;
601 double height = 0;
602 if (!params.GetDouble("width", &width) ||
603 !params.GetDouble("height", &height))
604 return Status(kUnknownError, "missing or invalid 'width' or 'height'");
606 ChromeDesktopImpl* desktop = NULL;
607 Status status = session->chrome->GetAsDesktop(&desktop);
608 if (status.IsError())
609 return status;
611 AutomationExtension* extension = NULL;
612 status = desktop->GetAutomationExtension(&extension);
613 if (status.IsError())
614 return status;
616 return extension->SetWindowSize(
617 static_cast<int>(width), static_cast<int>(height));
620 Status ExecuteMaximizeWindow(
621 Session* session,
622 const base::DictionaryValue& params,
623 scoped_ptr<base::Value>* value) {
624 ChromeDesktopImpl* desktop = NULL;
625 Status status = session->chrome->GetAsDesktop(&desktop);
626 if (status.IsError())
627 return status;
629 AutomationExtension* extension = NULL;
630 status = desktop->GetAutomationExtension(&extension);
631 if (status.IsError())
632 return status;
634 return extension->MaximizeWindow();
637 Status ExecuteGetAvailableLogTypes(
638 Session* session,
639 const base::DictionaryValue& params,
640 scoped_ptr<base::Value>* value) {
641 scoped_ptr<base::ListValue> types(new base::ListValue());
642 std::vector<WebDriverLog*> logs = session->GetAllLogs();
643 for (std::vector<WebDriverLog*>::const_iterator log = logs.begin();
644 log != logs.end();
645 ++log) {
646 types->AppendString((*log)->type());
648 *value = types.Pass();
649 return Status(kOk);
652 Status ExecuteGetLog(
653 Session* session,
654 const base::DictionaryValue& params,
655 scoped_ptr<base::Value>* value) {
656 std::string log_type;
657 if (!params.GetString("type", &log_type)) {
658 return Status(kUnknownError, "missing or invalid 'type'");
660 std::vector<WebDriverLog*> logs = session->GetAllLogs();
661 for (std::vector<WebDriverLog*>::const_iterator log = logs.begin();
662 log != logs.end();
663 ++log) {
664 if (log_type == (*log)->type()) {
665 *value = (*log)->GetAndClearEntries();
666 return Status(kOk);
669 return Status(kUnknownError, "log type '" + log_type + "' not found");
672 Status ExecuteUploadFile(
673 Session* session,
674 const base::DictionaryValue& params,
675 scoped_ptr<base::Value>* value) {
676 std::string base64_zip_data;
677 if (!params.GetString("file", &base64_zip_data))
678 return Status(kUnknownError, "missing or invalid 'file'");
679 std::string zip_data;
680 if (!Base64Decode(base64_zip_data, &zip_data))
681 return Status(kUnknownError, "unable to decode 'file'");
683 if (!session->temp_dir.IsValid()) {
684 if (!session->temp_dir.CreateUniqueTempDir())
685 return Status(kUnknownError, "unable to create temp dir");
687 base::FilePath upload_dir;
688 if (!base::CreateTemporaryDirInDir(session->temp_dir.path(),
689 FILE_PATH_LITERAL("upload"),
690 &upload_dir)) {
691 return Status(kUnknownError, "unable to create temp dir");
693 std::string error_msg;
694 base::FilePath upload;
695 Status status = UnzipSoleFile(upload_dir, zip_data, &upload);
696 if (status.IsError())
697 return Status(kUnknownError, "unable to unzip 'file'", status);
699 value->reset(new base::StringValue(upload.value()));
700 return Status(kOk);
703 Status ExecuteIsAutoReporting(
704 Session* session,
705 const base::DictionaryValue& params,
706 scoped_ptr<base::Value>* value) {
707 value->reset(new base::FundamentalValue(session->auto_reporting_enabled));
708 return Status(kOk);
711 Status ExecuteSetAutoReporting(
712 Session* session,
713 const base::DictionaryValue& params,
714 scoped_ptr<base::Value>* value) {
715 bool enabled;
716 if (!params.GetBoolean("enabled", &enabled))
717 return Status(kUnknownError, "missing parameter 'enabled'");
718 session->auto_reporting_enabled = enabled;
719 return Status(kOk);