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