普通文本  |  976行  |  34.6 KB

// Copyright 2014 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 "ui/display/chromeos/display_configurator.h"

#include "base/bind.h"
#include "base/command_line.h"
#include "base/logging.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/stringprintf.h"
#include "base/sys_info.h"
#include "base/time/time.h"
#include "ui/display/display_switches.h"
#include "ui/display/types/display_mode.h"
#include "ui/display/types/display_snapshot.h"
#include "ui/display/types/native_display_delegate.h"

namespace ui {

namespace {

typedef std::vector<const DisplayMode*> DisplayModeList;

// The delay to perform configuration after RRNotify. See the comment for
// |configure_timer_|.
const int kConfigureDelayMs = 500;

// The delay spent before reading the display configuration after coming out of
// suspend. While coming out of suspend the display state may be updating. This
// is used to wait until the hardware had a chance to update the display state
// such that we read an up to date state.
const int kResumeDelayMs = 500;

// Returns a string describing |state|.
std::string DisplayPowerStateToString(chromeos::DisplayPowerState state) {
  switch (state) {
    case chromeos::DISPLAY_POWER_ALL_ON:
      return "ALL_ON";
    case chromeos::DISPLAY_POWER_ALL_OFF:
      return "ALL_OFF";
    case chromeos::DISPLAY_POWER_INTERNAL_OFF_EXTERNAL_ON:
      return "INTERNAL_OFF_EXTERNAL_ON";
    case chromeos::DISPLAY_POWER_INTERNAL_ON_EXTERNAL_OFF:
      return "INTERNAL_ON_EXTERNAL_OFF";
    default:
      return "unknown (" + base::IntToString(state) + ")";
  }
}

// Returns a string describing |state|.
std::string DisplayStateToString(MultipleDisplayState state) {
  switch (state) {
    case MULTIPLE_DISPLAY_STATE_INVALID:
      return "INVALID";
    case MULTIPLE_DISPLAY_STATE_HEADLESS:
      return "HEADLESS";
    case MULTIPLE_DISPLAY_STATE_SINGLE:
      return "SINGLE";
    case MULTIPLE_DISPLAY_STATE_DUAL_MIRROR:
      return "DUAL_MIRROR";
    case MULTIPLE_DISPLAY_STATE_DUAL_EXTENDED:
      return "DUAL_EXTENDED";
  }
  NOTREACHED() << "Unknown state " << state;
  return "INVALID";
}

// Returns the number of displays in |displays| that should be turned on, per
// |state|.  If |display_power| is non-NULL, it is updated to contain the
// on/off state of each corresponding entry in |displays|.
int GetDisplayPower(
    const std::vector<DisplayConfigurator::DisplayState>& display_states,
    chromeos::DisplayPowerState state,
    std::vector<bool>* display_power) {
  int num_on_displays = 0;
  if (display_power)
    display_power->resize(display_states.size());

  for (size_t i = 0; i < display_states.size(); ++i) {
    bool internal =
        display_states[i].display->type() == DISPLAY_CONNECTION_TYPE_INTERNAL;
    bool on =
        state == chromeos::DISPLAY_POWER_ALL_ON ||
        (state == chromeos::DISPLAY_POWER_INTERNAL_OFF_EXTERNAL_ON &&
         !internal) ||
        (state == chromeos::DISPLAY_POWER_INTERNAL_ON_EXTERNAL_OFF && internal);
    if (display_power)
      (*display_power)[i] = on;
    if (on)
      num_on_displays++;
  }
  return num_on_displays;
}

}  // namespace


const int DisplayConfigurator::kSetDisplayPowerNoFlags = 0;
const int DisplayConfigurator::kSetDisplayPowerForceProbe = 1 << 0;
const int
DisplayConfigurator::kSetDisplayPowerOnlyIfSingleInternalDisplay = 1 << 1;

DisplayConfigurator::DisplayState::DisplayState()
    : display(NULL),
      selected_mode(NULL),
      mirror_mode(NULL) {}

bool DisplayConfigurator::TestApi::TriggerConfigureTimeout() {
  if (configurator_->configure_timer_.IsRunning()) {
    configurator_->configure_timer_.user_task().Run();
    configurator_->configure_timer_.Stop();
    return true;
  } else {
    return false;
  }
}

// static
const DisplayMode* DisplayConfigurator::FindDisplayModeMatchingSize(
    const DisplaySnapshot& display,
    const gfx::Size& size) {
  const DisplayMode* best_mode = NULL;
  for (DisplayModeList::const_iterator it = display.modes().begin();
       it != display.modes().end();
       ++it) {
    const DisplayMode* mode = *it;

    if (mode->size() != size)
      continue;

    if (!best_mode) {
      best_mode = mode;
      continue;
    }

    if (mode->is_interlaced()) {
      if (!best_mode->is_interlaced())
        continue;
    } else {
      // Reset the best rate if the non interlaced is
      // found the first time.
      if (best_mode->is_interlaced()) {
        best_mode = mode;
        continue;
      }
    }
    if (mode->refresh_rate() < best_mode->refresh_rate())
      continue;

    best_mode = mode;
  }

  return best_mode;
}

DisplayConfigurator::DisplayConfigurator()
    : state_controller_(NULL),
      mirroring_controller_(NULL),
      is_panel_fitting_enabled_(false),
      configure_display_(base::SysInfo::IsRunningOnChromeOS()),
      display_state_(MULTIPLE_DISPLAY_STATE_INVALID),
      requested_power_state_(chromeos::DISPLAY_POWER_ALL_ON),
      current_power_state_(chromeos::DISPLAY_POWER_ALL_ON),
      next_display_protection_client_id_(1) {}

DisplayConfigurator::~DisplayConfigurator() {
  if (native_display_delegate_)
    native_display_delegate_->RemoveObserver(this);
}

void DisplayConfigurator::SetDelegateForTesting(
    scoped_ptr<NativeDisplayDelegate> display_delegate) {
  DCHECK(!native_display_delegate_);

  native_display_delegate_ = display_delegate.Pass();
  configure_display_ = true;
}

void DisplayConfigurator::SetInitialDisplayPower(
    chromeos::DisplayPowerState power_state) {
  DCHECK_EQ(display_state_, MULTIPLE_DISPLAY_STATE_INVALID);
  requested_power_state_ = current_power_state_ = power_state;
}

void DisplayConfigurator::Init(bool is_panel_fitting_enabled) {
  is_panel_fitting_enabled_ = is_panel_fitting_enabled;
  if (!configure_display_)
    return;

  // If the delegate is already initialized don't update it (For example, tests
  // set their own delegates).
  if (!native_display_delegate_) {
    native_display_delegate_ = CreatePlatformNativeDisplayDelegate();
    native_display_delegate_->AddObserver(this);
  }
}

void DisplayConfigurator::ForceInitialConfigure(
    uint32_t background_color_argb) {
  if (!configure_display_)
    return;

  native_display_delegate_->GrabServer();
  native_display_delegate_->Initialize();

  UpdateCachedDisplays();
  if (cached_displays_.size() > 1 && background_color_argb)
    native_display_delegate_->SetBackgroundColor(background_color_argb);
  const MultipleDisplayState new_state = ChooseDisplayState(
      requested_power_state_);
  const bool success = EnterStateOrFallBackToSoftwareMirroring(
      new_state, requested_power_state_);

  // Force the DPMS on chrome startup as the driver doesn't always detect
  // that all displays are on when signing out.
  native_display_delegate_->ForceDPMSOn();
  native_display_delegate_->UngrabServer();
  NotifyObservers(success, new_state);
}

bool DisplayConfigurator::IsMirroring() const {
  return display_state_ == MULTIPLE_DISPLAY_STATE_DUAL_MIRROR ||
      (mirroring_controller_ &&
       mirroring_controller_->SoftwareMirroringEnabled());
}

bool DisplayConfigurator::ApplyProtections(const ContentProtections& requests) {
  for (DisplayStateList::const_iterator it = cached_displays_.begin();
       it != cached_displays_.end();
       ++it) {
    uint32_t all_desired = 0;

    // In mirror mode, protection request of all displays need to be fulfilled.
    // In non-mirror mode, only request of client's display needs to be
    // fulfilled.
    ContentProtections::const_iterator request_it;
    if (IsMirroring()) {
      for (request_it = requests.begin();
           request_it != requests.end();
           ++request_it)
        all_desired |= request_it->second;
    } else {
      request_it = requests.find(it->display->display_id());
      if (request_it != requests.end())
        all_desired = request_it->second;
    }

    switch (it->display->type()) {
      case DISPLAY_CONNECTION_TYPE_UNKNOWN:
        return false;
      // DisplayPort, DVI, and HDMI all support HDCP.
      case DISPLAY_CONNECTION_TYPE_DISPLAYPORT:
      case DISPLAY_CONNECTION_TYPE_DVI:
      case DISPLAY_CONNECTION_TYPE_HDMI: {
        HDCPState current_state;
        // Need to poll the driver for updates since other applications may
        // have updated the state.
        if (!native_display_delegate_->GetHDCPState(*it->display,
                                                    &current_state))
          return false;
        bool current_desired = (current_state != HDCP_STATE_UNDESIRED);
        bool new_desired = (all_desired & CONTENT_PROTECTION_METHOD_HDCP);
        // Don't enable again if HDCP is already active. Some buggy drivers
        // may disable and enable if setting "desired" in active state.
        if (current_desired != new_desired) {
          HDCPState new_state =
              new_desired ? HDCP_STATE_DESIRED : HDCP_STATE_UNDESIRED;
          if (!native_display_delegate_->SetHDCPState(*it->display, new_state))
            return false;
        }
        break;
      }
      case DISPLAY_CONNECTION_TYPE_INTERNAL:
      case DISPLAY_CONNECTION_TYPE_VGA:
      case DISPLAY_CONNECTION_TYPE_NETWORK:
        // No protections for these types. Do nothing.
        break;
      case DISPLAY_CONNECTION_TYPE_NONE:
        NOTREACHED();
        break;
    }
  }

  return true;
}

DisplayConfigurator::ContentProtectionClientId
DisplayConfigurator::RegisterContentProtectionClient() {
  if (!configure_display_)
    return kInvalidClientId;

  return next_display_protection_client_id_++;
}

void DisplayConfigurator::UnregisterContentProtectionClient(
    ContentProtectionClientId client_id) {
  client_protection_requests_.erase(client_id);

  ContentProtections protections;
  for (ProtectionRequests::const_iterator it =
           client_protection_requests_.begin();
       it != client_protection_requests_.end();
       ++it) {
    for (ContentProtections::const_iterator it2 = it->second.begin();
         it2 != it->second.end();
         ++it2) {
      protections[it2->first] |= it2->second;
    }
  }

  ApplyProtections(protections);
}

bool DisplayConfigurator::QueryContentProtectionStatus(
    ContentProtectionClientId client_id,
    int64_t display_id,
    uint32_t* link_mask,
    uint32_t* protection_mask) {
  if (!configure_display_)
    return false;

  uint32_t enabled = 0;
  uint32_t unfulfilled = 0;
  *link_mask = 0;
  for (DisplayStateList::const_iterator it = cached_displays_.begin();
       it != cached_displays_.end();
       ++it) {
    // Query display if it is in mirror mode or client on the same display.
    if (!IsMirroring() && it->display->display_id() != display_id)
      continue;

    *link_mask |= it->display->type();
    switch (it->display->type()) {
      case DISPLAY_CONNECTION_TYPE_UNKNOWN:
        return false;
      // DisplayPort, DVI, and HDMI all support HDCP.
      case DISPLAY_CONNECTION_TYPE_DISPLAYPORT:
      case DISPLAY_CONNECTION_TYPE_DVI:
      case DISPLAY_CONNECTION_TYPE_HDMI: {
        HDCPState state;
        if (!native_display_delegate_->GetHDCPState(*it->display, &state))
          return false;
        if (state == HDCP_STATE_ENABLED)
          enabled |= CONTENT_PROTECTION_METHOD_HDCP;
        else
          unfulfilled |= CONTENT_PROTECTION_METHOD_HDCP;
        break;
      }
      case DISPLAY_CONNECTION_TYPE_INTERNAL:
      case DISPLAY_CONNECTION_TYPE_VGA:
      case DISPLAY_CONNECTION_TYPE_NETWORK:
        // No protections for these types. Do nothing.
        break;
      case DISPLAY_CONNECTION_TYPE_NONE:
        NOTREACHED();
        break;
    }
  }

  // Don't reveal protections requested by other clients.
  ProtectionRequests::iterator it = client_protection_requests_.find(client_id);
  if (it != client_protection_requests_.end()) {
    uint32_t requested_mask = 0;
    if (it->second.find(display_id) != it->second.end())
      requested_mask = it->second[display_id];
    *protection_mask = enabled & ~unfulfilled & requested_mask;
  } else {
    *protection_mask = 0;
  }
  return true;
}

bool DisplayConfigurator::EnableContentProtection(
    ContentProtectionClientId client_id,
    int64_t display_id,
    uint32_t desired_method_mask) {
  if (!configure_display_)
    return false;

  ContentProtections protections;
  for (ProtectionRequests::const_iterator it =
           client_protection_requests_.begin();
       it != client_protection_requests_.end();
       ++it) {
    for (ContentProtections::const_iterator it2 = it->second.begin();
         it2 != it->second.end();
         ++it2) {
      if (it->first == client_id && it2->first == display_id)
        continue;
      protections[it2->first] |= it2->second;
    }
  }
  protections[display_id] |= desired_method_mask;

  if (!ApplyProtections(protections))
    return false;

  if (desired_method_mask == CONTENT_PROTECTION_METHOD_NONE) {
    if (client_protection_requests_.find(client_id) !=
        client_protection_requests_.end()) {
      client_protection_requests_[client_id].erase(display_id);
      if (client_protection_requests_[client_id].size() == 0)
        client_protection_requests_.erase(client_id);
    }
  } else {
    client_protection_requests_[client_id][display_id] = desired_method_mask;
  }

  return true;
}

std::vector<ui::ColorCalibrationProfile>
DisplayConfigurator::GetAvailableColorCalibrationProfiles(int64_t display_id) {
  if (!base::CommandLine::ForCurrentProcess()->HasSwitch(
          switches::kDisableDisplayColorCalibration)) {
    for (size_t i = 0; i < cached_displays_.size(); ++i) {
      if (cached_displays_[i].display &&
          cached_displays_[i].display->display_id() == display_id) {
        return native_display_delegate_->GetAvailableColorCalibrationProfiles(
            *cached_displays_[i].display);
      }
    }
  }

  return std::vector<ui::ColorCalibrationProfile>();
}

bool DisplayConfigurator::SetColorCalibrationProfile(
    int64_t display_id,
    ui::ColorCalibrationProfile new_profile) {
  for (size_t i = 0; i < cached_displays_.size(); ++i) {
    if (cached_displays_[i].display &&
        cached_displays_[i].display->display_id() == display_id) {
      return native_display_delegate_->SetColorCalibrationProfile(
          *cached_displays_[i].display, new_profile);
    }
  }

  return false;
}

void DisplayConfigurator::PrepareForExit() {
  configure_display_ = false;
}

bool DisplayConfigurator::SetDisplayPower(
    chromeos::DisplayPowerState power_state,
    int flags) {
  if (!configure_display_)
    return false;

  VLOG(1) << "SetDisplayPower: power_state="
          << DisplayPowerStateToString(power_state) << " flags=" << flags
          << ", configure timer="
          << (configure_timer_.IsRunning() ? "Running" : "Stopped");
  if (power_state == current_power_state_ &&
      !(flags & kSetDisplayPowerForceProbe))
    return true;

  native_display_delegate_->GrabServer();
  UpdateCachedDisplays();

  const MultipleDisplayState new_state = ChooseDisplayState(power_state);
  bool attempted_change = false;
  bool success = false;

  bool only_if_single_internal_display =
      flags & kSetDisplayPowerOnlyIfSingleInternalDisplay;
  bool single_internal_display =
      cached_displays_.size() == 1 &&
      cached_displays_[0].display->type() == DISPLAY_CONNECTION_TYPE_INTERNAL;
  if (single_internal_display || !only_if_single_internal_display) {
    success = EnterStateOrFallBackToSoftwareMirroring(new_state, power_state);
    attempted_change = true;

    // Force the DPMS on since the driver doesn't always detect that it
    // should turn on. This is needed when coming back from idle suspend.
    if (success && power_state != chromeos::DISPLAY_POWER_ALL_OFF)
      native_display_delegate_->ForceDPMSOn();
  }

  native_display_delegate_->UngrabServer();
  if (attempted_change)
    NotifyObservers(success, new_state);
  return success;
}

bool DisplayConfigurator::SetDisplayMode(MultipleDisplayState new_state) {
  if (!configure_display_)
    return false;

  VLOG(1) << "SetDisplayMode: state=" << DisplayStateToString(new_state);
  if (display_state_ == new_state) {
    // Cancel software mirroring if the state is moving from
    // MULTIPLE_DISPLAY_STATE_DUAL_EXTENDED to
    // MULTIPLE_DISPLAY_STATE_DUAL_EXTENDED.
    if (mirroring_controller_ &&
        new_state == MULTIPLE_DISPLAY_STATE_DUAL_EXTENDED)
      mirroring_controller_->SetSoftwareMirroring(false);
    NotifyObservers(true, new_state);
    return true;
  }

  native_display_delegate_->GrabServer();
  UpdateCachedDisplays();
  const bool success = EnterStateOrFallBackToSoftwareMirroring(
      new_state, requested_power_state_);
  native_display_delegate_->UngrabServer();

  NotifyObservers(success, new_state);
  return success;
}

void DisplayConfigurator::OnConfigurationChanged() {
  // Configure displays with |kConfigureDelayMs| delay,
  // so that time-consuming ConfigureDisplays() won't be called multiple times.
  if (configure_timer_.IsRunning()) {
    // Note: when the timer is running it is possible that a different task
    // (SetDisplayPower()) is scheduled. In these cases, prefer the already
    // scheduled task to ConfigureDisplays() since ConfigureDisplays() performs
    // only basic configuration while SetDisplayPower() will perform additional
    // operations.
    configure_timer_.Reset();
  } else {
    configure_timer_.Start(
        FROM_HERE,
        base::TimeDelta::FromMilliseconds(kConfigureDelayMs),
        this,
        &DisplayConfigurator::ConfigureDisplays);
  }
}

void DisplayConfigurator::AddObserver(Observer* observer) {
  observers_.AddObserver(observer);
}

void DisplayConfigurator::RemoveObserver(Observer* observer) {
  observers_.RemoveObserver(observer);
}

void DisplayConfigurator::SuspendDisplays() {
  // If the display is off due to user inactivity and there's only a single
  // internal display connected, switch to the all-on state before
  // suspending.  This shouldn't be very noticeable to the user since the
  // backlight is off at this point, and doing this lets us resume directly
  // into the "on" state, which greatly reduces resume times.
  if (requested_power_state_ == chromeos::DISPLAY_POWER_ALL_OFF) {
    SetDisplayPower(chromeos::DISPLAY_POWER_ALL_ON,
                    kSetDisplayPowerOnlyIfSingleInternalDisplay);

    // We need to make sure that the monitor configuration we just did actually
    // completes before we return, because otherwise the X message could be
    // racing with the HandleSuspendReadiness message.
    native_display_delegate_->SyncWithServer();
  }
}

void DisplayConfigurator::ResumeDisplays() {
  // Force probing to ensure that we pick up any changes that were made
  // while the system was suspended.
  configure_timer_.Start(
      FROM_HERE,
      base::TimeDelta::FromMilliseconds(kResumeDelayMs),
      base::Bind(base::IgnoreResult(&DisplayConfigurator::SetDisplayPower),
                 base::Unretained(this),
                 requested_power_state_,
                 kSetDisplayPowerForceProbe));
}

void DisplayConfigurator::UpdateCachedDisplays() {
  std::vector<DisplaySnapshot*> snapshots =
      native_display_delegate_->GetDisplays();

  cached_displays_.clear();
  for (size_t i = 0; i < snapshots.size(); ++i) {
    DisplayState display_state;
    display_state.display = snapshots[i];
    cached_displays_.push_back(display_state);
  }

  // Set |selected_mode| fields.
  for (size_t i = 0; i < cached_displays_.size(); ++i) {
    DisplayState* display_state = &cached_displays_[i];
    if (display_state->display->has_proper_display_id()) {
      gfx::Size size;
      if (state_controller_ &&
          state_controller_->GetResolutionForDisplayId(
              display_state->display->display_id(), &size)) {
        display_state->selected_mode =
            FindDisplayModeMatchingSize(*display_state->display, size);
      }
    }
    // Fall back to native mode.
    if (!display_state->selected_mode)
      display_state->selected_mode = display_state->display->native_mode();
  }

  // Set |mirror_mode| fields.
  if (cached_displays_.size() == 2) {
    bool one_is_internal =
        cached_displays_[0].display->type() == DISPLAY_CONNECTION_TYPE_INTERNAL;
    bool two_is_internal =
        cached_displays_[1].display->type() == DISPLAY_CONNECTION_TYPE_INTERNAL;
    int internal_displays =
        (one_is_internal ? 1 : 0) + (two_is_internal ? 1 : 0);
    DCHECK_LT(internal_displays, 2);
    LOG_IF(WARNING, internal_displays == 2)
        << "Two internal displays detected.";

    bool can_mirror = false;
    for (int attempt = 0; !can_mirror && attempt < 2; ++attempt) {
      // Try preserving external display's aspect ratio on the first attempt.
      // If that fails, fall back to the highest matching resolution.
      bool preserve_aspect = attempt == 0;

      if (internal_displays == 1) {
        if (one_is_internal) {
          can_mirror = FindMirrorMode(&cached_displays_[0],
                                      &cached_displays_[1],
                                      is_panel_fitting_enabled_,
                                      preserve_aspect);
        } else {
          DCHECK(two_is_internal);
          can_mirror = FindMirrorMode(&cached_displays_[1],
                                      &cached_displays_[0],
                                      is_panel_fitting_enabled_,
                                      preserve_aspect);
        }
      } else {  // if (internal_displays == 0)
        // No panel fitting for external displays, so fall back to exact match.
        can_mirror = FindMirrorMode(
            &cached_displays_[0], &cached_displays_[1], false, preserve_aspect);
        if (!can_mirror && preserve_aspect) {
          // FindMirrorMode() will try to preserve aspect ratio of what it
          // thinks is external display, so if it didn't succeed with one, maybe
          // it will succeed with the other.  This way we will have the correct
          // aspect ratio on at least one of them.
          can_mirror = FindMirrorMode(&cached_displays_[1],
                                      &cached_displays_[0],
                                      false,
                                      preserve_aspect);
        }
      }
    }
  }
}

bool DisplayConfigurator::FindMirrorMode(DisplayState* internal_display,
                                         DisplayState* external_display,
                                         bool try_panel_fitting,
                                         bool preserve_aspect) {
  const DisplayMode* internal_native_info =
      internal_display->display->native_mode();
  const DisplayMode* external_native_info =
      external_display->display->native_mode();
  if (!internal_native_info || !external_native_info)
    return false;

  // Check if some external display resolution can be mirrored on internal.
  // Prefer the modes in the order they're present in DisplaySnapshot, assuming
  // this is the order in which they look better on the monitor.
  for (DisplayModeList::const_iterator external_it =
           external_display->display->modes().begin();
       external_it != external_display->display->modes().end();
       ++external_it) {
    const DisplayMode& external_info = **external_it;
    bool is_native_aspect_ratio =
        external_native_info->size().width() * external_info.size().height() ==
        external_native_info->size().height() * external_info.size().width();
    if (preserve_aspect && !is_native_aspect_ratio)
      continue;  // Allow only aspect ratio preserving modes for mirroring.

    // Try finding an exact match.
    for (DisplayModeList::const_iterator internal_it =
             internal_display->display->modes().begin();
         internal_it != internal_display->display->modes().end();
         ++internal_it) {
      const DisplayMode& internal_info = **internal_it;
      if (internal_info.size().width() == external_info.size().width() &&
          internal_info.size().height() == external_info.size().height() &&
          internal_info.is_interlaced() == external_info.is_interlaced()) {
        internal_display->mirror_mode = *internal_it;
        external_display->mirror_mode = *external_it;
        return true;  // Mirror mode found.
      }
    }

    // Try to create a matching internal display mode by panel fitting.
    if (try_panel_fitting) {
      // We can downscale by 1.125, and upscale indefinitely. Downscaling looks
      // ugly, so, can fit == can upscale. Also, internal panels don't support
      // fitting interlaced modes.
      bool can_fit = internal_native_info->size().width() >=
                         external_info.size().width() &&
                     internal_native_info->size().height() >=
                         external_info.size().height() &&
                     !external_info.is_interlaced();
      if (can_fit) {
        native_display_delegate_->AddMode(*internal_display->display,
                                          *external_it);
        internal_display->display->add_mode(*external_it);
        internal_display->mirror_mode = *external_it;
        external_display->mirror_mode = *external_it;
        return true;  // Mirror mode created.
      }
    }
  }

  return false;
}

void DisplayConfigurator::ConfigureDisplays() {
  if (!configure_display_)
    return;

  native_display_delegate_->GrabServer();
  UpdateCachedDisplays();
  const MultipleDisplayState new_state = ChooseDisplayState(
      requested_power_state_);
  const bool success = EnterStateOrFallBackToSoftwareMirroring(
      new_state, requested_power_state_);
  native_display_delegate_->UngrabServer();

  NotifyObservers(success, new_state);
}

void DisplayConfigurator::NotifyObservers(
    bool success,
    MultipleDisplayState attempted_state) {
  if (success) {
    FOR_EACH_OBSERVER(
        Observer, observers_, OnDisplayModeChanged(cached_displays_));
  } else {
    FOR_EACH_OBSERVER(
        Observer, observers_, OnDisplayModeChangeFailed(attempted_state));
  }
}

bool DisplayConfigurator::EnterStateOrFallBackToSoftwareMirroring(
    MultipleDisplayState display_state,
    chromeos::DisplayPowerState power_state) {
  bool success = EnterState(display_state, power_state);
  if (mirroring_controller_) {
    bool enable_software_mirroring = false;
    if (!success && display_state == MULTIPLE_DISPLAY_STATE_DUAL_MIRROR) {
      if (display_state_ != MULTIPLE_DISPLAY_STATE_DUAL_EXTENDED ||
          current_power_state_ != power_state)
        EnterState(MULTIPLE_DISPLAY_STATE_DUAL_EXTENDED, power_state);
      enable_software_mirroring = success =
          display_state_ == MULTIPLE_DISPLAY_STATE_DUAL_EXTENDED;
    }
    mirroring_controller_->SetSoftwareMirroring(enable_software_mirroring);
  }
  return success;
}

bool DisplayConfigurator::EnterState(MultipleDisplayState display_state,
                                     chromeos::DisplayPowerState power_state) {
  std::vector<bool> display_power;
  int num_on_displays =
      GetDisplayPower(cached_displays_, power_state, &display_power);
  VLOG(1) << "EnterState: display=" << DisplayStateToString(display_state)
          << " power=" << DisplayPowerStateToString(power_state);

  // Save the requested state so we'll try to use it next time even if we fail.
  requested_power_state_ = power_state;

  // Framebuffer dimensions.
  gfx::Size size;

  std::vector<gfx::Point> new_origins(cached_displays_.size(), gfx::Point());
  std::vector<const DisplayMode*> new_mode;
  for (size_t i = 0; i < cached_displays_.size(); ++i)
    new_mode.push_back(cached_displays_[i].display->current_mode());

  switch (display_state) {
    case MULTIPLE_DISPLAY_STATE_INVALID:
      NOTREACHED() << "Ignoring request to enter invalid state with "
                   << cached_displays_.size() << " connected display(s)";
      return false;
    case MULTIPLE_DISPLAY_STATE_HEADLESS:
      if (cached_displays_.size() != 0) {
        LOG(WARNING) << "Ignoring request to enter headless mode with "
                     << cached_displays_.size() << " connected display(s)";
        return false;
      }
      break;
    case MULTIPLE_DISPLAY_STATE_SINGLE: {
      // If there are multiple displays connected, only one should be turned on.
      if (cached_displays_.size() != 1 && num_on_displays != 1) {
        LOG(WARNING) << "Ignoring request to enter single mode with "
                     << cached_displays_.size() << " connected displays and "
                     << num_on_displays << " turned on";
        return false;
      }

      for (size_t i = 0; i < cached_displays_.size(); ++i) {
        DisplayState* state = &cached_displays_[i];
        new_mode[i] = display_power[i] ? state->selected_mode : NULL;

        if (display_power[i] || cached_displays_.size() == 1) {
          const DisplayMode* mode_info = state->selected_mode;
          if (!mode_info) {
            LOG(WARNING) << "No selected mode when configuring display: "
                         << state->display->ToString();
            return false;
          }
          if (mode_info->size() == gfx::Size(1024, 768)) {
            VLOG(1) << "Potentially misdetecting display(1024x768):"
                    << " displays size=" << cached_displays_.size()
                    << ", num_on_displays=" << num_on_displays
                    << ", current size:" << size.width() << "x" << size.height()
                    << ", i=" << i << ", display=" << state->display->ToString()
                    << ", display_mode=" << mode_info->ToString();
          }
          size = mode_info->size();
        }
      }
      break;
    }
    case MULTIPLE_DISPLAY_STATE_DUAL_MIRROR: {
      if (cached_displays_.size() != 2 ||
          (num_on_displays != 0 && num_on_displays != 2)) {
        LOG(WARNING) << "Ignoring request to enter mirrored mode with "
                     << cached_displays_.size() << " connected display(s) and "
                     << num_on_displays << " turned on";
        return false;
      }

      const DisplayMode* mode_info = cached_displays_[0].mirror_mode;
      if (!mode_info) {
        LOG(WARNING) << "No mirror mode when configuring display: "
                     << cached_displays_[0].display->ToString();
        return false;
      }
      size = mode_info->size();

      for (size_t i = 0; i < cached_displays_.size(); ++i) {
        DisplayState* state = &cached_displays_[i];
        new_mode[i] = display_power[i] ? state->mirror_mode : NULL;
      }
      break;
    }
    case MULTIPLE_DISPLAY_STATE_DUAL_EXTENDED: {
      if (cached_displays_.size() != 2 ||
          (num_on_displays != 0 && num_on_displays != 2)) {
        LOG(WARNING) << "Ignoring request to enter extended mode with "
                     << cached_displays_.size() << " connected display(s) and "
                     << num_on_displays << " turned on";
        return false;
      }

      for (size_t i = 0; i < cached_displays_.size(); ++i) {
        DisplayState* state = &cached_displays_[i];
        new_origins[i].set_y(size.height() ? size.height() + kVerticalGap : 0);
        new_mode[i] = display_power[i] ? state->selected_mode : NULL;

        // Retain the full screen size even if all displays are off so the
        // same desktop configuration can be restored when the displays are
        // turned back on.
        const DisplayMode* mode_info = cached_displays_[i].selected_mode;
        if (!mode_info) {
          LOG(WARNING) << "No selected mode when configuring display: "
                       << state->display->ToString();
          return false;
        }

        size.set_width(std::max<int>(size.width(), mode_info->size().width()));
        size.set_height(size.height() + (size.height() ? kVerticalGap : 0) +
                        mode_info->size().height());
      }
      break;
    }
  }

  // Finally, apply the desired changes.
  bool all_succeeded = true;
  if (!cached_displays_.empty()) {
    native_display_delegate_->CreateFrameBuffer(size);
    for (size_t i = 0; i < cached_displays_.size(); ++i) {
      const DisplayState& state = cached_displays_[i];
      bool configure_succeeded = false;

      while (true) {
        if (native_display_delegate_->Configure(
                *state.display, new_mode[i], new_origins[i])) {
          state.display->set_current_mode(new_mode[i]);
          state.display->set_origin(new_origins[i]);

          configure_succeeded = true;
          break;
        }

        const DisplayMode* mode_info = new_mode[i];
        if (!mode_info)
          break;

        // Find the mode with the next-best resolution and see if that can
        // be set.
        int best_mode_pixels = 0;

        int current_mode_pixels = mode_info->size().GetArea();
        for (DisplayModeList::const_iterator it =
                 state.display->modes().begin();
             it != state.display->modes().end();
             it++) {
          int pixel_count = (*it)->size().GetArea();
          if ((pixel_count < current_mode_pixels) &&
              (pixel_count > best_mode_pixels)) {
            new_mode[i] = *it;
            best_mode_pixels = pixel_count;
          }
        }

        if (best_mode_pixels == 0)
          break;
      }

      if (!configure_succeeded)
        all_succeeded = false;

      // If we are trying to set mirror mode and one of the modesets fails,
      // then the two monitors will be mis-matched.  In this case, return
      // false to let the observers be aware.
      if (display_state == MULTIPLE_DISPLAY_STATE_DUAL_MIRROR &&
          display_power[i] &&
          state.display->current_mode() != state.mirror_mode)
        all_succeeded = false;
    }
  }

  if (all_succeeded) {
    display_state_ = display_state;
    current_power_state_ = power_state;
    framebuffer_size_ = size;
  }
  return all_succeeded;
}

MultipleDisplayState DisplayConfigurator::ChooseDisplayState(
    chromeos::DisplayPowerState power_state) const {
  int num_on_displays = GetDisplayPower(cached_displays_, power_state, NULL);
  switch (cached_displays_.size()) {
    case 0:
      return MULTIPLE_DISPLAY_STATE_HEADLESS;
    case 1:
      return MULTIPLE_DISPLAY_STATE_SINGLE;
    case 2: {
      if (num_on_displays == 1) {
        // If only one display is currently turned on, return the "single"
        // state so that its native mode will be used.
        return MULTIPLE_DISPLAY_STATE_SINGLE;
      } else {
        if (!state_controller_)
          return MULTIPLE_DISPLAY_STATE_DUAL_EXTENDED;
        // With either both displays on or both displays off, use one of the
        // dual modes.
        std::vector<int64_t> display_ids;
        for (size_t i = 0; i < cached_displays_.size(); ++i) {
          // If display id isn't available, switch to extended mode.
          if (!cached_displays_[i].display->has_proper_display_id())
            return MULTIPLE_DISPLAY_STATE_DUAL_EXTENDED;
          display_ids.push_back(cached_displays_[i].display->display_id());
        }
        return state_controller_->GetStateForDisplayIds(display_ids);
      }
    }
    default:
      NOTREACHED();
  }
  return MULTIPLE_DISPLAY_STATE_INVALID;
}

}  // namespace ui