| // Copyright 2020 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chromeos/ash/components/phonehub/notification_processor.h" |
| #include <string> |
| |
| #include "ash/constants/ash_features.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "base/test/task_environment.h" |
| #include "chromeos/ash/components/phonehub/fake_notification_manager.h" |
| #include "chromeos/ash/components/phonehub/phone_model_test_util.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "ui/gfx/image/image_skia.h" |
| #include "ui/gfx/image/image_unittest_util.h" |
| |
| namespace ash { |
| namespace phonehub { |
| |
| namespace { |
| |
| constexpr int64_t kNotificationIdA = 1; |
| constexpr int64_t kNotificationIdB = 2; |
| constexpr int64_t kNotificationIdC = 3; |
| |
| constexpr int64_t kInlineReplyIdA = 3; |
| constexpr int64_t kInlineReplyIdB = 4; |
| |
| constexpr int64_t kOpenableActionId = -2; |
| constexpr int64_t kAnswerActionId = 1; |
| constexpr int64_t kDeclineActionId = 2; |
| constexpr int64_t kHangupActionId = 3; |
| |
| const char kIconDataA[] = "icon_a"; |
| const char kIconDataB[] = "icon_b"; |
| |
| const char kSharedImageA[] = "shared_image_a"; |
| const char kSharedImageB[] = "shared_image_b"; |
| |
| const char kContactImageA[] = "contact_image_a"; |
| const char kContactImageB[] = "contact_image_b"; |
| |
| // Garbage color for the purpose of verification in these tests. |
| const SkColor kIconColor = SkColorSetRGB(0x12, 0x34, 0x56); |
| |
| SkBitmap TestBitmap() { |
| SkBitmap bitmap; |
| bitmap.allocN32Pixels(1, 1); |
| return bitmap; |
| } |
| |
| gfx::Image TestImage() { |
| gfx::ImageSkia image_skia = gfx::ImageSkia::CreateFrom1xBitmap(TestBitmap()); |
| image_skia.MakeThreadSafe(); |
| return gfx::Image(image_skia); |
| } |
| |
| } // namespace |
| |
| class NotificationProcessorTest : public testing::Test { |
| public: |
| friend class NotificationProcessor; |
| |
| NotificationProcessorTest() { |
| scoped_feature_list_.InitWithFeatures( |
| /*enabled_features=*/{features::kEcheSWA, |
| features::kPhoneHubCallNotification}, |
| /*disabled_features=*/{}); |
| } |
| NotificationProcessorTest(const NotificationProcessorTest&) = delete; |
| NotificationProcessorTest& operator=(const NotificationProcessorTest&) = |
| delete; |
| ~NotificationProcessorTest() override = default; |
| |
| class FakeImageDecoderDelegate |
| : public NotificationProcessor::ImageDecoderDelegate { |
| public: |
| using DecodeImageCallback = NotificationProcessor::DecodeImageCallback; |
| |
| FakeImageDecoderDelegate() = default; |
| ~FakeImageDecoderDelegate() override = default; |
| |
| void PerformImageDecode( |
| const std::string& data, |
| DecodeImageCallback single_image_decoded_closure) override { |
| decode_image_callbacks_.push(std::move(single_image_decoded_closure)); |
| } |
| |
| size_t NumberOfDecodeImageCallbacks() { |
| return decode_image_callbacks_.size(); |
| } |
| |
| void RunNextCallback(SkBitmap bitmap) { |
| std::move(decode_image_callbacks_.front()).Run(bitmap); |
| decode_image_callbacks_.pop(); |
| } |
| |
| void RunAllCallbacks() { |
| while (!decode_image_callbacks_.empty()) |
| RunNextCallback(TestBitmap()); |
| } |
| |
| void RunAllCallbacksWithEmpty() { |
| while (!decode_image_callbacks_.empty()) |
| RunNextCallback(SkBitmap()); |
| } |
| |
| std::queue<DecodeImageCallback> decode_image_callbacks_; |
| }; |
| |
| // testing::Test: |
| void SetUp() override { |
| fake_notification_manager_ = std::make_unique<FakeNotificationManager>(); |
| |
| notification_processor_ = base::WrapUnique(new NotificationProcessor( |
| fake_notification_manager_.get(), |
| std::make_unique<FakeImageDecoderDelegate>())); |
| } |
| |
| FakeImageDecoderDelegate* image_decoder_delegate() { |
| return static_cast<FakeImageDecoderDelegate*>( |
| notification_processor_->delegate_.get()); |
| } |
| |
| NotificationProcessor* notification_processor() { |
| return notification_processor_.get(); |
| } |
| |
| FakeNotificationManager* fake_notification_manager() { |
| return fake_notification_manager_.get(); |
| } |
| |
| size_t NumPendingRequests() { |
| return notification_processor()->pending_notification_requests_.size(); |
| } |
| |
| proto::Notification CreateNewInlineReplyableOpenableNotification( |
| int64_t notification_id, |
| int64_t inline_reply_id, |
| Notification::InteractionBehavior behavior) { |
| return CreateNewInlineReplyableNotification( |
| notification_id, inline_reply_id, |
| /* icon= */ std::string(), |
| /* shared_image= */ std::string(), |
| /* contact_image= */ std::string(), behavior); |
| } |
| |
| proto::Notification CreateNewInlineReplyableNotification( |
| int64_t notification_id, |
| int64_t inline_reply_id, |
| std::string icon = std::string(), |
| std::string shared_image = std::string(), |
| std::string contact_image = std::string(), |
| Notification::InteractionBehavior behavior = |
| Notification::InteractionBehavior::kNone, |
| proto::AppStreamabilityStatus app_streamability_status = |
| proto::AppStreamabilityStatus::STREAMABLE) { |
| auto origin_app = std::make_unique<proto::App>(); |
| origin_app->set_icon(icon); |
| origin_app->set_app_streamability_status(app_streamability_status); |
| |
| proto::Notification notification; |
| notification.set_id(notification_id); |
| notification.set_allocated_origin_app(origin_app.release()); |
| notification.set_contact_image(contact_image); |
| notification.set_shared_image(shared_image); |
| notification.set_category( |
| proto::Notification::Category::Notification_Category_CONVERSATION); |
| |
| notification.add_actions(); |
| proto::Action* mutable_action = notification.mutable_actions(0); |
| mutable_action->set_id(inline_reply_id); |
| mutable_action->set_type(proto::Action_InputType::Action_InputType_TEXT); |
| |
| if (behavior == Notification::InteractionBehavior::kOpenable) { |
| notification.add_actions(); |
| proto::Action* open_action = notification.mutable_actions(1); |
| open_action->set_id(kOpenableActionId); |
| open_action->set_type(proto::Action_InputType::Action_InputType_OPEN); |
| } |
| return notification; |
| } |
| |
| proto::Notification CreateNewInlineReplyableMonochromeIconNotification( |
| int64_t notification_id, |
| int64_t inline_reply_id, |
| absl::optional<SkColor> icon_color = absl::nullopt, |
| std::string icon = std::string()) { |
| proto::Notification notification = CreateNewInlineReplyableNotification( |
| notification_id, inline_reply_id, icon); |
| proto::App* origin_app = notification.mutable_origin_app(); |
| origin_app->set_icon_styling( |
| proto::NotificationIconStyling::ICON_STYLE_MONOCHROME_SMALL_ICON); |
| if (icon_color != absl::nullopt) { |
| auto color_rgb = std::make_unique<proto::ColorRgb>(); |
| color_rgb->set_red(SkColorGetR(*icon_color)); |
| color_rgb->set_green(SkColorGetG(*icon_color)); |
| color_rgb->set_blue(SkColorGetB(*icon_color)); |
| origin_app->set_allocated_icon_color(color_rgb.release()); |
| } |
| return notification; |
| } |
| |
| proto::Notification CreateNonTextTypeNotification( |
| int64_t notification_id, |
| int64_t inline_reply_id, |
| std::string icon = std::string()) { |
| auto origin_app = std::make_unique<proto::App>(); |
| origin_app->set_icon(icon); |
| |
| proto::Notification notification; |
| notification.set_id(notification_id); |
| notification.set_allocated_origin_app(origin_app.release()); |
| |
| notification.add_actions(); |
| proto::Action* open_action = notification.mutable_actions(0); |
| open_action->set_id(kOpenableActionId); |
| open_action->set_type(proto::Action_InputType::Action_InputType_OPEN); |
| |
| return notification; |
| } |
| |
| proto::Notification CreateIncomingCallNotification(int64_t notification_id) { |
| auto origin_app = std::make_unique<proto::App>(); |
| origin_app->set_icon(std::string()); |
| |
| proto::Notification notification; |
| notification.set_id(notification_id); |
| notification.set_allocated_origin_app(origin_app.release()); |
| notification.set_contact_image(std::string()); |
| notification.set_shared_image(std::string()); |
| notification.set_category( |
| proto::Notification::Category::Notification_Category_INCOMING_CALL); |
| |
| notification.add_actions(); |
| proto::Action* answer_action = notification.mutable_actions(0); |
| answer_action->set_id(kAnswerActionId); |
| answer_action->set_call_action( |
| proto::Action_CallAction::Action_CallAction_ANSWER); |
| |
| notification.add_actions(); |
| proto::Action* decline_action = notification.mutable_actions(1); |
| decline_action->set_id(kDeclineActionId); |
| decline_action->set_call_action( |
| proto::Action_CallAction::Action_CallAction_DECLINE); |
| |
| return notification; |
| } |
| |
| proto::Notification CreateOngoingCallNotification(int64_t notification_id) { |
| auto origin_app = std::make_unique<proto::App>(); |
| origin_app->set_icon(std::string()); |
| |
| proto::Notification notification; |
| notification.set_id(notification_id); |
| notification.set_allocated_origin_app(origin_app.release()); |
| notification.set_contact_image(std::string()); |
| notification.set_shared_image(std::string()); |
| notification.set_category( |
| proto::Notification::Category::Notification_Category_ONGOING_CALL); |
| |
| notification.add_actions(); |
| proto::Action* hangup_action = notification.mutable_actions(0); |
| hangup_action->set_id(kHangupActionId); |
| hangup_action->set_call_action( |
| proto::Action_CallAction::Action_CallAction_HANGUP); |
| |
| notification.add_actions(); |
| proto::Action* open_action = notification.mutable_actions(1); |
| open_action->set_id(kOpenableActionId); |
| open_action->set_type(proto::Action_InputType::Action_InputType_OPEN); |
| |
| return notification; |
| } |
| |
| private: |
| std::unique_ptr<FakeNotificationManager> fake_notification_manager_; |
| std::unique_ptr<NotificationProcessor> notification_processor_; |
| |
| base::test::ScopedFeatureList scoped_feature_list_; |
| base::test::SingleThreadTaskEnvironment task_environment_; |
| }; |
| |
| TEST_F(NotificationProcessorTest, FailedToDecodeImage) { |
| std::vector<proto::Notification> first_set_of_notifications; |
| |
| // The icon should be an empty image as the decoder failed to decode the |
| // image. |
| first_set_of_notifications.emplace_back(CreateNewInlineReplyableNotification( |
| kNotificationIdA, kInlineReplyIdA, kIconDataA)); |
| notification_processor()->AddNotifications(first_set_of_notifications); |
| image_decoder_delegate()->RunNextCallback(SkBitmap()); |
| |
| const Notification* notification = |
| fake_notification_manager()->GetNotification(kNotificationIdA); |
| EXPECT_TRUE(notification->app_metadata().icon.IsEmpty()); |
| EXPECT_FALSE(notification->shared_image().has_value()); |
| EXPECT_FALSE(notification->contact_image().has_value()); |
| } |
| |
| TEST_F(NotificationProcessorTest, ShouldSkipDecodeImageIfNotAvailable) { |
| std::vector<proto::Notification> first_set_of_notifications; |
| |
| first_set_of_notifications.emplace_back(CreateNonTextTypeNotification( |
| kNotificationIdA, kInlineReplyIdA, kIconDataA)); |
| notification_processor()->AddNotifications(first_set_of_notifications); |
| |
| EXPECT_EQ(0u, image_decoder_delegate()->NumberOfDecodeImageCallbacks()); |
| image_decoder_delegate()->RunAllCallbacks(); |
| |
| EXPECT_EQ(0u, fake_notification_manager()->num_notifications()); |
| } |
| |
| TEST_F(NotificationProcessorTest, MonochromeIconFieldsPopulatedCorrectly) { |
| std::vector<proto::Notification> first_set_of_notifications; |
| |
| // Legacy notifications don't supply color and should not be filled in. |
| first_set_of_notifications.emplace_back(CreateNewInlineReplyableNotification( |
| kNotificationIdA, kInlineReplyIdA, kIconDataA)); |
| notification_processor()->AddNotifications(first_set_of_notifications); |
| image_decoder_delegate()->RunAllCallbacks(); |
| |
| const Notification* notification = |
| fake_notification_manager()->GetNotification(kNotificationIdA); |
| EXPECT_FALSE(notification->app_metadata().icon_is_monochrome); |
| EXPECT_TRUE(gfx::test::AreImagesEqual(notification->app_metadata().icon, |
| TestImage())); |
| EXPECT_FALSE(notification->app_metadata().icon_color.has_value()); |
| |
| // Monochrome notifications without a color should not have icon_color filled. |
| first_set_of_notifications.clear(); |
| first_set_of_notifications.emplace_back( |
| CreateNewInlineReplyableMonochromeIconNotification( |
| kNotificationIdA, kInlineReplyIdA, absl::nullopt, kIconDataA)); |
| notification_processor()->AddNotifications(first_set_of_notifications); |
| image_decoder_delegate()->RunAllCallbacks(); |
| |
| notification = fake_notification_manager()->GetNotification(kNotificationIdA); |
| EXPECT_TRUE(notification->app_metadata().icon_is_monochrome); |
| EXPECT_TRUE(gfx::test::AreImagesEqual(notification->app_metadata().icon, |
| TestImage())); |
| EXPECT_FALSE(notification->app_metadata().icon_color.has_value()); |
| |
| // Monochrome notifications with a color should have icon_color filled. |
| first_set_of_notifications.clear(); |
| first_set_of_notifications.emplace_back( |
| CreateNewInlineReplyableMonochromeIconNotification( |
| kNotificationIdA, kInlineReplyIdA, kIconColor, kIconDataA)); |
| notification_processor()->AddNotifications(first_set_of_notifications); |
| image_decoder_delegate()->RunAllCallbacks(); |
| |
| notification = fake_notification_manager()->GetNotification(kNotificationIdA); |
| EXPECT_TRUE(notification->app_metadata().icon_is_monochrome); |
| EXPECT_TRUE(gfx::test::AreImagesEqual(notification->app_metadata().icon, |
| TestImage())); |
| EXPECT_TRUE(notification->app_metadata().icon_color.has_value()); |
| EXPECT_TRUE(*notification->app_metadata().icon_color == kIconColor); |
| |
| // Monochrome notifications without an icon should not fill in color. |
| first_set_of_notifications.clear(); |
| first_set_of_notifications.emplace_back( |
| CreateNewInlineReplyableMonochromeIconNotification( |
| kNotificationIdA, kInlineReplyIdA, kIconColor, |
| /*icon=*/std::string())); |
| notification_processor()->AddNotifications(first_set_of_notifications); |
| image_decoder_delegate()->RunAllCallbacksWithEmpty(); |
| |
| notification = fake_notification_manager()->GetNotification(kNotificationIdA); |
| EXPECT_TRUE(notification->app_metadata().icon_is_monochrome); |
| EXPECT_TRUE(notification->app_metadata().icon.IsEmpty()); |
| EXPECT_FALSE(notification->app_metadata().icon_color.has_value()); |
| } |
| |
| TEST_F(NotificationProcessorTest, ImageFieldPopulatedCorrectly) { |
| std::vector<proto::Notification> first_set_of_notifications; |
| |
| // The icon should be populated. The shared and contact image should be null. |
| first_set_of_notifications.emplace_back(CreateNewInlineReplyableNotification( |
| kNotificationIdA, kInlineReplyIdA, kIconDataA)); |
| notification_processor()->AddNotifications(first_set_of_notifications); |
| image_decoder_delegate()->RunAllCallbacks(); |
| |
| const Notification* notification = |
| fake_notification_manager()->GetNotification(kNotificationIdA); |
| EXPECT_TRUE(gfx::test::AreImagesEqual(notification->app_metadata().icon, |
| TestImage())); |
| EXPECT_FALSE(notification->shared_image().has_value()); |
| EXPECT_FALSE(notification->contact_image().has_value()); |
| |
| // The icon and shared image should be populated. The contact image should be |
| // null. |
| first_set_of_notifications.clear(); |
| first_set_of_notifications.emplace_back(CreateNewInlineReplyableNotification( |
| kNotificationIdA, kInlineReplyIdA, kIconDataA, kSharedImageA)); |
| notification_processor()->AddNotifications(first_set_of_notifications); |
| image_decoder_delegate()->RunAllCallbacks(); |
| |
| notification = fake_notification_manager()->GetNotification(kNotificationIdA); |
| EXPECT_TRUE(gfx::test::AreImagesEqual(notification->app_metadata().icon, |
| TestImage())); |
| EXPECT_TRUE( |
| gfx::test::AreImagesEqual(*notification->shared_image(), TestImage())); |
| EXPECT_FALSE(notification->contact_image().has_value()); |
| |
| // The icon and contact image should be populated. The shared image should be |
| // null. |
| first_set_of_notifications.clear(); |
| first_set_of_notifications.emplace_back(CreateNewInlineReplyableNotification( |
| kNotificationIdA, kInlineReplyIdA, kIconDataA, std::string(), |
| kContactImageA)); |
| notification_processor()->AddNotifications(first_set_of_notifications); |
| image_decoder_delegate()->RunAllCallbacks(); |
| |
| notification = fake_notification_manager()->GetNotification(kNotificationIdA); |
| EXPECT_TRUE(gfx::test::AreImagesEqual(notification->app_metadata().icon, |
| TestImage())); |
| EXPECT_FALSE(notification->shared_image().has_value()); |
| EXPECT_TRUE( |
| gfx::test::AreImagesEqual(*notification->contact_image(), TestImage())); |
| |
| // All images should be should be populated. |
| first_set_of_notifications.clear(); |
| first_set_of_notifications.emplace_back(CreateNewInlineReplyableNotification( |
| kNotificationIdA, kInlineReplyIdA, kIconDataA, kSharedImageA, |
| kContactImageA)); |
| notification_processor()->AddNotifications(first_set_of_notifications); |
| image_decoder_delegate()->RunAllCallbacks(); |
| EXPECT_TRUE(gfx::test::AreImagesEqual(notification->app_metadata().icon, |
| TestImage())); |
| EXPECT_TRUE( |
| gfx::test::AreImagesEqual(*notification->shared_image(), TestImage())); |
| EXPECT_TRUE( |
| gfx::test::AreImagesEqual(*notification->contact_image(), TestImage())); |
| } |
| |
| TEST_F(NotificationProcessorTest, StreamabilityStatus) { |
| std::vector<proto::Notification> first_set_of_notifications; |
| |
| first_set_of_notifications.emplace_back(CreateNewInlineReplyableNotification( |
| kNotificationIdA, kInlineReplyIdA, kIconDataA, std::string(), |
| std::string(), Notification::InteractionBehavior::kNone, |
| proto::AppStreamabilityStatus::BLOCK_LISTED)); |
| notification_processor()->AddNotifications(first_set_of_notifications); |
| image_decoder_delegate()->RunAllCallbacks(); |
| |
| const Notification* notification = |
| fake_notification_manager()->GetNotification(kNotificationIdA); |
| EXPECT_EQ(proto::AppStreamabilityStatus::BLOCK_LISTED, |
| notification->app_metadata().app_streamability_status); |
| } |
| |
| TEST_F(NotificationProcessorTest, AddRemoveClearWithoutRace) { |
| // Add 2 notifications with all images populated. |
| std::vector<proto::Notification> first_set_of_notifications; |
| first_set_of_notifications.emplace_back(CreateNewInlineReplyableNotification( |
| kNotificationIdA, kInlineReplyIdA, kIconDataA, kSharedImageA, |
| kContactImageA)); |
| first_set_of_notifications.emplace_back(CreateNewInlineReplyableNotification( |
| kNotificationIdB, kInlineReplyIdB, kIconDataB, kSharedImageB, |
| kContactImageB)); |
| |
| notification_processor()->AddNotifications(first_set_of_notifications); |
| |
| // 6 image decode callbacks will occur for kIconDataA, kSharedImageA, |
| // kContactImageA, kIconDataB, kSharedImageB, and kContactImageB. |
| EXPECT_EQ(6u, image_decoder_delegate()->NumberOfDecodeImageCallbacks()); |
| image_decoder_delegate()->RunAllCallbacks(); |
| |
| EXPECT_EQ(2u, fake_notification_manager()->num_notifications()); |
| EXPECT_TRUE(fake_notification_manager()->GetNotification(kNotificationIdA)); |
| EXPECT_TRUE(fake_notification_manager()->GetNotification(kNotificationIdB)); |
| |
| // Remove notification with id kNotificationIdA. |
| base::flat_set<int64_t> ids_of_notifications_to_remove; |
| ids_of_notifications_to_remove.emplace(kNotificationIdA); |
| notification_processor()->RemoveNotifications(ids_of_notifications_to_remove); |
| EXPECT_EQ(1u, fake_notification_manager()->num_notifications()); |
| EXPECT_FALSE(fake_notification_manager()->GetNotification(kNotificationIdA)); |
| EXPECT_TRUE(fake_notification_manager()->GetNotification(kNotificationIdB)); |
| |
| // Clear all notifications. |
| notification_processor()->ClearNotificationsAndPendingUpdates(); |
| EXPECT_EQ(0u, fake_notification_manager()->num_notifications()); |
| } |
| |
| TEST_F(NotificationProcessorTest, AddRemoveWithRace) { |
| // Add 2 notifications. |
| std::vector<proto::Notification> first_set_of_notifications; |
| first_set_of_notifications.emplace_back(CreateNewInlineReplyableNotification( |
| kNotificationIdA, kInlineReplyIdA, kIconDataA, kSharedImageA)); |
| first_set_of_notifications.emplace_back(CreateNewInlineReplyableNotification( |
| kNotificationIdB, kInlineReplyIdB, kIconDataB)); |
| |
| notification_processor()->AddNotifications(first_set_of_notifications); |
| |
| // One pending requests because |first_set_of_notifications| processing |
| // occurred immediately. |
| EXPECT_EQ(1u, NumPendingRequests()); |
| |
| // Remove notification with id kNotificationIdA while |
| // |first_set_of_notifications| is still being processed. |
| base::flat_set<int64_t> ids_of_notifications_to_remove; |
| ids_of_notifications_to_remove.emplace(kNotificationIdA); |
| notification_processor()->RemoveNotifications(ids_of_notifications_to_remove); |
| |
| // Pending delete request, first in the queue. |
| EXPECT_EQ(2u, NumPendingRequests()); |
| EXPECT_EQ(0u, fake_notification_manager()->num_notifications()); |
| |
| // Add a set of notifications such that only one image needs to be decoded, |
| // when neither the first set has completed processing more the remove request |
| // has been fully processed. |
| std::vector<proto::Notification> second_set_of_notifications; |
| second_set_of_notifications.emplace_back(CreateNewInlineReplyableNotification( |
| kNotificationIdA, kInlineReplyIdA, kIconDataA)); |
| notification_processor()->AddNotifications(second_set_of_notifications); |
| |
| // Pending add request, second in the queue. |
| EXPECT_EQ(3u, NumPendingRequests()); |
| EXPECT_EQ(0u, fake_notification_manager()->num_notifications()); |
| |
| // 3 image decode callbacks will occur. When the last image decode callback is |
| // finished running, which in this case is icon2, it will cause the next |
| // notification edit request to be executed. |
| EXPECT_EQ(3u, image_decoder_delegate()->NumberOfDecodeImageCallbacks()); |
| EXPECT_EQ(3u, NumPendingRequests()); |
| image_decoder_delegate()->RunNextCallback(TestBitmap()); |
| |
| EXPECT_EQ(2u, image_decoder_delegate()->NumberOfDecodeImageCallbacks()); |
| EXPECT_EQ(3u, NumPendingRequests()); |
| image_decoder_delegate()->RunNextCallback(TestBitmap()); |
| |
| EXPECT_EQ(1u, image_decoder_delegate()->NumberOfDecodeImageCallbacks()); |
| EXPECT_EQ(3u, NumPendingRequests()); |
| |
| // The scheduled remove callback will occur, then subsequently the add |
| // notification with 1 image. |
| image_decoder_delegate()->RunNextCallback(TestBitmap()); |
| EXPECT_EQ(1u, NumPendingRequests()); |
| EXPECT_EQ(1u, image_decoder_delegate()->NumberOfDecodeImageCallbacks()); |
| |
| EXPECT_EQ(1u, fake_notification_manager()->num_notifications()); |
| EXPECT_FALSE(fake_notification_manager()->GetNotification(kNotificationIdA)); |
| EXPECT_TRUE(fake_notification_manager()->GetNotification(kNotificationIdB)); |
| |
| // 1 image decode callback will occur. |
| image_decoder_delegate()->RunAllCallbacks(); |
| EXPECT_EQ(0u, NumPendingRequests()); |
| EXPECT_EQ(2u, fake_notification_manager()->num_notifications()); |
| EXPECT_TRUE(fake_notification_manager()->GetNotification(kNotificationIdA)); |
| EXPECT_TRUE(fake_notification_manager()->GetNotification(kNotificationIdB)); |
| } |
| |
| TEST_F(NotificationProcessorTest, AddClearAllWithRace) { |
| std::vector<proto::Notification> first_set_of_notifications; |
| first_set_of_notifications.emplace_back(CreateNewInlineReplyableNotification( |
| kNotificationIdA, kInlineReplyIdA, kIconDataA, kSharedImageA)); |
| first_set_of_notifications.emplace_back(CreateNewInlineReplyableNotification( |
| kNotificationIdB, kInlineReplyIdB, kIconDataA)); |
| |
| notification_processor()->AddNotifications(first_set_of_notifications); |
| |
| // Clearing Notifications will invalidate all callbacks in process and |
| // immediately clear all pointers. |
| notification_processor()->ClearNotificationsAndPendingUpdates(); |
| EXPECT_EQ(0u, fake_notification_manager()->num_notifications()); |
| image_decoder_delegate()->RunAllCallbacks(); |
| EXPECT_EQ(0u, NumPendingRequests()); |
| EXPECT_EQ(0u, fake_notification_manager()->num_notifications()); |
| EXPECT_FALSE(fake_notification_manager()->GetNotification(kNotificationIdA)); |
| EXPECT_FALSE(fake_notification_manager()->GetNotification(kNotificationIdB)); |
| } |
| |
| TEST_F(NotificationProcessorTest, InteractionBehaviorPopulatedCorrectly) { |
| std::vector<proto::Notification> first_set_of_notifications; |
| |
| // The notification should be openable if a OPEN action is specified. |
| first_set_of_notifications.emplace_back( |
| CreateNewInlineReplyableOpenableNotification( |
| kNotificationIdA, kInlineReplyIdA, |
| Notification::InteractionBehavior::kOpenable)); |
| notification_processor()->AddNotifications(first_set_of_notifications); |
| image_decoder_delegate()->RunAllCallbacks(); |
| |
| const Notification* notification = |
| fake_notification_manager()->GetNotification(kNotificationIdA); |
| EXPECT_EQ(Notification::InteractionBehavior::kOpenable, |
| notification->interaction_behavior()); |
| EXPECT_EQ(Notification::Category::kConversation, notification->category()); |
| |
| // The notification should not specify interaction behaviors if none are |
| // available. |
| first_set_of_notifications.clear(); |
| first_set_of_notifications.emplace_back( |
| CreateNewInlineReplyableNotification(kNotificationIdA, kInlineReplyIdA)); |
| notification_processor()->AddNotifications(first_set_of_notifications); |
| image_decoder_delegate()->RunAllCallbacks(); |
| |
| notification = fake_notification_manager()->GetNotification(kNotificationIdA); |
| EXPECT_EQ(Notification::InteractionBehavior::kNone, |
| notification->interaction_behavior()); |
| EXPECT_EQ(Notification::Category::kConversation, notification->category()); |
| |
| // The notification has IncomingCall category if answer action is |
| // found. |
| first_set_of_notifications.clear(); |
| first_set_of_notifications.emplace_back( |
| CreateIncomingCallNotification(kNotificationIdA)); |
| notification_processor()->AddNotifications(first_set_of_notifications); |
| image_decoder_delegate()->RunAllCallbacks(); |
| |
| notification = fake_notification_manager()->GetNotification(kNotificationIdA); |
| EXPECT_EQ(Notification::Category::kIncomingCall, notification->category()); |
| |
| // The notification has OngoingCall category if hangup action is |
| // found. |
| first_set_of_notifications.clear(); |
| first_set_of_notifications.emplace_back( |
| CreateOngoingCallNotification(kNotificationIdC)); |
| notification_processor()->AddNotifications(first_set_of_notifications); |
| image_decoder_delegate()->RunAllCallbacks(); |
| |
| notification = fake_notification_manager()->GetNotification(kNotificationIdC); |
| EXPECT_EQ(Notification::Category::kOngoingCall, notification->category()); |
| } |
| |
| TEST_F(NotificationProcessorTest, ActionIdMapPopulatedCorrectly) { |
| std::vector<proto::Notification> first_set_of_notifications; |
| |
| // The inline reply notification should have reply action id. |
| first_set_of_notifications.emplace_back( |
| CreateNewInlineReplyableNotification(kNotificationIdA, kInlineReplyIdA)); |
| notification_processor()->AddNotifications(first_set_of_notifications); |
| image_decoder_delegate()->RunAllCallbacks(); |
| |
| const Notification* notification = |
| fake_notification_manager()->GetNotification(kNotificationIdA); |
| EXPECT_EQ(1u, notification->action_id_map().size()); |
| EXPECT_EQ(kInlineReplyIdA, notification->action_id_map().at( |
| Notification::ActionType::kInlineReply)); |
| |
| // The incoming call notification should have answer and decline action ids. |
| first_set_of_notifications.clear(); |
| first_set_of_notifications.emplace_back( |
| CreateIncomingCallNotification(kNotificationIdA)); |
| notification_processor()->AddNotifications(first_set_of_notifications); |
| image_decoder_delegate()->RunAllCallbacks(); |
| |
| notification = fake_notification_manager()->GetNotification(kNotificationIdA); |
| EXPECT_EQ(2u, notification->action_id_map().size()); |
| EXPECT_EQ(kAnswerActionId, notification->action_id_map().at( |
| Notification::ActionType::kAnswer)); |
| EXPECT_EQ(kDeclineActionId, notification->action_id_map().at( |
| Notification::ActionType::kDecline)); |
| |
| // The ongoing call notification should have hangup action id. |
| first_set_of_notifications.clear(); |
| first_set_of_notifications.emplace_back( |
| CreateOngoingCallNotification(kNotificationIdC)); |
| notification_processor()->AddNotifications(first_set_of_notifications); |
| image_decoder_delegate()->RunAllCallbacks(); |
| |
| notification = fake_notification_manager()->GetNotification(kNotificationIdC); |
| EXPECT_EQ(1u, notification->action_id_map().size()); |
| EXPECT_EQ(kHangupActionId, notification->action_id_map().at( |
| Notification::ActionType::kHangup)); |
| } |
| |
| } // namespace phonehub |
| } // namespace ash |