Pin Chrome's shortcut to the Win10 Start menu on install and OS upgrade.
[chromium-blink-merge.git] / ash / wm / maximize_mode / maximize_mode_controller.cc
blob0f7325a6ba0caedec30605d899d28b3890c4719e
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 base::TimeDelta kLidRecentlyOpenedDuration =
61 base::TimeDelta::FromSeconds(2);
63 #if defined(OS_CHROMEOS)
64 // When the device approaches vertical orientation (i.e. portrait orientation)
65 // the accelerometers for the base and lid approach the same values (i.e.
66 // gravity pointing in the direction of the hinge). When this happens abrupt
67 // small acceleration perpendicular to the hinge can lead to incorrect hinge
68 // angle calculations. To prevent this the accelerometer updates will be
69 // smoothed over time in order to reduce this noise.
70 // This is the minimum acceleration parallel to the hinge under which to begin
71 // smoothing in m/s^2.
72 const float kHingeVerticalSmoothingStart = 7.0f;
73 // This is the maximum acceleration parallel to the hinge under which smoothing
74 // will incorporate new acceleration values, in m/s^2.
75 const float kHingeVerticalSmoothingMaximum = 9.5f;
77 // The maximum deviation between the magnitude of the two accelerometers under
78 // which to detect hinge angle in m/s^2. These accelerometers are attached to
79 // the same physical device and so should be under the same acceleration.
80 const float kNoisyMagnitudeDeviation = 1.0f;
82 // The angle between chromeos::AccelerometerReadings are considered stable if
83 // their magnitudes do not differ greatly. This returns false if the deviation
84 // between the screen and keyboard accelerometers is too high.
85 bool IsAngleBetweenAccelerometerReadingsStable(
86 const chromeos::AccelerometerUpdate& update) {
87 return std::abs(
88 ui::ConvertAccelerometerReadingToVector3dF(
89 update.get(chromeos::ACCELEROMETER_SOURCE_ATTACHED_KEYBOARD))
90 .Length() -
91 ui::ConvertAccelerometerReadingToVector3dF(
92 update.get(chromeos::ACCELEROMETER_SOURCE_SCREEN)).Length()) <=
93 kNoisyMagnitudeDeviation;
95 #endif // OS_CHROMEOS
97 } // namespace
99 MaximizeModeController::MaximizeModeController()
100 : have_seen_accelerometer_data_(false),
101 lid_open_past_180_(false),
102 touchview_usage_interval_start_time_(base::Time::Now()),
103 tick_clock_(new base::DefaultTickClock()),
104 lid_is_closed_(false) {
105 Shell* shell = Shell::GetInstance();
106 shell->AddShellObserver(this);
107 shell->metrics()->RecordUserMetricsAction(
108 ash::UMA_MAXIMIZE_MODE_INITIALLY_DISABLED);
110 #if defined(OS_CHROMEOS)
111 chromeos::AccelerometerReader::GetInstance()->AddObserver(this);
112 chromeos::DBusThreadManager::Get()->
113 GetPowerManagerClient()->AddObserver(this);
114 #endif // OS_CHROMEOS
117 MaximizeModeController::~MaximizeModeController() {
118 Shell::GetInstance()->RemoveShellObserver(this);
119 #if defined(OS_CHROMEOS)
120 chromeos::AccelerometerReader::GetInstance()->RemoveObserver(this);
121 chromeos::DBusThreadManager::Get()->
122 GetPowerManagerClient()->RemoveObserver(this);
123 #endif // OS_CHROMEOS
126 bool MaximizeModeController::CanEnterMaximizeMode() {
127 // If we have ever seen accelerometer data, then HandleHingeRotation may
128 // trigger maximize mode at some point in the future.
129 // The --enable-touch-view-testing switch can also mean that we may enter
130 // maximize mode.
131 // TODO(mgiuca): This can result in false positives, as it returns true for
132 // any device with an accelerometer. Have TouchView-enabled devices explicitly
133 // set a flag, and change this implementation to simply return true iff the
134 // flag is present (http://crbug.com/457445).
135 return have_seen_accelerometer_data_ ||
136 base::CommandLine::ForCurrentProcess()->HasSwitch(
137 switches::kAshEnableTouchViewTesting);
140 void MaximizeModeController::EnableMaximizeModeWindowManager(
141 bool should_enable) {
142 bool is_enabled = !!maximize_mode_window_manager_.get();
143 if (should_enable == is_enabled)
144 return;
146 Shell* shell = Shell::GetInstance();
148 if (should_enable) {
149 maximize_mode_window_manager_.reset(new MaximizeModeWindowManager());
150 // TODO(jonross): Move the maximize mode notifications from ShellObserver
151 // to MaximizeModeController::Observer
152 shell->metrics()->RecordUserMetricsAction(ash::UMA_MAXIMIZE_MODE_ENABLED);
153 shell->OnMaximizeModeStarted();
154 } else {
155 maximize_mode_window_manager_.reset();
156 shell->metrics()->RecordUserMetricsAction(ash::UMA_MAXIMIZE_MODE_DISABLED);
157 shell->OnMaximizeModeEnded();
161 bool MaximizeModeController::IsMaximizeModeWindowManagerEnabled() const {
162 return maximize_mode_window_manager_.get() != NULL;
165 void MaximizeModeController::AddWindow(aura::Window* window) {
166 if (IsMaximizeModeWindowManagerEnabled())
167 maximize_mode_window_manager_->AddWindow(window);
170 #if defined(OS_CHROMEOS)
171 void MaximizeModeController::OnAccelerometerUpdated(
172 scoped_refptr<const chromeos::AccelerometerUpdate> update) {
173 bool first_accelerometer_update = !have_seen_accelerometer_data_;
174 have_seen_accelerometer_data_ = true;
176 if (!update->has(chromeos::ACCELEROMETER_SOURCE_SCREEN))
177 return;
179 // Whether or not we enter maximize mode affects whether we handle screen
180 // rotation, so determine whether to enter maximize mode first.
181 if (!update->has(chromeos::ACCELEROMETER_SOURCE_ATTACHED_KEYBOARD)) {
182 if (first_accelerometer_update)
183 EnterMaximizeMode();
184 } else if (ui::IsAccelerometerReadingStable(
185 *update, chromeos::ACCELEROMETER_SOURCE_SCREEN) &&
186 ui::IsAccelerometerReadingStable(
187 *update, chromeos::ACCELEROMETER_SOURCE_ATTACHED_KEYBOARD) &&
188 IsAngleBetweenAccelerometerReadingsStable(*update)) {
189 // update.has(chromeos::ACCELEROMETER_SOURCE_ATTACHED_KEYBOARD)
190 // Ignore the reading if it appears unstable. The reading is considered
191 // unstable if it deviates too much from gravity and/or the magnitude of the
192 // reading from the lid differs too much from the reading from the base.
193 HandleHingeRotation(update);
197 void MaximizeModeController::LidEventReceived(bool open,
198 const base::TimeTicks& time) {
199 if (open)
200 last_lid_open_time_ = time;
201 lid_is_closed_ = !open;
202 LeaveMaximizeMode();
205 void MaximizeModeController::SuspendImminent() {
206 // The system is about to suspend, so record TouchView usage interval metrics
207 // based on whether TouchView mode is currently active.
208 RecordTouchViewUsageInterval(CurrentTouchViewIntervalType());
211 void MaximizeModeController::SuspendDone(
212 const base::TimeDelta& sleep_duration) {
213 // We do not want TouchView usage metrics to include time spent in suspend.
214 touchview_usage_interval_start_time_ = base::Time::Now();
217 void MaximizeModeController::HandleHingeRotation(
218 scoped_refptr<const chromeos::AccelerometerUpdate> update) {
219 static const gfx::Vector3dF hinge_vector(1.0f, 0.0f, 0.0f);
220 gfx::Vector3dF base_reading(ui::ConvertAccelerometerReadingToVector3dF(
221 update->get(chromeos::ACCELEROMETER_SOURCE_ATTACHED_KEYBOARD)));
222 gfx::Vector3dF lid_reading(ui::ConvertAccelerometerReadingToVector3dF(
223 update->get(chromeos::ACCELEROMETER_SOURCE_SCREEN)));
225 // As the hinge approaches a vertical angle, the base and lid accelerometers
226 // approach the same values making any angle calculations highly inaccurate.
227 // Smooth out instantaneous acceleration when nearly vertical to increase
228 // accuracy.
229 float largest_hinge_acceleration =
230 std::max(std::abs(base_reading.x()), std::abs(lid_reading.x()));
231 float smoothing_ratio =
232 std::max(0.0f, std::min(1.0f, (largest_hinge_acceleration -
233 kHingeVerticalSmoothingStart) /
234 (kHingeVerticalSmoothingMaximum -
235 kHingeVerticalSmoothingStart)));
237 base_smoothed_.Scale(smoothing_ratio);
238 base_reading.Scale(1.0f - smoothing_ratio);
239 base_smoothed_.Add(base_reading);
241 lid_smoothed_.Scale(smoothing_ratio);
242 lid_reading.Scale(1.0f - smoothing_ratio);
243 lid_smoothed_.Add(lid_reading);
245 // Ignore the component of acceleration parallel to the hinge for the purposes
246 // of hinge angle calculation.
247 gfx::Vector3dF base_flattened(base_smoothed_);
248 gfx::Vector3dF lid_flattened(lid_smoothed_);
249 base_flattened.set_x(0.0f);
250 lid_flattened.set_x(0.0f);
252 // Compute the angle between the base and the lid.
253 float lid_angle = 180.0f - gfx::ClockwiseAngleBetweenVectorsInDegrees(
254 base_flattened, lid_flattened, hinge_vector);
255 if (lid_angle < 0.0f)
256 lid_angle += 360.0f;
258 bool is_angle_stable = lid_angle >= kMinStableAngle &&
259 lid_angle <= kMaxStableAngle;
261 // Clear the last_lid_open_time_ for a stable reading so that there is less
262 // chance of a delay if the lid is moved from the close state to the fully
263 // open state very quickly.
264 if (is_angle_stable)
265 last_lid_open_time_ = base::TimeTicks();
267 // Toggle maximize mode on or off when corresponding thresholds are passed.
268 if (lid_open_past_180_ && is_angle_stable &&
269 lid_angle <= kExitMaximizeModeAngle) {
270 lid_open_past_180_ = false;
271 if (!base::CommandLine::ForCurrentProcess()->
272 HasSwitch(switches::kAshEnableTouchViewTesting)) {
273 LeaveMaximizeMode();
275 event_blocker_.reset();
276 } else if (!lid_open_past_180_ && !lid_is_closed_ &&
277 lid_angle >= kEnterMaximizeModeAngle &&
278 (is_angle_stable || !WasLidOpenedRecently())) {
279 lid_open_past_180_ = true;
280 if (!base::CommandLine::ForCurrentProcess()->
281 HasSwitch(switches::kAshEnableTouchViewTesting)) {
282 EnterMaximizeMode();
284 #if defined(USE_X11)
285 event_blocker_.reset(new ScopedDisableInternalMouseAndKeyboardX11);
286 #elif defined(USE_OZONE)
287 event_blocker_.reset(new ScopedDisableInternalMouseAndKeyboardOzone);
288 #endif
291 #endif // OS_CHROMEOS
293 void MaximizeModeController::EnterMaximizeMode() {
294 if (IsMaximizeModeWindowManagerEnabled())
295 return;
296 EnableMaximizeModeWindowManager(true);
299 void MaximizeModeController::LeaveMaximizeMode() {
300 if (!IsMaximizeModeWindowManagerEnabled())
301 return;
302 EnableMaximizeModeWindowManager(false);
305 // Called after maximize mode has started, windows might still animate though.
306 void MaximizeModeController::OnMaximizeModeStarted() {
307 RecordTouchViewUsageInterval(TOUCH_VIEW_INTERVAL_INACTIVE);
310 // Called after maximize mode has ended, windows might still be returning to
311 // their original position.
312 void MaximizeModeController::OnMaximizeModeEnded() {
313 RecordTouchViewUsageInterval(TOUCH_VIEW_INTERVAL_ACTIVE);
316 void MaximizeModeController::RecordTouchViewUsageInterval(
317 TouchViewIntervalType type) {
318 if (!CanEnterMaximizeMode())
319 return;
321 base::Time current_time = base::Time::Now();
322 base::TimeDelta delta = current_time - touchview_usage_interval_start_time_;
323 switch (type) {
324 case TOUCH_VIEW_INTERVAL_INACTIVE:
325 UMA_HISTOGRAM_LONG_TIMES("Ash.TouchView.TouchViewInactive", delta);
326 total_non_touchview_time_ += delta;
327 break;
328 case TOUCH_VIEW_INTERVAL_ACTIVE:
329 UMA_HISTOGRAM_LONG_TIMES("Ash.TouchView.TouchViewActive", delta);
330 total_touchview_time_ += delta;
331 break;
334 touchview_usage_interval_start_time_ = current_time;
337 MaximizeModeController::TouchViewIntervalType
338 MaximizeModeController::CurrentTouchViewIntervalType() {
339 if (IsMaximizeModeWindowManagerEnabled())
340 return TOUCH_VIEW_INTERVAL_ACTIVE;
341 return TOUCH_VIEW_INTERVAL_INACTIVE;
344 void MaximizeModeController::OnAppTerminating() {
345 // The system is about to shut down, so record TouchView usage interval
346 // metrics based on whether TouchView mode is currently active.
347 RecordTouchViewUsageInterval(CurrentTouchViewIntervalType());
349 if (CanEnterMaximizeMode()) {
350 UMA_HISTOGRAM_CUSTOM_COUNTS("Ash.TouchView.TouchViewActiveTotal",
351 total_touchview_time_.InMinutes(),
352 1, base::TimeDelta::FromDays(7).InMinutes(), 50);
353 UMA_HISTOGRAM_CUSTOM_COUNTS("Ash.TouchView.TouchViewInactiveTotal",
354 total_non_touchview_time_.InMinutes(),
355 1, base::TimeDelta::FromDays(7).InMinutes(), 50);
356 base::TimeDelta total_runtime = total_touchview_time_ +
357 total_non_touchview_time_;
358 if (total_runtime.InSeconds() > 0) {
359 UMA_HISTOGRAM_PERCENTAGE("Ash.TouchView.TouchViewActivePercentage",
360 100 * total_touchview_time_.InSeconds() / total_runtime.InSeconds());
365 bool MaximizeModeController::WasLidOpenedRecently() const {
366 if (last_lid_open_time_.is_null())
367 return false;
369 base::TimeTicks now = tick_clock_->NowTicks();
370 DCHECK(now >= last_lid_open_time_);
371 base::TimeDelta elapsed_time = now - last_lid_open_time_;
372 return elapsed_time <= kLidRecentlyOpenedDuration;
375 void MaximizeModeController::SetTickClockForTest(
376 scoped_ptr<base::TickClock> tick_clock) {
377 DCHECK(tick_clock_);
378 tick_clock_ = tick_clock.Pass();
381 } // namespace ash