1 // Copyright 2013 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/resolution_notification_controller.h"
6
7 #include <utility>
8
9 #include "ash/display/display_change_dialog.h"
10 #include "ash/display/display_util.h"
11 #include "ash/public/cpp/ash_features.h"
12 #include "ash/public/cpp/notification_utils.h"
13 #include "ash/resources/vector_icons/vector_icons.h"
14 #include "ash/session/session_controller_impl.h"
15 #include "ash/shell.h"
16 #include "ash/strings/grit/ash_strings.h"
17 #include "ash/system/screen_layout_observer.h"
18 #include "base/strings/utf_string_conversions.h"
19 #include "ui/base/l10n/l10n_util.h"
20 #include "ui/display/display.h"
21 #include "ui/display/display_features.h"
22 #include "ui/display/manager/display_manager.h"
23 #include "ui/display/manager/managed_display_info.h"
24 #include "ui/display/screen.h"
25
26 namespace ash {
27
28 struct ResolutionNotificationController::ResolutionChangeInfo {
29 ResolutionChangeInfo(int64_t display_id,
30 const display::ManagedDisplayMode& old_resolution,
31 const display::ManagedDisplayMode& new_resolution,
32 base::OnceClosure accept_callback);
33 ~ResolutionChangeInfo();
34
35 // The id of the display where the resolution change happens.
36 const int64_t display_id;
37
38 // The resolution before the change.
39 display::ManagedDisplayMode old_resolution;
40
41 // The requested resolution. Note that this may be different from
42 // |current_resolution| which is the actual resolution set.
43 display::ManagedDisplayMode new_resolution;
44
45 // The actual resolution after the change.
46 display::ManagedDisplayMode current_resolution;
47
48 // The callback when accept is chosen.
49 base::OnceClosure accept_callback;
50
51 private:
52 DISALLOW_COPY_AND_ASSIGN(ResolutionChangeInfo);
53 };
54
ResolutionChangeInfo(int64_t display_id,const display::ManagedDisplayMode & old_resolution,const display::ManagedDisplayMode & new_resolution,base::OnceClosure accept_callback)55 ResolutionNotificationController::ResolutionChangeInfo::ResolutionChangeInfo(
56 int64_t display_id,
57 const display::ManagedDisplayMode& old_resolution,
58 const display::ManagedDisplayMode& new_resolution,
59 base::OnceClosure accept_callback)
60 : display_id(display_id),
61 old_resolution(old_resolution),
62 new_resolution(new_resolution),
63 accept_callback(std::move(accept_callback)) {}
64
65 ResolutionNotificationController::ResolutionChangeInfo::
66 ~ResolutionChangeInfo() = default;
67
ResolutionNotificationController()68 ResolutionNotificationController::ResolutionNotificationController() {
69 Shell::Get()->window_tree_host_manager()->AddObserver(this);
70 display::Screen::GetScreen()->AddObserver(this);
71 }
72
~ResolutionNotificationController()73 ResolutionNotificationController::~ResolutionNotificationController() {
74 Shell::Get()->window_tree_host_manager()->RemoveObserver(this);
75 display::Screen::GetScreen()->RemoveObserver(this);
76 }
77
PrepareNotificationAndSetDisplayMode(int64_t display_id,const display::ManagedDisplayMode & old_resolution,const display::ManagedDisplayMode & new_resolution,mojom::DisplayConfigSource source,base::OnceClosure accept_callback)78 bool ResolutionNotificationController::PrepareNotificationAndSetDisplayMode(
79 int64_t display_id,
80 const display::ManagedDisplayMode& old_resolution,
81 const display::ManagedDisplayMode& new_resolution,
82 mojom::DisplayConfigSource source,
83 base::OnceClosure accept_callback) {
84 Shell::Get()->screen_layout_observer()->SetDisplayChangedFromSettingsUI(
85 display_id);
86 display::DisplayManager* const display_manager =
87 Shell::Get()->display_manager();
88 if (source == mojom::DisplayConfigSource::kPolicy ||
89 display::Display::IsInternalDisplayId(display_id)) {
90 // We don't show notifications to confirm/revert the resolution change in
91 // the case of an internal display or policy-forced changes.
92 return display_manager->SetDisplayMode(display_id, new_resolution);
93 }
94
95 // If multiple resolution changes are invoked for the same display,
96 // the original resolution for the first resolution change has to be used
97 // instead of the specified |old_resolution|.
98 display::ManagedDisplayMode original_resolution;
99 if (change_info_ && change_info_->display_id == display_id) {
100 DCHECK_EQ(change_info_->new_resolution.size(), old_resolution.size());
101 original_resolution = change_info_->old_resolution;
102 }
103
104 if (change_info_ && change_info_->display_id != display_id) {
105 // Preparing the notification for a new resolution change of another display
106 // before the previous one was accepted. We decided that it's safer to
107 // revert the previous resolution change since the user didn't explicitly
108 // accept it, and we have no way of knowing for sure that it worked.
109 RevertResolutionChange(false /* display_was_removed */);
110 }
111
112 change_info_ = std::make_unique<ResolutionChangeInfo>(
113 display_id, old_resolution, new_resolution, std::move(accept_callback));
114 if (!original_resolution.size().IsEmpty())
115 change_info_->old_resolution = original_resolution;
116
117 if (!display_manager->SetDisplayMode(display_id, new_resolution)) {
118 // Discard the prepared notification data since we failed to set the new
119 // resolution.
120 change_info_.reset();
121 return false;
122 }
123
124 return true;
125 }
126
CreateOrReplaceModalDialog()127 void ResolutionNotificationController::CreateOrReplaceModalDialog() {
128 if (confirmation_dialog_)
129 confirmation_dialog_->GetWidget()->CloseNow();
130
131 if (!change_info_)
132 return;
133
134 const base::string16 display_name =
135 base::UTF8ToUTF16(Shell::Get()->display_manager()->GetDisplayNameForId(
136 change_info_->display_id));
137 const base::string16 actual_display_size =
138 base::UTF8ToUTF16(change_info_->current_resolution.size().ToString());
139 const base::string16 requested_display_size =
140 base::UTF8ToUTF16(change_info_->new_resolution.size().ToString());
141
142 base::string16 dialog_title =
143 l10n_util::GetStringUTF16(IDS_ASH_RESOLUTION_CHANGE_DIALOG_TITLE);
144
145 // Construct the timeout message, leaving a placeholder for the countdown
146 // timer so that the string does not need to be completely rebuilt every
147 // timer tick.
148 constexpr char kTimeoutPlaceHolder[] = "$1";
149
150 base::string16 timeout_message_with_placeholder;
151 if (display::features::IsListAllDisplayModesEnabled()) {
152 dialog_title = l10n_util::GetStringUTF16(
153 IDS_ASH_RESOLUTION_REFRESH_CHANGE_DIALOG_TITLE);
154
155 const base::string16 actual_refresh_rate = ConvertRefreshRateToString16(
156 change_info_->current_resolution.refresh_rate());
157 const base::string16 requested_refresh_rate = ConvertRefreshRateToString16(
158 change_info_->new_resolution.refresh_rate());
159
160 const bool no_fallback = actual_display_size == requested_display_size &&
161 actual_refresh_rate == requested_refresh_rate;
162
163 timeout_message_with_placeholder =
164 no_fallback
165 ? l10n_util::GetStringFUTF16(
166 IDS_ASH_RESOLUTION_REFRESH_CHANGE_DIALOG_CHANGED,
167 display_name, actual_display_size, actual_refresh_rate,
168 base::UTF8ToUTF16(kTimeoutPlaceHolder))
169 : l10n_util::GetStringFUTF16(
170 IDS_ASH_RESOLUTION_REFRESH_CHANGE_DIALOG_FALLBACK,
171 {display_name, requested_display_size, requested_refresh_rate,
172 actual_display_size, actual_refresh_rate,
173 base::UTF8ToUTF16(kTimeoutPlaceHolder)},
174 /*offsets=*/nullptr);
175
176 } else {
177 timeout_message_with_placeholder =
178 actual_display_size == requested_display_size
179 ? l10n_util::GetStringFUTF16(
180 IDS_ASH_RESOLUTION_CHANGE_DIALOG_CHANGED, display_name,
181 actual_display_size, base::UTF8ToUTF16(kTimeoutPlaceHolder))
182 : l10n_util::GetStringFUTF16(
183 IDS_ASH_RESOLUTION_CHANGE_DIALOG_FALLBACK, display_name,
184 requested_display_size, actual_display_size,
185 base::UTF8ToUTF16(kTimeoutPlaceHolder));
186 }
187
188 DisplayChangeDialog* dialog = new DisplayChangeDialog(
189 std::move(dialog_title), std::move(timeout_message_with_placeholder),
190 base::BindOnce(&ResolutionNotificationController::AcceptResolutionChange,
191 weak_factory_.GetWeakPtr()),
192 base::BindOnce(&ResolutionNotificationController::RevertResolutionChange,
193 weak_factory_.GetWeakPtr()));
194 confirmation_dialog_ = dialog->GetWeakPtr();
195 }
196
AcceptResolutionChange()197 void ResolutionNotificationController::AcceptResolutionChange() {
198 if (!change_info_)
199 return;
200 base::OnceClosure callback = std::move(change_info_->accept_callback);
201 change_info_.reset();
202 std::move(callback).Run();
203 }
204
RevertResolutionChange(bool display_was_removed)205 void ResolutionNotificationController::RevertResolutionChange(
206 bool display_was_removed) {
207 if (!change_info_)
208 return;
209 const int64_t display_id = change_info_->display_id;
210 display::ManagedDisplayMode old_resolution = change_info_->old_resolution;
211 change_info_.reset();
212 Shell::Get()->screen_layout_observer()->SetDisplayChangedFromSettingsUI(
213 display_id);
214 if (display_was_removed) {
215 // If display was removed then we are inside the stack of
216 // DisplayManager::UpdateDisplaysWith(), and we need to update the selected
217 // mode of this removed display without reentering again into
218 // UpdateDisplaysWith() because this can cause a crash. crbug.com/709722.
219 Shell::Get()->display_manager()->SetSelectedModeForDisplayId(
220 display_id, old_resolution);
221 } else {
222 Shell::Get()->display_manager()->SetDisplayMode(display_id, old_resolution);
223 }
224 }
225
OnDisplayRemoved(const display::Display & old_display)226 void ResolutionNotificationController::OnDisplayRemoved(
227 const display::Display& old_display) {
228 if (change_info_ && change_info_->display_id == old_display.id()) {
229 if (confirmation_dialog_)
230 confirmation_dialog_->GetWidget()->CloseNow();
231 RevertResolutionChange(true /* display_was_removed */);
232 }
233 }
234
OnDisplayConfigurationChanged()235 void ResolutionNotificationController::OnDisplayConfigurationChanged() {
236 if (!change_info_)
237 return;
238
239 display::ManagedDisplayMode mode;
240 if (Shell::Get()->display_manager()->GetActiveModeForDisplayId(
241 change_info_->display_id, &mode)) {
242 change_info_->current_resolution = mode;
243 }
244
245 CreateOrReplaceModalDialog();
246 }
247
248
249 } // namespace ash
250