Home | History | Annotate | Download | only in maximize_mode
      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