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

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

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

Powered by Google App Engine
This is Rietveld 408576698