Home | History | Annotate | Download | only in wm
      1 // Copyright 2013 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/immersive_fullscreen_controller.h"
      6 
      7 #include <set>
      8 
      9 #include "ash/ash_constants.h"
     10 #include "ash/shell.h"
     11 #include "ash/wm/resize_handle_window_targeter.h"
     12 #include "ash/wm/window_state.h"
     13 #include "base/metrics/histogram.h"
     14 #include "ui/aura/client/aura_constants.h"
     15 #include "ui/aura/client/capture_client.h"
     16 #include "ui/aura/client/cursor_client.h"
     17 #include "ui/aura/client/screen_position_client.h"
     18 #include "ui/aura/env.h"
     19 #include "ui/aura/window.h"
     20 #include "ui/aura/window_event_dispatcher.h"
     21 #include "ui/gfx/animation/slide_animation.h"
     22 #include "ui/gfx/display.h"
     23 #include "ui/gfx/point.h"
     24 #include "ui/gfx/rect.h"
     25 #include "ui/gfx/screen.h"
     26 #include "ui/views/bubble/bubble_delegate.h"
     27 #include "ui/views/view.h"
     28 #include "ui/views/widget/widget.h"
     29 #include "ui/wm/core/transient_window_manager.h"
     30 #include "ui/wm/core/window_util.h"
     31 #include "ui/wm/public/activation_client.h"
     32 
     33 using views::View;
     34 
     35 namespace ash {
     36 
     37 namespace {
     38 
     39 // Duration for the reveal show/hide slide animation. The slower duration is
     40 // used for the initial slide out to give the user more change to see what
     41 // happened.
     42 const int kRevealSlowAnimationDurationMs = 400;
     43 const int kRevealFastAnimationDurationMs = 200;
     44 
     45 // The delay in milliseconds between the mouse stopping at the top edge of the
     46 // screen and the top-of-window views revealing.
     47 const int kMouseRevealDelayMs = 200;
     48 
     49 // The maximum amount of pixels that the cursor can move for the cursor to be
     50 // considered "stopped". This allows the user to reveal the top-of-window views
     51 // without holding the cursor completely still.
     52 const int kMouseRevealXThresholdPixels = 3;
     53 
     54 // Used to multiply x value of an update in check to determine if gesture is
     55 // vertical. This is used to make sure that gesture is close to vertical instead
     56 // of just more vertical then horizontal.
     57 const int kSwipeVerticalThresholdMultiplier = 3;
     58 
     59 // The height in pixels of the region above the top edge of the display which
     60 // hosts the immersive fullscreen window in which mouse events are ignored
     61 // (cannot reveal or unreveal the top-of-window views).
     62 // See ShouldIgnoreMouseEventAtLocation() for more details.
     63 const int kHeightOfDeadRegionAboveTopContainer = 10;
     64 
     65 // Returns the BubbleDelegateView corresponding to |maybe_bubble| if
     66 // |maybe_bubble| is a bubble.
     67 views::BubbleDelegateView* AsBubbleDelegate(aura::Window* maybe_bubble) {
     68   if (!maybe_bubble)
     69     return NULL;
     70   views::Widget* widget = views::Widget::GetWidgetForNativeView(maybe_bubble);
     71   if (!widget)
     72     return NULL;
     73   return widget->widget_delegate()->AsBubbleDelegate();
     74 }
     75 
     76 // Returns true if |maybe_transient| is a transient child of |toplevel|.
     77 bool IsWindowTransientChildOf(aura::Window* maybe_transient,
     78                               aura::Window* toplevel) {
     79   if (!maybe_transient || !toplevel)
     80     return false;
     81 
     82   for (aura::Window* window = maybe_transient; window;
     83        window = ::wm::GetTransientParent(window)) {
     84     if (window == toplevel)
     85       return true;
     86   }
     87   return false;
     88 }
     89 
     90 // Returns the location of |event| in screen coordinates.
     91 gfx::Point GetEventLocationInScreen(const ui::LocatedEvent& event) {
     92   gfx::Point location_in_screen = event.location();
     93   aura::Window* target = static_cast<aura::Window*>(event.target());
     94   aura::client::ScreenPositionClient* screen_position_client =
     95       aura::client::GetScreenPositionClient(target->GetRootWindow());
     96   screen_position_client->ConvertPointToScreen(target, &location_in_screen);
     97   return location_in_screen;
     98 }
     99 
    100 // Returns the bounds of the display nearest to |window| in screen coordinates.
    101 gfx::Rect GetDisplayBoundsInScreen(aura::Window* window) {
    102   return Shell::GetScreen()->GetDisplayNearestWindow(window).bounds();
    103 }
    104 
    105 }  // namespace
    106 
    107 // The height in pixels of the region below the top edge of the display in which
    108 // the mouse can trigger revealing the top-of-window views.
    109 #if defined(OS_WIN)
    110 // Windows 8 reserves some pixels at the top of the screen for the hand icon
    111 // that allows you to drag a metro app off the screen, so a few additional
    112 // pixels of space must be reserved for the mouse reveal.
    113 const int ImmersiveFullscreenController::kMouseRevealBoundsHeight = 9;
    114 #else
    115 // The height must be greater than 1px because the top pixel is used to trigger
    116 // moving the cursor between displays if the user has a vertical display layout
    117 // (primary display above/below secondary display).
    118 const int ImmersiveFullscreenController::kMouseRevealBoundsHeight = 3;
    119 #endif
    120 
    121 ////////////////////////////////////////////////////////////////////////////////
    122 
    123 // Class which keeps the top-of-window views revealed as long as one of the
    124 // bubbles it is observing is visible. The logic to keep the top-of-window
    125 // views revealed based on the visibility of bubbles anchored to
    126 // children of |ImmersiveFullscreenController::top_container_| is separate from
    127 // the logic related to |ImmersiveFullscreenController::focus_revealed_lock_|
    128 // so that bubbles which are not activatable and bubbles which do not close
    129 // upon deactivation also keep the top-of-window views revealed for the
    130 // duration of their visibility.
    131 class ImmersiveFullscreenController::BubbleManager
    132     : public aura::WindowObserver {
    133  public:
    134   explicit BubbleManager(ImmersiveFullscreenController* controller);
    135   virtual ~BubbleManager();
    136 
    137   // Start / stop observing changes to |bubble|'s visibility.
    138   void StartObserving(aura::Window* bubble);
    139   void StopObserving(aura::Window* bubble);
    140 
    141  private:
    142   // Updates |revealed_lock_| based on whether any of |bubbles_| is visible.
    143   void UpdateRevealedLock();
    144 
    145   // aura::WindowObserver overrides:
    146   virtual void OnWindowVisibilityChanged(aura::Window* window,
    147                                          bool visible) OVERRIDE;
    148   virtual void OnWindowDestroying(aura::Window* window) OVERRIDE;
    149 
    150   ImmersiveFullscreenController* controller_;
    151 
    152   std::set<aura::Window*> bubbles_;
    153 
    154   // Lock which keeps the top-of-window views revealed based on whether any of
    155   // |bubbles_| is visible.
    156   scoped_ptr<ImmersiveRevealedLock> revealed_lock_;
    157 
    158   DISALLOW_COPY_AND_ASSIGN(BubbleManager);
    159 };
    160 
    161 ImmersiveFullscreenController::BubbleManager::BubbleManager(
    162     ImmersiveFullscreenController* controller)
    163     : controller_(controller) {
    164 }
    165 
    166 ImmersiveFullscreenController::BubbleManager::~BubbleManager() {
    167   for (std::set<aura::Window*>::const_iterator it = bubbles_.begin();
    168        it != bubbles_.end(); ++it) {
    169     (*it)->RemoveObserver(this);
    170   }
    171 }
    172 
    173 void ImmersiveFullscreenController::BubbleManager::StartObserving(
    174     aura::Window* bubble) {
    175   if (bubbles_.insert(bubble).second) {
    176     bubble->AddObserver(this);
    177     UpdateRevealedLock();
    178   }
    179 }
    180 
    181 void ImmersiveFullscreenController::BubbleManager::StopObserving(
    182     aura::Window* bubble) {
    183   if (bubbles_.erase(bubble)) {
    184     bubble->RemoveObserver(this);
    185     UpdateRevealedLock();
    186   }
    187 }
    188 
    189 void ImmersiveFullscreenController::BubbleManager::UpdateRevealedLock() {
    190   bool has_visible_bubble = false;
    191   for (std::set<aura::Window*>::const_iterator it = bubbles_.begin();
    192        it != bubbles_.end(); ++it) {
    193     if ((*it)->IsVisible()) {
    194       has_visible_bubble = true;
    195       break;
    196     }
    197   }
    198 
    199   bool was_revealed = controller_->IsRevealed();
    200   if (has_visible_bubble) {
    201     if (!revealed_lock_.get()) {
    202       // Reveal the top-of-window views without animating because it looks
    203       // weird for the top-of-window views to animate and the bubble not to
    204       // animate along with the top-of-window views.
    205       revealed_lock_.reset(controller_->GetRevealedLock(
    206           ImmersiveFullscreenController::ANIMATE_REVEAL_NO));
    207     }
    208   } else {
    209     revealed_lock_.reset();
    210   }
    211 
    212   if (!was_revealed && revealed_lock_.get()) {
    213     // Currently, there is no nice way for bubbles to reposition themselves
    214     // whenever the anchor view moves. Tell the bubbles to reposition themselves
    215     // explicitly instead. The hidden bubbles are also repositioned because
    216     // BubbleDelegateView does not reposition its widget as a result of a
    217     // visibility change.
    218     for (std::set<aura::Window*>::const_iterator it = bubbles_.begin();
    219          it != bubbles_.end(); ++it) {
    220       AsBubbleDelegate(*it)->OnAnchorBoundsChanged();
    221     }
    222   }
    223 }
    224 
    225 void ImmersiveFullscreenController::BubbleManager::OnWindowVisibilityChanged(
    226     aura::Window*,
    227     bool visible) {
    228   UpdateRevealedLock();
    229 }
    230 
    231 void ImmersiveFullscreenController::BubbleManager::OnWindowDestroying(
    232     aura::Window* window) {
    233   StopObserving(window);
    234 }
    235 
    236 ////////////////////////////////////////////////////////////////////////////////
    237 
    238 ImmersiveFullscreenController::ImmersiveFullscreenController()
    239     : delegate_(NULL),
    240       top_container_(NULL),
    241       widget_(NULL),
    242       native_window_(NULL),
    243       observers_enabled_(false),
    244       enabled_(false),
    245       reveal_state_(CLOSED),
    246       revealed_lock_count_(0),
    247       mouse_x_when_hit_top_in_screen_(-1),
    248       gesture_begun_(false),
    249       animation_(new gfx::SlideAnimation(this)),
    250       animations_disabled_for_test_(false),
    251       weak_ptr_factory_(this) {
    252 }
    253 
    254 ImmersiveFullscreenController::~ImmersiveFullscreenController() {
    255   EnableWindowObservers(false);
    256 }
    257 
    258 void ImmersiveFullscreenController::Init(Delegate* delegate,
    259                                          views::Widget* widget,
    260                                          views::View* top_container) {
    261   delegate_ = delegate;
    262   top_container_ = top_container;
    263   widget_ = widget;
    264   native_window_ = widget_->GetNativeWindow();
    265   native_window_->SetEventTargeter(scoped_ptr<ui::EventTargeter>(
    266       new ResizeHandleWindowTargeter(native_window_, this)));
    267 }
    268 
    269 void ImmersiveFullscreenController::SetEnabled(WindowType window_type,
    270                                                bool enabled) {
    271   if (enabled_ == enabled)
    272     return;
    273   enabled_ = enabled;
    274 
    275   EnableWindowObservers(enabled_);
    276 
    277   ash::wm::WindowState* window_state = wm::GetWindowState(native_window_);
    278   // Auto hide the shelf in immersive fullscreen instead of hiding it.
    279   window_state->set_hide_shelf_when_fullscreen(!enabled);
    280 
    281   // Update the window's immersive mode state for the window manager.
    282   window_state->set_in_immersive_fullscreen(enabled);
    283 
    284   Shell::GetInstance()->UpdateShelfVisibility();
    285 
    286   if (enabled_) {
    287     // Animate enabling immersive mode by sliding out the top-of-window views.
    288     // No animation occurs if a lock is holding the top-of-window views open.
    289 
    290     // Do a reveal to set the initial state for the animation. (And any
    291     // required state in case the animation cannot run because of a lock holding
    292     // the top-of-window views open.)
    293     MaybeStartReveal(ANIMATE_NO);
    294 
    295     // Reset the located event and the focus revealed locks so that they do not
    296     // affect whether the top-of-window views are hidden.
    297     located_event_revealed_lock_.reset();
    298     focus_revealed_lock_.reset();
    299 
    300     // Try doing the animation.
    301     MaybeEndReveal(ANIMATE_SLOW);
    302 
    303     if (reveal_state_ == REVEALED) {
    304       // Reveal was unsuccessful. Reacquire the revealed locks if appropriate.
    305       UpdateLocatedEventRevealedLock(NULL);
    306       UpdateFocusRevealedLock();
    307     } else {
    308       // Clearing focus is important because it closes focus-related popups like
    309       // the touch selection handles.
    310       widget_->GetFocusManager()->ClearFocus();
    311     }
    312   } else {
    313     // Stop cursor-at-top tracking.
    314     top_edge_hover_timer_.Stop();
    315     reveal_state_ = CLOSED;
    316 
    317     delegate_->OnImmersiveFullscreenExited();
    318   }
    319 
    320   if (enabled_) {
    321     UMA_HISTOGRAM_ENUMERATION("Ash.ImmersiveFullscreen.WindowType",
    322                               window_type,
    323                               WINDOW_TYPE_COUNT);
    324   }
    325 }
    326 
    327 bool ImmersiveFullscreenController::IsEnabled() const {
    328   return enabled_;
    329 }
    330 
    331 bool ImmersiveFullscreenController::IsRevealed() const {
    332   return enabled_ && reveal_state_ != CLOSED;
    333 }
    334 
    335 ImmersiveRevealedLock* ImmersiveFullscreenController::GetRevealedLock(
    336     AnimateReveal animate_reveal) {
    337   return new ImmersiveRevealedLock(weak_ptr_factory_.GetWeakPtr(),
    338                                    animate_reveal);
    339 }
    340 
    341 ////////////////////////////////////////////////////////////////////////////////
    342 // Testing interface:
    343 
    344 void ImmersiveFullscreenController::SetupForTest() {
    345   DCHECK(!enabled_);
    346   animations_disabled_for_test_ = true;
    347 
    348   // Move the mouse off of the top-of-window views so that it does not keep the
    349   // top-of-window views revealed.
    350   std::vector<gfx::Rect> bounds_in_screen(
    351       delegate_->GetVisibleBoundsInScreen());
    352   DCHECK(!bounds_in_screen.empty());
    353   int bottommost_in_screen = bounds_in_screen[0].bottom();
    354   for (size_t i = 1; i < bounds_in_screen.size(); ++i) {
    355     if (bounds_in_screen[i].bottom() > bottommost_in_screen)
    356       bottommost_in_screen = bounds_in_screen[i].bottom();
    357   }
    358   gfx::Point cursor_pos(0, bottommost_in_screen + 100);
    359   aura::Env::GetInstance()->set_last_mouse_location(cursor_pos);
    360   UpdateLocatedEventRevealedLock(NULL);
    361 }
    362 
    363 ////////////////////////////////////////////////////////////////////////////////
    364 // ui::EventHandler overrides:
    365 
    366 void ImmersiveFullscreenController::OnMouseEvent(ui::MouseEvent* event) {
    367   if (!enabled_)
    368     return;
    369 
    370   if (event->type() != ui::ET_MOUSE_MOVED &&
    371       event->type() != ui::ET_MOUSE_PRESSED &&
    372       event->type() != ui::ET_MOUSE_RELEASED &&
    373       event->type() != ui::ET_MOUSE_CAPTURE_CHANGED) {
    374     return;
    375   }
    376 
    377   // Mouse hover can initiate revealing the top-of-window views while |widget_|
    378   // is inactive.
    379 
    380   if (reveal_state_ == SLIDING_OPEN || reveal_state_ == REVEALED) {
    381     top_edge_hover_timer_.Stop();
    382     UpdateLocatedEventRevealedLock(event);
    383   } else if (event->type() != ui::ET_MOUSE_CAPTURE_CHANGED) {
    384     // Trigger a reveal if the cursor pauses at the top of the screen for a
    385     // while.
    386     UpdateTopEdgeHoverTimer(event);
    387   }
    388 }
    389 
    390 void ImmersiveFullscreenController::OnTouchEvent(ui::TouchEvent* event) {
    391   if (!enabled_ || event->type() != ui::ET_TOUCH_PRESSED)
    392     return;
    393 
    394   // Touch should not initiate revealing the top-of-window views while |widget_|
    395   // is inactive.
    396   if (!widget_->IsActive())
    397     return;
    398 
    399   UpdateLocatedEventRevealedLock(event);
    400 }
    401 
    402 void ImmersiveFullscreenController::OnGestureEvent(ui::GestureEvent* event) {
    403   if (!enabled_)
    404     return;
    405 
    406   // Touch gestures should not initiate revealing the top-of-window views while
    407   // |widget_| is inactive.
    408   if (!widget_->IsActive())
    409     return;
    410 
    411   switch (event->type()) {
    412 #if defined(OS_WIN)
    413     case ui::ET_GESTURE_WIN8_EDGE_SWIPE:
    414       UpdateRevealedLocksForSwipe(GetSwipeType(event));
    415       event->SetHandled();
    416       break;
    417 #endif
    418     case ui::ET_GESTURE_SCROLL_BEGIN:
    419       if (ShouldHandleGestureEvent(GetEventLocationInScreen(*event))) {
    420         gesture_begun_ = true;
    421         // Do not consume the event. Otherwise, we end up consuming all
    422         // ui::ET_GESTURE_SCROLL_BEGIN events in the top-of-window views
    423         // when the top-of-window views are revealed.
    424       }
    425       break;
    426     case ui::ET_GESTURE_SCROLL_UPDATE:
    427       if (gesture_begun_) {
    428         if (UpdateRevealedLocksForSwipe(GetSwipeType(event)))
    429           event->SetHandled();
    430         gesture_begun_ = false;
    431       }
    432       break;
    433     case ui::ET_GESTURE_SCROLL_END:
    434     case ui::ET_SCROLL_FLING_START:
    435       gesture_begun_ = false;
    436       break;
    437     default:
    438       break;
    439   }
    440 }
    441 
    442 ////////////////////////////////////////////////////////////////////////////////
    443 // views::FocusChangeListener overrides:
    444 
    445 void ImmersiveFullscreenController::OnWillChangeFocus(
    446     views::View* focused_before,
    447     views::View* focused_now) {
    448 }
    449 
    450 void ImmersiveFullscreenController::OnDidChangeFocus(
    451     views::View* focused_before,
    452     views::View* focused_now) {
    453   UpdateFocusRevealedLock();
    454 }
    455 
    456 ////////////////////////////////////////////////////////////////////////////////
    457 // views::WidgetObserver overrides:
    458 
    459 void ImmersiveFullscreenController::OnWidgetDestroying(views::Widget* widget) {
    460   EnableWindowObservers(false);
    461   native_window_ = NULL;
    462 
    463   // Set |enabled_| to false such that any calls to MaybeStartReveal() and
    464   // MaybeEndReveal() have no effect.
    465   enabled_ = false;
    466 }
    467 
    468 void ImmersiveFullscreenController::OnWidgetActivationChanged(
    469     views::Widget* widget,
    470     bool active) {
    471   UpdateFocusRevealedLock();
    472 }
    473 
    474 ////////////////////////////////////////////////////////////////////////////////
    475 // gfx::AnimationDelegate overrides:
    476 
    477 void ImmersiveFullscreenController::AnimationEnded(
    478     const gfx::Animation* animation) {
    479   if (reveal_state_ == SLIDING_OPEN) {
    480     OnSlideOpenAnimationCompleted();
    481   } else if (reveal_state_ == SLIDING_CLOSED) {
    482     OnSlideClosedAnimationCompleted();
    483   }
    484 }
    485 
    486 void ImmersiveFullscreenController::AnimationProgressed(
    487     const gfx::Animation* animation) {
    488   delegate_->SetVisibleFraction(animation->GetCurrentValue());
    489 }
    490 
    491 ////////////////////////////////////////////////////////////////////////////////
    492 // aura::WindowObserver overrides:
    493 
    494 void ImmersiveFullscreenController::OnTransientChildAdded(
    495     aura::Window* window,
    496     aura::Window* transient) {
    497   views::BubbleDelegateView* bubble_delegate = AsBubbleDelegate(transient);
    498   if (bubble_delegate &&
    499       bubble_delegate->GetAnchorView() &&
    500       top_container_->Contains(bubble_delegate->GetAnchorView())) {
    501     // Observe the aura::Window because the BubbleDelegateView may not be
    502     // parented to the widget's root view yet so |bubble_delegate->GetWidget()|
    503     // may still return NULL.
    504     bubble_manager_->StartObserving(transient);
    505   }
    506 }
    507 
    508 void ImmersiveFullscreenController::OnTransientChildRemoved(
    509     aura::Window* window,
    510     aura::Window* transient) {
    511   bubble_manager_->StopObserving(transient);
    512 }
    513 
    514 ////////////////////////////////////////////////////////////////////////////////
    515 // ash::ImmersiveRevealedLock::Delegate overrides:
    516 
    517 void ImmersiveFullscreenController::LockRevealedState(
    518     AnimateReveal animate_reveal) {
    519   ++revealed_lock_count_;
    520   Animate animate = (animate_reveal == ANIMATE_REVEAL_YES) ?
    521       ANIMATE_FAST : ANIMATE_NO;
    522   MaybeStartReveal(animate);
    523 }
    524 
    525 void ImmersiveFullscreenController::UnlockRevealedState() {
    526   --revealed_lock_count_;
    527   DCHECK_GE(revealed_lock_count_, 0);
    528   if (revealed_lock_count_ == 0) {
    529     // Always animate ending the reveal fast.
    530     MaybeEndReveal(ANIMATE_FAST);
    531   }
    532 }
    533 
    534 ////////////////////////////////////////////////////////////////////////////////
    535 // private:
    536 
    537 void ImmersiveFullscreenController::EnableWindowObservers(bool enable) {
    538   if (observers_enabled_ == enable)
    539     return;
    540   observers_enabled_ = enable;
    541 
    542   views::FocusManager* focus_manager = widget_->GetFocusManager();
    543 
    544   if (enable) {
    545     widget_->AddObserver(this);
    546     focus_manager->AddFocusChangeListener(this);
    547     Shell::GetInstance()->AddPreTargetHandler(this);
    548     ::wm::TransientWindowManager::Get(native_window_)->
    549         AddObserver(this);
    550 
    551     RecreateBubbleManager();
    552   } else {
    553     widget_->RemoveObserver(this);
    554     focus_manager->RemoveFocusChangeListener(this);
    555     Shell::GetInstance()->RemovePreTargetHandler(this);
    556     ::wm::TransientWindowManager::Get(native_window_)->
    557         RemoveObserver(this);
    558 
    559     // We have stopped observing whether transient children are added or removed
    560     // to |native_window_|. The set of bubbles that BubbleManager is observing
    561     // will become stale really quickly. Destroy BubbleManager and recreate it
    562     // when we start observing |native_window_| again.
    563     bubble_manager_.reset();
    564 
    565     animation_->Stop();
    566   }
    567 }
    568 
    569 void ImmersiveFullscreenController::UpdateTopEdgeHoverTimer(
    570     ui::MouseEvent* event) {
    571   DCHECK(enabled_);
    572   DCHECK(reveal_state_ == SLIDING_CLOSED || reveal_state_ == CLOSED);
    573 
    574   // Check whether |native_window_| is the event target's parent window instead
    575   // of checking for activation. This allows the timer to be started when
    576   // |widget_| is inactive but prevents starting the timer if the mouse is over
    577   // a portion of the top edge obscured by an unrelated widget.
    578   if (!top_edge_hover_timer_.IsRunning() &&
    579       !native_window_->Contains(static_cast<aura::Window*>(event->target()))) {
    580     return;
    581   }
    582 
    583   // Mouse hover should not initiate revealing the top-of-window views while a
    584   // window has mouse capture.
    585   if (aura::client::GetCaptureWindow(native_window_))
    586     return;
    587 
    588   gfx::Point location_in_screen = GetEventLocationInScreen(*event);
    589   if (ShouldIgnoreMouseEventAtLocation(location_in_screen))
    590     return;
    591 
    592   // Stop the timer if the cursor left the top edge or is on a different
    593   // display.
    594   gfx::Rect hit_bounds_in_screen = GetDisplayBoundsInScreen(native_window_);
    595   hit_bounds_in_screen.set_height(kMouseRevealBoundsHeight);
    596   if (!hit_bounds_in_screen.Contains(location_in_screen)) {
    597     top_edge_hover_timer_.Stop();
    598     return;
    599   }
    600 
    601   // The cursor is now at the top of the screen. Consider the cursor "not
    602   // moving" even if it moves a little bit because users don't have perfect
    603   // pointing precision. (The y position is not tested because
    604   // |hit_bounds_in_screen| is short.)
    605   if (top_edge_hover_timer_.IsRunning() &&
    606       abs(location_in_screen.x() - mouse_x_when_hit_top_in_screen_) <=
    607           kMouseRevealXThresholdPixels)
    608     return;
    609 
    610   // Start the reveal if the cursor doesn't move for some amount of time.
    611   mouse_x_when_hit_top_in_screen_ = location_in_screen.x();
    612   top_edge_hover_timer_.Stop();
    613   // Timer is stopped when |this| is destroyed, hence Unretained() is safe.
    614   top_edge_hover_timer_.Start(
    615       FROM_HERE,
    616       base::TimeDelta::FromMilliseconds(kMouseRevealDelayMs),
    617       base::Bind(
    618           &ImmersiveFullscreenController::AcquireLocatedEventRevealedLock,
    619           base::Unretained(this)));
    620 }
    621 
    622 void ImmersiveFullscreenController::UpdateLocatedEventRevealedLock(
    623     ui::LocatedEvent* event) {
    624   if (!enabled_)
    625     return;
    626   DCHECK(!event || event->IsMouseEvent() || event->IsTouchEvent());
    627 
    628   // Neither the mouse nor touch can initiate a reveal when the top-of-window
    629   // views are sliding closed or are closed with the following exceptions:
    630   // - Hovering at y = 0 which is handled in OnMouseEvent().
    631   // - Doing a SWIPE_OPEN edge gesture which is handled in OnGestureEvent().
    632   if (reveal_state_ == CLOSED || reveal_state_ == SLIDING_CLOSED)
    633     return;
    634 
    635   // For the sake of simplicity, ignore |widget_|'s activation in computing
    636   // whether the top-of-window views should stay revealed. Ideally, the
    637   // top-of-window views would stay revealed only when the mouse cursor is
    638   // hovered above a non-obscured portion of the top-of-window views. The
    639   // top-of-window views may be partially obscured when |widget_| is inactive.
    640 
    641   // Ignore all events while a window has capture. This keeps the top-of-window
    642   // views revealed during a drag.
    643   if (aura::client::GetCaptureWindow(native_window_))
    644     return;
    645 
    646   gfx::Point location_in_screen;
    647   if (event && event->type() != ui::ET_MOUSE_CAPTURE_CHANGED) {
    648     location_in_screen = GetEventLocationInScreen(*event);
    649   } else {
    650     aura::client::CursorClient* cursor_client = aura::client::GetCursorClient(
    651         native_window_->GetRootWindow());
    652     if (!cursor_client->IsMouseEventsEnabled()) {
    653       // If mouse events are disabled, the user's last interaction was probably
    654       // via touch. Do no do further processing in this case as there is no easy
    655       // way of retrieving the position of the user's last touch.
    656       return;
    657     }
    658     location_in_screen = aura::Env::GetInstance()->last_mouse_location();
    659   }
    660 
    661   if ((!event || event->IsMouseEvent()) &&
    662       ShouldIgnoreMouseEventAtLocation(location_in_screen)) {
    663     return;
    664   }
    665 
    666   // The visible bounds of |top_container_| should be contained in
    667   // |hit_bounds_in_screen|.
    668   std::vector<gfx::Rect> hit_bounds_in_screen =
    669       delegate_->GetVisibleBoundsInScreen();
    670   bool keep_revealed = false;
    671   for (size_t i = 0; i < hit_bounds_in_screen.size(); ++i) {
    672     // Allow the cursor to move slightly off the top-of-window views before
    673     // sliding closed. In the case of ImmersiveModeControllerAsh, this helps
    674     // when the user is attempting to click on the bookmark bar and overshoots
    675     // slightly.
    676     if (event && event->type() == ui::ET_MOUSE_MOVED) {
    677       const int kBoundsOffsetY = 8;
    678       hit_bounds_in_screen[i].Inset(0, 0, 0, -kBoundsOffsetY);
    679     }
    680 
    681     if (hit_bounds_in_screen[i].Contains(location_in_screen)) {
    682       keep_revealed = true;
    683       break;
    684     }
    685   }
    686 
    687   if (keep_revealed)
    688     AcquireLocatedEventRevealedLock();
    689   else
    690     located_event_revealed_lock_.reset();
    691 }
    692 
    693 void ImmersiveFullscreenController::AcquireLocatedEventRevealedLock() {
    694   // CAUTION: Acquiring the lock results in a reentrant call to
    695   // AcquireLocatedEventRevealedLock() when
    696   // |ImmersiveFullscreenController::animations_disabled_for_test_| is true.
    697   if (!located_event_revealed_lock_.get())
    698     located_event_revealed_lock_.reset(GetRevealedLock(ANIMATE_REVEAL_YES));
    699 }
    700 
    701 void ImmersiveFullscreenController::UpdateFocusRevealedLock() {
    702   if (!enabled_)
    703     return;
    704 
    705   bool hold_lock = false;
    706   if (widget_->IsActive()) {
    707     views::View* focused_view = widget_->GetFocusManager()->GetFocusedView();
    708     if (top_container_->Contains(focused_view))
    709       hold_lock = true;
    710   } else {
    711     aura::Window* active_window = aura::client::GetActivationClient(
    712         native_window_->GetRootWindow())->GetActiveWindow();
    713     views::BubbleDelegateView* bubble_delegate =
    714         AsBubbleDelegate(active_window);
    715     if (bubble_delegate && bubble_delegate->anchor_widget()) {
    716       // BubbleManager will already have locked the top-of-window views if the
    717       // bubble is anchored to a child of |top_container_|. Don't acquire
    718       // |focus_revealed_lock_| here for the sake of simplicity.
    719       // Note: Instead of checking for the existence of the |anchor_view|,
    720       // the existence of the |anchor_widget| is performed to avoid the case
    721       // where the view is already gone (and the widget is still running).
    722     } else {
    723       // The currently active window is not |native_window_| and it is not a
    724       // bubble with an anchor view. The top-of-window views should be revealed
    725       // if:
    726       // 1) The active window is a transient child of |native_window_|.
    727       // 2) The top-of-window views are already revealed. This restriction
    728       //    prevents a transient window opened by the web contents while the
    729       //    top-of-window views are hidden from from initiating a reveal.
    730       // The top-of-window views will stay revealed till |native_window_| is
    731       // reactivated.
    732       if (IsRevealed() &&
    733           IsWindowTransientChildOf(active_window, native_window_)) {
    734         hold_lock = true;
    735       }
    736     }
    737   }
    738 
    739   if (hold_lock) {
    740     if (!focus_revealed_lock_.get())
    741       focus_revealed_lock_.reset(GetRevealedLock(ANIMATE_REVEAL_YES));
    742   } else {
    743     focus_revealed_lock_.reset();
    744   }
    745 }
    746 
    747 bool ImmersiveFullscreenController::UpdateRevealedLocksForSwipe(
    748     SwipeType swipe_type) {
    749   if (!enabled_ || swipe_type == SWIPE_NONE)
    750     return false;
    751 
    752   // Swipes while |native_window_| is inactive should have been filtered out in
    753   // OnGestureEvent().
    754   DCHECK(widget_->IsActive());
    755 
    756   if (reveal_state_ == SLIDING_CLOSED || reveal_state_ == CLOSED) {
    757     if (swipe_type == SWIPE_OPEN && !located_event_revealed_lock_.get()) {
    758       located_event_revealed_lock_.reset(GetRevealedLock(ANIMATE_REVEAL_YES));
    759       return true;
    760     }
    761   } else {
    762     if (swipe_type == SWIPE_CLOSE) {
    763       // Attempt to end the reveal. If other code is holding onto a lock, the
    764       // attempt will be unsuccessful.
    765       located_event_revealed_lock_.reset();
    766       focus_revealed_lock_.reset();
    767 
    768       if (reveal_state_ == SLIDING_CLOSED || reveal_state_ == CLOSED) {
    769         widget_->GetFocusManager()->ClearFocus();
    770         return true;
    771       }
    772 
    773       // Ending the reveal was unsuccessful. Reaquire the locks if appropriate.
    774       UpdateLocatedEventRevealedLock(NULL);
    775       UpdateFocusRevealedLock();
    776     }
    777   }
    778   return false;
    779 }
    780 
    781 int ImmersiveFullscreenController::GetAnimationDuration(Animate animate) const {
    782   switch (animate) {
    783     case ANIMATE_NO:
    784       return 0;
    785     case ANIMATE_SLOW:
    786       return kRevealSlowAnimationDurationMs;
    787     case ANIMATE_FAST:
    788       return kRevealFastAnimationDurationMs;
    789   }
    790   NOTREACHED();
    791   return 0;
    792 }
    793 
    794 void ImmersiveFullscreenController::MaybeStartReveal(Animate animate) {
    795   if (!enabled_)
    796     return;
    797 
    798   if (animations_disabled_for_test_)
    799     animate = ANIMATE_NO;
    800 
    801   // Callers with ANIMATE_NO expect this function to synchronously reveal the
    802   // top-of-window views.
    803   if (reveal_state_ == REVEALED ||
    804       (reveal_state_ == SLIDING_OPEN && animate != ANIMATE_NO)) {
    805     return;
    806   }
    807 
    808   RevealState previous_reveal_state = reveal_state_;
    809   reveal_state_ = SLIDING_OPEN;
    810   if (previous_reveal_state == CLOSED) {
    811     delegate_->OnImmersiveRevealStarted();
    812 
    813     // Do not do any more processing if OnImmersiveRevealStarted() changed
    814     // |reveal_state_|.
    815     if (reveal_state_ != SLIDING_OPEN)
    816       return;
    817   }
    818   // Slide in the reveal view.
    819   if (animate == ANIMATE_NO) {
    820     animation_->Reset(1);
    821     OnSlideOpenAnimationCompleted();
    822   } else {
    823     animation_->SetSlideDuration(GetAnimationDuration(animate));
    824     animation_->Show();
    825   }
    826 }
    827 
    828 void ImmersiveFullscreenController::OnSlideOpenAnimationCompleted() {
    829   DCHECK_EQ(SLIDING_OPEN, reveal_state_);
    830   reveal_state_ = REVEALED;
    831   delegate_->SetVisibleFraction(1);
    832 
    833   // The user may not have moved the mouse since the reveal was initiated.
    834   // Update the revealed lock to reflect the mouse's current state.
    835   UpdateLocatedEventRevealedLock(NULL);
    836 }
    837 
    838 void ImmersiveFullscreenController::MaybeEndReveal(Animate animate) {
    839   if (!enabled_ || revealed_lock_count_ != 0)
    840     return;
    841 
    842   if (animations_disabled_for_test_)
    843     animate = ANIMATE_NO;
    844 
    845   // Callers with ANIMATE_NO expect this function to synchronously close the
    846   // top-of-window views.
    847   if (reveal_state_ == CLOSED ||
    848       (reveal_state_ == SLIDING_CLOSED && animate != ANIMATE_NO)) {
    849     return;
    850   }
    851 
    852   reveal_state_ = SLIDING_CLOSED;
    853   int duration_ms = GetAnimationDuration(animate);
    854   if (duration_ms > 0) {
    855     animation_->SetSlideDuration(duration_ms);
    856     animation_->Hide();
    857   } else {
    858     animation_->Reset(0);
    859     OnSlideClosedAnimationCompleted();
    860   }
    861 }
    862 
    863 void ImmersiveFullscreenController::OnSlideClosedAnimationCompleted() {
    864   DCHECK_EQ(SLIDING_CLOSED, reveal_state_);
    865   reveal_state_ = CLOSED;
    866   delegate_->OnImmersiveRevealEnded();
    867 }
    868 
    869 ImmersiveFullscreenController::SwipeType
    870 ImmersiveFullscreenController::GetSwipeType(ui::GestureEvent* event) const {
    871 #if defined(OS_WIN)
    872   if (event->type() == ui::ET_GESTURE_WIN8_EDGE_SWIPE)
    873     return SWIPE_OPEN;
    874 #endif
    875   if (event->type() != ui::ET_GESTURE_SCROLL_UPDATE)
    876     return SWIPE_NONE;
    877   // Make sure that it is a clear vertical gesture.
    878   if (std::abs(event->details().scroll_y()) <=
    879       kSwipeVerticalThresholdMultiplier * std::abs(event->details().scroll_x()))
    880     return SWIPE_NONE;
    881   if (event->details().scroll_y() < 0)
    882     return SWIPE_CLOSE;
    883   else if (event->details().scroll_y() > 0)
    884     return SWIPE_OPEN;
    885   return SWIPE_NONE;
    886 }
    887 
    888 bool ImmersiveFullscreenController::ShouldIgnoreMouseEventAtLocation(
    889     const gfx::Point& location) const {
    890   // Ignore mouse events in the region immediately above the top edge of the
    891   // display. This is to handle the case of a user with a vertical display
    892   // layout (primary display above/below secondary display) and the immersive
    893   // fullscreen window on the bottom display. It is really hard to trigger a
    894   // reveal in this case because:
    895   // - It is hard to stop the cursor in the top |kMouseRevealBoundsHeight|
    896   //   pixels of the bottom display.
    897   // - The cursor is warped to the top display if the cursor gets to the top
    898   //   edge of the bottom display.
    899   // Mouse events are ignored in the bottom few pixels of the top display
    900   // (Mouse events in this region cannot start or end a reveal). This allows a
    901   // user to overshoot the top of the bottom display and still reveal the
    902   // top-of-window views.
    903   gfx::Rect dead_region = GetDisplayBoundsInScreen(native_window_);
    904   dead_region.set_y(dead_region.y() - kHeightOfDeadRegionAboveTopContainer);
    905   dead_region.set_height(kHeightOfDeadRegionAboveTopContainer);
    906   return dead_region.Contains(location);
    907 }
    908 
    909 bool ImmersiveFullscreenController::ShouldHandleGestureEvent(
    910     const gfx::Point& location) const {
    911   DCHECK(widget_->IsActive());
    912   if (reveal_state_ == REVEALED) {
    913     std::vector<gfx::Rect> hit_bounds_in_screen(
    914         delegate_->GetVisibleBoundsInScreen());
    915     for (size_t i = 0; i < hit_bounds_in_screen.size(); ++i) {
    916       if (hit_bounds_in_screen[i].Contains(location))
    917         return true;
    918     }
    919     return false;
    920   }
    921 
    922   // When the top-of-window views are not fully revealed, handle gestures which
    923   // start in the top few pixels of the screen.
    924   gfx::Rect hit_bounds_in_screen(GetDisplayBoundsInScreen(native_window_));
    925   hit_bounds_in_screen.set_height(kImmersiveFullscreenTopEdgeInset);
    926   if (hit_bounds_in_screen.Contains(location))
    927     return true;
    928 
    929   // There may be a bezel sensor off screen logically above
    930   // |hit_bounds_in_screen|. The check for the event not contained by the
    931   // closest screen ensures that the event is from a valid bezel (as opposed to
    932   // another screen in an extended desktop).
    933   gfx::Rect screen_bounds =
    934       Shell::GetScreen()->GetDisplayNearestPoint(location).bounds();
    935   return (!screen_bounds.Contains(location) &&
    936           location.y() < hit_bounds_in_screen.y() &&
    937           location.x() >= hit_bounds_in_screen.x() &&
    938           location.x() < hit_bounds_in_screen.right());
    939 }
    940 
    941 void ImmersiveFullscreenController::RecreateBubbleManager() {
    942   bubble_manager_.reset(new BubbleManager(this));
    943   const std::vector<aura::Window*> transient_children =
    944       ::wm::GetTransientChildren(native_window_);
    945   for (size_t i = 0; i < transient_children.size(); ++i) {
    946     aura::Window* transient_child = transient_children[i];
    947     views::BubbleDelegateView* bubble_delegate =
    948         AsBubbleDelegate(transient_child);
    949     if (bubble_delegate &&
    950         bubble_delegate->GetAnchorView() &&
    951         top_container_->Contains(bubble_delegate->GetAnchorView())) {
    952       bubble_manager_->StartObserving(transient_child);
    953     }
    954   }
    955 }
    956 
    957 }  // namespace ash
    958