chromium/ash/wm/gestures/back_gesture/back_gesture_contextual_nudge_controller_impl.cc

// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ash/wm/gestures/back_gesture/back_gesture_contextual_nudge_controller_impl.h"

#include "ash/controls/contextual_tooltip.h"
#include "ash/public/cpp/back_gesture_contextual_nudge_delegate.h"
#include "ash/session/session_controller_impl.h"
#include "ash/shell.h"
#include "ash/shell_delegate.h"
#include "ash/wm/gestures/back_gesture/back_gesture_contextual_nudge.h"
#include "ash/wm/window_util.h"
#include "base/functional/bind.h"
#include "base/time/time.h"
#include "components/prefs/pref_service.h"
#include "ui/aura/client/window_types.h"
#include "ui/display/tablet_state.h"
#include "ui/wm/public/activation_client.h"

namespace ash {

namespace {

PrefService* GetActivePrefService() {
  return Shell::Get()->session_controller()->GetActivePrefService();
}

}  // namespace

BackGestureContextualNudgeControllerImpl::
    BackGestureContextualNudgeControllerImpl() {
  shelf_control_visible_ = ShelfConfig::Get()->shelf_controls_shown();
  ShelfConfig::Get()->AddObserver(this);
}

BackGestureContextualNudgeControllerImpl::
    ~BackGestureContextualNudgeControllerImpl() {
  ShelfConfig::Get()->RemoveObserver(this);
  DoCleanUp();
}

void BackGestureContextualNudgeControllerImpl::OnBackGestureStarted() {
  if (nudge_) {
    nudge_->CancelAnimationOrFadeOutToHide();
  }
}

void BackGestureContextualNudgeControllerImpl::OnActiveUserSessionChanged(
    const AccountId& account_id) {
  UpdateWindowMonitoring(/*can_show_nudge_immediately=*/true);
}

void BackGestureContextualNudgeControllerImpl::OnSessionStateChanged(
    session_manager::SessionState state) {
  UpdateWindowMonitoring(/*can_show_nudge_immediately=*/true);
}

void BackGestureContextualNudgeControllerImpl::OnDisplayTabletStateChanged(
    display::TabletState state) {
  switch (state) {
    case display::TabletState::kEnteringTabletMode:
    case display::TabletState::kExitingTabletMode:
      break;
    case display::TabletState::kInClamshellMode:
      UpdateWindowMonitoring(/*can_show_nudge_immediately=*/false);
      break;
    case display::TabletState::kInTabletMode:
      UpdateWindowMonitoring(/*can_show_nudge_immediately=*/true);
      break;
  }
}

void BackGestureContextualNudgeControllerImpl::OnWindowActivated(
    ActivationReason reason,
    aura::Window* gained_active,
    aura::Window* lost_active) {
  if (!gained_active)
    return;

  // If another window is activated when the nudge is waiting to be shown or
  // is currently being shown, cancel the animation.
  if (nudge_)
    nudge_->CancelAnimationOrFadeOutToHide();

  if (!nudge_ || !nudge_->ShouldNudgeCountAsShown()) {
    // Start tracking |gained_active|'s navigation status and show the
    // contextual nudge ui if applicable.
    nudge_delegate_->MaybeStartTrackingNavigation(gained_active);
  }
}

void BackGestureContextualNudgeControllerImpl::NavigationEntryChanged(
    aura::Window* window) {
  // If navigation entry changed when the nudge is waiting to be shown or is
  // currently being shown, cancel the animation.
  if (nudge_)
    nudge_->CancelAnimationOrFadeOutToHide();

  MaybeShowNudgeUi(window);
}

void BackGestureContextualNudgeControllerImpl::OnShelfConfigUpdated() {
  bool updated_shelf_control_visibility =
      ShelfConfig::Get()->shelf_controls_shown();

  if (shelf_control_visible_ == updated_shelf_control_visibility)
    return;

  shelf_control_visible_ = updated_shelf_control_visibility;

  if (!nudge_ || !shelf_control_visible_)
    return;

  nudge_->CancelAnimationOrFadeOutToHide();
}

bool BackGestureContextualNudgeControllerImpl::CanShowNudge(
    base::TimeDelta* recheck_delay) const {
  if (!Shell::Get()->IsInTabletMode())
    return false;

  if (Shell::Get()->session_controller()->GetSessionState() !=
      session_manager::SessionState::ACTIVE) {
    return false;
  }

  if (shelf_control_visible_)
    return false;

  return contextual_tooltip::ShouldShowNudge(
      GetActivePrefService(), contextual_tooltip::TooltipType::kBackGesture,
      recheck_delay);
}

void BackGestureContextualNudgeControllerImpl::MaybeShowNudgeUi(
    aura::Window* window) {
  if ((!nudge_ || !nudge_->ShouldNudgeCountAsShown()) &&
      window->GetType() == aura::client::WINDOW_TYPE_NORMAL &&
      !window->is_destroying() &&
      Shell::Get()->shell_delegate()->CanGoBack(window) &&
      CanShowNudge(nullptr)) {
    contextual_tooltip::SetBackGestureNudgeShowing(true);
    nudge_ = std::make_unique<BackGestureContextualNudge>(base::BindOnce(
        &BackGestureContextualNudgeControllerImpl::OnNudgeAnimationFinished,
        weak_ptr_factory_.GetWeakPtr()));
  }
}

void BackGestureContextualNudgeControllerImpl::UpdateWindowMonitoring(
    bool can_show_nudge_immediately) {
  base::TimeDelta recheck_delay;
  const bool should_monitor = CanShowNudge(&recheck_delay);

  // If the nudge cannot be shown at this time, but could become available after
  // some time, schedule a timer to re-evaluate window monitoring state.
  if (!should_monitor && !recheck_delay.is_zero()) {
    auto_show_timer_.Start(
        FROM_HERE, recheck_delay,
        base::BindOnce(
            &BackGestureContextualNudgeControllerImpl::UpdateWindowMonitoring,
            base::Unretained(this), /*can_show_nudge_immediately=*/false));
  }

  if (is_monitoring_windows_ == should_monitor)
    return;
  is_monitoring_windows_ = should_monitor;

  if (should_monitor) {
    // Start monitoring window.
    nudge_delegate_ = Shell::Get()
                          ->shell_delegate()
                          ->CreateBackGestureContextualNudgeDelegate(this);
    // If there is an active window at this moment and we should monitor its
    // navigation status, start monitoring it now.
    aura::Window* active_window = window_util::GetActiveWindow();
    if (active_window) {
      if (can_show_nudge_immediately)
        MaybeShowNudgeUi(active_window);
      nudge_delegate_->MaybeStartTrackingNavigation(active_window);
    }

    Shell::Get()->activation_client()->AddObserver(this);
    return;
  }

  // Stop monitoring window.
  nudge_delegate_.reset();
  Shell::Get()->activation_client()->RemoveObserver(this);
  // Cancel any in-waiting animation or in-progress animation.
  if (nudge_)
    nudge_->CancelAnimationOrFadeOutToHide();
}

void BackGestureContextualNudgeControllerImpl::OnNudgeAnimationFinished(
    bool animation_completed) {
  const bool count_as_shown = nudge_->ShouldNudgeCountAsShown();
  // UpdateWindowMonitoring() might attempt to cancel any in-progress nudge,
  // which would switch the nudge into an invalid state. Reset the nudge before
  // window monitoring is updated.
  nudge_.reset();

  if (animation_completed)
    DCHECK(count_as_shown);

  contextual_tooltip::SetBackGestureNudgeShowing(false);

  if (count_as_shown) {
    UpdateWindowMonitoring(/*can_show_nudge_immediately=*/false);

    // Set a timer to monitoring windows and show nudge ui again.
    auto_show_timer_.Start(
        FROM_HERE, contextual_tooltip::kMinInterval,
        base::BindOnce(
            &BackGestureContextualNudgeControllerImpl::UpdateWindowMonitoring,
            base::Unretained(this),
            /*can_show_nudge_immediately=*/false));
  }
}

void BackGestureContextualNudgeControllerImpl::DoCleanUp() {
  if (is_monitoring_windows_) {
    Shell::Get()->activation_client()->RemoveObserver(this);
    nudge_delegate_.reset();
  }

  nudge_.reset();
  contextual_tooltip::SetBackGestureNudgeShowing(false);
  is_monitoring_windows_ = false;
}

}  // namespace ash