Skip to content

Add image on notification support #2491

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Mar 11, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions Example/Firebase.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,8 @@
51885509223067E900CA4141 /* FIRInstanceIDTokenOperationsTest.m in Sources */ = {isa = PBXBuildFile; fileRef = DE958BDB21F7DF0B00E6C1C5 /* FIRInstanceIDTokenOperationsTest.m */; };
5188550A223067E900CA4141 /* FIRInstanceIDUtilitiesTest.m in Sources */ = {isa = PBXBuildFile; fileRef = DE958BE121F7DF0C00E6C1C5 /* FIRInstanceIDUtilitiesTest.m */; };
5188550C2230873000CA4141 /* FIRInstanceIDAPNSInfoTest.m in Sources */ = {isa = PBXBuildFile; fileRef = DE958BDA21F7DF0B00E6C1C5 /* FIRInstanceIDAPNSInfoTest.m */; };
5188550E2231E02400CA4141 /* FIRMessagingExtensionHelperTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 5188550D2231E02300CA4141 /* FIRMessagingExtensionHelperTest.m */; };
518855112231E09300CA4141 /* FIRMessagingExtensionHelperTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 518855102231E09300CA4141 /* FIRMessagingExtensionHelperTest.m */; };
7E21E0731F857DFC00D0AC1C /* FIROAuthProviderTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 7E21E0721F857DFC00D0AC1C /* FIROAuthProviderTests.m */; };
7E9485421F578AC4005A3939 /* FIRAuthURLPresenterTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 7E94853F1F578A9D005A3939 /* FIRAuthURLPresenterTests.m */; };
7EE21F7A1FE89193009B1370 /* FIREmailLinkRequestTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 7EE21F791FE89193009B1370 /* FIREmailLinkRequestTests.m */; };
Expand Down Expand Up @@ -1078,6 +1080,9 @@
518854E22230652B00CA4141 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
518854E32230652B00CA4141 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
518854EC223066BE00CA4141 /* InstanceID_Tests_tvOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = InstanceID_Tests_tvOS.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
5188550D2231E02300CA4141 /* FIRMessagingExtensionHelperTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = FIRMessagingExtensionHelperTest.m; path = Messaging/Tests/FIRMessagingExtensionHelperTest.m; sourceTree = "<group>"; };
5188550F2231E04C00CA4141 /* FIRMessagingClientTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = FIRMessagingClientTest.m; path = Messaging/Tests/FIRMessagingClientTest.m; sourceTree = "<group>"; };
518855102231E09300CA4141 /* FIRMessagingExtensionHelperTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = FIRMessagingExtensionHelperTest.m; path = Messaging/Tests/FIRMessagingExtensionHelperTest.m; sourceTree = "<group>"; };
6003F58D195388D20070C39A /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
6003F58F195388D20070C39A /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; };
6003F591195388D20070C39A /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; };
Expand Down Expand Up @@ -1914,6 +1919,9 @@
6003F581195388D10070C39A = {
isa = PBXGroup;
children = (
518855102231E09300CA4141 /* FIRMessagingExtensionHelperTest.m */,
5188550F2231E04C00CA4141 /* FIRMessagingClientTest.m */,
5188550D2231E02300CA4141 /* FIRMessagingExtensionHelperTest.m */,
60FF7A9C1954A5C5007DD14C /* Podspec Metadata */,
DE9314EB1E86C6FF0083EDBF /* Auth */,
DEE14D661E844677006FA992 /* Core */,
Expand Down Expand Up @@ -4297,6 +4305,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
518855112231E09300CA4141 /* FIRMessagingExtensionHelperTest.m in Sources */,
511DD2AA2225C8D50094D78D /* FIRMessagingTestUtilities.h in Sources */,
511DD2AB2225C8D50094D78D /* FIRMessagingTestUtilities.m in Sources */,
511DD2922225C8C40094D78D /* FIRInstanceIDWithFCMTest.m in Sources */,
Expand Down Expand Up @@ -4921,6 +4930,7 @@
buildActionMask = 2147483647;
files = (
DE9315F41E8738E60083EDBF /* FIRMessagingClientTest.m in Sources */,
5188550E2231E02400CA4141 /* FIRMessagingExtensionHelperTest.m in Sources */,
DE9315F51E8738E60083EDBF /* FIRMessagingCodedInputStreamTest.m in Sources */,
DE9315F71E8738E60083EDBF /* FIRMessagingContextManagerServiceTest.m in Sources */,
DE9315FD1E8738E60083EDBF /* FIRMessagingPubSubTest.m in Sources */,
Expand Down
110 changes: 110 additions & 0 deletions Example/Messaging/Tests/FIRMessagingExtensionHelperTest.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
/*
* Copyright 2019 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#import <UIKit/UIKit.h>
#import <XCTest/XCTest.h>

#import <OCMock/OCMock.h>

#import "FIRMessaging.h"
#import "FIRMessagingExtensionHelper.h"

typedef void (^FIRMessagingContentHandler)(UNNotificationContent *content);

static NSString *const kFCMPayloadOptionsName = @"fcm_options";
static NSString *const kFCMPayloadOptionsImageURLName = @"image";
static NSString *const kValidImageURL =
@"https://firebasestorage.googleapis.com/v0/b/fcm-ios-f7f9c.appspot.com/o/"
@"chubbyBunny.jpg?alt=media&token=d6c56a57-c007-4b27-b20f-f267cc83e9e5";

@interface FIRMessagingExtensionHelper (ExposedForTest)
#if TARGET_OS_IOS && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
- (void)loadAttachmentForURL:(NSURL *)attachmentURL
completionHandler:(void (^)(UNNotificationAttachment *))completionHandler;
#endif
@end

@interface FIRMessagingExtensionHelperTest : XCTestCase {
id _mockExtensionHelper;
}

@end

@implementation FIRMessagingExtensionHelperTest

- (void)setUp {
[super setUp];
FIRMessagingExtensionHelper *extensionHelper = [FIRMessaging extensionHelper];
_mockExtensionHelper = OCMPartialMock(extensionHelper);
}

- (void)tearDown {
[_mockExtensionHelper stopMocking];
}

#if TARGET_OS_IOS && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
- (void)testModifyNotificationWithValidPayloadData {
XCTestExpectation *validPayloadExpectation =
[self expectationWithDescription:@"Test payload is valid."];

UNMutableNotificationContent *content = [[UNMutableNotificationContent alloc] init];
content.userInfo = @{kFCMPayloadOptionsName : @{kFCMPayloadOptionsImageURLName : kValidImageURL}};
FIRMessagingContentHandler handler = ^(UNNotificationContent *content) {
[validPayloadExpectation fulfill];
};
[_mockExtensionHelper populateNotificationContent:content withContentHandler:handler];

OCMVerify([_mockExtensionHelper loadAttachmentForURL:[OCMArg any]
completionHandler:[OCMArg any]]);
[self waitForExpectationsWithTimeout:1.0 handler:nil];
}

- (void)testModifyNotificationWithInvalidPayloadData {
XCTestExpectation *validPayloadExpectation =
[self expectationWithDescription:@"Test payload is valid."];

UNMutableNotificationContent *content = [[UNMutableNotificationContent alloc] init];
content.userInfo =
@{kFCMPayloadOptionsName : @{kFCMPayloadOptionsImageURLName : @"a invalid URL"}};
FIRMessagingContentHandler handler = ^(UNNotificationContent *content) {
[validPayloadExpectation fulfill];
};
[_mockExtensionHelper populateNotificationContent:content withContentHandler:handler];

OCMReject([_mockExtensionHelper loadAttachmentForURL:[OCMArg any]
completionHandler:[OCMArg any]]);
[self waitForExpectationsWithTimeout:1.0 handler:nil];
}

- (void)testModifyNotificationWithEmptyPayloadData {
XCTestExpectation *validPayloadExpectation =
[self expectationWithDescription:@"Test payload is valid."];

UNMutableNotificationContent *content = [[UNMutableNotificationContent alloc] init];
content.userInfo =
@{kFCMPayloadOptionsName : @{kFCMPayloadOptionsImageURLName : @"a invalid URL"}};
FIRMessagingContentHandler handler = ^(UNNotificationContent *content) {
[validPayloadExpectation fulfill];
};
[_mockExtensionHelper populateNotificationContent:content withContentHandler:handler];

OCMReject([_mockExtensionHelper loadAttachmentForURL:[OCMArg any]
completionHandler:[OCMArg any]]);
[self waitForExpectationsWithTimeout:1.0 handler:nil];
}
#endif

@end
6 changes: 6 additions & 0 deletions Firebase/Messaging/FIRMMessageCode.h
Original file line number Diff line number Diff line change
Expand Up @@ -188,4 +188,10 @@ typedef NS_ENUM(NSInteger, FIRMessagingMessageCode) {
kFIRMessagingMessageCodeAnalyticsInvalidEvent = 19006, // I-FCM019006
kFIRMessagingMessageCodeAnalytics007 = 19007, // I-FCM019007
kFIRMessagingMessageCodeAnalyticsCouldNotInvokeAnalyticsLog = 19008, // I-FCM019008
// FIRMessagingExtensionHelper.m
kFIRMessagingServiceExtensionImageInvalidURL = 20000,
kFIRMessagingServiceExtensionImageNotDownloaded = 20001,
kFIRMessagingServiceExtensionLocalFileNotCreated = 20002,
kFIRMessagingServiceExtensionImageNotAttached = 20003,

};
10 changes: 10 additions & 0 deletions Firebase/Messaging/FIRMessaging.m
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
#import "FIRMessagingContextManagerService.h"
#import "FIRMessagingDataMessageManager.h"
#import "FIRMessagingDefines.h"
#import "FIRMessagingExtensionHelper.h"
#import "FIRMessagingLogger.h"
#import "FIRMessagingPubSub.h"
#import "FIRMessagingReceiver.h"
Expand Down Expand Up @@ -178,6 +179,15 @@ + (FIRMessaging *)messaging {
return messaging;
}

+ (FIRMessagingExtensionHelper *)extensionHelper {
static dispatch_once_t once;
static FIRMessagingExtensionHelper *extensionHelper;
dispatch_once(&once, ^{
extensionHelper = [[FIRMessagingExtensionHelper alloc] init];
});
return extensionHelper;
}

- (instancetype)initWithAnalytics:(nullable id<FIRAnalyticsInterop>)analytics
withInstanceID:(FIRInstanceID *)instanceID
withUserDefaults:(GULUserDefaults *)defaults {
Expand Down
116 changes: 116 additions & 0 deletions Firebase/Messaging/FIRMessagingExtensionHelper.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
/*
* Copyright 2019 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#import "FIRMessagingExtensionHelper.h"

#import "FIRMMessageCode.h"
#import "FIRMessagingLogger.h"

static NSString *const kPayloadOptionsName = @"fcm_options";
static NSString *const kPayloadOptionsImageURLName = @"image";

@interface FIRMessagingExtensionHelper ()
@property(nonatomic, strong) void (^contentHandler)(UNNotificationContent *contentToDeliver);
@property(nonatomic, strong) UNMutableNotificationContent *bestAttemptContent;

@end

@implementation FIRMessagingExtensionHelper

- (void)populateNotificationContent:(UNMutableNotificationContent *)content
withContentHandler:(void (^)(UNNotificationContent *_Nonnull))contentHandler {
self.contentHandler = [contentHandler copy];
self.bestAttemptContent = content;

NSString *currentImageURL = content.userInfo[kPayloadOptionsName][kPayloadOptionsImageURLName];
if (!currentImageURL) {
[self deliverNotification];
return;
}
#if TARGET_OS_IOS
NSURL *attachmentURL = [NSURL URLWithString:currentImageURL];
if (attachmentURL) {
[self loadAttachmentForURL:attachmentURL
completionHandler:^(UNNotificationAttachment *attachment) {
self.bestAttemptContent.attachments = @[ attachment ];
[self deliverNotification];
}];
} else {
FIRMessagingLoggerError(kFIRMessagingServiceExtensionImageInvalidURL,
@"The Image URL provided is invalid %@.", currentImageURL);
[self deliverNotification];
}
#else
[self deliverNotification];
#endif
}

#if TARGET_OS_IOS
- (void)loadAttachmentForURL:(NSURL *)attachmentURL
completionHandler:(void (^)(UNNotificationAttachment *))completionHandler {
__block UNNotificationAttachment *attachment = nil;

NSURLSession *session = [NSURLSession
sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
[[session
downloadTaskWithURL:attachmentURL
completionHandler:^(NSURL *temporaryFileLocation, NSURLResponse *response, NSError *error) {
if (error != nil) {
FIRMessagingLoggerError(kFIRMessagingServiceExtensionImageNotDownloaded,
@"Failed to download image given URL %@, error: %@\n",
attachmentURL, error);
completionHandler(attachment);
return;
}

NSFileManager *fileManager = [NSFileManager defaultManager];
NSString *fileExtension =
[NSString stringWithFormat:@".%@", [response.suggestedFilename pathExtension]];
NSURL *localURL = [NSURL
fileURLWithPath:[temporaryFileLocation.path stringByAppendingString:fileExtension]];
[fileManager moveItemAtURL:temporaryFileLocation toURL:localURL error:&error];
if (error) {
FIRMessagingLoggerError(
kFIRMessagingServiceExtensionLocalFileNotCreated,
@"Failed to move the image file to local location: %@, error: %@\n", localURL,
error);
completionHandler(attachment);
return;
}

attachment = [UNNotificationAttachment attachmentWithIdentifier:@""
URL:localURL
options:nil
error:&error];
if (error) {
FIRMessagingLoggerError(kFIRMessagingServiceExtensionImageNotAttached,
@"Failed to create attachment with URL %@, error: %@\n",
localURL, error);
completionHandler(attachment);
return;
}
completionHandler(attachment);
}] resume];
}
#endif

- (void)deliverNotification {
if (self.contentHandler) {
self.contentHandler(self.bestAttemptContent);
}
}

@end
13 changes: 13 additions & 0 deletions Firebase/Messaging/Public/FIRMessaging.h
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,8 @@ NS_SWIFT_NAME(MessagingRemoteMessage)
@end

@class FIRMessaging;
@class FIRMessagingExtensionHelper;

/**
* A protocol to handle token update or data message delivery from FCM.
*
Expand Down Expand Up @@ -330,6 +332,17 @@ NS_SWIFT_NAME(Messaging)
*/
+ (instancetype)messaging NS_SWIFT_NAME(messaging());

/**
* FIRMessagingExtensionHelper
*
* Use FIRMessagingExtensionHelper to populate rich UI contents for your notifications.
* e.g. If an image URL is set in your notification payload or on the console, call
* FIRMessagingExtensionHelper API to render it on your notification.
*
* @return An instance of FIRMessagingExtensionHelper that handles the extensions API.
*/
+ (FIRMessagingExtensionHelper *)extensionHelper NS_SWIFT_NAME(serviceExtension()) NS_AVAILABLE_IOS(10.0);

/**
* Unavailable. Use +messaging instead.
*/
Expand Down
34 changes: 34 additions & 0 deletions Firebase/Messaging/Public/FIRMessagingExtensionHelper.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* Copyright 2019 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
#import <UserNotifications/UserNotifications.h>
#endif

NS_ASSUME_NONNULL_BEGIN

__IOS_AVAILABLE(10.0)
@interface FIRMessagingExtensionHelper : NSObject

/// Call this API to complete your notification content modification. If you like to
/// overwrite some properties of the content instead of using the default payload,
/// make sure to make your customized motification to the content before passing it to
/// this call.
- (void)populateNotificationContent:(UNMutableNotificationContent *)content
withContentHandler:(void (^)(UNNotificationContent *_Nonnull))contentHandler;

@end

NS_ASSUME_NONNULL_END
1 change: 1 addition & 0 deletions Firebase/Messaging/Public/FirebaseMessaging.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,4 @@
*/

#import "FIRMessaging.h"
#import "FIRMessagingExtensionHelper.h"