Commit a1a1eb0c authored by yoshiki iguchi's avatar yoshiki iguchi Committed by Commit Bot

Sync DnD (Do-not-Disturb) status with Android

This CL adds a feature to sync the DnD status between Chrome and Android.
- When the DnD status is changed on Android side, set it to Chrome-side.
- When the DnD status is changed on Chrome side, set it to Android-side.

Bug: b/79951342
Test: Ran added tests
Change-Id: I3eba63420a91de60e7baf333dfb4dd7c1da74310
Reviewed-on: https://chromium-review.googlesource.com/1111495
Commit-Queue: Yoshiki Iguchi <yoshiki@chromium.org>
Reviewed-by: default avatarYusuke Sato <yusukes@chromium.org>
Reviewed-by: default avatarGreg Kerr <kerrnel@chromium.org>
Reviewed-by: default avatarEliot Courtney <edcourtney@chromium.org>
Cr-Commit-Position: refs/heads/master@{#570357}
parent e1a24b09
...@@ -14,6 +14,7 @@ ...@@ -14,6 +14,7 @@
#include "base/stl_util.h" #include "base/stl_util.h"
#include "base/strings/utf_string_conversions.h" #include "base/strings/utf_string_conversions.h"
#include "components/arc/mojo_channel.h" #include "components/arc/mojo_channel.h"
#include "ui/message_center/message_center_observer.h"
#include "ui/message_center/views/message_view_factory.h" #include "ui/message_center/views/message_view_factory.h"
using arc::ConnectionHolder; using arc::ConnectionHolder;
...@@ -22,6 +23,8 @@ using arc::mojom::ArcNotificationData; ...@@ -22,6 +23,8 @@ using arc::mojom::ArcNotificationData;
using arc::mojom::ArcNotificationDataPtr; using arc::mojom::ArcNotificationDataPtr;
using arc::mojom::ArcNotificationEvent; using arc::mojom::ArcNotificationEvent;
using arc::mojom::ArcNotificationPriority; using arc::mojom::ArcNotificationPriority;
using arc::mojom::ArcDoNotDisturbStatus;
using arc::mojom::ArcDoNotDisturbStatusPtr;
using arc::mojom::NotificationsHost; using arc::mojom::NotificationsHost;
using arc::mojom::NotificationsInstance; using arc::mojom::NotificationsInstance;
using arc::mojom::NotificationsInstancePtr; using arc::mojom::NotificationsInstancePtr;
...@@ -40,6 +43,19 @@ std::unique_ptr<message_center::MessageView> CreateCustomMessageView( ...@@ -40,6 +43,19 @@ std::unique_ptr<message_center::MessageView> CreateCustomMessageView(
return arc_delegate->CreateCustomMessageView(notification); return arc_delegate->CreateCustomMessageView(notification);
} }
class DoNotDisturbManager : public message_center::MessageCenterObserver {
public:
DoNotDisturbManager(ArcNotificationManager* manager) : manager_(manager) {}
void OnQuietModeChanged(bool in_quiet_mode) override {
manager_->SetDoNotDisturbStatusOnAndroid(in_quiet_mode);
}
private:
ArcNotificationManager* const manager_;
DISALLOW_COPY_AND_ASSIGN(DoNotDisturbManager);
};
} // namespace } // namespace
class ArcNotificationManager::InstanceOwner { class ArcNotificationManager::InstanceOwner {
...@@ -87,14 +103,21 @@ ArcNotificationManager::ArcNotificationManager( ...@@ -87,14 +103,21 @@ ArcNotificationManager::ArcNotificationManager(
: delegate_(std::move(delegate)), : delegate_(std::move(delegate)),
main_profile_id_(main_profile_id), main_profile_id_(main_profile_id),
message_center_(message_center), message_center_(message_center),
do_not_disturb_manager_(new DoNotDisturbManager(this)),
instance_owner_(std::make_unique<InstanceOwner>()) { instance_owner_(std::make_unique<InstanceOwner>()) {
DCHECK(message_center_);
instance_owner_->holder()->SetHost(this); instance_owner_->holder()->SetHost(this);
instance_owner_->holder()->AddObserver(this); instance_owner_->holder()->AddObserver(this);
if (!message_center::MessageViewFactory::HasCustomNotificationViewFactory()) if (!message_center::MessageViewFactory::HasCustomNotificationViewFactory())
SetCustomNotificationViewFactory(); SetCustomNotificationViewFactory();
message_center_->AddObserver(do_not_disturb_manager_.get());
} }
ArcNotificationManager::~ArcNotificationManager() { ArcNotificationManager::~ArcNotificationManager() {
message_center_->RemoveObserver(do_not_disturb_manager_.get());
instance_owner_->holder()->RemoveObserver(this); instance_owner_->holder()->RemoveObserver(this);
instance_owner_->holder()->SetHost(nullptr); instance_owner_->holder()->SetHost(nullptr);
...@@ -113,8 +136,12 @@ ArcNotificationManager::GetConnectionHolderForTest() { ...@@ -113,8 +136,12 @@ ArcNotificationManager::GetConnectionHolderForTest() {
void ArcNotificationManager::OnConnectionReady() { void ArcNotificationManager::OnConnectionReady() {
DCHECK(!ready_); DCHECK(!ready_);
// TODO(hidehiko): Replace this by ConnectionHolder::IsConnected(). // TODO(hidehiko): Replace this by ConnectionHolder::IsConnected().
ready_ = true; ready_ = true;
// Sync the initial quiet mode state with Android.
SetDoNotDisturbStatusOnAndroid(message_center_->IsQuietMode());
} }
void ArcNotificationManager::OnConnectionClosed() { void ArcNotificationManager::OnConnectionClosed() {
...@@ -429,4 +456,34 @@ void ArcNotificationManager::OnGotAppId(ArcNotificationDataPtr data, ...@@ -429,4 +456,34 @@ void ArcNotificationManager::OnGotAppId(ArcNotificationDataPtr data,
it->second->OnUpdatedFromAndroid(std::move(data), app_id); it->second->OnUpdatedFromAndroid(std::move(data), app_id);
} }
void ArcNotificationManager::OnDoNotDisturbStatusUpdated(
ArcDoNotDisturbStatusPtr status) {
// Remove the observer to prevent from sending the command to Android since
// this request came from Android.
message_center_->RemoveObserver(do_not_disturb_manager_.get());
if (message_center_->IsQuietMode() != status->enabled)
message_center_->SetQuietMode(status->enabled);
// Add back the observer.
message_center_->AddObserver(do_not_disturb_manager_.get());
}
void ArcNotificationManager::SetDoNotDisturbStatusOnAndroid(bool enabled) {
auto* notifications_instance = ARC_GET_INSTANCE_FOR_METHOD(
instance_owner_->holder(), SetDoNotDisturbStatusOnAndroid);
// On shutdown, the ARC channel may quit earlier than notifications.
if (!notifications_instance) {
VLOG(2) << "Trying to send the Do Not Disturb status (" << enabled
<< "), but the ARC channel has already gone.";
return;
}
ArcDoNotDisturbStatusPtr status = ArcDoNotDisturbStatus::New();
status->enabled = enabled;
notifications_instance->SetDoNotDisturbStatusOnAndroid(std::move(status));
}
} // namespace ash } // namespace ash
...@@ -51,6 +51,8 @@ class ArcNotificationManager ...@@ -51,6 +51,8 @@ class ArcNotificationManager
void OnNotificationUpdated(arc::mojom::ArcNotificationDataPtr data) override; void OnNotificationUpdated(arc::mojom::ArcNotificationDataPtr data) override;
void OnNotificationRemoved(const std::string& key) override; void OnNotificationRemoved(const std::string& key) override;
void OpenMessageCenter() override; void OpenMessageCenter() override;
void OnDoNotDisturbStatusUpdated(
arc::mojom::ArcDoNotDisturbStatusPtr status) override;
// Methods called from ArcNotificationItem: // Methods called from ArcNotificationItem:
void SendNotificationRemovedFromChrome(const std::string& key); void SendNotificationRemovedFromChrome(const std::string& key);
...@@ -63,6 +65,7 @@ class ArcNotificationManager ...@@ -63,6 +65,7 @@ class ArcNotificationManager
void OpenNotificationSnoozeSettings(const std::string& key); void OpenNotificationSnoozeSettings(const std::string& key);
bool IsOpeningSettingsSupported() const; bool IsOpeningSettingsSupported() const;
void SendNotificationToggleExpansionOnChrome(const std::string& key); void SendNotificationToggleExpansionOnChrome(const std::string& key);
void SetDoNotDisturbStatusOnAndroid(bool enabled);
private: private:
// Helper class to own MojoChannel and ConnectionHolder. // Helper class to own MojoChannel and ConnectionHolder.
...@@ -77,6 +80,8 @@ class ArcNotificationManager ...@@ -77,6 +80,8 @@ class ArcNotificationManager
std::unique_ptr<ArcNotificationManagerDelegate> delegate_; std::unique_ptr<ArcNotificationManagerDelegate> delegate_;
const AccountId main_profile_id_; const AccountId main_profile_id_;
message_center::MessageCenter* const message_center_; message_center::MessageCenter* const message_center_;
const std::unique_ptr<message_center::MessageCenterObserver>
do_not_disturb_manager_;
using ItemMap = using ItemMap =
std::unordered_map<std::string, std::unique_ptr<ArcNotificationItem>>; std::unordered_map<std::string, std::unique_ptr<ArcNotificationItem>>;
......
...@@ -19,6 +19,7 @@ ...@@ -19,6 +19,7 @@
#include "mojo/public/cpp/bindings/interface_ptr.h" #include "mojo/public/cpp/bindings/interface_ptr.h"
#include "testing/gtest/include/gtest/gtest.h" #include "testing/gtest/include/gtest/gtest.h"
#include "ui/message_center/fake_message_center.h" #include "ui/message_center/fake_message_center.h"
#include "ui/message_center/message_center_observer.h"
namespace ash { namespace ash {
...@@ -52,10 +53,21 @@ class MockMessageCenter : public message_center::FakeMessageCenter { ...@@ -52,10 +53,21 @@ class MockMessageCenter : public message_center::FakeMessageCenter {
return visible_notifications_; return visible_notifications_;
} }
void SetQuietMode(bool in_quiet_mode) override {
if (in_quiet_mode != in_quiet_mode_) {
in_quiet_mode_ = in_quiet_mode;
for (auto& observer : observer_list())
observer.OnQuietModeChanged(in_quiet_mode);
}
}
bool IsQuietMode() const override { return in_quiet_mode_; }
private: private:
message_center::NotificationList::Notifications visible_notifications_; message_center::NotificationList::Notifications visible_notifications_;
std::map<std::string, std::unique_ptr<message_center::Notification>> std::map<std::string, std::unique_ptr<message_center::Notification>>
owned_notifications_; owned_notifications_;
bool in_quiet_mode_ = false;
DISALLOW_COPY_AND_ASSIGN(MockMessageCenter); DISALLOW_COPY_AND_ASSIGN(MockMessageCenter);
}; };
...@@ -112,16 +124,7 @@ class ArcNotificationManagerTest : public testing::Test { ...@@ -112,16 +124,7 @@ class ArcNotificationManagerTest : public testing::Test {
void FlushInstanceCall() { binding_->FlushForTesting(); } void FlushInstanceCall() { binding_->FlushForTesting(); }
private: void ConnectMojoChannel() {
void SetUp() override {
arc_notifications_instance_ =
std::make_unique<arc::FakeNotificationsInstance>();
message_center_ = std::make_unique<MockMessageCenter>();
arc_notification_manager_ = std::make_unique<ArcNotificationManager>(
std::make_unique<FakeArcNotificationManagerDelegate>(),
EmptyAccountId(), message_center_.get());
binding_ = binding_ =
std::make_unique<mojo::Binding<arc::mojom::NotificationsInstance>>( std::make_unique<mojo::Binding<arc::mojom::NotificationsInstance>>(
arc_notifications_instance_.get()); arc_notifications_instance_.get());
...@@ -133,6 +136,17 @@ class ArcNotificationManagerTest : public testing::Test { ...@@ -133,6 +136,17 @@ class ArcNotificationManagerTest : public testing::Test {
arc_notification_manager_->GetConnectionHolderForTest()); arc_notification_manager_->GetConnectionHolderForTest());
} }
private:
void SetUp() override {
arc_notifications_instance_ =
std::make_unique<arc::FakeNotificationsInstance>();
message_center_ = std::make_unique<MockMessageCenter>();
arc_notification_manager_ = std::make_unique<ArcNotificationManager>(
std::make_unique<FakeArcNotificationManagerDelegate>(),
EmptyAccountId(), message_center_.get());
}
void TearDown() override { void TearDown() override {
arc_notification_manager_.reset(); arc_notification_manager_.reset();
message_center_.reset(); message_center_.reset();
...@@ -161,10 +175,11 @@ TEST_F(ArcNotificationManagerTest, NotificationCreatedAndRemoved) { ...@@ -161,10 +175,11 @@ TEST_F(ArcNotificationManagerTest, NotificationCreatedAndRemoved) {
} }
TEST_F(ArcNotificationManagerTest, NotificationRemovedByChrome) { TEST_F(ArcNotificationManagerTest, NotificationRemovedByChrome) {
ConnectMojoChannel();
EXPECT_EQ(0u, message_center()->GetVisibleNotifications().size()); EXPECT_EQ(0u, message_center()->GetVisibleNotifications().size());
std::string key = CreateNotification(); std::string key = CreateNotification();
EXPECT_EQ(1u, message_center()->GetVisibleNotifications().size()); EXPECT_EQ(1u, message_center()->GetVisibleNotifications().size());
{ {
message_center::Notification* notification = message_center::Notification* notification =
*message_center()->GetVisibleNotifications().begin(); *message_center()->GetVisibleNotifications().begin();
...@@ -181,6 +196,8 @@ TEST_F(ArcNotificationManagerTest, NotificationRemovedByChrome) { ...@@ -181,6 +196,8 @@ TEST_F(ArcNotificationManagerTest, NotificationRemovedByChrome) {
} }
TEST_F(ArcNotificationManagerTest, NotificationRemovedByConnectionClose) { TEST_F(ArcNotificationManagerTest, NotificationRemovedByConnectionClose) {
ConnectMojoChannel();
EXPECT_EQ(0u, message_center()->GetVisibleNotifications().size()); EXPECT_EQ(0u, message_center()->GetVisibleNotifications().size());
CreateNotificationWithKey("notification1"); CreateNotificationWithKey("notification1");
CreateNotificationWithKey("notification2"); CreateNotificationWithKey("notification2");
...@@ -192,4 +209,96 @@ TEST_F(ArcNotificationManagerTest, NotificationRemovedByConnectionClose) { ...@@ -192,4 +209,96 @@ TEST_F(ArcNotificationManagerTest, NotificationRemovedByConnectionClose) {
EXPECT_EQ(0u, message_center()->GetVisibleNotifications().size()); EXPECT_EQ(0u, message_center()->GetVisibleNotifications().size());
} }
TEST_F(ArcNotificationManagerTest, DoNotDisturbSetStatusByRequestFromAndroid) {
ConnectMojoChannel();
// Check the initial conditions.
EXPECT_FALSE(message_center()->IsQuietMode());
EXPECT_TRUE(
arc_notifications_instance()->latest_do_not_disturb_status().is_null());
// Emulate the request from Android to turn on Do-Not-Distturb.
arc::mojom::ArcDoNotDisturbStatusPtr status =
arc::mojom::ArcDoNotDisturbStatus::New();
status->enabled = true;
arc_notification_manager()->OnDoNotDisturbStatusUpdated(std::move(status));
// Confirm that the Do-Not-Disturb is on.
EXPECT_TRUE(message_center()->IsQuietMode());
// Confirm that no message back to Android.
EXPECT_TRUE(
arc_notifications_instance()->latest_do_not_disturb_status().is_null());
// Emulate the request from Android to turn off Do-Not-Disturb.
status = arc::mojom::ArcDoNotDisturbStatus::New();
status->enabled = false;
arc_notification_manager()->OnDoNotDisturbStatusUpdated(std::move(status));
// Confirm that the Do-Not-Disturb is off.
EXPECT_FALSE(message_center()->IsQuietMode());
// Confirm that no message back to Android.
EXPECT_TRUE(
arc_notifications_instance()->latest_do_not_disturb_status().is_null());
}
TEST_F(ArcNotificationManagerTest, DoNotDisturbSendStatusToAndroid) {
ConnectMojoChannel();
// FlushInstanceCall();
// Check the initial conditions.
EXPECT_TRUE(
arc_notifications_instance()->latest_do_not_disturb_status().is_null());
EXPECT_FALSE(message_center()->IsQuietMode());
// Trying setting the current value (false). This should be no-op
message_center()->SetQuietMode(false);
// A request to Android should not be sent, since the status is not changed.
EXPECT_TRUE(
arc_notifications_instance()->latest_do_not_disturb_status().is_null());
// Trying turning on.
message_center()->SetQuietMode(true);
FlushInstanceCall();
// base::RunLoop().RunUntilIdle();
EXPECT_FALSE(
arc_notifications_instance()->latest_do_not_disturb_status().is_null());
// A request to Android should be sent.
EXPECT_TRUE(
arc_notifications_instance()->latest_do_not_disturb_status()->enabled);
}
TEST_F(ArcNotificationManagerTest, DoNotDisturbSyncInitialDisabledState) {
// Check the initial conditions.
EXPECT_TRUE(
arc_notifications_instance()->latest_do_not_disturb_status().is_null());
EXPECT_FALSE(message_center()->IsQuietMode());
// Establish the mojo connection.
ConnectMojoChannel();
FlushInstanceCall();
// The request to Android should be sent, and the status should be synced
// accordingly.
EXPECT_FALSE(
arc_notifications_instance()->latest_do_not_disturb_status().is_null());
EXPECT_FALSE(
arc_notifications_instance()->latest_do_not_disturb_status()->enabled);
}
TEST_F(ArcNotificationManagerTest, DoNotDisturbSyncInitialEnabledState) {
// Set quiet mode.
message_center()->SetQuietMode(true);
// Check the initial condition.
EXPECT_TRUE(message_center()->IsQuietMode());
EXPECT_TRUE(
arc_notifications_instance()->latest_do_not_disturb_status().is_null());
// Establish the mojo connection.
ConnectMojoChannel();
FlushInstanceCall();
// The request to Android should be sent, and the status should be synced
// accordingly.
EXPECT_FALSE(
arc_notifications_instance()->latest_do_not_disturb_status().is_null());
EXPECT_TRUE(
arc_notifications_instance()->latest_do_not_disturb_status()->enabled);
}
} // namespace ash } // namespace ash
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
// //
// Next MinVersion: 18 // Next MinVersion: 19
module arc.mojom; module arc.mojom;
...@@ -156,8 +156,17 @@ struct ArcNotificationData { ...@@ -156,8 +156,17 @@ struct ArcNotificationData {
ArcNotificationFlags? flags; ArcNotificationFlags? flags;
}; };
// Next Method ID: 7 [Extensible, MinVersion=18]
struct ArcDoNotDisturbStatus {
bool enabled;
};
// Next Method ID: 8
interface NotificationsHost { interface NotificationsHost {
// Set the Do-Not-Disturb status on Chrome side from Android side.
[MinVersion=18]
OnDoNotDisturbStatusUpdated@7(ArcDoNotDisturbStatus status);
// Tells the Chrome that a notification is posted (created or updated) on // Tells the Chrome that a notification is posted (created or updated) on
// Android. If you know that the notification exists, please consider to use // Android. If you know that the notification exists, please consider to use
// OnNotificationUpdate instead. // OnNotificationUpdate instead.
...@@ -177,7 +186,7 @@ interface NotificationsHost { ...@@ -177,7 +186,7 @@ interface NotificationsHost {
OpenMessageCenter@6(); OpenMessageCenter@6();
}; };
// Next Method ID: 7 // Next Method ID: 8
// TODO(lhchavez): Migrate all request/response messages to Mojo. // TODO(lhchavez): Migrate all request/response messages to Mojo.
interface NotificationsInstance { interface NotificationsInstance {
// DEPRECATED: Please use Init@5 instead. // DEPRECATED: Please use Init@5 instead.
...@@ -213,4 +222,8 @@ interface NotificationsInstance { ...@@ -213,4 +222,8 @@ interface NotificationsInstance {
// side. // side.
[MinVersion=17] [MinVersion=17]
OpenNotificationSnoozeSettings@6(string key); OpenNotificationSnoozeSettings@6(string key);
// Set the Do-Not-Disturb status on Android side from Chrome side.
[MinVersion=18]
SetDoNotDisturbStatusOnAndroid@7(ArcDoNotDisturbStatus status);
}; };
...@@ -30,6 +30,11 @@ void FakeNotificationsInstance::OpenNotificationSettings( ...@@ -30,6 +30,11 @@ void FakeNotificationsInstance::OpenNotificationSettings(
void FakeNotificationsInstance::OpenNotificationSnoozeSettings( void FakeNotificationsInstance::OpenNotificationSnoozeSettings(
const std::string& key) {} const std::string& key) {}
void FakeNotificationsInstance::SetDoNotDisturbStatusOnAndroid(
mojom::ArcDoNotDisturbStatusPtr status) {
latest_do_not_disturb_status_ = std::move(status);
}
void FakeNotificationsInstance::InitDeprecated( void FakeNotificationsInstance::InitDeprecated(
mojom::NotificationsHostPtr host_ptr) { mojom::NotificationsHostPtr host_ptr) {
Init(std::move(host_ptr), base::DoNothing()); Init(std::move(host_ptr), base::DoNothing());
...@@ -45,4 +50,9 @@ FakeNotificationsInstance::events() const { ...@@ -45,4 +50,9 @@ FakeNotificationsInstance::events() const {
return events_; return events_;
} }
const mojom::ArcDoNotDisturbStatusPtr&
FakeNotificationsInstance::latest_do_not_disturb_status() const {
return latest_do_not_disturb_status_;
}
} // namespace arc } // namespace arc
...@@ -30,12 +30,16 @@ class FakeNotificationsInstance : public mojom::NotificationsInstance { ...@@ -30,12 +30,16 @@ class FakeNotificationsInstance : public mojom::NotificationsInstance {
void CloseNotificationWindow(const std::string& key) override; void CloseNotificationWindow(const std::string& key) override;
void OpenNotificationSettings(const std::string& key) override; void OpenNotificationSettings(const std::string& key) override;
void OpenNotificationSnoozeSettings(const std::string& key) override; void OpenNotificationSnoozeSettings(const std::string& key) override;
void SetDoNotDisturbStatusOnAndroid(
mojom::ArcDoNotDisturbStatusPtr status) override;
const std::vector<std::pair<std::string, mojom::ArcNotificationEvent>>& const std::vector<std::pair<std::string, mojom::ArcNotificationEvent>>&
events() const; events() const;
const mojom::ArcDoNotDisturbStatusPtr& latest_do_not_disturb_status() const;
private: private:
std::vector<std::pair<std::string, mojom::ArcNotificationEvent>> events_; std::vector<std::pair<std::string, mojom::ArcNotificationEvent>> events_;
mojom::ArcDoNotDisturbStatusPtr latest_do_not_disturb_status_;
DISALLOW_COPY_AND_ASSIGN(FakeNotificationsInstance); DISALLOW_COPY_AND_ASSIGN(FakeNotificationsInstance);
}; };
......
...@@ -15,9 +15,11 @@ FakeMessageCenter::~FakeMessageCenter() { ...@@ -15,9 +15,11 @@ FakeMessageCenter::~FakeMessageCenter() {
} }
void FakeMessageCenter::AddObserver(MessageCenterObserver* observer) { void FakeMessageCenter::AddObserver(MessageCenterObserver* observer) {
observer_list_.AddObserver(observer);
} }
void FakeMessageCenter::RemoveObserver(MessageCenterObserver* observer) { void FakeMessageCenter::RemoveObserver(MessageCenterObserver* observer) {
observer_list_.RemoveObserver(observer);
} }
void FakeMessageCenter::AddNotificationBlocker(NotificationBlocker* blocker) { void FakeMessageCenter::AddNotificationBlocker(NotificationBlocker* blocker) {
......
...@@ -74,8 +74,12 @@ class FakeMessageCenter : public MessageCenter { ...@@ -74,8 +74,12 @@ class FakeMessageCenter : public MessageCenter {
protected: protected:
void DisableTimersForTest() override; void DisableTimersForTest() override;
const base::ObserverList<MessageCenterObserver>& observer_list() const {
return observer_list_;
}
private: private:
base::ObserverList<MessageCenterObserver> observer_list_;
const NotificationList::Notifications empty_notifications_; const NotificationList::Notifications empty_notifications_;
bool has_message_center_view_ = true; bool has_message_center_view_ = true;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment