Home | History | Annotate | Download | only in multi_user
      1 // Copyright 2014 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 "chrome/browser/ui/ash/multi_user/user_switch_animator_chromeos.h"
      6 
      7 #include "ash/desktop_background/user_wallpaper_delegate.h"
      8 #include "ash/root_window_controller.h"
      9 #include "ash/shelf/shelf_layout_manager.h"
     10 #include "ash/shelf/shelf_widget.h"
     11 #include "ash/shell.h"
     12 #include "ash/wm/mru_window_tracker.h"
     13 #include "ash/wm/window_positioner.h"
     14 #include "ash/wm/window_state.h"
     15 #include "chrome/browser/chromeos/login/users/wallpaper/wallpaper_manager.h"
     16 #include "chrome/browser/ui/ash/launcher/chrome_launcher_controller.h"
     17 #include "chrome/browser/ui/ash/multi_user/multi_user_notification_blocker_chromeos.h"
     18 #include "chrome/browser/ui/ash/multi_user/multi_user_window_manager_chromeos.h"
     19 #include "ui/wm/public/activation_client.h"
     20 
     21 namespace chrome {
     22 
     23 namespace {
     24 
     25 // The minimal possible animation time for animations which should happen
     26 // "instantly".
     27 const int kMinimalAnimationTimeMS = 1;
     28 
     29 // logic while the user gets switched.
     30 class UserChangeActionDisabler {
     31  public:
     32   UserChangeActionDisabler() {
     33     ash::WindowPositioner::DisableAutoPositioning(true);
     34     ash::Shell::GetInstance()->mru_window_tracker()->SetIgnoreActivations(true);
     35   }
     36 
     37   ~UserChangeActionDisabler() {
     38     ash::WindowPositioner::DisableAutoPositioning(false);
     39     ash::Shell::GetInstance()->mru_window_tracker()->SetIgnoreActivations(
     40         false);
     41   }
     42  private:
     43 
     44   DISALLOW_COPY_AND_ASSIGN(UserChangeActionDisabler);
     45 };
     46 
     47 }  // namespace
     48 
     49 UserSwichAnimatorChromeOS::UserSwichAnimatorChromeOS(
     50     MultiUserWindowManagerChromeOS* owner,
     51     const std::string& new_user_id,
     52     int animation_speed_ms)
     53     : owner_(owner),
     54       new_user_id_(new_user_id),
     55       animation_speed_ms_(animation_speed_ms),
     56       animation_step_(ANIMATION_STEP_HIDE_OLD_USER),
     57       screen_cover_(GetScreenCover(NULL)) {
     58   AdvanceUserTransitionAnimation();
     59 
     60   if (!animation_speed_ms_) {
     61     FinalizeAnimation();
     62   } else {
     63     user_changed_animation_timer_.reset(new base::Timer(
     64         FROM_HERE,
     65         base::TimeDelta::FromMilliseconds(animation_speed_ms_),
     66         base::Bind(
     67             &UserSwichAnimatorChromeOS::AdvanceUserTransitionAnimation,
     68             base::Unretained(this)),
     69         true));
     70     user_changed_animation_timer_->Reset();
     71   }
     72 }
     73 
     74 UserSwichAnimatorChromeOS::~UserSwichAnimatorChromeOS() {
     75   FinalizeAnimation();
     76 }
     77 
     78 // static
     79 bool UserSwichAnimatorChromeOS::CoversScreen(aura::Window* window) {
     80   // Full screen covers the screen naturally. Since a normal window can have the
     81   // same size as the work area, we only compare the bounds against the work
     82   // area.
     83   if (ash::wm::GetWindowState(window)->IsFullscreen())
     84     return true;
     85   gfx::Rect bounds = window->GetBoundsInRootWindow();
     86   gfx::Rect work_area = gfx::Screen::GetScreenFor(window)->
     87       GetDisplayNearestWindow(window).work_area();
     88   bounds.Intersect(work_area);
     89   return work_area == bounds;
     90 }
     91 
     92 void UserSwichAnimatorChromeOS::AdvanceUserTransitionAnimation() {
     93   DCHECK_NE(animation_step_, ANIMATION_STEP_ENDED);
     94 
     95   TransitionWallpaper(animation_step_);
     96   TransitionUserShelf(animation_step_);
     97   TransitionWindows(animation_step_);
     98 
     99   // Advance to the next step.
    100   switch (animation_step_) {
    101     case ANIMATION_STEP_HIDE_OLD_USER:
    102       animation_step_ = ANIMATION_STEP_SHOW_NEW_USER;
    103       break;
    104     case ANIMATION_STEP_SHOW_NEW_USER:
    105       animation_step_ = ANIMATION_STEP_FINALIZE;
    106       break;
    107     case ANIMATION_STEP_FINALIZE:
    108       user_changed_animation_timer_.reset();
    109       animation_step_ = ANIMATION_STEP_ENDED;
    110       break;
    111     case ANIMATION_STEP_ENDED:
    112       NOTREACHED();
    113       break;
    114   }
    115 }
    116 
    117 void UserSwichAnimatorChromeOS::CancelAnimation() {
    118   animation_step_ = ANIMATION_STEP_ENDED;
    119 }
    120 
    121 void UserSwichAnimatorChromeOS::FinalizeAnimation() {
    122   user_changed_animation_timer_.reset();
    123   while (ANIMATION_STEP_ENDED != animation_step_)
    124     AdvanceUserTransitionAnimation();
    125 }
    126 
    127 void UserSwichAnimatorChromeOS::TransitionWallpaper(
    128     AnimationStep animation_step) {
    129   // Handle the wallpaper switch.
    130   ash::UserWallpaperDelegate* wallpaper_delegate =
    131       ash::Shell::GetInstance()->user_wallpaper_delegate();
    132   if (animation_step == ANIMATION_STEP_HIDE_OLD_USER) {
    133     // Set the wallpaper cross dissolve animation duration to our complete
    134     // animation cycle for a fade in and fade out.
    135     int duration =
    136         NO_USER_COVERS_SCREEN == screen_cover_ ? (2 * animation_speed_ms_) : 0;
    137     wallpaper_delegate->SetAnimationDurationOverride(
    138         std::max(duration, kMinimalAnimationTimeMS));
    139     if (screen_cover_ != NEW_USER_COVERS_SCREEN) {
    140       chromeos::WallpaperManager::Get()->SetUserWallpaperNow(new_user_id_);
    141       wallpaper_user_id_ =
    142           (NO_USER_COVERS_SCREEN == screen_cover_ ? "->" : "") +
    143           new_user_id_;
    144     }
    145   } else if (animation_step == ANIMATION_STEP_FINALIZE) {
    146     // Revert the wallpaper cross dissolve animation duration back to the
    147     // default.
    148     if (screen_cover_ == NEW_USER_COVERS_SCREEN)
    149       chromeos::WallpaperManager::Get()->SetUserWallpaperNow(new_user_id_);
    150 
    151     // Coming here the wallpaper user id is the final result. No matter how we
    152     // got here.
    153     wallpaper_user_id_ = new_user_id_;
    154     wallpaper_delegate->SetAnimationDurationOverride(0);
    155   }
    156 }
    157 
    158 void UserSwichAnimatorChromeOS::TransitionUserShelf(
    159     AnimationStep animation_step) {
    160   ChromeLauncherController* chrome_launcher_controller =
    161       ChromeLauncherController::instance();
    162   // The shelf animation duration override.
    163   int duration_override = animation_speed_ms_;
    164   // Handle the shelf order of items. This is done once the old user is hidden.
    165   if (animation_step == ANIMATION_STEP_SHOW_NEW_USER) {
    166     // Some unit tests have no ChromeLauncherController.
    167     if (chrome_launcher_controller)
    168       chrome_launcher_controller->ActiveUserChanged(new_user_id_);
    169     // Hide the black rectangle on top of each shelf again.
    170     aura::Window::Windows root_windows = ash::Shell::GetAllRootWindows();
    171     for (aura::Window::Windows::const_iterator iter = root_windows.begin();
    172          iter != root_windows.end(); ++iter) {
    173       ash::ShelfWidget* shelf =
    174           ash::RootWindowController::ForWindow(*iter)->shelf();
    175       shelf->HideShelfBehindBlackBar(false, duration_override);
    176     }
    177     // We kicked off the shelf animation above and the override can be
    178     // removed.
    179     duration_override = 0;
    180   }
    181 
    182   if (!animation_speed_ms_ || animation_step == ANIMATION_STEP_FINALIZE)
    183     return;
    184 
    185   // Note: The animation duration override will be set before the old user gets
    186   // hidden and reset after the animations for the new user got kicked off.
    187   ash::Shell::RootWindowControllerList controller =
    188       ash::Shell::GetInstance()->GetAllRootWindowControllers();
    189   for (ash::Shell::RootWindowControllerList::iterator iter = controller.begin();
    190        iter != controller.end(); ++iter) {
    191     (*iter)->GetShelfLayoutManager()->SetAnimationDurationOverride(
    192         duration_override);
    193   }
    194 
    195   if (animation_step != ANIMATION_STEP_HIDE_OLD_USER)
    196     return;
    197 
    198   // For each root window hide the shelf.
    199   aura::Window::Windows root_windows = ash::Shell::GetAllRootWindows();
    200 
    201   for (aura::Window::Windows::const_iterator iter = root_windows.begin();
    202        iter != root_windows.end(); ++iter) {
    203     // Hiding the shelf will cause a resize on a maximized window.
    204     // If the shelf is then shown for the following user in the same location,
    205     // the window gets resized again. Since each resize can cause a considerable
    206     // CPU usage and therefore effect jank, we should avoid hiding the shelf if
    207     // the start and end location are the same and cover the shelf instead with
    208     // a black rectangle on top.
    209     if (GetScreenCover(*iter) != NO_USER_COVERS_SCREEN &&
    210         (!chrome_launcher_controller ||
    211          !chrome_launcher_controller->ShelfBoundsChangesProbablyWithUser(
    212              *iter, new_user_id_))) {
    213       ash::ShelfWidget* shelf =
    214           ash::RootWindowController::ForWindow(*iter)->shelf();
    215       shelf->HideShelfBehindBlackBar(true, duration_override);
    216     } else {
    217       // This shelf change is only part of the animation and will be updated by
    218       // ChromeLauncherController::ActiveUserChanged() to the new users value.
    219       // Note that the user preference will not be changed.
    220       ash::Shell::GetInstance()->SetShelfAutoHideBehavior(
    221           ash::SHELF_AUTO_HIDE_ALWAYS_HIDDEN, *iter);
    222     }
    223   }
    224 }
    225 
    226 void UserSwichAnimatorChromeOS::TransitionWindows(
    227     AnimationStep animation_step) {
    228   // Disable the window position manager and the MRU window tracker temporarily.
    229   UserChangeActionDisabler disabler;
    230 
    231   if (animation_step == ANIMATION_STEP_HIDE_OLD_USER ||
    232       (animation_step == ANIMATION_STEP_FINALIZE &&
    233        screen_cover_ == BOTH_USERS_COVER_SCREEN)) {
    234     // We need to show/hide the windows in the same order as they were created
    235     // in their parent window(s) to keep the layer / window hierarchy in sync.
    236     // To achieve that we first collect all parent windows and then enumerate
    237     // all windows in those parent windows and show or hide them accordingly.
    238 
    239     // Create a list of all parent windows we have to check.
    240     std::set<aura::Window*> parent_list;
    241     for (MultiUserWindowManagerChromeOS::WindowToEntryMap::const_iterator it =
    242              owner_->window_to_entry().begin();
    243          it != owner_->window_to_entry().end(); ++it) {
    244       aura::Window* parent = it->first->parent();
    245       if (parent_list.find(parent) == parent_list.end())
    246         parent_list.insert(parent);
    247     }
    248 
    249     for (std::set<aura::Window*>::iterator it_parents = parent_list.begin();
    250          it_parents != parent_list.end(); ++it_parents) {
    251       const aura::Window::Windows window_list = (*it_parents)->children();
    252       // In case of |BOTH_USERS_COVER_SCREEN| the desktop might shine through
    253       // if all windows fade (in or out). To avoid this we only fade the topmost
    254       // covering window (in / out) and make / keep all other covering windows
    255       // visible while animating. |foreground_window_found| will get set when
    256       // the top fading window was found.
    257       bool foreground_window_found = false;
    258       // Covering windows which follow the fade direction will also fade - all
    259       // others will get immediately shown / kept shown until the animation is
    260       // finished.
    261       bool foreground_becomes_visible = false;
    262       for (aura::Window::Windows::const_reverse_iterator it_window =
    263                window_list.rbegin();
    264            it_window != window_list.rend(); ++it_window) {
    265         aura::Window* window = *it_window;
    266         MultiUserWindowManagerChromeOS::WindowToEntryMap::const_iterator
    267             it_map = owner_->window_to_entry().find(window);
    268         if (it_map != owner_->window_to_entry().end()) {
    269           bool should_be_visible =
    270               it_map->second->show_for_user() == new_user_id_ &&
    271               it_map->second->show();
    272           bool is_visible = window->IsVisible();
    273           ash::wm::WindowState* window_state = ash::wm::GetWindowState(window);
    274           if (it_map->second->owner() == new_user_id_ &&
    275               it_map->second->show_for_user() != new_user_id_ &&
    276               window_state->IsMinimized()) {
    277             // Pull back minimized visiting windows to the owners desktop.
    278             owner_->ShowWindowForUserIntern(window, new_user_id_);
    279             window_state->Unminimize();
    280           } else if (should_be_visible != is_visible) {
    281             bool animate = true;
    282             int duration = animation_step == ANIMATION_STEP_FINALIZE ?
    283                                0 : (2 * animation_speed_ms_);
    284             duration = std::max(kMinimalAnimationTimeMS, duration);
    285             if (animation_step != ANIMATION_STEP_FINALIZE &&
    286                 screen_cover_ == BOTH_USERS_COVER_SCREEN &&
    287                 CoversScreen(window)) {
    288               if (!foreground_window_found) {
    289                 foreground_window_found = true;
    290                 foreground_becomes_visible = should_be_visible;
    291               } else if (should_be_visible != foreground_becomes_visible) {
    292                 // Covering windows behind the foreground window which are
    293                 // inverting their visibility should immediately become visible
    294                 // or stay visible until the animation is finished.
    295                 duration = kMinimalAnimationTimeMS;
    296                 if (!should_be_visible)
    297                   animate = false;
    298               }
    299             }
    300             if (animate)
    301               owner_->SetWindowVisibility(window, should_be_visible, duration);
    302           }
    303         }
    304       }
    305     }
    306   }
    307 
    308   // Activation and real switch are happening after the other user gets shown.
    309   if (animation_step == ANIMATION_STEP_SHOW_NEW_USER) {
    310     // Finally we need to restore the previously active window.
    311     ash::MruWindowTracker::WindowList mru_list =
    312         ash::Shell::GetInstance()->mru_window_tracker()->BuildMruWindowList();
    313     if (!mru_list.empty()) {
    314       aura::Window* window = mru_list[0];
    315       ash::wm::WindowState* window_state = ash::wm::GetWindowState(window);
    316       if (owner_->IsWindowOnDesktopOfUser(window, new_user_id_) &&
    317           !window_state->IsMinimized()) {
    318         aura::client::ActivationClient* client =
    319             aura::client::GetActivationClient(window->GetRootWindow());
    320         // Several unit tests come here without an activation client.
    321         if (client)
    322           client->ActivateWindow(window);
    323       }
    324     }
    325 
    326     // This is called directly here to make sure notification_blocker will see
    327     // the new window status.
    328     owner_->notification_blocker()->ActiveUserChanged(new_user_id_);
    329   }
    330 }
    331 
    332 UserSwichAnimatorChromeOS::TransitioningScreenCover
    333 UserSwichAnimatorChromeOS::GetScreenCover(aura::Window* root_window) {
    334   TransitioningScreenCover cover = NO_USER_COVERS_SCREEN;
    335   for (MultiUserWindowManagerChromeOS::WindowToEntryMap::const_iterator it_map =
    336            owner_->window_to_entry().begin();
    337        it_map != owner_->window_to_entry().end();
    338        ++it_map) {
    339     aura::Window* window = it_map->first;
    340     if (root_window && window->GetRootWindow() != root_window)
    341       continue;
    342     if (window->IsVisible() && CoversScreen(window)) {
    343       if (cover == NEW_USER_COVERS_SCREEN)
    344         return BOTH_USERS_COVER_SCREEN;
    345       else
    346         cover = OLD_USER_COVERS_SCREEN;
    347     } else if (owner_->IsWindowOnDesktopOfUser(window, new_user_id_) &&
    348                CoversScreen(window)) {
    349       if (cover == OLD_USER_COVERS_SCREEN)
    350         return BOTH_USERS_COVER_SCREEN;
    351       else
    352         cover = NEW_USER_COVERS_SCREEN;
    353     }
    354   }
    355   return cover;
    356 }
    357 
    358 }  // namespace chrome
    359