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

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

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

Powered by Google App Engine
This is Rietveld 408576698