Fix translation extraction failure.
[chromium-blink-merge.git] / ash / wm / maximize_mode / maximize_mode_controller.cc
blob883317045edf3cd642a36199801705ec28a91418
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.
5 #include "ash/wm/maximize_mode/maximize_mode_controller.h"
7 #include "ash/accelerators/accelerator_controller.h"
8 #include "ash/accelerators/accelerator_table.h"
9 #include "ash/ash_switches.h"
10 #include "ash/display/display_manager.h"
11 #include "ash/shell.h"
12 #include "ash/wm/maximize_mode/maximize_mode_window_manager.h"
13 #include "ash/wm/maximize_mode/scoped_disable_internal_mouse_and_keyboard.h"
14 #include "base/command_line.h"
15 #include "base/metrics/histogram.h"
16 #include "base/time/default_tick_clock.h"
17 #include "base/time/tick_clock.h"
18 #include "ui/base/accelerators/accelerator.h"
19 #include "ui/events/event.h"
20 #include "ui/events/keycodes/keyboard_codes.h"
21 #include "ui/gfx/geometry/vector3d_f.h"
23 #if defined(USE_X11)
24 #include "ash/wm/maximize_mode/scoped_disable_internal_mouse_and_keyboard_x11.h"
25 #endif
27 #if defined(USE_OZONE)
28 #include "ash/wm/maximize_mode/scoped_disable_internal_mouse_and_keyboard_ozone.h"
29 #endif
31 #if defined(OS_CHROMEOS)
32 #include "chromeos/dbus/dbus_thread_manager.h"
33 #include "ui/chromeos/accelerometer/accelerometer_util.h"
34 #endif // OS_CHROMEOS
36 namespace ash {
38 namespace {
40 #if defined(OS_CHROMEOS)
41 // The hinge angle at which to enter maximize mode.
42 const float kEnterMaximizeModeAngle = 200.0f;
44 // The angle at which to exit maximize mode, this is specifically less than the
45 // angle to enter maximize mode to prevent rapid toggling when near the angle.
46 const float kExitMaximizeModeAngle = 160.0f;
48 // Defines a range for which accelerometer readings are considered accurate.
49 // When the lid is near open (or near closed) the accelerometer readings may be
50 // inaccurate and a lid that is fully open may appear to be near closed (and
51 // vice versa).
52 const float kMinStableAngle = 20.0f;
53 const float kMaxStableAngle = 340.0f;
54 #endif // OS_CHROMEOS
56 // The time duration to consider the lid to be recently opened.
57 // This is used to prevent entering maximize mode if an erroneous accelerometer
58 // reading makes the lid appear to be fully open when the user is opening the
59 // lid from a closed position.
60 const int kLidRecentlyOpenedDurationSeconds = 2;
62 #if defined(OS_CHROMEOS)
63 // When the device approaches vertical orientation (i.e. portrait orientation)
64 // the accelerometers for the base and lid approach the same values (i.e.
65 // gravity pointing in the direction of the hinge). When this happens abrupt
66 // small acceleration perpendicular to the hinge can lead to incorrect hinge
67 // angle calculations. To prevent this the accelerometer updates will be
68 // smoothed over time in order to reduce this noise.
69 // This is the minimum acceleration parallel to the hinge under which to begin
70 // smoothing in m/s^2.
71 const float kHingeVerticalSmoothingStart = 7.0f;
72 // This is the maximum acceleration parallel to the hinge under which smoothing
73 // will incorporate new acceleration values, in m/s^2.
74 const float kHingeVerticalSmoothingMaximum = 9.5f;
76 // The maximum deviation between the magnitude of the two accelerometers under
77 // which to detect hinge angle in m/s^2. These accelerometers are attached to
78 // the same physical device and so should be under the same acceleration.
79 const float kNoisyMagnitudeDeviation = 1.0f;
81 // The angle between chromeos::AccelerometerReadings are considered stable if
82 // their magnitudes do not differ greatly. This returns false if the deviation
83 // between the screen and keyboard accelerometers is too high.
84 bool IsAngleBetweenAccelerometerReadingsStable(
85 const chromeos::AccelerometerUpdate& update) {
86 return std::abs(
87 ui::ConvertAccelerometerReadingToVector3dF(
88 update.get(chromeos::ACCELEROMETER_SOURCE_ATTACHED_KEYBOARD))
89 .Length() -
90 ui::ConvertAccelerometerReadingToVector3dF(
91 update.get(chromeos::ACCELEROMETER_SOURCE_SCREEN)).Length()) <=
92 kNoisyMagnitudeDeviation;
94 #endif // OS_CHROMEOS
96 } // namespace
98 MaximizeModeController::MaximizeModeController()
99 : have_seen_accelerometer_data_(false),
100 lid_open_past_180_(false),
101 touchview_usage_interval_start_time_(base::Time::Now()),
102 tick_clock_(new base::DefaultTickClock()),
103 lid_is_closed_(false) {
104 Shell* shell = Shell::GetInstance();
105 shell->AddShellObserver(this);
106 shell->metrics()->RecordUserMetricsAction(
107 ash::UMA_MAXIMIZE_MODE_INITIALLY_DISABLED);
109 #if defined(OS_CHROMEOS)
110 chromeos::AccelerometerReader::GetInstance()->AddObserver(this);
111 chromeos::DBusThreadManager::Get()->
112 GetPowerManagerClient()->AddObserver(this);
113 #endif // OS_CHROMEOS
116 MaximizeModeController::~MaximizeModeController() {
117 Shell::GetInstance()->RemoveShellObserver(this);
118 #if defined(OS_CHROMEOS)
119 chromeos::AccelerometerReader::GetInstance()->RemoveObserver(this);
120 chromeos::DBusThreadManager::Get()->
121 GetPowerManagerClient()->RemoveObserver(this);
122 #endif // OS_CHROMEOS
125 bool MaximizeModeController::CanEnterMaximizeMode() {
126 // If we have ever seen accelerometer data, then HandleHingeRotation may
127 // trigger maximize mode at some point in the future.
128 // The --enable-touch-view-testing switch can also mean that we may enter
129 // maximize mode.
130 // TODO(mgiuca): This can result in false positives, as it returns true for
131 // any device with an accelerometer. Have TouchView-enabled devices explicitly
132 // set a flag, and change this implementation to simply return true iff the
133 // flag is present (http://crbug.com/457445).
134 return have_seen_accelerometer_data_ ||
135 base::CommandLine::ForCurrentProcess()->HasSwitch(
136 switches::kAshEnableTouchViewTesting);
139 void MaximizeModeController::EnableMaximizeModeWindowManager(
140 bool should_enable) {
141 bool is_enabled = !!maximize_mode_window_manager_.get();
142 if (should_enable == is_enabled)
143 return;
145 Shell* shell = Shell::GetInstance();
147 if (should_enable) {
148 maximize_mode_window_manager_.reset(new MaximizeModeWindowManager());
149 // TODO(jonross): Move the maximize mode notifications from ShellObserver
150 // to MaximizeModeController::Observer
151 shell->metrics()->RecordUserMetricsAction(ash::UMA_MAXIMIZE_MODE_ENABLED);
152 shell->OnMaximizeModeStarted();
153 } else {
154 maximize_mode_window_manager_.reset();
155 shell->metrics()->RecordUserMetricsAction(ash::UMA_MAXIMIZE_MODE_DISABLED);
156 shell->OnMaximizeModeEnded();
160 bool MaximizeModeController::IsMaximizeModeWindowManagerEnabled() const {
161 return maximize_mode_window_manager_.get() != NULL;
164 void MaximizeModeController::AddWindow(aura::Window* window) {
165 if (IsMaximizeModeWindowManagerEnabled())
166 maximize_mode_window_manager_->AddWindow(window);
169 #if defined(OS_CHROMEOS)
170 void MaximizeModeController::OnAccelerometerUpdated(
171 scoped_refptr<const chromeos::AccelerometerUpdate> update) {
172 bool first_accelerometer_update = !have_seen_accelerometer_data_;
173 have_seen_accelerometer_data_ = true;
175 if (!update->has(chromeos::ACCELEROMETER_SOURCE_SCREEN))
176 return;
178 // Whether or not we enter maximize mode affects whether we handle screen
179 // rotation, so determine whether to enter maximize mode first.
180 if (!update->has(chromeos::ACCELEROMETER_SOURCE_ATTACHED_KEYBOARD)) {
181 if (first_accelerometer_update)
182 EnterMaximizeMode();
183 } else if (ui::IsAccelerometerReadingStable(
184 *update, chromeos::ACCELEROMETER_SOURCE_SCREEN) &&
185 ui::IsAccelerometerReadingStable(
186 *update, chromeos::ACCELEROMETER_SOURCE_ATTACHED_KEYBOARD) &&
187 IsAngleBetweenAccelerometerReadingsStable(*update)) {
188 // update.has(chromeos::ACCELEROMETER_SOURCE_ATTACHED_KEYBOARD)
189 // Ignore the reading if it appears unstable. The reading is considered
190 // unstable if it deviates too much from gravity and/or the magnitude of the
191 // reading from the lid differs too much from the reading from the base.
192 HandleHingeRotation(update);
196 void MaximizeModeController::LidEventReceived(bool open,
197 const base::TimeTicks& time) {
198 if (open)
199 last_lid_open_time_ = time;
200 lid_is_closed_ = !open;
201 LeaveMaximizeMode();
204 void MaximizeModeController::SuspendImminent() {
205 // The system is about to suspend, so record TouchView usage interval metrics
206 // based on whether TouchView mode is currently active.
207 RecordTouchViewUsageInterval(CurrentTouchViewIntervalType());
210 void MaximizeModeController::SuspendDone(
211 const base::TimeDelta& sleep_duration) {
212 // We do not want TouchView usage metrics to include time spent in suspend.
213 touchview_usage_interval_start_time_ = base::Time::Now();
216 void MaximizeModeController::HandleHingeRotation(
217 scoped_refptr<const chromeos::AccelerometerUpdate> update) {
218 static const gfx::Vector3dF hinge_vector(1.0f, 0.0f, 0.0f);
219 gfx::Vector3dF base_reading(ui::ConvertAccelerometerReadingToVector3dF(
220 update->get(chromeos::ACCELEROMETER_SOURCE_ATTACHED_KEYBOARD)));
221 gfx::Vector3dF lid_reading(ui::ConvertAccelerometerReadingToVector3dF(
222 update->get(chromeos::ACCELEROMETER_SOURCE_SCREEN)));
224 // As the hinge approaches a vertical angle, the base and lid accelerometers
225 // approach the same values making any angle calculations highly inaccurate.
226 // Smooth out instantaneous acceleration when nearly vertical to increase
227 // accuracy.
228 float largest_hinge_acceleration =
229 std::max(std::abs(base_reading.x()), std::abs(lid_reading.x()));
230 float smoothing_ratio =
231 std::max(0.0f, std::min(1.0f, (largest_hinge_acceleration -
232 kHingeVerticalSmoothingStart) /
233 (kHingeVerticalSmoothingMaximum -
234 kHingeVerticalSmoothingStart)));
236 base_smoothed_.Scale(smoothing_ratio);
237 base_reading.Scale(1.0f - smoothing_ratio);
238 base_smoothed_.Add(base_reading);
240 lid_smoothed_.Scale(smoothing_ratio);
241 lid_reading.Scale(1.0f - smoothing_ratio);
242 lid_smoothed_.Add(lid_reading);
244 // Ignore the component of acceleration parallel to the hinge for the purposes
245 // of hinge angle calculation.
246 gfx::Vector3dF base_flattened(base_smoothed_);
247 gfx::Vector3dF lid_flattened(lid_smoothed_);
248 base_flattened.set_x(0.0f);
249 lid_flattened.set_x(0.0f);
251 // Compute the angle between the base and the lid.
252 float lid_angle = 180.0f - gfx::ClockwiseAngleBetweenVectorsInDegrees(
253 base_flattened, lid_flattened, hinge_vector);
254 if (lid_angle < 0.0f)
255 lid_angle += 360.0f;
257 bool is_angle_stable = lid_angle >= kMinStableAngle &&
258 lid_angle <= kMaxStableAngle;
260 // Clear the last_lid_open_time_ for a stable reading so that there is less
261 // chance of a delay if the lid is moved from the close state to the fully
262 // open state very quickly.
263 if (is_angle_stable)
264 last_lid_open_time_ = base::TimeTicks();
266 // Toggle maximize mode on or off when corresponding thresholds are passed.
267 if (lid_open_past_180_ && is_angle_stable &&
268 lid_angle <= kExitMaximizeModeAngle) {
269 lid_open_past_180_ = false;
270 if (!base::CommandLine::ForCurrentProcess()->
271 HasSwitch(switches::kAshEnableTouchViewTesting)) {
272 LeaveMaximizeMode();
274 event_blocker_.reset();
275 } else if (!lid_open_past_180_ && !lid_is_closed_ &&
276 lid_angle >= kEnterMaximizeModeAngle &&
277 (is_angle_stable || !WasLidOpenedRecently())) {
278 lid_open_past_180_ = true;
279 if (!base::CommandLine::ForCurrentProcess()->
280 HasSwitch(switches::kAshEnableTouchViewTesting)) {
281 EnterMaximizeMode();
283 #if defined(USE_X11)
284 event_blocker_.reset(new ScopedDisableInternalMouseAndKeyboardX11);
285 #elif defined(USE_OZONE)
286 event_blocker_.reset(new ScopedDisableInternalMouseAndKeyboardOzone);
287 #endif
290 #endif // OS_CHROMEOS
292 void MaximizeModeController::EnterMaximizeMode() {
293 if (IsMaximizeModeWindowManagerEnabled())
294 return;
295 EnableMaximizeModeWindowManager(true);
298 void MaximizeModeController::LeaveMaximizeMode() {
299 if (!IsMaximizeModeWindowManagerEnabled())
300 return;
301 EnableMaximizeModeWindowManager(false);
304 // Called after maximize mode has started, windows might still animate though.
305 void MaximizeModeController::OnMaximizeModeStarted() {
306 RecordTouchViewUsageInterval(TOUCH_VIEW_INTERVAL_INACTIVE);
309 // Called after maximize mode has ended, windows might still be returning to
310 // their original position.
311 void MaximizeModeController::OnMaximizeModeEnded() {
312 RecordTouchViewUsageInterval(TOUCH_VIEW_INTERVAL_ACTIVE);
315 void MaximizeModeController::RecordTouchViewUsageInterval(
316 TouchViewIntervalType type) {
317 if (!CanEnterMaximizeMode())
318 return;
320 base::Time current_time = base::Time::Now();
321 base::TimeDelta delta = current_time - touchview_usage_interval_start_time_;
322 switch (type) {
323 case TOUCH_VIEW_INTERVAL_INACTIVE:
324 UMA_HISTOGRAM_LONG_TIMES("Ash.TouchView.TouchViewInactive", delta);
325 total_non_touchview_time_ += delta;
326 break;
327 case TOUCH_VIEW_INTERVAL_ACTIVE:
328 UMA_HISTOGRAM_LONG_TIMES("Ash.TouchView.TouchViewActive", delta);
329 total_touchview_time_ += delta;
330 break;
333 touchview_usage_interval_start_time_ = current_time;
336 MaximizeModeController::TouchViewIntervalType
337 MaximizeModeController::CurrentTouchViewIntervalType() {
338 if (IsMaximizeModeWindowManagerEnabled())
339 return TOUCH_VIEW_INTERVAL_ACTIVE;
340 return TOUCH_VIEW_INTERVAL_INACTIVE;
343 void MaximizeModeController::OnAppTerminating() {
344 // The system is about to shut down, so record TouchView usage interval
345 // metrics based on whether TouchView mode is currently active.
346 RecordTouchViewUsageInterval(CurrentTouchViewIntervalType());
348 if (CanEnterMaximizeMode()) {
349 UMA_HISTOGRAM_CUSTOM_COUNTS("Ash.TouchView.TouchViewActiveTotal",
350 total_touchview_time_.InMinutes(),
351 1, base::TimeDelta::FromDays(7).InMinutes(), 50);
352 UMA_HISTOGRAM_CUSTOM_COUNTS("Ash.TouchView.TouchViewInactiveTotal",
353 total_non_touchview_time_.InMinutes(),
354 1, base::TimeDelta::FromDays(7).InMinutes(), 50);
355 base::TimeDelta total_runtime = total_touchview_time_ +
356 total_non_touchview_time_;
357 if (total_runtime.InSeconds() > 0) {
358 UMA_HISTOGRAM_PERCENTAGE("Ash.TouchView.TouchViewActivePercentage",
359 100 * total_touchview_time_.InSeconds() / total_runtime.InSeconds());
364 bool MaximizeModeController::WasLidOpenedRecently() const {
365 if (last_lid_open_time_.is_null())
366 return false;
368 base::TimeTicks now = tick_clock_->NowTicks();
369 DCHECK(now >= last_lid_open_time_);
370 base::TimeDelta elapsed_time = now - last_lid_open_time_;
371 return elapsed_time.InSeconds() <= kLidRecentlyOpenedDurationSeconds;
374 void MaximizeModeController::SetTickClockForTest(
375 scoped_ptr<base::TickClock> tick_clock) {
376 DCHECK(tick_clock_);
377 tick_clock_ = tick_clock.Pass();
380 } // namespace ash