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_event_blocker.h"
     14 #include "ash/wm/maximize_mode/maximize_mode_window_manager.h"
     15 #include "base/auto_reset.h"
     16 #include "base/command_line.h"
     17 #include "base/metrics/histogram.h"
     18 #include "ui/base/accelerators/accelerator.h"
     19 #include "ui/events/event.h"
     20 #include "ui/events/event_handler.h"
     21 #include "ui/events/keycodes/keyboard_codes.h"
     22 #include "ui/gfx/vector3d_f.h"
     23 
     24 namespace ash {
     25 
     26 namespace {
     27 
     28 // The hinge angle at which to enter maximize mode.
     29 const float kEnterMaximizeModeAngle = 200.0f;
     30 
     31 // The angle at which to exit maximize mode, this is specifically less than the
     32 // angle to enter maximize mode to prevent rapid toggling when near the angle.
     33 const float kExitMaximizeModeAngle = 160.0f;
     34 
     35 // When the lid is fully open 360 degrees, the accelerometer readings can
     36 // occasionally appear as though the lid is almost closed. If the lid appears
     37 // near closed but the device is on we assume it is an erroneous reading from
     38 // it being open 360 degrees.
     39 const float kFullyOpenAngleErrorTolerance = 20.0f;
     40 
     41 // When the device approaches vertical orientation (i.e. portrait orientation)
     42 // the accelerometers for the base and lid approach the same values (i.e.
     43 // gravity pointing in the direction of the hinge). When this happens we cannot
     44 // compute the hinge angle reliably and must turn ignore accelerometer readings.
     45 // This is the minimum acceleration perpendicular to the hinge under which to
     46 // detect hinge angle.
     47 const float kHingeAngleDetectionThreshold = 0.25f;
     48 
     49 // The maximum deviation from the acceleration expected due to gravity under
     50 // which to detect hinge angle and screen rotation.
     51 const float kDeviationFromGravityThreshold = 0.1f;
     52 
     53 // The maximum deviation between the magnitude of the two accelerometers under
     54 // which to detect hinge angle and screen rotation. These accelerometers are
     55 // attached to the same physical device and so should be under the same
     56 // acceleration.
     57 const float kNoisyMagnitudeDeviation = 0.1f;
     58 
     59 // The angle which the screen has to be rotated past before the display will
     60 // rotate to match it (i.e. 45.0f is no stickiness).
     61 const float kDisplayRotationStickyAngleDegrees = 60.0f;
     62 
     63 // The minimum acceleration in a direction required to trigger screen rotation.
     64 // This prevents rapid toggling of rotation when the device is near flat and
     65 // there is very little screen aligned force on it. The value is effectively the
     66 // sine of the rise angle required, with the current value requiring at least a
     67 // 25 degree rise.
     68 const float kMinimumAccelerationScreenRotation = 0.42f;
     69 
     70 const float kRadiansToDegrees = 180.0f / 3.14159265f;
     71 
     72 // Returns the angle between |base| and |other| in degrees.
     73 float AngleBetweenVectorsInDegrees(const gfx::Vector3dF& base,
     74                                  const gfx::Vector3dF& other) {
     75   return acos(gfx::DotProduct(base, other) /
     76               base.Length() / other.Length()) * kRadiansToDegrees;
     77 }
     78 
     79 // Returns the clockwise angle between |base| and |other| where |normal| is the
     80 // normal of the virtual surface to measure clockwise according to.
     81 float ClockwiseAngleBetweenVectorsInDegrees(const gfx::Vector3dF& base,
     82                                             const gfx::Vector3dF& other,
     83                                             const gfx::Vector3dF& normal) {
     84   float angle = AngleBetweenVectorsInDegrees(base, other);
     85   gfx::Vector3dF cross(base);
     86   cross.Cross(other);
     87   // If the dot product of this cross product is normal, it means that the
     88   // shortest angle between |base| and |other| was counterclockwise with respect
     89   // to the surface represented by |normal| and this angle must be reversed.
     90   if (gfx::DotProduct(cross, normal) > 0.0f)
     91     angle = 360.0f - angle;
     92   return angle;
     93 }
     94 
     95 #if defined(OS_CHROMEOS)
     96 
     97 // An event handler which listens for a volume down + power keypress and
     98 // triggers a screenshot when this is seen.
     99 class ScreenshotActionHandler : public ui::EventHandler {
    100  public:
    101   ScreenshotActionHandler();
    102   virtual ~ScreenshotActionHandler();
    103 
    104   // ui::EventHandler:
    105   virtual void OnKeyEvent(ui::KeyEvent* event) OVERRIDE;
    106 
    107  private:
    108   bool volume_down_pressed_;
    109 
    110   DISALLOW_COPY_AND_ASSIGN(ScreenshotActionHandler);
    111 };
    112 
    113 ScreenshotActionHandler::ScreenshotActionHandler()
    114     : volume_down_pressed_(false) {
    115   Shell::GetInstance()->PrependPreTargetHandler(this);
    116 }
    117 
    118 ScreenshotActionHandler::~ScreenshotActionHandler() {
    119   Shell::GetInstance()->RemovePreTargetHandler(this);
    120 }
    121 
    122 void ScreenshotActionHandler::OnKeyEvent(ui::KeyEvent* event) {
    123   if (event->key_code() == ui::VKEY_VOLUME_DOWN) {
    124     volume_down_pressed_ = event->type() == ui::ET_KEY_PRESSED ||
    125                            event->type() == ui::ET_TRANSLATED_KEY_PRESS;
    126   } else if (volume_down_pressed_ &&
    127              event->key_code() == ui::VKEY_POWER &&
    128              event->type() == ui::ET_KEY_PRESSED) {
    129     Shell::GetInstance()->accelerator_controller()->PerformAction(
    130         ash::TAKE_SCREENSHOT, ui::Accelerator());
    131   }
    132 }
    133 
    134 #endif  // OS_CHROMEOS
    135 
    136 }  // namespace
    137 
    138 MaximizeModeController::MaximizeModeController()
    139     : rotation_locked_(false),
    140       have_seen_accelerometer_data_(false),
    141       in_set_screen_rotation_(false),
    142       user_rotation_(gfx::Display::ROTATE_0),
    143       last_touchview_transition_time_(base::Time::Now()) {
    144   Shell::GetInstance()->accelerometer_controller()->AddObserver(this);
    145   Shell::GetInstance()->AddShellObserver(this);
    146 }
    147 
    148 MaximizeModeController::~MaximizeModeController() {
    149   Shell::GetInstance()->RemoveShellObserver(this);
    150   Shell::GetInstance()->accelerometer_controller()->RemoveObserver(this);
    151 }
    152 
    153 void MaximizeModeController::SetRotationLocked(bool rotation_locked) {
    154   if (rotation_locked_ == rotation_locked)
    155     return;
    156   rotation_locked_ = rotation_locked;
    157   FOR_EACH_OBSERVER(Observer, observers_,
    158                     OnRotationLockChanged(rotation_locked_));
    159 }
    160 
    161 void MaximizeModeController::AddObserver(Observer* observer) {
    162   observers_.AddObserver(observer);
    163 }
    164 
    165 void MaximizeModeController::RemoveObserver(Observer* observer) {
    166   observers_.RemoveObserver(observer);
    167 }
    168 
    169 bool MaximizeModeController::CanEnterMaximizeMode() {
    170   // If we have ever seen accelerometer data, then HandleHingeRotation may
    171   // trigger maximize mode at some point in the future.
    172   // The --enable-touch-view-testing switch can also mean that we may enter
    173   // maximize mode.
    174   return have_seen_accelerometer_data_ ||
    175          CommandLine::ForCurrentProcess()->HasSwitch(
    176              switches::kAshEnableTouchViewTesting);
    177 }
    178 
    179 void MaximizeModeController::EnableMaximizeModeWindowManager(bool enable) {
    180   if (enable && !maximize_mode_window_manager_.get()) {
    181     maximize_mode_window_manager_.reset(new MaximizeModeWindowManager());
    182     // TODO(jonross): Move the maximize mode notifications from ShellObserver
    183     // to MaximizeModeController::Observer
    184     Shell::GetInstance()->OnMaximizeModeStarted();
    185   } else if (!enable && maximize_mode_window_manager_.get()) {
    186     maximize_mode_window_manager_.reset();
    187     Shell::GetInstance()->OnMaximizeModeEnded();
    188   }
    189 }
    190 
    191 bool MaximizeModeController::IsMaximizeModeWindowManagerEnabled() const {
    192   return maximize_mode_window_manager_.get() != NULL;
    193 }
    194 
    195 void MaximizeModeController::AddWindow(aura::Window* window) {
    196   if (IsMaximizeModeWindowManagerEnabled())
    197     maximize_mode_window_manager_->AddWindow(window);
    198 }
    199 
    200 void MaximizeModeController::Shutdown() {
    201   maximize_mode_window_manager_.reset();
    202   Shell::GetInstance()->OnMaximizeModeEnded();
    203 }
    204 
    205 void MaximizeModeController::OnAccelerometerUpdated(
    206     const gfx::Vector3dF& base,
    207     const gfx::Vector3dF& lid) {
    208   have_seen_accelerometer_data_ = true;
    209 
    210   // Ignore the reading if it appears unstable. The reading is considered
    211   // unstable if it deviates too much from gravity and/or the magnitude of the
    212   // reading from the lid differs too much from the reading from the base.
    213   float base_magnitude = base.Length();
    214   float lid_magnitude = lid.Length();
    215   if (std::abs(base_magnitude - lid_magnitude) > kNoisyMagnitudeDeviation ||
    216       std::abs(base_magnitude - 1.0f) > kDeviationFromGravityThreshold ||
    217       std::abs(lid_magnitude - 1.0f) > kDeviationFromGravityThreshold) {
    218       return;
    219   }
    220 
    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(base, lid);
    224   HandleScreenRotation(lid);
    225 }
    226 
    227 void MaximizeModeController::OnDisplayConfigurationChanged() {
    228   if (in_set_screen_rotation_)
    229     return;
    230   DisplayManager* display_manager = Shell::GetInstance()->display_manager();
    231   gfx::Display::Rotation user_rotation = display_manager->
    232       GetDisplayInfo(gfx::Display::InternalDisplayId()).rotation();
    233   if (user_rotation != current_rotation_) {
    234     // A user may change other display configuration settings. When the user
    235     // does change the rotation setting, then lock rotation to prevent the
    236     // accelerometer from erasing their change.
    237     SetRotationLocked(true);
    238     user_rotation_ = user_rotation;
    239     current_rotation_ = user_rotation;
    240   }
    241 }
    242 
    243 void MaximizeModeController::HandleHingeRotation(const gfx::Vector3dF& base,
    244                                                  const gfx::Vector3dF& lid) {
    245   static const gfx::Vector3dF hinge_vector(0.0f, 1.0f, 0.0f);
    246   bool maximize_mode_engaged = IsMaximizeModeWindowManagerEnabled();
    247   // Ignore the component of acceleration parallel to the hinge for the purposes
    248   // of hinge angle calculation.
    249   gfx::Vector3dF base_flattened(base);
    250   gfx::Vector3dF lid_flattened(lid);
    251   base_flattened.set_y(0.0f);
    252   lid_flattened.set_y(0.0f);
    253 
    254   // As the hinge approaches a vertical angle, the base and lid accelerometers
    255   // approach the same values making any angle calculations highly inaccurate.
    256   // Bail out early when it is too close.
    257   if (base_flattened.Length() < kHingeAngleDetectionThreshold ||
    258       lid_flattened.Length() < kHingeAngleDetectionThreshold) {
    259     return;
    260   }
    261 
    262   // Compute the angle between the base and the lid.
    263   float angle = ClockwiseAngleBetweenVectorsInDegrees(base_flattened,
    264       lid_flattened, hinge_vector);
    265 
    266   // Toggle maximize mode on or off when corresponding thresholds are passed.
    267   // TODO(flackr): Make MaximizeModeController own the MaximizeModeWindowManager
    268   // such that observations of state changes occur after the change and shell
    269   // has fewer states to track.
    270   if (maximize_mode_engaged &&
    271       angle > kFullyOpenAngleErrorTolerance &&
    272       angle < kExitMaximizeModeAngle) {
    273     LeaveMaximizeMode();
    274   } else if (!maximize_mode_engaged &&
    275       angle > kEnterMaximizeModeAngle) {
    276     EnterMaximizeMode();
    277   }
    278 }
    279 
    280 void MaximizeModeController::HandleScreenRotation(const gfx::Vector3dF& lid) {
    281   bool maximize_mode_engaged = IsMaximizeModeWindowManagerEnabled();
    282 
    283   // TODO(jonross): track the updated rotation angle even when locked. So that
    284   // when rotation lock is removed the accelerometer rotation can be applied
    285   // without waiting for the next update.
    286   if (!maximize_mode_engaged || rotation_locked_)
    287     return;
    288 
    289   DisplayManager* display_manager =
    290       Shell::GetInstance()->display_manager();
    291   gfx::Display::Rotation current_rotation = display_manager->GetDisplayInfo(
    292       gfx::Display::InternalDisplayId()).rotation();
    293 
    294   // After determining maximize mode state, determine if the screen should
    295   // be rotated.
    296   gfx::Vector3dF lid_flattened(lid.x(), lid.y(), 0.0f);
    297   float lid_flattened_length = lid_flattened.Length();
    298   // When the lid is close to being flat, don't change rotation as it is too
    299   // sensitive to slight movements.
    300   if (lid_flattened_length < kMinimumAccelerationScreenRotation)
    301     return;
    302 
    303   // The reference vector is the angle of gravity when the device is rotated
    304   // clockwise by 45 degrees. Computing the angle between this vector and
    305   // gravity we can easily determine the expected display rotation.
    306   static gfx::Vector3dF rotation_reference(-1.0f, 1.0f, 0.0f);
    307 
    308   // Set the down vector to match the expected direction of gravity given the
    309   // last configured rotation. This is used to enforce a stickiness that the
    310   // user must overcome to rotate the display and prevents frequent rotations
    311   // when holding the device near 45 degrees.
    312   gfx::Vector3dF down(0.0f, 0.0f, 0.0f);
    313   if (current_rotation == gfx::Display::ROTATE_0)
    314     down.set_x(-1.0f);
    315   else if (current_rotation == gfx::Display::ROTATE_90)
    316     down.set_y(1.0f);
    317   else if (current_rotation == gfx::Display::ROTATE_180)
    318     down.set_x(1.0f);
    319   else
    320     down.set_y(-1.0f);
    321 
    322   // Don't rotate if the screen has not passed the threshold.
    323   if (AngleBetweenVectorsInDegrees(down, lid_flattened) <
    324       kDisplayRotationStickyAngleDegrees) {
    325     return;
    326   }
    327 
    328   float angle = ClockwiseAngleBetweenVectorsInDegrees(rotation_reference,
    329       lid_flattened, gfx::Vector3dF(0.0f, 0.0f, -1.0f));
    330 
    331   gfx::Display::Rotation new_rotation = gfx::Display::ROTATE_90;
    332   if (angle < 90.0f)
    333     new_rotation = gfx::Display::ROTATE_0;
    334   else if (angle < 180.0f)
    335     new_rotation = gfx::Display::ROTATE_270;
    336   else if (angle < 270.0f)
    337     new_rotation = gfx::Display::ROTATE_180;
    338 
    339   if (new_rotation != current_rotation)
    340     SetDisplayRotation(display_manager, new_rotation);
    341 }
    342 
    343 void MaximizeModeController::SetDisplayRotation(
    344     DisplayManager* display_manager,
    345     gfx::Display::Rotation rotation) {
    346   base::AutoReset<bool> auto_in_set_screen_rotation(
    347       &in_set_screen_rotation_, true);
    348   current_rotation_ = rotation;
    349   display_manager->SetDisplayRotation(gfx::Display::InternalDisplayId(),
    350                                       rotation);
    351 }
    352 
    353 void MaximizeModeController::EnterMaximizeMode() {
    354   DisplayManager* display_manager = Shell::GetInstance()->display_manager();
    355   current_rotation_ = user_rotation_ = display_manager->
    356       GetDisplayInfo(gfx::Display::InternalDisplayId()).rotation();
    357   EnableMaximizeModeWindowManager(true);
    358   event_blocker_.reset(new MaximizeModeEventBlocker);
    359 #if defined(OS_CHROMEOS)
    360   event_handler_.reset(new ScreenshotActionHandler);
    361 #endif
    362   Shell::GetInstance()->display_controller()->AddObserver(this);
    363 }
    364 
    365 void MaximizeModeController::LeaveMaximizeMode() {
    366   DisplayManager* display_manager = Shell::GetInstance()->display_manager();
    367   gfx::Display::Rotation current_rotation = display_manager->
    368       GetDisplayInfo(gfx::Display::InternalDisplayId()).rotation();
    369   if (current_rotation != user_rotation_)
    370     SetDisplayRotation(display_manager, user_rotation_);
    371   rotation_locked_ = false;
    372   EnableMaximizeModeWindowManager(false);
    373   event_blocker_.reset();
    374   event_handler_.reset();
    375 }
    376 
    377 void MaximizeModeController::OnSuspend() {
    378   RecordTouchViewStateTransition();
    379 }
    380 
    381 void MaximizeModeController::OnResume() {
    382   last_touchview_transition_time_ = base::Time::Now();
    383 }
    384 
    385 // Called after maximize mode has started, windows might still animate though.
    386 void MaximizeModeController::OnMaximizeModeStarted() {
    387   RecordTouchViewStateTransition();
    388 }
    389 
    390 // Called after maximize mode has ended, windows might still be returning to
    391 // their original position.
    392 void MaximizeModeController::OnMaximizeModeEnded() {
    393   RecordTouchViewStateTransition();
    394 }
    395 
    396 void MaximizeModeController::RecordTouchViewStateTransition() {
    397   if (CanEnterMaximizeMode()) {
    398     base::Time current_time = base::Time::Now();
    399     base::TimeDelta delta = current_time - last_touchview_transition_time_;
    400     if (IsMaximizeModeWindowManagerEnabled()) {
    401       UMA_HISTOGRAM_LONG_TIMES("Ash.TouchView.TouchViewInactive", delta);
    402       total_non_touchview_time_ += delta;
    403     } else {
    404       UMA_HISTOGRAM_LONG_TIMES("Ash.TouchView.TouchViewActive", delta);
    405       total_touchview_time_ += delta;
    406     }
    407     last_touchview_transition_time_ = current_time;
    408   }
    409 }
    410 
    411 void MaximizeModeController::OnAppTerminating() {
    412   if (CanEnterMaximizeMode()) {
    413     RecordTouchViewStateTransition();
    414     UMA_HISTOGRAM_CUSTOM_COUNTS("Ash.TouchView.TouchViewActiveTotal",
    415         total_touchview_time_.InMinutes(),
    416         1, base::TimeDelta::FromDays(7).InMinutes(), 50);
    417     UMA_HISTOGRAM_CUSTOM_COUNTS("Ash.TouchView.TouchViewInactiveTotal",
    418         total_non_touchview_time_.InMinutes(),
    419         1, base::TimeDelta::FromDays(7).InMinutes(), 50);
    420     base::TimeDelta total_runtime = total_touchview_time_ +
    421         total_non_touchview_time_;
    422     if (total_runtime.InSeconds() > 0) {
    423       UMA_HISTOGRAM_PERCENTAGE("Ash.TouchView.TouchViewActivePercentage",
    424           100 * total_touchview_time_.InSeconds() / total_runtime.InSeconds());
    425     }
    426   }
    427   Shell::GetInstance()->display_controller()->RemoveObserver(this);
    428 }
    429 
    430 }  // namespace ash
    431