text: // Copyright 2014 The Chromium Authors. All rights reserved.
  // Use of this source code is governed by a BSD-style license that can be
  // found in the LICENSE file.
  
  #include "chrome/browser/ui/ash/multi_user/user_switch_animator_chromeos.h"
  
  #include "ash/desktop_background/user_wallpaper_delegate.h"
  #include "ash/root_window_controller.h"
  #include "ash/shelf/shelf_layout_manager.h"
  #include "ash/shelf/shelf_widget.h"
  #include "ash/shell.h"
  #include "ash/wm/mru_window_tracker.h"
  #include "ash/wm/window_positioner.h"
  #include "ash/wm/window_state.h"
  #include "chrome/browser/chromeos/login/users/wallpaper/wallpaper_manager.h"
  #include "chrome/browser/ui/ash/launcher/chrome_launcher_controller.h"
  #include "chrome/browser/ui/ash/multi_user/multi_user_notification_blocker_chromeos.h"
  #include "chrome/browser/ui/ash/multi_user/multi_user_window_manager_chromeos.h"
  #include "ui/wm/public/activation_client.h"
  
  namespace chrome {
  
  namespace {
  
  // The minimal possible animation time for animations which should happen
  // "instantly".
  const int kMinimalAnimationTimeMS = 1;
  
  // logic while the user gets switched.
  class UserChangeActionDisabler {
   public:
    UserChangeActionDisabler() {
      ash::WindowPositioner::DisableAutoPositioning(true);
      ash::Shell::GetInstance()->mru_window_tracker()->SetIgnoreActivations(true);
    }
  
    ~UserChangeActionDisabler() {
      ash::WindowPositioner::DisableAutoPositioning(false);
      ash::Shell::GetInstance()->mru_window_tracker()->SetIgnoreActivations(
          false);
    }
   private:
  
    DISALLOW_COPY_AND_ASSIGN(UserChangeActionDisabler);
  };
  
  }  // namespace
  
  UserSwichAnimatorChromeOS::UserSwichAnimatorChromeOS(
      MultiUserWindowManagerChromeOS* owner,
      const std::string& new_user_id,
      int animation_speed_ms)
      : owner_(owner),
        new_user_id_(new_user_id),
        animation_speed_ms_(animation_speed_ms),
        animation_step_(ANIMATION_STEP_HIDE_OLD_USER),
        screen_cover_(GetScreenCover(NULL)) {
    AdvanceUserTransitionAnimation();
  
    if (!animation_speed_ms_) {
      FinalizeAnimation();
    } else {
      user_changed_animation_timer_.reset(new base::Timer(
          FROM_HERE,
          base::TimeDelta::FromMilliseconds(animation_speed_ms_),
          base::Bind(
              &UserSwichAnimatorChromeOS::AdvanceUserTransitionAnimation,
              base::Unretained(this)),
          true));
      user_changed_animation_timer_->Reset();
    }
  }
  
  UserSwichAnimatorChromeOS::~UserSwichAnimatorChromeOS() {
    FinalizeAnimation();
  }
  
  // static
  bool UserSwichAnimatorChromeOS::CoversScreen(aura::Window* window) {
    // Full screen covers the screen naturally. Since a normal window can have the
    // same size as the work area, we only compare the bounds against the work
    // area.
    if (ash::wm::GetWindowState(window)->IsFullscreen())
      return true;
    gfx::Rect bounds = window->GetBoundsInRootWindow();
    gfx::Rect work_area = gfx::Screen::GetScreenFor(window)->
        GetDisplayNearestWindow(window).work_area();
    bounds.Intersect(work_area);
    return work_area == bounds;
  }
  
  void UserSwichAnimatorChromeOS::AdvanceUserTransitionAnimation() {
    DCHECK_NE(animation_step_, ANIMATION_STEP_ENDED);
  
    TransitionWallpaper(animation_step_);
    TransitionUserShelf(animation_step_);
    TransitionWindows(animation_step_);
  
    // Advance to the next step.
    switch (animation_step_) {
      case ANIMATION_STEP_HIDE_OLD_USER:
        animation_step_ = ANIMATION_STEP_SHOW_NEW_USER;
        break;
      case ANIMATION_STEP_SHOW_NEW_USER:
        animation_step_ = ANIMATION_STEP_FINALIZE;
        break;
      case ANIMATION_STEP_FINALIZE:
        user_changed_animation_timer_.reset();
        animation_step_ = ANIMATION_STEP_ENDED;
        break;
      case ANIMATION_STEP_ENDED:
        NOTREACHED();
        break;
    }
  }
  
  void UserSwichAnimatorChromeOS::CancelAnimation() {
    animation_step_ = ANIMATION_STEP_ENDED;
  }
  
  void UserSwichAnimatorChromeOS::FinalizeAnimation() {
    user_changed_animation_timer_.reset();
    while (ANIMATION_STEP_ENDED != animation_step_)
      AdvanceUserTransitionAnimation();
  }
  
  void UserSwichAnimatorChromeOS::TransitionWallpaper(
      AnimationStep animation_step) {
    // Handle the wallpaper switch.
    ash::UserWallpaperDelegate* wallpaper_delegate =
        ash::Shell::GetInstance()->user_wallpaper_delegate();
    if (animation_step == ANIMATION_STEP_HIDE_OLD_USER) {
      // Set the wallpaper cross dissolve animation duration to our complete
      // animation cycle for a fade in and fade out.
      int duration =
          NO_USER_COVERS_SCREEN == screen_cover_ ? (2 * animation_speed_ms_) : 0;
      wallpaper_delegate->SetAnimationDurationOverride(
          std::max(duration, kMinimalAnimationTimeMS));
      if (screen_cover_ != NEW_USER_COVERS_SCREEN) {
        chromeos::WallpaperManager::Get()->SetUserWallpaperNow(new_user_id_);
        wallpaper_user_id_ =
            (NO_USER_COVERS_SCREEN == screen_cover_ ? "->" : "") +
            new_user_id_;
      }
    } else if (animation_step == ANIMATION_STEP_FINALIZE) {
      // Revert the wallpaper cross dissolve animation duration back to the
      // default.
      if (screen_cover_ == NEW_USER_COVERS_SCREEN)
        chromeos::WallpaperManager::Get()->SetUserWallpaperNow(new_user_id_);
  
      // Coming here the wallpaper user id is the final result. No matter how we
      // got here.
      wallpaper_user_id_ = new_user_id_;
      wallpaper_delegate->SetAnimationDurationOverride(0);
    }
  }
  
  void UserSwichAnimatorChromeOS::TransitionUserShelf(
      AnimationStep animation_step) {
    ChromeLauncherController* chrome_launcher_controller =
        ChromeLauncherController::instance();
    // The shelf animation duration override.
    int duration_override = animation_speed_ms_;
    // Handle the shelf order of items. This is done once the old user is hidden.
    if (animation_step == ANIMATION_STEP_SHOW_NEW_USER) {
      // Some unit tests have no ChromeLauncherController.
      if (chrome_launcher_controller)
        chrome_launcher_controller->ActiveUserChanged(new_user_id_);
      // Hide the black rectangle on top of each shelf again.
      aura::Window::Windows root_windows = ash::Shell::GetAllRootWindows();
      for (aura::Window::Windows::const_iterator iter = root_windows.begin();
           iter != root_windows.end(); ++iter) {
        ash::ShelfWidget* shelf =
            ash::RootWindowController::ForWindow(*iter)->shelf();
        shelf->HideShelfBehindBlackBar(false, duration_override);
      }
      // We kicked off the shelf animation above and the override can be
      // removed.
      duration_override = 0;
    }
  
    if (!animation_speed_ms_ || animation_step == ANIMATION_STEP_FINALIZE)
      return;
  
    // Note: The animation duration override will be set before the old user gets
    // hidden and reset after the animations for the new user got kicked off.
    ash::Shell::RootWindowControllerList controller =
        ash::Shell::GetInstance()->GetAllRootWindowControllers();
    for (ash::Shell::RootWindowControllerList::iterator iter = controller.begin();
         iter != controller.end(); ++iter) {
      (*iter)->GetShelfLayoutManager()->SetAnimationDurationOverride(
          duration_override);
    }
  
    if (animation_step != ANIMATION_STEP_HIDE_OLD_USER)
      return;
  
    // For each root window hide the shelf.
    aura::Window::Windows root_windows = ash::Shell::GetAllRootWindows();
  
    for (aura::Window::Windows::const_iterator iter = root_windows.begin();
         iter != root_windows.end(); ++iter) {
      // Hiding the shelf will cause a resize on a maximized window.
      // If the shelf is then shown for the following user in the same location,
      // the window gets resized again. Since each resize can cause a considerable
      // CPU usage and therefore effect jank, we should avoid hiding the shelf if
      // the start and end location are the same and cover the shelf instead with
      // a black rectangle on top.
      if (GetScreenCover(*iter) != NO_USER_COVERS_SCREEN &&
          (!chrome_launcher_controller ||
           !chrome_launcher_controller->ShelfBoundsChangesProbablyWithUser(
               *iter, new_user_id_))) {
        ash::ShelfWidget* shelf =
            ash::RootWindowController::ForWindow(*iter)->shelf();
        shelf->HideShelfBehindBlackBar(true, duration_override);
      } else {
        // This shelf change is only part of the animation and will be updated by
        // ChromeLauncherController::ActiveUserChanged() to the new users value.
        // Note that the user preference will not be changed.
        ash::Shell::GetInstance()->SetShelfAutoHideBehavior(
            ash::SHELF_AUTO_HIDE_ALWAYS_HIDDEN, *iter);
      }
    }
  }
  
  void UserSwichAnimatorChromeOS::TransitionWindows(
      AnimationStep animation_step) {
    // Disable the window position manager and the MRU window tracker temporarily.
    UserChangeActionDisabler disabler;
  
    if (animation_step == ANIMATION_STEP_HIDE_OLD_USER ||
        (animation_step == ANIMATION_STEP_FINALIZE &&
         screen_cover_ == BOTH_USERS_COVER_SCREEN)) {
      // We need to show/hide the windows in the same order as they were created
      // in their parent window(s) to keep the layer / window hierarchy in sync.
      // To achieve that we first collect all parent windows and then enumerate
      // all windows in those parent windows and show or hide them accordingly.
  
      // Create a list of all parent windows we have to check.
      std::set<aura::Window*> parent_list;
      for (MultiUserWindowManagerChromeOS::WindowToEntryMap::const_iterator it =
               owner_->window_to_entry().begin();
           it != owner_->window_to_entry().end(); ++it) {
        aura::Window* parent = it->first->parent();
        if (parent_list.find(parent) == parent_list.end())
          parent_list.insert(parent);
      }
  
      for (std::set<aura::Window*>::iterator it_parents = parent_list.begin();
           it_parents != parent_list.end(); ++it_parents) {
        const aura::Window::Windows window_list = (*it_parents)->children();
        // In case of |BOTH_USERS_COVER_SCREEN| the desktop might shine through
        // if all windows fade (in or out). To avoid this we only fade the topmost
        // covering window (in / out) and make / keep all other covering windows
        // visible while animating. |foreground_window_found| will get set when
        // the top fading window was found.
        bool foreground_window_found = false;
        // Covering windows which follow the fade direction will also fade - all
        // others will get immediately shown / kept shown until the animation is
        // finished.
        bool foreground_becomes_visible = false;
        for (aura::Window::Windows::const_reverse_iterator it_window =
                 window_list.rbegin();
             it_window != window_list.rend(); ++it_window) {
          aura::Window* window = *it_window;
          MultiUserWindowManagerChromeOS::WindowToEntryMap::const_iterator
              it_map = owner_->window_to_entry().find(window);
          if (it_map != owner_->window_to_entry().end()) {
            bool should_be_visible =
                it_map->second->show_for_user() == new_user_id_ &&
                it_map->second->show();
            bool is_visible = window->IsVisible();
            ash::wm::WindowState* window_state = ash::wm::GetWindowState(window);
            if (it_map->second->owner() == new_user_id_ &&
                it_map->second->show_for_user() != new_user_id_ &&
                window_state->IsMinimized()) {
              // Pull back minimized visiting windows to the owners desktop.
              owner_->ShowWindowForUserIntern(window, new_user_id_);
              window_state->Unminimize();
            } else if (should_be_visible != is_visible) {
              bool animate = true;
              int duration = animation_step == ANIMATION_STEP_FINALIZE ?
                                 0 : (2 * animation_speed_ms_);
              duration = std::max(kMinimalAnimationTimeMS, duration);
              if (animation_step != ANIMATION_STEP_FINALIZE &&
                  screen_cover_ == BOTH_USERS_COVER_SCREEN &&
                  CoversScreen(window)) {
                if (!foreground_window_found) {
                  foreground_window_found = true;
                  foreground_becomes_visible = should_be_visible;
                } else if (should_be_visible != foreground_becomes_visible) {
                  // Covering windows behind the foreground window which are
                  // inverting their visibility should immediately become visible
                  // or stay visible until the animation is finished.
                  duration = kMinimalAnimationTimeMS;
                  if (!should_be_visible)
                    animate = false;
                }
              }
              if (animate)
                owner_->SetWindowVisibility(window, should_be_visible, duration);
            }
          }
        }
      }
    }
  
    // Activation and real switch are happening after the other user gets shown.
    if (animation_step == ANIMATION_STEP_SHOW_NEW_USER) {
      // Finally we need to restore the previously active window.
      ash::MruWindowTracker::WindowList mru_list =
          ash::Shell::GetInstance()->mru_window_tracker()->BuildMruWindowList();
      if (!mru_list.empty()) {
        aura::Window* window = mru_list[0];
        ash::wm::WindowState* window_state = ash::wm::GetWindowState(window);
        if (owner_->IsWindowOnDesktopOfUser(window, new_user_id_) &&
            !window_state->IsMinimized()) {
          aura::client::ActivationClient* client =
              aura::client::GetActivationClient(window->GetRootWindow());
          // Several unit tests come here without an activation client.
          if (client)
            client->ActivateWindow(window);
        }
      }
  
      // This is called directly here to make sure notification_blocker will see
      // the new window status.
      owner_->notification_blocker()->ActiveUserChanged(new_user_id_);
    }
  }
  
  UserSwichAnimatorChromeOS::TransitioningScreenCover
  UserSwichAnimatorChromeOS::GetScreenCover(aura::Window* root_window) {
    TransitioningScreenCover cover = NO_USER_COVERS_SCREEN;
    for (MultiUserWindowManagerChromeOS::WindowToEntryMap::const_iterator it_map =
             owner_->window_to_entry().begin();
         it_map != owner_->window_to_entry().end();
         ++it_map) {
      aura::Window* window = it_map->first;
      if (root_window && window->GetRootWindow() != root_window)
        continue;
      if (window->IsVisible() && CoversScreen(window)) {
        if (cover == NEW_USER_COVERS_SCREEN)
          return BOTH_USERS_COVER_SCREEN;
        else
          cover = OLD_USER_COVERS_SCREEN;
      } else if (owner_->IsWindowOnDesktopOfUser(window, new_user_id_) &&
                 CoversScreen(window)) {
        if (cover == OLD_USER_COVERS_SCREEN)
          return BOTH_USERS_COVER_SCREEN;
        else
          cover = NEW_USER_COVERS_SCREEN;
      }
    }
    return cover;
  }
  
  }  // namespace chrome
  