Commit 87d6424d authored by Xiaoqian Dai's avatar Xiaoqian Dai Committed by Commit Bot

Clamshell <-> tablet mode transition

There are 3 cases in clamshell to tablet mode transition:
1) overview is active but splitview is not: keep overview active during
   transition.
2) overview and splitview are both active: keep them both active during
   transition.
3) overview and splitview are both inactive: keep the current behavior.
   a) if the top window is a snapped window, put it in splitview
   b) if the second top window is also a snapped window that snaps to the
      opposite side of the screen, put it in splitview as well
   c) if the top window is not a snapped window, maximize or fullscreen
      all windows when entering tablet mode.

There are 4 cases in tablet to clamshell mode transition:
1) overview is active but splitview is not: keep overview active during
   transition.
2) overview and splitview are both active: keep them both active during
   transition.
3) overview is inactive but splitview is active (i.e., two snapped windows),
   splitview will be no longer active after transition. But the two snapped
   window will still keep snapped in clamshell mode.
4) overview and splitview are both inactive: keep the current behavior,
   i.e., restore all windows to its old window state before entering tablet
   mode.


Bug: 890029
Change-Id: I7769021f74c3e9d591b19258be2821992d780451
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1658955
Commit-Queue: Xiaoqian Dai <xdai@chromium.org>
Reviewed-by: default avatarMitsuru Oshima <oshima@chromium.org>
Cr-Commit-Position: refs/heads/master@{#670678}
parent 2f8031be
...@@ -768,10 +768,10 @@ void SplitViewController::OnWindowBoundsChanged( ...@@ -768,10 +768,10 @@ void SplitViewController::OnWindowBoundsChanged(
const gfx::Rect& new_bounds, const gfx::Rect& new_bounds,
ui::PropertyChangeReason reason) { ui::PropertyChangeReason reason) {
if (split_view_type_ != SplitViewType::kClamshellType || if (split_view_type_ != SplitViewType::kClamshellType ||
reason == ui::PropertyChangeReason::FROM_ANIMATION) { reason == ui::PropertyChangeReason::FROM_ANIMATION ||
!InSplitViewMode()) {
return; return;
} }
DCHECK(InSplitViewMode());
wm::WindowState* window_state = wm::GetWindowState(window); wm::WindowState* window_state = wm::GetWindowState(window);
const bool is_window_moved = window_state->is_dragged() && const bool is_window_moved = window_state->is_dragged() &&
...@@ -1109,14 +1109,37 @@ void SplitViewController::OnDisplayMetricsChanged( ...@@ -1109,14 +1109,37 @@ void SplitViewController::OnDisplayMetricsChanged(
UpdateSnappedWindowsAndDividerBounds(); UpdateSnappedWindowsAndDividerBounds();
} }
void SplitViewController::OnTabletModeStarted() { void SplitViewController::OnTabletModeStarting() {
split_view_type_ = SplitViewType::kTabletType; split_view_type_ = SplitViewType::kTabletType;
// If splitview is active when tablet mode is starting, do the clamshell mode
// splitview to tablet mode splitview transition by adding the split view
// divider bar.
if (InSplitViewMode()) {
divider_position_ = GetClosestFixedDividerPosition();
split_view_divider_ = std::make_unique<SplitViewDivider>(
this, GetDefaultSnappedWindow()->GetRootWindow());
UpdateSnappedWindowsAndDividerBounds();
NotifyDividerPositionChanged();
}
} }
void SplitViewController::OnTabletModeEnding() { void SplitViewController::OnTabletModeEnding() {
if (IsClamshellSplitViewModeEnabled()) if (IsClamshellSplitViewModeEnabled()) {
split_view_type_ = SplitViewType::kClamshellType; split_view_type_ = SplitViewType::kClamshellType;
// If splitview is active when tablet mode is ending, simply destroy the
// split view divider bar as we don't have the bar in clamshell split view
// mode.
if (InSplitViewMode())
split_view_divider_.reset();
} else if (InSplitViewMode()) {
// If clamshell splitview mode is not enabled, fall back to the old
// behavior: end splitview and overivew and all windows will return to its
// old window state before entering tablet mode.
EndSplitView(); EndSplitView();
EndOverview();
}
} }
void SplitViewController::OnTabletControllerDestroyed() { void SplitViewController::OnTabletControllerDestroyed() {
......
...@@ -59,18 +59,17 @@ class ASH_EXPORT SplitViewController : public SplitViewNotifier, ...@@ -59,18 +59,17 @@ class ASH_EXPORT SplitViewController : public SplitViewNotifier,
// top of the screen. // top of the screen.
enum SnapPosition { NONE, LEFT, RIGHT }; enum SnapPosition { NONE, LEFT, RIGHT };
// Why splitview was ended. For now, all reasons will be kNormal except when // Why splitview was ended.
// the home launcher button is pressed, an unsnappable window just got
// activated, the active user session changed, or the window dragging
// started.
enum class EndReason { enum class EndReason {
kNormal = 0, kNormal = 0,
kHomeLauncherPressed, kHomeLauncherPressed,
kUnsnappableWindowActivated, kUnsnappableWindowActivated,
kActiveUserChanged, kActiveUserChanged,
kWindowDragStarted, kWindowDragStarted,
// TODO(950827): Consider not ending Split-View on PIP expand. // TODO(edcourtney): Consider not ending Split-View on PIP expand.
// See crbug.com/950827.
kPipExpanded, kPipExpanded,
kExitTabletMode,
}; };
// The behaviors of split view are very different when in tablet mode and in // The behaviors of split view are very different when in tablet mode and in
...@@ -192,7 +191,7 @@ class ASH_EXPORT SplitViewController : public SplitViewNotifier, ...@@ -192,7 +191,7 @@ class ASH_EXPORT SplitViewController : public SplitViewNotifier,
uint32_t metrics) override; uint32_t metrics) override;
// TabletModeObserver: // TabletModeObserver:
void OnTabletModeStarted() override; void OnTabletModeStarting() override;
void OnTabletModeEnding() override; void OnTabletModeEnding() override;
void OnTabletControllerDestroyed() override; void OnTabletControllerDestroyed() override;
......
...@@ -251,8 +251,7 @@ SplitViewDivider::~SplitViewDivider() { ...@@ -251,8 +251,7 @@ SplitViewDivider::~SplitViewDivider() {
divider_widget_->Close(); divider_widget_->Close();
split_view_window_targeter_.reset(); split_view_window_targeter_.reset();
for (auto* iter : observed_windows_) for (auto* iter : observed_windows_)
iter->RemoveObserver(this); RemoveObservedWindow(iter);
observed_windows_.clear();
} }
// static // static
......
...@@ -910,6 +910,8 @@ void TabletModeController::ResetPauser() { ...@@ -910,6 +910,8 @@ void TabletModeController::ResetPauser() {
} }
void TabletModeController::FinishInitTabletMode() { void TabletModeController::FinishInitTabletMode() {
for (auto& observer : tablet_mode_observers_)
observer.OnTabletModeStarting();
tablet_mode_window_manager_ = std::make_unique<TabletModeWindowManager>(); tablet_mode_window_manager_ = std::make_unique<TabletModeWindowManager>();
tablet_mode_window_manager_->Init(); tablet_mode_window_manager_->Init();
......
...@@ -13,6 +13,9 @@ namespace ash { ...@@ -13,6 +13,9 @@ namespace ash {
// NOTE: Code in chrome should use TabletModeClientObserver. // NOTE: Code in chrome should use TabletModeClientObserver.
class ASH_EXPORT TabletModeObserver { class ASH_EXPORT TabletModeObserver {
public: public:
// Called when the tablet mode is about to start.
virtual void OnTabletModeStarting() {}
// Called when the tablet mode has started. Windows might still be animating // Called when the tablet mode has started. Windows might still be animating
// though. // though.
virtual void OnTabletModeStarted() {} virtual void OnTabletModeStarted() {}
......
...@@ -9,6 +9,7 @@ ...@@ -9,6 +9,7 @@
#include "ash/public/cpp/app_types.h" #include "ash/public/cpp/app_types.h"
#include "ash/public/cpp/ash_switches.h" #include "ash/public/cpp/ash_switches.h"
#include "ash/public/cpp/shell_window_ids.h" #include "ash/public/cpp/shell_window_ids.h"
#include "ash/public/cpp/window_properties.h"
#include "ash/root_window_controller.h" #include "ash/root_window_controller.h"
#include "ash/scoped_animation_disabler.h" #include "ash/scoped_animation_disabler.h"
#include "ash/session/session_controller_impl.h" #include "ash/session/session_controller_impl.h"
...@@ -36,25 +37,18 @@ namespace ash { ...@@ -36,25 +37,18 @@ namespace ash {
namespace { namespace {
// Exits overview mode if it is currently active. Returns true if overview is // This function is called to check if window[i] is eligible to be carried over
// active before cancelling it. // to split view mode during clamshell <-> tablet mode transition. Returns true
bool CancelOverview() { // if windows[i] exists, can snap in split view, is not showing in overview, and
OverviewController* controller = Shell::Get()->overview_controller(); // is not ARC window.
if (controller->InOverviewSession()) { // TODO(xdai): Make it work for ARC windows. (see
controller->EndOverview(); // https://crbug.com/922282 and
return true;
}
return false;
}
// Returns true if windows[i] exists, can snap in split view, and is not ARC. A
// window snapped in clamshell mode must meet these criteria to potentially
// carry over into tablet mode split view. We want ARC windows to be included,
// but there is an obstacle (see https://crbug.com/922282 and
// https://buganizer.corp.google.com/issues/123432223). // https://buganizer.corp.google.com/issues/123432223).
bool IsCandidateForSplitView(const MruWindowTracker::WindowList& windows, bool IsCarryOverCandidateForSplitView(
const MruWindowTracker::WindowList& windows,
size_t i) { size_t i) {
return windows.size() > i && CanSnapInSplitview(windows[i]) && return windows.size() > i && CanSnapInSplitview(windows[i]) &&
!windows[i]->GetProperty(kIsShowingInOverviewKey) &&
static_cast<ash::AppType>(windows[i]->GetProperty( static_cast<ash::AppType>(windows[i]->GetProperty(
aura::client::kAppType)) != AppType::ARC_APP; aura::client::kAppType)) != AppType::ARC_APP;
} }
...@@ -73,6 +67,24 @@ void DoSplitView( ...@@ -73,6 +67,24 @@ void DoSplitView(
split_view_controller->SnapWindow(windows[i], positions[i]); split_view_controller->SnapWindow(windows[i], positions[i]);
} }
// Returns the windows that are currently showing in splitscreen.
base::flat_map<aura::Window*, WindowStateType> GetWindowsInSplitView() {
base::flat_map<aura::Window*, WindowStateType> windows;
SplitViewController* split_view_controller =
Shell::Get()->split_view_controller();
if (split_view_controller && split_view_controller->InSplitViewMode()) {
if (split_view_controller->left_window()) {
windows.emplace(split_view_controller->left_window(),
WindowStateType::kLeftSnapped);
}
if (split_view_controller->right_window()) {
windows.emplace(split_view_controller->right_window(),
WindowStateType::kRightSnapped);
}
}
return windows;
}
} // namespace } // namespace
// Class which tells tablet mode controller to observe a given window for UMA // Class which tells tablet mode controller to observe a given window for UMA
...@@ -132,9 +144,19 @@ aura::Window* TabletModeWindowManager::GetTopWindow() { ...@@ -132,9 +144,19 @@ aura::Window* TabletModeWindowManager::GetTopWindow() {
} }
void TabletModeWindowManager::Init() { void TabletModeWindowManager::Init() {
// The overview mode needs to be ended before the tablet mode is started. To // There are 3 cases when entering tablet mode:
// guarantee the proper order, it will be turned off from here. // 1) overview is active but split view is inactive: keep overview active in
CancelOverview(); // tablet mode.
// 2) overview and splitview are both active (splitview can only be active
// when overview is active in clamshell mode): keep overview and splitview
// both active in tablet mode.
// 3) overview is inactive: keep the current behavior, i.e.,
// a. if the top window is a snapped window, put it in splitview
// b. if the second top window is also a snapped window and snapped to
// the other side, put it in split view as well. Otherwise, open
// overview on the other side of the screen
// c. if the top window is not a snapped window, maximize all windows
// when entering tablet mode.
{ {
ScopedObserveWindowAnimation scoped_observe(GetTopWindow(), this, ScopedObserveWindowAnimation scoped_observe(GetTopWindow(), this,
...@@ -153,10 +175,41 @@ void TabletModeWindowManager::Init() { ...@@ -153,10 +175,41 @@ void TabletModeWindowManager::Init() {
} }
void TabletModeWindowManager::Shutdown() { void TabletModeWindowManager::Shutdown() {
// Overview mode needs to be ended before exiting tablet mode to prevent // There are 4 cases when exiting tablet mode:
// transforming windows which are currently in // 1) overview is active but split view is inactive: keep overview active in
// overview: http://crbug.com/366605 // clamshell mode.
const bool was_in_overview = CancelOverview(); // 2) overview and splitview are both active: keep overview and splitview both
// active in clamshell mode, unless if it's single split state, splitview
// and overview will both be ended.
// 3) overview is inactive but split view is active (two snapped windows):
// split view is no longer active. But the two snapped windows will still
// keep snapped in clamshell mode.
// 4) overview and splitview are both inactive: keep the current behavior,
// i.e., restore all windows to its window state before entering tablet
// mode.
// TODO(xdai): Instead of caching snapped windows and their state here, we
// should try to see if it can be done in the WindowState::State impl.
base::flat_map<aura::Window*, WindowStateType> windows_in_splitview =
GetWindowsInSplitView();
// For case 2 and 3: End splitview mode for two snapped windows case or single
// split case to match the clamshell split view behavior. (there is no both
// snapped state or single split state in clamshell split view). The windows
// will still be kept snapped though.
SplitViewController* split_view_controller =
Shell::Get()->split_view_controller();
if (split_view_controller->InSplitViewMode()) {
OverviewController* overview_controller =
Shell::Get()->overview_controller();
if (!overview_controller->InOverviewSession() ||
overview_controller->overview_session()->IsEmpty()) {
Shell::Get()->split_view_controller()->EndSplitView(
SplitViewController::EndReason::kExitTabletMode);
overview_controller->EndOverview();
}
}
for (aura::Window* window : added_windows_) for (aura::Window* window : added_windows_)
window->RemoveObserver(this); window->RemoveObserver(this);
added_windows_.clear(); added_windows_.clear();
...@@ -169,7 +222,7 @@ void TabletModeWindowManager::Shutdown() { ...@@ -169,7 +222,7 @@ void TabletModeWindowManager::Shutdown() {
ScopedObserveWindowAnimation scoped_observe(GetTopWindow(), this, ScopedObserveWindowAnimation scoped_observe(GetTopWindow(), this,
/*exiting_tablet_mode=*/true); /*exiting_tablet_mode=*/true);
ArrangeWindowsForDesktopMode(was_in_overview); ArrangeWindowsForClamshellMode(windows_in_splitview);
} }
int TabletModeWindowManager::GetNumberOfManagedWindows() { int TabletModeWindowManager::GetNumberOfManagedWindows() {
...@@ -238,6 +291,7 @@ void TabletModeWindowManager::OnSplitViewModeEnded() { ...@@ -238,6 +291,7 @@ void TabletModeWindowManager::OnSplitViewModeEnded() {
case SplitViewController::EndReason::kHomeLauncherPressed: case SplitViewController::EndReason::kHomeLauncherPressed:
case SplitViewController::EndReason::kActiveUserChanged: case SplitViewController::EndReason::kActiveUserChanged:
case SplitViewController::EndReason::kWindowDragStarted: case SplitViewController::EndReason::kWindowDragStarted:
case SplitViewController::EndReason::kExitTabletMode:
// For the case of kHomeLauncherPressed, the home launcher will minimize // For the case of kHomeLauncherPressed, the home launcher will minimize
// the snapped windows after ending splitview, so avoid maximizing them // the snapped windows after ending splitview, so avoid maximizing them
// here. For the case of kActiveUserChanged, the snapped windows will be // here. For the case of kActiveUserChanged, the snapped windows will be
...@@ -442,7 +496,7 @@ std::vector<SplitViewController::SnapPosition> ...@@ -442,7 +496,7 @@ std::vector<SplitViewController::SnapPosition>
TabletModeWindowManager::GetSnapPositions( TabletModeWindowManager::GetSnapPositions(
const MruWindowTracker::WindowList& windows) const { const MruWindowTracker::WindowList& windows) const {
std::vector<SplitViewController::SnapPosition> result; std::vector<SplitViewController::SnapPosition> result;
if (!IsCandidateForSplitView(windows, 0u)) if (!IsCarryOverCandidateForSplitView(windows, 0u))
return result; return result;
switch (GetDesktopWindowStateType(windows[0])) { switch (GetDesktopWindowStateType(windows[0])) {
case WindowStateType::kLeftSnapped: case WindowStateType::kLeftSnapped:
...@@ -450,7 +504,7 @@ TabletModeWindowManager::GetSnapPositions( ...@@ -450,7 +504,7 @@ TabletModeWindowManager::GetSnapPositions(
// the left in split view. If windows[1] was snapped on the right in // the left in split view. If windows[1] was snapped on the right in
// desktop mode, then snap windows[1] on the right in split view. // desktop mode, then snap windows[1] on the right in split view.
result.push_back(SplitViewController::LEFT); result.push_back(SplitViewController::LEFT);
if (IsCandidateForSplitView(windows, 1u) && if (IsCarryOverCandidateForSplitView(windows, 1u) &&
GetDesktopWindowStateType(windows[1]) == GetDesktopWindowStateType(windows[1]) ==
WindowStateType::kRightSnapped) { WindowStateType::kRightSnapped) {
result.push_back(SplitViewController::RIGHT); result.push_back(SplitViewController::RIGHT);
...@@ -461,7 +515,7 @@ TabletModeWindowManager::GetSnapPositions( ...@@ -461,7 +515,7 @@ TabletModeWindowManager::GetSnapPositions(
// the right in split view. If windows[1] was snapped on the left in // the right in split view. If windows[1] was snapped on the left in
// desktop mode, then snap windows[1] on the left in split view. // desktop mode, then snap windows[1] on the left in split view.
result.push_back(SplitViewController::RIGHT); result.push_back(SplitViewController::RIGHT);
if (IsCandidateForSplitView(windows, 1u) && if (IsCarryOverCandidateForSplitView(windows, 1u) &&
GetDesktopWindowStateType(windows[1]) == GetDesktopWindowStateType(windows[1]) ==
WindowStateType::kLeftSnapped) { WindowStateType::kLeftSnapped) {
result.push_back(SplitViewController::LEFT); result.push_back(SplitViewController::LEFT);
...@@ -511,11 +565,25 @@ void TabletModeWindowManager::ArrangeWindowsForTabletMode() { ...@@ -511,11 +565,25 @@ void TabletModeWindowManager::ArrangeWindowsForTabletMode() {
DoSplitView(split_view_eligible_windows, snap_positions); DoSplitView(split_view_eligible_windows, snap_positions);
} }
void TabletModeWindowManager::ArrangeWindowsForDesktopMode( void TabletModeWindowManager::ArrangeWindowsForClamshellMode(
bool was_in_overview) { base::flat_map<aura::Window*, WindowStateType> windows_in_splitview) {
while (window_state_map_.size()) { while (window_state_map_.size()) {
aura::Window* window = window_state_map_.begin()->first; aura::Window* window = window_state_map_.begin()->first;
ForgetWindow(window, /*destroyed=*/false, was_in_overview); ForgetWindow(window, /*destroyed=*/false);
// Arriving here the window state has changed to its clamshell window state.
// Since we need to keep the windows that were in splitview still be snapped
// in clamshell mode, change its window state to the corresponding snapped
// window state.
if (windows_in_splitview.find(window) != windows_in_splitview.end() &&
IsClamshellSplitViewModeEnabled()) {
WindowStateType type = windows_in_splitview[window];
const wm::WMEvent event((type == WindowStateType::kLeftSnapped)
? wm::WM_EVENT_SNAP_LEFT
: wm::WM_EVENT_SNAP_RIGHT);
wm::GetWindowState(window)->OnWMEvent(&event);
windows_in_splitview.erase(window);
}
} }
} }
...@@ -538,8 +606,7 @@ void TabletModeWindowManager::TrackWindow(aura::Window* window, ...@@ -538,8 +606,7 @@ void TabletModeWindowManager::TrackWindow(aura::Window* window,
} }
void TabletModeWindowManager::ForgetWindow(aura::Window* window, void TabletModeWindowManager::ForgetWindow(aura::Window* window,
bool destroyed, bool destroyed) {
bool was_in_overview) {
added_windows_.erase(window); added_windows_.erase(window);
window->RemoveObserver(this); window->RemoveObserver(this);
...@@ -560,7 +627,7 @@ void TabletModeWindowManager::ForgetWindow(aura::Window* window, ...@@ -560,7 +627,7 @@ void TabletModeWindowManager::ForgetWindow(aura::Window* window,
} else { } else {
// By telling the state object to revert, it will switch back the old // By telling the state object to revert, it will switch back the old
// State object and destroy itself, calling WindowStateDestroyed(). // State object and destroy itself, calling WindowStateDestroyed().
it->second->LeaveTabletMode(wm::GetWindowState(it->first), was_in_overview); it->second->LeaveTabletMode(wm::GetWindowState(it->first));
DCHECK(!IsTrackingWindow(window)); DCHECK(!IsTrackingWindow(window));
} }
} }
......
...@@ -124,9 +124,10 @@ class ASH_EXPORT TabletModeWindowManager : public aura::WindowObserver, ...@@ -124,9 +124,10 @@ class ASH_EXPORT TabletModeWindowManager : public aura::WindowObserver,
void ArrangeWindowsForTabletMode(); void ArrangeWindowsForTabletMode();
// Revert all windows to how they were arranged before tablet mode. // Revert all windows to how they were arranged before tablet mode.
// |was_in_overview| indicates whether it was in overview before entering // |windows_in_splitview| contains the windows that were in splitview before
// desktop mode. // entering clamshell mode.
void ArrangeWindowsForDesktopMode(bool was_in_overview = false); void ArrangeWindowsForClamshellMode(
base::flat_map<aura::Window*, WindowStateType> windows_in_splitview);
// If the given window should be handled by us, this function will add it to // If the given window should be handled by us, this function will add it to
// the list of known windows (remembering the initial show state). // the list of known windows (remembering the initial show state).
...@@ -137,13 +138,10 @@ class ASH_EXPORT TabletModeWindowManager : public aura::WindowObserver, ...@@ -137,13 +138,10 @@ class ASH_EXPORT TabletModeWindowManager : public aura::WindowObserver,
bool snap = false, bool snap = false,
bool animate_bounds_on_attach = true); bool animate_bounds_on_attach = true);
// Remove a window from our tracking list. |was_in_overview| used when // Remove a window from our tracking list. If the window is going to be
// |destroyed| is false to help handle leaving tablet mode. If the window is // destroyed, do not restore its old previous window state object as it will
// going to be destroyed, do not restore its old previous window state object // send unnecessary window state change event.
// as it will send unnecessary window state change event. void ForgetWindow(aura::Window* window, bool destroyed);
void ForgetWindow(aura::Window* window,
bool destroyed,
bool was_in_overview = false);
// Returns true when the given window should be modified in any way by us. // Returns true when the given window should be modified in any way by us.
bool ShouldHandleWindow(aura::Window* window); bool ShouldHandleWindow(aura::Window* window);
......
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
#include <string> #include <string>
#include "ash/public/cpp/ash_features.h"
#include "ash/public/cpp/ash_switches.h" #include "ash/public/cpp/ash_switches.h"
#include "ash/public/cpp/shelf_prefs.h" #include "ash/public/cpp/shelf_prefs.h"
#include "ash/public/cpp/test/shell_test_api.h" #include "ash/public/cpp/test/shell_test_api.h"
...@@ -20,6 +21,7 @@ ...@@ -20,6 +21,7 @@
#include "ash/test/ash_test_base.h" #include "ash/test/ash_test_base.h"
#include "ash/wm/mru_window_tracker.h" #include "ash/wm/mru_window_tracker.h"
#include "ash/wm/overview/overview_controller.h" #include "ash/wm/overview/overview_controller.h"
#include "ash/wm/splitview/split_view_utils.h"
#include "ash/wm/switchable_windows.h" #include "ash/wm/switchable_windows.h"
#include "ash/wm/tablet_mode/tablet_mode_controller.h" #include "ash/wm/tablet_mode/tablet_mode_controller.h"
#include "ash/wm/tablet_mode/tablet_mode_controller_test_api.h" #include "ash/wm/tablet_mode/tablet_mode_controller_test_api.h"
...@@ -33,6 +35,7 @@ ...@@ -33,6 +35,7 @@
#include "base/run_loop.h" #include "base/run_loop.h"
#include "base/strings/stringprintf.h" #include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h" #include "base/strings/utf_string_conversions.h"
#include "base/test/scoped_feature_list.h"
#include "base/values.h" #include "base/values.h"
#include "ui/aura/client/aura_constants.h" #include "ui/aura/client/aura_constants.h"
#include "ui/aura/test/test_window_delegate.h" #include "ui/aura/test/test_window_delegate.h"
...@@ -1302,33 +1305,6 @@ TEST_F(TabletModeWindowManagerTest, TryToDesktopSizeDragUnmaximizable) { ...@@ -1302,33 +1305,6 @@ TEST_F(TabletModeWindowManagerTest, TryToDesktopSizeDragUnmaximizable) {
EXPECT_EQ(first_dragged_origin.y() + 5, window->bounds().y()); EXPECT_EQ(first_dragged_origin.y() + 5, window->bounds().y());
} }
// Test that overview is exited before entering / exiting tablet mode so that
// the window changes made by TabletModeWindowManager do not conflict with
// those made in WindowOverview.
TEST_F(TabletModeWindowManagerTest, ExitsOverview) {
// Bounds for windows we know can be controlled.
gfx::Rect rect1(10, 10, 200, 50);
gfx::Rect rect2(10, 60, 200, 50);
std::unique_ptr<aura::Window> w1(
CreateWindow(aura::client::WINDOW_TYPE_NORMAL, rect1));
std::unique_ptr<aura::Window> w2(
CreateWindow(aura::client::WINDOW_TYPE_NORMAL, rect2));
OverviewController* overview_controller = Shell::Get()->overview_controller();
ASSERT_TRUE(overview_controller->StartOverview());
ASSERT_TRUE(overview_controller->InOverviewSession());
TabletModeWindowManager* manager = CreateTabletModeWindowManager();
ASSERT_TRUE(manager);
EXPECT_FALSE(overview_controller->InOverviewSession());
ASSERT_TRUE(overview_controller->StartOverview());
ASSERT_TRUE(overview_controller->InOverviewSession());
// Destroy the manager again and check that the windows return to their
// previous state.
DestroyTabletModeWindowManager();
EXPECT_FALSE(overview_controller->InOverviewSession());
}
// Test that an edge swipe from the top will end full screen mode. // Test that an edge swipe from the top will end full screen mode.
TEST_F(TabletModeWindowManagerTest, ExitFullScreenWithEdgeSwipeFromTop) { TEST_F(TabletModeWindowManagerTest, ExitFullScreenWithEdgeSwipeFromTop) {
gfx::Rect rect(10, 10, 200, 50); gfx::Rect rect(10, 10, 200, 50);
...@@ -1810,4 +1786,218 @@ TEST_F(TabletModeWindowManagerTest, DontMaximizeTransientChild) { ...@@ -1810,4 +1786,218 @@ TEST_F(TabletModeWindowManagerTest, DontMaximizeTransientChild) {
EXPECT_EQ(rect.size(), child->bounds().size()); EXPECT_EQ(rect.size(), child->bounds().size());
} }
// Test clamshell mode <-> tablet mode transition if clamshell splitscreen is
// not enabled.
TEST_F(TabletModeWindowManagerTest, ClamshellTabletTransitionTest) {
gfx::Rect rect(10, 10, 200, 50);
std::unique_ptr<aura::Window> window(
CreateWindow(aura::client::WINDOW_TYPE_NORMAL, rect));
// 1. Clamshell -> tablet. If overview is active, it should still be kept
// active after transition.
OverviewController* overview_controller = Shell::Get()->overview_controller();
EXPECT_TRUE(overview_controller->StartOverview());
EXPECT_TRUE(overview_controller->InOverviewSession());
TabletModeWindowManager* manager = CreateTabletModeWindowManager();
EXPECT_TRUE(manager);
EXPECT_TRUE(overview_controller->InOverviewSession());
// 2. Tablet -> Clamshell. If overview is active, it should still be kept
// active after transition.
DestroyTabletModeWindowManager();
EXPECT_TRUE(overview_controller->InOverviewSession());
// 3. Clamshell -> tablet. If overview is inactive, it should still be kept
// inactive after transition. All windows will be maximized.
EXPECT_TRUE(overview_controller->EndOverview());
EXPECT_FALSE(overview_controller->InOverviewSession());
CreateTabletModeWindowManager();
EXPECT_FALSE(overview_controller->InOverviewSession());
EXPECT_TRUE(wm::GetWindowState(window.get())->IsMaximized());
// 4. Tablet -> Clamshell. The window should be restored to its old state.
DestroyTabletModeWindowManager();
EXPECT_FALSE(overview_controller->InOverviewSession());
EXPECT_FALSE(wm::GetWindowState(window.get())->IsMaximized());
// 5. Clamshell -> Tablet. If the window is snapped, it will be carried over
// to splitview in tablet mode.
const wm::WMEvent event(wm::WM_EVENT_SNAP_LEFT);
wm::GetWindowState(window.get())->OnWMEvent(&event);
EXPECT_TRUE(wm::GetWindowState(window.get())->IsSnapped());
// After transition, we should be in single split screen.
CreateTabletModeWindowManager();
EXPECT_TRUE(overview_controller->InOverviewSession());
EXPECT_TRUE(Shell::Get()->split_view_controller()->InSplitViewMode());
EXPECT_TRUE(wm::GetWindowState(window.get())->IsSnapped());
// 6. Tablet -> Clamshell. Since clamshell splitscreen is not enabled, oveview
// and splitview will be both ended, and the window is restored to its old
// state.
DestroyTabletModeWindowManager();
EXPECT_FALSE(overview_controller->InOverviewSession());
EXPECT_FALSE(Shell::Get()->split_view_controller()->InSplitViewMode());
EXPECT_TRUE(wm::GetWindowState(window.get())->IsSnapped());
// Create another normal state window to test additional scenarios.
std::unique_ptr<aura::Window> window2(
CreateWindow(aura::client::WINDOW_TYPE_NORMAL, rect));
wm::ActivateWindow(window2.get());
// 7. Clamshell -> Tablet. Since the top active window is not a snapped
// window, all windows will be maximized after the transition.
CreateTabletModeWindowManager();
EXPECT_TRUE(wm::GetWindowState(window.get())->IsMaximized());
EXPECT_TRUE(wm::GetWindowState(window2.get())->IsMaximized());
// 8. Tablet -> Clamshell. If the two windows are in splitscreen in tablet
// mode, after transition they will restore to their old window states.
Shell::Get()->split_view_controller()->SnapWindow(window.get(),
SplitViewController::LEFT);
Shell::Get()->split_view_controller()->SnapWindow(window2.get(),
SplitViewController::RIGHT);
EXPECT_TRUE(Shell::Get()->split_view_controller()->InSplitViewMode());
DestroyTabletModeWindowManager();
EXPECT_FALSE(Shell::Get()->split_view_controller()->InSplitViewMode());
EXPECT_TRUE(wm::GetWindowState(window.get())->IsSnapped());
EXPECT_FALSE(wm::GetWindowState(window2.get())->IsSnapped());
// 9. Clamshell -> Tablet. If the top two windows are snapped to both sides of
// the screen, they will carry over to tablet split view mode.
const wm::WMEvent event2(wm::WM_EVENT_SNAP_RIGHT);
wm::GetWindowState(window2.get())->OnWMEvent(&event2);
CreateTabletModeWindowManager();
EXPECT_TRUE(Shell::Get()->split_view_controller()->InSplitViewMode());
// 10. Tablet -> Clamshell. If overview and splitview are both active, they
// will be both ended after the transition.
overview_controller->StartOverview();
EXPECT_TRUE(overview_controller->InOverviewSession());
EXPECT_TRUE(Shell::Get()->split_view_controller()->InSplitViewMode());
DestroyTabletModeWindowManager();
EXPECT_FALSE(overview_controller->InOverviewSession());
EXPECT_FALSE(Shell::Get()->split_view_controller()->InSplitViewMode());
}
// The class to test TabletModeWindowManagerTest related functionalities when
// clamshell split screen feature is enabled.
class TabletModeWindowManagerWithClamshellSplitViewTest
: public TabletModeWindowManagerTest {
public:
TabletModeWindowManagerWithClamshellSplitViewTest() = default;
~TabletModeWindowManagerWithClamshellSplitViewTest() override = default;
// AshTestBase:
void SetUp() override {
scoped_feature_list_.InitAndEnableFeature(
ash::features::kDragToSnapInClamshellMode);
TabletModeWindowManagerTest::SetUp();
DCHECK(ShouldAllowSplitView());
}
private:
base::test::ScopedFeatureList scoped_feature_list_;
DISALLOW_COPY_AND_ASSIGN(TabletModeWindowManagerWithClamshellSplitViewTest);
};
// Test clamshell mode <-> tablet mode transition if clamshell splitscreen is
// enabled.
TEST_F(TabletModeWindowManagerWithClamshellSplitViewTest,
ClamshellTabletTransitionTest) {
gfx::Rect rect(10, 10, 200, 50);
std::unique_ptr<aura::Window> window(
CreateWindow(aura::client::WINDOW_TYPE_NORMAL, rect));
// 1. Clamshell -> tablet. If overview is active, it should still be kept
// active after transition.
OverviewController* overview_controller = Shell::Get()->overview_controller();
EXPECT_TRUE(overview_controller->StartOverview());
EXPECT_TRUE(overview_controller->InOverviewSession());
TabletModeWindowManager* manager = CreateTabletModeWindowManager();
EXPECT_TRUE(manager);
EXPECT_TRUE(overview_controller->InOverviewSession());
// 2. Tablet -> Clamshell. If overview is active, it should still be kept
// active after transition.
DestroyTabletModeWindowManager();
EXPECT_TRUE(overview_controller->InOverviewSession());
// 3. Clamshell -> tablet. If overview is inactive, it should still be kept
// inactive after transition. All windows will be maximized.
EXPECT_TRUE(overview_controller->EndOverview());
EXPECT_FALSE(overview_controller->InOverviewSession());
CreateTabletModeWindowManager();
EXPECT_FALSE(overview_controller->InOverviewSession());
EXPECT_TRUE(wm::GetWindowState(window.get())->IsMaximized());
// 4. Tablet -> Clamshell. The window should be restored to its old state.
DestroyTabletModeWindowManager();
EXPECT_FALSE(overview_controller->InOverviewSession());
EXPECT_FALSE(wm::GetWindowState(window.get())->IsMaximized());
// 5. Clamshell -> Tablet. If the window is snapped, it will be carried over
// to splitview in tablet mode.
const wm::WMEvent event(wm::WM_EVENT_SNAP_LEFT);
wm::GetWindowState(window.get())->OnWMEvent(&event);
EXPECT_TRUE(wm::GetWindowState(window.get())->IsSnapped());
// After transition, we should be in single split screen.
CreateTabletModeWindowManager();
EXPECT_TRUE(overview_controller->InOverviewSession());
EXPECT_TRUE(Shell::Get()->split_view_controller()->InSplitViewMode());
EXPECT_TRUE(wm::GetWindowState(window.get())->IsSnapped());
// 6. Tablet -> Clamshell. Since there is only 1 window, splitview and
// overview will be both ended. The window will be kept snapped.
DestroyTabletModeWindowManager();
EXPECT_FALSE(overview_controller->InOverviewSession());
EXPECT_FALSE(Shell::Get()->split_view_controller()->InSplitViewMode());
EXPECT_TRUE(wm::GetWindowState(window.get())->IsSnapped());
// Create another normal state window to test additional scenarios.
std::unique_ptr<aura::Window> window2(
CreateWindow(aura::client::WINDOW_TYPE_NORMAL, rect));
wm::ActivateWindow(window2.get());
// 7. Clamshell -> Tablet. Since top window is not a snapped window, all
// windows will be maximized.
CreateTabletModeWindowManager();
EXPECT_TRUE(wm::GetWindowState(window.get())->IsMaximized());
EXPECT_TRUE(wm::GetWindowState(window2.get())->IsMaximized());
// 8. Tablet -> Clamshell. If tablet splitscreen is active with two snapped
// windows, the two windows will remain snapped in clamshell mode.
Shell::Get()->split_view_controller()->SnapWindow(window.get(),
SplitViewController::LEFT);
Shell::Get()->split_view_controller()->SnapWindow(window2.get(),
SplitViewController::RIGHT);
EXPECT_TRUE(Shell::Get()->split_view_controller()->InSplitViewMode());
EXPECT_FALSE(overview_controller->InOverviewSession());
DestroyTabletModeWindowManager();
EXPECT_TRUE(wm::GetWindowState(window.get())->IsSnapped());
EXPECT_TRUE(wm::GetWindowState(window2.get())->IsSnapped());
EXPECT_FALSE(Shell::Get()->split_view_controller()->InSplitViewMode());
EXPECT_FALSE(overview_controller->InOverviewSession());
// 9. Clamshell -> Tablet. If two window are snapped to two sides of the
// screen, they will carry over to splitscreen in tablet mode.
CreateTabletModeWindowManager();
EXPECT_TRUE(Shell::Get()->split_view_controller()->InSplitViewMode());
EXPECT_FALSE(overview_controller->InOverviewSession());
EXPECT_TRUE(wm::GetWindowState(window.get())->IsSnapped());
EXPECT_TRUE(wm::GetWindowState(window2.get())->IsSnapped());
// 10. Tablet -> Clamshell. If overview and splitview are both active, after
// transition, they will remain both active.
overview_controller->StartOverview();
EXPECT_TRUE(Shell::Get()->split_view_controller()->InSplitViewMode());
EXPECT_TRUE(overview_controller->InOverviewSession());
DestroyTabletModeWindowManager();
EXPECT_TRUE(Shell::Get()->split_view_controller()->InSplitViewMode());
EXPECT_TRUE(overview_controller->InOverviewSession());
// 11. Clamshell -> Tablet. The same as 10.
CreateTabletModeWindowManager();
EXPECT_TRUE(Shell::Get()->split_view_controller()->InSplitViewMode());
EXPECT_TRUE(overview_controller->InOverviewSession());
}
} // namespace ash } // namespace ash
...@@ -197,18 +197,19 @@ TabletModeWindowState::~TabletModeWindowState() { ...@@ -197,18 +197,19 @@ TabletModeWindowState::~TabletModeWindowState() {
creator_->WindowStateDestroyed(window_); creator_->WindowStateDestroyed(window_);
} }
void TabletModeWindowState::LeaveTabletMode(wm::WindowState* window_state, void TabletModeWindowState::LeaveTabletMode(wm::WindowState* window_state) {
bool was_in_overview) { // Only do bounds change animation if the window is the top window or a window
// TODO(minch): Keep the current animation if leaving tablet mode from // showing in splitview, and the window has changed its state. Otherwise,
// overview. Need more investigation for windows' transform animation and // restore its bounds immediately.
// updates bounds animation when overview is active. EnterAnimationType animation_type =
bool use_default = was_in_overview || window_state->IsSnapped() || window_state->IsSnapped() || IsTopWindow(window_state->window())
IsTopWindow(window_state->window()); ? DEFAULT
: IMMEDIATE;
if (old_state_->GetType() == window_state->GetStateType() && if (old_state_->GetType() == window_state->GetStateType() &&
!window_state->IsNormalStateType()) { !window_state->IsNormalStateType()) {
use_default = false; animation_type = IMMEDIATE;
} }
old_state_->set_enter_animation_type(use_default ? DEFAULT : IMMEDIATE); old_state_->set_enter_animation_type(animation_type);
// Note: When we return we will destroy ourselves with the |our_reference|. // Note: When we return we will destroy ourselves with the |our_reference|.
std::unique_ptr<wm::WindowState::State> our_reference = std::unique_ptr<wm::WindowState::State> our_reference =
window_state->SetStateObject(std::move(old_state_)); window_state->SetStateObject(std::move(old_state_));
......
...@@ -41,7 +41,7 @@ class TabletModeWindowState : public wm::WindowState::State { ...@@ -41,7 +41,7 @@ class TabletModeWindowState : public wm::WindowState::State {
void set_ignore_wm_events(bool ignore) { ignore_wm_events_ = ignore; } void set_ignore_wm_events(bool ignore) { ignore_wm_events_ = ignore; }
// Leaves the tablet mode by reverting to previous state object. // Leaves the tablet mode by reverting to previous state object.
void LeaveTabletMode(wm::WindowState* window_state, bool was_in_overview); void LeaveTabletMode(wm::WindowState* window_state);
// WindowState::State overrides: // WindowState::State overrides:
void OnWMEvent(wm::WindowState* window_state, void OnWMEvent(wm::WindowState* window_state,
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment