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 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
) {
88 ui::ConvertAccelerometerReadingToVector3dF(
89 update
.get(chromeos::ACCELEROMETER_SOURCE_ATTACHED_KEYBOARD
))
91 ui::ConvertAccelerometerReadingToVector3dF(
92 update
.get(chromeos::ACCELEROMETER_SOURCE_SCREEN
)).Length()) <=
93 kNoisyMagnitudeDeviation
;
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
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
)
146 Shell
* shell
= Shell::GetInstance();
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();
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
))
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
)
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
) {
200 last_lid_open_time_
= time
;
201 lid_is_closed_
= !open
;
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
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
)
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.
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
)) {
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
)) {
285 event_blocker_
.reset(new ScopedDisableInternalMouseAndKeyboardX11
);
286 #elif defined(USE_OZONE)
287 event_blocker_
.reset(new ScopedDisableInternalMouseAndKeyboardOzone
);
291 #endif // OS_CHROMEOS
293 void MaximizeModeController::EnterMaximizeMode() {
294 if (IsMaximizeModeWindowManagerEnabled())
296 EnableMaximizeModeWindowManager(true);
299 void MaximizeModeController::LeaveMaximizeMode() {
300 if (!IsMaximizeModeWindowManagerEnabled())
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())
321 base::Time current_time
= base::Time::Now();
322 base::TimeDelta delta
= current_time
- touchview_usage_interval_start_time_
;
324 case TOUCH_VIEW_INTERVAL_INACTIVE
:
325 UMA_HISTOGRAM_LONG_TIMES("Ash.TouchView.TouchViewInactive", delta
);
326 total_non_touchview_time_
+= delta
;
328 case TOUCH_VIEW_INTERVAL_ACTIVE
:
329 UMA_HISTOGRAM_LONG_TIMES("Ash.TouchView.TouchViewActive", delta
);
330 total_touchview_time_
+= delta
;
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())
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
) {
378 tick_clock_
= tick_clock
.Pass();