diff --git a/CHANGES.rst b/CHANGES.rst
index 551a0ef5f..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)
===============================================
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/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
diff --git a/RiotShareExtension/Managers/ShareExtensionManager.m b/RiotShareExtension/Managers/ShareExtensionManager.m
index 541b5892d..182a9481c 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"];
@@ -215,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];
@@ -233,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];
@@ -311,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
@@ -327,15 +352,51 @@ 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"];
}
-- (UIAlertController *)compressionPromptForImage:(NSData *)imageData shareBlock:(void(^)())shareBlock
+- (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;
+}
+
+// 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)
@@ -364,8 +425,6 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode)
{
shareBlock();
}
-
- [compressionPrompt dismissViewControllerAnimated:YES completion:nil];
}
}]];
@@ -391,8 +450,6 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode)
{
shareBlock();
}
-
- [compressionPrompt dismissViewControllerAnimated:YES completion:nil];
}
}]];
@@ -419,15 +476,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];
@@ -446,8 +501,6 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode)
{
shareBlock();
}
-
- [compressionPrompt dismissViewControllerAnimated:YES completion:nil];
}
}]];
@@ -455,20 +508,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();
@@ -599,113 +653,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);
- }
-
- }];
+ }
}
}
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