From ca9de747f0676e49e57c0242f57225ad3bbfae36 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Fri, 4 Jan 2019 13:22:31 +0100 Subject: [PATCH 1/8] Make delegate weak on ShareExtensionManager --- RiotShareExtension/Managers/ShareExtensionManager.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RiotShareExtension/Managers/ShareExtensionManager.h b/RiotShareExtension/Managers/ShareExtensionManager.h index 7ba468716..bbd549103 100644 --- a/RiotShareExtension/Managers/ShareExtensionManager.h +++ b/RiotShareExtension/Managers/ShareExtensionManager.h @@ -89,7 +89,7 @@ extern NSString *const kShareExtensionManagerDidUpdateAccountDataNotification; /** A delegate used to notify about needed UI changes when sharing */ -@property (nonatomic) id delegate; +@property (nonatomic, weak) id delegate; /** The singleton instance From ff15000569201fabbb30be9188a66bf69c08ae4f Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Fri, 4 Jan 2019 13:38:31 +0100 Subject: [PATCH 2/8] Enable background task on MXSDKOptions in share extension --- RiotShareExtension/Managers/ShareExtensionManager.m | 3 +++ 1 file changed, 3 insertions(+) diff --git a/RiotShareExtension/Managers/ShareExtensionManager.m b/RiotShareExtension/Managers/ShareExtensionManager.m index 541b5892d..7ea366c5b 100644 --- a/RiotShareExtension/Managers/ShareExtensionManager.m +++ b/RiotShareExtension/Managers/ShareExtensionManager.m @@ -19,6 +19,7 @@ #import @import MobileCoreServices; #import "objc/runtime.h" +#include NSString *const kShareExtensionManagerDidUpdateAccountDataNotification = @"kShareExtensionManagerDidUpdateAccountDataNotification"; @@ -73,6 +74,8 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) sdkOptions.disableIdenticonUseForUserAvatar = YES; // Enable e2e encryption for newly created MXSession sdkOptions.enableCryptoWhenStartingMXSession = YES; + // Use UIKit BackgroundTask for handling background tasks in the SDK + sdkOptions.backgroundModeHandler = [[MXUIKitBackgroundModeHandler alloc] init]; // Customize the localized string table [NSBundle mxk_customizeLocalizedStringTableName:@"Vector"]; From fdaf5ce28d4e1ef000cea16effaf62e59c5b5e02 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Fri, 4 Jan 2019 13:45:41 +0100 Subject: [PATCH 3/8] [ShareExtension] Handle image items manually and remove NSData coercion, as UIlmage to NSData coercion do not work. That was causing screenshot sharing to fail (Fix #2022). --- .../Managers/ShareExtensionManager.m | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/RiotShareExtension/Managers/ShareExtensionManager.m b/RiotShareExtension/Managers/ShareExtensionManager.m index 7ea366c5b..503451029 100644 --- a/RiotShareExtension/Managers/ShareExtensionManager.m +++ b/RiotShareExtension/Managers/ShareExtensionManager.m @@ -218,13 +218,31 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) else if ([itemProvider hasItemConformingToTypeIdentifier:UTTypeImage]) { itemProvider.isLoaded = NO; - [itemProvider loadItemForTypeIdentifier:UTTypeImage options:nil completionHandler:^(NSData *imageData, NSError * _Null_unspecified error) + + [itemProvider loadItemForTypeIdentifier:UTTypeImage options:nil completionHandler:^(id _Nullable itemProviderItem, NSError * _Null_unspecified error) { if (weakSelf) { typeof(self) self = weakSelf; itemProvider.isLoaded = YES; - + + NSData *imageData; + + if ([(NSObject *)itemProviderItem isKindOfClass:[NSData class]]) + { + imageData = (NSData*)itemProviderItem; + } + else if ([(NSObject *)itemProviderItem isKindOfClass:[NSURL class]]) + { + NSURL *imageURL = (NSURL*)itemProviderItem; + imageData = [NSData dataWithContentsOfURL:imageURL]; + } + else if ([(NSObject *)itemProviderItem isKindOfClass:[UIImage class]]) + { + UIImage *image = (UIImage*)itemProviderItem; + imageData = UIImageJPEGRepresentation(image, 1.0); + } + if (imageData) { [self.pendingImages addObject:imageData]; From 58f3e673f96253020bbcfbc71d99074626f45f2b Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Fri, 4 Jan 2019 13:52:26 +0100 Subject: [PATCH 4/8] [ShareExtension] Image sharing: Update compression prompt behavior. Do not propose actual size when one of all selected images is not oriented up to avoid a crash due to a out of memory. --- .../Managers/ShareExtensionManager.m | 70 +++++++++++++------ 1 file changed, 47 insertions(+), 23 deletions(-) diff --git a/RiotShareExtension/Managers/ShareExtensionManager.m b/RiotShareExtension/Managers/ShareExtensionManager.m index 503451029..95457ddaf 100644 --- a/RiotShareExtension/Managers/ShareExtensionManager.m +++ b/RiotShareExtension/Managers/ShareExtensionManager.m @@ -254,10 +254,10 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) if ([self areAttachmentsFullyLoaded]) { - UIAlertController *compressionPrompt = [self compressionPromptForImage:self.pendingImages.firstObject shareBlock:^{ + UIAlertController *compressionPrompt = [self compressionPromptForPendingImagesWithShareBlock:^{ [self sendImages:self.pendingImages withProviders:item.attachments toRoom:room extensionItem:item failureBlock:failureBlock]; }]; - + if (compressionPrompt) { [self.delegate shareExtensionManager:self showImageCompressionPrompt:compressionPrompt]; @@ -348,15 +348,46 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) [self.primaryViewController destroy]; self.primaryViewController = nil; + +- (BOOL)isAPendingImageNotOrientedUp +{ + BOOL isAPendingImageNotOrientedUp = NO; + + for (NSData *imageData in self.pendingImages) + { + @autoreleasepool + { + UIImage *image = [UIImage imageWithData:imageData]; + + if (image && image.imageOrientation != UIImageOrientationUp) + { + isAPendingImageNotOrientedUp = YES; + break; + } + } + } + + return isAPendingImageNotOrientedUp; } -- (UIAlertController *)compressionPromptForImage:(NSData *)imageData shareBlock:(void(^)())shareBlock +// TODO: When select multiple images: +// - Enhance prompt to display sum of all file sizes for each compression. +// - Find a way to choose compression sizes for all images. +- (UIAlertController *)compressionPromptForPendingImagesWithShareBlock:(void(^)(void))shareBlock { + if (!self.pendingImages.count) + { + return nil; + } + UIAlertController *compressionPrompt; - UIImage *image = [UIImage imageWithData:imageData]; + BOOL isAPendingImageNotOrientedUp = [self isAPendingImageNotOrientedUp]; + + NSData *firstImageData = self.pendingImages.firstObject; + UIImage *firstImage = [UIImage imageWithData:firstImageData]; // Get available sizes for this image - MXKImageCompressionSizes compressionSizes = [MXKTools availableCompressionSizesForImage:image originalFileSize:imageData.length]; + MXKImageCompressionSizes compressionSizes = [MXKTools availableCompressionSizesForImage:firstImage originalFileSize:firstImageData.length]; // Apply the compression mode if (compressionSizes.small.fileSize || compressionSizes.medium.fileSize || compressionSizes.large.fileSize) @@ -385,8 +416,6 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) { shareBlock(); } - - [compressionPrompt dismissViewControllerAnimated:YES completion:nil]; } }]]; @@ -412,8 +441,6 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) { shareBlock(); } - - [compressionPrompt dismissViewControllerAnimated:YES completion:nil]; } }]]; @@ -440,15 +467,13 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) { shareBlock(); } - - [compressionPrompt dismissViewControllerAnimated:YES completion:nil]; } }]]; } // To limit memory consumption, we suggest the original resolution only if the image orientation is up, or if the image size is moderate - if (image.imageOrientation == UIImageOrientationUp || !compressionSizes.large.fileSize) + if (!isAPendingImageNotOrientedUp || !compressionSizes.large.fileSize) { NSString *resolution = [NSString stringWithFormat:@"%@ (%d x %d)", [MXTools fileSizeToString:compressionSizes.original.fileSize round:NO], (int)compressionSizes.original.imageSize.width, (int)compressionSizes.original.imageSize.height]; @@ -467,8 +492,6 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) { shareBlock(); } - - [compressionPrompt dismissViewControllerAnimated:YES completion:nil]; } }]]; @@ -476,20 +499,21 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) [compressionPrompt addAction:[UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"cancel"] style:UIAlertActionStyleCancel - handler:^(UIAlertAction * action) { - - if (weakSelf) - { - [compressionPrompt dismissViewControllerAnimated:YES completion:nil]; - } - - }]]; + handler:nil]]; } else { - self.imageCompressionMode = ImageCompressionModeNone; + if (isAPendingImageNotOrientedUp && self.pendingImages.count > 1) + { + self.imageCompressionMode = ImageCompressionModeSmall; + } + else + { + self.imageCompressionMode = ImageCompressionModeNone; + } + if (shareBlock) { shareBlock(); From bf37e48a1b06266a985e09c392b46ec4c90b3ea7 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Fri, 4 Jan 2019 13:55:46 +0100 Subject: [PATCH 5/8] [ShareExtension] Improve performance of `sendImages` method in ShareExtensionManager to avoid out of memory crash. --- .../Managers/ShareExtensionManager.m | 218 ++++++++++-------- 1 file changed, 122 insertions(+), 96 deletions(-) diff --git a/RiotShareExtension/Managers/ShareExtensionManager.m b/RiotShareExtension/Managers/ShareExtensionManager.m index 95457ddaf..c791d78fc 100644 --- a/RiotShareExtension/Managers/ShareExtensionManager.m +++ b/RiotShareExtension/Managers/ShareExtensionManager.m @@ -644,113 +644,139 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) - (void)sendImages:(NSMutableArray *)imageDatas withProviders:(NSArray*)itemProviders toRoom:(MXRoom *)room extensionItem:(NSExtensionItem *)extensionItem failureBlock:(void(^)(NSError *error))failureBlock { + if (imageDatas.count == 0) + { + NSLog(@"[ShareExtensionManager] sendImages: no images to send."); + + if (failureBlock) + { + failureBlock(nil); + } + return; + } + [self didStartSendingToRoom:room]; __block NSUInteger count = imageDatas.count; for (NSInteger index = 0; index < imageDatas.count; index++) { - NSItemProvider *itemProvider = itemProviders[index]; - NSData *imageData = imageDatas[index]; - UIImage *image = [UIImage imageWithData:imageData]; - - if (!image) + @autoreleasepool { - NSLog(@"[ShareExtensionManager] loadItemForTypeIdentifier: failed."); - if (failureBlock) - { - failureBlock(nil); - failureBlock = nil; - } - return; - } - - // Prepare the image - UIImage *convertedImage = image; - - if (self.imageCompressionMode == ImageCompressionModeSmall) - { - convertedImage = [MXKTools reduceImage:image toFitInSize:CGSizeMake(MXKTOOLS_SMALL_IMAGE_SIZE, MXKTOOLS_SMALL_IMAGE_SIZE)]; - } - else if (self.imageCompressionMode == ImageCompressionModeMedium) - { - convertedImage = [MXKTools reduceImage:image toFitInSize:CGSizeMake(MXKTOOLS_MEDIUM_IMAGE_SIZE, MXKTOOLS_MEDIUM_IMAGE_SIZE)]; - } - else if (self.imageCompressionMode == ImageCompressionModeLarge) - { - convertedImage = [MXKTools reduceImage:image toFitInSize:CGSizeMake(self.actualLargeSize, self.actualLargeSize)]; - } - - // Make sure the uploaded image orientation is up - convertedImage = [MXKTools forceImageOrientationUp:convertedImage]; - - NSString *mimeType; - if ([itemProvider hasItemConformingToTypeIdentifier:(__bridge NSString *)kUTTypePNG]) - { - mimeType = @"image/png"; - if (convertedImage != image) - { - imageData = UIImagePNGRepresentation(convertedImage); - } - } - else if ([itemProvider hasItemConformingToTypeIdentifier:(__bridge NSString *)kUTTypeJPEG]) - { - mimeType = @"image/jpeg"; - if (convertedImage != image) - { - imageData = UIImageJPEGRepresentation(convertedImage, 0.9); - } - } - else - { - // Other image types like GIF - NSString *imageFileName = itemProvider.registeredTypeIdentifiers[0]; - mimeType = (__bridge_transfer NSString *) UTTypeCopyPreferredTagWithClass((__bridge CFStringRef)imageFileName, kUTTagClassMIMEType); - } - - // Sanity check - if (!mimeType) - { - NSLog(@"[ShareExtensionManager] sendImage failed. Cannot determine MIME type of %@", itemProvider); - if (failureBlock) - { - failureBlock(nil); - } - return; - } - - UIImage *thumbnail = nil; - // Thumbnail is useful only in case of encrypted room - if (room.summary.isEncrypted) - { - thumbnail = [MXKTools reduceImage:convertedImage toFitInSize:CGSizeMake(800, 600)]; - if (thumbnail == convertedImage) - { - thumbnail = nil; - } - } - - __weak typeof(self) weakSelf = self; - - [room sendImage:imageData withImageSize:convertedImage.size mimeType:mimeType andThumbnail:thumbnail localEcho:nil success:^(NSString *eventId) { + NSItemProvider *itemProvider = itemProviders[index]; + NSData *imageData = imageDatas[index]; + UIImage *image = [UIImage imageWithData:imageData]; - if (!--count && weakSelf) + if (!image) { - typeof(self) self = weakSelf; + NSLog(@"[ShareExtensionManager] loadItemForTypeIdentifier: failed."); + if (failureBlock) + { + failureBlock(nil); + } + return; + } + + // Prepare the image + UIImage *convertedImage; + CGSize newImageSize; + + switch (self.imageCompressionMode) { + case ImageCompressionModeSmall: + newImageSize = CGSizeMake(MXKTOOLS_SMALL_IMAGE_SIZE, MXKTOOLS_SMALL_IMAGE_SIZE); + break; + case ImageCompressionModeMedium: + newImageSize = CGSizeMake(MXKTOOLS_MEDIUM_IMAGE_SIZE, MXKTOOLS_MEDIUM_IMAGE_SIZE); + break; + case ImageCompressionModeLarge: + newImageSize = CGSizeMake(self.actualLargeSize, self.actualLargeSize); + break; + default: + newImageSize = CGSizeZero; + break; + } + + if (CGSizeEqualToSize(newImageSize, CGSizeZero)) + { + // No resize to make + // Make sure the uploaded image orientation is up + convertedImage = [MXKTools forceImageOrientationUp:image]; + } + else + { + // Resize the image and set image in right orientation too + convertedImage = [MXKTools resizeImageWithData:imageData toFitInSize:newImageSize]; + } + + NSString *mimeType; + if ([itemProvider hasItemConformingToTypeIdentifier:(__bridge NSString *)kUTTypePNG]) + { + mimeType = @"image/png"; + if (convertedImage != image) + { + imageData = UIImagePNGRepresentation(convertedImage); + } + } + else if ([itemProvider hasItemConformingToTypeIdentifier:(__bridge NSString *)kUTTypeJPEG]) + { + mimeType = @"image/jpeg"; + if (convertedImage != image) + { + imageData = UIImageJPEGRepresentation(convertedImage, 0.9); + } + } + else + { + // Other image types like GIF + NSString *imageFileName = itemProvider.registeredTypeIdentifiers[0]; + mimeType = (__bridge_transfer NSString *) UTTypeCopyPreferredTagWithClass((__bridge CFStringRef)imageFileName, kUTTagClassMIMEType); + } + + // Sanity check + if (!mimeType) + { + NSLog(@"[ShareExtensionManager] sendImage failed. Cannot determine MIME type of %@", itemProvider); + if (failureBlock) + { + failureBlock(nil); + } + return; + } + + UIImage *thumbnail = nil; + // Thumbnail is useful only in case of encrypted room + if (room.summary.isEncrypted) + { + thumbnail = [MXKTools reduceImage:convertedImage toFitInSize:CGSizeMake(800, 600)]; + if (thumbnail == convertedImage) + { + thumbnail = nil; + } + } + + __weak typeof(self) weakSelf = self; + + [room sendImage:imageData withImageSize:convertedImage.size mimeType:mimeType andThumbnail:thumbnail localEcho:nil success:^(NSString *eventId) { - [self resetPendingData]; - [self.shareExtensionContext completeRequestReturningItems:@[extensionItem] completionHandler:nil]; - } + if (!--count && weakSelf) + { + typeof(self) self = weakSelf; + + [self resetPendingData]; + [self completeRequestReturningItems:@[extensionItem] completionHandler:nil]; + } + + } failure:^(NSError *error) { + + NSLog(@"[ShareExtensionManager] sendImage failed."); + if (failureBlock) + { + failureBlock(error); + } + + }]; - } failure:^(NSError *error) { - - NSLog(@"[ShareExtensionManager] sendImage failed."); - if (failureBlock) - { - failureBlock(error); - } - - }]; + } } } From 3cb34107787349ed83738a44c2996391b1e19905 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Fri, 4 Jan 2019 13:57:35 +0100 Subject: [PATCH 6/8] [ShareExtension] Add a workaround to free memory when share extension completes or cancel before fixing all memory leaks. --- RiotShareExtension/Managers/ShareExtensionManager.m | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/RiotShareExtension/Managers/ShareExtensionManager.m b/RiotShareExtension/Managers/ShareExtensionManager.m index c791d78fc..182a9481c 100644 --- a/RiotShareExtension/Managers/ShareExtensionManager.m +++ b/RiotShareExtension/Managers/ShareExtensionManager.m @@ -332,6 +332,10 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) [self.primaryViewController destroy]; self.primaryViewController = nil; + + // FIXME: Share extension memory usage increase when launched several times and then crash due to some memory leaks. + // For now, we force the share extension to exit and free memory. + [NSException raise:@"Kill the app extension" format:@"Free memory used by share extension"]; } #pragma mark - Private @@ -348,6 +352,11 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) [self.primaryViewController destroy]; self.primaryViewController = nil; + + // FIXME: Share extension memory usage increase when launched several times and then crash due to some memory leaks. + // For now, we force the share extension to exit and free memory. + [NSException raise:@"Kill the app extension" format:@"Free memory used by share extension"]; +} - (BOOL)isAPendingImageNotOrientedUp { From 397facc12f74e44a1fff1b2fe70dc4a0ea29ffe5 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Fri, 4 Jan 2019 14:01:30 +0100 Subject: [PATCH 7/8] Update changes --- CHANGES.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.rst b/CHANGES.rst index 0b6e26b73..1d2a36ccb 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -6,6 +6,7 @@ Improvements: Bug fix: * Registration: email or phone number is no more skippable (#2140). +* Share extension: Fix screenshot sharing (#2022). Improve image sharing performance to avoid out of memory crash. Changes in 0.7.8 (2018-12-12) =============================================== From 54bdf2948ea5f7c4f24b61aa93cea0d545c0cbd5 Mon Sep 17 00:00:00 2001 From: manuroe Date: Fri, 4 Jan 2019 16:59:07 +0100 Subject: [PATCH 8/8] version++ A PR was missing. --- CHANGES.rst | 7 ++++++- Riot/SupportingFiles/Info.plist | 4 ++-- RiotShareExtension/SupportingFiles/Info.plist | 4 ++-- SiriIntents/Info.plist | 4 ++-- 4 files changed, 12 insertions(+), 7 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index d01f13dd1..cadc8d154 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,3 +1,9 @@ +Changes in 0.7.10 (2019-01-04) +=============================================== + +Bug fix: + * Share extension: Fix screenshot sharing (#2022). Improve image sharing performance to avoid out of memory crash. + Changes in 0.7.9 (2019-01-04) =============================================== @@ -6,7 +12,6 @@ Improvements: Bug fix: * Registration: email or phone number is no more skippable (#2140). -* Share extension: Fix screenshot sharing (#2022). Improve image sharing performance to avoid out of memory crash. Changes in 0.7.8 (2018-12-12) =============================================== diff --git a/Riot/SupportingFiles/Info.plist b/Riot/SupportingFiles/Info.plist index 65dfe3390..f4fb1ea3e 100644 --- a/Riot/SupportingFiles/Info.plist +++ b/Riot/SupportingFiles/Info.plist @@ -17,11 +17,11 @@ CFBundlePackageType APPL CFBundleShortVersionString - 0.7.9 + 0.7.10 CFBundleSignature ???? CFBundleVersion - 0.7.9 + 0.7.10 ITSAppUsesNonExemptEncryption ITSEncryptionExportComplianceCode diff --git a/RiotShareExtension/SupportingFiles/Info.plist b/RiotShareExtension/SupportingFiles/Info.plist index 564cffca5..af4f7ad4b 100644 --- a/RiotShareExtension/SupportingFiles/Info.plist +++ b/RiotShareExtension/SupportingFiles/Info.plist @@ -17,9 +17,9 @@ CFBundlePackageType XPC! CFBundleShortVersionString - 0.7.9 + 0.7.10 CFBundleVersion - 0.7.9 + 0.7.10 NSExtension NSExtensionAttributes diff --git a/SiriIntents/Info.plist b/SiriIntents/Info.plist index 35241169c..6d62b7e83 100644 --- a/SiriIntents/Info.plist +++ b/SiriIntents/Info.plist @@ -17,9 +17,9 @@ CFBundlePackageType XPC! CFBundleShortVersionString - 0.7.9 + 0.7.10 CFBundleVersion - 0.7.9 + 0.7.10 NSExtension NSExtensionAttributes