Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(197)

Side by Side Diff: ash/wm/maximize_mode/maximize_mode_controller.cc

Issue 2118643002: Move MaximizeModeController into ash/common (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: doh Created 4 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2014 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/wm/maximize_mode/maximize_mode_controller.h"
6
7 #include <utility>
8
9 #include "ash/accelerators/accelerator_controller.h"
10 #include "ash/accelerators/accelerator_table.h"
11 #include "ash/common/ash_switches.h"
12 #include "ash/common/wm/maximize_mode/maximize_mode_window_manager.h"
13 #include "ash/common/wm_shell.h"
14 #include "ash/display/display_manager.h"
15 #include "ash/shell.h"
16 #include "ash/wm/maximize_mode/scoped_disable_internal_mouse_and_keyboard.h"
17 #include "base/command_line.h"
18 #include "base/metrics/histogram.h"
19 #include "base/time/default_tick_clock.h"
20 #include "base/time/tick_clock.h"
21 #include "ui/base/accelerators/accelerator.h"
22 #include "ui/display/display.h"
23 #include "ui/events/event.h"
24 #include "ui/events/keycodes/keyboard_codes.h"
25 #include "ui/gfx/geometry/vector3d_f.h"
26
27 #if defined(USE_X11)
28 #include "ash/wm/maximize_mode/scoped_disable_internal_mouse_and_keyboard_x11.h"
29 #endif
30
31 #if defined(USE_OZONE)
32 #include "ash/wm/maximize_mode/scoped_disable_internal_mouse_and_keyboard_ozone. h"
33 #endif
34
35 #if defined(OS_CHROMEOS)
36 #include "chromeos/dbus/dbus_thread_manager.h"
37 #include "ui/chromeos/accelerometer/accelerometer_util.h"
38 #endif // OS_CHROMEOS
39
40 namespace ash {
41
42 namespace {
43
44 #if defined(OS_CHROMEOS)
45 // The hinge angle at which to enter maximize mode.
46 const float kEnterMaximizeModeAngle = 200.0f;
47
48 // The angle at which to exit maximize mode, this is specifically less than the
49 // angle to enter maximize mode to prevent rapid toggling when near the angle.
50 const float kExitMaximizeModeAngle = 160.0f;
51
52 // Defines a range for which accelerometer readings are considered accurate.
53 // When the lid is near open (or near closed) the accelerometer readings may be
54 // inaccurate and a lid that is fully open may appear to be near closed (and
55 // vice versa).
56 const float kMinStableAngle = 20.0f;
57 const float kMaxStableAngle = 340.0f;
58 #endif // OS_CHROMEOS
59
60 // The time duration to consider the lid to be recently opened.
61 // This is used to prevent entering maximize mode if an erroneous accelerometer
62 // reading makes the lid appear to be fully open when the user is opening the
63 // lid from a closed position.
64 const int kLidRecentlyOpenedDurationSeconds = 2;
65
66 #if defined(OS_CHROMEOS)
67 // When the device approaches vertical orientation (i.e. portrait orientation)
68 // the accelerometers for the base and lid approach the same values (i.e.
69 // gravity pointing in the direction of the hinge). When this happens abrupt
70 // small acceleration perpendicular to the hinge can lead to incorrect hinge
71 // angle calculations. To prevent this the accelerometer updates will be
72 // smoothed over time in order to reduce this noise.
73 // This is the minimum acceleration parallel to the hinge under which to begin
74 // smoothing in m/s^2.
75 const float kHingeVerticalSmoothingStart = 7.0f;
76 // This is the maximum acceleration parallel to the hinge under which smoothing
77 // will incorporate new acceleration values, in m/s^2.
78 const float kHingeVerticalSmoothingMaximum = 9.5f;
79
80 // The maximum deviation between the magnitude of the two accelerometers under
81 // which to detect hinge angle in m/s^2. These accelerometers are attached to
82 // the same physical device and so should be under the same acceleration.
83 const float kNoisyMagnitudeDeviation = 1.0f;
84
85 // The angle between chromeos::AccelerometerReadings are considered stable if
86 // their magnitudes do not differ greatly. This returns false if the deviation
87 // between the screen and keyboard accelerometers is too high.
88 bool IsAngleBetweenAccelerometerReadingsStable(
89 const chromeos::AccelerometerUpdate& update) {
90 return std::abs(
91 ui::ConvertAccelerometerReadingToVector3dF(
92 update.get(chromeos::ACCELEROMETER_SOURCE_ATTACHED_KEYBOARD))
93 .Length() -
94 ui::ConvertAccelerometerReadingToVector3dF(
95 update.get(chromeos::ACCELEROMETER_SOURCE_SCREEN))
96 .Length()) <= kNoisyMagnitudeDeviation;
97 }
98 #endif // OS_CHROMEOS
99
100 } // namespace
101
102 MaximizeModeController::MaximizeModeController()
103 : have_seen_accelerometer_data_(false),
104 touchview_usage_interval_start_time_(base::Time::Now()),
105 tick_clock_(new base::DefaultTickClock()),
106 #if defined(OS_CHROMEOS)
107 tablet_mode_switch_is_on_(false),
108 #endif
109 lid_is_closed_(false) {
110 WmShell::Get()->AddShellObserver(this);
111 WmShell::Get()->RecordUserMetricsAction(UMA_MAXIMIZE_MODE_INITIALLY_DISABLED);
112
113 #if defined(OS_CHROMEOS)
114 // TODO(jonross): Do not create MaximizeModeController if the flag is
115 // unavailable. This will require refactoring
116 // IsMaximizeModeWindowManagerEnabled to check for the existance of the
117 // controller.
118 if (base::CommandLine::ForCurrentProcess()->HasSwitch(
119 switches::kAshEnableTouchView)) {
120 chromeos::AccelerometerReader::GetInstance()->AddObserver(this);
121 Shell::GetInstance()->window_tree_host_manager()->AddObserver(this);
122 }
123 chromeos::DBusThreadManager::Get()->GetPowerManagerClient()->AddObserver(
124 this);
125 #endif // OS_CHROMEOS
126 }
127
128 MaximizeModeController::~MaximizeModeController() {
129 WmShell::Get()->RemoveShellObserver(this);
130 #if defined(OS_CHROMEOS)
131 if (base::CommandLine::ForCurrentProcess()->HasSwitch(
132 switches::kAshEnableTouchView)) {
133 chromeos::AccelerometerReader::GetInstance()->RemoveObserver(this);
134 Shell::GetInstance()->window_tree_host_manager()->RemoveObserver(this);
135 }
136 chromeos::DBusThreadManager::Get()->GetPowerManagerClient()->RemoveObserver(
137 this);
138 #endif // OS_CHROMEOS
139 }
140
141 bool MaximizeModeController::CanEnterMaximizeMode() {
142 // If we have ever seen accelerometer data, then HandleHingeRotation may
143 // trigger maximize mode at some point in the future.
144 // The --enable-touch-view-testing switch can also mean that we may enter
145 // maximize mode.
146 // TODO(mgiuca): This can result in false positives, as it returns true for
147 // any device with an accelerometer. Have TouchView-enabled devices explicitly
148 // set a flag, and change this implementation to simply return true iff the
149 // flag is present (http://crbug.com/457445).
150 return have_seen_accelerometer_data_ ||
151 base::CommandLine::ForCurrentProcess()->HasSwitch(
152 switches::kAshEnableTouchViewTesting);
153 }
154
155 // TODO(jcliang): Hide or remove EnableMaximizeModeWindowManager
156 // (http://crbug.com/620241).
157 void MaximizeModeController::EnableMaximizeModeWindowManager(
158 bool should_enable) {
159 bool is_enabled = !!maximize_mode_window_manager_.get();
160 if (should_enable == is_enabled)
161 return;
162
163 Shell* shell = Shell::GetInstance();
164
165 if (should_enable) {
166 maximize_mode_window_manager_.reset(new MaximizeModeWindowManager());
167 // TODO(jonross): Move the maximize mode notifications from ShellObserver
168 // to MaximizeModeController::Observer
169 WmShell::Get()->RecordUserMetricsAction(UMA_MAXIMIZE_MODE_ENABLED);
170 shell->OnMaximizeModeStarted();
171 } else {
172 maximize_mode_window_manager_.reset();
173 WmShell::Get()->RecordUserMetricsAction(UMA_MAXIMIZE_MODE_DISABLED);
174 shell->OnMaximizeModeEnded();
175 }
176 }
177
178 bool MaximizeModeController::IsMaximizeModeWindowManagerEnabled() const {
179 return maximize_mode_window_manager_.get() != NULL;
180 }
181
182 void MaximizeModeController::AddWindow(WmWindow* window) {
183 if (IsMaximizeModeWindowManagerEnabled())
184 maximize_mode_window_manager_->AddWindow(window);
185 }
186
187 #if defined(OS_CHROMEOS)
188 void MaximizeModeController::OnAccelerometerUpdated(
189 scoped_refptr<const chromeos::AccelerometerUpdate> update) {
190 bool first_accelerometer_update = !have_seen_accelerometer_data_;
191 have_seen_accelerometer_data_ = true;
192
193 if (!update->has(chromeos::ACCELEROMETER_SOURCE_SCREEN))
194 return;
195
196 if (!display::Display::HasInternalDisplay())
197 return;
198
199 if (!Shell::GetInstance()->display_manager()->IsActiveDisplayId(
200 display::Display::InternalDisplayId())) {
201 return;
202 }
203
204 // Whether or not we enter maximize mode affects whether we handle screen
205 // rotation, so determine whether to enter maximize mode first.
206 if (!update->has(chromeos::ACCELEROMETER_SOURCE_ATTACHED_KEYBOARD)) {
207 if (first_accelerometer_update)
208 EnterMaximizeMode();
209 } else if (ui::IsAccelerometerReadingStable(
210 *update, chromeos::ACCELEROMETER_SOURCE_SCREEN) &&
211 ui::IsAccelerometerReadingStable(
212 *update, chromeos::ACCELEROMETER_SOURCE_ATTACHED_KEYBOARD) &&
213 IsAngleBetweenAccelerometerReadingsStable(*update)) {
214 // update.has(chromeos::ACCELEROMETER_SOURCE_ATTACHED_KEYBOARD)
215 // Ignore the reading if it appears unstable. The reading is considered
216 // unstable if it deviates too much from gravity and/or the magnitude of the
217 // reading from the lid differs too much from the reading from the base.
218 HandleHingeRotation(update);
219 }
220 }
221
222 void MaximizeModeController::LidEventReceived(bool open,
223 const base::TimeTicks& time) {
224 if (open)
225 last_lid_open_time_ = time;
226 lid_is_closed_ = !open;
227 LeaveMaximizeMode();
228 }
229
230 void MaximizeModeController::TabletModeEventReceived(
231 bool on,
232 const base::TimeTicks& time) {
233 tablet_mode_switch_is_on_ = on;
234 if (on && !IsMaximizeModeWindowManagerEnabled())
235 EnterMaximizeMode();
236 }
237
238 void MaximizeModeController::SuspendImminent() {
239 // The system is about to suspend, so record TouchView usage interval metrics
240 // based on whether TouchView mode is currently active.
241 RecordTouchViewUsageInterval(CurrentTouchViewIntervalType());
242 }
243
244 void MaximizeModeController::SuspendDone(
245 const base::TimeDelta& sleep_duration) {
246 // We do not want TouchView usage metrics to include time spent in suspend.
247 touchview_usage_interval_start_time_ = base::Time::Now();
248 }
249
250 void MaximizeModeController::HandleHingeRotation(
251 scoped_refptr<const chromeos::AccelerometerUpdate> update) {
252 static const gfx::Vector3dF hinge_vector(1.0f, 0.0f, 0.0f);
253 gfx::Vector3dF base_reading(ui::ConvertAccelerometerReadingToVector3dF(
254 update->get(chromeos::ACCELEROMETER_SOURCE_ATTACHED_KEYBOARD)));
255 gfx::Vector3dF lid_reading(ui::ConvertAccelerometerReadingToVector3dF(
256 update->get(chromeos::ACCELEROMETER_SOURCE_SCREEN)));
257
258 // As the hinge approaches a vertical angle, the base and lid accelerometers
259 // approach the same values making any angle calculations highly inaccurate.
260 // Smooth out instantaneous acceleration when nearly vertical to increase
261 // accuracy.
262 float largest_hinge_acceleration =
263 std::max(std::abs(base_reading.x()), std::abs(lid_reading.x()));
264 float smoothing_ratio =
265 std::max(0.0f, std::min(1.0f, (largest_hinge_acceleration -
266 kHingeVerticalSmoothingStart) /
267 (kHingeVerticalSmoothingMaximum -
268 kHingeVerticalSmoothingStart)));
269
270 // We cannot trust the computed lid angle when the device is held vertically.
271 bool is_angle_reliable =
272 largest_hinge_acceleration <= kHingeVerticalSmoothingMaximum;
273
274 base_smoothed_.Scale(smoothing_ratio);
275 base_reading.Scale(1.0f - smoothing_ratio);
276 base_smoothed_.Add(base_reading);
277
278 lid_smoothed_.Scale(smoothing_ratio);
279 lid_reading.Scale(1.0f - smoothing_ratio);
280 lid_smoothed_.Add(lid_reading);
281
282 if (tablet_mode_switch_is_on_)
283 return;
284
285 // Ignore the component of acceleration parallel to the hinge for the purposes
286 // of hinge angle calculation.
287 gfx::Vector3dF base_flattened(base_smoothed_);
288 gfx::Vector3dF lid_flattened(lid_smoothed_);
289 base_flattened.set_x(0.0f);
290 lid_flattened.set_x(0.0f);
291
292 // Compute the angle between the base and the lid.
293 float lid_angle = 180.0f - gfx::ClockwiseAngleBetweenVectorsInDegrees(
294 base_flattened, lid_flattened, hinge_vector);
295 if (lid_angle < 0.0f)
296 lid_angle += 360.0f;
297
298 bool is_angle_stable = is_angle_reliable && lid_angle >= kMinStableAngle &&
299 lid_angle <= kMaxStableAngle;
300
301 // Clear the last_lid_open_time_ for a stable reading so that there is less
302 // chance of a delay if the lid is moved from the close state to the fully
303 // open state very quickly.
304 if (is_angle_stable)
305 last_lid_open_time_ = base::TimeTicks();
306
307 // Toggle maximize mode on or off when corresponding thresholds are passed.
308 if (IsMaximizeModeWindowManagerEnabled() && is_angle_stable &&
309 lid_angle <= kExitMaximizeModeAngle) {
310 LeaveMaximizeMode();
311 } else if (!IsMaximizeModeWindowManagerEnabled() && !lid_is_closed_ &&
312 lid_angle >= kEnterMaximizeModeAngle &&
313 (is_angle_stable || !WasLidOpenedRecently())) {
314 EnterMaximizeMode();
315 }
316 }
317 #endif // OS_CHROMEOS
318
319 void MaximizeModeController::EnterMaximizeMode() {
320 // Always reset first to avoid creation before destruction of a previous
321 // object.
322 event_blocker_.reset();
323 #if defined(USE_X11)
324 event_blocker_.reset(new ScopedDisableInternalMouseAndKeyboardX11);
325 #elif defined(USE_OZONE)
326 event_blocker_.reset(new ScopedDisableInternalMouseAndKeyboardOzone);
327 #endif
328
329 if (base::CommandLine::ForCurrentProcess()->HasSwitch(
330 switches::kAshEnableTouchViewTesting)) {
331 // We don't let accelerometer updates interfere with the maximize mode
332 // status as set by the touch-view-testing keyboard shortcut.
333 return;
334 }
335
336 if (IsMaximizeModeWindowManagerEnabled())
337 return;
338 EnableMaximizeModeWindowManager(true);
339 }
340
341 void MaximizeModeController::LeaveMaximizeMode() {
342 event_blocker_.reset();
343
344 if (base::CommandLine::ForCurrentProcess()->HasSwitch(
345 switches::kAshEnableTouchViewTesting)) {
346 // We don't let accelerometer updates interfere with the maximize mode
347 // status as set by the touch-view-testing keyboard shortcut.
348 return;
349 }
350
351 if (!IsMaximizeModeWindowManagerEnabled())
352 return;
353 EnableMaximizeModeWindowManager(false);
354 }
355
356 // Called after maximize mode has started, windows might still animate though.
357 void MaximizeModeController::OnMaximizeModeStarted() {
358 RecordTouchViewUsageInterval(TOUCH_VIEW_INTERVAL_INACTIVE);
359 }
360
361 // Called after maximize mode has ended, windows might still be returning to
362 // their original position.
363 void MaximizeModeController::OnMaximizeModeEnded() {
364 RecordTouchViewUsageInterval(TOUCH_VIEW_INTERVAL_ACTIVE);
365 }
366
367 void MaximizeModeController::OnDisplayConfigurationChanged() {
368 if (!display::Display::HasInternalDisplay() ||
369 !Shell::GetInstance()->display_manager()->IsActiveDisplayId(
370 display::Display::InternalDisplayId())) {
371 LeaveMaximizeMode();
372 }
373 }
374
375 void MaximizeModeController::RecordTouchViewUsageInterval(
376 TouchViewIntervalType type) {
377 if (!CanEnterMaximizeMode())
378 return;
379
380 base::Time current_time = base::Time::Now();
381 base::TimeDelta delta = current_time - touchview_usage_interval_start_time_;
382 switch (type) {
383 case TOUCH_VIEW_INTERVAL_INACTIVE:
384 UMA_HISTOGRAM_LONG_TIMES("Ash.TouchView.TouchViewInactive", delta);
385 total_non_touchview_time_ += delta;
386 break;
387 case TOUCH_VIEW_INTERVAL_ACTIVE:
388 UMA_HISTOGRAM_LONG_TIMES("Ash.TouchView.TouchViewActive", delta);
389 total_touchview_time_ += delta;
390 break;
391 }
392
393 touchview_usage_interval_start_time_ = current_time;
394 }
395
396 MaximizeModeController::TouchViewIntervalType
397 MaximizeModeController::CurrentTouchViewIntervalType() {
398 if (IsMaximizeModeWindowManagerEnabled())
399 return TOUCH_VIEW_INTERVAL_ACTIVE;
400 return TOUCH_VIEW_INTERVAL_INACTIVE;
401 }
402
403 void MaximizeModeController::OnAppTerminating() {
404 // The system is about to shut down, so record TouchView usage interval
405 // metrics based on whether TouchView mode is currently active.
406 RecordTouchViewUsageInterval(CurrentTouchViewIntervalType());
407
408 if (CanEnterMaximizeMode()) {
409 UMA_HISTOGRAM_CUSTOM_COUNTS("Ash.TouchView.TouchViewActiveTotal",
410 total_touchview_time_.InMinutes(), 1,
411 base::TimeDelta::FromDays(7).InMinutes(), 50);
412 UMA_HISTOGRAM_CUSTOM_COUNTS("Ash.TouchView.TouchViewInactiveTotal",
413 total_non_touchview_time_.InMinutes(), 1,
414 base::TimeDelta::FromDays(7).InMinutes(), 50);
415 base::TimeDelta total_runtime =
416 total_touchview_time_ + total_non_touchview_time_;
417 if (total_runtime.InSeconds() > 0) {
418 UMA_HISTOGRAM_PERCENTAGE(
419 "Ash.TouchView.TouchViewActivePercentage",
420 100 * total_touchview_time_.InSeconds() / total_runtime.InSeconds());
421 }
422 }
423 }
424
425 bool MaximizeModeController::WasLidOpenedRecently() const {
426 if (last_lid_open_time_.is_null())
427 return false;
428
429 base::TimeTicks now = tick_clock_->NowTicks();
430 DCHECK(now >= last_lid_open_time_);
431 base::TimeDelta elapsed_time = now - last_lid_open_time_;
432 return elapsed_time.InSeconds() <= kLidRecentlyOpenedDurationSeconds;
433 }
434
435 void MaximizeModeController::SetTickClockForTest(
436 std::unique_ptr<base::TickClock> tick_clock) {
437 DCHECK(tick_clock_);
438 tick_clock_ = std::move(tick_clock);
439 }
440
441 } // namespace ash
OLDNEW
« no previous file with comments | « ash/wm/maximize_mode/maximize_mode_controller.h ('k') | ash/wm/maximize_mode/maximize_mode_controller_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698