text: // Copyright (c) 2012 The Chromium Authors. All rights reserved.
  // Use of this source code is governed by a BSD-style license that can be
  // found in the LICENSE file.
  
  #include "chrome/browser/chromeos/display/display_preferences.h"
  
  #include "ash/display/display_layout_store.h"
  #include "ash/display/display_manager.h"
  #include "ash/display/display_pref_util.h"
  #include "ash/display/resolution_notification_controller.h"
  #include "ash/shell.h"
  #include "base/prefs/pref_registry_simple.h"
  #include "base/prefs/pref_service.h"
  #include "base/prefs/scoped_user_pref_update.h"
  #include "base/strings/string16.h"
  #include "base/strings/string_number_conversions.h"
  #include "base/strings/string_split.h"
  #include "base/strings/string_util.h"
  #include "base/values.h"
  #include "chrome/browser/browser_process.h"
  #include "chrome/browser/chromeos/login/user_manager.h"
  #include "chrome/common/pref_names.h"
  #include "chromeos/display/output_configurator.h"
  #include "third_party/cros_system_api/dbus/service_constants.h"
  #include "ui/gfx/display.h"
  #include "ui/gfx/insets.h"
  #include "ui/gfx/screen.h"
  #include "url/url_canon.h"
  #include "url/url_util.h"
  
  namespace chromeos {
  namespace {
  
  const char kInsetsTopKey[] = "insets_top";
  const char kInsetsLeftKey[] = "insets_left";
  const char kInsetsBottomKey[] = "insets_bottom";
  const char kInsetsRightKey[] = "insets_right";
  
  // This kind of boilerplates should be done by base::JSONValueConverter but it
  // doesn't support classes like gfx::Insets for now.
  // TODO(mukai): fix base::JSONValueConverter and use it here.
  bool ValueToInsets(const base::DictionaryValue& value, gfx::Insets* insets) {
    DCHECK(insets);
    int top = 0;
    int left = 0;
    int bottom = 0;
    int right = 0;
    if (value.GetInteger(kInsetsTopKey, &top) &&
        value.GetInteger(kInsetsLeftKey, &left) &&
        value.GetInteger(kInsetsBottomKey, &bottom) &&
        value.GetInteger(kInsetsRightKey, &right)) {
      insets->Set(top, left, bottom, right);
      return true;
    }
    return false;
  }
  
  void InsetsToValue(const gfx::Insets& insets, base::DictionaryValue* value) {
    DCHECK(value);
    value->SetInteger(kInsetsTopKey, insets.top());
    value->SetInteger(kInsetsLeftKey, insets.left());
    value->SetInteger(kInsetsBottomKey, insets.bottom());
    value->SetInteger(kInsetsRightKey, insets.right());
  }
  
  ash::internal::DisplayManager* GetDisplayManager() {
    return ash::Shell::GetInstance()->display_manager();
  }
  
  // Returns true id the current user can write display preferences to
  // Local State.
  bool UserCanSaveDisplayPreference() {
    UserManager* user_manager = UserManager::Get();
    return user_manager->IsUserLoggedIn() &&
        (user_manager->IsLoggedInAsRegularUser() ||
         user_manager->IsLoggedInAsLocallyManagedUser() ||
         user_manager->IsLoggedInAsKioskApp());
  }
  
  ash::DisplayController* GetDisplayController() {
    return ash::Shell::GetInstance()->display_controller();
  }
  
  void LoadDisplayLayouts() {
    PrefService* local_state = g_browser_process->local_state();
    ash::internal::DisplayLayoutStore* layout_store =
        GetDisplayManager()->layout_store();
  
    const base::DictionaryValue* layouts = local_state->GetDictionary(
        prefs::kSecondaryDisplays);
    for (DictionaryValue::Iterator it(*layouts); !it.IsAtEnd(); it.Advance()) {
      ash::DisplayLayout layout;
      if (!ash::DisplayLayout::ConvertFromValue(it.value(), &layout)) {
        LOG(WARNING) << "Invalid preference value for " << it.key();
        continue;
      }
  
      if (it.key().find(",") != std::string::npos) {
        std::vector<std::string> ids;
        base::SplitString(it.key(), ',', &ids);
        int64 id1 = gfx::Display::kInvalidDisplayID;
        int64 id2 = gfx::Display::kInvalidDisplayID;
        if (!base::StringToInt64(ids[0], &id1) ||
            !base::StringToInt64(ids[1], &id2) ||
            id1 == gfx::Display::kInvalidDisplayID ||
            id2 == gfx::Display::kInvalidDisplayID) {
          continue;
        }
        layout_store->RegisterLayoutForDisplayIdPair(id1, id2, layout);
      }
    }
  }
  
  void LoadDisplayProperties() {
    PrefService* local_state = g_browser_process->local_state();
    const base::DictionaryValue* properties = local_state->GetDictionary(
        prefs::kDisplayProperties);
    for (DictionaryValue::Iterator it(*properties); !it.IsAtEnd(); it.Advance()) {
      const base::DictionaryValue* dict_value = NULL;
      if (!it.value().GetAsDictionary(&dict_value) || dict_value == NULL)
        continue;
      int64 id = gfx::Display::kInvalidDisplayID;
      if (!base::StringToInt64(it.key(), &id) ||
          id == gfx::Display::kInvalidDisplayID) {
        continue;
      }
      gfx::Display::Rotation rotation = gfx::Display::ROTATE_0;
      float ui_scale = 1.0f;
      const gfx::Insets* insets_to_set = NULL;
  
      int rotation_value = 0;
      if (dict_value->GetInteger("rotation", &rotation_value)) {
        rotation = static_cast<gfx::Display::Rotation>(rotation_value);
      }
      int ui_scale_value = 0;
      if (dict_value->GetInteger("ui-scale", &ui_scale_value))
        ui_scale = static_cast<float>(ui_scale_value) / 1000.0f;
  
      int width = 0, height = 0;
      dict_value->GetInteger("width", &width);
      dict_value->GetInteger("height", &height);
      gfx::Size resolution_in_pixels(width, height);
  
      gfx::Insets insets;
      if (ValueToInsets(*dict_value, &insets))
        insets_to_set = &insets;
      GetDisplayManager()->RegisterDisplayProperty(id,
                                                   rotation,
                                                   ui_scale,
                                                   insets_to_set,
                                                   resolution_in_pixels);
    }
  }
  
  void StoreDisplayLayoutPref(const ash::DisplayIdPair& pair,
                              const ash::DisplayLayout& display_layout) {
    std::string name =
        base::Int64ToString(pair.first) + "," + base::Int64ToString(pair.second);
  
    PrefService* local_state = g_browser_process->local_state();
    DictionaryPrefUpdate update(local_state, prefs::kSecondaryDisplays);
    base::DictionaryValue* pref_data = update.Get();
    scoped_ptr<base::Value> layout_value(new base::DictionaryValue());
    if (pref_data->HasKey(name)) {
      base::Value* value = NULL;
      if (pref_data->Get(name, &value) && value != NULL)
        layout_value.reset(value->DeepCopy());
    }
    if (ash::DisplayLayout::ConvertToValue(display_layout, layout_value.get()))
      pref_data->Set(name, layout_value.release());
  }
  
  void StoreCurrentDisplayLayoutPrefs() {
    if (!UserCanSaveDisplayPreference() ||
        GetDisplayManager()->num_connected_displays() < 2) {
      return;
    }
  
    ash::DisplayIdPair pair = GetDisplayManager()->GetCurrentDisplayIdPair();
    ash::DisplayLayout display_layout =
        GetDisplayManager()->layout_store()->GetRegisteredDisplayLayout(pair);
    StoreDisplayLayoutPref(pair, display_layout);
  }
  
  void StoreCurrentDisplayProperties() {
    ash::internal::DisplayManager* display_manager = GetDisplayManager();
    PrefService* local_state = g_browser_process->local_state();
  
    DictionaryPrefUpdate update(local_state, prefs::kDisplayProperties);
    base::DictionaryValue* pref_data = update.Get();
  
    size_t num = display_manager->GetNumDisplays();
    for (size_t i = 0; i < num; ++i) {
      const gfx::Display& display = display_manager->GetDisplayAt(i);
      int64 id = display.id();
      ash::internal::DisplayInfo info = display_manager->GetDisplayInfo(id);
  
      scoped_ptr<base::DictionaryValue> property_value(
          new base::DictionaryValue());
      property_value->SetInteger("rotation", static_cast<int>(info.rotation()));
      property_value->SetInteger("ui-scale",
                                 static_cast<int>(info.ui_scale() * 1000));
      gfx::Size resolution;
      if (!display.IsInternal() &&
          display_manager->GetSelectedResolutionForDisplayId(id, &resolution)) {
        property_value->SetInteger("width", resolution.width());
        property_value->SetInteger("height", resolution.height());
      }
  
      if (!info.overscan_insets_in_dip().empty())
        InsetsToValue(info.overscan_insets_in_dip(), property_value.get());
      pref_data->Set(base::Int64ToString(id), property_value.release());
    }
  }
  
  typedef std::map<chromeos::DisplayPowerState, std::string>
      DisplayPowerStateToStringMap;
  
  const DisplayPowerStateToStringMap* GetDisplayPowerStateToStringMap() {
    static const DisplayPowerStateToStringMap* map = ash::CreateToStringMap(
        chromeos::DISPLAY_POWER_ALL_ON, "all_on",
        chromeos::DISPLAY_POWER_ALL_OFF, "all_off",
        chromeos::DISPLAY_POWER_INTERNAL_OFF_EXTERNAL_ON,
        "internal_off_external_on",
        chromeos::DISPLAY_POWER_INTERNAL_ON_EXTERNAL_OFF,
        "internal_on_external_off");
    return map;
  }
  
  bool GetDisplayPowerStateFromString(const base::StringPiece& state,
                                      chromeos::DisplayPowerState* field) {
    if (ash::ReverseFind(GetDisplayPowerStateToStringMap(), state, field))
      return true;
    LOG(ERROR) << "Invalid display power state value:" << state;
    return false;
  }
  
  void StoreDisplayPowerState(DisplayPowerState power_state) {
    const DisplayPowerStateToStringMap* map = GetDisplayPowerStateToStringMap();
    DisplayPowerStateToStringMap::const_iterator iter = map->find(power_state);
    std::string value = iter != map->end() ? iter->second : std::string();
    PrefService* local_state = g_browser_process->local_state();
    local_state->SetString(prefs::kDisplayPowerState, value);
  }
  
  void StoreCurrentDisplayPowerState() {
    StoreDisplayPowerState(
        ash::Shell::GetInstance()->output_configurator()->power_state());
  }
  
  }  // namespace
  
  void RegisterDisplayLocalStatePrefs(PrefRegistrySimple* registry) {
    // Per-display preference.
    registry->RegisterDictionaryPref(prefs::kSecondaryDisplays);
    registry->RegisterDictionaryPref(prefs::kDisplayProperties);
    DisplayPowerStateToStringMap::const_iterator iter =
        GetDisplayPowerStateToStringMap()->find(chromeos::DISPLAY_POWER_ALL_ON);
    registry->RegisterStringPref(prefs::kDisplayPowerState, iter->second);
  }
  
  void StoreDisplayPrefs() {
    // Stores the power state regardless of the login status, because the power
    // state respects to the current status (close/open) of the lid which can be
    // changed in any situation. See crbug.com/285360
    StoreCurrentDisplayPowerState();
  
    // Do not store prefs when the confirmation dialog is shown.
    if (!UserCanSaveDisplayPreference() ||
        ash::Shell::GetInstance()->resolution_notification_controller()->
            DoesNotificationTimeout()) {
      return;
    }
    StoreCurrentDisplayLayoutPrefs();
    StoreCurrentDisplayProperties();
  }
  
  void SetCurrentDisplayLayout(const ash::DisplayLayout& layout) {
    GetDisplayManager()->SetLayoutForCurrentDisplays(layout);
  }
  
  void LoadDisplayPreferences(bool first_run_after_boot) {
    LoadDisplayLayouts();
    LoadDisplayProperties();
    if (!first_run_after_boot) {
      PrefService* local_state = g_browser_process->local_state();
      // Restore DisplayPowerState:
      std::string value = local_state->GetString(prefs::kDisplayPowerState);
      chromeos::DisplayPowerState power_state;
      if (GetDisplayPowerStateFromString(value, &power_state)) {
        ash::Shell::GetInstance()->output_configurator()->SetInitialDisplayPower(
            power_state);
      }
    }
  }
  
  // Stores the display layout for given display pairs.
  void StoreDisplayLayoutPrefForTest(int64 id1,
                                     int64 id2,
                                     const ash::DisplayLayout& layout) {
    StoreDisplayLayoutPref(std::make_pair(id1, id2), layout);
  }
  
  // Stores the given |power_state|.
  void StoreDisplayPowerStateForTest(DisplayPowerState power_state) {
    StoreDisplayPowerState(power_state);
  }
  
  }  // namespace chromeos
  