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

Side by Side Diff: ash/wm/tablet_mode/tablet_mode_controller.cc

Issue 2909763002: Revert of Rename MaximizeMode to TabletMode (Closed)
Patch Set: 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/tablet_mode/tablet_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/tablet_mode/scoped_disable_internal_mouse_and_keyboard.h"
13 #include "ash/wm/tablet_mode/tablet_mode_window_manager.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 tablet mode.
32 const float kEnterTabletModeAngle = 200.0f;
33
34 // The angle at which to exit tablet mode, this is specifically less than the
35 // angle to enter tablet mode to prevent rapid toggling when near the angle.
36 const float kExitTabletModeAngle = 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 tablet 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 tablet mode is turned on, if
89 // any.
90 TabletModeController::ForceTabletMode GetTabletMode() {
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 TabletModeController::ForceTabletMode::CLAMSHELL;
97
98 if (switch_value == switches::kAshForceTabletModeTouchView)
99 return TabletModeController::ForceTabletMode::TOUCHVIEW;
100 }
101 return TabletModeController::ForceTabletMode::NONE;
102 }
103
104 } // namespace
105
106 TabletModeController::TabletModeController()
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 TabletModeController if the flag is
120 // unavailable. This will require refactoring
121 // IsTabletModeWindowManagerEnabled 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 &TabletModeController::OnGetSwitchStates, weak_factory_.GetWeakPtr()));
132 }
133
134 TabletModeController::~TabletModeController() {
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 TabletModeController::CanEnterTabletMode() {
146 // If we have ever seen accelerometer data, then HandleHingeRotation may
147 // trigger tablet mode at some point in the future.
148 // All TouchView-enabled devices can enter tablet mode.
149 return have_seen_accelerometer_data_ || IsEnabled();
150 }
151
152 // TODO(jcliang): Hide or remove EnableTabletModeWindowManager
153 // (http://crbug.com/620241).
154 void TabletModeController::EnableTabletModeWindowManager(bool should_enable) {
155 bool is_enabled = !!tablet_mode_window_manager_.get();
156 if (should_enable == is_enabled)
157 return;
158
159 if (should_enable) {
160 tablet_mode_window_manager_.reset(new TabletModeWindowManager());
161 // TODO(jonross): Move the tablet mode notifications from ShellObserver
162 // to TabletModeController::Observer
163 ShellPort::Get()->RecordUserMetricsAction(UMA_MAXIMIZE_MODE_ENABLED);
164 Shell::Get()->NotifyTabletModeStarted();
165
166 observers_.ForAllPtrs([](mojom::TouchViewObserver* observer) {
167 observer->OnTouchViewToggled(true);
168 });
169
170 } else {
171 tablet_mode_window_manager_->SetIgnoreWmEventsForExit();
172 Shell::Get()->NotifyTabletModeEnding();
173 tablet_mode_window_manager_.reset();
174 ShellPort::Get()->RecordUserMetricsAction(UMA_MAXIMIZE_MODE_DISABLED);
175 Shell::Get()->NotifyTabletModeEnded();
176
177 observers_.ForAllPtrs([](mojom::TouchViewObserver* observer) {
178 observer->OnTouchViewToggled(false);
179 });
180 }
181 }
182
183 bool TabletModeController::IsTabletModeWindowManagerEnabled() const {
184 return tablet_mode_window_manager_.get() != NULL;
185 }
186
187 void TabletModeController::AddWindow(aura::Window* window) {
188 if (IsTabletModeWindowManagerEnabled())
189 tablet_mode_window_manager_->AddWindow(window);
190 }
191
192 void TabletModeController::BindRequest(mojom::TouchViewManagerRequest request) {
193 bindings_.AddBinding(this, std::move(request));
194 }
195
196 void TabletModeController::OnAccelerometerUpdated(
197 scoped_refptr<const chromeos::AccelerometerUpdate> update) {
198 if (!AllowEnterExitTabletMode())
199 return;
200
201 have_seen_accelerometer_data_ = true;
202 can_detect_lid_angle_ =
203 update->has(chromeos::ACCELEROMETER_SOURCE_SCREEN) &&
204 update->has(chromeos::ACCELEROMETER_SOURCE_ATTACHED_KEYBOARD);
205
206 if (!can_detect_lid_angle_)
207 return;
208
209 if (!display::Display::HasInternalDisplay())
210 return;
211
212 if (!ShellPort::Get()->IsActiveDisplayId(
213 display::Display::InternalDisplayId())) {
214 return;
215 }
216
217 // Whether or not we enter tablet mode affects whether we handle screen
218 // rotation, so determine whether to enter tablet mode first.
219 if (ui::IsAccelerometerReadingStable(*update,
220 chromeos::ACCELEROMETER_SOURCE_SCREEN) &&
221 ui::IsAccelerometerReadingStable(
222 *update, chromeos::ACCELEROMETER_SOURCE_ATTACHED_KEYBOARD) &&
223 IsAngleBetweenAccelerometerReadingsStable(*update)) {
224 // update.has(chromeos::ACCELEROMETER_SOURCE_ATTACHED_KEYBOARD)
225 // Ignore the reading if it appears unstable. The reading is considered
226 // unstable if it deviates too much from gravity and/or the magnitude of the
227 // reading from the lid differs too much from the reading from the base.
228 HandleHingeRotation(update);
229 }
230 }
231
232 void TabletModeController::LidEventReceived(
233 chromeos::PowerManagerClient::LidState state,
234 const base::TimeTicks& time) {
235 if (!AllowEnterExitTabletMode())
236 return;
237
238 const bool open = state == chromeos::PowerManagerClient::LidState::OPEN;
239 if (open)
240 last_lid_open_time_ = time;
241 lid_is_closed_ = !open;
242 LeaveTabletMode();
243 }
244
245 void TabletModeController::TabletModeEventReceived(
246 chromeos::PowerManagerClient::TabletMode mode,
247 const base::TimeTicks& time) {
248 if (!AllowEnterExitTabletMode())
249 return;
250
251 const bool on = mode == chromeos::PowerManagerClient::TabletMode::ON;
252 tablet_mode_switch_is_on_ = on;
253 // Do not change if docked.
254 if (!display::Display::HasInternalDisplay() ||
255 !ShellPort::Get()->IsActiveDisplayId(
256 display::Display::InternalDisplayId())) {
257 return;
258 }
259 // The tablet mode switch activates at 300 degrees, so it is always reliable
260 // when |on|. However we wish to exit tablet mode at a smaller angle, so
261 // when |on| is false we ignore if it is possible to calculate the lid angle.
262 if (on && !IsTabletModeWindowManagerEnabled()) {
263 EnterTabletMode();
264 } else if (!on && IsTabletModeWindowManagerEnabled() &&
265 !can_detect_lid_angle_) {
266 LeaveTabletMode();
267 }
268 }
269
270 void TabletModeController::SuspendImminent() {
271 // The system is about to suspend, so record TouchView usage interval metrics
272 // based on whether TouchView mode is currently active.
273 RecordTouchViewUsageInterval(CurrentTouchViewIntervalType());
274 }
275
276 void TabletModeController::SuspendDone(const base::TimeDelta& sleep_duration) {
277 // We do not want TouchView usage metrics to include time spent in suspend.
278 touchview_usage_interval_start_time_ = base::Time::Now();
279 }
280
281 void TabletModeController::HandleHingeRotation(
282 scoped_refptr<const chromeos::AccelerometerUpdate> update) {
283 static const gfx::Vector3dF hinge_vector(1.0f, 0.0f, 0.0f);
284 gfx::Vector3dF base_reading(ui::ConvertAccelerometerReadingToVector3dF(
285 update->get(chromeos::ACCELEROMETER_SOURCE_ATTACHED_KEYBOARD)));
286 gfx::Vector3dF lid_reading(ui::ConvertAccelerometerReadingToVector3dF(
287 update->get(chromeos::ACCELEROMETER_SOURCE_SCREEN)));
288
289 // As the hinge approaches a vertical angle, the base and lid accelerometers
290 // approach the same values making any angle calculations highly inaccurate.
291 // Smooth out instantaneous acceleration when nearly vertical to increase
292 // accuracy.
293 float largest_hinge_acceleration =
294 std::max(std::abs(base_reading.x()), std::abs(lid_reading.x()));
295 float smoothing_ratio =
296 std::max(0.0f, std::min(1.0f, (largest_hinge_acceleration -
297 kHingeVerticalSmoothingStart) /
298 (kHingeVerticalSmoothingMaximum -
299 kHingeVerticalSmoothingStart)));
300
301 // We cannot trust the computed lid angle when the device is held vertically.
302 bool is_angle_reliable =
303 largest_hinge_acceleration <= kHingeVerticalSmoothingMaximum;
304
305 base_smoothed_.Scale(smoothing_ratio);
306 base_reading.Scale(1.0f - smoothing_ratio);
307 base_smoothed_.Add(base_reading);
308
309 lid_smoothed_.Scale(smoothing_ratio);
310 lid_reading.Scale(1.0f - smoothing_ratio);
311 lid_smoothed_.Add(lid_reading);
312
313 if (tablet_mode_switch_is_on_)
314 return;
315
316 // Ignore the component of acceleration parallel to the hinge for the purposes
317 // of hinge angle calculation.
318 gfx::Vector3dF base_flattened(base_smoothed_);
319 gfx::Vector3dF lid_flattened(lid_smoothed_);
320 base_flattened.set_x(0.0f);
321 lid_flattened.set_x(0.0f);
322
323 // Compute the angle between the base and the lid.
324 float lid_angle = 180.0f - gfx::ClockwiseAngleBetweenVectorsInDegrees(
325 base_flattened, lid_flattened, hinge_vector);
326 if (lid_angle < 0.0f)
327 lid_angle += 360.0f;
328
329 bool is_angle_stable = is_angle_reliable && lid_angle >= kMinStableAngle &&
330 lid_angle <= kMaxStableAngle;
331
332 // Clear the last_lid_open_time_ for a stable reading so that there is less
333 // chance of a delay if the lid is moved from the close state to the fully
334 // open state very quickly.
335 if (is_angle_stable)
336 last_lid_open_time_ = base::TimeTicks();
337
338 // Toggle tablet mode on or off when corresponding thresholds are passed.
339 if (IsTabletModeWindowManagerEnabled() && is_angle_stable &&
340 lid_angle <= kExitTabletModeAngle) {
341 LeaveTabletMode();
342 } else if (!IsTabletModeWindowManagerEnabled() && !lid_is_closed_ &&
343 lid_angle >= kEnterTabletModeAngle &&
344 (is_angle_stable || !WasLidOpenedRecently())) {
345 EnterTabletMode();
346 }
347 }
348
349 void TabletModeController::EnterTabletMode() {
350 // Always reset first to avoid creation before destruction of a previous
351 // object.
352 event_blocker_ =
353 ShellPort::Get()->CreateScopedDisableInternalMouseAndKeyboard();
354
355 if (IsTabletModeWindowManagerEnabled())
356 return;
357 EnableTabletModeWindowManager(true);
358 }
359
360 void TabletModeController::LeaveTabletMode() {
361 event_blocker_.reset();
362
363 if (!IsTabletModeWindowManagerEnabled())
364 return;
365 EnableTabletModeWindowManager(false);
366 }
367
368 // Called after tablet mode has started, windows might still animate though.
369 void TabletModeController::OnTabletModeStarted() {
370 RecordTouchViewUsageInterval(TOUCH_VIEW_INTERVAL_INACTIVE);
371 }
372
373 // Called after tablet mode has ended, windows might still be returning to
374 // their original position.
375 void TabletModeController::OnTabletModeEnded() {
376 RecordTouchViewUsageInterval(TOUCH_VIEW_INTERVAL_ACTIVE);
377 }
378
379 void TabletModeController::OnShellInitialized() {
380 force_tablet_mode_ = GetTabletMode();
381 if (force_tablet_mode_ == ForceTabletMode::TOUCHVIEW)
382 EnterTabletMode();
383 }
384
385 void TabletModeController::OnDisplayConfigurationChanged() {
386 if (!display::Display::HasInternalDisplay() ||
387 !ShellPort::Get()->IsActiveDisplayId(
388 display::Display::InternalDisplayId())) {
389 LeaveTabletMode();
390 } else if (tablet_mode_switch_is_on_ && !IsTabletModeWindowManagerEnabled()) {
391 // The internal display has returned, as we are exiting docked mode.
392 // The device is still in tablet mode, so trigger tablet mode, as this
393 // switch leads to the ignoring of accelerometer events. When the switch is
394 // not set the next stable accelerometer readings will trigger maximize
395 // mode.
396 EnterTabletMode();
397 }
398 }
399
400 void TabletModeController::RecordTouchViewUsageInterval(
401 TouchViewIntervalType type) {
402 if (!CanEnterTabletMode())
403 return;
404
405 base::Time current_time = base::Time::Now();
406 base::TimeDelta delta = current_time - touchview_usage_interval_start_time_;
407 switch (type) {
408 case TOUCH_VIEW_INTERVAL_INACTIVE:
409 UMA_HISTOGRAM_LONG_TIMES("Ash.TouchView.TouchViewInactive", delta);
410 total_non_touchview_time_ += delta;
411 break;
412 case TOUCH_VIEW_INTERVAL_ACTIVE:
413 UMA_HISTOGRAM_LONG_TIMES("Ash.TouchView.TouchViewActive", delta);
414 total_touchview_time_ += delta;
415 break;
416 }
417
418 touchview_usage_interval_start_time_ = current_time;
419 }
420
421 TabletModeController::TouchViewIntervalType
422 TabletModeController::CurrentTouchViewIntervalType() {
423 if (IsTabletModeWindowManagerEnabled())
424 return TOUCH_VIEW_INTERVAL_ACTIVE;
425 return TOUCH_VIEW_INTERVAL_INACTIVE;
426 }
427
428 void TabletModeController::AddObserver(mojom::TouchViewObserverPtr observer) {
429 observer->OnTouchViewToggled(IsTabletModeWindowManagerEnabled());
430 observers_.AddPtr(std::move(observer));
431 }
432
433 bool TabletModeController::AllowEnterExitTabletMode() const {
434 return force_tablet_mode_ == ForceTabletMode::NONE;
435 }
436
437 void TabletModeController::OnChromeTerminating() {
438 // The system is about to shut down, so record TouchView usage interval
439 // metrics based on whether TouchView mode is currently active.
440 RecordTouchViewUsageInterval(CurrentTouchViewIntervalType());
441
442 if (CanEnterTabletMode()) {
443 UMA_HISTOGRAM_CUSTOM_COUNTS("Ash.TouchView.TouchViewActiveTotal",
444 total_touchview_time_.InMinutes(), 1,
445 base::TimeDelta::FromDays(7).InMinutes(), 50);
446 UMA_HISTOGRAM_CUSTOM_COUNTS("Ash.TouchView.TouchViewInactiveTotal",
447 total_non_touchview_time_.InMinutes(), 1,
448 base::TimeDelta::FromDays(7).InMinutes(), 50);
449 base::TimeDelta total_runtime =
450 total_touchview_time_ + total_non_touchview_time_;
451 if (total_runtime.InSeconds() > 0) {
452 UMA_HISTOGRAM_PERCENTAGE(
453 "Ash.TouchView.TouchViewActivePercentage",
454 100 * total_touchview_time_.InSeconds() / total_runtime.InSeconds());
455 }
456 }
457 }
458
459 void TabletModeController::OnGetSwitchStates(
460 chromeos::PowerManagerClient::LidState lid_state,
461 chromeos::PowerManagerClient::TabletMode tablet_mode) {
462 LidEventReceived(lid_state, base::TimeTicks::Now());
463 TabletModeEventReceived(tablet_mode, base::TimeTicks::Now());
464 }
465
466 bool TabletModeController::WasLidOpenedRecently() const {
467 if (last_lid_open_time_.is_null())
468 return false;
469
470 base::TimeTicks now = tick_clock_->NowTicks();
471 DCHECK(now >= last_lid_open_time_);
472 base::TimeDelta elapsed_time = now - last_lid_open_time_;
473 return elapsed_time.InSeconds() <= kLidRecentlyOpenedDurationSeconds;
474 }
475
476 void TabletModeController::SetTickClockForTest(
477 std::unique_ptr<base::TickClock> tick_clock) {
478 DCHECK(tick_clock_);
479 tick_clock_ = std::move(tick_clock);
480 }
481
482 } // namespace ash
OLDNEW
« no previous file with comments | « ash/wm/tablet_mode/tablet_mode_controller.h ('k') | ash/wm/tablet_mode/tablet_mode_controller_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698