0

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:
Andre Le
2022-08-15 18:16:09 +00:00
committed by Chromium LUCI CQ
parent e1b241cc0a
commit dc8851656a
8 changed files with 243 additions and 14 deletions

@ -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

@ -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) {