1 // Copyright 2018 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 "ash/display/cros_display_config.h"
6 
7 #include <utility>
8 
9 #include "ash/display/display_alignment_controller.h"
10 #include "ash/display/display_configuration_controller.h"
11 #include "ash/display/display_highlight_controller.h"
12 #include "ash/display/display_prefs.h"
13 #include "ash/display/overscan_calibrator.h"
14 #include "ash/display/resolution_notification_controller.h"
15 #include "ash/display/screen_orientation_controller.h"
16 #include "ash/display/touch_calibrator_controller.h"
17 #include "ash/display/window_tree_host_manager.h"
18 #include "ash/public/cpp/ash_features.h"
19 #include "ash/public/cpp/tablet_mode_observer.h"
20 #include "ash/public/mojom/cros_display_config.mojom.h"
21 #include "ash/shell.h"
22 #include "ash/touch/ash_touch_transform_controller.h"
23 #include "ash/wm/tablet_mode/tablet_mode_controller.h"
24 #include "base/bind.h"
25 #include "base/optional.h"
26 #include "base/strings/string_number_conversions.h"
27 #include "mojo/public/cpp/bindings/associated_remote.h"
28 #include "ui/display/display.h"
29 #include "ui/display/display_layout.h"
30 #include "ui/display/display_layout_builder.h"
31 #include "ui/display/display_observer.h"
32 #include "ui/display/manager/display_manager.h"
33 #include "ui/display/manager/display_util.h"
34 #include "ui/display/mojom/display_mojom_traits.h"
35 #include "ui/display/screen.h"
36 
37 namespace ash {
38 
39 namespace {
40 
41 // Maximum allowed bounds origin absolute value.
42 constexpr int kMaxBoundsOrigin = 200 * 1000;
43 
44 // This is the default range of display width in logical pixels allowed after
45 // applying display zoom.
46 constexpr int kDefaultMaxZoomWidth = 4096;
47 constexpr int kDefaultMinZoomWidth = 640;
48 
GetDisplayManager()49 display::DisplayManager* GetDisplayManager() {
50   return Shell::Get()->display_manager();
51 }
52 
GetDisplayId(const std::string & display_id_str)53 int64_t GetDisplayId(const std::string& display_id_str) {
54   int64_t display_id;
55   if (!base::StringToInt64(display_id_str, &display_id))
56     display_id = display::kInvalidDisplayId;
57   return display_id;
58 }
59 
60 // Gets the display with the provided string id.
GetDisplay(const std::string & display_id_str)61 display::Display GetDisplay(const std::string& display_id_str) {
62   int64_t display_id = GetDisplayId(display_id_str);
63   if (display_id == display::kInvalidDisplayId)
64     return display::Display();
65   display::DisplayManager* display_manager = GetDisplayManager();
66   if (display_manager->IsInUnifiedMode() &&
67       display_id != display::kUnifiedDisplayId) {
68     // In unified desktop mode, the mirroring displays, which constitue the
69     // combined unified display, are contained in the software mirroring
70     // displays list in the display manager.
71     // The active list of displays only contains a single unified display entry
72     // with id |kUnifiedDisplayId|.
73     return display_manager->GetMirroringDisplayById(display_id);
74   }
75   return display_manager->GetDisplayForId(display_id);
76 }
77 
GetMojomDisplayLayoutPosition(display::DisplayPlacement::Position position)78 mojom::DisplayLayoutPosition GetMojomDisplayLayoutPosition(
79     display::DisplayPlacement::Position position) {
80   switch (position) {
81     case display::DisplayPlacement::TOP:
82       return mojom::DisplayLayoutPosition::kTop;
83     case display::DisplayPlacement::RIGHT:
84       return mojom::DisplayLayoutPosition::kRight;
85     case display::DisplayPlacement::BOTTOM:
86       return mojom::DisplayLayoutPosition::kBottom;
87     case display::DisplayPlacement::LEFT:
88       return mojom::DisplayLayoutPosition::kLeft;
89   }
90   NOTREACHED();
91   return mojom::DisplayLayoutPosition::kLeft;
92 }
93 
GetDisplayPlacementPosition(mojom::DisplayLayoutPosition position)94 display::DisplayPlacement::Position GetDisplayPlacementPosition(
95     mojom::DisplayLayoutPosition position) {
96   switch (position) {
97     case mojom::DisplayLayoutPosition::kTop:
98       return display::DisplayPlacement::TOP;
99     case mojom::DisplayLayoutPosition::kRight:
100       return display::DisplayPlacement::RIGHT;
101     case mojom::DisplayLayoutPosition::kBottom:
102       return display::DisplayPlacement::BOTTOM;
103     case mojom::DisplayLayoutPosition::kLeft:
104       return display::DisplayPlacement::LEFT;
105   }
106   NOTREACHED();
107   return display::DisplayPlacement::LEFT;
108 }
109 
GetDisplayLayouts()110 std::vector<mojom::DisplayLayoutPtr> GetDisplayLayouts() {
111   auto layouts = std::vector<mojom::DisplayLayoutPtr>();
112   display::Screen* screen = display::Screen::GetScreen();
113   const std::vector<display::Display>& displays = screen->GetAllDisplays();
114   display::DisplayManager* display_manager = GetDisplayManager();
115   for (const display::Display& display : displays) {
116     const display::DisplayPlacement placement =
117         display_manager->GetCurrentResolvedDisplayLayout().FindPlacementById(
118             display.id());
119     if (placement.display_id == display::kInvalidDisplayId)
120       continue;
121     auto layout = mojom::DisplayLayout::New();
122     layout->id = base::NumberToString(placement.display_id);
123     layout->parent_id = base::NumberToString(placement.parent_display_id);
124     layout->position = GetMojomDisplayLayoutPosition(placement.position);
125     layout->offset = placement.offset;
126     layouts.emplace_back(std::move(layout));
127   }
128   return layouts;
129 }
130 
GetDisplayUnifiedLayouts()131 std::vector<mojom::DisplayLayoutPtr> GetDisplayUnifiedLayouts() {
132   auto layouts = std::vector<mojom::DisplayLayoutPtr>();
133   display::DisplayManager* display_manager = GetDisplayManager();
134 
135   const display::UnifiedDesktopLayoutMatrix& matrix =
136       display_manager->current_unified_desktop_matrix();
137   for (size_t row_index = 0; row_index < matrix.size(); ++row_index) {
138     const auto& row = matrix[row_index];
139     for (size_t column_index = 0; column_index < row.size(); ++column_index) {
140       if (column_index == 0 && row_index == 0) {
141         // No placement for the primary display.
142         continue;
143       }
144       auto layout = mojom::DisplayLayout::New();
145       const int64_t display_id = row[column_index];
146       // Parent display is either the one in the above row, or the one on the
147       // left in the same row.
148       const int64_t parent_id = column_index == 0
149                                     ? matrix[row_index - 1][column_index]
150                                     : row[column_index - 1];
151       layout->id = base::NumberToString(display_id);
152       layout->parent_id = base::NumberToString(parent_id);
153       layout->position = column_index == 0
154                              ? mojom::DisplayLayoutPosition::kBottom
155                              : mojom::DisplayLayoutPosition::kRight;
156       layout->offset = 0;
157       layouts.emplace_back(std::move(layout));
158     }
159   }
160   return layouts;
161 }
162 
SetDisplayLayoutMode(const mojom::DisplayLayoutInfo & info)163 mojom::DisplayConfigResult SetDisplayLayoutMode(
164     const mojom::DisplayLayoutInfo& info) {
165   display::DisplayManager* display_manager = GetDisplayManager();
166   if (info.layout_mode == mojom::DisplayLayoutMode::kNormal) {
167     display_manager->SetDefaultMultiDisplayModeForCurrentDisplays(
168         display::DisplayManager::EXTENDED);
169     display_manager->SetMirrorMode(display::MirrorMode::kOff, base::nullopt);
170     return mojom::DisplayConfigResult::kSuccess;
171   }
172 
173   if (info.layout_mode == mojom::DisplayLayoutMode::kUnified) {
174     if (!display_manager->unified_desktop_enabled())
175       return mojom::DisplayConfigResult::kUnifiedNotEnabledError;
176     display_manager->SetDefaultMultiDisplayModeForCurrentDisplays(
177         display::DisplayManager::UNIFIED);
178     display_manager->SetMirrorMode(display::MirrorMode::kOff, base::nullopt);
179     return mojom::DisplayConfigResult::kSuccess;
180   }
181 
182   DCHECK(info.layout_mode == mojom::DisplayLayoutMode::kMirrored);
183 
184   // 'Normal' mirror mode.
185   if (!info.mirror_source_id) {
186     display_manager->SetMirrorMode(display::MirrorMode::kNormal, base::nullopt);
187     return mojom::DisplayConfigResult::kSuccess;
188   }
189 
190   // 'Mixed' mirror mode.
191   display::Display source = GetDisplay(*info.mirror_source_id);
192   if (source.id() == display::kInvalidDisplayId)
193     return mojom::DisplayConfigResult::kMirrorModeSourceIdError;
194   display::DisplayIdList destination_ids;
195   if (info.mirror_destination_ids) {
196     for (const std::string& id_str : *info.mirror_destination_ids) {
197       int64_t destination_id = GetDisplayId(id_str);
198       if (destination_id == display::kInvalidDisplayId)
199         return mojom::DisplayConfigResult::kMirrorModeDestIdError;
200       destination_ids.emplace_back(destination_id);
201     }
202   } else {
203     const std::vector<display::Display>& displays =
204         display::Screen::GetScreen()->GetAllDisplays();
205     for (const display::Display& display : displays)
206       destination_ids.emplace_back(display.id());
207   }
208   base::Optional<display::MixedMirrorModeParams> mixed_params(
209       base::in_place, source.id(), destination_ids);
210   const display::MixedMirrorModeParamsErrors error_type =
211       display::ValidateParamsForMixedMirrorMode(
212           display_manager->GetCurrentDisplayIdList(), *mixed_params);
213   switch (error_type) {
214     case display::MixedMirrorModeParamsErrors::kErrorSingleDisplay:
215       return mojom::DisplayConfigResult::kMirrorModeSingleDisplayError;
216     case display::MixedMirrorModeParamsErrors::kErrorSourceIdNotFound:
217       return mojom::DisplayConfigResult::kMirrorModeSourceIdError;
218     case display::MixedMirrorModeParamsErrors::kErrorDestinationIdsEmpty:
219     case display::MixedMirrorModeParamsErrors::kErrorDestinationIdNotFound:
220     case display::MixedMirrorModeParamsErrors::kErrorDuplicateId:
221       return mojom::DisplayConfigResult::kMirrorModeDestIdError;
222     case display::MixedMirrorModeParamsErrors::kSuccess:
223       break;
224   }
225   display_manager->SetMirrorMode(display::MirrorMode::kMixed, mixed_params);
226   return mojom::DisplayConfigResult::kSuccess;
227 }
228 
GetDisplayMode(const display::ManagedDisplayInfo & display_info,const display::ManagedDisplayMode & display_mode)229 mojom::DisplayModePtr GetDisplayMode(
230     const display::ManagedDisplayInfo& display_info,
231     const display::ManagedDisplayMode& display_mode) {
232   auto result = mojom::DisplayMode::New();
233   gfx::Size size_dip = display_mode.GetSizeInDIP();
234   result->size = size_dip;
235   result->size_in_native_pixels = display_mode.size();
236   result->device_scale_factor = display_mode.device_scale_factor();
237   result->refresh_rate = display_mode.refresh_rate();
238   result->is_native = display_mode.native();
239   result->is_interlaced = display_mode.is_interlaced();
240   return result;
241 }
242 
DisplayRotationFromRotationOptions(mojom::DisplayRotationOptions option)243 display::Display::Rotation DisplayRotationFromRotationOptions(
244     mojom::DisplayRotationOptions option) {
245   switch (option) {
246     case mojom::DisplayRotationOptions::kAutoRotate:
247       return display::Display::ROTATE_0;
248 
249     case mojom::DisplayRotationOptions::kZeroDegrees:
250       return display::Display::ROTATE_0;
251 
252     case mojom::DisplayRotationOptions::k90Degrees:
253       return display::Display::ROTATE_90;
254 
255     case mojom::DisplayRotationOptions::k180Degrees:
256       return display::Display::ROTATE_180;
257 
258     case mojom::DisplayRotationOptions::k270Degrees:
259       return display::Display::ROTATE_270;
260   }
261 }
262 
RotationOptionsFromDisplayRotation(display::Display::Rotation rotation)263 mojom::DisplayRotationOptions RotationOptionsFromDisplayRotation(
264     display::Display::Rotation rotation) {
265   const bool is_in_tablet_physical_state =
266       Shell::Get()->tablet_mode_controller()->is_in_tablet_physical_state();
267   const bool is_auto_rotate_enabled =
268       !Shell::Get()->screen_orientation_controller()->user_rotation_locked();
269   if (is_in_tablet_physical_state && is_auto_rotate_enabled)
270     return mojom::DisplayRotationOptions::kAutoRotate;
271 
272   switch (rotation) {
273     case display::Display::ROTATE_0:
274       return mojom::DisplayRotationOptions::kZeroDegrees;
275 
276     case display::Display::ROTATE_90:
277       return mojom::DisplayRotationOptions::k90Degrees;
278 
279     case display::Display::ROTATE_180:
280       return mojom::DisplayRotationOptions::k180Degrees;
281 
282     case display::Display::ROTATE_270:
283       return mojom::DisplayRotationOptions::k270Degrees;
284   }
285 }
286 
GetDisplayUnitInfo(const display::Display & display,int64_t primary_id)287 mojom::DisplayUnitInfoPtr GetDisplayUnitInfo(const display::Display& display,
288                                              int64_t primary_id) {
289   display::DisplayManager* display_manager = GetDisplayManager();
290   const display::ManagedDisplayInfo& display_info =
291       display_manager->GetDisplayInfo(display.id());
292 
293   auto info = mojom::DisplayUnitInfo::New();
294   info->id = base::NumberToString(display.id());
295   info->name = display_manager->GetDisplayNameForId(display.id());
296 
297   if (!display_info.manufacturer_id().empty() ||
298       !display_info.product_id().empty() ||
299       (display_info.year_of_manufacture() !=
300        display::kInvalidYearOfManufacture)) {
301     info->edid = mojom::Edid::New();
302     info->edid->manufacturer_id = display_info.manufacturer_id();
303     info->edid->product_id = display_info.product_id();
304     info->edid->year_of_manufacture = display_info.year_of_manufacture();
305   }
306 
307   info->is_primary = display.id() == primary_id;
308   info->is_internal = display.IsInternal();
309   info->is_enabled = true;
310   info->is_in_tablet_physical_state =
311       Shell::Get()->tablet_mode_controller()->is_in_tablet_physical_state();
312   const bool has_accelerometer_support =
313       display.accelerometer_support() ==
314       display::Display::AccelerometerSupport::AVAILABLE;
315   info->has_touch_support =
316       display.touch_support() == display::Display::TouchSupport::AVAILABLE;
317   info->has_accelerometer_support = has_accelerometer_support;
318 
319   const float device_dpi = display_info.device_dpi();
320   info->dpi_x = device_dpi * display.size().width() /
321                 display_info.bounds_in_native().width();
322   info->dpi_y = device_dpi * display.size().height() /
323                 display_info.bounds_in_native().height();
324 
325   info->rotation_options =
326       RotationOptionsFromDisplayRotation(display.rotation());
327   info->bounds = display.bounds();
328   info->overscan = display_manager->GetOverscanInsets(display.id());
329   info->work_area = display.work_area();
330 
331   int display_mode_index = 0;
332   display::ManagedDisplayMode active_mode;
333   bool has_active_mode = display_manager->GetActiveModeForDisplayId(
334       display_info.id(), &active_mode);
335   for (const display::ManagedDisplayMode& display_mode :
336        display_info.display_modes()) {
337     info->available_display_modes.emplace_back(
338         GetDisplayMode(display_info, display_mode));
339     if (has_active_mode && display_mode.IsEquivalent(active_mode))
340       info->selected_display_mode_index = display_mode_index;
341     ++display_mode_index;
342   }
343 
344   info->display_zoom_factor = display_info.zoom_factor();
345   if (has_active_mode) {
346     auto zoom_levels = display::GetDisplayZoomFactors(active_mode);
347     // Ensure that the current zoom factor is in the list.
348     display::InsertDsfIntoList(&zoom_levels, display_info.zoom_factor());
349     info->available_display_zoom_factors.assign(zoom_levels.begin(),
350                                                 zoom_levels.end());
351 
352   } else {
353     info->available_display_zoom_factors.push_back(display_info.zoom_factor());
354   }
355 
356   return info;
357 }
358 
359 // Validates that DisplayProperties are valid with the current DisplayManager
360 // configuration. Returns an error on failure.
ValidateDisplayProperties(const mojom::DisplayConfigProperties & properties,const display::Display & display)361 mojom::DisplayConfigResult ValidateDisplayProperties(
362     const mojom::DisplayConfigProperties& properties,
363     const display::Display& display) {
364   display::DisplayManager* display_manager = GetDisplayManager();
365 
366   int64_t id = display.id();
367   if (id == display::kInvalidDisplayId)
368     return mojom::DisplayConfigResult::kInvalidDisplayIdError;
369 
370   // Overscan cannot be changed for the internal display, and should be at most
371   // half of the screen size.
372   if (properties.overscan) {
373     if (display.IsInternal())
374       return mojom::DisplayConfigResult::kNotSupportedOnInternalDisplayError;
375 
376     if (properties.overscan->left() < 0 || properties.overscan->top() < 0 ||
377         properties.overscan->right() < 0 || properties.overscan->bottom() < 0) {
378       DLOG(ERROR) << "Negative overscan";
379       return mojom::DisplayConfigResult::kPropertyValueOutOfRangeError;
380     }
381 
382     const gfx::Insets overscan = display_manager->GetOverscanInsets(id);
383     int screen_width = display.bounds().width() + overscan.width();
384     int screen_height = display.bounds().height() + overscan.height();
385 
386     if ((properties.overscan->left() + properties.overscan->right()) * 2 >
387             screen_width ||
388         (properties.overscan->top() + properties.overscan->bottom()) * 2 >
389             screen_height) {
390       DLOG(ERROR) << "Overscan: " << properties.overscan->ToString()
391                   << " exceeds bounds: " << screen_width << "x"
392                   << screen_height;
393       return mojom::DisplayConfigResult::kPropertyValueOutOfRangeError;
394     }
395   }
396 
397   // The bounds cannot be changed for the primary display and should be inside
398   // a reasonable bounds.
399   if (properties.bounds_origin) {
400     const display::Display& primary =
401         display::Screen::GetScreen()->GetPrimaryDisplay();
402     if (id == primary.id() || properties.set_primary)
403       return mojom::DisplayConfigResult::kNotSupportedOnInternalDisplayError;
404     if (properties.bounds_origin->x() > kMaxBoundsOrigin ||
405         properties.bounds_origin->x() < -kMaxBoundsOrigin ||
406         properties.bounds_origin->y() > kMaxBoundsOrigin ||
407         properties.bounds_origin->y() < -kMaxBoundsOrigin) {
408       DLOG(ERROR) << "Bounds origin out of range";
409       return mojom::DisplayConfigResult::kPropertyValueOutOfRangeError;
410     }
411   }
412 
413   if (properties.display_zoom_factor > 0) {
414     display::ManagedDisplayMode current_mode;
415     if (!display_manager->GetActiveModeForDisplayId(id, &current_mode)) {
416       DLOG(ERROR) << "No active mode for display: " << id;
417       return mojom::DisplayConfigResult::kInvalidDisplayIdError;
418     }
419     // This check is added to limit the range of display zoom that can be
420     // applied via the system display API. The said range is such that when a
421     // display zoom is applied, the final logical width in pixels should lie
422     // within the range of 640 pixels and 4096 pixels.
423     const int landscape_width =
424         std::max(current_mode.size().width(), current_mode.size().height());
425     const int max_allowed_width =
426         std::max(kDefaultMaxZoomWidth, landscape_width);
427     const int min_allowed_width =
428         std::min(kDefaultMinZoomWidth, landscape_width);
429     int current_width = static_cast<float>(landscape_width) /
430                         current_mode.device_scale_factor();
431     if (current_width / properties.display_zoom_factor > max_allowed_width ||
432         current_width / properties.display_zoom_factor < min_allowed_width) {
433       DLOG(ERROR) << "Display zoom factor out of range";
434       return mojom::DisplayConfigResult::kPropertyValueOutOfRangeError;
435     }
436   }
437 
438   return mojom::DisplayConfigResult::kSuccess;
439 }
440 
441 // Sets the display layout for the target display in reference to the primary
442 // display.
SetDisplayLayoutFromBounds(const gfx::Rect & primary_display_bounds,int64_t primary_display_id,const gfx::Rect & target_display_bounds,int64_t target_display_id)443 void SetDisplayLayoutFromBounds(const gfx::Rect& primary_display_bounds,
444                                 int64_t primary_display_id,
445                                 const gfx::Rect& target_display_bounds,
446                                 int64_t target_display_id) {
447   display::DisplayPlacement placement(
448       display::DisplayLayout::CreatePlacementForRectangles(
449           primary_display_bounds, target_display_bounds));
450   placement.display_id = target_display_id;
451   placement.parent_display_id = primary_display_id;
452 
453   std::unique_ptr<display::DisplayLayout> layout(new display::DisplayLayout);
454   layout->placement_list.push_back(placement);
455   layout->primary_id = primary_display_id;
456 
457   Shell::Get()->display_configuration_controller()->SetDisplayLayout(
458       std::move(layout));
459 }
460 
461 // Attempts to set the display mode for display |id|.
SetDisplayMode(int64_t id,const mojom::DisplayMode & display_mode,mojom::DisplayConfigSource source)462 mojom::DisplayConfigResult SetDisplayMode(
463     int64_t id,
464     const mojom::DisplayMode& display_mode,
465     mojom::DisplayConfigSource source) {
466   display::DisplayManager* display_manager = GetDisplayManager();
467 
468   display::ManagedDisplayMode current_mode;
469   if (!display_manager->GetActiveModeForDisplayId(id, &current_mode))
470     return mojom::DisplayConfigResult::kInvalidDisplayIdError;
471 
472   display::ManagedDisplayMode new_mode(
473       display_mode.size_in_native_pixels, display_mode.refresh_rate,
474       display_mode.is_interlaced, display_mode.is_native,
475       display_mode.device_scale_factor);
476 
477   if (!new_mode.IsEquivalent(current_mode)) {
478     // For the internal display, the display mode will be applied directly.
479     // Otherwise a confirm/revert notification will be prepared first, and the
480     // display mode will be applied. If the user accepts the mode change by
481     // dismissing the notification, MaybeStoreDisplayPrefs() will be called back
482     // to persist the new preferences.
483     if (!Shell::Get()
484              ->resolution_notification_controller()
485              ->PrepareNotificationAndSetDisplayMode(
486                  id, current_mode, new_mode, source, base::BindOnce([]() {
487                    Shell::Get()->display_prefs()->MaybeStoreDisplayPrefs();
488                  }))) {
489       return mojom::DisplayConfigResult::kSetDisplayModeError;
490     }
491   }
492 
493   return mojom::DisplayConfigResult::kSuccess;
494 }
495 
GetCalibrationPair(const mojom::TouchCalibrationPair & pair)496 display::TouchCalibrationData::CalibrationPointPair GetCalibrationPair(
497     const mojom::TouchCalibrationPair& pair) {
498   return std::make_pair(pair.display_point, pair.touch_point);
499 }
500 
501 }  // namespace
502 
503 // -----------------------------------------------------------------------------
504 // CrosDisplayConfig::ObserverImpl:
505 
506 // Observes display and tablet mode events, and notifies the
507 // CrosDisplayConfigObservers with OnDisplayConfigChanged() in response to those
508 // events.
509 class CrosDisplayConfig::ObserverImpl
510     : public display::DisplayObserver,
511       public TabletModeObserver,
512       public ScreenOrientationController::Observer {
513  public:
ObserverImpl()514   explicit ObserverImpl() {
515     display::Screen::GetScreen()->AddObserver(this);
516     Shell::Get()->tablet_mode_controller()->AddObserver(this);
517     Shell::Get()->screen_orientation_controller()->AddObserver(this);
518   }
519 
~ObserverImpl()520   ~ObserverImpl() override {
521     Shell::Get()->screen_orientation_controller()->RemoveObserver(this);
522     Shell::Get()->tablet_mode_controller()->RemoveObserver(this);
523     display::Screen::GetScreen()->RemoveObserver(this);
524   }
525 
AddObserver(mojo::PendingAssociatedRemote<mojom::CrosDisplayConfigObserver> observer)526   void AddObserver(
527       mojo::PendingAssociatedRemote<mojom::CrosDisplayConfigObserver>
528           observer) {
529     observers_.Add(mojo::AssociatedRemote<mojom::CrosDisplayConfigObserver>(
530         std::move(observer)));
531   }
532 
533   // display::DisplayObserver:
OnDisplayAdded(const display::Display & new_display)534   void OnDisplayAdded(const display::Display& new_display) override {
535     NotifyObserversDisplayConfigChanged();
536   }
537 
OnDisplayRemoved(const display::Display & old_display)538   void OnDisplayRemoved(const display::Display& old_display) override {
539     NotifyObserversDisplayConfigChanged();
540   }
541 
OnDisplayMetricsChanged(const display::Display & display,uint32_t metrics)542   void OnDisplayMetricsChanged(const display::Display& display,
543                                uint32_t metrics) override {
544     NotifyObserversDisplayConfigChanged();
545   }
546 
547   // TabletModeObserver:
OnTabletPhysicalStateChanged()548   void OnTabletPhysicalStateChanged() override {
549     NotifyObserversDisplayConfigChanged();
550   }
551 
552   // ScreenOrientationController::Observer:
OnUserRotationLockChanged()553   void OnUserRotationLockChanged() override {
554     NotifyObserversDisplayConfigChanged();
555   }
556 
557  private:
NotifyObserversDisplayConfigChanged()558   void NotifyObserversDisplayConfigChanged() {
559     for (auto& observer : observers_)
560       observer->OnDisplayConfigChanged();
561   }
562 
563   mojo::AssociatedRemoteSet<mojom::CrosDisplayConfigObserver> observers_;
564 
565   DISALLOW_COPY_AND_ASSIGN(ObserverImpl);
566 };
567 
568 // -----------------------------------------------------------------------------
569 // CrosDisplayConfig:
570 
CrosDisplayConfig()571 CrosDisplayConfig::CrosDisplayConfig()
572     : observer_impl_(std::make_unique<ObserverImpl>()) {}
573 
574 CrosDisplayConfig::~CrosDisplayConfig() = default;
575 
BindReceiver(mojo::PendingReceiver<mojom::CrosDisplayConfigController> receiver)576 void CrosDisplayConfig::BindReceiver(
577     mojo::PendingReceiver<mojom::CrosDisplayConfigController> receiver) {
578   receivers_.Add(this, std::move(receiver));
579 }
580 
AddObserver(mojo::PendingAssociatedRemote<mojom::CrosDisplayConfigObserver> observer)581 void CrosDisplayConfig::AddObserver(
582     mojo::PendingAssociatedRemote<mojom::CrosDisplayConfigObserver> observer) {
583   observer_impl_->AddObserver(std::move(observer));
584 }
585 
GetDisplayLayoutInfo(GetDisplayLayoutInfoCallback callback)586 void CrosDisplayConfig::GetDisplayLayoutInfo(
587     GetDisplayLayoutInfoCallback callback) {
588   display::DisplayManager* display_manager = GetDisplayManager();
589 
590   auto info = mojom::DisplayLayoutInfo::New();
591   if (display_manager->IsInUnifiedMode()) {
592     info->layout_mode = mojom::DisplayLayoutMode::kUnified;
593   } else if (display_manager->IsInMirrorMode()) {
594     info->layout_mode = mojom::DisplayLayoutMode::kMirrored;
595     info->mirror_source_id =
596         base::NumberToString(display_manager->mirroring_source_id());
597     info->mirror_destination_ids = std::vector<std::string>();
598     for (int64_t id : display_manager->GetMirroringDestinationDisplayIdList())
599       info->mirror_destination_ids->emplace_back(base::NumberToString(id));
600   } else {
601     info->layout_mode = mojom::DisplayLayoutMode::kNormal;
602   }
603 
604   if (display_manager->IsInUnifiedMode()) {
605     info->layouts = GetDisplayUnifiedLayouts();
606   } else if (display_manager->num_connected_displays() > 1) {
607     info->layouts = GetDisplayLayouts();
608   }
609 
610   std::move(callback).Run(std::move(info));
611 }
612 
SetDisplayLayouts(const std::vector<mojom::DisplayLayoutPtr> & layouts)613 mojom::DisplayConfigResult SetDisplayLayouts(
614     const std::vector<mojom::DisplayLayoutPtr>& layouts) {
615   display::DisplayManager* display_manager = GetDisplayManager();
616   display::DisplayLayoutBuilder builder(
617       display_manager->GetCurrentResolvedDisplayLayout());
618   int64_t root_id = display::kInvalidDisplayId;
619   std::set<int64_t> layout_ids;
620   builder.ClearPlacements();
621   for (const mojom::DisplayLayoutPtr& layout_ptr : layouts) {
622     const mojom::DisplayLayout& layout = *layout_ptr;
623     display::Display display = GetDisplay(layout.id);
624     if (display.id() == display::kInvalidDisplayId) {
625       LOG(ERROR) << "Display layout has invalid id: " << layout.id;
626       return mojom::DisplayConfigResult::kInvalidDisplayIdError;
627     }
628     display::Display parent = GetDisplay(layout.parent_id);
629     if (parent.id() == display::kInvalidDisplayId) {
630       if (root_id != display::kInvalidDisplayId) {
631         LOG(ERROR) << "Display layout has invalid parent: " << layout.parent_id;
632         return mojom::DisplayConfigResult::kInvalidDisplayLayoutError;
633       }
634       root_id = display.id();
635       continue;  // No placement for root (primary) display.
636     }
637     layout_ids.insert(display.id());
638     display::DisplayPlacement::Position position =
639         GetDisplayPlacementPosition(layout.position);
640     builder.AddDisplayPlacement(display.id(), parent.id(), position,
641                                 layout.offset);
642   }
643 
644   const display::DisplayIdList display_ids =
645       display_manager->GetCurrentDisplayIdList();
646   std::unique_ptr<display::DisplayLayout> layout = builder.Build();
647   if (display_manager->IsInUnifiedMode()) {
648     if (root_id == display::kInvalidDisplayId) {
649       // Look for a display with no layout info to use as the root.
650       for (int64_t id : display_ids) {
651         if (!base::Contains(layout_ids, id)) {
652           root_id = id;
653           break;
654         }
655       }
656       if (root_id == display::kInvalidDisplayId) {
657         LOG(ERROR) << "Invalid unified layout: No root id";
658         return mojom::DisplayConfigResult::kInvalidDisplayLayoutError;
659       }
660     }
661     layout->primary_id = root_id;
662     display::UnifiedDesktopLayoutMatrix matrix;
663     if (!display::BuildUnifiedDesktopMatrix(display_ids, *layout, &matrix)) {
664       LOG(ERROR) << "Invalid unified layout: No proper conversion to a matrix";
665       return mojom::DisplayConfigResult::kInvalidDisplayLayoutError;
666     }
667     Shell::Get()
668         ->display_configuration_controller()
669         ->SetUnifiedDesktopLayoutMatrix(matrix);
670   } else {
671     if (!display::DisplayLayout::Validate(display_ids, *layout)) {
672       return mojom::DisplayConfigResult::kInvalidDisplayLayoutError;
673     }
674     Shell::Get()->display_configuration_controller()->SetDisplayLayout(
675         std::move(layout));
676   }
677   return mojom::DisplayConfigResult::kSuccess;
678 }
679 
SetDisplayLayoutInfo(mojom::DisplayLayoutInfoPtr info,SetDisplayLayoutInfoCallback callback)680 void CrosDisplayConfig::SetDisplayLayoutInfo(
681     mojom::DisplayLayoutInfoPtr info,
682     SetDisplayLayoutInfoCallback callback) {
683   mojom::DisplayConfigResult result = SetDisplayLayoutMode(*info);
684   if (result != mojom::DisplayConfigResult::kSuccess) {
685     std::move(callback).Run(result);
686     return;
687   }
688   if (info->layouts) {
689     result = SetDisplayLayouts(*info->layouts);
690     if (result != mojom::DisplayConfigResult::kSuccess) {
691       std::move(callback).Run(result);
692       return;
693     }
694   }
695   std::move(callback).Run(mojom::DisplayConfigResult::kSuccess);
696 }
697 
GetDisplayUnitInfoList(bool single_unified,GetDisplayUnitInfoListCallback callback)698 void CrosDisplayConfig::GetDisplayUnitInfoList(
699     bool single_unified,
700     GetDisplayUnitInfoListCallback callback) {
701   std::vector<mojom::DisplayUnitInfoPtr> info_list;
702   display::DisplayManager* display_manager = GetDisplayManager();
703 
704   std::vector<display::Display> displays;
705   int64_t primary_id;
706   if (!display_manager->IsInUnifiedMode()) {
707     displays = display::Screen::GetScreen()->GetAllDisplays();
708     primary_id = display::Screen::GetScreen()->GetPrimaryDisplay().id();
709   } else if (single_unified) {
710     for (size_t i = 0; i < display_manager->GetNumDisplays(); ++i)
711       displays.push_back(display_manager->GetDisplayAt(i));
712     primary_id = display::Screen::GetScreen()->GetPrimaryDisplay().id();
713   } else {
714     displays = display_manager->software_mirroring_display_list();
715     primary_id = Shell::Get()
716                      ->display_configuration_controller()
717                      ->GetPrimaryMirroringDisplayForUnifiedDesktop()
718                      .id();
719   }
720 
721   for (const display::Display& display : displays)
722     info_list.emplace_back(GetDisplayUnitInfo(display, primary_id));
723   std::move(callback).Run(std::move(info_list));
724 }
725 
SetDisplayProperties(const std::string & id,mojom::DisplayConfigPropertiesPtr properties,mojom::DisplayConfigSource source,SetDisplayPropertiesCallback callback)726 void CrosDisplayConfig::SetDisplayProperties(
727     const std::string& id,
728     mojom::DisplayConfigPropertiesPtr properties,
729     mojom::DisplayConfigSource source,
730     SetDisplayPropertiesCallback callback) {
731   const display::Display display = GetDisplay(id);
732   mojom::DisplayConfigResult result =
733       ValidateDisplayProperties(*properties, display);
734   if (result != mojom::DisplayConfigResult::kSuccess) {
735     std::move(callback).Run(result);
736     return;
737   }
738 
739   display::DisplayManager* display_manager = GetDisplayManager();
740   DisplayConfigurationController* display_configuration_controller =
741       Shell::Get()->display_configuration_controller();
742   const display::Display& primary =
743       display::Screen::GetScreen()->GetPrimaryDisplay();
744 
745   if (properties->set_primary && display.id() != primary.id()) {
746     display_configuration_controller->SetPrimaryDisplayId(
747         display.id(), false /* don't throttle */);
748   }
749 
750   if (properties->overscan)
751     display_manager->SetOverscanInsets(display.id(), *properties->overscan);
752 
753   if (properties->rotation) {
754     const mojom::DisplayRotationOptions rotation_options =
755         properties->rotation->rotation;
756     const bool is_in_tablet_physical_state =
757         Shell::Get()->tablet_mode_controller()->is_in_tablet_physical_state();
758     auto* screen_orientation_controller =
759         Shell::Get()->screen_orientation_controller();
760     const bool auto_rotate_requested =
761         rotation_options == mojom::DisplayRotationOptions::kAutoRotate;
762     if (auto_rotate_requested && !is_in_tablet_physical_state) {
763       LOG(ERROR) << "Auto-rotate is supported only when the device is in "
764                  << "physical tablet state. This will be treated as a request "
765                  << " to set the display rotation to 0 degrees.";
766     }
767 
768     display::Display::Rotation rotation =
769         DisplayRotationFromRotationOptions(properties->rotation->rotation);
770     if (is_in_tablet_physical_state) {
771       if (auto_rotate_requested) {
772         if (screen_orientation_controller->user_rotation_locked())
773           screen_orientation_controller->ToggleUserRotationLock();
774       } else {
775         screen_orientation_controller->SetLockToRotation(rotation);
776       }
777     } else {
778       display_configuration_controller->SetDisplayRotation(
779           display.id(), rotation, display::Display::RotationSource::USER);
780     }
781   }
782 
783   if (properties->bounds_origin &&
784       *properties->bounds_origin != display.bounds().origin()) {
785     gfx::Rect display_bounds = display.bounds();
786     display_bounds.Offset(
787         properties->bounds_origin->x() - display.bounds().x(),
788         properties->bounds_origin->y() - display.bounds().y());
789     SetDisplayLayoutFromBounds(primary.bounds(), primary.id(), display_bounds,
790                                display.id());
791   }
792 
793   if (properties->display_zoom_factor > 0) {
794     display_manager->UpdateZoomFactor(display.id(),
795                                       properties->display_zoom_factor);
796   }
797 
798   // Set the display mode. Note: if this returns an error, other properties
799   // will have already been applied. TODO(stevenjb): Validate the display mode
800   // before applying any properties.
801   if (properties->display_mode) {
802     result = SetDisplayMode(display.id(), *properties->display_mode, source);
803     if (result != mojom::DisplayConfigResult::kSuccess) {
804       std::move(callback).Run(result);
805       return;
806     }
807   }
808 
809   std::move(callback).Run(mojom::DisplayConfigResult::kSuccess);
810 }
811 
SetUnifiedDesktopEnabled(bool enabled)812 void CrosDisplayConfig::SetUnifiedDesktopEnabled(bool enabled) {
813   GetDisplayManager()->SetUnifiedDesktopEnabled(enabled);
814 }
815 
OverscanCalibration(const std::string & display_id,mojom::DisplayConfigOperation op,const base::Optional<gfx::Insets> & delta,OverscanCalibrationCallback callback)816 void CrosDisplayConfig::OverscanCalibration(
817     const std::string& display_id,
818     mojom::DisplayConfigOperation op,
819     const base::Optional<gfx::Insets>& delta,
820     OverscanCalibrationCallback callback) {
821   display::Display display = GetDisplay(display_id);
822   if (display.id() == display::kInvalidDisplayId) {
823     std::move(callback).Run(mojom::DisplayConfigResult::kInvalidDisplayIdError);
824     return;
825   }
826 
827   OverscanCalibrator* calibrator = GetOverscanCalibrator(display_id);
828   if (!calibrator && op != mojom::DisplayConfigOperation::kStart) {
829     LOG(ERROR) << "Calibrator does not exist for op=" << op;
830     std::move(callback).Run(
831         mojom::DisplayConfigResult::kCalibrationNotAvailableError);
832     return;
833   }
834   switch (op) {
835     case mojom::DisplayConfigOperation::kStart: {
836       DVLOG(1) << "OverscanCalibrationStart: " << display_id;
837       gfx::Insets insets =
838           Shell::Get()->window_tree_host_manager()->GetOverscanInsets(
839               display.id());
840       if (calibrator)
841         DVLOG(1) << "Replacing existing calibrator for id: " << display_id;
842       overscan_calibrators_[display_id] =
843           std::make_unique<OverscanCalibrator>(display, insets);
844       break;
845     }
846     case mojom::DisplayConfigOperation::kAdjust:
847       DVLOG(1) << "OverscanCalibrationAdjust: " << display_id;
848       if (!delta) {
849         LOG(ERROR) << "Delta not provided for for adjust: " << display_id;
850         std::move(callback).Run(
851             mojom::DisplayConfigResult::kCalibrationFailedError);
852         return;
853       }
854       calibrator->UpdateInsets(calibrator->insets() + *delta);
855       break;
856     case mojom::DisplayConfigOperation::kReset:
857       DVLOG(1) << "OverscanCalibrationReset: " << display_id;
858       calibrator->Reset();
859       break;
860     case mojom::DisplayConfigOperation::kComplete:
861       DVLOG(1) << "OverscanCalibrationComplete: " << display_id;
862       calibrator->Commit();
863       overscan_calibrators_[display_id].reset();
864       break;
865     case mojom::DisplayConfigOperation::kShowNative:
866       LOG(ERROR) << "Operation not supported: " << op;
867       std::move(callback).Run(
868           mojom::DisplayConfigResult::kInvalidOperationError);
869       return;
870       return;
871   }
872   std::move(callback).Run(mojom::DisplayConfigResult::kSuccess);
873 }
874 
TouchCalibration(const std::string & display_id,mojom::DisplayConfigOperation op,mojom::TouchCalibrationPtr calibration,TouchCalibrationCallback callback)875 void CrosDisplayConfig::TouchCalibration(const std::string& display_id,
876                                          mojom::DisplayConfigOperation op,
877                                          mojom::TouchCalibrationPtr calibration,
878                                          TouchCalibrationCallback callback) {
879   display::Display display = GetDisplay(display_id);
880   if (display.id() == display::kInvalidDisplayId) {
881     std::move(callback).Run(mojom::DisplayConfigResult::kInvalidDisplayIdError);
882     return;
883   }
884   if (display.IsInternal()) {
885     LOG(ERROR) << "Internal display cannot be calibrated for touch: "
886                << display_id;
887     std::move(callback).Run(
888         mojom::DisplayConfigResult::kCalibrationNotAvailableError);
889     return;
890   }
891   if (!display::HasExternalTouchscreenDevice()) {
892     LOG(ERROR)
893         << "Touch calibration called with no external touch screen device.";
894     std::move(callback).Run(
895         mojom::DisplayConfigResult::kCalibrationNotAvailableError);
896     return;
897   }
898 
899   if (op == mojom::DisplayConfigOperation::kStart ||
900       op == mojom::DisplayConfigOperation::kShowNative) {
901     if (touch_calibrator_ && touch_calibrator_->IsCalibrating()) {
902       LOG(ERROR) << "Touch calibration already active.";
903       std::move(callback).Run(
904           mojom::DisplayConfigResult::kCalibrationInProgressError);
905       return;
906     }
907     if (!touch_calibrator_)
908       touch_calibrator_ = std::make_unique<TouchCalibratorController>();
909     if (op == mojom::DisplayConfigOperation::kShowNative) {
910       // For native calibration, |callback| is not run until calibration
911       // completes.
912       touch_calibrator_->StartCalibration(
913           display, /*is_custom_calibration=*/false,
914           base::BindOnce(
915               [](TouchCalibrationCallback callback, bool result) {
916                 std::move(callback).Run(
917                     result
918                         ? mojom::DisplayConfigResult::kSuccess
919                         : mojom::DisplayConfigResult::kCalibrationFailedError);
920               },
921               std::move(callback)));
922       return;
923     }
924     // For custom calibration, start calibration and run |callback| now.
925     touch_calibrator_->StartCalibration(display, /*is_custom_calibration=*/true,
926                                         base::OnceCallback<void(bool)>());
927     std::move(callback).Run(mojom::DisplayConfigResult::kSuccess);
928     return;
929   }
930 
931   if (op == mojom::DisplayConfigOperation::kReset) {
932     Shell::Get()->display_manager()->ClearTouchCalibrationData(display.id(),
933                                                                base::nullopt);
934     std::move(callback).Run(mojom::DisplayConfigResult::kSuccess);
935     return;
936   }
937 
938   if (op != mojom::DisplayConfigOperation::kComplete) {
939     LOG(ERROR) << "Unknown operation: " << op;
940     std::move(callback).Run(
941         mojom::DisplayConfigResult::kCalibrationNotStartedError);
942     return;
943   }
944 
945   if (!touch_calibrator_) {
946     LOG(ERROR) << "Touch calibration not active.";
947     std::move(callback).Run(
948         mojom::DisplayConfigResult::kCalibrationNotStartedError);
949     return;
950   }
951 
952   if (!calibration || calibration->pairs.size() != 4) {
953     LOG(ERROR) << "Touch calibration requires four calibration pairs.";
954     std::move(callback).Run(
955         mojom::DisplayConfigResult::kCalibrationInvalidDataError);
956     return;
957   }
958 
959   Shell::Get()->touch_transformer_controller()->SetForCalibration(false);
960 
961   display::TouchCalibrationData::CalibrationPointPairQuad calibration_points;
962   calibration_points[0] = GetCalibrationPair(*calibration->pairs[0]);
963   calibration_points[1] = GetCalibrationPair(*calibration->pairs[1]);
964   calibration_points[2] = GetCalibrationPair(*calibration->pairs[2]);
965   calibration_points[3] = GetCalibrationPair(*calibration->pairs[3]);
966 
967   gfx::Size bounds = calibration->bounds;
968   for (size_t row = 0; row < calibration_points.size(); row++) {
969     // Coordinates for display and touch point cannot be negative.
970     if (calibration_points[row].first.x() < 0 ||
971         calibration_points[row].first.y() < 0 ||
972         calibration_points[row].second.x() < 0 ||
973         calibration_points[row].second.y() < 0) {
974       LOG(ERROR)
975           << "Display points and touch points cannot have negative coordinates";
976       touch_calibrator_->StopCalibrationAndResetParams();
977       std::move(callback).Run(
978           mojom::DisplayConfigResult::kCalibrationInvalidDataError);
979       return;
980     }
981     // Coordinates for display points cannot be greater than the screen
982     // bounds.
983     if (calibration_points[row].first.x() > bounds.width() ||
984         calibration_points[row].first.y() > bounds.height()) {
985       LOG(ERROR) << "Display point coordinates cannot be more than size of the "
986                     "display.";
987       touch_calibrator_->StopCalibrationAndResetParams();
988       std::move(callback).Run(
989           mojom::DisplayConfigResult::kCalibrationInvalidDataError);
990       return;
991     }
992   }
993 
994   touch_calibrator_->CompleteCalibration(calibration_points, bounds);
995   std::move(callback).Run(mojom::DisplayConfigResult::kSuccess);
996 }
997 
GetOverscanCalibrator(const std::string & id)998 OverscanCalibrator* CrosDisplayConfig::GetOverscanCalibrator(
999     const std::string& id) {
1000   auto iter = overscan_calibrators_.find(id);
1001   return iter == overscan_calibrators_.end() ? nullptr : iter->second.get();
1002 }
1003 
HighlightDisplay(int64_t display_id)1004 void CrosDisplayConfig::HighlightDisplay(int64_t display_id) {
1005   DCHECK(base::FeatureList::IsEnabled(features::kDisplayIdentification));
1006 
1007   Shell::Get()->display_highlight_controller()->SetHighlightedDisplay(
1008       display_id);
1009 }
1010 
DragDisplayDelta(int64_t display_id,int32_t delta_x,int32_t delta_y)1011 void CrosDisplayConfig::DragDisplayDelta(int64_t display_id,
1012                                          int32_t delta_x,
1013                                          int32_t delta_y) {
1014   DCHECK(features::IsDisplayAlignmentAssistanceEnabled());
1015   Shell::Get()->display_alignment_controller()->DisplayDragged(
1016       display_id, delta_x, delta_y);
1017 }
1018 
1019 }  // namespace ash
1020