0

PrivacyIndicators: Configure notification message and icon color

Privacy indicators notification should show app name in the message.
Thus, we modified AppAccessNotifier and PrivacyIndicatorController to
make this happen. We also add a new color to AshColorProvider that will
be used in the notification as well as the status area indicator.

Bug: 1344681
Change-Id: I0a49e9959130987d42c06e229600d61881e7a26f
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3814159
Reviewed-by: Peter Kasting <pkasting@chromium.org>
Reviewed-by: James Cook <jamescook@chromium.org>
Commit-Queue: Andre Le <leandre@chromium.org>
Reviewed-by: Ahmed Mehfooz <amehfooz@chromium.org>
Reviewed-by: Sean Kau <skau@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1034242}
This commit is contained in:
Andre Le
2022-08-11 23:47:18 +00:00
committed by Chromium LUCI CQ
parent 05af1b42b6
commit e71d527f1c
14 changed files with 131 additions and 21 deletions

@ -3918,6 +3918,18 @@ Here are some things you can try to get started.
<message name="IDS_PRIVACY_NOTIFICATION_TITLE_MIC" desc="Title for a notification shown when an app is using the microphone.">
Microphone in use
</message>
<message name="IDS_PRIVACY_NOTIFICATION_MESSAGE_DEFAULT_APP_NAME" desc="A default value for app name in the message label for a notification shown when an app is using the camera/microphone. This is used when no app name is detected and we need to use a default value">
An app
</message>
<message name="IDS_PRIVACY_NOTIFICATION_MESSAGE_CAMERA_AND_MIC" desc="Message label for a notification shown when an app is using the camera and the microphone.">
<ph name="APP_NAME">$1<ex>Google meet</ex></ph> is currently using your camera and microphone
</message>
<message name="IDS_PRIVACY_NOTIFICATION_MESSAGE_CAMERA" desc="Message label for a notification shown when an app is using the camera.">
<ph name="APP_NAME">$1<ex>Google meet</ex></ph> is currently using your camera
</message>
<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>
<!-- 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 @@
bfce600ac28b2dc9009fe5a7ea5e8e2251f8b782

@ -0,0 +1 @@
f36a9f365ff1aee48b5123904b9b651a05e443b4

@ -0,0 +1 @@
f14a36a2a977cb81ad6cf1e76f5687f4fc498187

@ -0,0 +1 @@
75cce1456a87ff61ca9c45f9eaff537bc02337f0

@ -379,6 +379,10 @@ void AddCrosStylesColorMixer(ui::ColorProvider* provider,
void AddAshColorMixer(ui::ColorProvider* provider,
const ui::ColorProviderManager::Key& key) {
ui::ColorMixer& mixer = provider->AddMixer();
bool dark_mode =
features::IsDarkLightModeEnabled()
? key.color_mode == ui::ColorProviderManager::ColorMode::kDark
: DarkLightModeControllerImpl::Get()->IsDarkModeEnabled();
AddShieldAndBaseColors(mixer, key);
AddControlsColors(mixer, key);
@ -391,6 +395,16 @@ void AddAshColorMixer(ui::ColorProvider* provider,
mixer[ui::kColorAshActionLabelFocusRingHover] =
ui::SetAlpha(cros_tokens::kColorPrimaryDark, 0x60);
// TODO(crbug/1350671): Use cros token for
// kPrivacyIndicatorsBackgroundColor instead of constant values.
if (dark_mode) {
mixer[ui::kColorAshPrivacyIndicatorsBackground] = {
SkColorSetRGB(0x37, 0xBE, 0x5F)};
} else {
mixer[ui::kColorAshPrivacyIndicatorsBackground] = {
SkColorSetRGB(0x14, 0x6C, 0x2E)};
}
mixer[ui::kColorAshAppListFocusRingNoKeyboard] = {SK_AlphaTRANSPARENT};
mixer[ui::kColorAshAppListSeparatorLight] = {
ui::kColorAshSystemUIMenuSeparator};

@ -34,6 +34,7 @@
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/color/color_id.h"
#include "ui/color/color_provider.h"
#include "ui/compositor/animation_throughput_reporter.h"
#include "ui/compositor/layer.h"
#include "ui/compositor/layer_animator.h"
@ -1485,10 +1486,21 @@ SkColor AshNotificationView::CalculateIconAndButtonsColor(
if (!notification)
return default_color;
auto color_id = notification->accent_color_id();
absl::optional<SkColor> accent_color = notification->accent_color();
if (!accent_color.has_value())
if ((!color_id || !GetWidget()) && !accent_color.has_value())
return default_color;
SkColor fg_color;
// ColorProvider needs widget to be created.
if (color_id && GetWidget()) {
fg_color = GetColorProvider()->GetColor(color_id.value());
} else {
fg_color = accent_color.value();
}
// TODO(crbug/1351205): move color calculation logic to color mixer.
// TODO(crbug/1294459): re-evaluate contrast, maybe increase or use fixed HSL
float minContrastRatio =
DarkLightModeControllerImpl::Get()->IsDarkModeEnabled()
@ -1499,7 +1511,7 @@ SkColor AshNotificationView::CalculateIconAndButtonsColor(
SkColor bg_color = AshColorProvider::Get()->GetBaseLayerColor(
AshColorProvider::BaseLayerType::kOpaque);
return color_utils::BlendForMinContrast(
*accent_color, bg_color,
fg_color, bg_color,
/*high_contrast_foreground=*/absl::nullopt, minContrastRatio)
.color;
}

@ -21,9 +21,11 @@ const char kPrivacyIndicatorsNotificationIdPrefix[] = "privacy-indicators";
const char kPrivacyIndicatorsNotifierId[] = "ash.privacy-indicators";
} // namespace
void ModifyPrivacyIndicatorsNotification(const std::string& app_id,
bool camera_is_used,
bool microphone_is_used) {
void ModifyPrivacyIndicatorsNotification(
const std::string& app_id,
absl::optional<std::u16string> app_name,
bool camera_is_used,
bool microphone_is_used) {
auto* message_center = message_center::MessageCenter::Get();
std::string id = kPrivacyIndicatorsNotificationIdPrefix + app_id;
bool notification_exist = message_center->FindVisibleNotificationById(id);
@ -34,14 +36,23 @@ void ModifyPrivacyIndicatorsNotification(const std::string& app_id,
return;
}
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) {
title = l10n_util::GetStringUTF16(
IDS_PRIVACY_NOTIFICATION_TITLE_CAMERA_AND_MIC);
message = l10n_util::GetStringFUTF16(
IDS_PRIVACY_NOTIFICATION_MESSAGE_CAMERA_AND_MIC, app_name_str);
} else if (camera_is_used) {
title = l10n_util::GetStringUTF16(IDS_PRIVACY_NOTIFICATION_TITLE_CAMERA);
message = l10n_util::GetStringFUTF16(
IDS_PRIVACY_NOTIFICATION_MESSAGE_CAMERA, app_name_str);
} else {
title = l10n_util::GetStringUTF16(IDS_PRIVACY_NOTIFICATION_TITLE_MIC);
message = l10n_util::GetStringFUTF16(IDS_PRIVACY_NOTIFICATION_MESSAGE_MIC,
app_name_str);
}
message_center::RichNotificationData optional_fields;
@ -51,7 +62,7 @@ void ModifyPrivacyIndicatorsNotification(const std::string& app_id,
auto notification = CreateSystemNotification(
message_center::NotificationType::NOTIFICATION_TYPE_SIMPLE, id, title,
std::u16string(),
message,
/*display_source=*/std::u16string(),
/*origin_url=*/GURL(),
message_center::NotifierId(message_center::NotifierType::SYSTEM_COMPONENT,
@ -61,6 +72,8 @@ void ModifyPrivacyIndicatorsNotification(const std::string& app_id,
/*delegate=*/nullptr, kImeMenuMicrophoneIcon,
message_center::SystemNotificationWarningLevel::NORMAL);
notification->set_accent_color_id(ui::kColorAshPrivacyIndicatorsBackground);
if (notification_exist) {
message_center->UpdateNotification(id, std::move(notification));
return;

@ -8,14 +8,17 @@
#include <string>
#include "ash/ash_export.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
namespace ash {
// Add, update, or remove the privacy notification associated with the given
// `app_id`.
void ASH_EXPORT ModifyPrivacyIndicatorsNotification(const std::string& app_id,
bool camera_is_used,
bool microphone_is_used);
void ASH_EXPORT
ModifyPrivacyIndicatorsNotification(const std::string& app_id,
absl::optional<std::u16string> app_name,
bool camera_is_used,
bool microphone_is_used);
} // namespace ash

@ -96,7 +96,7 @@ absl::optional<std::u16string> AppAccessNotifier::GetAppAccessingMicrophone() {
// in that case instead of using DCHECK().
if (!reg_cache || !cap_cache)
return absl::nullopt;
return GetAppAccessingMicrophone(cap_cache, reg_cache);
return GetMostRecentAppAccessingMicrophone(cap_cache, reg_cache);
}
void AppAccessNotifier::OnCapabilityAccessUpdate(
@ -108,8 +108,11 @@ void AppAccessNotifier::OnCapabilityAccessUpdate(
bool camera_is_used = update.Camera() == apps::mojom::OptionalBool::kTrue;
if (ash::features::IsPrivacyIndicatorsEnabled()) {
ash::ModifyPrivacyIndicatorsNotification(update.AppId(), camera_is_used,
microphone_is_used);
auto app_id = update.AppId();
ash::ModifyPrivacyIndicatorsNotification(
app_id,
GetAppShortNameFromAppId(app_id, GetActiveUserAppRegistryCache()),
camera_is_used, microphone_is_used);
}
if (microphone_is_used) {
@ -147,6 +150,21 @@ void AppAccessNotifier::ActiveUserChanged(user_manager::User* active_user) {
CheckActiveUserChanged();
}
// static
absl::optional<std::u16string> AppAccessNotifier::GetAppShortNameFromAppId(
std::string app_id,
apps::AppRegistryCache* registry_cache) {
absl::optional<std::u16string> name;
if (!registry_cache)
return name;
registry_cache->ForEachApp([&app_id, &name](const apps::AppUpdate& update) {
if (update.AppId() == app_id)
name = base::UTF8ToUTF16(update.ShortName());
});
return name;
}
AccountId AppAccessNotifier::GetActiveUserAccountId() {
auto* manager = user_manager::UserManager::Get();
const user_manager::User* active_user = manager->GetActiveUser();
@ -178,7 +196,8 @@ AppAccessNotifier::GetActiveUserAppCapabilityAccessCache() {
return GetAppCapabilityAccessCache(GetActiveUserAccountId());
}
absl::optional<std::u16string> AppAccessNotifier::GetAppAccessingMicrophone(
absl::optional<std::u16string>
AppAccessNotifier::GetMostRecentAppAccessingMicrophone(
apps::AppCapabilityAccessCache* capability_cache,
apps::AppRegistryCache* registry_cache) {
if (mic_using_app_ids[active_user_account_id_].empty())

@ -44,12 +44,6 @@ class AppAccessNotifier
// ash::MicrophoneMuteNotificationDelegate
absl::optional<std::u16string> GetAppAccessingMicrophone() override;
// Returns the "short name" of the registered app to most recently attempt to
// access the microphone, or an empty (optional) string if none exists.
absl::optional<std::u16string> GetAppAccessingMicrophone(
apps::AppCapabilityAccessCache* capability_cache,
apps::AppRegistryCache* registry_cache);
// apps::AppCapabilityAccessCache::Observer
void OnCapabilityAccessUpdate(
const apps::CapabilityAccessUpdate& update) override;
@ -62,6 +56,11 @@ class AppAccessNotifier
// user_manager::UserManager::UserSessionStateObserver
void ActiveUserChanged(user_manager::User* active_user) override;
// Get the app short name of the app with `app_id`.
static absl::optional<std::u16string> GetAppShortNameFromAppId(
std::string app_id,
apps::AppRegistryCache* registry_cache);
protected:
// Returns the active user's account ID if we have an active user, an empty
// account ID otherwise.
@ -73,10 +72,19 @@ class AppAccessNotifier
void CheckActiveUserChanged();
private:
friend class AppAccessNotifierTest;
// Returns the AppCapabilityAccessCache associated with the active user's
// account ID.
apps::AppCapabilityAccessCache* GetActiveUserAppCapabilityAccessCache();
// Returns the "short name" of the registered app to most recently attempt to
// access the microphone, or an empty (optional) string if none exists. Used
// for the microphone mute notification.
absl::optional<std::u16string> GetMostRecentAppAccessingMicrophone(
apps::AppCapabilityAccessCache* capability_cache,
apps::AppRegistryCache* registry_cache);
// List of IDs of apps that have attempted to use the microphone, in order of
// most-recently-launched.
using MruAppIdList = std::list<std::string>;

@ -110,8 +110,8 @@ class AppAccessNotifierTest : public testing::Test,
account_id_primary_user_)
? &registry_cache_primary_user_
: &registry_cache_secondary_user_;
return microphone_mute_notification_delegate_->GetAppAccessingMicrophone(
cap_cache, reg_cache);
return microphone_mute_notification_delegate_
->GetMostRecentAppAccessingMicrophone(cap_cache, reg_cache);
}
static apps::AppPtr MakeApp(const std::string app_id, const char* name) {
@ -395,3 +395,13 @@ TEST_P(AppAccessNotifierTest, AppAccessNotification) {
EXPECT_TRUE(message_center::MessageCenter::Get()->FindNotificationById(
kPrivacyIndicatorsNotificationIdPrefix + id1));
}
TEST_P(AppAccessNotifierTest, GetShortNameFromAppId) {
// Test that GetAppShortNameFromAppId works properly.
const std::string id = "test_app_id";
LaunchAppUsingCameraOrMicrophone(id, "test_app_name", /*use_camera=*/false,
/*use_microphone=*/true);
EXPECT_EQ(AppAccessNotifier::GetAppShortNameFromAppId(
id, &registry_cache_primary_user_),
u"test_app_name");
}

@ -210,6 +210,8 @@
\
E_CPONLY(kColorAshOnboardingFocusRing) \
\
E_CPONLY(kColorAshPrivacyIndicatorsBackground) \
\
/* TODO(crbug/1319917): Remove these when dark light mode is launched. */ \
E_CPONLY(kColorAshSystemUILightBorderColor1) \
E_CPONLY(kColorAshSystemUILightBorderColor2) \

@ -185,6 +185,12 @@ class MESSAGE_CENTER_PUBLIC_EXPORT RichNotificationData {
// SystemNotificationWarningLevel should be used.
absl::optional<SkColor> accent_color;
// Similar to `accent_color`, but store a ColorId instead of SkColor so that
// the notification view can use this id to correctly handle theme change. In
// CrOS notification, if `accent_color_id` is provided, `accent_color` will
// not be used.
absl::optional<ui::ColorId> accent_color_id;
// Controls whether a settings button should appear on the notification. See
// enum definition. TODO(estade): turn this into a boolean. See
// crbug.com/780342
@ -441,6 +447,13 @@ class MESSAGE_CENTER_PUBLIC_EXPORT Notification {
optional_fields_.accent_color = accent_color;
}
absl::optional<ui::ColorId> accent_color_id() const {
return optional_fields_.accent_color_id;
}
void set_accent_color_id(ui::ColorId accent_color_id) {
optional_fields_.accent_color_id = accent_color_id;
}
bool should_show_settings_button() const {
return optional_fields_.settings_button_handler !=
SettingsButtonHandler::NONE;