Skip to content

Commit

Permalink
chromeos: Microphone mute notifications
Browse files Browse the repository at this point in the history
Show a notification if an app is trying to access the microphone while
the microphone is muted.

Notification logic resides in MicrophoneMutNotificationController.

Logic for determining whether a microphone-using app is running (and
the name of said app) resides in MediaNotificationModel, an abstract,
singleton-providing interface under ash/public/cpp/ with a
concrete implementation under chrome/browser/ui/ash/.

Notification strings added to ash_strings.grd.

Guard notifications with base::Feature kMicMuteNotifications.

Add method to the FakeCrasAudioClient API for setting
client-type/number-of-active-input-streams-with-permission map, for
testing.

In MicGainSliderView::OnThemeChanged(), call into the superclass at
the top.  This change courtesy of tbarzic, which fixes unit test
failures from Chromium LUCI CQ.

Bug: 1201371
Change-Id: Ic89d117ba788e411cf0275adfdf4379a6e1907fd
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2860331
Commit-Queue: Roger Tinkoff <rtinkoff@google.com>
Reviewed-by: Ahmed Mehfooz <amehfooz@chromium.org>
Reviewed-by: James Cook <jamescook@chromium.org>
Reviewed-by: Toni Baržić <tbarzic@chromium.org>
Cr-Commit-Position: refs/heads/master@{#882580}
  • Loading branch information
rogertinkoff authored and Chromium LUCI CQ committed May 13, 2021
1 parent f3c449b commit 4a008a2
Show file tree
Hide file tree
Showing 24 changed files with 717 additions and 5 deletions.
3 changes: 3 additions & 0 deletions ash/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -1109,6 +1109,8 @@ component("ash") {
"system/message_center/unified_message_center_view.h",
"system/message_center/unified_message_list_view.cc",
"system/message_center/unified_message_list_view.h",
"system/microphone_mute/microphone_mute_notification_controller.cc",
"system/microphone_mute/microphone_mute_notification_controller.h",
"system/model/clock_model.cc",
"system/model/clock_model.h",
"system/model/clock_observer.h",
Expand Down Expand Up @@ -2259,6 +2261,7 @@ test("ash_unittests") {
"system/message_center/unified_message_center_bubble_unittest.cc",
"system/message_center/unified_message_center_view_unittest.cc",
"system/message_center/unified_message_list_view_unittest.cc",
"system/microphone_mute/microphone_mute_notification_controller_unittest.cc",
"system/nearby_share/nearby_share_feature_pod_controller_unittest.cc",
"system/network/active_network_icon_unittest.cc",
"system/network/auto_connect_notifier_unittest.cc",
Expand Down
11 changes: 11 additions & 0 deletions ash/ash_strings.grd
Original file line number Diff line number Diff line change
Expand Up @@ -3076,6 +3076,17 @@ Here are some things you can try to get started.
An application is using your microphone
</message>

<!-- Strings for microphone mute switch notification -->
<message name="IDS_MICROPHONE_MUTE_SWITCH_ON_NOTIFICATION_TITLE" desc="Title for a notification shown to the users when an app tries to use the microphone while the microphone mute switch, which disables microphone input, is turned on">
Microphone is off
</message>
<message name="IDS_MICROPHONE_MUTE_SWITCH_ON_NOTIFICATION_MESSAGE" desc="Message for a notification shown to the users when an app tries to use the microphone while the microphone mute switch, which disables microphone input, is turned on">
An app is trying to access the microphone. Turn off the microphone mute switch to allow access.
</message>
<message name="IDS_MICROPHONE_MUTE_SWITCH_ON_NOTIFICATION_MESSAGE_WITH_APP_NAME" desc="Message for a notification shown to the users when an app tries to use the microphone while the microphone mute switch, which disables microphone input, is turned on">
<ph name="APP_NAME">$1<ex>Parallels Desktop</ex></ph> is trying to access the microphone. Turn off the microphone mute switch to allow access.
</message>

<message name="IDS_ASH_MESSAGE_CENTER_UNLOCK_TO_PERFORM_ACTION" desc="The short message to encourage user to unlock the device so that Chrome OS can perform the notification action selected by user after unlocking.">
Unlock device to perform the notification action
</message>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
9c446eb6a1dad02fe31d366910b57b4942c684f7
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
e7b83d22230b704fa74103296c0151d9b263be81
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
e7b83d22230b704fa74103296c0151d9b263be81
9 changes: 9 additions & 0 deletions ash/constants/ash_features.cc
Original file line number Diff line number Diff line change
Expand Up @@ -474,6 +474,11 @@ const base::Feature kLacrosSupport{"LacrosSupport",
const base::Feature kLanguageSettingsUpdate2{"LanguageSettingsUpdate2",
base::FEATURE_DISABLED_BY_DEFAULT};

// Enables notification of when a microphone-using app is launched while the
// microphone is muted.
const base::Feature kMicMuteNotifications{"MicMuteNotifications",
base::FEATURE_DISABLED_BY_DEFAULT};

// Controls whether to enable the requirement of a minimum chrome version on the
// device through the policy DeviceMinimumVersion. If the requirement is
// not met and the warning time in the policy has expired, the user is
Expand Down Expand Up @@ -853,6 +858,10 @@ bool IsKerberosSettingsSectionEnabled() {
return base::FeatureList::IsEnabled(kKerberosSettingsSection);
}

bool IsMicMuteNotificationsEnabled() {
return base::FeatureList::IsEnabled(kMicMuteNotifications);
}

bool IsMinimumChromeVersionEnabled() {
return base::FeatureList::IsEnabled(kMinimumChromeVersion);
}
Expand Down
3 changes: 3 additions & 0 deletions ash/constants/ash_features.h
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,8 @@ extern const base::Feature kMediaAppHandlesPdf;
COMPONENT_EXPORT(ASH_CONSTANTS)
extern const base::Feature kMediaAppVideoControls;
COMPONENT_EXPORT(ASH_CONSTANTS)
extern const base::Feature kMicMuteNotifications;
COMPONENT_EXPORT(ASH_CONSTANTS)
extern const base::Feature kMinimumChromeVersion;
COMPONENT_EXPORT(ASH_CONSTANTS)
extern const base::Feature kMultilingualTyping;
Expand Down Expand Up @@ -373,6 +375,7 @@ COMPONENT_EXPORT(ASH_CONSTANTS) bool IsImeSandboxEnabled();
COMPONENT_EXPORT(ASH_CONSTANTS)
bool IsInstantTetheringBackgroundAdvertisingSupported();
COMPONENT_EXPORT(ASH_CONSTANTS) bool IsKerberosSettingsSectionEnabled();
COMPONENT_EXPORT(ASH_CONSTANTS) bool IsMicMuteNotificationsEnabled();
COMPONENT_EXPORT(ASH_CONSTANTS) bool IsMinimumChromeVersionEnabled();
COMPONENT_EXPORT(ASH_CONSTANTS) bool IsNetworkingInDiagnosticsAppEnabled();
COMPONENT_EXPORT(ASH_CONSTANTS) bool IsNewOobeLayoutEnabled();
Expand Down
2 changes: 2 additions & 0 deletions ash/public/cpp/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,8 @@ component("cpp") {
"message_center/arc_notifications_host_initializer.h",
"metrics_util.cc",
"metrics_util.h",
"microphone_mute_notification_delegate.cc",
"microphone_mute_notification_delegate.h",
"move_to_desks_menu_delegate.cc",
"move_to_desks_menu_delegate.h",
"nearby_share_controller.h",
Expand Down
33 changes: 33 additions & 0 deletions ash/public/cpp/microphone_mute_notification_delegate.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Copyright 2021 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/public/cpp/microphone_mute_notification_delegate.h"

#include "base/check.h"
#include "base/check_op.h"

namespace ash {

namespace {

MicrophoneMuteNotificationDelegate* g_instance = nullptr;

} // namespace

// static
MicrophoneMuteNotificationDelegate* MicrophoneMuteNotificationDelegate::Get() {
return g_instance;
}

MicrophoneMuteNotificationDelegate::MicrophoneMuteNotificationDelegate() {
DCHECK(!g_instance);
g_instance = this;
}

MicrophoneMuteNotificationDelegate::~MicrophoneMuteNotificationDelegate() {
DCHECK_EQ(this, g_instance);
g_instance = nullptr;
}

} // namespace ash
37 changes: 37 additions & 0 deletions ash/public/cpp/microphone_mute_notification_delegate.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Copyright 2021 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.

#ifndef ASH_PUBLIC_CPP_MICROPHONE_MUTE_NOTIFICATION_DELEGATE_H_
#define ASH_PUBLIC_CPP_MICROPHONE_MUTE_NOTIFICATION_DELEGATE_H_

#include <string>

#include "ash/public/cpp/ash_public_export.h"
#include "base/optional.h"

namespace ash {

// This delegate exists so that code relevant to microphone mute notifications
// under //ash can call back into //chrome. The actual delegate instance is
// owned and constructed by code in //chrome during startup.
class ASH_PUBLIC_EXPORT MicrophoneMuteNotificationDelegate {
public:
static MicrophoneMuteNotificationDelegate* Get();

// Returns an optional string with:
//
// No value, if no app is accessing the mic
// Empty value, if an app is accessing the mic but no name could be determined
// Non-empty value, if an app is accessing the mic and a name could be
// determined
virtual base::Optional<std::u16string> GetAppAccessingMicrophone() = 0;

protected:
MicrophoneMuteNotificationDelegate();
virtual ~MicrophoneMuteNotificationDelegate();
};

} // namespace ash

#endif // ASH_PUBLIC_CPP_MICROPHONE_MUTE_NOTIFICATION_DELEGATE_H_
105 changes: 105 additions & 0 deletions ash/system/microphone_mute/microphone_mute_notification_controller.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
// Copyright 2021 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/microphone_mute/microphone_mute_notification_controller.h"

#include <cstdint>
#include <string>

#include "ash/public/cpp/microphone_mute_notification_delegate.h"
#include "ash/public/cpp/notification_utils.h"
#include "ash/strings/grit/ash_strings.h"
#include "base/strings/utf_string_conversions.h"
#include "components/vector_icons/vector_icons.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/message_center/message_center.h"
#include "ui/message_center/public/cpp/notification.h"

namespace ash {

// static
const char MicrophoneMuteNotificationController::kNotificationId[] =
"ash://microphone_mute";

MicrophoneMuteNotificationController::MicrophoneMuteNotificationController() {
audio_observation_.Observe(CrasAudioHandler::Get());
}

MicrophoneMuteNotificationController::~MicrophoneMuteNotificationController() =
default;

void MicrophoneMuteNotificationController::OnInputMuteChanged(bool mute_on) {
// Catches the case where the mic is muted while a mic-using app is running.
mic_mute_on_ = mute_on;
MaybeShowNotification();
}

void MicrophoneMuteNotificationController::MaybeShowNotification() {
if (mic_mute_on_) {
base::Optional<std::u16string> app_name =
MicrophoneMuteNotificationDelegate::Get()->GetAppAccessingMicrophone();
if (app_name.has_value() || HaveActiveInputStreams()) {
GenerateMicrophoneMuteNotification(app_name);
return;
}
}

RemoveMicrophoneMuteNotification();
}

void MicrophoneMuteNotificationController::GenerateMicrophoneMuteNotification(
const base::Optional<std::u16string>& app_name) {
std::unique_ptr<message_center::Notification> notification =
CreateSystemNotification(
message_center::NOTIFICATION_TYPE_SIMPLE, kNotificationId,
GetNotificationTitle(), GetNotificationMessage(app_name),
/*display_source=*/std::u16string(), GURL(),
message_center::NotifierId(
message_center::NotifierType::SYSTEM_COMPONENT, kNotificationId),
message_center::RichNotificationData(), nullptr,
vector_icons::kSettingsIcon,
message_center::SystemNotificationWarningLevel::NORMAL);

message_center::MessageCenter::Get()->AddNotification(
std::move(notification));
}

std::u16string MicrophoneMuteNotificationController::GetNotificationMessage(
const base::Optional<std::u16string>& app_name) const {
return !app_name.value_or(u"").empty()
? l10n_util::GetStringFUTF16(
IDS_MICROPHONE_MUTE_SWITCH_ON_NOTIFICATION_MESSAGE_WITH_APP_NAME,
app_name.value())
: l10n_util::GetStringUTF16(
IDS_MICROPHONE_MUTE_SWITCH_ON_NOTIFICATION_MESSAGE);
}

std::u16string MicrophoneMuteNotificationController::GetNotificationTitle()
const {
return l10n_util::GetStringUTF16(
IDS_MICROPHONE_MUTE_SWITCH_ON_NOTIFICATION_TITLE);
}

void MicrophoneMuteNotificationController::RemoveMicrophoneMuteNotification() {
message_center::MessageCenter::Get()->RemoveNotification(kNotificationId,
/*by_user=*/false);
}

bool MicrophoneMuteNotificationController::HaveActiveInputStreams() {
base::flat_map<CrasAudioHandler::ClientType, uint32_t> input_streams =
CrasAudioHandler::Get()->GetNumberOfInputStreamsWithPermission();
for (auto& per_client_type_count : input_streams) {
if (per_client_type_count.second > 0)
return true;
}
return false;
}

void MicrophoneMuteNotificationController::
OnNumberOfInputStreamsWithPermissionChanged() {
// Catches the case where a mic-using app is launched while the mic is muted.
MaybeShowNotification();
}

} // namespace ash
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// Copyright 2021 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.

#ifndef ASH_SYSTEM_MICROPHONE_MUTE_MICROPHONE_MUTE_NOTIFICATION_CONTROLLER_H_
#define ASH_SYSTEM_MICROPHONE_MUTE_MICROPHONE_MUTE_NOTIFICATION_CONTROLLER_H_

#include <string>

#include "ash/ash_export.h"
#include "ash/components/audio/cras_audio_handler.h"
#include "base/macros.h"
#include "base/memory/weak_ptr.h"
#include "base/scoped_observation.h"

namespace ash {

// Controller class to manage microphone mute notifications. This
// notification shows up when the user launches an app that uses the microphone
// while the microphone is muted.
class ASH_EXPORT MicrophoneMuteNotificationController
: public ash::CrasAudioHandler::AudioObserver {
public:
MicrophoneMuteNotificationController();
MicrophoneMuteNotificationController(
const MicrophoneMuteNotificationController&) = delete;
MicrophoneMuteNotificationController& operator=(
const MicrophoneMuteNotificationController&) = delete;
~MicrophoneMuteNotificationController() override;

// Shows the microphone muted notification if it needs to be shown.
void MaybeShowNotification();

// ash::CrasAudioHandler::AudioObserver:
void OnInputMuteChanged(bool mute_on) override;
void OnNumberOfInputStreamsWithPermissionChanged() override;

private:
friend class MicrophoneMuteNotificationControllerTest;

// Creates a notification for telling the user they're attempting to use the
// mic while the mis is muted.
void GenerateMicrophoneMuteNotification(
const base::Optional<std::u16string>& app_name);

// Mic mute notification title.
std::u16string GetNotificationTitle() const;

// Mic mute notification body.
std::u16string GetNotificationMessage(
const base::Optional<std::u16string>& app_name) const;

// Takes down the mic mute notification.
void RemoveMicrophoneMuteNotification();

// Returns true if we have any active input stream with permission, of any
// client type. See
// ash::CrasAudioClient::NumberOfInputStreamsWithPermissionChanged() for more
// details.
bool HaveActiveInputStreams();

static const char kNotificationId[];

// A value of true means the mic is muted.
bool mic_mute_on_ = false;

base::ScopedObservation<ash::CrasAudioHandler,
AudioObserver,
&ash::CrasAudioHandler::AddAudioObserver,
&ash::CrasAudioHandler::RemoveAudioObserver>
audio_observation_{this};

base::WeakPtrFactory<MicrophoneMuteNotificationController> weak_ptr_factory_{
this};
};

} // namespace ash

#endif // ASH_SYSTEM_MICROPHONE_MUTE_MICROPHONE_MUTE_NOTIFICATION_CONTROLLER_H_
Loading

0 comments on commit 4a008a2

Please sign in to comment.