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
33using views::View;
34
35namespace ash {
36
37namespace {
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.
42const int kRevealSlowAnimationDurationMs = 400;
43const 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.
47const 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.
52const 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.
57const 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.
63const int kHeightOfDeadRegionAboveTopContainer = 10;
64
65// Returns the BubbleDelegateView corresponding to |maybe_bubble| if
66// |maybe_bubble| is a bubble.
67views::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|.
77bool 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.
91gfx::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.
101gfx::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.
113const 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).
118const 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.
131class 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
161ImmersiveFullscreenController::BubbleManager::BubbleManager(
162    ImmersiveFullscreenController* controller)
163    : controller_(controller) {
164}
165
166ImmersiveFullscreenController::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
173void ImmersiveFullscreenController::BubbleManager::StartObserving(
174    aura::Window* bubble) {
175  if (bubbles_.insert(bubble).second) {
176    bubble->AddObserver(this);
177    UpdateRevealedLock();
178  }
179}
180
181void ImmersiveFullscreenController::BubbleManager::StopObserving(
182    aura::Window* bubble) {
183  if (bubbles_.erase(bubble)) {
184    bubble->RemoveObserver(this);
185    UpdateRevealedLock();
186  }
187}
188
189void 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
225void ImmersiveFullscreenController::BubbleManager::OnWindowVisibilityChanged(
226    aura::Window*,
227    bool visible) {
228  UpdateRevealedLock();
229}
230
231void ImmersiveFullscreenController::BubbleManager::OnWindowDestroying(
232    aura::Window* window) {
233  StopObserving(window);
234}
235
236////////////////////////////////////////////////////////////////////////////////
237
238ImmersiveFullscreenController::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
254ImmersiveFullscreenController::~ImmersiveFullscreenController() {
255  EnableWindowObservers(false);
256}
257
258void 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
269void 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
327bool ImmersiveFullscreenController::IsEnabled() const {
328  return enabled_;
329}
330
331bool ImmersiveFullscreenController::IsRevealed() const {
332  return enabled_ && reveal_state_ != CLOSED;
333}
334
335ImmersiveRevealedLock* ImmersiveFullscreenController::GetRevealedLock(
336    AnimateReveal animate_reveal) {
337  return new ImmersiveRevealedLock(weak_ptr_factory_.GetWeakPtr(),
338                                   animate_reveal);
339}
340
341////////////////////////////////////////////////////////////////////////////////
342// Testing interface:
343
344void 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
366void 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
390void 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
402void 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
445void ImmersiveFullscreenController::OnWillChangeFocus(
446    views::View* focused_before,
447    views::View* focused_now) {
448}
449
450void ImmersiveFullscreenController::OnDidChangeFocus(
451    views::View* focused_before,
452    views::View* focused_now) {
453  UpdateFocusRevealedLock();
454}
455
456////////////////////////////////////////////////////////////////////////////////
457// views::WidgetObserver overrides:
458
459void 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
468void ImmersiveFullscreenController::OnWidgetActivationChanged(
469    views::Widget* widget,
470    bool active) {
471  UpdateFocusRevealedLock();
472}
473
474////////////////////////////////////////////////////////////////////////////////
475// gfx::AnimationDelegate overrides:
476
477void 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
486void ImmersiveFullscreenController::AnimationProgressed(
487    const gfx::Animation* animation) {
488  delegate_->SetVisibleFraction(animation->GetCurrentValue());
489}
490
491////////////////////////////////////////////////////////////////////////////////
492// aura::WindowObserver overrides:
493
494void 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
508void ImmersiveFullscreenController::OnTransientChildRemoved(
509    aura::Window* window,
510    aura::Window* transient) {
511  bubble_manager_->StopObserving(transient);
512}
513
514////////////////////////////////////////////////////////////////////////////////
515// ash::ImmersiveRevealedLock::Delegate overrides:
516
517void 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
525void 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
537void 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
569void 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
622void 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
693void 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
701void 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
747bool 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
781int 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
794void 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
828void 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
838void 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
863void ImmersiveFullscreenController::OnSlideClosedAnimationCompleted() {
864  DCHECK_EQ(SLIDING_CLOSED, reveal_state_);
865  reveal_state_ = CLOSED;
866  delegate_->OnImmersiveRevealEnded();
867}
868
869ImmersiveFullscreenController::SwipeType
870ImmersiveFullscreenController::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
888bool 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
909bool 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
941void 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