ui_lock_controller.cc 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695
  1. // Copyright 2020 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 "components/exo/ui_lock_controller.h"
  5. #include <memory>
  6. #include "ash/bluetooth_devices_observer.h"
  7. #include "ash/constants/app_types.h"
  8. #include "ash/constants/ash_features.h"
  9. #include "ash/public/cpp/keyboard/keyboard_controller.h"
  10. #include "ash/public/cpp/session/session_controller.h"
  11. #include "ash/resources/vector_icons/vector_icons.h"
  12. #include "ash/wm/window_state.h"
  13. #include "ash/wm/window_state_observer.h"
  14. #include "base/bind.h"
  15. #include "base/feature_list.h"
  16. #include "base/scoped_observation.h"
  17. #include "base/time/time.h"
  18. #include "base/timer/timer.h"
  19. #include "chromeos/ui/base/window_properties.h"
  20. #include "components/exo/pointer.h"
  21. #include "components/exo/seat.h"
  22. #include "components/exo/shell_surface_util.h"
  23. #include "components/exo/surface.h"
  24. #include "components/exo/wm_helper.h"
  25. #include "components/exo/wm_helper_chromeos.h"
  26. #include "components/fullscreen_control/fullscreen_control_popup.h"
  27. #include "components/fullscreen_control/subtle_notification_view.h"
  28. #include "components/strings/grit/components_strings.h"
  29. #include "third_party/abseil-cpp/absl/types/optional.h"
  30. #include "ui/aura/client/aura_constants.h"
  31. #include "ui/aura/window.h"
  32. #include "ui/base/l10n/l10n_util.h"
  33. #include "ui/base/user_activity/user_activity_detector.h"
  34. #include "ui/base/user_activity/user_activity_observer.h"
  35. #include "ui/events/devices/device_data_manager.h"
  36. #include "ui/events/event_constants.h"
  37. #include "ui/events/keycodes/dom/dom_code.h"
  38. #include "ui/gfx/paint_vector_icon.h"
  39. #include "ui/strings/grit/ui_strings.h"
  40. #include "ui/views/widget/widget.h"
  41. namespace {
  42. // The Esc hold notification shows a message to press and hold Esc to exit
  43. // fullscreen. It will hide after a 4s timeout but shows again each time the
  44. // window goes to fullscreen.
  45. //
  46. // The exit popup is a circle with an 'X' close icon which exits fullscreen when
  47. // the user clicks it.
  48. // * It is only shown on windows with property kEscHoldToExitFullscreen.
  49. // * It is displayed when the mouse moves to the top 3px of the screen.
  50. // * It will hide after a 3s timeout, or the user moves below 150px.
  51. // * After hiding, there is a cooldown where it will not display again until the
  52. // mouse moves below 150px.
  53. // Duration to show notifications.
  54. constexpr auto kNotificationDuration = base::Seconds(4);
  55. // Position of Esc notification from top of screen.
  56. const int kEscNotificationTopPx = 45;
  57. // Duration to show the exit 'X' popup.
  58. constexpr auto kExitPopupDuration = base::Seconds(3);
  59. // Display the exit popup if mouse is above this height.
  60. constexpr float kExitPopupDisplayHeight = 3.f;
  61. // Hide the exit popup if mouse is below this height.
  62. constexpr float kExitPopupHideHeight = 150.f;
  63. // Once the pointer capture notification has finished showing without
  64. // being interrupted, don't show it again until this long has passed.
  65. constexpr auto kPointerCaptureNotificationCooldown = base::Minutes(5);
  66. constexpr auto kReshowNotificationsWhenIdleFor = base::Minutes(5);
  67. constexpr int kUILockControllerSeatObserverPriority = 1;
  68. static_assert(
  69. exo::Seat::IsValidObserverPriority(kUILockControllerSeatObserverPriority),
  70. "kUILockCOntrollerSeatObserverPriority is not in the valid range");
  71. bool IsUILockControllerEnabled(aura::Window* window) {
  72. if (!window)
  73. return false;
  74. if (window->GetProperty(chromeos::kEscHoldToExitFullscreen) ||
  75. window->GetProperty(chromeos::kUseOverviewToExitFullscreen) ||
  76. window->GetProperty(chromeos::kUseOverviewToExitPointerLock)) {
  77. return true;
  78. }
  79. return false;
  80. }
  81. // Return true if an external keyboard is attached to the device.
  82. //
  83. // Note: May mistakenly return true when certain non-keyboard devices are
  84. // attached; see crbug/882410, crbug/884096.
  85. //
  86. // Copied from ash/keyboard/virtual_keyboard_controller.cc.
  87. // TODO(cpelling): Refactor to avoid duplicating this logic.
  88. bool HasExternalKeyboard() {
  89. ui::DeviceDataManager* device_data_manager =
  90. ui::DeviceDataManager::GetInstance();
  91. ash::BluetoothDevicesObserver bluetooth(base::DoNothing());
  92. for (const ui::InputDevice& device :
  93. device_data_manager->GetKeyboardDevices()) {
  94. ui::InputDeviceType type = device.type;
  95. if (type == ui::InputDeviceType::INPUT_DEVICE_USB ||
  96. (type == ui::InputDeviceType::INPUT_DEVICE_BLUETOOTH &&
  97. bluetooth.IsConnectedBluetoothDevice(device))) {
  98. return true;
  99. }
  100. }
  101. return false;
  102. }
  103. // Creates the separator view between bubble views of modifiers and key.
  104. std::unique_ptr<views::View> CreateIconView(const gfx::VectorIcon& icon) {
  105. constexpr int kIconSize = 28;
  106. std::unique_ptr<views::ImageView> view = std::make_unique<views::ImageView>();
  107. gfx::ImageSkia image = gfx::CreateVectorIcon(icon, SK_ColorWHITE);
  108. view->SetImage(ui::ImageModel::FromImageSkia(image));
  109. view->SetImageSize(gfx::Size(kIconSize, kIconSize));
  110. return view;
  111. }
  112. // Create and position Esc notification.
  113. views::Widget* CreateEscNotification(
  114. aura::Window* parent,
  115. int message_id,
  116. std::initializer_list<int> key_message_ids) {
  117. auto content_view = std::make_unique<SubtleNotificationView>();
  118. std::vector<std::u16string> key_names;
  119. std::vector<std::unique_ptr<views::View>> icons;
  120. for (int key_message_id : key_message_ids) {
  121. key_names.push_back(l10n_util::GetStringUTF16(key_message_id));
  122. if (key_message_id == IDS_APP_OVERVIEW_KEY) {
  123. icons.push_back(CreateIconView(ash::kKsvOverviewIcon));
  124. } else {
  125. icons.push_back(nullptr);
  126. }
  127. }
  128. content_view->UpdateContent(
  129. l10n_util::GetStringFUTF16(message_id, key_names, nullptr),
  130. std::move(icons));
  131. gfx::Size size = content_view->GetPreferredSize();
  132. views::Widget* popup = SubtleNotificationView::CreatePopupWidget(
  133. parent, std::move(content_view));
  134. popup->SetZOrderLevel(ui::ZOrderLevel::kSecuritySurface);
  135. gfx::Rect bounds = parent->GetBoundsInScreen();
  136. int y = bounds.y() + kEscNotificationTopPx;
  137. bounds.ClampToCenteredSize(size);
  138. bounds.set_y(y);
  139. popup->SetBounds(bounds);
  140. return popup;
  141. }
  142. // Exits fullscreen to previous state.
  143. void ExitFullscreen(aura::Window* window) {
  144. ash::WindowState* window_state = ash::WindowState::Get(window);
  145. if (window_state->IsFullscreen())
  146. window_state->Restore();
  147. }
  148. // Owns the widgets for messages prompting to exit fullscreen/mouselock, and
  149. // the exit popup. Owned as a window property.
  150. class ExitNotifier : public ui::EventHandler,
  151. public exo::UILockController::Notifier,
  152. public ash::WindowStateObserver {
  153. public:
  154. explicit ExitNotifier(exo::UILockController* controller, aura::Window* window)
  155. : window_(window) {
  156. controller_observation_.Observe(controller);
  157. ash::WindowState* window_state = ash::WindowState::Get(window);
  158. window_state_observation_.Observe(window_state);
  159. if (window_state->IsFullscreen())
  160. OnFullscreen();
  161. }
  162. ExitNotifier(const ExitNotifier&) = delete;
  163. ExitNotifier& operator=(const ExitNotifier&) = delete;
  164. ~ExitNotifier() override {
  165. OnExitFullscreen();
  166. ClosePointerCaptureNotification();
  167. }
  168. void OnPointerCaptureEnabled() {
  169. pointer_is_captured_ = true;
  170. MaybeShowPointerCaptureNotification();
  171. }
  172. void OnPointerCaptureDisabled() { pointer_is_captured_ = false; }
  173. // If this window is currently in a state that would have triggered a
  174. // notification when entered, re-show that notification as a reminder.
  175. void NotifyAgain() override {
  176. // Always reset the notification cooldown, to ensure notifications show in
  177. // the case where pointer lock is not currently active but will be soon.
  178. next_pointer_notify_time_ = base::TimeTicks::Now();
  179. ash::WindowState* window_state = ash::WindowState::Get(window_);
  180. if (window_state->IsFullscreen()) {
  181. OnFullscreen();
  182. } else if (pointer_is_captured_) {
  183. MaybeShowPointerCaptureNotification();
  184. }
  185. }
  186. void OnUILockControllerDestroying() override {
  187. controller_observation_.Reset();
  188. }
  189. views::Widget* fullscreen_esc_notification() {
  190. return fullscreen_esc_notification_;
  191. }
  192. views::Widget* pointer_capture_notification() {
  193. return pointer_capture_notification_;
  194. }
  195. FullscreenControlPopup* exit_popup() { return exit_popup_.get(); }
  196. private:
  197. void MaybeShowPointerCaptureNotification() {
  198. // Respect cooldown.
  199. if (base::TimeTicks::Now() < next_pointer_notify_time_)
  200. return;
  201. want_pointer_capture_notification_ = true;
  202. // Don't show in fullscreen; the fullscreen notification will show and is
  203. // prioritized.
  204. ash::WindowState* window_state = ash::WindowState::Get(window_);
  205. if (window_state->IsFullscreen())
  206. return;
  207. if (pointer_capture_notification_) {
  208. pointer_capture_notification_->CloseWithReason(
  209. views::Widget::ClosedReason::kUnspecified);
  210. }
  211. if (HasExternalKeyboard()) {
  212. if (ash::KeyboardController::Get()->AreTopRowKeysFunctionKeys()) {
  213. pointer_capture_notification_ =
  214. CreateEscNotification(window_, IDS_PRESS_TO_EXIT_MOUSELOCK_TWO_KEYS,
  215. {IDS_APP_META_KEY, IDS_APP_F5_KEY});
  216. } else {
  217. pointer_capture_notification_ = CreateEscNotification(
  218. window_, IDS_PRESS_TO_EXIT_MOUSELOCK, {IDS_APP_F5_KEY});
  219. }
  220. } else {
  221. if (ash::KeyboardController::Get()->AreTopRowKeysFunctionKeys()) {
  222. pointer_capture_notification_ =
  223. CreateEscNotification(window_, IDS_PRESS_TO_EXIT_MOUSELOCK_TWO_KEYS,
  224. {IDS_APP_SEARCH_KEY, IDS_APP_OVERVIEW_KEY});
  225. } else {
  226. pointer_capture_notification_ = CreateEscNotification(
  227. window_, IDS_PRESS_TO_EXIT_MOUSELOCK, {IDS_APP_OVERVIEW_KEY});
  228. }
  229. }
  230. pointer_capture_notification_->Show();
  231. // Close Esc notification after 4s.
  232. pointer_capture_notify_timer_.Start(
  233. FROM_HERE, kNotificationDuration,
  234. base::BindOnce(&ExitNotifier::OnPointerCaptureNotifyTimerFinished,
  235. base::Unretained(this)));
  236. }
  237. void ClosePointerCaptureNotification() {
  238. pointer_capture_notify_timer_.Stop();
  239. if (pointer_capture_notification_) {
  240. pointer_capture_notification_->CloseWithReason(
  241. views::Widget::ClosedReason::kUnspecified);
  242. pointer_capture_notification_ = nullptr;
  243. }
  244. }
  245. void OnPointerCaptureNotifyTimerFinished() {
  246. // Start the cooldown when the timer successfully elapses, to ensure the
  247. // notification was shown for a sufficiently long time.
  248. next_pointer_notify_time_ =
  249. base::TimeTicks::Now() + kPointerCaptureNotificationCooldown;
  250. ClosePointerCaptureNotification();
  251. want_pointer_capture_notification_ = false;
  252. }
  253. // Overridden from ui::EventHandler:
  254. void OnMouseEvent(ui::MouseEvent* event) override {
  255. gfx::PointF point = event->location_f();
  256. aura::Window::ConvertPointToTarget(
  257. static_cast<aura::Window*>(event->target()), window_, &point);
  258. if (!fullscreen_esc_notification_ && !exit_popup_cooldown_ &&
  259. window_ == exo::WMHelper::GetInstance()->GetActiveWindow() &&
  260. point.y() <= kExitPopupDisplayHeight) {
  261. // Show exit popup if mouse is above 3px, unless esc notification is
  262. // visible, or during cooldown (popup shown and mouse still at top).
  263. if (!exit_popup_) {
  264. exit_popup_ = std::make_unique<FullscreenControlPopup>(
  265. window_, base::BindRepeating(&ExitFullscreen, window_),
  266. base::DoNothing());
  267. }
  268. views::Widget* widget =
  269. views::Widget::GetTopLevelWidgetForNativeView(window_);
  270. exit_popup_->Show(widget->GetClientAreaBoundsInScreen());
  271. exit_popup_timer_.Start(
  272. FROM_HERE, kExitPopupDuration,
  273. base::BindOnce(&ExitNotifier::HideExitPopup, base::Unretained(this),
  274. /*animate=*/true));
  275. exit_popup_cooldown_ = true;
  276. } else if (point.y() > kExitPopupHideHeight) {
  277. // Hide exit popup if mouse is below 150px, reset cooloff.
  278. HideExitPopup(/*animate=*/true);
  279. exit_popup_cooldown_ = false;
  280. }
  281. }
  282. // Overridden from ash::WindowStateObserver:
  283. void OnPostWindowStateTypeChange(
  284. ash::WindowState* window_state,
  285. chromeos::WindowStateType old_type) override {
  286. DCHECK_EQ(window_, window_state->window());
  287. if (window_state->IsFullscreen()) {
  288. OnFullscreen();
  289. } else {
  290. OnExitFullscreen();
  291. }
  292. }
  293. void OnFullscreen() {
  294. // Register ui::EventHandler to watch if mouse goes to top of screen.
  295. if (!is_handling_events_ &&
  296. window_->GetProperty(chromeos::kEscHoldToExitFullscreen)) {
  297. window_->AddPreTargetHandler(this);
  298. is_handling_events_ = true;
  299. }
  300. // Only show Esc notification when window is active.
  301. if (window_ != exo::WMHelper::GetInstance()->GetActiveWindow())
  302. return;
  303. // Fullscreen notifications override pointer capture notifications.
  304. ClosePointerCaptureNotification();
  305. if (fullscreen_esc_notification_) {
  306. fullscreen_esc_notification_->CloseWithReason(
  307. views::Widget::ClosedReason::kUnspecified);
  308. }
  309. if (window_->GetProperty(chromeos::kUseOverviewToExitFullscreen)) {
  310. if (HasExternalKeyboard()) {
  311. if (ash::KeyboardController::Get()->AreTopRowKeysFunctionKeys()) {
  312. fullscreen_esc_notification_ = CreateEscNotification(
  313. window_, IDS_FULLSCREEN_PRESS_TO_EXIT_FULLSCREEN_TWO_KEYS,
  314. {IDS_APP_META_KEY, IDS_APP_F5_KEY});
  315. } else {
  316. fullscreen_esc_notification_ = CreateEscNotification(
  317. window_, IDS_FULLSCREEN_PRESS_TO_EXIT_FULLSCREEN,
  318. {IDS_APP_F5_KEY});
  319. }
  320. } else {
  321. if (ash::KeyboardController::Get()->AreTopRowKeysFunctionKeys()) {
  322. fullscreen_esc_notification_ = CreateEscNotification(
  323. window_, IDS_FULLSCREEN_PRESS_TO_EXIT_FULLSCREEN_TWO_KEYS,
  324. {IDS_APP_SEARCH_KEY, IDS_APP_OVERVIEW_KEY});
  325. } else {
  326. fullscreen_esc_notification_ = CreateEscNotification(
  327. window_, IDS_FULLSCREEN_PRESS_TO_EXIT_FULLSCREEN,
  328. {IDS_APP_OVERVIEW_KEY});
  329. }
  330. }
  331. } else {
  332. fullscreen_esc_notification_ = CreateEscNotification(
  333. window_,
  334. window_->GetProperty(chromeos::kEscHoldToExitFullscreen)
  335. ? IDS_FULLSCREEN_HOLD_TO_EXIT_FULLSCREEN
  336. : IDS_FULLSCREEN_PRESS_TO_EXIT_FULLSCREEN,
  337. {IDS_APP_ESC_KEY});
  338. }
  339. fullscreen_esc_notification_->Show();
  340. // Close Esc notification after 4s.
  341. fullscreen_notify_timer_.Start(
  342. FROM_HERE, kNotificationDuration,
  343. base::BindOnce(&ExitNotifier::CloseFullscreenEscNotification,
  344. base::Unretained(this)));
  345. }
  346. void OnExitFullscreen() {
  347. if (is_handling_events_) {
  348. window_->RemovePreTargetHandler(this);
  349. is_handling_events_ = false;
  350. }
  351. CloseFullscreenEscNotification();
  352. HideExitPopup();
  353. }
  354. void CloseFullscreenEscNotification() {
  355. if (!fullscreen_esc_notification_)
  356. return;
  357. fullscreen_esc_notification_->CloseWithReason(
  358. views::Widget::ClosedReason::kUnspecified);
  359. fullscreen_esc_notification_ = nullptr;
  360. // If a pointer capture notification was previously requested and didn't
  361. // show (or didn't complete its timer), show it now.
  362. //
  363. // This is to prevent the following scenario:
  364. // 1. App goes fullscreen
  365. // 2. App immediately requests pointer capture; no notification is shown,
  366. // since the fullscreen notification is already visible.
  367. // 3. App immediately unfullscreens; the fullscreen notification closes.
  368. //
  369. // Without this check, the app would have gained pointer capture without
  370. // any notification showing.
  371. if (want_pointer_capture_notification_)
  372. MaybeShowPointerCaptureNotification();
  373. }
  374. void HideExitPopup(bool animate = false) {
  375. if (exit_popup_)
  376. exit_popup_->Hide(animate);
  377. }
  378. aura::Window* const window_;
  379. views::Widget* fullscreen_esc_notification_ = nullptr;
  380. views::Widget* pointer_capture_notification_ = nullptr;
  381. bool want_pointer_capture_notification_ = false;
  382. bool pointer_is_captured_ = false;
  383. std::unique_ptr<FullscreenControlPopup> exit_popup_;
  384. bool is_handling_events_ = false;
  385. bool exit_popup_cooldown_ = false;
  386. base::OneShotTimer fullscreen_notify_timer_;
  387. base::OneShotTimer pointer_capture_notify_timer_;
  388. base::TimeTicks next_pointer_notify_time_;
  389. base::OneShotTimer exit_popup_timer_;
  390. base::ScopedObservation<ash::WindowState, ash::WindowStateObserver>
  391. window_state_observation_{this};
  392. base::ScopedObservation<exo::UILockController,
  393. exo::UILockController::Notifier>
  394. controller_observation_{this};
  395. };
  396. } // namespace
  397. DEFINE_UI_CLASS_PROPERTY_TYPE(ExitNotifier*)
  398. namespace exo {
  399. namespace {
  400. DEFINE_OWNED_UI_CLASS_PROPERTY_KEY(ExitNotifier, kExitNotifierKey, nullptr)
  401. ExitNotifier* GetExitNotifier(UILockController* controller,
  402. aura::Window* window,
  403. bool create) {
  404. if (!base::FeatureList::IsEnabled(chromeos::features::kExoLockNotification))
  405. return nullptr;
  406. if (!window)
  407. return nullptr;
  408. aura::Window* toplevel = window->GetToplevelWindow();
  409. if (!IsUILockControllerEnabled(toplevel))
  410. return nullptr;
  411. ExitNotifier* notifier = toplevel->GetProperty(kExitNotifierKey);
  412. if (!notifier && create) {
  413. // Object is owned as a window property.
  414. notifier = toplevel->SetProperty(
  415. kExitNotifierKey, std::make_unique<ExitNotifier>(controller, toplevel));
  416. }
  417. return notifier;
  418. }
  419. } // namespace
  420. constexpr auto kLongPressEscapeDuration = base::Seconds(2);
  421. constexpr auto kExcludedFlags = ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN |
  422. ui::EF_ALT_DOWN | ui::EF_COMMAND_DOWN |
  423. ui::EF_ALTGR_DOWN | ui::EF_IS_REPEAT;
  424. UILockController::UILockController(Seat* seat) : seat_(seat) {
  425. last_activity_time_ = base::TimeTicks::Now();
  426. WMHelper::GetInstance()->AddPreTargetHandler(this);
  427. seat_->AddObserver(this, kUILockControllerSeatObserverPriority);
  428. WMHelper::GetInstance()->AddPowerObserver(this);
  429. auto* session_controller = ash::SessionController::Get();
  430. if (session_controller)
  431. session_controller->AddObserver(this);
  432. ui::UserActivityDetector::Get()->AddObserver(this);
  433. }
  434. UILockController::~UILockController() {
  435. ui::UserActivityDetector::Get()->RemoveObserver(this);
  436. auto* session_controller = ash::SessionController::Get();
  437. if (session_controller)
  438. session_controller->RemoveObserver(this);
  439. WMHelper::GetInstance()->RemovePowerObserver(this);
  440. seat_->RemoveObserver(this);
  441. WMHelper::GetInstance()->RemovePreTargetHandler(this);
  442. for (Notifier& notifier : notifiers_)
  443. notifier.OnUILockControllerDestroying();
  444. }
  445. void UILockController::OnKeyEvent(ui::KeyEvent* event) {
  446. // TODO(oshima): Rather than handling key event here, add a hook in
  447. // keyboard.cc to intercept key event and handle this.
  448. // If no surface is focused, let another handler process the event.
  449. aura::Window* window = static_cast<aura::Window*>(event->target());
  450. if (!GetTargetSurfaceForKeyboardFocus(window))
  451. return;
  452. if (event->code() == ui::DomCode::ESCAPE &&
  453. (event->flags() & kExcludedFlags) == 0) {
  454. OnEscapeKey(event->type() == ui::ET_KEY_PRESSED);
  455. }
  456. }
  457. void UILockController::SuspendDone() {
  458. ReshowAllNotifications();
  459. }
  460. void UILockController::ScreenBrightnessChanged(double percent) {
  461. // Show alert when the device returns from low (epsilon) brightness which
  462. // covers three cases.
  463. // 1. The device returns from sleep.
  464. // 2. The device lid is opened (with sleep on).
  465. // 3. The device returns from low display brightness.
  466. double epsilon = std::numeric_limits<double>::epsilon();
  467. if (percent <= epsilon) {
  468. device_in_dark_ = true;
  469. } else {
  470. if (device_in_dark_)
  471. ReshowAllNotifications();
  472. device_in_dark_ = false;
  473. }
  474. }
  475. void UILockController::LidEventReceived(bool opened) {
  476. // Show alert when the lid is opened. This also covers the case when the user
  477. // turns off "Sleep when cover is closed".
  478. if (opened)
  479. ReshowAllNotifications();
  480. }
  481. void UILockController::OnLockStateChanged(bool locked) {
  482. if (!locked)
  483. ReshowAllNotifications();
  484. }
  485. void UILockController::OnSurfaceFocused(Surface* gained_focus,
  486. Surface* lost_focus,
  487. bool has_focused_surface) {
  488. if (gained_focus != focused_surface_to_unlock_)
  489. StopTimer();
  490. if (gained_focus)
  491. GetExitNotifier(this, gained_focus->window(), true);
  492. }
  493. void UILockController::OnPointerCaptureEnabled(Pointer* pointer,
  494. aura::Window* window) {
  495. aura::Window* toplevel = window ? window->GetToplevelWindow() : nullptr;
  496. if (!toplevel ||
  497. !toplevel->GetProperty(chromeos::kUseOverviewToExitPointerLock))
  498. return;
  499. captured_pointers_.insert(pointer);
  500. ExitNotifier* notifier = GetExitNotifier(this, window, false);
  501. if (notifier)
  502. notifier->OnPointerCaptureEnabled();
  503. }
  504. void UILockController::OnPointerCaptureDisabled(Pointer* pointer,
  505. aura::Window* window) {
  506. if (captured_pointers_.empty())
  507. return;
  508. captured_pointers_.erase(pointer);
  509. if (captured_pointers_.empty()) {
  510. ExitNotifier* notifier = GetExitNotifier(this, window, false);
  511. if (notifier)
  512. notifier->OnPointerCaptureDisabled();
  513. }
  514. }
  515. void UILockController::OnUserActivity(const ui::Event* event) {
  516. base::TimeTicks now = base::TimeTicks::Now();
  517. if (now - last_activity_time_ >= kReshowNotificationsWhenIdleFor) {
  518. ReshowAllNotifications();
  519. }
  520. last_activity_time_ = now;
  521. }
  522. views::Widget* UILockController::GetPointerCaptureNotificationForTesting(
  523. aura::Window* window) {
  524. return window->GetProperty(kExitNotifierKey)->pointer_capture_notification();
  525. }
  526. views::Widget* UILockController::GetEscNotificationForTesting(
  527. aura::Window* window) {
  528. return window->GetProperty(kExitNotifierKey)->fullscreen_esc_notification();
  529. }
  530. FullscreenControlPopup* UILockController::GetExitPopupForTesting(
  531. aura::Window* window) {
  532. return window->GetProperty(kExitNotifierKey)->exit_popup();
  533. }
  534. void UILockController::AddObserver(UILockController::Notifier* notifier) {
  535. notifiers_.AddObserver(notifier);
  536. }
  537. void UILockController::RemoveObserver(UILockController::Notifier* notifier) {
  538. notifiers_.RemoveObserver(notifier);
  539. }
  540. void UILockController::ReshowAllNotifications() {
  541. VLOG(1) << "ReshowAllNotifications";
  542. for (Notifier& notifier : notifiers_)
  543. notifier.NotifyAgain();
  544. }
  545. namespace {
  546. bool EscapeHoldShouldExitFullscreen(Seat* seat) {
  547. auto* surface = seat->GetFocusedSurface();
  548. if (!surface)
  549. return false;
  550. auto* widget =
  551. views::Widget::GetTopLevelWidgetForNativeView(surface->window());
  552. if (!widget)
  553. return false;
  554. aura::Window* window = widget->GetNativeWindow();
  555. if (!window || !window->GetProperty(chromeos::kEscHoldToExitFullscreen)) {
  556. return false;
  557. }
  558. auto* window_state = ash::WindowState::Get(window);
  559. return window_state && window_state->IsFullscreen();
  560. }
  561. } // namespace
  562. void UILockController::OnEscapeKey(bool pressed) {
  563. if (pressed) {
  564. if (EscapeHoldShouldExitFullscreen(seat_) &&
  565. !exit_fullscreen_timer_.IsRunning()) {
  566. focused_surface_to_unlock_ = seat_->GetFocusedSurface();
  567. exit_fullscreen_timer_.Start(
  568. FROM_HERE, kLongPressEscapeDuration,
  569. base::BindOnce(&UILockController::OnEscapeHeld,
  570. base::Unretained(this)));
  571. }
  572. } else {
  573. StopTimer();
  574. }
  575. }
  576. void UILockController::OnEscapeHeld() {
  577. auto* surface = seat_->GetFocusedSurface();
  578. if (!surface || surface != focused_surface_to_unlock_) {
  579. focused_surface_to_unlock_ = nullptr;
  580. return;
  581. }
  582. focused_surface_to_unlock_ = nullptr;
  583. ExitFullscreen(surface->window()->GetToplevelWindow());
  584. }
  585. void UILockController::StopTimer() {
  586. if (exit_fullscreen_timer_.IsRunning()) {
  587. exit_fullscreen_timer_.Stop();
  588. focused_surface_to_unlock_ = nullptr;
  589. }
  590. }
  591. } // namespace exo