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 "ash/accelerators/accelerator_controller.h" 8 #include "ash/accelerators/accelerator_table.h" 9 #include "ash/accelerometer/accelerometer_controller.h" 10 #include "ash/ash_switches.h" 11 #include "ash/display/display_manager.h" 12 #include "ash/shell.h" 13 #include "ash/wm/maximize_mode/maximize_mode_window_manager.h" 14 #include "ash/wm/maximize_mode/scoped_disable_internal_mouse_and_keyboard.h" 15 #include "base/auto_reset.h" 16 #include "base/command_line.h" 17 #include "base/metrics/histogram.h" 18 #include "base/time/default_tick_clock.h" 19 #include "base/time/tick_clock.h" 20 #include "ui/base/accelerators/accelerator.h" 21 #include "ui/events/event.h" 22 #include "ui/events/keycodes/keyboard_codes.h" 23 #include "ui/gfx/vector3d_f.h" 24 25 #if defined(USE_X11) 26 #include "ash/wm/maximize_mode/scoped_disable_internal_mouse_and_keyboard_x11.h" 27 #endif 28 29 #if defined(OS_CHROMEOS) 30 #include "chromeos/dbus/dbus_thread_manager.h" 31 #endif // OS_CHROMEOS 32 33 namespace ash { 34 35 namespace { 36 37 // The hinge angle at which to enter maximize mode. 38 const float kEnterMaximizeModeAngle = 200.0f; 39 40 // The angle at which to exit maximize mode, this is specifically less than the 41 // angle to enter maximize mode to prevent rapid toggling when near the angle. 42 const float kExitMaximizeModeAngle = 160.0f; 43 44 // Defines a range for which accelerometer readings are considered accurate. 45 // When the lid is near open (or near closed) the accelerometer readings may be 46 // inaccurate and a lid that is fully open may appear to be near closed (and 47 // vice versa). 48 const float kMinStableAngle = 20.0f; 49 const float kMaxStableAngle = 340.0f; 50 51 // The time duration to consider the lid to be recently opened. 52 // This is used to prevent entering maximize mode if an erroneous accelerometer 53 // reading makes the lid appear to be fully open when the user is opening the 54 // lid from a closed position. 55 const base::TimeDelta kLidRecentlyOpenedDuration = 56 base::TimeDelta::FromSeconds(2); 57 58 // The mean acceleration due to gravity on Earth in m/s^2. 59 const float kMeanGravity = 9.80665f; 60 61 // When the device approaches vertical orientation (i.e. portrait orientation) 62 // the accelerometers for the base and lid approach the same values (i.e. 63 // gravity pointing in the direction of the hinge). When this happens we cannot 64 // compute the hinge angle reliably and must turn ignore accelerometer readings. 65 // This is the minimum acceleration perpendicular to the hinge under which to 66 // detect hinge angle in m/s^2. 67 const float kHingeAngleDetectionThreshold = 2.5f; 68 69 // The maximum deviation from the acceleration expected due to gravity under 70 // which to detect hinge angle and screen rotation in m/s^2 71 const float kDeviationFromGravityThreshold = 1.0f; 72 73 // The maximum deviation between the magnitude of the two accelerometers under 74 // which to detect hinge angle and screen rotation in m/s^2. These 75 // accelerometers are attached to the same physical device and so should be 76 // under the same acceleration. 77 const float kNoisyMagnitudeDeviation = 1.0f; 78 79 // The angle which the screen has to be rotated past before the display will 80 // rotate to match it (i.e. 45.0f is no stickiness). 81 const float kDisplayRotationStickyAngleDegrees = 60.0f; 82 83 // The minimum acceleration in m/s^2 in a direction required to trigger screen 84 // rotation. This prevents rapid toggling of rotation when the device is near 85 // flat and there is very little screen aligned force on it. The value is 86 // effectively the sine of the rise angle required times the acceleration due 87 // to gravity, with the current value requiring at least a 25 degree rise. 88 const float kMinimumAccelerationScreenRotation = 4.2f; 89 90 const float kRadiansToDegrees = 180.0f / 3.14159265f; 91 92 // Returns the angle between |base| and |other| in degrees. 93 float AngleBetweenVectorsInDegrees(const gfx::Vector3dF& base, 94 const gfx::Vector3dF& other) { 95 return acos(gfx::DotProduct(base, other) / 96 base.Length() / other.Length()) * kRadiansToDegrees; 97 } 98 99 // Returns the clockwise angle between |base| and |other| where |normal| is the 100 // normal of the virtual surface to measure clockwise according to. 101 float ClockwiseAngleBetweenVectorsInDegrees(const gfx::Vector3dF& base, 102 const gfx::Vector3dF& other, 103 const gfx::Vector3dF& normal) { 104 float angle = AngleBetweenVectorsInDegrees(base, other); 105 gfx::Vector3dF cross(base); 106 cross.Cross(other); 107 // If the dot product of this cross product is normal, it means that the 108 // shortest angle between |base| and |other| was counterclockwise with respect 109 // to the surface represented by |normal| and this angle must be reversed. 110 if (gfx::DotProduct(cross, normal) > 0.0f) 111 angle = 360.0f - angle; 112 return angle; 113 } 114 115 } // namespace 116 117 MaximizeModeController::MaximizeModeController() 118 : rotation_locked_(false), 119 have_seen_accelerometer_data_(false), 120 ignore_display_configuration_updates_(false), 121 shutting_down_(false), 122 user_rotation_(gfx::Display::ROTATE_0), 123 last_touchview_transition_time_(base::Time::Now()), 124 tick_clock_(new base::DefaultTickClock()), 125 lid_is_closed_(false) { 126 Shell::GetInstance()->accelerometer_controller()->AddObserver(this); 127 Shell::GetInstance()->AddShellObserver(this); 128 #if defined(OS_CHROMEOS) 129 chromeos::DBusThreadManager::Get()-> 130 GetPowerManagerClient()->AddObserver(this); 131 #endif // OS_CHROMEOS 132 } 133 134 MaximizeModeController::~MaximizeModeController() { 135 Shell::GetInstance()->RemoveShellObserver(this); 136 Shell::GetInstance()->accelerometer_controller()->RemoveObserver(this); 137 #if defined(OS_CHROMEOS) 138 chromeos::DBusThreadManager::Get()-> 139 GetPowerManagerClient()->RemoveObserver(this); 140 #endif // OS_CHROMEOS 141 } 142 143 void MaximizeModeController::SetRotationLocked(bool rotation_locked) { 144 if (rotation_locked_ == rotation_locked) 145 return; 146 base::AutoReset<bool> auto_ignore_display_configuration_updates( 147 &ignore_display_configuration_updates_, true); 148 rotation_locked_ = rotation_locked; 149 Shell::GetInstance()->display_manager()-> 150 RegisterDisplayRotationProperties(rotation_locked_, current_rotation_); 151 FOR_EACH_OBSERVER(Observer, observers_, 152 OnRotationLockChanged(rotation_locked_)); 153 } 154 155 void MaximizeModeController::AddObserver(Observer* observer) { 156 observers_.AddObserver(observer); 157 } 158 159 void MaximizeModeController::RemoveObserver(Observer* observer) { 160 observers_.RemoveObserver(observer); 161 } 162 163 bool MaximizeModeController::CanEnterMaximizeMode() { 164 // If we have ever seen accelerometer data, then HandleHingeRotation may 165 // trigger maximize mode at some point in the future. 166 // The --enable-touch-view-testing switch can also mean that we may enter 167 // maximize mode. 168 return have_seen_accelerometer_data_ || 169 CommandLine::ForCurrentProcess()->HasSwitch( 170 switches::kAshEnableTouchViewTesting); 171 } 172 173 void MaximizeModeController::EnableMaximizeModeWindowManager(bool enable) { 174 if (enable && !maximize_mode_window_manager_.get()) { 175 maximize_mode_window_manager_.reset(new MaximizeModeWindowManager()); 176 // TODO(jonross): Move the maximize mode notifications from ShellObserver 177 // to MaximizeModeController::Observer 178 Shell::GetInstance()->OnMaximizeModeStarted(); 179 } else if (!enable && maximize_mode_window_manager_.get()) { 180 maximize_mode_window_manager_.reset(); 181 Shell::GetInstance()->OnMaximizeModeEnded(); 182 } 183 } 184 185 bool MaximizeModeController::IsMaximizeModeWindowManagerEnabled() const { 186 return maximize_mode_window_manager_.get() != NULL; 187 } 188 189 void MaximizeModeController::AddWindow(aura::Window* window) { 190 if (IsMaximizeModeWindowManagerEnabled()) 191 maximize_mode_window_manager_->AddWindow(window); 192 } 193 194 void MaximizeModeController::Shutdown() { 195 shutting_down_ = true; 196 LeaveMaximizeMode(); 197 } 198 199 void MaximizeModeController::OnAccelerometerUpdated( 200 const ui::AccelerometerUpdate& update) { 201 bool first_accelerometer_update = !have_seen_accelerometer_data_; 202 have_seen_accelerometer_data_ = true; 203 204 // Ignore the reading if it appears unstable. The reading is considered 205 // unstable if it deviates too much from gravity and/or the magnitude of the 206 // reading from the lid differs too much from the reading from the base. 207 float base_magnitude = 208 update.has(ui::ACCELEROMETER_SOURCE_ATTACHED_KEYBOARD) ? 209 update.get(ui::ACCELEROMETER_SOURCE_ATTACHED_KEYBOARD).Length() : 210 0.0f; 211 float lid_magnitude = update.has(ui::ACCELEROMETER_SOURCE_SCREEN) ? 212 update.get(ui::ACCELEROMETER_SOURCE_SCREEN).Length() : 0.0f; 213 bool lid_stable = update.has(ui::ACCELEROMETER_SOURCE_SCREEN) && 214 std::abs(lid_magnitude - kMeanGravity) <= kDeviationFromGravityThreshold; 215 bool base_angle_stable = lid_stable && 216 update.has(ui::ACCELEROMETER_SOURCE_ATTACHED_KEYBOARD) && 217 std::abs(base_magnitude - lid_magnitude) <= kNoisyMagnitudeDeviation && 218 std::abs(base_magnitude - kMeanGravity) <= kDeviationFromGravityThreshold; 219 220 if (base_angle_stable) { 221 // Responding to the hinge rotation can change the maximize mode state which 222 // affects screen rotation, so we handle hinge rotation first. 223 HandleHingeRotation( 224 update.get(ui::ACCELEROMETER_SOURCE_ATTACHED_KEYBOARD), 225 update.get(ui::ACCELEROMETER_SOURCE_SCREEN)); 226 } 227 if (lid_stable) 228 HandleScreenRotation(update.get(ui::ACCELEROMETER_SOURCE_SCREEN)); 229 230 if (first_accelerometer_update) { 231 // On the first accelerometer update we will know if we have entered 232 // maximize mode or not. Update the preferences to reflect the current 233 // state. 234 Shell::GetInstance()->display_manager()-> 235 RegisterDisplayRotationProperties(rotation_locked_, current_rotation_); 236 } 237 } 238 239 void MaximizeModeController::OnDisplayConfigurationChanged() { 240 if (ignore_display_configuration_updates_) 241 return; 242 DisplayManager* display_manager = Shell::GetInstance()->display_manager(); 243 gfx::Display::Rotation user_rotation = display_manager-> 244 GetDisplayInfo(gfx::Display::InternalDisplayId()).rotation(); 245 if (user_rotation != current_rotation_) { 246 // A user may change other display configuration settings. When the user 247 // does change the rotation setting, then lock rotation to prevent the 248 // accelerometer from erasing their change. 249 SetRotationLocked(true); 250 user_rotation_ = user_rotation; 251 current_rotation_ = user_rotation; 252 } 253 } 254 255 #if defined(OS_CHROMEOS) 256 void MaximizeModeController::LidEventReceived(bool open, 257 const base::TimeTicks& time) { 258 if (open) 259 last_lid_open_time_ = time; 260 lid_is_closed_ = !open; 261 LeaveMaximizeMode(); 262 } 263 264 void MaximizeModeController::SuspendImminent() { 265 RecordTouchViewStateTransition(); 266 } 267 268 void MaximizeModeController::SuspendDone( 269 const base::TimeDelta& sleep_duration) { 270 last_touchview_transition_time_ = base::Time::Now(); 271 } 272 #endif // OS_CHROMEOS 273 274 void MaximizeModeController::HandleHingeRotation(const gfx::Vector3dF& base, 275 const gfx::Vector3dF& lid) { 276 static const gfx::Vector3dF hinge_vector(1.0f, 0.0f, 0.0f); 277 bool maximize_mode_engaged = IsMaximizeModeWindowManagerEnabled(); 278 // Ignore the component of acceleration parallel to the hinge for the purposes 279 // of hinge angle calculation. 280 gfx::Vector3dF base_flattened(base); 281 gfx::Vector3dF lid_flattened(lid); 282 base_flattened.set_x(0.0f); 283 lid_flattened.set_x(0.0f); 284 285 // As the hinge approaches a vertical angle, the base and lid accelerometers 286 // approach the same values making any angle calculations highly inaccurate. 287 // Bail out early when it is too close. 288 if (base_flattened.Length() < kHingeAngleDetectionThreshold || 289 lid_flattened.Length() < kHingeAngleDetectionThreshold) { 290 return; 291 } 292 293 // Compute the angle between the base and the lid. 294 float lid_angle = 180.0f - ClockwiseAngleBetweenVectorsInDegrees( 295 base_flattened, lid_flattened, hinge_vector); 296 if (lid_angle < 0.0f) 297 lid_angle += 360.0f; 298 299 bool is_angle_stable = lid_angle >= kMinStableAngle && 300 lid_angle <= kMaxStableAngle; 301 302 // Clear the last_lid_open_time_ for a stable reading so that there is less 303 // chance of a delay if the lid is moved from the close state to the fully 304 // open state very quickly. 305 if (is_angle_stable) 306 last_lid_open_time_ = base::TimeTicks(); 307 308 // Toggle maximize mode on or off when corresponding thresholds are passed. 309 // TODO(flackr): Make MaximizeModeController own the MaximizeModeWindowManager 310 // such that observations of state changes occur after the change and shell 311 // has fewer states to track. 312 if (maximize_mode_engaged && is_angle_stable && 313 lid_angle <= kExitMaximizeModeAngle) { 314 LeaveMaximizeMode(); 315 } else if (!lid_is_closed_ && !maximize_mode_engaged && 316 lid_angle >= kEnterMaximizeModeAngle && 317 (is_angle_stable || !WasLidOpenedRecently())) { 318 EnterMaximizeMode(); 319 } 320 } 321 322 void MaximizeModeController::HandleScreenRotation(const gfx::Vector3dF& lid) { 323 bool maximize_mode_engaged = IsMaximizeModeWindowManagerEnabled(); 324 325 // TODO(jonross): track the updated rotation angle even when locked. So that 326 // when rotation lock is removed the accelerometer rotation can be applied 327 // without waiting for the next update. 328 if (!maximize_mode_engaged || rotation_locked_) 329 return; 330 331 DisplayManager* display_manager = 332 Shell::GetInstance()->display_manager(); 333 gfx::Display::Rotation current_rotation = display_manager->GetDisplayInfo( 334 gfx::Display::InternalDisplayId()).rotation(); 335 336 // After determining maximize mode state, determine if the screen should 337 // be rotated. 338 gfx::Vector3dF lid_flattened(lid.x(), lid.y(), 0.0f); 339 float lid_flattened_length = lid_flattened.Length(); 340 // When the lid is close to being flat, don't change rotation as it is too 341 // sensitive to slight movements. 342 if (lid_flattened_length < kMinimumAccelerationScreenRotation) 343 return; 344 345 // The reference vector is the angle of gravity when the device is rotated 346 // clockwise by 45 degrees. Computing the angle between this vector and 347 // gravity we can easily determine the expected display rotation. 348 static const gfx::Vector3dF rotation_reference(-1.0f, -1.0f, 0.0f); 349 350 // Set the down vector to match the expected direction of gravity given the 351 // last configured rotation. This is used to enforce a stickiness that the 352 // user must overcome to rotate the display and prevents frequent rotations 353 // when holding the device near 45 degrees. 354 gfx::Vector3dF down(0.0f, 0.0f, 0.0f); 355 if (current_rotation == gfx::Display::ROTATE_0) 356 down.set_y(-1.0f); 357 else if (current_rotation == gfx::Display::ROTATE_90) 358 down.set_x(-1.0f); 359 else if (current_rotation == gfx::Display::ROTATE_180) 360 down.set_y(1.0f); 361 else 362 down.set_x(1.0f); 363 364 // Don't rotate if the screen has not passed the threshold. 365 if (AngleBetweenVectorsInDegrees(down, lid_flattened) < 366 kDisplayRotationStickyAngleDegrees) { 367 return; 368 } 369 370 float angle = ClockwiseAngleBetweenVectorsInDegrees(rotation_reference, 371 lid_flattened, gfx::Vector3dF(0.0f, 0.0f, -1.0f)); 372 373 gfx::Display::Rotation new_rotation = gfx::Display::ROTATE_90; 374 if (angle < 90.0f) 375 new_rotation = gfx::Display::ROTATE_0; 376 else if (angle < 180.0f) 377 new_rotation = gfx::Display::ROTATE_270; 378 else if (angle < 270.0f) 379 new_rotation = gfx::Display::ROTATE_180; 380 381 if (new_rotation != current_rotation) 382 SetDisplayRotation(display_manager, new_rotation); 383 } 384 385 void MaximizeModeController::SetDisplayRotation( 386 DisplayManager* display_manager, 387 gfx::Display::Rotation rotation) { 388 base::AutoReset<bool> auto_ignore_display_configuration_updates( 389 &ignore_display_configuration_updates_, true); 390 current_rotation_ = rotation; 391 display_manager->SetDisplayRotation(gfx::Display::InternalDisplayId(), 392 rotation); 393 } 394 395 void MaximizeModeController::EnterMaximizeMode() { 396 if (IsMaximizeModeWindowManagerEnabled()) 397 return; 398 DisplayManager* display_manager = Shell::GetInstance()->display_manager(); 399 if (display_manager->HasInternalDisplay()) { 400 current_rotation_ = user_rotation_ = display_manager-> 401 GetDisplayInfo(gfx::Display::InternalDisplayId()).rotation(); 402 LoadDisplayRotationProperties(); 403 } 404 EnableMaximizeModeWindowManager(true); 405 #if defined(USE_X11) 406 event_blocker_.reset(new ScopedDisableInternalMouseAndKeyboardX11); 407 #endif 408 Shell::GetInstance()->display_controller()->AddObserver(this); 409 } 410 411 void MaximizeModeController::LeaveMaximizeMode() { 412 if (!IsMaximizeModeWindowManagerEnabled()) 413 return; 414 DisplayManager* display_manager = Shell::GetInstance()->display_manager(); 415 if (display_manager->HasInternalDisplay()) { 416 gfx::Display::Rotation current_rotation = display_manager-> 417 GetDisplayInfo(gfx::Display::InternalDisplayId()).rotation(); 418 if (current_rotation != user_rotation_) 419 SetDisplayRotation(display_manager, user_rotation_); 420 } 421 if (!shutting_down_) 422 SetRotationLocked(false); 423 EnableMaximizeModeWindowManager(false); 424 event_blocker_.reset(); 425 Shell::GetInstance()->display_controller()->RemoveObserver(this); 426 } 427 428 // Called after maximize mode has started, windows might still animate though. 429 void MaximizeModeController::OnMaximizeModeStarted() { 430 RecordTouchViewStateTransition(); 431 } 432 433 // Called after maximize mode has ended, windows might still be returning to 434 // their original position. 435 void MaximizeModeController::OnMaximizeModeEnded() { 436 RecordTouchViewStateTransition(); 437 } 438 439 void MaximizeModeController::RecordTouchViewStateTransition() { 440 if (CanEnterMaximizeMode()) { 441 base::Time current_time = base::Time::Now(); 442 base::TimeDelta delta = current_time - last_touchview_transition_time_; 443 if (IsMaximizeModeWindowManagerEnabled()) { 444 UMA_HISTOGRAM_LONG_TIMES("Ash.TouchView.TouchViewInactive", delta); 445 total_non_touchview_time_ += delta; 446 } else { 447 UMA_HISTOGRAM_LONG_TIMES("Ash.TouchView.TouchViewActive", delta); 448 total_touchview_time_ += delta; 449 } 450 last_touchview_transition_time_ = current_time; 451 } 452 } 453 454 void MaximizeModeController::LoadDisplayRotationProperties() { 455 DisplayManager* display_manager = Shell::GetInstance()->display_manager(); 456 if (!display_manager->registered_internal_display_rotation_lock()) 457 return; 458 459 SetDisplayRotation(display_manager, 460 display_manager->registered_internal_display_rotation()); 461 SetRotationLocked(true); 462 } 463 464 void MaximizeModeController::OnAppTerminating() { 465 if (CanEnterMaximizeMode()) { 466 RecordTouchViewStateTransition(); 467 UMA_HISTOGRAM_CUSTOM_COUNTS("Ash.TouchView.TouchViewActiveTotal", 468 total_touchview_time_.InMinutes(), 469 1, base::TimeDelta::FromDays(7).InMinutes(), 50); 470 UMA_HISTOGRAM_CUSTOM_COUNTS("Ash.TouchView.TouchViewInactiveTotal", 471 total_non_touchview_time_.InMinutes(), 472 1, base::TimeDelta::FromDays(7).InMinutes(), 50); 473 base::TimeDelta total_runtime = total_touchview_time_ + 474 total_non_touchview_time_; 475 if (total_runtime.InSeconds() > 0) { 476 UMA_HISTOGRAM_PERCENTAGE("Ash.TouchView.TouchViewActivePercentage", 477 100 * total_touchview_time_.InSeconds() / total_runtime.InSeconds()); 478 } 479 } 480 Shell::GetInstance()->display_controller()->RemoveObserver(this); 481 } 482 483 bool MaximizeModeController::WasLidOpenedRecently() const { 484 if (last_lid_open_time_.is_null()) 485 return false; 486 487 base::TimeTicks now = tick_clock_->NowTicks(); 488 DCHECK(now >= last_lid_open_time_); 489 base::TimeDelta elapsed_time = now - last_lid_open_time_; 490 return elapsed_time <= kLidRecentlyOpenedDuration; 491 } 492 493 void MaximizeModeController::SetTickClockForTest( 494 scoped_ptr<base::TickClock> tick_clock) { 495 DCHECK(tick_clock_); 496 tick_clock_ = tick_clock.Pass(); 497 } 498 499 } // namespace ash 500