window_cycle_controller.cc 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503
  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. #include "ash/wm/window_cycle/window_cycle_controller.h"
  5. #include "ash/accelerators/accelerator_controller_impl.h"
  6. #include "ash/accessibility/accessibility_controller_impl.h"
  7. #include "ash/constants/ash_pref_names.h"
  8. #include "ash/events/event_rewriter_controller_impl.h"
  9. #include "ash/metrics/task_switch_metrics_recorder.h"
  10. #include "ash/metrics/task_switch_source.h"
  11. #include "ash/metrics/user_metrics_recorder.h"
  12. #include "ash/public/cpp/accelerators.h"
  13. #include "ash/public/cpp/shell_window_ids.h"
  14. #include "ash/session/session_controller_impl.h"
  15. #include "ash/shell.h"
  16. #include "ash/strings/grit/ash_strings.h"
  17. #include "ash/wallpaper/wallpaper_controller_impl.h"
  18. #include "ash/wm/desks/desk.h"
  19. #include "ash/wm/desks/desks_controller.h"
  20. #include "ash/wm/desks/desks_util.h"
  21. #include "ash/wm/mru_window_tracker.h"
  22. #include "ash/wm/overview/overview_controller.h"
  23. #include "ash/wm/screen_pinning_controller.h"
  24. #include "ash/wm/window_cycle/window_cycle_event_filter.h"
  25. #include "ash/wm/window_cycle/window_cycle_list.h"
  26. #include "ash/wm/window_state.h"
  27. #include "ash/wm/window_util.h"
  28. #include "base/bind.h"
  29. #include "base/check.h"
  30. #include "base/metrics/histogram_functions.h"
  31. #include "base/metrics/user_metrics.h"
  32. #include "components/prefs/pref_change_registrar.h"
  33. #include "components/prefs/pref_service.h"
  34. #include "ui/base/l10n/l10n_util.h"
  35. namespace ash {
  36. namespace {
  37. constexpr char kAltTabDesksSwitchDistanceHistogramName[] =
  38. "Ash.WindowCycleController.DesksSwitchDistance";
  39. constexpr char kAltTabInitialModeHistogramName[] =
  40. "Ash.WindowCycleController.InitialMode";
  41. constexpr char kAltTabItemsHistogramName[] = "Ash.WindowCycleController.Items";
  42. constexpr char kAltTabSwitchModeHistogramName[] =
  43. "Ash.WindowCycleController.SwitchMode";
  44. constexpr char kAltTabModeSwitchSourceHistogramName[] =
  45. "Ash.WindowCycleController.ModeSwitchSource";
  46. // Enumeration of the alt-tab modes to record initial mode and mode switch.
  47. // Note that these values are persisted to histograms so existing values should
  48. // remain unchanged and new values should be added to the end.
  49. enum class AltTabMode {
  50. // The window list includes all windows from all desks.
  51. kAllDesks,
  52. // The window list only includes windows from the active desk.
  53. kCurrentDesk,
  54. kMaxValue = kCurrentDesk,
  55. };
  56. // Returns the most recently active window from the |window_list| or nullptr
  57. // if the list is empty.
  58. aura::Window* GetActiveWindow(
  59. const WindowCycleController::WindowList& window_list) {
  60. return window_list.empty() ? nullptr : window_list[0];
  61. }
  62. void ReportPossibleDesksSwitchStats(int active_desk_container_id_before_cycle) {
  63. // Report only for users who have 2 or more desks, since we're only interested
  64. // in seeing how users of Virtual Desks use window cycling.
  65. auto* desks_controller = DesksController::Get();
  66. if (!desks_controller)
  67. return;
  68. if (desks_controller->desks().size() < 2)
  69. return;
  70. // Note that this functions is called while a potential desk switch animation
  71. // is starting, in this case we want the target active desk (i.e. the soon-to-
  72. // be active desk after the animation finishes).
  73. const int active_desk_container_id_after_cycle =
  74. desks_controller->GetTargetActiveDesk()->container_id();
  75. DCHECK_NE(active_desk_container_id_before_cycle, kShellWindowId_Invalid);
  76. DCHECK_NE(active_desk_container_id_after_cycle, kShellWindowId_Invalid);
  77. // Note that the desks containers IDs are consecutive. See
  78. // |ash::ShellWindowId|.
  79. const int desks_switch_distance =
  80. std::abs(active_desk_container_id_after_cycle -
  81. active_desk_container_id_before_cycle);
  82. base::UmaHistogramExactLinear(kAltTabDesksSwitchDistanceHistogramName,
  83. desks_switch_distance,
  84. desks_util::kMaxNumberOfDesks);
  85. }
  86. } // namespace
  87. //////////////////////////////////////////////////////////////////////////////
  88. // WindowCycleController, public:
  89. WindowCycleController::WindowCycleController() {
  90. Shell::Get()->session_controller()->AddObserver(this);
  91. }
  92. WindowCycleController::~WindowCycleController() {
  93. Shell::Get()->session_controller()->RemoveObserver(this);
  94. }
  95. // static
  96. bool WindowCycleController::CanCycle() {
  97. return !Shell::Get()->session_controller()->IsScreenLocked() &&
  98. !Shell::IsSystemModalWindowOpen() &&
  99. !Shell::Get()->screen_pinning_controller()->IsPinned() &&
  100. !window_util::IsAnyWindowDragged() &&
  101. !Shell::Get()->desks_controller()->AreDesksBeingModified();
  102. }
  103. // static
  104. void WindowCycleController::RegisterProfilePrefs(PrefRegistrySimple* registry) {
  105. registry->RegisterBooleanPref(prefs::kAltTabPerDesk, DesksMruType::kAllDesks);
  106. }
  107. void WindowCycleController::HandleCycleWindow(
  108. WindowCyclingDirection direction) {
  109. if (!CanCycle())
  110. return;
  111. const bool should_start_alt_tab = !IsCycling();
  112. if (should_start_alt_tab)
  113. StartCycling();
  114. Step(direction, /*starting_alt_tab_or_switching_mode=*/should_start_alt_tab);
  115. }
  116. void WindowCycleController::HandleKeyboardNavigation(
  117. KeyboardNavDirection direction) {
  118. // If the UI is not shown yet, discard the event.
  119. if (!CanCycle() || !IsCycling() || !window_cycle_list_->cycle_view() ||
  120. !IsValidKeyboardNavigation(direction)) {
  121. return;
  122. }
  123. switch (direction) {
  124. // Pressing the Up arrow key moves the focus from the window cycle list
  125. // to the tab slider button.
  126. case KeyboardNavDirection::kUp:
  127. DCHECK(!IsTabSliderFocused() && IsInteractiveAltTabModeAllowed());
  128. window_cycle_list_->SetFocusTabSlider(true);
  129. // Focusing the alt-tab mode button announces the current mode.
  130. Shell::Get()
  131. ->accessibility_controller()
  132. ->TriggerAccessibilityAlertWithMessage(l10n_util::GetStringUTF8(
  133. IsAltTabPerActiveDesk()
  134. ? IDS_ASH_ALT_TAB_CURRENT_DESK_MODE_SELECTED_TITLE
  135. : IDS_ASH_ALT_TAB_ALL_DESKS_MODE_SELECTED_TITLE));
  136. break;
  137. // Pressing the Down arrow key does the opposite of the Up arrow key.
  138. case KeyboardNavDirection::kDown: {
  139. DCHECK(IsTabSliderFocused());
  140. window_cycle_list_->SetFocusTabSlider(false);
  141. aura::Window* target_window = window_cycle_list_->GetTargetWindow();
  142. // Cannot press the Down arrow key if there is no window.
  143. DCHECK(target_window);
  144. // Announce the selected window in the window cycle list.
  145. Shell::Get()
  146. ->accessibility_controller()
  147. ->TriggerAccessibilityAlertWithMessage(
  148. l10n_util::GetStringFUTF8(IDS_ASH_ALT_TAB_WINDOW_SELECTED_TITLE,
  149. target_window->GetTitle()));
  150. break;
  151. }
  152. // Pressing the Left or Right arrow keys cycles through the window list
  153. // or switches alt-tab mode depending on which component is focused.
  154. case KeyboardNavDirection::kRight:
  155. case KeyboardNavDirection::kLeft:
  156. if (!IsTabSliderFocused()) {
  157. // Cycling through the window list if focusing the window.
  158. HandleCycleWindow(direction == KeyboardNavDirection::kRight
  159. ? WindowCyclingDirection::kForward
  160. : WindowCyclingDirection::kBackward);
  161. } else {
  162. // Switch the mode if focusing the button. Navigating right triggers
  163. // the right button corresponding to the active desk mode. On the other
  164. // hand, navigating left enables the all-desk mode.
  165. OnModeChanged(direction == KeyboardNavDirection::kRight,
  166. ModeSwitchSource::kKeyboard);
  167. }
  168. break;
  169. case KeyboardNavDirection::kInvalid:
  170. default:
  171. NOTREACHED();
  172. break;
  173. }
  174. }
  175. void WindowCycleController::Drag(float delta_x) {
  176. DCHECK(window_cycle_list_);
  177. window_cycle_list_->Drag(delta_x);
  178. }
  179. void WindowCycleController::StartFling(float velocity_x) {
  180. DCHECK(window_cycle_list_);
  181. window_cycle_list_->StartFling(velocity_x);
  182. }
  183. void WindowCycleController::StartCycling() {
  184. Shell* shell = Shell::Get();
  185. // Close the wallpaper preview if it is open to prevent visual glitches where
  186. // the window view item for the preview is transparent
  187. // (http://crbug.com/895265).
  188. shell->wallpaper_controller()->MaybeClosePreviewWallpaper();
  189. shell->event_rewriter_controller()->SetAltDownRemappingEnabled(false);
  190. // End overview as the window cycle list takes over window switching.
  191. shell->overview_controller()->EndOverview(
  192. OverviewEndAction::kStartedWindowCycle);
  193. WindowCycleController::WindowList window_list = CreateWindowList();
  194. SaveCurrentActiveDeskAndWindow(window_list);
  195. window_cycle_list_ = std::make_unique<WindowCycleList>(window_list);
  196. event_filter_ = std::make_unique<WindowCycleEventFilter>();
  197. base::RecordAction(base::UserMetricsAction("WindowCycleController_Cycle"));
  198. base::UmaHistogramCounts100(kAltTabItemsHistogramName, window_list.size());
  199. if (IsInteractiveAltTabModeAllowed()) {
  200. // When alt-tab interactive mode is available, report the initial alt-tab
  201. // mode which indicates the user's preferred mode.
  202. base::UmaHistogramEnumeration(kAltTabInitialModeHistogramName,
  203. IsAltTabPerActiveDesk()
  204. ? AltTabMode::kCurrentDesk
  205. : AltTabMode::kAllDesks);
  206. }
  207. desks_observation_.Observe(DesksController::Get());
  208. }
  209. void WindowCycleController::CompleteCycling() {
  210. DCHECK(window_cycle_list_);
  211. window_cycle_list_->set_user_did_accept(true);
  212. StopCycling();
  213. }
  214. void WindowCycleController::CancelCycling() {
  215. StopCycling();
  216. }
  217. void WindowCycleController::MaybeResetCycleList() {
  218. if (!IsCycling())
  219. return;
  220. WindowCycleController::WindowList window_list = CreateWindowList();
  221. SaveCurrentActiveDeskAndWindow(window_list);
  222. DCHECK(window_cycle_list_);
  223. window_cycle_list_->ReplaceWindows(window_list);
  224. }
  225. void WindowCycleController::SetFocusedWindow(aura::Window* window) {
  226. if (!IsCycling())
  227. return;
  228. DCHECK(window_cycle_list_);
  229. window_cycle_list_->SetFocusedWindow(window);
  230. }
  231. bool WindowCycleController::IsEventInCycleView(const ui::LocatedEvent* event) {
  232. return window_cycle_list_ && window_cycle_list_->IsEventInCycleView(event);
  233. }
  234. aura::Window* WindowCycleController::GetWindowAtPoint(
  235. const ui::LocatedEvent* event) {
  236. return window_cycle_list_ ? window_cycle_list_->GetWindowAtPoint(event)
  237. : nullptr;
  238. }
  239. bool WindowCycleController::IsEventInTabSliderContainer(
  240. const ui::LocatedEvent* event) {
  241. return window_cycle_list_ &&
  242. window_cycle_list_->IsEventInTabSliderContainer(event);
  243. }
  244. bool WindowCycleController::IsWindowListVisible() {
  245. return window_cycle_list_ && window_cycle_list_->ShouldShowUi();
  246. }
  247. bool WindowCycleController::IsInteractiveAltTabModeAllowed() {
  248. return Shell::Get()->desks_controller()->GetNumberOfDesks() > 1;
  249. }
  250. bool WindowCycleController::IsAltTabPerActiveDesk() {
  251. return IsInteractiveAltTabModeAllowed() && active_user_pref_service_ &&
  252. active_user_pref_service_->GetBoolean(prefs::kAltTabPerDesk);
  253. }
  254. bool WindowCycleController::IsSwitchingMode() {
  255. return IsInteractiveAltTabModeAllowed() && is_switching_mode_;
  256. }
  257. bool WindowCycleController::IsTabSliderFocused() {
  258. return IsInteractiveAltTabModeAllowed() &&
  259. window_cycle_list_->IsTabSliderFocused();
  260. }
  261. void WindowCycleController::OnActiveUserPrefServiceChanged(
  262. PrefService* pref_service) {
  263. active_user_pref_service_ = pref_service;
  264. InitFromUserPrefs();
  265. }
  266. void WindowCycleController::OnModeChanged(bool per_desk,
  267. ModeSwitchSource source) {
  268. DCHECK(IsInteractiveAltTabModeAllowed() && IsCycling());
  269. // Save to the active user prefs.
  270. auto* prefs = Shell::Get()->session_controller()->GetActivePrefService();
  271. if (!prefs) {
  272. // Can be null in tests.
  273. return;
  274. }
  275. // Avoid an unnecessary update if any.
  276. if (per_desk == prefs->GetBoolean(prefs::kAltTabPerDesk))
  277. return;
  278. prefs->SetBoolean(prefs::kAltTabPerDesk, per_desk);
  279. // Report the alt-tab mode the user switches to and the source of switch.
  280. base::UmaHistogramEnumeration(
  281. kAltTabSwitchModeHistogramName,
  282. per_desk ? AltTabMode::kCurrentDesk : AltTabMode::kAllDesks);
  283. base::UmaHistogramEnumeration(kAltTabModeSwitchSourceHistogramName, source);
  284. // Announce the new mode and the updated window selection via ChromeVox.
  285. aura::Window* target_window = window_cycle_list_->GetTargetWindow();
  286. const std::string mode_switched_string = l10n_util::GetStringUTF8(
  287. per_desk ? IDS_ASH_ALT_TAB_CURRENT_DESK_MODE_SELECTED_TITLE
  288. : IDS_ASH_ALT_TAB_ALL_DESKS_MODE_SELECTED_TITLE);
  289. // A ChromeVox string announcing the selected window in the window cycle list
  290. // or no recent items if there's no window in the list.
  291. const std::string window_selected_string =
  292. target_window
  293. ? l10n_util::GetStringFUTF8(IDS_ASH_ALT_TAB_WINDOW_SELECTED_TITLE,
  294. target_window->GetTitle())
  295. : l10n_util::GetStringUTF8(IDS_ASH_OVERVIEW_NO_RECENT_ITEMS);
  296. switch (source) {
  297. case ModeSwitchSource::kClick:
  298. Shell::Get()
  299. ->accessibility_controller()
  300. ->TriggerAccessibilityAlertWithMessage(base::JoinString(
  301. {mode_switched_string, window_selected_string}, " "));
  302. // If the user clicks the mode button, remove the focus from it.
  303. window_cycle_list_->SetFocusTabSlider(false);
  304. break;
  305. case ModeSwitchSource::kKeyboard:
  306. // Additionally, during keyboard navigation, notify that the user can
  307. // press the Down arrow key to navigate among the cycle windows if the
  308. // list is not empty.
  309. Shell::Get()
  310. ->accessibility_controller()
  311. ->TriggerAccessibilityAlertWithMessage(base::JoinString(
  312. {mode_switched_string, window_selected_string,
  313. target_window ? l10n_util::GetStringUTF8(
  314. IDS_ASH_ALT_TAB_FOCUS_WINDOW_LIST_TITLE)
  315. : std::string()},
  316. " "));
  317. break;
  318. default:
  319. NOTREACHED();
  320. }
  321. }
  322. void WindowCycleController::OnDeskAdded(const Desk* desk) {
  323. CancelCycling();
  324. }
  325. void WindowCycleController::OnDeskRemoved(const Desk* desk) {
  326. CancelCycling();
  327. }
  328. //////////////////////////////////////////////////////////////////////////////
  329. // WindowCycleController, private:
  330. WindowCycleController::WindowList WindowCycleController::CreateWindowList() {
  331. WindowCycleController::WindowList window_list =
  332. Shell::Get()->mru_window_tracker()->BuildWindowForCycleWithPipList(
  333. IsAltTabPerActiveDesk() ? kActiveDesk : kAllDesks);
  334. // Window cycle list windows will handle showing their transient related
  335. // windows, so if a window in |window_list| has a transient root also in
  336. // |window_list|, we can remove it as the transient root will handle showing
  337. // the window.
  338. window_util::EnsureTransientRoots(&window_list);
  339. return window_list;
  340. }
  341. void WindowCycleController::SaveCurrentActiveDeskAndWindow(
  342. const WindowCycleController::WindowList& window_list) {
  343. active_desk_container_id_before_cycle_ =
  344. desks_util::GetActiveDeskContainerId();
  345. active_window_before_window_cycle_ = GetActiveWindow(window_list);
  346. }
  347. void WindowCycleController::Step(WindowCyclingDirection direction,
  348. bool starting_alt_tab_or_switching_mode) {
  349. DCHECK(window_cycle_list_);
  350. window_cycle_list_->Step(direction, starting_alt_tab_or_switching_mode);
  351. }
  352. void WindowCycleController::StopCycling() {
  353. // There's an edge case where `StopCycling()` is already triggered via an alt
  354. // release event, but user doesn't release the tap on the
  355. // `window_cycle_list_`. If we reset `window_cycle_list_` first,
  356. // `WindowEventDispatcher::DispatchSyntheticTouchEvent` will be triggered
  357. // because of the availability changed for the `window_cycle_list_`. Thus
  358. // `event_filter_` will still receive the event and try to handle the event
  359. // even though it's in the process of stopping cycling. To avoid this, we
  360. // should remove our event filter first. Please check
  361. // https://crbug.com/1228381 for more details.
  362. event_filter_.reset();
  363. desks_observation_.Reset();
  364. window_cycle_list_.reset();
  365. // We can't use the MRU window list here to get the active window, since
  366. // cycling can activate a window on a different desk, leading to a desk-switch
  367. // animation launching. Getting the MRU window list for the active desk now
  368. // will always be for the current active desk, not the target active desk.
  369. aura::Window* active_window_after_window_cycle =
  370. window_util::GetActiveWindow();
  371. if (active_window_after_window_cycle != nullptr &&
  372. active_window_before_window_cycle_ != active_window_after_window_cycle) {
  373. Shell::Get()->metrics()->task_switch_metrics_recorder().OnTaskSwitch(
  374. TaskSwitchSource::WINDOW_CYCLE_CONTROLLER);
  375. ReportPossibleDesksSwitchStats(active_desk_container_id_before_cycle_);
  376. }
  377. active_window_before_window_cycle_ = nullptr;
  378. active_desk_container_id_before_cycle_ = kShellWindowId_Invalid;
  379. Shell::Get()->event_rewriter_controller()->SetAltDownRemappingEnabled(true);
  380. }
  381. void WindowCycleController::InitFromUserPrefs() {
  382. DCHECK(active_user_pref_service_);
  383. pref_change_registrar_ = std::make_unique<PrefChangeRegistrar>();
  384. pref_change_registrar_->Init(active_user_pref_service_);
  385. pref_change_registrar_->Add(
  386. prefs::kAltTabPerDesk,
  387. base::BindRepeating(&WindowCycleController::OnAltTabModePrefChanged,
  388. base::Unretained(this)));
  389. OnAltTabModePrefChanged();
  390. }
  391. void WindowCycleController::OnAltTabModePrefChanged() {
  392. // Only update UI for alt-tab mode if the user is using alt-tab with the
  393. // interactive alt-tab mode supported.
  394. if (!IsInteractiveAltTabModeAllowed() || !IsCycling())
  395. return;
  396. is_switching_mode_ = true;
  397. // Update the window cycle list.
  398. MaybeResetCycleList();
  399. // After the cycle is reset, imitate the same forward cycling behavior as
  400. // starting alt-tab with `Step()`, which makes sure the correct window is
  401. // selected and highlighted.
  402. Step(WindowCyclingDirection::kForward,
  403. /*starting_alt_tab_or_switching_mode=*/true);
  404. // Update tab slider button UI.
  405. window_cycle_list_->OnModePrefsChanged();
  406. is_switching_mode_ = false;
  407. }
  408. bool WindowCycleController::IsValidKeyboardNavigation(
  409. KeyboardNavDirection direction) {
  410. // Only allow Left and Right arrow keys if interactive alt-tab mode is not
  411. // in use.
  412. if (!IsInteractiveAltTabModeAllowed()) {
  413. return direction == KeyboardNavDirection::kLeft ||
  414. direction == KeyboardNavDirection::kRight;
  415. }
  416. // If the focus is on the window cycle list, the user can navigate up to
  417. // focus the mode buttons, or left and right to change the window selection.
  418. if (!IsTabSliderFocused())
  419. return direction != KeyboardNavDirection::kDown;
  420. // If the focus is on the tab slider button, the user can navigate down to
  421. // focus the non-empty list, determined by non-null target window. The user
  422. // can only navigate left while focusing the right button and vice versa.
  423. const bool per_desk = IsAltTabPerActiveDesk();
  424. return (direction == KeyboardNavDirection::kDown &&
  425. window_cycle_list_->GetTargetWindow()) ||
  426. (per_desk && direction == KeyboardNavDirection::kLeft) ||
  427. (!per_desk && direction == KeyboardNavDirection::kRight);
  428. }
  429. } // namespace ash