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"
24 #include "ash/wm/maximize_mode/scoped_disable_internal_mouse_and_keyboard_x11.h"
27 #if defined(USE_OZONE)
28 #include "ash/wm/maximize_mode/scoped_disable_internal_mouse_and_keyboard_ozone.h"
31 #if defined(OS_CHROMEOS)
32 #include "chromeos/dbus/dbus_thread_manager.h"
33 #include "ui/chromeos/accelerometer/accelerometer_util.h"
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
52 const float kMinStableAngle
= 20.0f
;
53 const float kMaxStableAngle
= 340.0f
;
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
) {
87 ui::ConvertAccelerometerReadingToVector3dF(
88 update
.get(chromeos::ACCELEROMETER_SOURCE_ATTACHED_KEYBOARD
))
90 ui::ConvertAccelerometerReadingToVector3dF(
91 update
.get(chromeos::ACCELEROMETER_SOURCE_SCREEN
)).Length()) <=
92 kNoisyMagnitudeDeviation
;
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
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
)
145 Shell
* shell
= Shell::GetInstance();
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();
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
))
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
)
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
) {
199 last_lid_open_time_
= time
;
200 lid_is_closed_
= !open
;
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
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
)
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.
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
)) {
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
)) {
284 event_blocker_
.reset(new ScopedDisableInternalMouseAndKeyboardX11
);
285 #elif defined(USE_OZONE)
286 event_blocker_
.reset(new ScopedDisableInternalMouseAndKeyboardOzone
);
290 #endif // OS_CHROMEOS
292 void MaximizeModeController::EnterMaximizeMode() {
293 if (IsMaximizeModeWindowManagerEnabled())
295 EnableMaximizeModeWindowManager(true);
298 void MaximizeModeController::LeaveMaximizeMode() {
299 if (!IsMaximizeModeWindowManagerEnabled())
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())
320 base::Time current_time
= base::Time::Now();
321 base::TimeDelta delta
= current_time
- touchview_usage_interval_start_time_
;
323 case TOUCH_VIEW_INTERVAL_INACTIVE
:
324 UMA_HISTOGRAM_LONG_TIMES("Ash.TouchView.TouchViewInactive", delta
);
325 total_non_touchview_time_
+= delta
;
327 case TOUCH_VIEW_INTERVAL_ACTIVE
:
328 UMA_HISTOGRAM_LONG_TIMES("Ash.TouchView.TouchViewActive", delta
);
329 total_touchview_time_
+= delta
;
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())
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
) {
377 tick_clock_
= tick_clock
.Pass();