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 "chrome/browser/ui/webui/options/chromeos/display_options_handler.h"
6
7#include <string>
8
9#include "ash/display/display_configurator_animation.h"
10#include "ash/display/display_controller.h"
11#include "ash/display/display_manager.h"
12#include "ash/display/resolution_notification_controller.h"
13#include "ash/shell.h"
14#include "base/bind.h"
15#include "base/logging.h"
16#include "base/strings/string_number_conversions.h"
17#include "base/strings/stringprintf.h"
18#include "base/values.h"
19#include "chrome/browser/chromeos/display/display_preferences.h"
20#include "chrome/grit/generated_resources.h"
21#include "content/public/browser/user_metrics.h"
22#include "content/public/browser/web_ui.h"
23#include "grit/ash_strings.h"
24#include "ui/base/l10n/l10n_util.h"
25#include "ui/gfx/display.h"
26#include "ui/gfx/rect.h"
27#include "ui/gfx/screen.h"
28#include "ui/gfx/size_conversions.h"
29
30using ash::DisplayManager;
31
32namespace chromeos {
33namespace options {
34namespace {
35
36DisplayManager* GetDisplayManager() {
37  return ash::Shell::GetInstance()->display_manager();
38}
39
40int64 GetDisplayId(const base::ListValue* args) {
41  // Assumes the display ID is specified as the first argument.
42  std::string id_value;
43  if (!args->GetString(0, &id_value)) {
44    LOG(ERROR) << "Can't find ID";
45    return gfx::Display::kInvalidDisplayID;
46  }
47
48  int64 display_id = gfx::Display::kInvalidDisplayID;
49  if (!base::StringToInt64(id_value, &display_id)) {
50    LOG(ERROR) << "Invalid display id: " << id_value;
51    return gfx::Display::kInvalidDisplayID;
52  }
53
54  return display_id;
55}
56
57base::string16 GetColorProfileName(ui::ColorCalibrationProfile profile) {
58  switch (profile) {
59    case ui::COLOR_PROFILE_STANDARD:
60      return l10n_util::GetStringUTF16(
61          IDS_OPTIONS_SETTINGS_DISPLAY_OPTIONS_COLOR_PROFILE_STANDARD);
62    case ui::COLOR_PROFILE_DYNAMIC:
63      return l10n_util::GetStringUTF16(
64          IDS_OPTIONS_SETTINGS_DISPLAY_OPTIONS_COLOR_PROFILE_DYNAMIC);
65    case ui::COLOR_PROFILE_MOVIE:
66      return l10n_util::GetStringUTF16(
67          IDS_OPTIONS_SETTINGS_DISPLAY_OPTIONS_COLOR_PROFILE_MOVIE);
68    case ui::COLOR_PROFILE_READING:
69      return l10n_util::GetStringUTF16(
70          IDS_OPTIONS_SETTINGS_DISPLAY_OPTIONS_COLOR_PROFILE_READING);
71    case ui::NUM_COLOR_PROFILES:
72      break;
73  }
74
75  NOTREACHED();
76  return base::string16();
77}
78
79int GetIntOrDouble(const base::DictionaryValue* dict,
80                   const std::string& field) {
81  double double_result = 0;
82  if (dict->GetDouble(field, &double_result))
83    return static_cast<int>(double_result);
84
85  int result = 0;
86  dict->GetInteger(field, &result);
87  return result;
88}
89
90bool GetFloat(const base::DictionaryValue* dict,
91              const std::string& field,
92              float* result) {
93  double double_result = 0;
94  if (dict->GetDouble(field, &double_result)) {
95    *result = static_cast<float>(double_result);
96    return true;
97  }
98  return false;
99}
100
101bool ConvertValueToDisplayMode(const base::DictionaryValue* dict,
102                               ash::DisplayMode* mode) {
103  mode->size.set_width(GetIntOrDouble(dict, "originalWidth"));
104  mode->size.set_height(GetIntOrDouble(dict, "originalHeight"));
105  if (mode->size.IsEmpty()) {
106    LOG(ERROR) << "missing width or height.";
107    return false;
108  }
109  if (!GetFloat(dict, "refreshRate", &mode->refresh_rate)) {
110    LOG(ERROR) << "missing refreshRate.";
111    return false;
112  }
113  if (!GetFloat(dict, "scale", &mode->ui_scale)) {
114    LOG(ERROR) << "missing ui-scale.";
115    return false;
116  }
117  if (!GetFloat(dict, "deviceScaleFactor", &mode->device_scale_factor)) {
118    LOG(ERROR) << "missing deviceScaleFactor.";
119    return false;
120  }
121  return true;
122}
123
124base::DictionaryValue* ConvertDisplayModeToValue(int64 display_id,
125                                                 const ash::DisplayMode& mode) {
126  bool is_internal = GetDisplayManager()->IsInternalDisplayId(display_id);
127  base::DictionaryValue* result = new base::DictionaryValue();
128  gfx::Size size_dip = mode.GetSizeInDIP();
129  result->SetInteger("width", size_dip.width());
130  result->SetInteger("height", size_dip.height());
131  result->SetInteger("originalWidth", mode.size.width());
132  result->SetInteger("originalHeight", mode.size.height());
133  result->SetDouble("deviceScaleFactor", mode.device_scale_factor);
134  result->SetDouble("scale", mode.ui_scale);
135  result->SetDouble("refreshRate", mode.refresh_rate);
136  result->SetBoolean(
137      "isBest", is_internal ? (mode.ui_scale == 1.0f) : mode.native);
138  result->SetBoolean("isNative", mode.native);
139  result->SetBoolean(
140      "selected", mode.IsEquivalent(
141          GetDisplayManager()->GetActiveModeForDisplayId(display_id)));
142  return result;
143}
144
145}  // namespace
146
147DisplayOptionsHandler::DisplayOptionsHandler() {
148#if !defined(USE_ATHENA)
149  // ash::Shell doesn't exist in Athena.
150  // See: http://crbug.com/416961
151  ash::Shell::GetInstance()->display_controller()->AddObserver(this);
152#endif
153}
154
155DisplayOptionsHandler::~DisplayOptionsHandler() {
156#if !defined(USE_ATHENA)
157  // ash::Shell doesn't exist in Athena.
158  ash::Shell::GetInstance()->display_controller()->RemoveObserver(this);
159#endif
160}
161
162void DisplayOptionsHandler::GetLocalizedValues(
163    base::DictionaryValue* localized_strings) {
164  DCHECK(localized_strings);
165  RegisterTitle(localized_strings, "displayOptionsPage",
166                IDS_OPTIONS_SETTINGS_DISPLAY_OPTIONS_TAB_TITLE);
167
168  localized_strings->SetString(
169      "selectedDisplayTitleOptions", l10n_util::GetStringUTF16(
170          IDS_OPTIONS_SETTINGS_DISPLAY_OPTIONS_OPTIONS));
171  localized_strings->SetString(
172      "selectedDisplayTitleResolution", l10n_util::GetStringUTF16(
173          IDS_OPTIONS_SETTINGS_DISPLAY_OPTIONS_RESOLUTION));
174  localized_strings->SetString(
175      "selectedDisplayTitleOrientation", l10n_util::GetStringUTF16(
176          IDS_OPTIONS_SETTINGS_DISPLAY_OPTIONS_ORIENTATION));
177  localized_strings->SetString(
178      "selectedDisplayTitleOverscan", l10n_util::GetStringUTF16(
179          IDS_OPTIONS_SETTINGS_DISPLAY_OPTIONS_OVERSCAN));
180
181  localized_strings->SetString("startMirroring", l10n_util::GetStringUTF16(
182      IDS_OPTIONS_SETTINGS_DISPLAY_OPTIONS_START_MIRRORING));
183  localized_strings->SetString("stopMirroring", l10n_util::GetStringUTF16(
184      IDS_OPTIONS_SETTINGS_DISPLAY_OPTIONS_STOP_MIRRORING));
185  localized_strings->SetString("mirroringDisplay", l10n_util::GetStringUTF16(
186      IDS_OPTIONS_SETTINGS_DISPLAY_OPTIONS_MIRRORING_DISPLAY_NAME));
187  localized_strings->SetString("setPrimary", l10n_util::GetStringUTF16(
188      IDS_OPTIONS_SETTINGS_DISPLAY_OPTIONS_SET_PRIMARY));
189  localized_strings->SetString("annotateBest", l10n_util::GetStringUTF16(
190      IDS_OPTIONS_SETTINGS_DISPLAY_OPTIONS_RESOLUTION_ANNOTATION_BEST));
191  localized_strings->SetString("annotateNative", l10n_util::GetStringUTF16(
192      IDS_OPTIONS_SETTINGS_DISPLAY_OPTIONS_RESOLUTION_ANNOTATION_NATIVE));
193  localized_strings->SetString("orientation0", l10n_util::GetStringUTF16(
194      IDS_OPTIONS_SETTINGS_DISPLAY_OPTIONS_STANDARD_ORIENTATION));
195  localized_strings->SetString("orientation90", l10n_util::GetStringUTF16(
196      IDS_ASH_STATUS_TRAY_DISPLAY_ORIENTATION_90));
197  localized_strings->SetString("orientation180", l10n_util::GetStringUTF16(
198      IDS_ASH_STATUS_TRAY_DISPLAY_ORIENTATION_180));
199  localized_strings->SetString("orientation270", l10n_util::GetStringUTF16(
200      IDS_ASH_STATUS_TRAY_DISPLAY_ORIENTATION_270));
201  localized_strings->SetString(
202      "startCalibratingOverscan", l10n_util::GetStringUTF16(
203          IDS_OPTIONS_SETTINGS_DISPLAY_OPTIONS_START_CALIBRATING_OVERSCAN));
204  localized_strings->SetString(
205      "selectedDisplayColorProfile", l10n_util::GetStringUTF16(
206          IDS_OPTIONS_SETTINGS_DISPLAY_OPTIONS_COLOR_PROFILE));
207}
208
209void DisplayOptionsHandler::InitializePage() {
210  DCHECK(web_ui());
211#if !defined(USE_ATHENA)
212  web_ui()->CallJavascriptFunction(
213      "options.BrowserOptions.enableDisplayButton",
214      base::FundamentalValue(true));
215#endif
216}
217
218void DisplayOptionsHandler::RegisterMessages() {
219  web_ui()->RegisterMessageCallback(
220      "getDisplayInfo",
221      base::Bind(&DisplayOptionsHandler::HandleDisplayInfo,
222                 base::Unretained(this)));
223  web_ui()->RegisterMessageCallback(
224      "setMirroring",
225      base::Bind(&DisplayOptionsHandler::HandleMirroring,
226                 base::Unretained(this)));
227  web_ui()->RegisterMessageCallback(
228      "setPrimary",
229      base::Bind(&DisplayOptionsHandler::HandleSetPrimary,
230                 base::Unretained(this)));
231  web_ui()->RegisterMessageCallback(
232      "setDisplayLayout",
233      base::Bind(&DisplayOptionsHandler::HandleDisplayLayout,
234                 base::Unretained(this)));
235  web_ui()->RegisterMessageCallback(
236      "setDisplayMode",
237      base::Bind(&DisplayOptionsHandler::HandleSetDisplayMode,
238                 base::Unretained(this)));
239  web_ui()->RegisterMessageCallback(
240      "setOrientation",
241      base::Bind(&DisplayOptionsHandler::HandleSetOrientation,
242                 base::Unretained(this)));
243  web_ui()->RegisterMessageCallback(
244      "setColorProfile",
245      base::Bind(&DisplayOptionsHandler::HandleSetColorProfile,
246                 base::Unretained(this)));
247}
248
249void DisplayOptionsHandler::OnDisplayConfigurationChanging() {
250}
251
252void DisplayOptionsHandler::OnDisplayConfigurationChanged() {
253  SendAllDisplayInfo();
254}
255
256void DisplayOptionsHandler::SendAllDisplayInfo() {
257  DisplayManager* display_manager = GetDisplayManager();
258
259  std::vector<gfx::Display> displays;
260  for (size_t i = 0; i < display_manager->GetNumDisplays(); ++i) {
261    displays.push_back(display_manager->GetDisplayAt(i));
262  }
263  SendDisplayInfo(displays);
264}
265
266void DisplayOptionsHandler::SendDisplayInfo(
267    const std::vector<gfx::Display>& displays) {
268  DisplayManager* display_manager = GetDisplayManager();
269  base::FundamentalValue mirroring(display_manager->IsMirrored());
270
271  int64 primary_id = ash::Shell::GetScreen()->GetPrimaryDisplay().id();
272  base::ListValue js_displays;
273  for (size_t i = 0; i < displays.size(); ++i) {
274    const gfx::Display& display = displays[i];
275    const ash::DisplayInfo& display_info =
276        display_manager->GetDisplayInfo(display.id());
277    const gfx::Rect& bounds = display.bounds();
278    base::DictionaryValue* js_display = new base::DictionaryValue();
279    js_display->SetString("id", base::Int64ToString(display.id()));
280    js_display->SetInteger("x", bounds.x());
281    js_display->SetInteger("y", bounds.y());
282    js_display->SetInteger("width", bounds.width());
283    js_display->SetInteger("height", bounds.height());
284    js_display->SetString("name",
285                          display_manager->GetDisplayNameForId(display.id()));
286    js_display->SetBoolean("isPrimary", display.id() == primary_id);
287    js_display->SetBoolean("isInternal", display.IsInternal());
288    js_display->SetInteger("orientation",
289                           static_cast<int>(display_info.rotation()));
290
291    base::ListValue* js_resolutions = new base::ListValue();
292    const std::vector<ash::DisplayMode>& display_modes =
293        display_info.display_modes();
294    for (size_t i = 0; i < display_modes.size(); ++i) {
295      js_resolutions->Append(
296          ConvertDisplayModeToValue(display.id(), display_modes[i]));
297    }
298    js_display->Set("resolutions", js_resolutions);
299
300    js_display->SetInteger("colorProfile", display_info.color_profile());
301    base::ListValue* available_color_profiles = new base::ListValue();
302    for (size_t i = 0;
303         i < display_info.available_color_profiles().size(); ++i) {
304      base::DictionaryValue* color_profile_dict = new base::DictionaryValue();
305      ui::ColorCalibrationProfile color_profile =
306          display_info.available_color_profiles()[i];
307      color_profile_dict->SetInteger("profileId", color_profile);
308      color_profile_dict->SetString("name", GetColorProfileName(color_profile));
309      available_color_profiles->Append(color_profile_dict);
310    }
311    js_display->Set("availableColorProfiles", available_color_profiles);
312    js_displays.Append(js_display);
313  }
314
315  scoped_ptr<base::Value> layout_value(base::Value::CreateNullValue());
316  scoped_ptr<base::Value> offset_value(base::Value::CreateNullValue());
317  if (display_manager->GetNumDisplays() > 1) {
318    const ash::DisplayLayout layout =
319        display_manager->GetCurrentDisplayLayout();
320    layout_value.reset(new base::FundamentalValue(layout.position));
321    offset_value.reset(new base::FundamentalValue(layout.offset));
322  }
323
324  web_ui()->CallJavascriptFunction(
325      "options.DisplayOptions.setDisplayInfo",
326      mirroring, js_displays, *layout_value.get(), *offset_value.get());
327}
328
329void DisplayOptionsHandler::OnFadeOutForMirroringFinished(bool is_mirroring) {
330  ash::Shell::GetInstance()->display_manager()->SetMirrorMode(is_mirroring);
331  // Not necessary to start fade-in animation. DisplayConfigurator will do that.
332}
333
334void DisplayOptionsHandler::OnFadeOutForDisplayLayoutFinished(
335    int position, int offset) {
336  SetCurrentDisplayLayout(
337      ash::DisplayLayout::FromInts(position, offset));
338  ash::Shell::GetInstance()->display_configurator_animation()->
339      StartFadeInAnimation();
340}
341
342void DisplayOptionsHandler::HandleDisplayInfo(
343    const base::ListValue* unused_args) {
344  SendAllDisplayInfo();
345}
346
347void DisplayOptionsHandler::HandleMirroring(const base::ListValue* args) {
348  DCHECK(!args->empty());
349  content::RecordAction(
350      base::UserMetricsAction("Options_DisplayToggleMirroring"));
351  bool is_mirroring = false;
352  args->GetBoolean(0, &is_mirroring);
353  ash::Shell::GetInstance()->display_configurator_animation()->
354      StartFadeOutAnimation(base::Bind(
355          &DisplayOptionsHandler::OnFadeOutForMirroringFinished,
356          base::Unretained(this),
357          is_mirroring));
358}
359
360void DisplayOptionsHandler::HandleSetPrimary(const base::ListValue* args) {
361  DCHECK(!args->empty());
362  int64 display_id = GetDisplayId(args);
363  if (display_id == gfx::Display::kInvalidDisplayID)
364    return;
365
366  content::RecordAction(base::UserMetricsAction("Options_DisplaySetPrimary"));
367  ash::Shell::GetInstance()->display_controller()->
368      SetPrimaryDisplayId(display_id);
369}
370
371void DisplayOptionsHandler::HandleDisplayLayout(const base::ListValue* args) {
372  double layout = -1;
373  double offset = -1;
374  if (!args->GetDouble(0, &layout) || !args->GetDouble(1, &offset)) {
375    LOG(ERROR) << "Invalid parameter";
376    SendAllDisplayInfo();
377    return;
378  }
379  DCHECK_LE(ash::DisplayLayout::TOP, layout);
380  DCHECK_GE(ash::DisplayLayout::LEFT, layout);
381  content::RecordAction(base::UserMetricsAction("Options_DisplayRearrange"));
382  ash::Shell::GetInstance()->display_configurator_animation()->
383      StartFadeOutAnimation(base::Bind(
384          &DisplayOptionsHandler::OnFadeOutForDisplayLayoutFinished,
385          base::Unretained(this),
386          static_cast<int>(layout),
387          static_cast<int>(offset)));
388}
389
390void DisplayOptionsHandler::HandleSetDisplayMode(const base::ListValue* args) {
391  DCHECK(!args->empty());
392
393  int64 display_id = GetDisplayId(args);
394  if (display_id == gfx::Display::kInvalidDisplayID)
395    return;
396
397  const base::DictionaryValue* mode_data = NULL;
398  if (!args->GetDictionary(1, &mode_data)) {
399    LOG(ERROR) << "Failed to get mode data";
400    return;
401  }
402
403  ash::DisplayMode mode;
404  if (!ConvertValueToDisplayMode(mode_data, &mode))
405    return;
406
407  content::RecordAction(
408      base::UserMetricsAction("Options_DisplaySetResolution"));
409  ash::DisplayManager* display_manager = GetDisplayManager();
410  ash::DisplayMode current_mode =
411      display_manager->GetActiveModeForDisplayId(display_id);
412  if (display_manager->SetDisplayMode(display_id, mode)) {
413    ash::Shell::GetInstance()->resolution_notification_controller()->
414        PrepareNotification(
415            display_id, current_mode, mode, base::Bind(&StoreDisplayPrefs));
416  }
417}
418
419void DisplayOptionsHandler::HandleSetOrientation(const base::ListValue* args) {
420  DCHECK(!args->empty());
421
422  int64 display_id = GetDisplayId(args);
423  if (display_id == gfx::Display::kInvalidDisplayID)
424    return;
425
426  std::string rotation_value;
427  gfx::Display::Rotation new_rotation = gfx::Display::ROTATE_0;
428  if (!args->GetString(1, &rotation_value)) {
429    LOG(ERROR) << "Can't find new orientation";
430    return;
431  }
432  if (rotation_value == "90")
433    new_rotation = gfx::Display::ROTATE_90;
434  else if (rotation_value == "180")
435    new_rotation = gfx::Display::ROTATE_180;
436  else if (rotation_value == "270")
437    new_rotation = gfx::Display::ROTATE_270;
438  else if (rotation_value != "0")
439    LOG(ERROR) << "Invalid rotation: " << rotation_value << " Falls back to 0";
440
441  content::RecordAction(
442      base::UserMetricsAction("Options_DisplaySetOrientation"));
443  GetDisplayManager()->SetDisplayRotation(display_id, new_rotation);
444}
445
446void DisplayOptionsHandler::HandleSetColorProfile(const base::ListValue* args) {
447  DCHECK(!args->empty());
448  int64 display_id = GetDisplayId(args);
449  if (display_id == gfx::Display::kInvalidDisplayID)
450    return;
451
452  std::string profile_value;
453  if (!args->GetString(1, &profile_value)) {
454    LOG(ERROR) << "Invalid profile_value";
455    return;
456  }
457
458  int profile_id;
459  if (!base::StringToInt(profile_value, &profile_id)) {
460    LOG(ERROR) << "Invalid profile: " << profile_value;
461    return;
462  }
463
464  if (profile_id < ui::COLOR_PROFILE_STANDARD ||
465      profile_id > ui::COLOR_PROFILE_READING) {
466    LOG(ERROR) << "Invalid profile_id: " << profile_id;
467    return;
468  }
469
470  GetDisplayManager()->SetColorCalibrationProfile(
471      display_id, static_cast<ui::ColorCalibrationProfile>(profile_id));
472  SendAllDisplayInfo();
473}
474
475}  // namespace options
476}  // namespace chromeos
477