maximize_mode_controller.cc revision a02191e04bc25c4935f804f2c080ae28663d096d
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/accelerometer/accelerometer_controller.h" 8#include "ash/display/display_manager.h" 9#include "ash/shell.h" 10#include "ash/wm/maximize_mode/maximize_mode_event_blocker.h" 11#include "ui/gfx/vector3d_f.h" 12 13namespace ash { 14 15namespace { 16 17// The hinge angle at which to enter maximize mode. 18const float kEnterMaximizeModeAngle = 200.0f; 19 20// The angle at which to exit maximize mode, this is specifically less than the 21// angle to enter maximize mode to prevent rapid toggling when near the angle. 22const float kExitMaximizeModeAngle = 160.0f; 23 24// When the lid is fully open 360 degrees, the accelerometer readings can 25// occasionally appear as though the lid is almost closed. If the lid appears 26// near closed but the device is on we assume it is an erroneous reading from 27// it being open 360 degrees. 28const float kFullyOpenAngleErrorTolerance = 20.0f; 29 30// When the device approaches vertical orientation (i.e. portrait orientation) 31// the accelerometers for the base and lid approach the same values (i.e. 32// gravity pointing in the direction of the hinge). When this happens we cannot 33// compute the hinge angle reliably and must turn ignore accelerometer readings. 34// This is the angle from vertical under which we will not compute a hinge 35// angle. 36const float kHingeAxisAlignedThreshold = 15.0f; 37 38// The maximum deviation from the acceleration expected due to gravity under 39// which to detect hinge angle and screen rotation. 40const float kDeviationFromGravityThreshold = 0.1f; 41 42// The maximum deviation between the magnitude of the two accelerometers under 43// which to detect hinge angle and screen rotation. These accelerometers are 44// attached to the same physical device and so should be under the same 45// acceleration. 46const float kNoisyMagnitudeDeviation = 0.1f; 47 48// The angle which the screen has to be rotated past before the display will 49// rotate to match it (i.e. 45.0f is no stickiness). 50const float kDisplayRotationStickyAngleDegrees = 60.0f; 51 52// The minimum acceleration in a direction required to trigger screen rotation. 53// This prevents rapid toggling of rotation when the device is near flat and 54// there is very little screen aligned force on it. 55const float kMinimumAccelerationScreenRotation = 0.3f; 56 57const float kRadiansToDegrees = 180.0f / 3.14159265f; 58 59// Returns the angle between |base| and |other| in degrees. 60float AngleBetweenVectorsInDegrees(const gfx::Vector3dF& base, 61 const gfx::Vector3dF& other) { 62 return acos(gfx::DotProduct(base, other) / 63 base.Length() / other.Length()) * kRadiansToDegrees; 64} 65 66// Returns the clockwise angle between |base| and |other| where |normal| is the 67// normal of the virtual surface to measure clockwise according to. 68float ClockwiseAngleBetweenVectorsInDegrees(const gfx::Vector3dF& base, 69 const gfx::Vector3dF& other, 70 const gfx::Vector3dF& normal) { 71 float angle = AngleBetweenVectorsInDegrees(base, other); 72 gfx::Vector3dF cross(base); 73 cross.Cross(other); 74 // If the dot product of this cross product is normal, it means that the 75 // shortest angle between |base| and |other| was counterclockwise with respect 76 // to the surface represented by |normal| and this angle must be reversed. 77 if (gfx::DotProduct(cross, normal) > 0.0f) 78 angle = 360.0f - angle; 79 return angle; 80} 81 82} // namespace 83 84MaximizeModeController::MaximizeModeController() 85 : rotation_locked_(false) { 86 Shell::GetInstance()->accelerometer_controller()->AddObserver(this); 87} 88 89MaximizeModeController::~MaximizeModeController() { 90 Shell::GetInstance()->accelerometer_controller()->RemoveObserver(this); 91} 92 93void MaximizeModeController::OnAccelerometerUpdated( 94 const gfx::Vector3dF& base, 95 const gfx::Vector3dF& lid) { 96 // Ignore the reading if it appears unstable. The reading is considered 97 // unstable if it deviates too much from gravity and/or the magnitude of the 98 // reading from the lid differs too much from the reading from the base. 99 float base_magnitude = base.Length(); 100 float lid_magnitude = lid.Length(); 101 if (std::abs(base_magnitude - lid_magnitude) > kNoisyMagnitudeDeviation || 102 std::abs(base_magnitude - 1.0f) > kDeviationFromGravityThreshold || 103 std::abs(lid_magnitude - 1.0f) > kDeviationFromGravityThreshold) { 104 return; 105 } 106 107 // Responding to the hinge rotation can change the maximize mode state which 108 // affects screen rotation, so we handle hinge rotation first. 109 HandleHingeRotation(base, lid); 110 HandleScreenRotation(lid); 111} 112 113void MaximizeModeController::HandleHingeRotation(const gfx::Vector3dF& base, 114 const gfx::Vector3dF& lid) { 115 static const gfx::Vector3dF hinge_vector(0.0f, 1.0f, 0.0f); 116 bool maximize_mode_engaged = 117 Shell::GetInstance()->IsMaximizeModeWindowManagerEnabled(); 118 119 // As the hinge approaches a vertical angle, the base and lid accelerometers 120 // approach the same values making any angle calculations highly inaccurate. 121 // Bail out early when it is too close. 122 float hinge_angle = AngleBetweenVectorsInDegrees(base, hinge_vector); 123 if (hinge_angle < kHingeAxisAlignedThreshold || 124 hinge_angle > 180.0f - kHingeAxisAlignedThreshold) { 125 return; 126 } 127 128 // Compute the angle between the base and the lid. 129 float angle = ClockwiseAngleBetweenVectorsInDegrees(base, lid, hinge_vector); 130 131 // Toggle maximize mode on or off when corresponding thresholds are passed. 132 // TODO(flackr): Make MaximizeModeController own the MaximizeModeWindowManager 133 // such that observations of state changes occur after the change and shell 134 // has fewer states to track. 135 if (maximize_mode_engaged && 136 angle > kFullyOpenAngleErrorTolerance && 137 angle < kExitMaximizeModeAngle) { 138 Shell::GetInstance()->EnableMaximizeModeWindowManager(false); 139 event_blocker_.reset(); 140 } else if (!maximize_mode_engaged && 141 angle > kEnterMaximizeModeAngle) { 142 Shell::GetInstance()->EnableMaximizeModeWindowManager(true); 143 event_blocker_.reset(new MaximizeModeEventBlocker); 144 } 145} 146 147void MaximizeModeController::HandleScreenRotation(const gfx::Vector3dF& lid) { 148 bool maximize_mode_engaged = 149 Shell::GetInstance()->IsMaximizeModeWindowManagerEnabled(); 150 151 DisplayManager* display_manager = 152 Shell::GetInstance()->display_manager(); 153 gfx::Display::Rotation current_rotation = display_manager->GetDisplayInfo( 154 gfx::Display::InternalDisplayId()).rotation(); 155 156 // If maximize mode is not engaged, ensure the screen is not rotated and 157 // do not rotate to match the current device orientation. 158 if (!maximize_mode_engaged) { 159 if (current_rotation != gfx::Display::ROTATE_0) { 160 // TODO(flackr): Currently this will prevent setting a manual rotation on 161 // the screen of a device with an accelerometer, this should only set it 162 // back to ROTATE_0 if it was last set by the accelerometer. 163 // Also, SetDisplayRotation will save the setting to the local store, 164 // this should be stored in a way that we can distinguish what the 165 // rotation was set by. 166 display_manager->SetDisplayRotation(gfx::Display::InternalDisplayId(), 167 gfx::Display::ROTATE_0); 168 } 169 rotation_locked_ = false; 170 return; 171 } 172 173 if (rotation_locked_) 174 return; 175 176 // After determining maximize mode state, determine if the screen should 177 // be rotated. 178 gfx::Vector3dF lid_flattened(lid.x(), lid.y(), 0.0f); 179 float lid_flattened_length = lid_flattened.Length(); 180 // When the lid is close to being flat, don't change rotation as it is too 181 // sensitive to slight movements. 182 if (lid_flattened_length < kMinimumAccelerationScreenRotation) 183 return; 184 185 // The reference vector is the angle of gravity when the device is rotated 186 // clockwise by 45 degrees. Computing the angle between this vector and 187 // gravity we can easily determine the expected display rotation. 188 static gfx::Vector3dF rotation_reference(-1.0f, 1.0f, 0.0f); 189 190 // Set the down vector to match the expected direction of gravity given the 191 // last configured rotation. This is used to enforce a stickiness that the 192 // user must overcome to rotate the display and prevents frequent rotations 193 // when holding the device near 45 degrees. 194 gfx::Vector3dF down(0.0f, 0.0f, 0.0f); 195 if (current_rotation == gfx::Display::ROTATE_0) 196 down.set_x(-1.0f); 197 else if (current_rotation == gfx::Display::ROTATE_90) 198 down.set_y(1.0f); 199 else if (current_rotation == gfx::Display::ROTATE_180) 200 down.set_x(1.0f); 201 else 202 down.set_y(-1.0f); 203 204 // Don't rotate if the screen has not passed the threshold. 205 if (AngleBetweenVectorsInDegrees(down, lid_flattened) < 206 kDisplayRotationStickyAngleDegrees) { 207 return; 208 } 209 210 float angle = ClockwiseAngleBetweenVectorsInDegrees(rotation_reference, 211 lid_flattened, gfx::Vector3dF(0.0f, 0.0f, -1.0f)); 212 213 gfx::Display::Rotation new_rotation = gfx::Display::ROTATE_90; 214 if (angle < 90.0f) 215 new_rotation = gfx::Display::ROTATE_0; 216 else if (angle < 180.0f) 217 new_rotation = gfx::Display::ROTATE_270; 218 else if (angle < 270.0f) 219 new_rotation = gfx::Display::ROTATE_180; 220 221 // When exiting maximize mode return rotation to 0. When entering, rotate to 222 // match screen orientation. 223 if (new_rotation == gfx::Display::ROTATE_0 || 224 maximize_mode_engaged) { 225 display_manager->SetDisplayRotation(gfx::Display::InternalDisplayId(), 226 new_rotation); 227 } 228} 229 230} // namespace ash 231