PrivacyIndicators: Add launch settings notification button.
Add the buttons to privacy indicators notification with the functionality to launch the app settings. The button to launch the app will be implemented in the future. Also, added unit tests for PrivacyIndicatorsController. Bug: 1344681 Change-Id: Ic97fe58da719fa37b57fb8268bf3ceef2d1877b8 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3827623 Reviewed-by: Ahmed Fakhry <afakhry@chromium.org> Reviewed-by: Alex Newcomer <newcomer@chromium.org> Commit-Queue: Andre Le <leandre@chromium.org> Cr-Commit-Position: refs/heads/main@{#1035123}
This commit is contained in:

committed by
Chromium LUCI CQ

parent
e1b241cc0a
commit
dc8851656a
ash
BUILD.gnash_strings.grd
ash_strings_grd
IDS_PRIVACY_NOTIFICATION_BUTTON_APP_LAUNCH.png.sha1IDS_PRIVACY_NOTIFICATION_BUTTON_APP_SETTINGS.png.sha1
system
chrome/browser/ui/ash
@ -2880,6 +2880,7 @@ test("ash_unittests") {
|
||||
"system/power/power_prefs_unittest.cc",
|
||||
"system/power/power_status_unittest.cc",
|
||||
"system/power/video_activity_notifier_unittest.cc",
|
||||
"system/privacy/privacy_indicators_controller_unittest.cc",
|
||||
"system/privacy_hub/camera_privacy_switch_controller_unittest.cc",
|
||||
"system/privacy_hub/microphone_privacy_switch_controller_unittest.cc",
|
||||
"system/progress_indicator/progress_indicator_animation_registry_unittest.cc",
|
||||
|
@ -3930,6 +3930,12 @@ Here are some things you can try to get started.
|
||||
<message name="IDS_PRIVACY_NOTIFICATION_MESSAGE_MIC" desc="Message label for a notification shown when an app is using the microphone.">
|
||||
<ph name="APP_NAME">$1<ex>Google meet</ex></ph> is currently using your microphone
|
||||
</message>
|
||||
<message name="IDS_PRIVACY_NOTIFICATION_BUTTON_APP_LAUNCH" desc="Title for the launch app button of the notification shown when an app is using the camera/microphone.">
|
||||
Go to app
|
||||
</message>
|
||||
<message name="IDS_PRIVACY_NOTIFICATION_BUTTON_APP_SETTINGS" desc="Title for the launch app button of the notification shown when an app is using the camera/microphone.">
|
||||
App settings
|
||||
</message>
|
||||
|
||||
<!-- Strings for microphone mute switch notification -->
|
||||
<message name="IDS_MICROPHONE_MUTED_NOTIFICATION_TITLE" desc="Title for a notification shown to the users when an app tries to use the microphone while the microphone is muted.i Similar to IDS_MICROPHONE_MUTED_NOTIFICATION_TITLE_WITH_APP_NAME, except this message contains a generic app name string. Used when the name of the app that's using the microphone cannot be determined.">
|
||||
|
@ -0,0 +1 @@
|
||||
b39e1039abbe8b4b283caaa49179df03e639d98b
|
@ -0,0 +1 @@
|
||||
0db2f5a5696cfaae437f53d87945927583da33df
|
@ -17,15 +17,46 @@
|
||||
namespace ash {
|
||||
|
||||
namespace {
|
||||
|
||||
const char kPrivacyIndicatorsNotificationIdPrefix[] = "privacy-indicators";
|
||||
const char kPrivacyIndicatorsNotifierId[] = "ash.privacy-indicators";
|
||||
|
||||
// Keep track of the button indexes in the privacy indicators notification.
|
||||
enum PrivacyIndicatorsNotificationButton { kAppLaunch, kAppSettings };
|
||||
|
||||
} // namespace
|
||||
|
||||
PrivacyIndicatorsNotificationDelegate::PrivacyIndicatorsNotificationDelegate(
|
||||
const AppActionClosure& launch_app,
|
||||
const AppActionClosure& launch_settings)
|
||||
: launch_app_(launch_app), launch_settings_(launch_settings) {}
|
||||
|
||||
PrivacyIndicatorsNotificationDelegate::
|
||||
~PrivacyIndicatorsNotificationDelegate() = default;
|
||||
|
||||
void PrivacyIndicatorsNotificationDelegate::Click(
|
||||
const absl::optional<int>& button_index,
|
||||
const absl::optional<std::u16string>& reply) {
|
||||
// Click on the notification body is no-op.
|
||||
if (!button_index)
|
||||
return;
|
||||
|
||||
switch (button_index.value()) {
|
||||
case PrivacyIndicatorsNotificationButton::kAppLaunch:
|
||||
launch_app_.Run();
|
||||
break;
|
||||
case PrivacyIndicatorsNotificationButton::kAppSettings:
|
||||
launch_settings_.Run();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void ModifyPrivacyIndicatorsNotification(
|
||||
const std::string& app_id,
|
||||
absl::optional<std::u16string> app_name,
|
||||
bool camera_is_used,
|
||||
bool microphone_is_used) {
|
||||
bool microphone_is_used,
|
||||
scoped_refptr<PrivacyIndicatorsNotificationDelegate> delegate) {
|
||||
auto* message_center = message_center::MessageCenter::Get();
|
||||
std::string id = kPrivacyIndicatorsNotificationIdPrefix + app_id;
|
||||
bool notification_exist = message_center->FindVisibleNotificationById(id);
|
||||
@ -38,6 +69,7 @@ void ModifyPrivacyIndicatorsNotification(
|
||||
|
||||
std::u16string app_name_str = app_name.value_or(l10n_util::GetStringUTF16(
|
||||
IDS_PRIVACY_NOTIFICATION_MESSAGE_DEFAULT_APP_NAME));
|
||||
|
||||
std::u16string title;
|
||||
std::u16string message;
|
||||
if (camera_is_used && microphone_is_used) {
|
||||
@ -60,6 +92,13 @@ void ModifyPrivacyIndicatorsNotification(
|
||||
// Make the notification low priority so that it is silently added (no popup).
|
||||
optional_fields.priority = message_center::LOW_PRIORITY;
|
||||
|
||||
// Note: The order of buttons added here should match the order in
|
||||
// PrivacyIndicatorsNotificationButton.
|
||||
optional_fields.buttons.emplace_back(
|
||||
l10n_util::GetStringUTF16(IDS_PRIVACY_NOTIFICATION_BUTTON_APP_LAUNCH));
|
||||
optional_fields.buttons.emplace_back(
|
||||
l10n_util::GetStringUTF16(IDS_PRIVACY_NOTIFICATION_BUTTON_APP_SETTINGS));
|
||||
|
||||
auto notification = CreateSystemNotification(
|
||||
message_center::NotificationType::NOTIFICATION_TYPE_SIMPLE, id, title,
|
||||
message,
|
||||
@ -69,7 +108,7 @@ void ModifyPrivacyIndicatorsNotification(
|
||||
kPrivacyIndicatorsNotifierId,
|
||||
NotificationCatalogName::kPrivacyIndicators),
|
||||
optional_fields,
|
||||
/*delegate=*/nullptr, kImeMenuMicrophoneIcon,
|
||||
/*delegate=*/delegate, kImeMenuMicrophoneIcon,
|
||||
message_center::SystemNotificationWarningLevel::NORMAL);
|
||||
|
||||
notification->set_accent_color_id(ui::kColorAshPrivacyIndicatorsBackground);
|
||||
|
@ -9,16 +9,48 @@
|
||||
|
||||
#include "ash/ash_export.h"
|
||||
#include "third_party/abseil-cpp/absl/types/optional.h"
|
||||
#include "ui/message_center/public/cpp/notification_delegate.h"
|
||||
|
||||
namespace ash {
|
||||
|
||||
using AppActionClosure = base::RepeatingCallback<void(void)>;
|
||||
|
||||
// An interface for the delegate of the privacy indicators notification,
|
||||
// handling launching the app and its settings.
|
||||
class ASH_EXPORT PrivacyIndicatorsNotificationDelegate
|
||||
: public message_center::NotificationDelegate {
|
||||
public:
|
||||
PrivacyIndicatorsNotificationDelegate(
|
||||
const AppActionClosure& launch_app,
|
||||
const AppActionClosure& launch_settings);
|
||||
PrivacyIndicatorsNotificationDelegate(
|
||||
const PrivacyIndicatorsNotificationDelegate&) = delete;
|
||||
PrivacyIndicatorsNotificationDelegate& operator=(
|
||||
const PrivacyIndicatorsNotificationDelegate&) = delete;
|
||||
|
||||
// message_center::NotificationDelegate:
|
||||
void Click(const absl::optional<int>& button_index,
|
||||
const absl::optional<std::u16string>& reply) override;
|
||||
|
||||
protected:
|
||||
~PrivacyIndicatorsNotificationDelegate() override;
|
||||
|
||||
private:
|
||||
const AppActionClosure launch_app_;
|
||||
const AppActionClosure launch_settings_;
|
||||
};
|
||||
|
||||
// Add, update, or remove the privacy notification associated with the given
|
||||
// `app_id`.
|
||||
void ASH_EXPORT
|
||||
ModifyPrivacyIndicatorsNotification(const std::string& app_id,
|
||||
absl::optional<std::u16string> app_name,
|
||||
bool camera_is_used,
|
||||
bool microphone_is_used);
|
||||
// The given scoped_refptr for `delegate` will be passed as a parameter for
|
||||
// CreateSystemNotification() in case of adding/updating the notification, can
|
||||
// be provided as a nullptr if irrelevant.
|
||||
void ASH_EXPORT ModifyPrivacyIndicatorsNotification(
|
||||
const std::string& app_id,
|
||||
absl::optional<std::u16string> app_name,
|
||||
bool camera_is_used,
|
||||
bool microphone_is_used,
|
||||
scoped_refptr<PrivacyIndicatorsNotificationDelegate> delegate);
|
||||
|
||||
} // namespace ash
|
||||
|
||||
|
135
ash/system/privacy/privacy_indicators_controller_unittest.cc
Normal file
135
ash/system/privacy/privacy_indicators_controller_unittest.cc
Normal file
@ -0,0 +1,135 @@
|
||||
// Copyright 2022 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "ash/system/privacy/privacy_indicators_controller.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "ash/system/message_center/ash_message_popup_collection.h"
|
||||
#include "ash/system/message_center/unified_message_center_bubble.h"
|
||||
#include "ash/system/message_center/unified_message_center_view.h"
|
||||
#include "ash/system/message_center/unified_message_list_view.h"
|
||||
#include "ash/system/unified/unified_system_tray.h"
|
||||
#include "ash/test/ash_test_base.h"
|
||||
#include "base/bind.h"
|
||||
#include "ui/events/test/event_generator.h"
|
||||
#include "ui/message_center/message_center.h"
|
||||
#include "ui/message_center/views/notification_view_base.h"
|
||||
#include "ui/views/widget/widget_utils.h"
|
||||
|
||||
namespace ash {
|
||||
|
||||
namespace {
|
||||
|
||||
const char kPrivacyIndicatorsNotificationIdPrefix[] = "privacy-indicators";
|
||||
|
||||
class TestDelegate : public PrivacyIndicatorsNotificationDelegate {
|
||||
public:
|
||||
TestDelegate()
|
||||
: PrivacyIndicatorsNotificationDelegate(
|
||||
base::BindRepeating(&TestDelegate::LaunchApp,
|
||||
base::Unretained(this)),
|
||||
base::BindRepeating(&TestDelegate::LaunchAppSettings,
|
||||
base::Unretained(this))) {}
|
||||
TestDelegate(const TestDelegate&) = delete;
|
||||
TestDelegate& operator=(const TestDelegate&) = delete;
|
||||
|
||||
void LaunchApp() { launch_app_called_ = true; }
|
||||
void LaunchAppSettings() { launch_settings_called_ = true; }
|
||||
|
||||
bool launch_app_called() { return launch_app_called_; }
|
||||
bool launch_settings_called() { return launch_settings_called_; }
|
||||
|
||||
private:
|
||||
~TestDelegate() override = default;
|
||||
|
||||
bool launch_app_called_ = false;
|
||||
bool launch_settings_called_ = false;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
class PrivacyIndicatorsControllerTest : public AshTestBase {
|
||||
public:
|
||||
PrivacyIndicatorsControllerTest() = default;
|
||||
PrivacyIndicatorsControllerTest(const PrivacyIndicatorsControllerTest&) =
|
||||
delete;
|
||||
PrivacyIndicatorsControllerTest& operator=(
|
||||
const PrivacyIndicatorsControllerTest&) = delete;
|
||||
~PrivacyIndicatorsControllerTest() override = default;
|
||||
|
||||
// Get the notification view from message center associated with `id`.
|
||||
views::View* GetNotificationViewFromMessageCenter(const std::string& id) {
|
||||
return GetPrimaryUnifiedSystemTray()
|
||||
->message_center_bubble()
|
||||
->message_center_view()
|
||||
->message_list_view()
|
||||
->GetMessageViewForNotificationId(id);
|
||||
}
|
||||
|
||||
// Get the popup notification view associated with `id`.
|
||||
views::View* GetPopupNotificationView(const std::string& id) {
|
||||
return GetPrimaryUnifiedSystemTray()
|
||||
->GetMessagePopupCollection()
|
||||
->GetMessageViewForNotificationId(id);
|
||||
}
|
||||
|
||||
void ClickView(message_center::NotificationViewBase* view, int button_index) {
|
||||
auto* action_buttons = view->GetViewByID(
|
||||
message_center::NotificationViewBase::kActionButtonsRow);
|
||||
|
||||
auto* button_view = action_buttons->children()[button_index];
|
||||
|
||||
ui::test::EventGenerator generator(GetRootWindow(button_view->GetWidget()));
|
||||
gfx::Point cursor_location = button_view->GetBoundsInScreen().CenterPoint();
|
||||
generator.MoveMouseTo(cursor_location);
|
||||
generator.ClickLeftButton();
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(PrivacyIndicatorsControllerTest, NotificationMetadata) {
|
||||
std::string app_id = "test_app_id";
|
||||
std::u16string app_name = u"test_app_name";
|
||||
std::string notification_id = kPrivacyIndicatorsNotificationIdPrefix + app_id;
|
||||
scoped_refptr<TestDelegate> delegate = base::MakeRefCounted<TestDelegate>();
|
||||
ash::ModifyPrivacyIndicatorsNotification(
|
||||
app_id, app_name, /*camera_is_used=*/true, /*microphone_is_used=*/true,
|
||||
delegate);
|
||||
|
||||
auto* notification =
|
||||
message_center::MessageCenter::Get()->FindNotificationById(
|
||||
notification_id);
|
||||
|
||||
// Notification message should contains app name.
|
||||
EXPECT_NE(std::string::npos, notification->message().find(app_name));
|
||||
}
|
||||
|
||||
TEST_F(PrivacyIndicatorsControllerTest, NotificationClickButton) {
|
||||
std::string app_id = "test_app_id";
|
||||
std::string notification_id = kPrivacyIndicatorsNotificationIdPrefix + app_id;
|
||||
scoped_refptr<TestDelegate> delegate = base::MakeRefCounted<TestDelegate>();
|
||||
ash::ModifyPrivacyIndicatorsNotification(
|
||||
app_id, u"test_app_name", /*camera_is_used=*/true,
|
||||
/*microphone_is_used=*/true, delegate);
|
||||
|
||||
// Privacy indicators notification should not be a popup. It is silently added
|
||||
// to the tray.
|
||||
EXPECT_FALSE(GetPopupNotificationView(notification_id));
|
||||
GetPrimaryUnifiedSystemTray()->ShowBubble();
|
||||
auto* notification_view = static_cast<message_center::NotificationViewBase*>(
|
||||
GetNotificationViewFromMessageCenter(notification_id));
|
||||
EXPECT_TRUE(notification_view);
|
||||
|
||||
// Clicking the first button will trigger launching the app.
|
||||
EXPECT_FALSE(delegate->launch_app_called());
|
||||
ClickView(notification_view, 0);
|
||||
EXPECT_TRUE(delegate->launch_app_called());
|
||||
|
||||
// Clicking the first button will trigger launching the app settings.
|
||||
EXPECT_FALSE(delegate->launch_settings_called());
|
||||
ClickView(notification_view, 1);
|
||||
EXPECT_TRUE(delegate->launch_settings_called());
|
||||
}
|
||||
|
||||
} // namespace ash
|
@ -14,7 +14,6 @@
|
||||
#include "base/strings/utf_string_conversions.h"
|
||||
#include "chrome/browser/apps/app_service/app_service_proxy.h"
|
||||
#include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
|
||||
#include "chrome/browser/ash/profiles/profile_helper.h"
|
||||
#include "chrome/browser/browser_process.h"
|
||||
#include "chrome/browser/profiles/profile_manager.h"
|
||||
#include "components/account_id/account_id.h"
|
||||
@ -36,13 +35,10 @@ apps::AppCapabilityAccessCache* GetAppCapabilityAccessCache(
|
||||
}
|
||||
|
||||
apps::AppRegistryCache* GetActiveUserAppRegistryCache() {
|
||||
auto* manager = user_manager::UserManager::Get();
|
||||
const user_manager::User* active_user = manager->GetActiveUser();
|
||||
if (!active_user)
|
||||
Profile* profile = ProfileManager::GetActiveUserProfile();
|
||||
if (!profile)
|
||||
return nullptr;
|
||||
|
||||
auto account_id = active_user->GetAccountId();
|
||||
Profile* profile = ash::ProfileHelper::Get()->GetProfileByUser(active_user);
|
||||
apps::AppServiceProxy* proxy =
|
||||
apps::AppServiceProxyFactory::GetForProfile(profile);
|
||||
return &proxy->AppRegistryCache();
|
||||
@ -70,6 +66,19 @@ absl::optional<std::u16string> MapAppIdToShortName(
|
||||
return absl::nullopt;
|
||||
}
|
||||
|
||||
void LaunchApp(const std::string& app_id) {
|
||||
// TODO(crbug/1351250): Finish this function.
|
||||
}
|
||||
|
||||
// Launch the native settings page of the app with `app_id`.
|
||||
void LaunchAppSettings(const std::string& app_id) {
|
||||
Profile* profile = ProfileManager::GetActiveUserProfile();
|
||||
if (!profile)
|
||||
return;
|
||||
apps::AppServiceProxyFactory::GetForProfile(profile)->OpenNativeSettings(
|
||||
app_id);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
AppAccessNotifier::AppAccessNotifier() {
|
||||
@ -109,10 +118,15 @@ void AppAccessNotifier::OnCapabilityAccessUpdate(
|
||||
|
||||
if (ash::features::IsPrivacyIndicatorsEnabled()) {
|
||||
auto app_id = update.AppId();
|
||||
|
||||
auto launch_app = base::BindRepeating(&LaunchApp, app_id);
|
||||
auto launch_settings = base::BindRepeating(&LaunchAppSettings, app_id);
|
||||
ash::ModifyPrivacyIndicatorsNotification(
|
||||
app_id,
|
||||
GetAppShortNameFromAppId(app_id, GetActiveUserAppRegistryCache()),
|
||||
camera_is_used, microphone_is_used);
|
||||
camera_is_used, microphone_is_used,
|
||||
base::MakeRefCounted<ash::PrivacyIndicatorsNotificationDelegate>(
|
||||
launch_app, launch_settings));
|
||||
}
|
||||
|
||||
if (microphone_is_used) {
|
||||
|
Reference in New Issue
Block a user