0

OnTask session end and locked mode notifications

This change introduces toast notifications for consumers when they enter
locked mode as well as the end of their session.

Bug: b:372540941, b:365849773
Change-Id: I8d08c1318324e0ca5fb5d7b75ec2372378ed1a0a
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5962745
Auto-Submit: Vignesh Shenvi <vshenvi@google.com>
Reviewed-by: Gavin Williams <gavinwill@chromium.org>
Reviewed-by: Jiaming Cheng <jiamingc@chromium.org>
Reviewed-by: Matthew Zhu <zhumatthew@google.com>
Commit-Queue: Gavin Williams <gavinwill@chromium.org>
Reviewed-by: Dana Fried <dfried@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1374697}
This commit is contained in:
Vignesh Shenvi
2024-10-28 17:10:27 +00:00
committed by Chromium LUCI CQ
parent 5ba2cc69c9
commit 6f7e1711e9
13 changed files with 183 additions and 27 deletions

@ -316,7 +316,9 @@ enum class ToastCatalogName {
kGameDashboardEnterTablet = 50,
kInformedRestoreOnboarding = 51,
kTouchpadDisabled = 52,
kMaxValue = kTouchpadDisabled
kOnTaskEnterLockedMode = 53,
kOnTaskSessionEnd = 54,
kMaxValue = kOnTaskSessionEnd
};
} // namespace ash

@ -8,6 +8,7 @@ assert(is_chromeos_ash, "OnTask is only supported in Ash")
static_library("on_task") {
sources = [
"notification_constants.h",
"on_task_blocklist.cc",
"on_task_blocklist.h",
"on_task_extensions_manager.h",
@ -27,6 +28,7 @@ static_library("on_task") {
"//base",
"//chromeos/ash/components/boca",
"//chromeos/ash/components/boca/proto",
"//chromeos/strings:strings_grit",
"//components/google/core/common",
"//components/policy/core/browser",
"//components/pref_registry",

@ -5,5 +5,6 @@ include_rules = [
"+components/policy/core/browser",
"+components/sessions/content/session_tab_helper.h",
"+components/sessions/core/session_id.h",
"+content/public/browser/web_contents.h"
"+content/public/browser/web_contents.h",
"+ui/base/l10n/l10n_util.h"
]

@ -0,0 +1,26 @@
// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROMEOS_ASH_COMPONENTS_BOCA_ON_TASK_NOTIFICATION_CONSTANTS_H_
#define CHROMEOS_ASH_COMPONENTS_BOCA_ON_TASK_NOTIFICATION_CONSTANTS_H_
#include "base/time/time.h"
namespace ash::boca {
// Interval for countdown notifications.
inline constexpr base::TimeDelta kOnTaskNotificationCountdownInterval =
base::Seconds(1);
// Notification id for the toast shown before entering locked fullscreen.
inline constexpr char kOnTaskEnterLockedModeNotificationId[] =
"OnTaskEnterLockedModeNotification";
// Notification id for the toast shown after the session has ended.
inline constexpr char kOnTaskSessionEndNotificationId[] =
"OnTaskSessionEndNotification";
} // namespace ash::boca
#endif // CHROMEOS_ASH_COMPONENTS_BOCA_ON_TASK_NOTIFICATION_CONSTANTS_H_

@ -13,6 +13,7 @@
#include "base/memory/ptr_util.h"
#include "base/task/sequenced_task_runner.h"
#include "base/timer/timer.h"
#include "chromeos/ash/components/boca/on_task/notification_constants.h"
namespace ash::boca {
@ -74,7 +75,7 @@ void OnTaskNotificationsManager::CreateToast(ToastCreateParams params) {
}
auto notification_timer = std::make_unique<base::RepeatingTimer>();
notification_timer->Start(
FROM_HERE, base::Seconds(1),
FROM_HERE, kOnTaskNotificationCountdownInterval,
base::BindRepeating(&OnTaskNotificationsManager::CreateToastInternal,
weak_ptr_factory_.GetWeakPtr(),
base::OwnedRef(params)));
@ -109,13 +110,12 @@ void OnTaskNotificationsManager::CreateToastInternal(
// Display toast.
ToastData toast_data(
params.id, params.catalog_name,
/*text=*/params.text_description_callback.Run(params.countdown_period),
base::Seconds(1));
/*text=*/params.text_description_callback.Run(params.countdown_period));
delegate_->ShowToast(std::move(toast_data));
// Decrement countdown period by one second before the next toast is
// scheduled.
params.countdown_period = params.countdown_period - base::Seconds(1);
// Decrement countdown period before the next toast is scheduled.
params.countdown_period =
params.countdown_period - kOnTaskNotificationCountdownInterval;
}
} // namespace ash::boca

@ -16,6 +16,7 @@
#include "base/test/bind.h"
#include "base/test/task_environment.h"
#include "base/time/time.h"
#include "chromeos/ash/components/boca/on_task/notification_constants.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
@ -24,7 +25,6 @@ namespace {
constexpr char kTestNotificationId[] = "TestOnTaskNotification";
constexpr std::u16string_view kToastDescription = u"TestDescription";
constexpr base::TimeDelta kToastCountdownInterval = base::Seconds(1);
constexpr base::TimeDelta kToastCountdownPeriod = base::Seconds(5);
// Fake delegate implementation for the `OnTaskNotificationsManager`.
@ -75,12 +75,12 @@ TEST_F(OnTaskNotificationsManagerTest, CreateToastWithNoCountdownPeriod) {
// Verify toast is shown after a 1 second delay because of the scheduled
// timer.
task_environment_.FastForwardBy(kToastCountdownInterval);
task_environment_.FastForwardBy(kOnTaskNotificationCountdownInterval);
ASSERT_EQ(fake_delegate_ptr_->GetToastCount(), 1u);
ASSERT_FALSE(callback_triggered);
// Advance timer by 1 more second and verify callback is triggered.
task_environment_.FastForwardBy(kToastCountdownInterval);
task_environment_.FastForwardBy(kOnTaskNotificationCountdownInterval);
EXPECT_TRUE(callback_triggered);
}
@ -101,7 +101,7 @@ TEST_F(OnTaskNotificationsManagerTest,
notifications_manager_->CreateToast(std::move(create_params_1));
// Verify toast is shown after a 1 second delay.
task_environment_.FastForwardBy(kToastCountdownInterval);
task_environment_.FastForwardBy(kOnTaskNotificationCountdownInterval);
ASSERT_EQ(fake_delegate_ptr_->GetToastCount(), 1u);
// Trigger another toast with the same id.
@ -140,13 +140,13 @@ TEST_F(OnTaskNotificationsManagerTest, StopProcessingToast) {
notifications_manager_->CreateToast(std::move(create_params));
// Verify toast is shown after a 1 second delay.
task_environment_.FastForwardBy(kToastCountdownInterval);
task_environment_.FastForwardBy(kOnTaskNotificationCountdownInterval);
ASSERT_EQ(fake_delegate_ptr_->GetToastCount(), 1u);
// Attempt to stop processing notification and verify toast is not shown with
// subsequent timer advances.
notifications_manager_->StopProcessingNotification(kTestNotificationId);
task_environment_.FastForwardBy(kToastCountdownInterval);
task_environment_.FastForwardBy(kOnTaskNotificationCountdownInterval);
EXPECT_EQ(fake_delegate_ptr_->GetToastCount(), 1u);
}
@ -166,12 +166,12 @@ TEST_F(OnTaskNotificationsManagerTest,
notifications_manager_->CreateToast(std::move(create_params));
// Verify toast is shown after a 1 second delay.
task_environment_.FastForwardBy(kToastCountdownInterval);
task_environment_.FastForwardBy(kOnTaskNotificationCountdownInterval);
ASSERT_EQ(fake_delegate_ptr_->GetToastCount(), 1u);
ASSERT_FALSE(callback_triggered);
// Toasts remain visible before count down.
task_environment_.FastForwardBy(kToastCountdownInterval);
task_environment_.FastForwardBy(kOnTaskNotificationCountdownInterval);
ASSERT_EQ(fake_delegate_ptr_->GetToastCount(), 2u);
ASSERT_FALSE(callback_triggered);

@ -6,6 +6,7 @@
#include <memory>
#include "ash/constants/notifier_catalogs.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/logging.h"
@ -14,14 +15,18 @@
#include "base/sequence_checker.h"
#include "base/task/bind_post_task.h"
#include "base/task/sequenced_task_runner.h"
#include "base/time/time.h"
#include "chromeos/ash/components/boca/on_task/activity/active_tab_tracker.h"
#include "chromeos/ash/components/boca/on_task/notification_constants.h"
#include "chromeos/ash/components/boca/on_task/on_task_blocklist.h"
#include "chromeos/ash/components/boca/on_task/on_task_notifications_manager.h"
#include "chromeos/ash/components/boca/on_task/on_task_system_web_app_manager.h"
#include "chromeos/strings/grit/chromeos_strings.h"
#include "components/sessions/core/session_id.h"
#include "ui/base/l10n/l10n_util.h"
#include "url/gurl.h"
namespace ash::boca {
namespace {
// Delay in seconds before we attempt to add a tab.
@ -44,7 +49,8 @@ OnTaskSessionManager::OnTaskSessionManager(
std::make_unique<OnTaskSessionManager::SystemWebAppLaunchHelper>(
system_web_app_manager_.get(),
std::vector<boca::BocaWindowObserver*>{&active_tab_tracker_,
this})) {}
this})),
notifications_manager_(OnTaskNotificationsManager::Create()) {}
OnTaskSessionManager::~OnTaskSessionManager() = default;
@ -79,6 +85,16 @@ void OnTaskSessionManager::OnSessionEnded(const std::string& session_id) {
// Re-enable extensions on session end to prepare for subsequent sessions.
extensions_manager_->ReEnableExtensions();
// Surface toast to notify users about session end.
OnTaskNotificationsManager::ToastCreateParams toast_create_params(
kOnTaskSessionEndNotificationId, ToastCatalogName::kOnTaskSessionEnd,
/*text_description_callback=*/
base::BindRepeating([](base::TimeDelta countdown_period) {
return l10n_util::GetStringUTF16(
IDS_ON_TASK_SESSION_END_NOTIFICATION_MESSAGE);
}));
notifications_manager_->CreateToast(std::move(toast_create_params));
}
void OnTaskSessionManager::OnBundleUpdated(const ::boca::Bundle& bundle) {
@ -146,18 +162,39 @@ void OnTaskSessionManager::OnBundleUpdated(const ::boca::Bundle& bundle) {
}
}
// Disable extensions in the context of OnTask before the window is locked.
// Re-enable them otherwise.
bool should_lock_window = bundle.locked();
if (should_lock_window) {
// Disable extensions as appropriate and surface toast before locking the
// window.
extensions_manager_->DisableExtensions();
auto lock_window_callback = base::BindRepeating(
&SystemWebAppLaunchHelper::SetPinStateForActiveSWAWindow,
base::Unretained(system_web_app_launch_helper_.get()),
should_lock_window,
/*callback=*/
base::BindRepeating(&OnTaskSessionManager::OnSetPinStateOnBocaSWAWindow,
weak_ptr_factory_.GetWeakPtr()));
OnTaskNotificationsManager::ToastCreateParams toast_create_params(
kOnTaskEnterLockedModeNotificationId,
ToastCatalogName::kOnTaskEnterLockedMode,
/*text_description_callback=*/
base::BindRepeating([](base::TimeDelta countdown_period) {
return l10n_util::GetStringUTF16(
IDS_ON_TASK_ENTER_LOCKED_MODE_NOTIFICATION_MESSAGE);
}),
/*completion_callback=*/std::move(lock_window_callback));
notifications_manager_->CreateToast(std::move(toast_create_params));
} else {
// Re-enable extensions as well as clear pending toasts before attempting to
// unlock the window.
extensions_manager_->ReEnableExtensions();
notifications_manager_->StopProcessingNotification(
kOnTaskEnterLockedModeNotificationId);
system_web_app_launch_helper_->SetPinStateForActiveSWAWindow(
/*pinned=*/should_lock_window,
base::BindRepeating(&OnTaskSessionManager::OnSetPinStateOnBocaSWAWindow,
weak_ptr_factory_.GetWeakPtr()));
}
system_web_app_launch_helper_->SetPinStateForActiveSWAWindow(
/*pinned=*/should_lock_window,
base::BindOnce(&OnTaskSessionManager::OnSetPinStateOnBocaSWAWindow,
weak_ptr_factory_.GetWeakPtr()));
}
void OnTaskSessionManager::OnAppReloaded() {
@ -287,7 +324,8 @@ void OnTaskSessionManager::SystemWebAppLaunchHelper::RemoveTab(
}
void OnTaskSessionManager::SystemWebAppLaunchHelper::
SetPinStateForActiveSWAWindow(bool pinned, base::OnceClosure callback) {
SetPinStateForActiveSWAWindow(bool pinned,
base::RepeatingClosure callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (launch_in_progress_) {
base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(

@ -9,6 +9,7 @@
#include <string>
#include "base/containers/flat_map.h"
#include "base/functional/callback_forward.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/sequence_checker.h"
@ -17,6 +18,7 @@
#include "chromeos/ash/components/boca/on_task/activity/active_tab_tracker.h"
#include "chromeos/ash/components/boca/on_task/on_task_blocklist.h"
#include "chromeos/ash/components/boca/on_task/on_task_extensions_manager.h"
#include "chromeos/ash/components/boca/on_task/on_task_notifications_manager.h"
#include "chromeos/ash/components/boca/on_task/on_task_system_web_app_manager.h"
#include "chromeos/ash/components/boca/proto/bundle.pb.h"
#include "url/gurl.h"
@ -75,7 +77,8 @@ class OnTaskSessionManager : public boca::BocaSessionManager::Observer,
base::OnceCallback<void(SessionID)> callback);
void RemoveTab(const std::set<SessionID>& tab_ids_to_remove,
base::OnceClosure callback);
void SetPinStateForActiveSWAWindow(bool pinned, base::OnceClosure callback);
void SetPinStateForActiveSWAWindow(bool pinned,
base::RepeatingClosure callback);
private:
// Callback triggered when the Boca SWA is launched. Normally at the onset
@ -131,6 +134,8 @@ class OnTaskSessionManager : public boca::BocaSessionManager::Observer,
const std::unique_ptr<SystemWebAppLaunchHelper> system_web_app_launch_helper_;
std::unique_ptr<OnTaskNotificationsManager> notifications_manager_;
base::WeakPtrFactory<OnTaskSessionManager> weak_ptr_factory_{this};
};

@ -5,13 +5,18 @@
#include "chromeos/ash/components/boca/on_task/on_task_session_manager.h"
#include <memory>
#include <set>
#include <utility>
#include "ash/public/cpp/system/toast_data.h"
#include "base/containers/flat_map.h"
#include "base/functional/callback.h"
#include "base/test/task_environment.h"
#include "chromeos/ash/components/boca/on_task/activity/active_tab_tracker.h"
#include "chromeos/ash/components/boca/on_task/notification_constants.h"
#include "chromeos/ash/components/boca/on_task/on_task_blocklist.h"
#include "chromeos/ash/components/boca/on_task/on_task_extensions_manager.h"
#include "chromeos/ash/components/boca/on_task/on_task_notifications_manager.h"
#include "chromeos/ash/components/boca/on_task/on_task_system_web_app_manager.h"
#include "chromeos/ash/components/boca/proto/roster.pb.h"
#include "chromeos/ash/components/boca/proto/session.pb.h"
@ -88,6 +93,27 @@ class OnTaskExtensionsManagerMock : public OnTaskExtensionsManager {
MOCK_METHOD(void, ReEnableExtensions, (), (override));
};
// Fake delegate implementation for the `OnTaskNotificationsManager` to minimize
// dependency on Ash UI.
class FakeOnTaskNotificationsManagerDelegate
: public OnTaskNotificationsManager::Delegate {
public:
FakeOnTaskNotificationsManagerDelegate() = default;
~FakeOnTaskNotificationsManagerDelegate() override = default;
void ShowToast(ToastData toast_data) override {
notifications_shown_.insert(toast_data.id);
}
bool WasNotificationShown(const std::string& id) {
return notifications_shown_.contains(id);
}
private:
std::set<std::string> notifications_shown_;
};
} // namespace
class OnTaskSessionManagerTest : public ::testing::Test {
@ -101,6 +127,15 @@ class OnTaskSessionManagerTest : public ::testing::Test {
extensions_manager_ptr_ = extensions_manager.get();
session_manager_ = std::make_unique<OnTaskSessionManager>(
std::move(system_web_app_manager), std::move(extensions_manager));
// Override notification manager implementation to minimize dependency on
// Ash UI.
auto fake_notifications_delegate =
std::make_unique<FakeOnTaskNotificationsManagerDelegate>();
fake_notifications_delegate_ptr_ = fake_notifications_delegate.get();
session_manager_->notifications_manager_ =
OnTaskNotificationsManager::CreateForTest(
std::move(fake_notifications_delegate));
}
base::flat_map<GURL, std::set<SessionID>>* provider_url_tab_ids_map() {
@ -114,10 +149,13 @@ class OnTaskSessionManagerTest : public ::testing::Test {
return &session_manager_->provider_url_restriction_level_map_;
}
base::test::SingleThreadTaskEnvironment task_environment_;
base::test::SingleThreadTaskEnvironment task_environment_{
base::test::TaskEnvironment::TimeSource::MOCK_TIME};
std::unique_ptr<OnTaskSessionManager> session_manager_;
raw_ptr<NiceMock<OnTaskSystemWebAppManagerMock>> system_web_app_manager_ptr_;
raw_ptr<NiceMock<OnTaskExtensionsManagerMock>> extensions_manager_ptr_;
raw_ptr<FakeOnTaskNotificationsManagerDelegate>
fake_notifications_delegate_ptr_;
};
TEST_F(OnTaskSessionManagerTest, ShouldLaunchBocaSWAOnSessionStart) {
@ -167,6 +205,11 @@ TEST_F(OnTaskSessionManagerTest, ShouldCloseBocaSWAOnSessionEnd) {
EXPECT_CALL(*system_web_app_manager_ptr_, CloseSystemWebAppWindow(kWindowId))
.Times(1);
session_manager_->OnSessionEnded("test_session_id");
// Verify session end notification was shown.
task_environment_.FastForwardBy(kOnTaskNotificationCountdownInterval);
EXPECT_TRUE(fake_notifications_delegate_ptr_->WasNotificationShown(
kOnTaskSessionEndNotificationId));
}
TEST_F(OnTaskSessionManagerTest, ShouldReEnableExtensionsOnSessionEnd) {
@ -175,6 +218,11 @@ TEST_F(OnTaskSessionManagerTest, ShouldReEnableExtensionsOnSessionEnd) {
.WillRepeatedly(Return(kWindowId));
EXPECT_CALL(*extensions_manager_ptr_, ReEnableExtensions).Times(1);
session_manager_->OnSessionEnded("test_session_id");
// Verify session end notification was shown.
task_environment_.FastForwardBy(kOnTaskNotificationCountdownInterval);
EXPECT_TRUE(fake_notifications_delegate_ptr_->WasNotificationShown(
kOnTaskSessionEndNotificationId));
}
TEST_F(OnTaskSessionManagerTest, ShouldIgnoreWhenNoBocaSWAOpenOnSessionEnd) {
@ -183,6 +231,11 @@ TEST_F(OnTaskSessionManagerTest, ShouldIgnoreWhenNoBocaSWAOpenOnSessionEnd) {
EXPECT_CALL(*system_web_app_manager_ptr_, CloseSystemWebAppWindow(_))
.Times(0);
session_manager_->OnSessionEnded("test_session_id");
// Verify session end notification was shown.
task_environment_.FastForwardBy(kOnTaskNotificationCountdownInterval);
EXPECT_TRUE(fake_notifications_delegate_ptr_->WasNotificationShown(
kOnTaskSessionEndNotificationId));
}
TEST_F(OnTaskSessionManagerTest, ShouldOpenTabsOnBundleUpdated) {
@ -326,6 +379,12 @@ TEST_F(OnTaskSessionManagerTest, ShouldPinBocaSWAWhenLockedOnBundleUpdated) {
bundle.add_content_configs()->set_url(kTestUrl1);
bundle.set_locked(true);
session_manager_->OnBundleUpdated(bundle);
// Verify notification and advance timer.
task_environment_.FastForwardBy(kOnTaskNotificationCountdownInterval);
EXPECT_TRUE(fake_notifications_delegate_ptr_->WasNotificationShown(
kOnTaskEnterLockedModeNotificationId));
task_environment_.FastForwardBy(kOnTaskNotificationCountdownInterval);
}
TEST_F(OnTaskSessionManagerTest,
@ -353,6 +412,12 @@ TEST_F(OnTaskSessionManagerTest,
bundle.set_locked(true);
session_manager_->OnSessionStarted("test_session_id", ::boca::UserIdentity());
session_manager_->OnBundleUpdated(bundle);
// Verify notification and advance timer.
task_environment_.FastForwardBy(kOnTaskNotificationCountdownInterval);
EXPECT_TRUE(fake_notifications_delegate_ptr_->WasNotificationShown(
kOnTaskEnterLockedModeNotificationId));
task_environment_.FastForwardBy(kOnTaskNotificationCountdownInterval);
}
TEST_F(OnTaskSessionManagerTest, ShouldAddTabsWhenAdditionalTabsFoundInBundle) {
@ -459,6 +524,12 @@ TEST_F(OnTaskSessionManagerTest, ShouldDisableExtensionsOnLock) {
bundle.add_content_configs()->set_url(kTestUrl1);
bundle.set_locked(true);
session_manager_->OnBundleUpdated(bundle);
// Verify notification and advance timer.
task_environment_.FastForwardBy(kOnTaskNotificationCountdownInterval);
EXPECT_TRUE(fake_notifications_delegate_ptr_->WasNotificationShown(
kOnTaskEnterLockedModeNotificationId));
task_environment_.FastForwardBy(kOnTaskNotificationCountdownInterval);
}
TEST_F(OnTaskSessionManagerTest, ShouldReEnableExtensionsOnUnlock) {

@ -6861,6 +6861,13 @@
on Sunday
</message>
<!-- Strings for OnTask notifications -->
<message name="IDS_ON_TASK_ENTER_LOCKED_MODE_NOTIFICATION_MESSAGE" desc="Toast message telling students that their teachers have locked their screen.">
Your teacher has locked your screen
</message>
<message name="IDS_ON_TASK_SESSION_END_NOTIFICATION_MESSAGE" desc="Toast message telling students that their class session ended.">
Your teacher has ended class
</message>
</messages>
</release>
</grit>

@ -0,0 +1 @@
fc50a768e914b60e8a0be9bf016543cb148ee930

@ -0,0 +1 @@
e7de2382513235aea76626eb1e65d6daea2cfdc9

@ -2308,6 +2308,8 @@ chromeos/ash/components/peripheral_notification/peripheral_notification_manager.
<int value="48" label="Video Conference Speak On Mute Opt In Confirmation"/>
<int value="50" label="Game Dashboard Disabled In Tablet Mode"/>
<int value="51" label="Informed Restore Onboarding Turn On Ask Every Time"/>
<int value="53" label="Enter OnTask locked mode"/>
<int value="54" label="OnTask session end"/>
</enum>
<enum name="TogglePickerAction">