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/chromeos/display/display_preferences.h"
6
7#include "ash/display/display_layout_store.h"
8#include "ash/display/display_manager.h"
9#include "ash/display/display_pref_util.h"
10#include "ash/shell.h"
11#include "base/prefs/pref_registry_simple.h"
12#include "base/prefs/pref_service.h"
13#include "base/prefs/scoped_user_pref_update.h"
14#include "base/strings/string16.h"
15#include "base/strings/string_number_conversions.h"
16#include "base/strings/string_split.h"
17#include "base/strings/string_util.h"
18#include "base/values.h"
19#include "chrome/browser/browser_process.h"
20#include "chrome/common/pref_names.h"
21#include "components/user_manager/user_manager.h"
22#include "third_party/cros_system_api/dbus/service_constants.h"
23#include "ui/gfx/display.h"
24#include "ui/gfx/insets.h"
25#include "ui/gfx/screen.h"
26#include "url/url_canon.h"
27#include "url/url_util.h"
28
29namespace chromeos {
30namespace {
31
32const char kInsetsTopKey[] = "insets_top";
33const char kInsetsLeftKey[] = "insets_left";
34const char kInsetsBottomKey[] = "insets_bottom";
35const char kInsetsRightKey[] = "insets_right";
36
37// This kind of boilerplates should be done by base::JSONValueConverter but it
38// doesn't support classes like gfx::Insets for now.
39// TODO(mukai): fix base::JSONValueConverter and use it here.
40bool ValueToInsets(const base::DictionaryValue& value, gfx::Insets* insets) {
41  DCHECK(insets);
42  int top = 0;
43  int left = 0;
44  int bottom = 0;
45  int right = 0;
46  if (value.GetInteger(kInsetsTopKey, &top) &&
47      value.GetInteger(kInsetsLeftKey, &left) &&
48      value.GetInteger(kInsetsBottomKey, &bottom) &&
49      value.GetInteger(kInsetsRightKey, &right)) {
50    insets->Set(top, left, bottom, right);
51    return true;
52  }
53  return false;
54}
55
56void InsetsToValue(const gfx::Insets& insets, base::DictionaryValue* value) {
57  DCHECK(value);
58  value->SetInteger(kInsetsTopKey, insets.top());
59  value->SetInteger(kInsetsLeftKey, insets.left());
60  value->SetInteger(kInsetsBottomKey, insets.bottom());
61  value->SetInteger(kInsetsRightKey, insets.right());
62}
63
64std::string ColorProfileToString(ui::ColorCalibrationProfile profile) {
65  switch (profile) {
66    case ui::COLOR_PROFILE_STANDARD:
67      return "standard";
68    case ui::COLOR_PROFILE_DYNAMIC:
69      return "dynamic";
70    case ui::COLOR_PROFILE_MOVIE:
71      return "movie";
72    case ui::COLOR_PROFILE_READING:
73      return "reading";
74    case ui::NUM_COLOR_PROFILES:
75      break;
76  }
77  NOTREACHED();
78  return "";
79}
80
81ui::ColorCalibrationProfile StringToColorProfile(std::string value) {
82  if (value == "standard")
83    return ui::COLOR_PROFILE_STANDARD;
84  else if (value == "dynamic")
85    return ui::COLOR_PROFILE_DYNAMIC;
86  else if (value == "movie")
87    return ui::COLOR_PROFILE_MOVIE;
88  else if (value == "reading")
89    return ui::COLOR_PROFILE_READING;
90  NOTREACHED();
91  return ui::COLOR_PROFILE_STANDARD;
92}
93
94ash::DisplayManager* GetDisplayManager() {
95  return ash::Shell::GetInstance()->display_manager();
96}
97
98// Returns true id the current user can write display preferences to
99// Local State.
100bool UserCanSaveDisplayPreference() {
101  user_manager::UserManager* user_manager = user_manager::UserManager::Get();
102  return user_manager->IsUserLoggedIn() &&
103      (user_manager->IsLoggedInAsRegularUser() ||
104       user_manager->IsLoggedInAsSupervisedUser() ||
105       user_manager->IsLoggedInAsKioskApp());
106}
107
108void LoadDisplayLayouts() {
109  PrefService* local_state = g_browser_process->local_state();
110  ash::DisplayLayoutStore* layout_store = GetDisplayManager()->layout_store();
111
112  const base::DictionaryValue* layouts = local_state->GetDictionary(
113      prefs::kSecondaryDisplays);
114  for (base::DictionaryValue::Iterator it(*layouts);
115       !it.IsAtEnd(); it.Advance()) {
116    ash::DisplayLayout layout;
117    if (!ash::DisplayLayout::ConvertFromValue(it.value(), &layout)) {
118      LOG(WARNING) << "Invalid preference value for " << it.key();
119      continue;
120    }
121
122    if (it.key().find(",") != std::string::npos) {
123      std::vector<std::string> ids;
124      base::SplitString(it.key(), ',', &ids);
125      int64 id1 = gfx::Display::kInvalidDisplayID;
126      int64 id2 = gfx::Display::kInvalidDisplayID;
127      if (!base::StringToInt64(ids[0], &id1) ||
128          !base::StringToInt64(ids[1], &id2) ||
129          id1 == gfx::Display::kInvalidDisplayID ||
130          id2 == gfx::Display::kInvalidDisplayID) {
131        continue;
132      }
133      layout_store->RegisterLayoutForDisplayIdPair(id1, id2, layout);
134    }
135  }
136}
137
138void LoadDisplayProperties() {
139  PrefService* local_state = g_browser_process->local_state();
140  const base::DictionaryValue* properties = local_state->GetDictionary(
141      prefs::kDisplayProperties);
142  for (base::DictionaryValue::Iterator it(*properties);
143       !it.IsAtEnd(); it.Advance()) {
144    const base::DictionaryValue* dict_value = NULL;
145    if (!it.value().GetAsDictionary(&dict_value) || dict_value == NULL)
146      continue;
147    int64 id = gfx::Display::kInvalidDisplayID;
148    if (!base::StringToInt64(it.key(), &id) ||
149        id == gfx::Display::kInvalidDisplayID) {
150      continue;
151    }
152    gfx::Display::Rotation rotation = gfx::Display::ROTATE_0;
153    float ui_scale = 1.0f;
154    const gfx::Insets* insets_to_set = NULL;
155
156    int rotation_value = 0;
157    if (dict_value->GetInteger("rotation", &rotation_value)) {
158      rotation = static_cast<gfx::Display::Rotation>(rotation_value);
159    }
160    int ui_scale_value = 0;
161    if (dict_value->GetInteger("ui-scale", &ui_scale_value))
162      ui_scale = static_cast<float>(ui_scale_value) / 1000.0f;
163
164    int width = 0, height = 0;
165    dict_value->GetInteger("width", &width);
166    dict_value->GetInteger("height", &height);
167    gfx::Size resolution_in_pixels(width, height);
168
169    float device_scale_factor = 1.0;
170    int dsf_value = 0;
171    if (dict_value->GetInteger("device-scale-factor", &dsf_value))
172      device_scale_factor = static_cast<float>(dsf_value) / 1000.0f;
173
174    gfx::Insets insets;
175    if (ValueToInsets(*dict_value, &insets))
176      insets_to_set = &insets;
177
178    ui::ColorCalibrationProfile color_profile = ui::COLOR_PROFILE_STANDARD;
179    std::string color_profile_name;
180    if (dict_value->GetString("color_profile_name", &color_profile_name))
181      color_profile = StringToColorProfile(color_profile_name);
182    GetDisplayManager()->RegisterDisplayProperty(id,
183                                                 rotation,
184                                                 ui_scale,
185                                                 insets_to_set,
186                                                 resolution_in_pixels,
187                                                 device_scale_factor,
188                                                 color_profile);
189  }
190}
191
192void LoadDisplayRotationState() {
193  PrefService* local_state = g_browser_process->local_state();
194  const base::DictionaryValue* properties =
195      local_state->GetDictionary(prefs::kDisplayRotationLock);
196
197  bool rotation_lock = false;
198  if (!properties->GetBoolean("lock", &rotation_lock))
199    return;
200
201  int rotation = gfx::Display::ROTATE_0;
202  if (!properties->GetInteger("orientation", &rotation))
203    return;
204
205  GetDisplayManager()->RegisterDisplayRotationProperties(rotation_lock,
206      static_cast<gfx::Display::Rotation>(rotation));
207}
208
209void StoreDisplayLayoutPref(const ash::DisplayIdPair& pair,
210                            const ash::DisplayLayout& display_layout) {
211  std::string name =
212      base::Int64ToString(pair.first) + "," + base::Int64ToString(pair.second);
213
214  PrefService* local_state = g_browser_process->local_state();
215  DictionaryPrefUpdate update(local_state, prefs::kSecondaryDisplays);
216  base::DictionaryValue* pref_data = update.Get();
217  scoped_ptr<base::Value> layout_value(new base::DictionaryValue());
218  if (pref_data->HasKey(name)) {
219    base::Value* value = NULL;
220    if (pref_data->Get(name, &value) && value != NULL)
221      layout_value.reset(value->DeepCopy());
222  }
223  if (ash::DisplayLayout::ConvertToValue(display_layout, layout_value.get()))
224    pref_data->Set(name, layout_value.release());
225}
226
227void StoreCurrentDisplayLayoutPrefs() {
228  if (!UserCanSaveDisplayPreference() ||
229      GetDisplayManager()->num_connected_displays() < 2) {
230    return;
231  }
232
233  ash::DisplayIdPair pair = GetDisplayManager()->GetCurrentDisplayIdPair();
234  ash::DisplayLayout display_layout =
235      GetDisplayManager()->layout_store()->GetRegisteredDisplayLayout(pair);
236  StoreDisplayLayoutPref(pair, display_layout);
237}
238
239void StoreCurrentDisplayProperties() {
240  ash::DisplayManager* display_manager = GetDisplayManager();
241  PrefService* local_state = g_browser_process->local_state();
242
243  DictionaryPrefUpdate update(local_state, prefs::kDisplayProperties);
244  base::DictionaryValue* pref_data = update.Get();
245
246  size_t num = display_manager->GetNumDisplays();
247  for (size_t i = 0; i < num; ++i) {
248    const gfx::Display& display = display_manager->GetDisplayAt(i);
249    int64 id = display.id();
250    ash::DisplayInfo info = display_manager->GetDisplayInfo(id);
251
252    scoped_ptr<base::DictionaryValue> property_value(
253        new base::DictionaryValue());
254    property_value->SetInteger("rotation", static_cast<int>(info.rotation()));
255    property_value->SetInteger(
256        "ui-scale",
257        static_cast<int>(info.configured_ui_scale() * 1000));
258    ash::DisplayMode mode;
259    if (!display.IsInternal() &&
260        display_manager->GetSelectedModeForDisplayId(id, &mode) &&
261        !mode.native) {
262      property_value->SetInteger("width", mode.size.width());
263      property_value->SetInteger("height", mode.size.height());
264      property_value->SetInteger(
265          "device-scale-factor",
266          static_cast<int>(mode.device_scale_factor * 1000));
267    }
268    if (!info.overscan_insets_in_dip().empty())
269      InsetsToValue(info.overscan_insets_in_dip(), property_value.get());
270    if (info.color_profile() != ui::COLOR_PROFILE_STANDARD) {
271      property_value->SetString(
272          "color_profile_name", ColorProfileToString(info.color_profile()));
273    }
274    pref_data->Set(base::Int64ToString(id), property_value.release());
275  }
276}
277
278typedef std::map<chromeos::DisplayPowerState, std::string>
279    DisplayPowerStateToStringMap;
280
281const DisplayPowerStateToStringMap* GetDisplayPowerStateToStringMap() {
282  // Don't save or retore ALL_OFF state. crbug.com/318456.
283  static const DisplayPowerStateToStringMap* map = ash::CreateToStringMap(
284      chromeos::DISPLAY_POWER_ALL_ON, "all_on",
285      chromeos::DISPLAY_POWER_INTERNAL_OFF_EXTERNAL_ON,
286      "internal_off_external_on",
287      chromeos::DISPLAY_POWER_INTERNAL_ON_EXTERNAL_OFF,
288      "internal_on_external_off");
289  return map;
290}
291
292bool GetDisplayPowerStateFromString(const base::StringPiece& state,
293                                    chromeos::DisplayPowerState* field) {
294  if (ash::ReverseFind(GetDisplayPowerStateToStringMap(), state, field))
295    return true;
296  LOG(ERROR) << "Invalid display power state value:" << state;
297  return false;
298}
299
300void StoreDisplayPowerState(DisplayPowerState power_state) {
301  const DisplayPowerStateToStringMap* map = GetDisplayPowerStateToStringMap();
302  DisplayPowerStateToStringMap::const_iterator iter = map->find(power_state);
303  if (iter != map->end()) {
304    PrefService* local_state = g_browser_process->local_state();
305    local_state->SetString(prefs::kDisplayPowerState, iter->second);
306  }
307}
308
309void StoreCurrentDisplayPowerState() {
310  StoreDisplayPowerState(
311      ash::Shell::GetInstance()->display_configurator()->
312          requested_power_state());
313}
314
315void StoreCurrentDisplayRotationLockPrefs() {
316  bool rotation_lock = ash::Shell::GetInstance()->display_manager()->
317      registered_internal_display_rotation_lock();
318  StoreDisplayRotationPrefs(rotation_lock);
319}
320
321}  // namespace
322
323void RegisterDisplayLocalStatePrefs(PrefRegistrySimple* registry) {
324  // Per-display preference.
325  registry->RegisterDictionaryPref(prefs::kSecondaryDisplays);
326  registry->RegisterDictionaryPref(prefs::kDisplayProperties);
327  DisplayPowerStateToStringMap::const_iterator iter =
328      GetDisplayPowerStateToStringMap()->find(chromeos::DISPLAY_POWER_ALL_ON);
329  registry->RegisterStringPref(prefs::kDisplayPowerState, iter->second);
330  registry->RegisterDictionaryPref(prefs::kDisplayRotationLock);
331}
332
333void StoreDisplayPrefs() {
334  // Stores the power state regardless of the login status, because the power
335  // state respects to the current status (close/open) of the lid which can be
336  // changed in any situation. See crbug.com/285360
337  StoreCurrentDisplayPowerState();
338  StoreCurrentDisplayRotationLockPrefs();
339
340  // Do not store prefs when the confirmation dialog is shown.
341  if (!UserCanSaveDisplayPreference() ||
342      !ash::Shell::GetInstance()->ShouldSaveDisplaySettings()) {
343    return;
344  }
345
346  StoreCurrentDisplayLayoutPrefs();
347  StoreCurrentDisplayProperties();
348}
349
350void StoreDisplayRotationPrefs(bool rotation_lock) {
351  ash::DisplayManager* display_manager = GetDisplayManager();
352  if (!display_manager->HasInternalDisplay())
353    return;
354
355  PrefService* local_state = g_browser_process->local_state();
356  DictionaryPrefUpdate update(local_state, prefs::kDisplayRotationLock);
357  base::DictionaryValue* pref_data = update.Get();
358  pref_data->SetBoolean("lock", rotation_lock);
359  gfx::Display::Rotation rotation = display_manager->
360      GetDisplayInfo(gfx::Display::InternalDisplayId()).rotation();
361  pref_data->SetInteger("orientation", static_cast<int>(rotation));
362}
363
364void SetCurrentDisplayLayout(const ash::DisplayLayout& layout) {
365  GetDisplayManager()->SetLayoutForCurrentDisplays(layout);
366}
367
368void LoadDisplayPreferences(bool first_run_after_boot) {
369  LoadDisplayLayouts();
370  LoadDisplayProperties();
371  LoadDisplayRotationState();
372  if (!first_run_after_boot) {
373    PrefService* local_state = g_browser_process->local_state();
374    // Restore DisplayPowerState:
375    std::string value = local_state->GetString(prefs::kDisplayPowerState);
376    chromeos::DisplayPowerState power_state;
377    if (GetDisplayPowerStateFromString(value, &power_state)) {
378      ash::Shell::GetInstance()->display_configurator()->SetInitialDisplayPower(
379          power_state);
380    }
381  }
382}
383
384// Stores the display layout for given display pairs.
385void StoreDisplayLayoutPrefForTest(int64 id1,
386                                   int64 id2,
387                                   const ash::DisplayLayout& layout) {
388  StoreDisplayLayoutPref(std::make_pair(id1, id2), layout);
389}
390
391// Stores the given |power_state|.
392void StoreDisplayPowerStateForTest(DisplayPowerState power_state) {
393  StoreDisplayPowerState(power_state);
394}
395
396}  // namespace chromeos
397