CrOS Game Dashboard: initial commit
Initial commit of controller and stubbed out session. The controller is only instantiated if kGameDashboard feature flag is enabled and on a test image. The latter requirement will be relaxed once the feature is further into development. Technical design doc: go/cros-game-dashboard-mvp-tdd Bug: http://b/270150206 Test: ash_unittests --gtest_filter=GameDashboard* Change-Id: Ieb82274b7a8351cf054d378a21d8105e104383c3 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4178993 Commit-Queue: Brad McKee <bradmckee@google.com> Reviewed-by: Ahmed Fakhry <afakhry@chromium.org> Cr-Commit-Position: refs/heads/main@{#1115211}
This commit is contained in:

committed by
Chromium LUCI CQ

parent
3bb6447614
commit
edde24d0e7
@ -573,6 +573,10 @@ component("ash") {
|
||||
"frame_throttler/frame_throttling_controller.cc",
|
||||
"frame_throttler/frame_throttling_controller.h",
|
||||
"frame_throttler/frame_throttling_observer.h",
|
||||
"game_dashboard/game_dashboard_controller.cc",
|
||||
"game_dashboard/game_dashboard_controller.h",
|
||||
"game_dashboard/game_dashboard_session.cc",
|
||||
"game_dashboard/game_dashboard_session.h",
|
||||
"glanceables/glanceables_controller.cc",
|
||||
"glanceables/glanceables_controller.h",
|
||||
"glanceables/glanceables_delegate.h",
|
||||
@ -2935,6 +2939,7 @@ test("ash_unittests") {
|
||||
"frame_sink/frame_sink_holder_unittest.cc",
|
||||
"frame_sink/ui_resource_manager_unittest.cc",
|
||||
"frame_throttler/frame_throttling_controller_unittest.cc",
|
||||
"game_dashboard/game_dashboard_unittest.cc",
|
||||
"glanceables/glanceables_unittests.cc",
|
||||
"glanceables/glanceables_welcome_label_unittest.cc",
|
||||
"glanceables/signout_screenshot_handler_unittest.cc",
|
||||
|
@ -22,6 +22,7 @@
|
||||
#include "ash/display/screen_orientation_controller.h"
|
||||
#include "ash/focus_cycler.h"
|
||||
#include "ash/frame/non_client_frame_view_ash.h"
|
||||
#include "ash/game_dashboard/game_dashboard_controller.h"
|
||||
#include "ash/ime/ime_controller_impl.h"
|
||||
#include "ash/keyboard/keyboard_controller_impl.h"
|
||||
#include "ash/media/media_controller_impl.h"
|
||||
@ -508,7 +509,7 @@ bool CanToggleGameDashboard() {
|
||||
return false;
|
||||
}
|
||||
aura::Window* window = window_util::GetActiveWindow();
|
||||
return window && IsArcWindow(window);
|
||||
return window && GameDashboardController::CanStart(window);
|
||||
}
|
||||
|
||||
bool CanToggleMultitaskMenu() {
|
||||
@ -1316,7 +1317,12 @@ void ToggleGameDashboard() {
|
||||
DCHECK(features::IsGameDashboardEnabled());
|
||||
aura::Window* window = window_util::GetActiveWindow();
|
||||
DCHECK(window);
|
||||
// TODO(phshah): Connect to the game dashboard controller.
|
||||
auto* controller = Shell::Get()->game_dashboard_controller();
|
||||
if (!controller->IsActive(window)) {
|
||||
controller->Start(window);
|
||||
} else {
|
||||
controller->ToggleMenu(window);
|
||||
}
|
||||
}
|
||||
|
||||
void ToggleHighContrast() {
|
||||
|
116
ash/game_dashboard/game_dashboard_controller.cc
Normal file
116
ash/game_dashboard/game_dashboard_controller.cc
Normal file
@ -0,0 +1,116 @@
|
||||
// Copyright 2023 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "ash/game_dashboard/game_dashboard_controller.h"
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "ash/game_dashboard/game_dashboard_session.h"
|
||||
#include "ash/public/cpp/app_types_util.h"
|
||||
#include "ash/session/session_controller_impl.h"
|
||||
#include "ash/shell.h"
|
||||
#include "base/logging.h"
|
||||
|
||||
namespace ash {
|
||||
|
||||
namespace {
|
||||
|
||||
// Gets top level window of the provided window if the top level window is not
|
||||
// null. Otherwise return the window.
|
||||
aura::Window* GetTopLevelWindow(aura::Window* window) {
|
||||
return window ? window->GetToplevelWindow() : nullptr;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
GameDashboardController::GameDashboardController() {
|
||||
Shell::Get()->session_controller()->AddObserver(this);
|
||||
}
|
||||
|
||||
GameDashboardController::~GameDashboardController() {
|
||||
ShutdownAllSessions();
|
||||
Shell::Get()->session_controller()->RemoveObserver(this);
|
||||
}
|
||||
|
||||
// static
|
||||
bool GameDashboardController::CanStart(aura::Window* window) {
|
||||
return IsArcWindow(window);
|
||||
}
|
||||
|
||||
bool GameDashboardController::IsActive(aura::Window* window) const {
|
||||
auto it = sessions_.find(window);
|
||||
return it != sessions_.end() && !it->second->is_shutting_down();
|
||||
}
|
||||
|
||||
bool GameDashboardController::Start(aura::Window* window) {
|
||||
window = GetTopLevelWindow(window);
|
||||
if (!window) {
|
||||
VLOG(1) << "Ignoring attempt to start game dashboard with a null window";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!CanStart(window)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto& session = sessions_[window];
|
||||
if (session) {
|
||||
// Already exists.
|
||||
return false;
|
||||
}
|
||||
|
||||
session = std::make_unique<GameDashboardSession>(window);
|
||||
session->Initialize();
|
||||
window_observations_.AddObservation(window);
|
||||
return true;
|
||||
}
|
||||
|
||||
void GameDashboardController::Stop(aura::Window* window) {
|
||||
auto it_session = sessions_.find(window);
|
||||
if (it_session != sessions_.end()) {
|
||||
window_observations_.RemoveObservation(window);
|
||||
it_session->second->Shutdown();
|
||||
sessions_.erase(it_session);
|
||||
}
|
||||
}
|
||||
|
||||
void GameDashboardController::ToggleMenu(aura::Window* window) {
|
||||
auto it_session = sessions_.find(GetTopLevelWindow(window));
|
||||
if (it_session != sessions_.end()) {
|
||||
it_session->second->ToggleMenu();
|
||||
}
|
||||
}
|
||||
|
||||
void GameDashboardController::OnActiveUserSessionChanged(
|
||||
const AccountId& account_id) {
|
||||
ShutdownAllSessions();
|
||||
}
|
||||
|
||||
void GameDashboardController::OnSessionStateChanged(
|
||||
session_manager::SessionState state) {
|
||||
if (Shell::Get()->session_controller()->IsUserSessionBlocked()) {
|
||||
ShutdownAllSessions();
|
||||
}
|
||||
}
|
||||
|
||||
void GameDashboardController::OnChromeTerminating() {
|
||||
ShutdownAllSessions();
|
||||
}
|
||||
|
||||
void GameDashboardController::OnWindowDestroying(aura::Window* window) {
|
||||
Stop(window);
|
||||
}
|
||||
|
||||
void GameDashboardController::ShutdownAllSessions() {
|
||||
window_observations_.RemoveAllObservations();
|
||||
for (auto& it_session : sessions_) {
|
||||
if (!it_session.second->is_shutting_down()) {
|
||||
it_session.second->Shutdown();
|
||||
}
|
||||
}
|
||||
sessions_.clear();
|
||||
}
|
||||
|
||||
} // namespace ash
|
75
ash/game_dashboard/game_dashboard_controller.h
Normal file
75
ash/game_dashboard/game_dashboard_controller.h
Normal file
@ -0,0 +1,75 @@
|
||||
// Copyright 2023 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef ASH_GAME_DASHBOARD_GAME_DASHBOARD_CONTROLLER_H_
|
||||
#define ASH_GAME_DASHBOARD_GAME_DASHBOARD_CONTROLLER_H_
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "ash/ash_export.h"
|
||||
#include "ash/public/cpp/session/session_observer.h"
|
||||
#include "base/containers/flat_map.h"
|
||||
#include "base/scoped_multi_source_observation.h"
|
||||
#include "ui/aura/window_observer.h"
|
||||
|
||||
namespace ash {
|
||||
|
||||
class GameDashboardSession;
|
||||
|
||||
// The game dashboard controller is responsible for creating and managing game
|
||||
// dashboard sessions. It provides a means to start a session on the focused
|
||||
// window, if that window is a relevant game surface. The session can also be
|
||||
// stopped.
|
||||
class ASH_EXPORT GameDashboardController : public SessionObserver,
|
||||
public aura::WindowObserver {
|
||||
public:
|
||||
GameDashboardController();
|
||||
GameDashboardController(const GameDashboardController&) = delete;
|
||||
GameDashboardController& operator=(const GameDashboardController&) = delete;
|
||||
~GameDashboardController() override;
|
||||
|
||||
// Returns true if this window supports starting the game dashboard. Otherwise
|
||||
// returns false.
|
||||
static bool CanStart(aura::Window* window);
|
||||
|
||||
// Returns true if there is an active game dashboard session associated
|
||||
// with the given window. Otherwise returns false.
|
||||
bool IsActive(aura::Window* window) const;
|
||||
|
||||
// If there is not an active game dashboard session for the given window,
|
||||
// starts one and returns true. If there is already an active session, this
|
||||
// method returns false.
|
||||
bool Start(aura::Window* window);
|
||||
|
||||
// If there is an active game dashboard session for the given window, stops
|
||||
// that session. Otherwise does nothing.
|
||||
void Stop(aura::Window* window);
|
||||
|
||||
// If there is an active game dashboard session for the given window, toggles
|
||||
// the associated game dashboard menu. Otherwise does nothing.
|
||||
void ToggleMenu(aura::Window* window);
|
||||
|
||||
// SessionObserver:
|
||||
void OnActiveUserSessionChanged(const AccountId& account_id) override;
|
||||
void OnSessionStateChanged(session_manager::SessionState state) override;
|
||||
void OnChromeTerminating() override;
|
||||
|
||||
// aura::WindowObserver:
|
||||
void OnWindowDestroying(aura::Window* window) override;
|
||||
|
||||
private:
|
||||
// Calls Shutdown on all active sessions and clears the session map. Generally
|
||||
// this is done on logged in user change or Chrome terminating.
|
||||
void ShutdownAllSessions();
|
||||
|
||||
base::flat_map<aura::Window*, std::unique_ptr<GameDashboardSession>>
|
||||
sessions_;
|
||||
|
||||
base::ScopedMultiSourceObservation<aura::Window, aura::WindowObserver>
|
||||
window_observations_{this};
|
||||
};
|
||||
|
||||
} // namespace ash
|
||||
|
||||
#endif // ASH_GAME_DASHBOARD_GAME_DASHBOARD_CONTROLLER_H_
|
30
ash/game_dashboard/game_dashboard_session.cc
Normal file
30
ash/game_dashboard/game_dashboard_session.cc
Normal file
@ -0,0 +1,30 @@
|
||||
// Copyright 2023 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "ash/game_dashboard/game_dashboard_session.h"
|
||||
|
||||
#include "base/check.h"
|
||||
|
||||
namespace ash {
|
||||
|
||||
GameDashboardSession::GameDashboardSession(aura::Window* window)
|
||||
: window_(window) {
|
||||
DCHECK(window);
|
||||
}
|
||||
|
||||
GameDashboardSession::~GameDashboardSession() = default;
|
||||
|
||||
void GameDashboardSession::Initialize() {
|
||||
// Not yet implemented
|
||||
}
|
||||
|
||||
void GameDashboardSession::Shutdown() {
|
||||
is_shutting_down_ = true;
|
||||
}
|
||||
|
||||
void GameDashboardSession::ToggleMenu() {
|
||||
// Not yet implemented
|
||||
}
|
||||
|
||||
} // namespace ash
|
49
ash/game_dashboard/game_dashboard_session.h
Normal file
49
ash/game_dashboard/game_dashboard_session.h
Normal file
@ -0,0 +1,49 @@
|
||||
// Copyright 2023 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef ASH_GAME_DASHBOARD_GAME_DASHBOARD_SESSION_H_
|
||||
#define ASH_GAME_DASHBOARD_GAME_DASHBOARD_SESSION_H_
|
||||
|
||||
namespace aura {
|
||||
class Window;
|
||||
}
|
||||
|
||||
namespace ash {
|
||||
|
||||
// A game dashboard session is created for an app that raised the
|
||||
// game dashboard UI. It should be initialized with the top level
|
||||
// window of the app in which the dashboard was raised. The session
|
||||
// may continue even when all visual elements are closed if there is
|
||||
// active screen recording in progress.
|
||||
class GameDashboardSession {
|
||||
public:
|
||||
explicit GameDashboardSession(aura::Window* window);
|
||||
GameDashboardSession(const GameDashboardSession&) = delete;
|
||||
GameDashboardSession& operator=(const GameDashboardSession&) = delete;
|
||||
~GameDashboardSession();
|
||||
|
||||
bool is_shutting_down() const { return is_shutting_down_; }
|
||||
aura::Window* window() const { return window_; }
|
||||
|
||||
// Creates all UI elements that might be needed for this session.
|
||||
void Initialize();
|
||||
|
||||
// `Shutdown()` should be called just before the session destructor is called.
|
||||
void Shutdown();
|
||||
|
||||
// Toggles the main menu.
|
||||
void ToggleMenu();
|
||||
|
||||
private:
|
||||
// The top window associated with this session.
|
||||
aura::Window* const window_;
|
||||
|
||||
// Once the `Shutdown()` method has been called on this session, this value
|
||||
// will be true.
|
||||
bool is_shutting_down_ = false;
|
||||
};
|
||||
|
||||
} // namespace ash
|
||||
|
||||
#endif // ASH_GAME_DASHBOARD_GAME_DASHBOARD_SESSION_H_
|
93
ash/game_dashboard/game_dashboard_unittest.cc
Normal file
93
ash/game_dashboard/game_dashboard_unittest.cc
Normal file
@ -0,0 +1,93 @@
|
||||
// Copyright 2023 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "ash/constants/app_types.h"
|
||||
#include "ash/constants/ash_features.h"
|
||||
#include "ash/game_dashboard/game_dashboard_controller.h"
|
||||
#include "ash/shell.h"
|
||||
#include "ash/test/ash_test_base.h"
|
||||
#include "base/system/sys_info.h"
|
||||
#include "base/test/scoped_feature_list.h"
|
||||
#include "ui/aura/client/aura_constants.h"
|
||||
|
||||
namespace ash {
|
||||
|
||||
class GameDashboardTest : public AshTestBase {
|
||||
protected:
|
||||
GameDashboardTest()
|
||||
: AshTestBase(base::test::TaskEnvironment::TimeSource::MOCK_TIME) {}
|
||||
~GameDashboardTest() override = default;
|
||||
|
||||
void SetUp() override {
|
||||
base::SysInfo::SetChromeOSVersionInfoForTest(
|
||||
"CHROMEOS_RELEASE_TRACK=testimage-channel",
|
||||
base::SysInfo::GetLsbReleaseTime());
|
||||
scoped_feature_list_.InitAndEnableFeature({features::kGameDashboard});
|
||||
AshTestBase::SetUp();
|
||||
EXPECT_TRUE(features::IsGameDashboardEnabled());
|
||||
}
|
||||
|
||||
void TearDown() override {
|
||||
AshTestBase::TearDown();
|
||||
base::SysInfo::ResetChromeOSVersionInfoForTest();
|
||||
}
|
||||
|
||||
base::test::ScopedFeatureList scoped_feature_list_;
|
||||
};
|
||||
|
||||
TEST_F(GameDashboardTest, StartStopController) {
|
||||
GameDashboardController* controller =
|
||||
Shell::Get()->game_dashboard_controller();
|
||||
ASSERT_TRUE(controller);
|
||||
|
||||
// Without a focused game window, the controller shouldn't start.
|
||||
controller->Start(nullptr);
|
||||
EXPECT_FALSE(controller->IsActive(nullptr));
|
||||
|
||||
// With a non-ARC app window, the controller shouldn't start, even if start is
|
||||
// requested.
|
||||
auto owned_browser_window =
|
||||
CreateAppWindow(gfx::Rect(5, 5, 20, 20), ash::AppType::BROWSER);
|
||||
aura::Window* browser_window = owned_browser_window.get();
|
||||
EXPECT_FALSE(controller->IsActive(browser_window));
|
||||
controller->Start(browser_window);
|
||||
EXPECT_FALSE(controller->IsActive(browser_window));
|
||||
|
||||
// Make sure it is safe to try to stop an inactive controller.
|
||||
controller->Stop(browser_window);
|
||||
EXPECT_FALSE(controller->IsActive(browser_window));
|
||||
|
||||
// With an active ARC window, the controller should start.
|
||||
auto owned_arc_window =
|
||||
CreateAppWindow(gfx::Rect(5, 5, 20, 20), ash::AppType::ARC_APP);
|
||||
aura::Window* arc_window = owned_arc_window.get();
|
||||
controller->Start(arc_window);
|
||||
EXPECT_TRUE(controller->IsActive(arc_window));
|
||||
|
||||
// Make sure an active controller stops.
|
||||
controller->Stop(arc_window);
|
||||
EXPECT_FALSE(controller->IsActive(arc_window));
|
||||
|
||||
// Test start following a stop.
|
||||
controller->Start(arc_window);
|
||||
EXPECT_TRUE(controller->IsActive(arc_window));
|
||||
}
|
||||
|
||||
TEST_F(GameDashboardTest, DestroyWindow) {
|
||||
GameDashboardController* controller =
|
||||
Shell::Get()->game_dashboard_controller();
|
||||
ASSERT_TRUE(controller);
|
||||
|
||||
// With an active ARC window, the controller should start.
|
||||
auto owned_window =
|
||||
CreateAppWindow(gfx::Rect(5, 5, 20, 20), ash::AppType::ARC_APP);
|
||||
aura::Window* window = owned_window.get();
|
||||
controller->Start(window);
|
||||
EXPECT_TRUE(controller->IsActive(window));
|
||||
|
||||
owned_window.reset();
|
||||
EXPECT_FALSE(controller->IsActive(window));
|
||||
}
|
||||
|
||||
} // namespace ash
|
@ -71,6 +71,7 @@
|
||||
#include "ash/frame/non_client_frame_view_ash.h"
|
||||
#include "ash/frame/snap_controller_impl.h"
|
||||
#include "ash/frame_throttler/frame_throttling_controller.h"
|
||||
#include "ash/game_dashboard/game_dashboard_controller.h"
|
||||
#include "ash/glanceables/glanceables_controller.h"
|
||||
#include "ash/glanceables/glanceables_delegate.h"
|
||||
#include "ash/host/ash_window_tree_host_init_params.h"
|
||||
@ -837,6 +838,8 @@ Shell::~Shell() {
|
||||
window_cycle_controller_.reset();
|
||||
overview_controller_.reset();
|
||||
|
||||
game_dashboard_controller_.reset();
|
||||
|
||||
// This must be destroyed before deleting all the windows below in
|
||||
// `CloseAllRootWindowChildWindows()`, since shutting down the session will
|
||||
// need to access those windows and it will be a UAF.
|
||||
@ -1165,6 +1168,10 @@ void Shell::Init(
|
||||
capture_mode_controller_ = std::make_unique<CaptureModeController>(
|
||||
shell_delegate_->CreateCaptureModeDelegate());
|
||||
|
||||
if (features::IsGameDashboardEnabled()) {
|
||||
game_dashboard_controller_ = std::make_unique<GameDashboardController>();
|
||||
}
|
||||
|
||||
// Accelerometer file reader starts listening to tablet mode controller.
|
||||
AccelerometerReader::GetInstance()->StartListenToTabletModeController();
|
||||
|
||||
|
@ -12,6 +12,7 @@
|
||||
#include "ash/accessibility/accessibility_event_handler_manager.h"
|
||||
#include "ash/ash_export.h"
|
||||
#include "ash/constants/ash_features.h"
|
||||
#include "ash/game_dashboard/game_dashboard_controller.h"
|
||||
#include "ash/in_session_auth/in_session_auth_dialog_controller_impl.h"
|
||||
#include "ash/public/cpp/session/session_observer.h"
|
||||
#include "ash/public/cpp/shelf_types.h"
|
||||
@ -250,6 +251,10 @@ namespace federated {
|
||||
class FederatedServiceControllerImpl;
|
||||
} // namespace federated
|
||||
|
||||
namespace game_dashboard {
|
||||
class GameDashboardController;
|
||||
} // namespace game_dashboard
|
||||
|
||||
namespace quick_pair {
|
||||
class Mediator;
|
||||
} // namespace quick_pair
|
||||
@ -507,6 +512,9 @@ class ASH_EXPORT Shell : public SessionObserver,
|
||||
FullscreenMagnifierController* fullscreen_magnifier_controller() {
|
||||
return fullscreen_magnifier_controller_.get();
|
||||
}
|
||||
GameDashboardController* game_dashboard_controller() {
|
||||
return game_dashboard_controller_.get();
|
||||
}
|
||||
GeolocationController* geolocation_controller() {
|
||||
return geolocation_controller_.get();
|
||||
}
|
||||
@ -908,6 +916,7 @@ class ASH_EXPORT Shell : public SessionObserver,
|
||||
firmware_update_notification_controller_;
|
||||
std::unique_ptr<FocusCycler> focus_cycler_;
|
||||
std::unique_ptr<FloatController> float_controller_;
|
||||
std::unique_ptr<GameDashboardController> game_dashboard_controller_;
|
||||
std::unique_ptr<GeolocationController> geolocation_controller_;
|
||||
std::unique_ptr<GlanceablesController> glanceables_controller_;
|
||||
std::unique_ptr<HoldingSpaceController> holding_space_controller_;
|
||||
|
Reference in New Issue
Block a user