• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.
AsBubbleDelegate(aura::Window * maybe_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|.
IsWindowTransientChildOf(aura::Window * maybe_transient,aura::Window * 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.
GetEventLocationInScreen(const ui::LocatedEvent & event)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.
GetDisplayBoundsInScreen(aura::Window * window)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 
BubbleManager(ImmersiveFullscreenController * controller)155 ImmersiveFullscreenController::BubbleManager::BubbleManager(
156     ImmersiveFullscreenController* controller)
157     : controller_(controller) {
158 }
159 
~BubbleManager()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 
StartObserving(aura::Window * bubble)167 void ImmersiveFullscreenController::BubbleManager::StartObserving(
168     aura::Window* bubble) {
169   if (bubbles_.insert(bubble).second) {
170     bubble->AddObserver(this);
171     UpdateRevealedLock();
172   }
173 }
174 
StopObserving(aura::Window * bubble)175 void ImmersiveFullscreenController::BubbleManager::StopObserving(
176     aura::Window* bubble) {
177   if (bubbles_.erase(bubble)) {
178     bubble->RemoveObserver(this);
179     UpdateRevealedLock();
180   }
181 }
182 
UpdateRevealedLock()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 
OnWindowVisibilityChanged(aura::Window *,bool visible)219 void ImmersiveFullscreenController::BubbleManager::OnWindowVisibilityChanged(
220     aura::Window*,
221     bool visible) {
222   UpdateRevealedLock();
223 }
224 
OnWindowDestroying(aura::Window * window)225 void ImmersiveFullscreenController::BubbleManager::OnWindowDestroying(
226     aura::Window* window) {
227   StopObserving(window);
228 }
229 
230 ////////////////////////////////////////////////////////////////////////////////
231 
ImmersiveFullscreenController()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 
~ImmersiveFullscreenController()248 ImmersiveFullscreenController::~ImmersiveFullscreenController() {
249   EnableWindowObservers(false);
250 }
251 
Init(Delegate * delegate,views::Widget * widget,views::View * top_container)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 
SetEnabled(WindowType window_type,bool enabled)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 
IsEnabled() const314 bool ImmersiveFullscreenController::IsEnabled() const {
315   return enabled_;
316 }
317 
IsRevealed() const318 bool ImmersiveFullscreenController::IsRevealed() const {
319   return enabled_ && reveal_state_ != CLOSED;
320 }
321 
GetRevealedLock(AnimateReveal animate_reveal)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 
SetupForTest()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 
OnMouseEvent(ui::MouseEvent * event)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 
OnTouchEvent(ui::TouchEvent * event)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 
OnGestureEvent(ui::GestureEvent * event)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 
OnWillChangeFocus(views::View * focused_before,views::View * focused_now)426 void ImmersiveFullscreenController::OnWillChangeFocus(
427     views::View* focused_before,
428     views::View* focused_now) {
429 }
430 
OnDidChangeFocus(views::View * focused_before,views::View * focused_now)431 void ImmersiveFullscreenController::OnDidChangeFocus(
432     views::View* focused_before,
433     views::View* focused_now) {
434   UpdateFocusRevealedLock();
435 }
436 
437 ////////////////////////////////////////////////////////////////////////////////
438 // views::WidgetObserver overrides:
439 
OnWidgetDestroying(views::Widget * widget)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 
OnWidgetActivationChanged(views::Widget * widget,bool active)449 void ImmersiveFullscreenController::OnWidgetActivationChanged(
450     views::Widget* widget,
451     bool active) {
452   UpdateFocusRevealedLock();
453 }
454 
455 ////////////////////////////////////////////////////////////////////////////////
456 // gfx::AnimationDelegate overrides:
457 
AnimationEnded(const gfx::Animation * animation)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 
AnimationProgressed(const gfx::Animation * animation)467 void ImmersiveFullscreenController::AnimationProgressed(
468     const gfx::Animation* animation) {
469   delegate_->SetVisibleFraction(animation->GetCurrentValue());
470 }
471 
472 ////////////////////////////////////////////////////////////////////////////////
473 // aura::WindowObserver overrides:
474 
OnAddTransientChild(aura::Window * window,aura::Window * transient)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 
OnRemoveTransientChild(aura::Window * window,aura::Window * transient)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 
LockRevealedState(AnimateReveal animate_reveal)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 
UnlockRevealedState()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 
EnableWindowObservers(bool enable)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 
UpdateTopEdgeHoverTimer(ui::MouseEvent * event)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 
UpdateLocatedEventRevealedLock(ui::LocatedEvent * event)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 
AcquireLocatedEventRevealedLock()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 
UpdateFocusRevealedLock()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 
UpdateRevealedLocksForSwipe(SwipeType swipe_type)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 
GetAnimationDuration(Animate animate) const759 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 
MaybeStartReveal(Animate animate)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 
OnSlideOpenAnimationCompleted()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 
MaybeEndReveal(Animate animate)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 
OnSlideClosedAnimationCompleted()841 void ImmersiveFullscreenController::OnSlideClosedAnimationCompleted() {
842   DCHECK_EQ(SLIDING_CLOSED, reveal_state_);
843   reveal_state_ = CLOSED;
844   delegate_->OnImmersiveRevealEnded();
845 }
846 
847 ImmersiveFullscreenController::SwipeType
GetSwipeType(ui::GestureEvent * event) const848 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 
ShouldIgnoreMouseEventAtLocation(const gfx::Point & location) const862 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 
ShouldHandleGestureEvent(const gfx::Point & location) const883 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 
RecreateBubbleManager()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