summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNicoleYarroch <nicole@livio.io>2018-03-12 12:54:21 -0400
committerNicoleYarroch <nicole@livio.io>2018-03-12 12:54:21 -0400
commit1ebdf4ff608307f146fd4a0be0296fa25c87956c (patch)
tree738a180d90db05ca435abe35231510eabe2523c3
parent1d4caa4b6474a0f451b178aabff767a247c1bb76 (diff)
parent0d4686c4e28201e56f4ec2830d15ad243a43dd3e (diff)
downloadsdl_ios-bugfix/issue_877_library_does_not_compile_cocoapods.tar.gz
Merge branch 'develop' into bugfix/issue_877_library_does_not_compile_cocoapodsbugfix/issue_877_library_does_not_compile_cocoapods
-rw-r--r--CHANGELOG.md4
-rw-r--r--LICENSE2
-rw-r--r--SmartDeviceLink-iOS.podspec2
-rw-r--r--SmartDeviceLink.podspec2
-rw-r--r--SmartDeviceLink/Info.plist2
-rw-r--r--SmartDeviceLink/SDLArtwork.h47
-rw-r--r--SmartDeviceLink/SDLArtwork.m70
-rw-r--r--SmartDeviceLink/SDLError.h1
-rw-r--r--SmartDeviceLink/SDLError.m9
-rw-r--r--SmartDeviceLink/SDLErrorConstants.h4
-rw-r--r--SmartDeviceLink/SDLFileManager.h32
-rw-r--r--SmartDeviceLink/SDLFileManager.m75
-rw-r--r--SmartDeviceLink/SDLFileManagerConstants.h28
-rw-r--r--SmartDeviceLink/SDLImage.h6
-rw-r--r--SmartDeviceLink/SDLImage.m9
-rw-r--r--SmartDeviceLink/SDLProxy.m2
-rw-r--r--SmartDeviceLink/SDLStreamingMediaLifecycleManager.m4
-rw-r--r--SmartDeviceLink/SDLTouchManager.m36
-rw-r--r--SmartDeviceLinkTests/DevAPISpecs/SDLArtworkSpec.m190
-rw-r--r--SmartDeviceLinkTests/DevAPISpecs/SDLFileManagerSpec.m582
-rw-r--r--SmartDeviceLinkTests/DevAPISpecs/SDLLifecycleConfigurationSpec.m8
-rw-r--r--SmartDeviceLinkTests/RPCSpecs/StructSpecs/SDLImageSpec.m85
-rw-r--r--SmartDeviceLinkTests/TestMultipleFilesConnectionManager.m1
-rw-r--r--SmartDeviceLink_Example/Classes/ProxyManager.m362
-rw-r--r--SmartDeviceLink_Example/Info.plist2
25 files changed, 1094 insertions, 471 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2106919ae..fde6ac2a2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,7 @@
+# 5.1.1
+### Bug Fixes
+* Fixed import statement, allows for Cocoapods release
+
# 5.1.0
### Enhancements
* Log unsuccessful RPC responses automatically [#811](https://github.com/smartdevicelink/sdl_ios/issues/811).
diff --git a/LICENSE b/LICENSE
index 255251f17..d59b381b9 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2017 SmartDeviceLink Consortium, Inc.
+Copyright (c) 2017 - 2018 SmartDeviceLink Consortium, Inc.
All rights reserved.
Redistribution and use in source and binary forms, with or without
diff --git a/SmartDeviceLink-iOS.podspec b/SmartDeviceLink-iOS.podspec
index f7bbdfbe1..fde2486b4 100644
--- a/SmartDeviceLink-iOS.podspec
+++ b/SmartDeviceLink-iOS.podspec
@@ -1,7 +1,7 @@
Pod::Spec.new do |s|
s.name = "SmartDeviceLink-iOS"
-s.version = "5.1.0"
+s.version = "5.1.1"
s.summary = "Connect your app with cars!"
s.homepage = "https://github.com/smartdevicelink/SmartDeviceLink-iOS"
s.license = { :type => "New BSD", :file => "LICENSE" }
diff --git a/SmartDeviceLink.podspec b/SmartDeviceLink.podspec
index 8a299308d..06bbf5c18 100644
--- a/SmartDeviceLink.podspec
+++ b/SmartDeviceLink.podspec
@@ -1,7 +1,7 @@
Pod::Spec.new do |s|
s.name = "SmartDeviceLink"
-s.version = "5.1.0"
+s.version = "5.1.1"
s.summary = "Connect your app with cars!"
s.homepage = "https://github.com/smartdevicelink/SmartDeviceLink-iOS"
s.license = { :type => "New BSD", :file => "LICENSE" }
diff --git a/SmartDeviceLink/Info.plist b/SmartDeviceLink/Info.plist
index e790eb616..b4453872e 100644
--- a/SmartDeviceLink/Info.plist
+++ b/SmartDeviceLink/Info.plist
@@ -15,7 +15,7 @@
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
- <string>5.0.0</string>
+ <string>5.1.1</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
diff --git a/SmartDeviceLink/SDLArtwork.h b/SmartDeviceLink/SDLArtwork.h
index ee9cf74f8..dbdcdffd5 100644
--- a/SmartDeviceLink/SDLArtwork.h
+++ b/SmartDeviceLink/SDLArtwork.h
@@ -34,7 +34,23 @@ NS_ASSUME_NONNULL_BEGIN
*
* @return An instance of this class to be passed to the file manager.
*/
-+ (instancetype)artworkWithImage:(UIImage *)image name:(NSString *)name asImageFormat:(SDLArtworkImageFormat)imageFormat NS_SWIFT_UNAVAILABLE("Use the standard initializer and set persistant to false");
++ (instancetype)artworkWithImage:(UIImage *)image name:(NSString *)name asImageFormat:(SDLArtworkImageFormat)imageFormat NS_SWIFT_UNAVAILABLE("Use the standard initializer and set persistant to false") __deprecated_msg("Use artworkWithImage:asImageFormat: instead");
+
+/**
+ * Convenience Helper to create an ephemeral artwork from an image. A unique name will be assigned to the image. This name is a string representation of the image's data which is created by hashing the data using the MD5 algorithm.
+ *
+ * This is an ephemeral file, it will not be persisted through sessions / ignition cycles. Any files that you do not *know* you will use in future sessions should be created through this method. For example, album / artist artwork should be ephemeral.
+ *
+ * Persistent files should be created using `persistentArtworkWithImage:name:asImageFormat:`
+ *
+ * @warning It is strongly recommended to pass the file url using an SDLFile initializer instead of the image. If you pass the UIImage, it is loaded into memory, and will be dumped to a temporary file. This will create a duplicate file. *Only pass a UIImage if the image is not stored on disk*.
+ *
+ * @param image The UIImage to be sent to the remote head unit
+ * @param imageFormat Whether the image should be converted to a PNG or JPG before transmission. Images with transparency or few colors should be PNGs. Images with many colors should be JPGs.
+ *
+ * @return An instance of this class to be passed to the file manager.
+ */
++ (instancetype)artworkWithImage:(UIImage *)image asImageFormat:(SDLArtworkImageFormat)imageFormat NS_SWIFT_UNAVAILABLE("Use the standard initializer and set persistant to false");
/**
* Convenience Helper to create a persistent artwork from an image.
@@ -51,7 +67,23 @@ NS_ASSUME_NONNULL_BEGIN
*
* @return An instance of this class to be passed to the file manager.
*/
-+ (instancetype)persistentArtworkWithImage:(UIImage *)image name:(NSString *)name asImageFormat:(SDLArtworkImageFormat)imageFormat NS_SWIFT_UNAVAILABLE("Use the standard initializer and set persistant to true");
++ (instancetype)persistentArtworkWithImage:(UIImage *)image name:(NSString *)name asImageFormat:(SDLArtworkImageFormat)imageFormat NS_SWIFT_UNAVAILABLE("Use the standard initializer and set persistant to true") __deprecated_msg("Use persistentArtworkWithImage:asImageFormat instead");
+
+/**
+ * Convenience Helper to create a persistent artwork from an image. A unique name will be assigned to the image. This name is a string representation of the image's data which is created by hashing the data using the MD5 algorithm.
+ *
+ * This is a persistent file, it will be persisted through sessions / ignition cycles. You will only have a limited space for all files, so be sure to only persist files that are required for all or most sessions. For example, menu artwork should be persistent.
+ *
+ * Ephemeral files should be created using `ephemeralArtworkWithImage:name:asImageFormat:`
+ *
+ * @warning It is strongly recommended to pass the file url using an SDLFile initializer instead of the image. If you pass the UIImage, it is loaded into memory, and will be dumped to a temporary file. This will create a duplicate file. *Only pass a UIImage if the image is not stored on disk*.
+ *
+ * @param image The UIImage to be sent to the remote head unit
+ * @param imageFormat Whether the image should be converted to a PNG or JPG before transmission. Images with transparency or few colors should be PNGs. Images with many colors should be JPGs.
+ *
+ * @return An instance of this class to be passed to the file manager.
+ */
++ (instancetype)persistentArtworkWithImage:(UIImage *)image asImageFormat:(SDLArtworkImageFormat)imageFormat NS_SWIFT_UNAVAILABLE("Use the standard initializer and set persistant to true");
/**
* Create a file for transmission to the remote system from a UIImage.
@@ -65,6 +97,17 @@ NS_ASSUME_NONNULL_BEGIN
*/
- (instancetype)initWithImage:(UIImage *)image name:(NSString *)name persistent:(BOOL)persistent asImageFormat:(SDLArtworkImageFormat)imageFormat;
+/**
+ * Create a file for transmission to the remote system from a UIImage. A unique name will be assigned to the image. This name is a string representation of the image's data which is created by hashing the data using the MD5 algorithm.
+
+ * @param image The UIImage to be sent to the remote head unit
+ * @param persistent Whether or not the artwork should be persistent.
+ * @param imageFormat Whether the image should be converted to a PNG or JPG before transmission. Images with transparency or few colors should be PNGs. Images with many colors should be JPGs.
+ *
+ * @return An instance of this class to be passed to the file manager.
+ */
+- (instancetype)initWithImage:(UIImage *)image persistent:(BOOL)persistent asImageFormat:(SDLArtworkImageFormat)imageFormat;
+
@end
NS_ASSUME_NONNULL_END
diff --git a/SmartDeviceLink/SDLArtwork.m b/SmartDeviceLink/SDLArtwork.m
index ac965f0dd..062374858 100644
--- a/SmartDeviceLink/SDLArtwork.m
+++ b/SmartDeviceLink/SDLArtwork.m
@@ -7,9 +7,8 @@
//
#import "SDLArtwork.h"
-
#import "SDLFileType.h"
-
+#import <CommonCrypto/CommonDigest.h>
NS_ASSUME_NONNULL_BEGIN
@@ -28,29 +27,88 @@ NS_ASSUME_NONNULL_BEGIN
return [[self alloc] initWithImage:image name:name persistent:NO asImageFormat:imageFormat];
}
++ (instancetype)artworkWithImage:(UIImage *)image asImageFormat:(SDLArtworkImageFormat)imageFormat {
+ return [[self alloc] initWithImage:image persistent:NO asImageFormat:imageFormat];
+}
+
+ (instancetype)persistentArtworkWithImage:(UIImage *)image name:(NSString *)name asImageFormat:(SDLArtworkImageFormat)imageFormat {
return [[self alloc] initWithImage:image name:name persistent:YES asImageFormat:imageFormat];
}
++ (instancetype)persistentArtworkWithImage:(UIImage *)image asImageFormat:(SDLArtworkImageFormat)imageFormat {
+ return [[self alloc] initWithImage:image persistent:YES asImageFormat:imageFormat];
+}
+
#pragma mark Private Lifecycle
- (instancetype)initWithImage:(UIImage *)image name:(NSString *)name persistent:(BOOL)persistent asImageFormat:(SDLArtworkImageFormat)imageFormat {
- NSData *imageData = nil;
- NSString *fileExtension = nil;
+ return [super initWithData:[self.class sdl_dataForUIImage:image imageFormat:imageFormat] name:name fileExtension:[self.class sdl_fileExtensionForImageFormat:imageFormat] persistent:persistent];
+}
+
+- (instancetype)initWithImage:(UIImage *)image persistent:(BOOL)persistent asImageFormat:(SDLArtworkImageFormat)imageFormat {
+ NSData *imageData = [self.class sdl_dataForUIImage:image imageFormat:imageFormat];
+ NSString *imageName = [self.class sdl_md5HashFromNSData:imageData];
+ return [super initWithData:[self.class sdl_dataForUIImage:image imageFormat:imageFormat] name:(imageName != nil ? imageName : @"") fileExtension:[self.class sdl_fileExtensionForImageFormat:imageFormat] persistent:persistent];
+}
+/**
+ * Returns the JPG or PNG image data for a UIImage.
+ *
+ * @param image A UIImage
+ * @param imageFormat The image format to use when converting the UIImage to NSData
+ * @return The image data
+ */
++ (NSData *)sdl_dataForUIImage:(UIImage *)image imageFormat:(SDLArtworkImageFormat)imageFormat {
+ NSData *imageData = nil;
switch (imageFormat) {
case SDLArtworkImageFormatPNG: {
imageData = UIImagePNGRepresentation(image);
- fileExtension = @"png";
} break;
case SDLArtworkImageFormatJPG: {
imageData = UIImageJPEGRepresentation(image, 0.85);
+ } break;
+ }
+ return imageData;
+}
+
+/**
+ * Returns the file extension for the image format.
+ *
+ * @param imageFormat Whether the image is a PNG or JPG
+ * @return The file extension for the image format
+ */
++ (NSString *)sdl_fileExtensionForImageFormat:(SDLArtworkImageFormat)imageFormat {
+ NSString *fileExtension = nil;
+ switch (imageFormat) {
+ case SDLArtworkImageFormatPNG: {
+ fileExtension = @"png";
+ } break;
+ case SDLArtworkImageFormatJPG: {
fileExtension = @"jpg";
} break;
}
+ return fileExtension;
+}
- return [super initWithData:imageData name:name fileExtension:fileExtension persistent:persistent];
+/**
+ * Creates a string representation of NSData by hashing the data using the MD5 hash function. This string is not guaranteed to be unique as collisions can occur, however collisions are extremely rare.
+ *
+ * Sourced from https://stackoverflow.com/questions/2018550/how-do-i-create-an-md5-hash-of-a-string-in-cocoa
+ *
+ * @param data The data to hash
+ * @return A MD5 hash of the data
+ */
++ (NSString *)sdl_md5HashFromNSData:(NSData *)data {
+ if (data == nil) { return nil; }
+
+ unsigned char hash[CC_MD5_DIGEST_LENGTH];
+ CC_MD5([data bytes], (CC_LONG)[data length], hash);
+ NSMutableString *formattedHash = [NSMutableString stringWithCapacity:CC_MD5_DIGEST_LENGTH * 2];
+ for (int i = 0; i < CC_MD5_DIGEST_LENGTH; i += 1) {
+ [formattedHash appendFormat:@"%02x", hash[i]];
+ }
+ return formattedHash;
}
@end
diff --git a/SmartDeviceLink/SDLError.h b/SmartDeviceLink/SDLError.h
index bdbabd7ae..220f5941d 100644
--- a/SmartDeviceLink/SDLError.h
+++ b/SmartDeviceLink/SDLError.h
@@ -44,6 +44,7 @@ extern SDLErrorDomain *const SDLErrorDomainFileManager;
+ (NSError *)sdl_fileManager_unableToDelete_ErrorWithUserInfo:(NSDictionary *)userInfo;
+ (NSError *)sdl_fileManager_fileDoesNotExistError;
+ (NSError *)sdl_fileManager_fileUploadCanceled;
++ (NSError *)sdl_fileManager_dataMissingError;
@end
diff --git a/SmartDeviceLink/SDLError.m b/SmartDeviceLink/SDLError.m
index ed9fa2899..7be6feeb9 100644
--- a/SmartDeviceLink/SDLError.m
+++ b/SmartDeviceLink/SDLError.m
@@ -163,6 +163,15 @@ SDLErrorDomain *const SDLErrorDomainFileManager = @"com.sdl.filemanager.error";
return [NSError errorWithDomain:SDLErrorDomainFileManager code:SDLFileManagerUploadCanceled userInfo:userInfo];
}
++ (NSError *)sdl_fileManager_dataMissingError {
+ NSDictionary<NSString *, NSString *> *userInfo = @{
+ NSLocalizedDescriptionKey: NSLocalizedString(@"The file upload was canceled", nil),
+ NSLocalizedFailureReasonErrorKey: NSLocalizedString(@"The data for the file is missing", nil),
+ NSLocalizedRecoverySuggestionErrorKey: NSLocalizedString(@"Make sure the data used to create the file is valid", nil)
+ };
+ return [NSError errorWithDomain:SDLErrorDomainFileManager code:SDLFileManagerErrorFileDataMissing userInfo:userInfo];
+}
+
#pragma mark SDLUploadFileOperation
+ (NSError *)sdl_fileManager_fileDoesNotExistError {
diff --git a/SmartDeviceLink/SDLErrorConstants.h b/SmartDeviceLink/SDLErrorConstants.h
index a3b694469..f44108255 100644
--- a/SmartDeviceLink/SDLErrorConstants.h
+++ b/SmartDeviceLink/SDLErrorConstants.h
@@ -82,4 +82,8 @@ typedef NS_ENUM(NSInteger, SDLFileManagerError) {
* One or more of multiple files being uploaded or deleted failed.
*/
SDLFileManagerMultipleFileDeleteTasksFailed = -8,
+ /*
+ * The file data is nil or empty.
+ */
+ SDLFileManagerErrorFileDataMissing = -9,
};
diff --git a/SmartDeviceLink/SDLFileManager.h b/SmartDeviceLink/SDLFileManager.h
index 720e95044..e29b4117d 100644
--- a/SmartDeviceLink/SDLFileManager.h
+++ b/SmartDeviceLink/SDLFileManager.h
@@ -8,6 +8,7 @@
#import <Foundation/Foundation.h>
+#import "SDLArtwork.h"
#import "SDLFileManagerConstants.h"
@class SDLFile;
@@ -117,12 +118,39 @@ typedef void (^SDLFileManagerStartupCompletionHandler)(BOOL success, NSError *__
/**
* Uploads an array of files to the remote file system. The files will be uploaded in the order in which they are added to the array, with the first file to be uploaded at index 0. The upload queue is sequential, meaning that once a upload request is sent to Core, the queue waits until a response is received from Core before the next the next upload request is sent.
*
- * @param files An array of SDLFiles to be sent
- * @param completionHandler an optional SDLFileManagerMultiUploadCompletionHandler
+ * @param files An array of SDLFiles to be sent
+ * @param completionHandler An optional SDLFileManagerMultiUploadCompletionHandler
*/
- (void)uploadFiles:(NSArray<SDLFile *> *)files completionHandler:(nullable SDLFileManagerMultiUploadCompletionHandler)completionHandler NS_SWIFT_NAME(upload(files:completionHandler:));
/**
+ * Uploads an artwork file to the remote file system and returns the name of the uploaded artwork once completed. If an artwork with the same name is already on the remote system, the artwork is not uploaded and the artwork name is simply returned.
+ *
+ * @param artwork A SDLArwork containing an image to be sent
+ * @param completion An optional completion handler that returns the name of the uploaded artwork. It also returns an error if the upload fails.
+ */
+- (void)uploadArtwork:(SDLArtwork *)artwork completionHandler:(nullable SDLFileManagerUploadArtworkCompletionHandler)completion NS_SWIFT_NAME(upload(artwork:completionHandler:));
+
+/**
+ * Uploads an array of artworks to the remote file system. The artworks will be uploaded in the order in which they are added to the array, with the first file to be uploaded at index 0. The upload queue is sequential, meaning that once a upload request is sent to Core, the queue waits until a response is received from Core before the next the next upload request is sent.
+ *
+ * @param artworks An array of SDLArtworks to be sent
+ * @param completion An optional SDLFileManagerMultiUploadArtworkCompletionHandler
+ */
+- (void)uploadArtworks:(NSArray<SDLArtwork *> *)artworks completionHandler:(nullable SDLFileManagerMultiUploadArtworkCompletionHandler)completion NS_SWIFT_NAME(upload(artworks:completionHandler:));
+
+/**
+ * Uploads an array of artworks to the remote file system. The artworks will be uploaded in the order in which they are added to the array, with the first file to be uploaded at index 0. The upload queue is sequential, meaning that once a upload request is sent to Core, the queue waits until a response is received from Core before the next the next upload request is sent.
+ *
+ * The optional progress handler can be used to keep track of the upload progress. After each artwork upload, the progress handler returns the artwork name, the upload percentage and an error, if one occured during the upload process. The progress handler also includes an option to cancel the upload of all remaining files in queue.
+ *
+ * @param artworks An array of SDLArtworks to be sent
+ * @param progressHandler An optional SDLFileManagerMultiUploadArtworkProgressHandler
+ * @param completion An optional SDLFileManagerMultiUploadArtworkCompletionHandler
+ */
+- (void)uploadArtworks:(NSArray<SDLArtwork *> *)artworks progressHandler:(nullable SDLFileManagerMultiUploadArtworkProgressHandler)progressHandler completionHandler:(nullable SDLFileManagerMultiUploadArtworkCompletionHandler)completion NS_SWIFT_NAME(upload(artworks:progressHandler:completionHandler:));
+
+/**
* A URL to the directory where temporary files are stored. When an SDLFile is created with NSData, it writes to a temporary file until the file manager finishes uploading it.
*
* The SDL library manages the creation and deletion of these files and you should not have to touch this directory at all.
diff --git a/SmartDeviceLink/SDLFileManager.m b/SmartDeviceLink/SDLFileManager.m
index 8942ec2d7..beda90390 100644
--- a/SmartDeviceLink/SDLFileManager.m
+++ b/SmartDeviceLink/SDLFileManager.m
@@ -255,7 +255,7 @@ SDLFileManagerState *const SDLFileManagerStateStartupError = @"StartupError";
}
#pragma mark - Uploading
-
+#pragma mark Files
- (void)uploadFiles:(NSArray<SDLFile *> *)files completionHandler:(nullable SDLFileManagerMultiUploadCompletionHandler)completionHandler {
[self uploadFiles:files progressHandler:nil completionHandler:completionHandler];
}
@@ -274,11 +274,6 @@ SDLFileManagerState *const SDLFileManagerStateStartupError = @"StartupError";
for(SDLFile *file in files) {
dispatch_group_enter(uploadFilesTask);
- // HAX: [#827](https://github.com/smartdevicelink/sdl_ios/issues/827) Older versions of Core had a bug where list files would cache incorrectly. This led to attempted uploads failing due to the system thinking they were already there when they were not.
- if (!file.persistent && [self.remoteFileNames containsObject:file.name] && ![self.uploadedEphemeralFileNames containsObject:file.name]) {
- file.overwrite = true;
- }
-
[self uploadFile:file completionHandler:^(BOOL success, NSUInteger bytesAvailable, NSError * _Nullable error) {
if(!success) {
failedUploads[file.name] = error;
@@ -347,9 +342,9 @@ SDLFileManagerState *const SDLFileManagerStateStartupError = @"StartupError";
}
- (void)uploadFile:(SDLFile *)file completionHandler:(nullable SDLFileManagerUploadCompletionHandler)handler {
- if (file == nil) {
+ if (file == nil || file.data.length == 0) {
if (handler != nil) {
- handler(NO, self.bytesAvailable, [NSError sdl_fileManager_unableToUploadError]);
+ handler(NO, self.bytesAvailable, [NSError sdl_fileManager_dataMissingError]);
}
return;
}
@@ -362,12 +357,16 @@ SDLFileManagerState *const SDLFileManagerStateStartupError = @"StartupError";
return;
}
+ // HAX: [#827](https://github.com/smartdevicelink/sdl_ios/issues/827) Older versions of Core had a bug where list files would cache incorrectly. This led to attempted uploads failing due to the system thinking they were already there when they were not.
+ if (!file.persistent && [self.remoteFileNames containsObject:file.name] && ![self.uploadedEphemeralFileNames containsObject:file.name]) {
+ file.overwrite = true;
+ }
+
// Check our overwrite settings and error out if it would overwrite
if (file.overwrite == NO && [self.remoteFileNames containsObject:file.name]) {
if (handler != nil) {
handler(NO, self.bytesAvailable, [NSError sdl_fileManager_cannotOverwriteError]);
}
-
return;
}
@@ -403,6 +402,64 @@ SDLFileManagerState *const SDLFileManagerStateStartupError = @"StartupError";
[self.transactionQueue addOperation:uploadOperation];
}
+#pragma mark Artworks
+
+- (void)uploadArtwork:(SDLArtwork *)artwork completionHandler:(nullable SDLFileManagerUploadArtworkCompletionHandler)completion {
+ [self uploadFile:artwork completionHandler:^(BOOL success, NSUInteger bytesAvailable, NSError * _Nullable error) {
+ if (completion == nil) { return; }
+ if ([self isErrorACannotOverwriteError:error]) {
+ // Artwork with same name already uploaded to remote
+ return completion(true, artwork.name, bytesAvailable, nil);
+ }
+ completion(success, artwork.name, bytesAvailable, error);
+ }];
+}
+
+- (void)uploadArtworks:(NSArray<SDLArtwork *> *)artworks completionHandler:(nullable SDLFileManagerMultiUploadArtworkCompletionHandler)completion {
+ [self uploadArtworks:artworks progressHandler:nil completionHandler:completion];
+}
+
+- (void)uploadArtworks:(NSArray<SDLArtwork *> *)artworks progressHandler:(nullable SDLFileManagerMultiUploadArtworkProgressHandler)progressHandler completionHandler:(nullable SDLFileManagerMultiUploadArtworkCompletionHandler)completion {
+ if (artworks.count == 0) {
+ @throw [NSException sdl_missingFilesException];
+ }
+
+ [self uploadFiles:artworks progressHandler:^BOOL(SDLFileName * _Nonnull fileName, float uploadPercentage, NSError * _Nullable error) {
+ if (progressHandler == nil) { return YES; }
+ if ([self isErrorACannotOverwriteError:error]) {
+ return progressHandler(fileName, uploadPercentage, nil);
+ }
+ return progressHandler(fileName, uploadPercentage, error);
+ } completionHandler:^(NSError * _Nullable error) {
+ if (completion == nil) { return; }
+
+ NSMutableSet<NSString *> *successfulArtworkUploadNames = [NSMutableSet set];
+ for (SDLArtwork *artwork in artworks) {
+ [successfulArtworkUploadNames addObject:artwork.name];
+ }
+ NSMutableDictionary *unsuccessfulArtworkUploadErrorUserInfo = [[NSMutableDictionary alloc] initWithDictionary:error.userInfo];
+
+ if (error != nil) {
+ for (NSString *erroredArtworkName in error.userInfo) {
+ if (![self isErrorACannotOverwriteError:[error.userInfo objectForKey:erroredArtworkName]]) {
+ [successfulArtworkUploadNames removeObject:erroredArtworkName];
+ } else {
+ // An overwrite error means that an artwork with the same name is already uploaded to the remote
+ [unsuccessfulArtworkUploadErrorUserInfo removeObjectForKey:erroredArtworkName];
+ }
+ }
+ }
+
+ return completion([NSArray arrayWithArray:[successfulArtworkUploadNames allObjects]], unsuccessfulArtworkUploadErrorUserInfo.count == 0 ? nil : [[NSError alloc] initWithDomain:error.domain code:error.code userInfo:unsuccessfulArtworkUploadErrorUserInfo]);
+ }];
+}
+
+- (BOOL)isErrorACannotOverwriteError:(NSError * _Nullable)error {
+ if (error != nil && error.code == SDLFileManagerErrorCannotOverwrite) {
+ return YES;
+ }
+ return NO;
+}
#pragma mark - Temporary Files
diff --git a/SmartDeviceLink/SDLFileManagerConstants.h b/SmartDeviceLink/SDLFileManagerConstants.h
index e448322c0..39950b4f6 100644
--- a/SmartDeviceLink/SDLFileManagerConstants.h
+++ b/SmartDeviceLink/SDLFileManagerConstants.h
@@ -66,5 +66,33 @@ typedef void(^SDLFileManagerMultiDeleteCompletionHandler)(NSError *__nullable er
*/
typedef void (^SDLFileManagerListFilesCompletionHandler)(BOOL success, NSUInteger bytesAvailable, NSArray<NSString *> *fileNames, NSError *__nullable error);
+/**
+ * A completion handler called after a response from Core to a artwork upload request.
+ *
+ * @param success Whether or not the upload was successful
+ * @param artworkName The unique identifier for the uploaded file.
+ * @param bytesAvailable The amount of space left for files on Core
+ * @param error The error that occurred during the request if one occurred, nil if not
+ */
+typedef void (^SDLFileManagerUploadArtworkCompletionHandler)(BOOL success, NSString *artworkName, NSUInteger bytesAvailable, NSError *__nullable error);
+
+/**
+ * A completion handler called after a set of upload artwork requests has completed.
+ *
+ * @param artworkNames The names of the artwork files successfully uploaded to the remote
+ * @param error The userInfo dictionary property, of type <NSString: NSError>, contains information on all failed uploads. The key is the name of the artwork that did not upload properly, the value is an error describing what went wrong on that particular upload attempt. If all artworks are uploaded successfully, nil is returned
+ */
+typedef void (^SDLFileManagerMultiUploadArtworkCompletionHandler)(NSArray<NSString *> *artworkNames, NSError *__nullable error);
+
+/**
+ * In a multiple request send, a handler called after each response from Core to an artwork upload request.
+ *
+ * @param artworkName The unique identifier for the uploaded file
+ * @param uploadPercentage The percentage of uploaded data. The upload percentage is calculated as the total file size of all attempted artwork uploads (regardless of the successfulness of the upload) divided by the sum of the data in all the files
+ * @param error The error that occurred during the upload request if one occurred, nil if no error occured
+ *
+ * @return Return NO to cancel any artworks that have not yet been sent. Return YES to continue sending artworks
+ */
+typedef BOOL (^SDLFileManagerMultiUploadArtworkProgressHandler)(NSString *artworkName, float uploadPercentage, NSError *__nullable error);
NS_ASSUME_NONNULL_END
diff --git a/SmartDeviceLink/SDLImage.h b/SmartDeviceLink/SDLImage.h
index cb21b9545..cb7cb089c 100644
--- a/SmartDeviceLink/SDLImage.h
+++ b/SmartDeviceLink/SDLImage.h
@@ -17,6 +17,10 @@ NS_ASSUME_NONNULL_BEGIN
- (instancetype)initWithName:(NSString *)name ofType:(SDLImageType)imageType;
+- (instancetype)initWithName:(NSString *)name;
+
+- (instancetype)initWithStaticImageValue:(UInt16)staticImageValue;
+
/**
* @abstract The static hex icon value or the binary image file name identifier (sent by SDLPutFile)
*
@@ -25,7 +29,7 @@ NS_ASSUME_NONNULL_BEGIN
@property (strong, nonatomic) NSString *value;
/**
- * @abstract Describes, whether it is a static or dynamic image
+ * @abstract Describes whether the image is static or dynamic
*
* Required
*/
diff --git a/SmartDeviceLink/SDLImage.m b/SmartDeviceLink/SDLImage.m
index ebd7e09d4..0728295aa 100644
--- a/SmartDeviceLink/SDLImage.m
+++ b/SmartDeviceLink/SDLImage.m
@@ -22,6 +22,15 @@ NS_ASSUME_NONNULL_BEGIN
return self;
}
+- (instancetype)initWithName:(NSString *)name {
+ return [self initWithName:name ofType:SDLImageTypeDynamic];
+}
+
+- (instancetype)initWithStaticImageValue:(UInt16)staticImageValue {
+ NSString *value = [NSString stringWithFormat:@"%hu", staticImageValue];
+ return [self initWithName:value ofType:SDLImageTypeStatic];
+}
+
- (void)setValue:(NSString *)value {
[store sdl_setObject:value forName:SDLNameValue];
}
diff --git a/SmartDeviceLink/SDLProxy.m b/SmartDeviceLink/SDLProxy.m
index 7311b7043..7be8ee121 100644
--- a/SmartDeviceLink/SDLProxy.m
+++ b/SmartDeviceLink/SDLProxy.m
@@ -41,7 +41,7 @@ typedef NSString SDLVehicleMake;
typedef void (^URLSessionTaskCompletionHandler)(NSData *data, NSURLResponse *response, NSError *error);
typedef void (^URLSessionDownloadTaskCompletionHandler)(NSURL *location, NSURLResponse *response, NSError *error);
-NSString *const SDLProxyVersion = @"5.1.0";
+NSString *const SDLProxyVersion = @"5.1.1";
const float StartSessionTime = 10.0;
const float NotifyProxyClosedDelay = (float)0.1;
const int PoliciesCorrelationId = 65535;
diff --git a/SmartDeviceLink/SDLStreamingMediaLifecycleManager.m b/SmartDeviceLink/SDLStreamingMediaLifecycleManager.m
index 31ed2ce5e..c21e5ffe2 100644
--- a/SmartDeviceLink/SDLStreamingMediaLifecycleManager.m
+++ b/SmartDeviceLink/SDLStreamingMediaLifecycleManager.m
@@ -291,7 +291,9 @@ typedef void(^SDLVideoCapabilityResponseHandler)(SDLVideoStreamingCapability *_N
[self sdl_sendBackgroundFrames];
[self.touchManager cancelPendingTouches];
- self.restartVideoStream = YES;
+
+ [self sdl_stopAudioSession];
+ [self sdl_stopVideoSession];
}
// Per Apple's guidelines: https://developer.apple.com/library/content/documentation/iPhone/Conceptual/iPhoneOSProgrammingGuide/StrategiesforHandlingAppStateTransitions/StrategiesforHandlingAppStateTransitions.html
diff --git a/SmartDeviceLink/SDLTouchManager.m b/SmartDeviceLink/SDLTouchManager.m
index 40b3b1c54..b411619f1 100644
--- a/SmartDeviceLink/SDLTouchManager.m
+++ b/SmartDeviceLink/SDLTouchManager.m
@@ -215,8 +215,10 @@ static NSUInteger const MaximumNumberOfTouches = 2;
self.currentPinchGesture = [[SDLPinchGesture alloc] initWithFirstTouch:self.previousTouch secondTouch:touch];
self.previousPinchDistance = self.currentPinchGesture.distance;
if ([self.touchEventDelegate respondsToSelector:@selector(touchManager:pinchDidStartInView:atCenterPoint:)]) {
- UIView *hitView = (self.hitTester != nil) ? [self.hitTester viewForPoint:self.currentPinchGesture.center] : nil;
- [self.touchEventDelegate touchManager:self pinchDidStartInView:hitView atCenterPoint:self.currentPinchGesture.center];
+ dispatch_async(dispatch_get_main_queue(), ^{
+ UIView *hitView = (self.hitTester != nil) ? [self.hitTester viewForPoint:self.currentPinchGesture.center] : nil;
+ [self.touchEventDelegate touchManager:self pinchDidStartInView:hitView atCenterPoint:self.currentPinchGesture.center];
+ });
}
} break;
}
@@ -257,8 +259,10 @@ static NSUInteger const MaximumNumberOfTouches = 2;
_performingTouchType = SDLPerformingTouchTypePanningTouch;
if ([self.touchEventDelegate respondsToSelector:@selector(touchManager:panningDidStartInView:atPoint:)]) {
- UIView *hitView = (self.hitTester != nil) ? [self.hitTester viewForPoint:touch.location] : nil;
- [self.touchEventDelegate touchManager:self panningDidStartInView:hitView atPoint:touch.location];
+ dispatch_async(dispatch_get_main_queue(), ^{
+ UIView *hitView = (self.hitTester != nil) ? [self.hitTester viewForPoint:touch.location] : nil;
+ [self.touchEventDelegate touchManager:self panningDidStartInView:hitView atPoint:touch.location];
+ });
}
} break;
case SDLPerformingTouchTypePanningTouch: {
@@ -284,16 +288,20 @@ static NSUInteger const MaximumNumberOfTouches = 2;
[self sdl_setMultiTouchFingerTouchForTouch:touch];
if (self.currentPinchGesture.isValid) {
if ([self.touchEventDelegate respondsToSelector:@selector(touchManager:pinchDidEndInView:atCenterPoint:)]) {
- UIView *hitView = (self.hitTester != nil) ? [self.hitTester viewForPoint:self.currentPinchGesture.center] : nil;
- [self.touchEventDelegate touchManager:self pinchDidEndInView:hitView atCenterPoint:self.currentPinchGesture.center];
+ dispatch_async(dispatch_get_main_queue(), ^{
+ UIView *hitView = (self.hitTester != nil) ? [self.hitTester viewForPoint:self.currentPinchGesture.center] : nil;
+ [self.touchEventDelegate touchManager:self pinchDidEndInView:hitView atCenterPoint:self.currentPinchGesture.center];
+ });
}
self.currentPinchGesture = nil;
}
} break;
case SDLPerformingTouchTypePanningTouch: {
if ([self.touchEventDelegate respondsToSelector:@selector(touchManager:panningDidEndInView:atPoint:)]) {
- UIView *hitView = (self.hitTester != nil) ? [self.hitTester viewForPoint:touch.location] : nil;
- [self.touchEventDelegate touchManager:self panningDidEndInView:hitView atPoint:touch.location];
+ dispatch_async(dispatch_get_main_queue(), ^{
+ UIView *hitView = (self.hitTester != nil) ? [self.hitTester viewForPoint:touch.location] : nil;
+ [self.touchEventDelegate touchManager:self panningDidEndInView:hitView atPoint:touch.location];
+ });
}
} break;
case SDLPerformingTouchTypeSingleTouch: {
@@ -313,8 +321,10 @@ static NSUInteger const MaximumNumberOfTouches = 2;
CGPoint centerPoint = CGPointCenterOfPoints(touch.location,
self.singleTapTouch.location);
if ([self.touchEventDelegate respondsToSelector:@selector(touchManager:didReceiveDoubleTapForView:atPoint:)]) {
- UIView *hitView = (self.hitTester != nil) ? [self.hitTester viewForPoint:centerPoint] : nil;
- [self.touchEventDelegate touchManager:self didReceiveDoubleTapForView:hitView atPoint:centerPoint];
+ dispatch_async(dispatch_get_main_queue(), ^{
+ UIView *hitView = (self.hitTester != nil) ? [self.hitTester viewForPoint:centerPoint] : nil;
+ [self.touchEventDelegate touchManager:self didReceiveDoubleTapForView:hitView atPoint:centerPoint];
+ });
}
}
@@ -398,8 +408,10 @@ static NSUInteger const MaximumNumberOfTouches = 2;
strongSelf.singleTapTouch = nil;
[strongSelf sdl_cancelSingleTapTimer];
if ([strongSelf.touchEventDelegate respondsToSelector:@selector(touchManager:didReceiveSingleTapForView:atPoint:)]) {
- UIView *hitView = (self.hitTester != nil) ? [self.hitTester viewForPoint:point] : nil;
- [strongSelf.touchEventDelegate touchManager:strongSelf didReceiveSingleTapForView:hitView atPoint:point];
+ dispatch_async(dispatch_get_main_queue(), ^{
+ UIView *hitView = (self.hitTester != nil) ? [self.hitTester viewForPoint:point] : nil;
+ [strongSelf.touchEventDelegate touchManager:strongSelf didReceiveSingleTapForView:hitView atPoint:point];
+ });
}
});
}
diff --git a/SmartDeviceLinkTests/DevAPISpecs/SDLArtworkSpec.m b/SmartDeviceLinkTests/DevAPISpecs/SDLArtworkSpec.m
index 026ab2609..f6fba3240 100644
--- a/SmartDeviceLinkTests/DevAPISpecs/SDLArtworkSpec.m
+++ b/SmartDeviceLinkTests/DevAPISpecs/SDLArtworkSpec.m
@@ -4,90 +4,148 @@
#import "SDLArtwork.h"
#import "SDLFileType.h"
+@interface SDLArtwork()
++ (NSString *)sdl_md5HashFromNSData:(NSData *)data;
+@end
+
QuickSpecBegin(SDLArtworkSpec)
describe(@"SDLArtwork", ^{
- __block SDLArtwork *testArtwork = nil;
-
- describe(@"when created", ^{
- context(@"As a PNG", ^{
- __block NSString *testArtworkName = nil;
- __block UIImage *testImage = nil;
- __block SDLArtworkImageFormat testFormat = NSNotFound;
-
- beforeEach(^{
- testImage = [UIImage imageNamed:@"testImagePNG" inBundle:[NSBundle bundleForClass:[self class]] compatibleWithTraitCollection:nil];
- testArtworkName = @"Test Artwork";
- testFormat = SDLArtworkImageFormatPNG;
-
- testArtwork = [[SDLArtwork alloc] initWithImage:testImage name:testArtworkName persistent:NO asImageFormat:testFormat];
- });
-
- it(@"should correctly store image data", ^{
- expect(testArtwork.data).to(equal(UIImagePNGRepresentation(testImage)));
+ __block SDLArtwork *expectedArtwork = nil;
+ __block UIImage *testImagePNG = nil;
+ __block UIImage *testImagePNG2 = nil;
+ __block UIImage *testImageJPG = nil;
+
+ beforeEach(^{
+ testImagePNG = [UIImage imageNamed:@"testImagePNG" inBundle:[NSBundle bundleForClass:[self class]] compatibleWithTraitCollection:nil];
+ testImagePNG2 = [UIImage imageNamed:@"TestLockScreenAppIcon" inBundle:[NSBundle bundleForClass:[self class]] compatibleWithTraitCollection:nil];
+ testImageJPG = [UIImage imageNamed:@"testImageJPG.jpg" inBundle:[NSBundle bundleForClass:[self class]] compatibleWithTraitCollection:nil];
+ });
+
+ context(@"On creation", ^{
+ describe(@"When setting the image", ^{
+ __block NSData *expectedImageData = nil;
+
+ it(@"should set the image data successfully for an image with a name", ^ {
+ expectedImageData = UIImagePNGRepresentation(testImagePNG);
+ expectedArtwork = [[SDLArtwork alloc] initWithImage:testImagePNG name:@"testImage" persistent:true asImageFormat:SDLArtworkImageFormatPNG];
});
-
- it(@"should correctly store name", ^{
- expect(testArtwork.name).to(equal(testArtworkName));
+
+ it(@"should set the image data successfully for an image without a name", ^ {
+ expectedImageData = UIImagePNGRepresentation(testImagePNG);
+ expectedArtwork = [[SDLArtwork alloc] initWithImage:testImagePNG persistent:true asImageFormat:SDLArtworkImageFormatPNG];
});
-
- it(@"should correctly store format", ^{
- expect(testArtwork.fileType).to(equal(SDLFileTypePNG));
+
+ it(@"should not set the image data if the image is nil", ^{
+ UIImage *testImage = nil;
+ expectedImageData = UIImagePNGRepresentation(testImage);
+ expectedArtwork = [[SDLArtwork alloc] initWithImage:testImage persistent:true asImageFormat:SDLArtworkImageFormatPNG];
});
-
- it(@"should correctly store persistence", ^{
- expect(@(testArtwork.persistent)).to(equal(@NO));
+
+ afterEach(^{
+ if (expectedImageData == nil) {
+ expect(expectedArtwork.data).to(beNil());
+ } else {
+ expect(expectedImageData).to(equal(expectedArtwork.data));
+ }
});
});
-
- context(@"As a JPG", ^{
- __block NSString *testArtworkName = nil;
- __block UIImage *testImage = nil;
- __block SDLArtworkImageFormat testFormat = NSNotFound;
-
- beforeEach(^{
- testImage = [UIImage imageNamed:@"testImagePNG" inBundle:[NSBundle bundleForClass:[self class]] compatibleWithTraitCollection:nil];
- testArtworkName = @"Test Artwork";
- testFormat = SDLArtworkImageFormatJPG;
-
- testArtwork = [[SDLArtwork alloc] initWithImage:testImage name:testArtworkName persistent:NO asImageFormat:testFormat];
+
+ describe(@"When setting the name", ^{
+ __block NSString *expectedName = nil;
+
+ it(@"should set the passed name correctly", ^{
+ NSString *imageName = @"TestImageName";
+ expectedName = imageName;
+ expectedArtwork = [[SDLArtwork alloc] initWithImage:testImagePNG name:imageName persistent:true asImageFormat:SDLArtworkImageFormatPNG];
});
-
- it(@"should correctly store image data", ^{
- expect(testArtwork.data).to(equal(UIImageJPEGRepresentation(testImage, 0.85)));
+
+ context(@"When no name is provided", ^{
+ it(@"should create a unique name based on the hash of the image", ^{
+ expectedName = [SDLArtwork sdl_md5HashFromNSData:UIImagePNGRepresentation(testImagePNG)];
+ expectedArtwork = [[SDLArtwork alloc] initWithImage:testImagePNG persistent:true asImageFormat:SDLArtworkImageFormatPNG];
+ });
+
+ it(@"should create an empty string if the image is nil", ^{
+ UIImage *testNilImage = nil;
+ expectedName = @"";
+ expectedArtwork = [[SDLArtwork alloc] initWithImage:testNilImage persistent:true asImageFormat:SDLArtworkImageFormatPNG];
+ });
});
-
- it(@"should correctly store name", ^{
- expect(testArtwork.name).to(equal(testArtworkName));
+
+ afterEach(^{
+ if (expectedName.length == 0) {
+ expect(expectedArtwork.name).to(beNil());
+ } else {
+ expect(expectedName).to(equal(expectedArtwork.name));
+ }
});
-
- it(@"should correctly store format", ^{
- expect(testArtwork.fileType).to(equal(SDLFileTypeJPEG));
+ });
+
+ describe(@"When setting the image format", ^{
+ __block SDLFileType expectedImageFormat = nil;
+ __block NSData *expectedImageData = nil;
+
+ it(@"should create a PNG image successfully", ^{
+ expectedImageFormat = SDLFileTypePNG;
+ expectedImageData = UIImagePNGRepresentation(testImagePNG);
+ expectedArtwork = [[SDLArtwork alloc] initWithImage:testImagePNG name:@"testImagePNG" persistent:true asImageFormat:SDLArtworkImageFormatPNG];
});
-
- it(@"should correctly store persistence", ^{
- expect(@(testArtwork.persistent)).to(equal(@NO));
+
+ it(@"should create a JPG image successfully", ^{
+ expectedImageFormat = SDLFileTypeJPEG;
+ expectedImageData = UIImageJPEGRepresentation(testImageJPG, 0.85);
+ expectedArtwork = [[SDLArtwork alloc] initWithImage:testImageJPG name:@"testImageJPG" persistent:true asImageFormat:SDLArtworkImageFormatJPG];
+ });
+
+ afterEach(^{
+ expect(expectedImageFormat).to(equal(expectedArtwork.fileType));
+ expect(expectedImageData).to(equal(expectedArtwork.data));
});
});
-
- describe(@"to be persistent", ^{
- __block NSString *testArtworkName = nil;
- __block UIImage *testImage = nil;
- __block SDLArtworkImageFormat testFormat = NSNotFound;
-
- beforeEach(^{
- testImage = [UIImage imageNamed:@"testImagePNG" inBundle:[NSBundle bundleForClass:[self class]] compatibleWithTraitCollection:nil];
- testArtworkName = @"Test Artwork";
- testFormat = SDLArtworkImageFormatPNG;
-
- testArtwork = [[SDLArtwork alloc] initWithImage:testImage name:testArtworkName persistent:YES asImageFormat:testFormat];
+
+ describe(@"When setting the image persistence", ^{
+ __block Boolean expectedPersistance = false;
+
+ it(@"should set the image persistence to ephemeral successfully", ^{
+ Boolean persistance = false;
+ expectedPersistance = persistance;
+ expectedArtwork = [[SDLArtwork alloc] initWithImage:testImagePNG name:@"testImagePNG" persistent:persistance asImageFormat:SDLArtworkImageFormatPNG];
+ });
+
+ it(@"should set the image persistence to permanent successfully", ^{
+ Boolean persistance = true;
+ expectedPersistance = persistance;
+ expectedArtwork = [[SDLArtwork alloc] initWithImage:testImagePNG name:@"testImagePNG" persistent:persistance asImageFormat:SDLArtworkImageFormatPNG];
});
-
- it(@"is persistent", ^{
- expect(@(testArtwork.persistent)).to(equal(@YES));
+
+ afterEach(^{
+ expect(expectedPersistance).to(equal(expectedArtwork.persistent));
});
});
});
+
+ context(@"A name created from hashing the image data should be unique to the image", ^{
+ __block NSString *expectedName1 = nil;
+ __block NSString *expectedName2 = nil;
+
+ beforeEach(^{
+ expectedName1 = nil;
+ expectedName2 = nil;
+ });
+
+ it(@"should create the same name for the same image", ^{
+ expectedName1 = [SDLArtwork sdl_md5HashFromNSData:UIImagePNGRepresentation(testImagePNG)];
+ expectedName2 = [SDLArtwork sdl_md5HashFromNSData:UIImagePNGRepresentation(testImagePNG)];
+ expect(expectedName1).to(equal(expectedName2));
+ });
+
+ it(@"should not create the same name for different images", ^{
+ expectedName1 = [SDLArtwork sdl_md5HashFromNSData:UIImagePNGRepresentation(testImagePNG)];
+ expectedName2 = [SDLArtwork sdl_md5HashFromNSData:UIImagePNGRepresentation(testImagePNG2)];
+ expect(expectedName1).toNot(equal(expectedName2));
+ });
+ });
});
QuickSpecEnd
diff --git a/SmartDeviceLinkTests/DevAPISpecs/SDLFileManagerSpec.m b/SmartDeviceLinkTests/DevAPISpecs/SDLFileManagerSpec.m
index 9a582b54b..14dd311fa 100644
--- a/SmartDeviceLinkTests/DevAPISpecs/SDLFileManagerSpec.m
+++ b/SmartDeviceLinkTests/DevAPISpecs/SDLFileManagerSpec.m
@@ -100,7 +100,6 @@ describe(@"SDLFileManager", ^{
});
it(@"should remain in the stopped state after receiving the response if disconnected", ^{
-
expect(testFileManager.currentState).toEventually(match(SDLFileManagerStateShutdown));
});
});
@@ -202,7 +201,7 @@ describe(@"SDLFileManager", ^{
__block SDLPutFile *sentPutFile = nil;
__block NSData *testFileData = nil;
- context(@"when there is a remote file named the same thing", ^{
+ context(@"when there is a remote file with the same file name", ^{
beforeEach(^{
testFileName = [testInitialFileNames anyObject];
testFileData = [@"someData" dataUsingEncoding:NSUTF8StringEncoding];
@@ -312,27 +311,45 @@ describe(@"SDLFileManager", ^{
});
context(@"when allow overwrite is NO", ^{
- __block SDLRPCRequest *lastRequest = nil;
+ __block NSString *testUploadFileName = nil;
+ __block Boolean testUploadOverwrite = NO;
beforeEach(^{
- testUploadFile.overwrite = NO;
+ testUploadFileName = [testInitialFileNames anyObject];
+ });
- [testFileManager uploadFile:testUploadFile completionHandler:^(BOOL success, NSUInteger bytesAvailable, NSError * _Nullable error) {
- completionSuccess = success;
- completionBytesAvailable = bytesAvailable;
- completionError = error;
- }];
+ it(@"should not upload the file if persistance is YES", ^{
+ SDLFile *persistantFile = [[SDLFile alloc] initWithData:testFileData name:testUploadFileName fileExtension:@"bin" persistent:YES];
+ persistantFile.overwrite = testUploadOverwrite;
+
+ waitUntilTimeout(1, ^(void (^done)(void)){
+ [testFileManager uploadFile:persistantFile completionHandler:^(BOOL success, NSUInteger bytesAvailable, NSError * _Nullable error) {
+ expect(testConnectionManager.receivedRequests.lastObject).toNot(beAnInstanceOf([SDLPutFile class]));
+ expect(@(success)).to(beFalse());
+ expect(@(bytesAvailable)).to(equal(@(testFileManager.bytesAvailable)));
+ expect(error).to(equal([NSError sdl_fileManager_cannotOverwriteError]));
+ done();
+ }];
+ });
+ });
- [NSThread sleepForTimeInterval:0.1];
+ it(@"should upload the file if persistance is NO", ^{
+ SDLFile *unPersistantFile = [[SDLFile alloc] initWithData:testFileData name:testUploadFileName fileExtension:@"bin" persistent:NO];
+ unPersistantFile.overwrite = testUploadOverwrite;
- lastRequest = testConnectionManager.receivedRequests.lastObject;
- });
+ waitUntilTimeout(1, ^(void (^done)(void)){
+ [testFileManager uploadFile:unPersistantFile completionHandler:^(BOOL success, NSUInteger bytesAvailable, NSError * _Nullable error) {
+ expect(testConnectionManager.receivedRequests.lastObject).to(beAnInstanceOf([SDLPutFile class]));
+ expect(@(success)).to(beTrue());
+ expect(@(bytesAvailable)).to(equal(@(testFileManager.bytesAvailable)));
+ expect(error).to(beNil());
+ done();
+ }];
+
+ [NSThread sleepForTimeInterval:0.1];
- it(@"should have called the completion handler with correct data", ^{
- expect(lastRequest).toNot(beAnInstanceOf([SDLPutFile class]));
- expect(@(completionSuccess)).to(equal(@NO));
- expect(@(completionBytesAvailable)).to(equal(@(testFileManager.bytesAvailable)));
- expect(completionError).to(equal([NSError sdl_fileManager_cannotOverwriteError]));
+ [testConnectionManager respondToLastRequestWithResponse:testListFilesResponse];
+ });
});
});
});
@@ -435,6 +452,109 @@ describe(@"SDLFileManager", ^{
});
});
});
+
+ context(@"When the file data is nil", ^{
+ it(@"should call the completion handler with an error", ^{
+ SDLFile *emptyFile = [[SDLFile alloc] initWithData:[[NSData alloc] init] name:@"testFile" fileExtension:@"bin" persistent:YES];
+
+ waitUntilTimeout(1, ^(void (^done)(void)){
+ [testFileManager uploadFile:emptyFile completionHandler:^(BOOL success, NSUInteger bytesAvailable, NSError * _Nullable error) {
+ expect(testConnectionManager.receivedRequests.lastObject).toNot(beAnInstanceOf([SDLPutFile class]));
+ expect(@(success)).to(beFalse());
+ expect(@(bytesAvailable)).to(equal(@(testFileManager.bytesAvailable)));
+ expect(error).to(equal([NSError sdl_fileManager_dataMissingError]));
+ done();
+ }];
+ });
+ });
+ });
+ });
+
+ describe(@"uploading artwork", ^{
+ __block UIImage *testUIImage = nil;
+ __block SDLArtwork *testArtwork = nil;
+
+ __block NSString *expectedArtworkName = nil;
+ __block Boolean expectedOverwrite = false;
+ __block NSUInteger expectedRemoteFilesCount = 0;
+ __block NSUInteger expectedBytesAvailable = 0;
+
+ __block Boolean expectedToUploadArtwork = true;
+ __block NSUInteger expectedRPCsSentCount = 1;
+
+ beforeEach(^{
+ UIGraphicsBeginImageContextWithOptions(CGSizeMake(5, 5), YES, 0);
+ CGContextRef context = UIGraphicsGetCurrentContext();
+ [[UIColor blackColor] setFill];
+ CGContextFillRect(context, CGRectMake(0, 0, 5, 5));
+ UIImage *blackSquareImage = UIGraphicsGetImageFromCurrentImageContext();
+ UIGraphicsEndImageContext();
+ testUIImage = blackSquareImage;
+
+ expectedRemoteFilesCount = testInitialFileNames.count;
+ expect(testFileManager.remoteFileNames.count).to(equal(expectedRemoteFilesCount));
+
+ expectedBytesAvailable = initialSpaceAvailable;
+ expect(testFileManager.bytesAvailable).to(equal(expectedBytesAvailable));
+
+ expectedRPCsSentCount = 1; // ListFiles RPC
+ expect(testConnectionManager.receivedRequests.count).to(equal(expectedRPCsSentCount));
+ });
+
+ it(@"should not upload the artwork again and simply return the artwork name when sending artwork that has already been uploaded", ^{
+ expectedArtworkName = [testListFilesResponse.filenames firstObject];
+ expectedOverwrite = false;
+ expectedRemoteFilesCount = testInitialFileNames.count;
+ expectedBytesAvailable = initialSpaceAvailable;
+ expectedToUploadArtwork = false;
+ });
+
+ it(@"should upload the artwork and return the artwork name when done when sending artwork that has not yet been uploaded", ^{
+ expectedArtworkName = @"uniqueArtworkName";
+ expectedOverwrite = false;
+ expectedRemoteFilesCount = testInitialFileNames.count + 1;
+ expectedBytesAvailable = 22;
+ expectedToUploadArtwork = true;
+ expectedRPCsSentCount += 1;
+ });
+
+ it(@"should upload the artwork and return the artwork name when done when sending arwork that is already been uploaded but overwrite is enabled", ^{
+ expectedArtworkName = [testListFilesResponse.filenames firstObject];
+ expectedOverwrite = true;
+ expectedRemoteFilesCount = testInitialFileNames.count;
+ expectedBytesAvailable = initialSpaceAvailable;
+ expectedToUploadArtwork = true;
+ expectedRPCsSentCount += 1;
+ });
+
+ afterEach(^{
+ testArtwork = [[SDLArtwork alloc] initWithImage:testUIImage name:expectedArtworkName persistent:true asImageFormat:SDLArtworkImageFormatPNG];
+ testArtwork.overwrite = expectedOverwrite;
+
+ waitUntilTimeout(1, ^(void (^done)(void)){
+ [testFileManager uploadArtwork:testArtwork completionHandler:^(BOOL success, NSString * _Nonnull artworkName, NSUInteger bytesAvailable, NSError * _Nullable error) {
+ expect(artworkName).to(equal(expectedArtworkName));
+ expect(success).to(beTrue());
+ expect(bytesAvailable).to(equal(expectedBytesAvailable));
+ expect(error).to(beNil());
+
+ expect(testFileManager.remoteFileNames.count).to(equal(expectedRemoteFilesCount));
+
+ done();
+ }];
+
+ if (expectedToUploadArtwork) {
+ [NSThread sleepForTimeInterval:0.1];
+
+ SDLPutFileResponse *successfulPutFileResponse = [[SDLPutFileResponse alloc] init];
+ successfulPutFileResponse.success = @YES;
+ successfulPutFileResponse.spaceAvailable = @(expectedBytesAvailable);
+ [testConnectionManager respondToLastRequestWithResponse:successfulPutFileResponse];
+ }
+ });
+
+ expect(testConnectionManager.receivedRequests.count).to(equal(expectedRPCsSentCount));
+ });
});
});
@@ -604,6 +724,73 @@ describe(@"SDLFileManager uploading/deleting multiple files", ^{
});
});
+ context(@"and all artworks are uploaded successfully", ^{
+ __block NSMutableArray<SDLArtwork *> *testArtworks = nil;
+ __block NSMutableDictionary *testConnectionManagerResponses;
+ __block NSMutableArray<NSString*> *expectedArtworkNames = nil;
+
+ beforeEach(^{
+ testArtworks = [NSMutableArray array];
+ testConnectionManagerResponses = [NSMutableDictionary dictionary];
+ expectedArtworkNames = [NSMutableArray array];
+ });
+
+ it(@"should upload one artwork successfully", ^{
+ UIGraphicsBeginImageContextWithOptions(CGSizeMake(5, 5), YES, 0);
+ CGContextRef context = UIGraphicsGetCurrentContext();
+ [[UIColor blackColor] setFill];
+ CGContextFillRect(context, CGRectMake(0, 0, 5, 5));
+ UIImage *blackSquareImage = UIGraphicsGetImageFromCurrentImageContext();
+ UIGraphicsEndImageContext();
+
+ SDLArtwork *testArtwork = [SDLArtwork artworkWithImage:blackSquareImage asImageFormat:SDLArtworkImageFormatPNG];
+ [testArtworks addObject:testArtwork];
+ [expectedArtworkNames addObject:testArtwork.name];
+
+ successfulResponse.spaceAvailable = @22;
+ testConnectionManagerResponses[testArtwork.name] = [[TestResponse alloc] initWithResponse:successfulResponse error:nil];
+ expectedSpaceLeft = @22;
+ testConnectionManager.responses = testConnectionManagerResponses;
+ });
+
+ it(@"should upload multiple artworks successfully", ^{
+ NSInteger spaceAvailable = 6000;
+ for (NSUInteger i = 0; i < 200; i += 1) {
+ UIGraphicsBeginImageContextWithOptions(CGSizeMake(5, 5), YES, 0);
+ CGContextRef context = UIGraphicsGetCurrentContext();
+ CGFloat grey = (i % 255) / 255.0;
+ [[UIColor colorWithRed:grey green:grey blue:grey alpha:1.0] setFill];
+ CGContextFillRect(context, CGRectMake(0, 0, 10, 10));
+ UIImage *greySquareImage = UIGraphicsGetImageFromCurrentImageContext();
+ UIGraphicsEndImageContext();
+
+ SDLArtwork *testArtwork = [SDLArtwork artworkWithImage:greySquareImage asImageFormat:SDLArtworkImageFormatPNG];
+ [testArtworks addObject:testArtwork];
+ [expectedArtworkNames addObject:testArtwork.name];
+
+ successfulResponse.spaceAvailable = @(spaceAvailable -= 1);
+ [expectedSuccessfulFileNames addObject:testArtwork.name];
+ testConnectionManagerResponses[testArtwork.name] = [[TestResponse alloc] initWithResponse:successfulResponse error:nil];
+ }
+ expectedSpaceLeft = @(spaceAvailable);
+ testConnectionManager.responses = testConnectionManagerResponses;
+ });
+
+ afterEach(^{
+ waitUntilTimeout(10, ^(void (^done)(void)){
+ [testFileManager uploadArtworks:testArtworks completionHandler:^(NSArray<NSString *> * _Nonnull artworkNames, NSError * _Nullable error) {
+ for (NSString *artworkName in expectedArtworkNames) {
+ expect(artworkNames).to(contain(artworkName));
+ }
+ expect(expectedArtworkNames.count).to(equal(artworkNames.count));
+ expect(error).to(beNil());
+ expect(testFileManager.bytesAvailable).to(equal(expectedSpaceLeft));
+ done();
+ }];
+ });
+ });
+ });
+
context(@"and all files are not uploaded successfully", ^{
__block NSMutableDictionary *testConnectionManagerResponses;
__block NSMutableDictionary *expectedFailedUploads;
@@ -620,69 +807,196 @@ describe(@"SDLFileManager uploading/deleting multiple files", ^{
});
context(@"When the file manager receives a notification from the remote that a file upload failed", ^{
+ describe(@"The correct errors should be returned", ^{
+ beforeEach(^{
+ testFailureIndexStart = -1;
+ testFailureIndexEnd = INT8_MAX;
+ });
+
+ it(@"should return an error when all files fail", ^{
+ testTotalFileCount = 5;
+ testFileNameBase = @"TestAllFilesUnsuccessful";
+ testFailureIndexStart = testTotalFileCount;
+ });
+
+ it(@"should return an error when the first file fails to upload", ^{
+ testTotalFileCount = 5;
+ testFileNameBase = @"TestFirstFileUnsuccessful";
+ testFailureIndexStart = 0;
+ });
+
+ it(@"should return an error when the last file fails to upload", ^{
+ testTotalFileCount = 100;
+ testFileNameBase = @"TestLastFileUnsuccessful";
+ testFailureIndexEnd = (testTotalFileCount - 1);
+ });
+
+ afterEach(^{
+ NSInteger testSpaceAvailable = initialSpaceAvailable;
+ for(int i = 0; i < testTotalFileCount; i += 1) {
+ NSString *testFileName = [NSString stringWithFormat:@"%@%d", testFileNameBase, i];
+ SDLFile *testSDLFile = [SDLFile fileWithData:[@"someTextData" dataUsingEncoding:NSUTF8StringEncoding] name:testFileName fileExtension:@"bin"];
+ testSDLFile.overwrite = true;
+ [testSDLFiles addObject:testSDLFile];
+
+ SDLPutFileResponse *response = [[SDLPutFileResponse alloc] init];
+ NSError *responseError = nil;
+ if (i <= testFailureIndexStart || i >= testFailureIndexEnd) {
+ // Failed response
+ response = failedResponse;
+ response.spaceAvailable = @(testSpaceAvailable);
+ responseError = [NSError sdl_lifecycle_unknownRemoteErrorWithDescription:[NSString stringWithFormat:@"file upload failed: %d", i] andReason:[NSString stringWithFormat:@"some error reason: %d", i]];
+ expectedFailedUploads[testFileName] = responseError;
+ } else {
+ // Successful response
+ response = successfulResponse;
+ response.spaceAvailable = @(testSpaceAvailable -= 1);
+ responseError = nil;
+ [expectedSuccessfulFileNames addObject:testFileName];
+ }
+
+ testConnectionManagerResponses[testFileName] = [[TestResponse alloc] initWithResponse:response error:responseError];
+ }
+
+ testConnectionManager.responses = testConnectionManagerResponses;
+ expectedError = [NSError sdl_fileManager_unableToUpload_ErrorWithUserInfo:expectedFailedUploads];
+ expectedSpaceLeft = @(testSpaceAvailable);
+ });
+ });
+
+ afterEach(^{
+ waitUntilTimeout(10, ^(void (^done)(void)){
+ [testFileManager uploadFiles:testSDLFiles completionHandler:^(NSError * _Nullable error) {
+ expect(error).to(equal(expectedError));
+ expect(testFileManager.bytesAvailable).to(equal(expectedSpaceLeft));
+ done();
+ }];
+ });
+ });
+ });
+
+ context(@"When the file manager receives a notification from the remote that an artwork upload failed", ^{
+ __block NSMutableArray<SDLArtwork *> *testArtworks = nil;
+ __block NSSet<NSNumber *> *testOverwriteErrorIndices = nil;
+ __block NSMutableArray<NSString *> *expectedSuccessfulArtworkNames = nil;
+ __block NSInteger expectedSuccessfulArtworkNameCount = 0;
+ __block NSInteger expectedErrorMessagesCount = 0;
+
beforeEach(^{
+ testArtworks = [NSMutableArray array];
+ testOverwriteErrorIndices = [NSSet set];
+ expectedSuccessfulArtworkNameCount = 0;
+ expectedSuccessfulArtworkNames = [NSMutableArray array];
+ expectedErrorMessagesCount = 0;
+
testFailureIndexStart = -1;
testFailureIndexEnd = INT8_MAX;
});
- it(@"should return an error when all files fail", ^{
- testTotalFileCount = 5;
- testFileNameBase = @"TestAllFilesUnsuccessful";
- testFailureIndexStart = testTotalFileCount;
- });
+ describe(@"The correct errors should be returned", ^{
+ it(@"should return an empty artwork name array if all artwork uploads failed", ^{
+ testTotalFileCount = 20;
+ testFailureIndexStart = testTotalFileCount;
+ expectedSuccessfulArtworkNameCount = 0;
+ expectedErrorMessagesCount = 20;
+ });
- it(@"should return an error when the first file fails to upload", ^{
- testTotalFileCount = 5;
- testFileNameBase = @"TestFirstFileUnsuccessful";
- testFailureIndexStart = 0;
- });
+ it(@"should not include the failed upload in the artwork names", ^{
+ testTotalFileCount = 5;
+ testFailureIndexStart = 1;
+ testFailureIndexEnd = 3;
+ expectedSuccessfulArtworkNameCount = 1;
+ expectedErrorMessagesCount = 4;
+ });
- it(@"should return an error when the last file fails to upload", ^{
- testTotalFileCount = 100;
- testFileNameBase = @"TestLastFileUnsuccessful";
- testFailureIndexEnd = (testTotalFileCount - 1);
- });
+ it(@"should not return any errors that are overwrite errors", ^{
+ testTotalFileCount = 12;
+ testFailureIndexEnd = 5;
+ testOverwriteErrorIndices = [[NSSet alloc] initWithArray:@[@6, @7]];
+ expectedSuccessfulArtworkNameCount = 7;
+ expectedErrorMessagesCount = 5;
+ });
- afterEach(^{
- NSInteger testSpaceAvailable = initialSpaceAvailable;
- for(int i = 0; i < testTotalFileCount; i += 1) {
- NSString *testFileName = [NSString stringWithFormat:@"%@%d", testFileNameBase, i];
- SDLFile *testSDLFile = [SDLFile fileWithData:[@"someTextData" dataUsingEncoding:NSUTF8StringEncoding] name:testFileName fileExtension:@"bin"];
- testSDLFile.overwrite = true;
- [testSDLFiles addObject:testSDLFile];
-
- SDLPutFileResponse *response = [[SDLPutFileResponse alloc] init];
- NSError *responseError = nil;
- if (i <= testFailureIndexStart || i >= testFailureIndexEnd) {
- // Failed response
- response = failedResponse;
- response.spaceAvailable = @(testSpaceAvailable);
- responseError = [NSError sdl_lifecycle_unknownRemoteErrorWithDescription:[NSString stringWithFormat:@"file upload failed: %d", i] andReason:[NSString stringWithFormat:@"some error reason: %d", i]];
- expectedFailedUploads[testFileName] = responseError;
- } else {
- // Successful response
- response = successfulResponse;
- response.spaceAvailable = @(testSpaceAvailable -= 1);
- responseError = nil;
- [expectedSuccessfulFileNames addObject:testFileName];
- }
+ it(@"should not return an error if all the errors are overwrite errors", ^{
+ testTotalFileCount = 10;
+ testFailureIndexEnd = 5;
+ testOverwriteErrorIndices = [[NSSet alloc] initWithArray:@[@5, @6, @7, @8, @9]];
+ expectedSuccessfulArtworkNameCount = 10;
+ expectedErrorMessagesCount = 0;
+ });
- testConnectionManagerResponses[testFileName] = [[TestResponse alloc] initWithResponse:response error:responseError];;
- }
+ afterEach(^{
+ NSInteger testSpaceAvailable = initialSpaceAvailable;
+ for(int i = 0; i < testTotalFileCount; i += 1) {
+ UIGraphicsBeginImageContextWithOptions(CGSizeMake(5, 5), YES, 0);
+ CGContextRef context = UIGraphicsGetCurrentContext();
+ CGFloat grey = (i % 255) / 255.0;
+ [[UIColor colorWithRed:grey green:grey blue:grey alpha:1.0] setFill];
+ CGContextFillRect(context, CGRectMake(0, 0, i + 1, i + 1));
+ UIImage *greySquareImage = UIGraphicsGetImageFromCurrentImageContext();
+ UIGraphicsEndImageContext();
+
+ SDLArtwork *testArtwork = [SDLArtwork artworkWithImage:greySquareImage asImageFormat:SDLArtworkImageFormatPNG];
+ [testArtworks addObject:testArtwork];
+
+ SDLPutFileResponse *response = [[SDLPutFileResponse alloc] init];
+ NSError *responseError = nil;
+ if (i <= testFailureIndexStart || i >= testFailureIndexEnd) {
+ // Failed response
+ response = failedResponse;
+ response.spaceAvailable = @(testSpaceAvailable);
+ if ([testOverwriteErrorIndices containsObject:@(i)]) {
+ // Overwrite error
+ responseError = [NSError sdl_fileManager_cannotOverwriteError];
+ [expectedSuccessfulArtworkNames addObject:testArtwork.name];
+ } else {
+ responseError = [NSError sdl_lifecycle_unknownRemoteErrorWithDescription:[NSString stringWithFormat:@"file upload failed: %d", i] andReason:[NSString stringWithFormat:@"some error reason: %d", i]];
+ expectedFailedUploads[testArtwork.name] = responseError;
+ }
+ } else {
+ // Successful response
+ response = successfulResponse;
+ response.spaceAvailable = @(testSpaceAvailable -= 1);
+ responseError = nil;
+ [expectedSuccessfulFileNames addObject:testArtwork.name];
+ [expectedSuccessfulArtworkNames addObject:testArtwork.name];
+ }
+ testConnectionManagerResponses[testArtwork.name] = [[TestResponse alloc] initWithResponse:response error:responseError];
+ }
- testConnectionManager.responses = testConnectionManagerResponses;
- expectedError = [NSError sdl_fileManager_unableToUpload_ErrorWithUserInfo:expectedFailedUploads];
- expectedSpaceLeft = @(testSpaceAvailable);
+ testConnectionManager.responses = testConnectionManagerResponses;
+ expectedError = expectedFailedUploads.count == 0 ? nil : [NSError sdl_fileManager_unableToUpload_ErrorWithUserInfo:expectedFailedUploads];
+ expectedSpaceLeft = @(testSpaceAvailable);
+ });
});
- });
- afterEach(^{
- waitUntilTimeout(10, ^(void (^done)(void)){
- [testFileManager uploadFiles:testSDLFiles completionHandler:^(NSError * _Nullable error) {
- expect(error).to(equal(expectedError));
- expect(testFileManager.bytesAvailable).to(equal(expectedSpaceLeft));
- done();
- }];
+ afterEach(^{
+ expect(testFileManager.remoteFileNames.count).to(equal(testListFilesResponse.filenames.count));
+
+ waitUntilTimeout(1, ^(void (^done)(void)){
+ [testFileManager uploadArtworks:testArtworks completionHandler:^(NSArray<NSString *> * _Nonnull artworkNames, NSError * _Nullable error) {
+ expect(artworkNames.count).to(equal(expectedSuccessfulArtworkNameCount));
+ if (expectedSuccessfulArtworkNames == nil) {
+ expect(artworkNames).to(beNil());
+ } else {
+ for (NSString *artworkName in expectedSuccessfulArtworkNames) {
+ expect(artworkNames).to(contain(artworkName));
+ }
+ }
+
+ if (expectedError == nil) {
+ expect(error).to(beNil());
+ } else {
+ for (NSString *artworkName in expectedError.userInfo) {
+ expect([error.userInfo objectForKey:artworkName]).toNot(beNil());
+ }
+ }
+
+ expect(error.userInfo.count).to(equal(expectedErrorMessagesCount));
+ expect(testFileManager.bytesAvailable).to(equal(expectedSpaceLeft));
+ done();
+ }];
+ });
});
});
});
@@ -698,32 +1012,33 @@ describe(@"SDLFileManager uploading/deleting multiple files", ^{
testFileManagerProgressResponses = [[NSMutableDictionary alloc] init];
});
- context(@"A progress handler should be returned for each file", ^{
- it(@"should upload 1 small file from memory without error", ^{
- testTotalFileCount = 1;
- testFileNameBase = @"TestProgressHandlerOneSmallFileMemory";
- });
+ describe(@"When uploading files", ^{
+ context(@"A progress handler should be returned for each file", ^{
+ it(@"should upload 1 small file from memory without error", ^{
+ testTotalFileCount = 1;
+ testFileNameBase = @"TestProgressHandlerOneSmallFileMemory";
+ });
- it(@"should upload a large number of small files from memory without error", ^{
- testTotalFileCount = 200;
- testFileNameBase = @"TestProgressHandlerMultipleSmallFileMemory";
- });
+ it(@"should upload a large number of small files from memory without error", ^{
+ testTotalFileCount = 200;
+ testFileNameBase = @"TestProgressHandlerMultipleSmallFileMemory";
+ });
- afterEach(^{
- NSData *testFileData = [@"someTextData" dataUsingEncoding:NSUTF8StringEncoding];
- float testTotalBytesToUpload = testTotalFileCount * testFileData.length;
- float testTotalBytesUploaded = 0.0;
+ afterEach(^{
+ NSData *testFileData = [@"someTextData" dataUsingEncoding:NSUTF8StringEncoding];
+ float testTotalBytesToUpload = testTotalFileCount * testFileData.length;
+ float testTotalBytesUploaded = 0.0;
- NSInteger testSpaceAvailable = initialSpaceAvailable;
- for(int i = 0; i < testTotalFileCount; i += 1) {
- NSString *testFileName = [NSString stringWithFormat:@"%@%d", testFileNameBase, i];
- SDLFile *testSDLFile = [SDLFile fileWithData:testFileData name:testFileName fileExtension:@"bin"];
- testSDLFile.overwrite = true;
- [testSDLFiles addObject:testSDLFile];
+ NSInteger testSpaceAvailable = initialSpaceAvailable;
+ for(int i = 0; i < testTotalFileCount; i += 1) {
+ NSString *testFileName = [NSString stringWithFormat:@"%@%d", testFileNameBase, i];
+ SDLFile *testSDLFile = [SDLFile fileWithData:testFileData name:testFileName fileExtension:@"bin"];
+ testSDLFile.overwrite = true;
+ [testSDLFiles addObject:testSDLFile];
- successfulResponse.spaceAvailable = @(testSpaceAvailable -= 10);
- [expectedSuccessfulFileNames addObject:testFileName];
- testFileManagerResponses[testFileName] = [[TestResponse alloc] initWithResponse:successfulResponse error:nil];
+ successfulResponse.spaceAvailable = @(testSpaceAvailable -= 10);
+ [expectedSuccessfulFileNames addObject:testFileName];
+ testFileManagerResponses[testFileName] = [[TestResponse alloc] initWithResponse:successfulResponse error:nil];
testTotalBytesUploaded += testSDLFile.fileSize;
testFileManagerProgressResponses[testFileName] = [[TestFileProgressResponse alloc] initWithFileName:testFileName testUploadPercentage:testTotalBytesUploaded / testTotalBytesToUpload error:nil];
@@ -750,6 +1065,81 @@ describe(@"SDLFileManager uploading/deleting multiple files", ^{
});
});
+ describe(@"When uploading artworks", ^{
+ __block NSMutableArray<SDLArtwork *> *testArtworks = nil;
+ __block NSMutableDictionary *testConnectionManagerResponses;
+ __block NSMutableArray<NSString*> *expectedArtworkNames = nil;
+
+ beforeEach(^{
+ testArtworks = [NSMutableArray array];
+ testConnectionManagerResponses = [NSMutableDictionary dictionary];
+ expectedArtworkNames = [NSMutableArray array];
+ testTotalFileCount = 0;
+ });
+
+ context(@"A progress handler should be returned for each artwork", ^{
+ it(@"should upload 1 artwork without error", ^{
+ testTotalFileCount = 1;
+ });
+
+ it(@"should upload multiple artworks without error", ^{
+ testTotalFileCount = 100;
+ });
+
+ afterEach(^{
+ NSInteger spaceAvailable = initialSpaceAvailable;
+ float testTotalBytesToUpload = 0; // testTotalFileCount * testFileData.length;
+ for (NSUInteger i = 0; i < testTotalFileCount; i += 1) {
+ UIGraphicsBeginImageContextWithOptions(CGSizeMake(5, 5), YES, 0);
+ CGContextRef context = UIGraphicsGetCurrentContext();
+ CGFloat grey = (i % 255) / 255.0;
+ [[UIColor colorWithRed:grey green:grey blue:grey alpha:1.0] setFill];
+ CGContextFillRect(context, CGRectMake(0, 0, 10, 10));
+ UIImage *greySquareImage = UIGraphicsGetImageFromCurrentImageContext();
+ UIGraphicsEndImageContext();
+
+ SDLArtwork *testArtwork = [SDLArtwork artworkWithImage:greySquareImage asImageFormat:SDLArtworkImageFormatPNG];
+ [testArtworks addObject:testArtwork];
+ [expectedArtworkNames addObject:testArtwork.name];
+ testTotalBytesToUpload += testArtwork.fileSize;
+
+ successfulResponse.spaceAvailable = @(spaceAvailable -= 1);
+ [expectedSuccessfulFileNames addObject:testArtwork.name];
+ testFileManagerResponses[testArtwork.name] = [[TestResponse alloc] initWithResponse:successfulResponse error:nil];
+
+ testFileManagerProgressResponses[testArtwork.name] = [[TestFileProgressResponse alloc] initWithFileName:testArtwork.name testUploadPercentage:0 error:nil];
+ }
+
+ float testTotalBytesUploaded = 0.0;
+ for (SDLArtwork *artwork in testArtworks) {
+ testTotalBytesUploaded += artwork.fileSize;
+ TestFileProgressResponse *response = testFileManagerProgressResponses[artwork.name];
+ response.testUploadPercentage = testTotalBytesUploaded / testTotalBytesToUpload;
+ }
+
+ expectedSpaceLeft = @(spaceAvailable);
+ testConnectionManager.responses = testFileManagerResponses;
+ });
+ });
+
+ afterEach(^{
+ waitUntilTimeout(10, ^(void (^done)(void)){
+ [testFileManager uploadArtworks:testArtworks progressHandler:^BOOL(NSString * _Nonnull artworkName, float uploadPercentage, NSError * _Nullable error) {
+ TestFileProgressResponse *testProgressResponse = testFileManagerProgressResponses[artworkName];
+ expect(artworkName).to(equal(testProgressResponse.testFileName));
+ expect(uploadPercentage).to(equal(testProgressResponse.testUploadPercentage));
+ expect(error).to(testProgressResponse.testError == nil ? beNil() : equal(testProgressResponse.testError));
+ return YES;
+ } completionHandler:^(NSArray<NSString *> * _Nonnull artworkNames, NSError * _Nullable error) {
+ expect(error).to(beNil());
+ expect(testFileManager.bytesAvailable).to(equal(expectedSpaceLeft));
+ done();
+ }];
+ });
+ });
+ });
+ });
+
context(@"When an upload is canceled while in progress by the cancel parameter of the progress handler", ^{
__block NSMutableDictionary *testResponses;
__block NSMutableDictionary *testProgressResponses;
@@ -1116,6 +1506,18 @@ describe(@"SDLFileManager uploading/deleting multiple files", ^{
[testFileManager deleteRemoteFilesWithNames:[NSArray array] completionHandler:nil];
}).to(raiseException().named([NSException sdl_missingFilesException].name));
});
+
+ it(@"should throw an exception when the artwork upload function is passed an empty array", ^{
+ expectAction(^{
+ [testFileManager uploadArtworks:[NSArray array] completionHandler:nil];
+ }).to(raiseException().named([NSException sdl_missingFilesException].name));
+ });
+
+ it(@"should throw an exception when the artwork upload function with a progress handler is passed an empty array", ^{
+ expectAction(^{
+ [testFileManager uploadArtworks:[NSArray array] progressHandler:nil completionHandler:nil];
+ }).to(raiseException().named([NSException sdl_missingFilesException].name));
+ });
});
});
diff --git a/SmartDeviceLinkTests/DevAPISpecs/SDLLifecycleConfigurationSpec.m b/SmartDeviceLinkTests/DevAPISpecs/SDLLifecycleConfigurationSpec.m
index 806dd2491..fa08d2e38 100644
--- a/SmartDeviceLinkTests/DevAPISpecs/SDLLifecycleConfigurationSpec.m
+++ b/SmartDeviceLinkTests/DevAPISpecs/SDLLifecycleConfigurationSpec.m
@@ -201,7 +201,7 @@ describe(@"a lifecycle configuration", ^{
update.ttsName = test;
expect(update.appName).to(beNil());
expect(update.shortAppName).to(beNil());
- expect(update.ttsName).to(match(test));
+ expect(update.ttsName).to(equal(test));
expect(update.voiceRecognitionCommandNames).to(beNil());
});
@@ -211,7 +211,7 @@ describe(@"a lifecycle configuration", ^{
expect(update.appName).to(beNil());
expect(update.shortAppName).to(beNil());
expect(update.ttsName).to(beNil());
- expect(update.voiceRecognitionCommandNames).to(match(test));
+ expect(update.voiceRecognitionCommandNames).to(equal(test));
});
});
@@ -251,7 +251,7 @@ describe(@"a lifecycle configuration", ^{
expect(update.appName).to(beNil());
expect(update.shortAppName).to(beNil());
- expect(update.ttsName).to(match(test));
+ expect(update.ttsName).to(equal(test));
expect(update.voiceRecognitionCommandNames).to(beNil());
});
@@ -262,7 +262,7 @@ describe(@"a lifecycle configuration", ^{
expect(update.appName).to(beNil());
expect(update.shortAppName).to(beNil());
expect(update.ttsName).to(beNil());
- expect(update.voiceRecognitionCommandNames).to(match(test));
+ expect(update.voiceRecognitionCommandNames).to(equal(test));
});
});
});
diff --git a/SmartDeviceLinkTests/RPCSpecs/StructSpecs/SDLImageSpec.m b/SmartDeviceLinkTests/RPCSpecs/StructSpecs/SDLImageSpec.m
index f1364c819..6ecbdbb12 100644
--- a/SmartDeviceLinkTests/RPCSpecs/StructSpecs/SDLImageSpec.m
+++ b/SmartDeviceLinkTests/RPCSpecs/StructSpecs/SDLImageSpec.m
@@ -15,31 +15,68 @@
QuickSpecBegin(SDLImageSpec)
-describe(@"Getter/Setter Tests", ^ {
- it(@"Should set and get correctly", ^ {
- SDLImage* testStruct = [[SDLImage alloc] init];
-
- testStruct.value = @"value";
- testStruct.imageType = SDLImageTypeStatic;
-
- expect(testStruct.value).to(equal(@"value"));
- expect(testStruct.imageType).to(equal(SDLImageTypeStatic));
- });
-
- it(@"Should get correctly when initialized", ^ {
- NSMutableDictionary* dict = [@{SDLNameValue:@"value",
- SDLNameImageType:SDLImageTypeStatic} mutableCopy];
- SDLImage* testStruct = [[SDLImage alloc] initWithDictionary:dict];
-
- expect(testStruct.value).to(equal(@"value"));
- expect(testStruct.imageType).to(equal(SDLImageTypeStatic));
+describe(@"Getter/Setter Tests", ^{
+ context(@"When creating", ^{
+ __block SDLImage *testSDLImage = nil;
+ __block NSString *expectedValue;
+ __block SDLImageType expectedImageType;
+
+ beforeEach(^{
+ testSDLImage = nil;
+ expectedValue = nil;
+ expectedImageType = nil;
+ });
+
+ it(@"Should set and get correctly", ^{
+ NSString *value = @"value";
+ SDLImageType imageType = SDLImageTypeDynamic;
+
+ testSDLImage = [[SDLImage alloc] init];
+ testSDLImage.value = value;
+ testSDLImage.imageType = imageType;
+
+ expectedValue = value;
+ expectedImageType = imageType;
+ });
+
+ it(@"Should get correctly when initialized as a dictionary", ^{
+ NSString *value = @"value";
+ SDLImageType imageType = SDLImageTypeStatic;
+
+ NSMutableDictionary* dict = [@{SDLNameValue:value,
+ SDLNameImageType:imageType} mutableCopy];
+ testSDLImage = [[SDLImage alloc] initWithDictionary:dict];
+
+ expectedValue = value;
+ expectedImageType = imageType;
+ });
+
+ it(@"Should get correctly when initialized with a name only", ^{
+ NSString *name = @"value";
+ testSDLImage = [[SDLImage alloc] initWithName:name];
+
+ expectedValue = name;
+ expectedImageType = SDLImageTypeDynamic;
+ });
+
+ it(@"Should get correctly when initialized with static image value", ^{
+ UInt16 staticImageValue = 2568;
+ testSDLImage = [[SDLImage alloc] initWithStaticImageValue:staticImageValue];
+
+ expectedValue = @"2568";
+ expectedImageType = SDLImageTypeStatic;
+ });
+
+ afterEach(^{
+ expect(testSDLImage.value).to(equal(expectedValue));
+ expect(testSDLImage.imageType).to(equal(expectedImageType));
+ });
});
-
- it(@"Should return nil if not set", ^ {
- SDLImage* testStruct = [[SDLImage alloc] init];
-
- expect(testStruct.value).to(beNil());
- expect(testStruct.imageType).to(beNil());
+
+ it(@"Should return nil if not set", ^{
+ SDLImage *testSDLImage = [[SDLImage alloc] init];
+ expect(testSDLImage.value).to(beNil());
+ expect(testSDLImage.imageType).to(beNil());
});
});
diff --git a/SmartDeviceLinkTests/TestMultipleFilesConnectionManager.m b/SmartDeviceLinkTests/TestMultipleFilesConnectionManager.m
index be7a8a98e..7eb43733f 100644
--- a/SmartDeviceLinkTests/TestMultipleFilesConnectionManager.m
+++ b/SmartDeviceLinkTests/TestMultipleFilesConnectionManager.m
@@ -21,7 +21,6 @@ NS_ASSUME_NONNULL_BEGIN
- (void)sendConnectionRequest:(__kindof SDLRPCRequest *)request withResponseHandler:(nullable SDLResponseHandler)handler {
[super sendConnectionRequest:request withResponseHandler:handler];
- // Send a response if the request is a putfile
if ([[request name] isEqualToString:SDLNamePutFile]) {
SDLPutFile *putfileRequest = (SDLPutFile *)request;
TestResponse *response = self.responses[putfileRequest.syncFileName];
diff --git a/SmartDeviceLink_Example/Classes/ProxyManager.m b/SmartDeviceLink_Example/Classes/ProxyManager.m
index e0f6e4512..a17e6bf10 100644
--- a/SmartDeviceLink_Example/Classes/ProxyManager.m
+++ b/SmartDeviceLink_Example/Classes/ProxyManager.m
@@ -12,12 +12,6 @@
NSString *const SDLAppName = @"SDL Example App";
NSString *const SDLAppId = @"9999";
-NSString *const HexagonOffSoftButtonArtworkName = @"HexagonOffSoftButtonIcon";
-NSString *const HexagonOnSoftButtonArtworkName = @"HexagonOnSoftButtonIcon";
-NSString *const MainGraphicArtworkName = @"MainArtwork";
-NSString *const MainGraphicBlankArtworkName = @"MainBlankArtwork";
-NSString *const StarSoftButtonArtworkName = @"StarSoftButtonIcon";
-
BOOL const ShouldRestartOnDisconnect = NO;
typedef NS_ENUM(NSUInteger, SDLHMIFirstState) {
@@ -33,6 +27,7 @@ NS_ASSUME_NONNULL_BEGIN
// Describes the first time the HMI state goes non-none and full.
@property (assign, nonatomic) SDLHMIFirstState firstTimeState;
+@property (assign, nonatomic) BOOL areImagesVisible;
@end
@@ -59,30 +54,30 @@ NS_ASSUME_NONNULL_BEGIN
_state = ProxyStateStopped;
_firstTimeState = SDLHMIFirstStateNone;
-
+ _areImagesVisible = YES;
+
return self;
}
- (void)startIAP {
- [self sdlex_updateProxyState:ProxyStateSearchingForConnection];
// Check for previous instance of sdlManager
if (self.sdlManager) { return; }
+ [self sdlex_updateProxyState:ProxyStateSearchingForConnection];
SDLLifecycleConfiguration *lifecycleConfig = [self.class sdlex_setLifecycleConfigurationPropertiesOnConfiguration:[SDLLifecycleConfiguration defaultConfigurationWithAppName:SDLAppName appId:SDLAppId]];
-
- SDLConfiguration *config = [SDLConfiguration configurationWithLifecycle:lifecycleConfig lockScreen:[SDLLockScreenConfiguration enabledConfiguration] logging:[SDLLogConfiguration debugConfiguration]];
- self.sdlManager = [[SDLManager alloc] initWithConfiguration:config delegate:self];
-
- [self startManager];
+ [self sdlex_startWithLifecycleConfiguration:lifecycleConfig];
}
- (void)startTCP {
- [self sdlex_updateProxyState:ProxyStateSearchingForConnection];
// Check for previous instance of sdlManager
if (self.sdlManager) { return; }
+ [self sdlex_updateProxyState:ProxyStateSearchingForConnection];
SDLLifecycleConfiguration *lifecycleConfig = [self.class sdlex_setLifecycleConfigurationPropertiesOnConfiguration:[SDLLifecycleConfiguration debugConfigurationWithAppName:SDLAppName appId:SDLAppId ipAddress:[Preferences sharedPreferences].ipAddress port:[Preferences sharedPreferences].port]];
- SDLConfiguration *config = [SDLConfiguration configurationWithLifecycle:lifecycleConfig lockScreen:[SDLLockScreenConfiguration enabledConfiguration] logging:[SDLLogConfiguration debugConfiguration]];
- self.sdlManager = [[SDLManager alloc] initWithConfiguration:config delegate:self];
+ [self sdlex_startWithLifecycleConfiguration:lifecycleConfig];
+}
+- (void)sdlex_startWithLifecycleConfiguration:(SDLLifecycleConfiguration *)lifecycleConfiguration {
+ SDLConfiguration *config = [SDLConfiguration configurationWithLifecycle:lifecycleConfiguration lockScreen:[SDLLockScreenConfiguration enabledConfiguration] logging:[self.class sdlex_logConfiguration]];
+ self.sdlManager = [[SDLManager alloc] initWithConfiguration:config delegate:self];
[self startManager];
}
@@ -96,59 +91,49 @@ NS_ASSUME_NONNULL_BEGIN
}
[weakSelf sdlex_updateProxyState:ProxyStateConnected];
-
[weakSelf sdlex_setupPermissionsCallbacks];
-
- if ([weakSelf.sdlManager.hmiLevel isEqualToEnum:SDLHMILevelFull]) {
- [weakSelf sdlex_showInitialData];
- }
}];
}
- (void)reset {
[self sdlex_updateProxyState:ProxyStateStopped];
[self.sdlManager stop];
- // Remove reference
self.sdlManager = nil;
}
-
#pragma mark - Helpers
- (void)sdlex_showInitialData {
- if (![self.sdlManager.hmiLevel isEqualToEnum:SDLHMILevelFull]) {
- return;
- }
-
- SDLSetDisplayLayout *displayLayout = [[SDLSetDisplayLayout alloc] initWithLayout:SDLPredefinedLayoutNonMedia];
- [self.sdlManager sendRequest:displayLayout];
-
- SDLShow *show = [self sdlex_show];
-
- [self.sdlManager sendSequentialRequests:@[displayLayout, show] progressHandler:^BOOL(__kindof SDLRPCRequest * _Nonnull request, __kindof SDLRPCResponse * _Nullable response, NSError * _Nullable error, float percentComplete) {
- NSLog(@"Display layout / show progress: %f%%", percentComplete * 100);
+ SDLSetDisplayLayout *displayLayout = [[SDLSetDisplayLayout alloc] initWithLayout:SDLPredefinedLayoutMedia];
+ SDLAddCommand *speakNameAddCommand = [self.class sdlex_speakNameCommandWithManager:self.sdlManager];
+ SDLAddCommand *performInteractionChoiceSetAddCommand = [self.class sdlex_interactionSetCommandWithManager:self.sdlManager];
+ SDLAddCommand *getVehicleDataAddCommand = [self.class sdlex_vehicleDataCommandWithManager:self.sdlManager];
+ SDLCreateInteractionChoiceSet *createInteractionChoiceSet = [self.class sdlex_createOnlyChoiceInteractionSet];
+
+ [self.sdlManager sendSequentialRequests:@[displayLayout, speakNameAddCommand, performInteractionChoiceSetAddCommand, getVehicleDataAddCommand, createInteractionChoiceSet] progressHandler:^BOOL(__kindof SDLRPCRequest * _Nonnull request, __kindof SDLRPCResponse * _Nullable response, NSError * _Nullable error, float percentComplete) {
+ SDLLogD(@"Show initial data RPC. Request: %@. Response: %@. Percentage complete: %f, error: %@", request, response, percentComplete, error);
return YES;
- } completionHandler:nil];
+ } completionHandler:^(BOOL success) {
+ !success ? SDLLogW(@"Some show initial data RPCs were not sent successfully") : SDLLogD(@"All show initial data RPCs were sent successfully");
+ }];
}
-- (SDLShow *)sdlex_show {
+- (void)sdlex_updateShowWithManager:(SDLManager *)manager {
NSString *mainField1Text = isTextOn ? @"Smart Device Link" : @"";
NSString *mainField2Text = isTextOn ? @"Example App" : @"";
SDLShow* show = [[SDLShow alloc] initWithMainField1:mainField1Text mainField2:mainField2Text alignment:SDLTextAlignmentCenter];
- show.softButtons = [self sdlex_softButtons];
- show.graphic = areImagesVisible ? [self.class sdlex_mainGraphicImage] : [self.class sdlex_mainGraphicImage];
- return show;
-// [manager sendRequest:show withResponseHandler:^(__kindof SDLRPCRequest * _Nullable request, __kindof SDLRPCResponse * _Nullable response, NSError * _Nullable error) {
-// NSLog(@"Show request: %@ response: %@ error: %@", request, response, error);
-// }];
-}
-- (NSArray<SDLSoftButton *> *)sdlex_softButtons {
- SDLSoftButton *starSoftButton = [self.class sdlex_softButton1WithManager:self.sdlManager];
- SDLSoftButton *hexagonSoftButton = [self sdlex_softButton2WithManager:self.sdlManager];
- SDLSoftButton *textSoftButton = [self sdlex_softButton3WithManager:self.sdlManager];
- SDLSoftButton *imageSoftButton = [self sdlex_softButton4WithManager:self.sdlManager];
- return @[starSoftButton, hexagonSoftButton, textSoftButton, imageSoftButton];
+ [self sdlex_prepareSoftButtonsWithImages:self.areImagesVisible completionHandler:^(NSArray<SDLSoftButton *> * _Nonnull softButtons) {
+ if (softButtons.count == 0) { return; }
+ show.softButtons = softButtons;
+ [manager sendRequest:show];
+ }];
+
+ [self sdlex_prepareMainGraphicImageWithImages:self.areImagesVisible completionHandler:^(SDLImage * _Nullable sdlImage) {
+ if (sdlImage == nil) { return; }
+ show.graphic = sdlImage;
+ [manager sendRequest:show];
+ }];
}
- (void)sdlex_setupPermissionsCallbacks {
@@ -176,8 +161,8 @@ NS_ASSUME_NONNULL_BEGIN
}
+ (SDLLifecycleConfiguration *)sdlex_setLifecycleConfigurationPropertiesOnConfiguration:(SDLLifecycleConfiguration *)config {
- SDLArtwork *appIconArt = [SDLArtwork persistentArtworkWithImage:[UIImage imageNamed:@"AppIcon60x60@2x"] name:@"AppIcon" asImageFormat:SDLArtworkImageFormatPNG];
-
+ SDLArtwork *appIconArt = [SDLArtwork persistentArtworkWithImage:[UIImage imageNamed:@"AppIcon60x60@2x"] asImageFormat:SDLArtworkImageFormatPNG];
+
config.shortAppName = @"SDL Example";
config.appIcon = appIconArt;
config.voiceRecognitionCommandNames = @[@"S D L Example"];
@@ -187,11 +172,12 @@ NS_ASSUME_NONNULL_BEGIN
}
+ (SDLLogConfiguration *)sdlex_logConfiguration {
+ // The default configuration shows Error, Warning and Debug logs. The only logs not shown with the default log configuration is Verbose.
SDLLogConfiguration *logConfig = [SDLLogConfiguration defaultConfiguration];
SDLLogFileModule *sdlExampleModule = [SDLLogFileModule moduleWithName:@"SDL Example" files:[NSSet setWithArray:@[@"ProxyManager"]]];
logConfig.modules = [logConfig.modules setByAddingObject:sdlExampleModule];
logConfig.targets = [logConfig.targets setByAddingObject:[SDLLogTargetFile logger]];
-// logConfig.filters = [logConfig.filters setByAddingObject:[SDLLogFilter filterByAllowingModules:[NSSet setWithObject:@"Transport"]]];
+ // logConfig.globalLogLevel = SDLLogLevelVerbose;
return logConfig;
}
@@ -297,16 +283,9 @@ NS_ASSUME_NONNULL_BEGIN
}
+ (void)sdlex_sendPerformOnlyChoiceInteractionWithManager:(SDLManager *)manager {
- SDLPerformInteraction *performOnlyChoiceInteraction = [[SDLPerformInteraction alloc] init];
- performOnlyChoiceInteraction.initialText = @"Choose the only one! You have 5 seconds...";
- performOnlyChoiceInteraction.initialPrompt = [SDLTTSChunk textChunksFromString:@"Choose it"];
- performOnlyChoiceInteraction.interactionMode = SDLInteractionModeBoth;
- performOnlyChoiceInteraction.interactionChoiceSetIDList = @[@0];
- performOnlyChoiceInteraction.helpPrompt = [SDLTTSChunk textChunksFromString:@"Do it"];
- performOnlyChoiceInteraction.timeoutPrompt = [SDLTTSChunk textChunksFromString:@"Too late"];
- performOnlyChoiceInteraction.timeout = @5000;
+ SDLPerformInteraction *performOnlyChoiceInteraction = [[SDLPerformInteraction alloc] initWithInitialPrompt:@"Choose an item from the list" initialText:@"Choose the only option! You have 5 seconds..." interactionChoiceSetIDList:@[@0] helpPrompt:@"Select an item from the list" timeoutPrompt:@"The list is closing" interactionMode:SDLInteractionModeBoth timeout:5000 vrHelp:nil];
performOnlyChoiceInteraction.interactionLayout = SDLLayoutModeListOnly;
-
+
[manager sendRequest:performOnlyChoiceInteraction withResponseHandler:^(__kindof SDLRPCRequest * _Nullable request, __kindof SDLPerformInteractionResponse * _Nullable response, NSError * _Nullable error) {
SDLLogD(@"Perform Interaction fired");
if ((response == nil) || (error != nil)) {
@@ -321,7 +300,35 @@ NS_ASSUME_NONNULL_BEGIN
}];
}
-+ (SDLSoftButton *)sdlex_softButton1WithManager:(SDLManager *)manager {
+- (void)sdlex_prepareSoftButtonsWithImages:(BOOL)areImagesVisible completionHandler:(void (^)(NSArray<SDLSoftButton *> *softButtons))completionHandler {
+ // Save each soft button to a specific index position in the array. Once all buttons have been created, return the array of buttons. This is done to prevent flickering that can occur if the UI is updated everytime a button is created.
+ NSMutableArray<SDLSoftButton *> *softButtons = [[NSMutableArray alloc] initWithObjects:[[SDLSoftButton alloc] init], [[SDLSoftButton alloc] init], [[SDLSoftButton alloc] init], [[SDLSoftButton alloc] init], nil];
+
+ dispatch_group_t dataDispatchGroup = dispatch_group_create();
+ dispatch_group_enter(dataDispatchGroup);
+
+ dispatch_group_enter(dataDispatchGroup);
+ [self sdlex_prepareSoftButton1WithManager:self.sdlManager isImageVisible:areImagesVisible completionHandler:^(SDLSoftButton * _Nonnull button) {
+ softButtons[0] = button;
+ dispatch_group_leave(dataDispatchGroup);
+ }];
+
+ dispatch_group_enter(dataDispatchGroup);
+ [self sdlex_prepareSoftButton2WithManager:self.sdlManager isImageVisible:areImagesVisible completionHandler:^(SDLSoftButton * _Nonnull button) {
+ softButtons[1] = button;
+ dispatch_group_leave(dataDispatchGroup);
+ }];
+
+ softButtons[2] = [self sdlex_prepareSoftButton3WithManager:self.sdlManager];
+ softButtons[3] = [self sdlex_prepareSoftButton4WithManager:self.sdlManager areImagesVisible:areImagesVisible];
+
+ dispatch_group_leave(dataDispatchGroup);
+ dispatch_group_notify(dataDispatchGroup, dispatch_get_main_queue(), ^{
+ completionHandler(softButtons);
+ });
+}
+
+- (void)sdlex_prepareSoftButton1WithManager:(SDLManager *)manager isImageVisible:(BOOL)imageVisible completionHandler:(void (^)(SDLSoftButton * button))completionHandler {
SDLSoftButton* softButton = [[SDLSoftButton alloc] initWithHandler:^(SDLOnButtonPress * _Nullable buttonPressNotification, SDLOnButtonEvent * _Nullable buttonEventNotification) {
if (buttonPressNotification == nil) {
return;
@@ -330,65 +337,56 @@ NS_ASSUME_NONNULL_BEGIN
SDLAlert* alert = [[SDLAlert alloc] init];
alert.alertText1 = @"You pushed the soft button!";
[manager sendRequest:alert];
-
- SDLLogD(@"Star icon soft button press fired");
}];
+
softButton.text = @"Press";
softButton.softButtonID = @100;
- if (areImagesVisible) {
- softButton.type = SDLSoftButtonTypeBoth;
- SDLImage* image = [[SDLImage alloc] init];
- image.imageType = SDLImageTypeDynamic;
- image.value = StarSoftButtonArtworkName;
- softButton.image = image;
- } else {
+ SDLArtwork *artwork = [self.class sdlex_softButton1Artwork];
+ if (!imageVisible) {
softButton.type = SDLSoftButtonTypeText;
+ return completionHandler(softButton);
}
- return softButton;
+ [manager.fileManager uploadArtwork:artwork completionHandler:^(BOOL success, NSString * _Nonnull artworkName, NSUInteger bytesAvailable, NSError * _Nullable error) {
+ softButton.type = success ? SDLSoftButtonTypeBoth : SDLSoftButtonTypeText;
+ softButton.image = success ? [[SDLImage alloc] initWithName:artworkName] : nil;
+ return completionHandler(softButton);
+ }];
}
-static Boolean isHexagonOn = true;
-- (SDLSoftButton *)sdlex_softButton2WithManager:(SDLManager *)manager {
+static BOOL isHexagonOn = YES;
+- (void)sdlex_prepareSoftButton2WithManager:(SDLManager *)manager isImageVisible:(BOOL)imageVisible completionHandler:(void (^)(SDLSoftButton * button))completionHandler {
SDLSoftButton* softButton = [[SDLSoftButton alloc] initWithHandler:^(SDLOnButtonPress * _Nullable buttonPressNotification, SDLOnButtonEvent * _Nullable buttonEventNotification) {
- if (buttonPressNotification == nil) {
- return;
- }
-
+ if (buttonPressNotification == nil) { return; }
isHexagonOn = !isHexagonOn;
- [self sdlex_showInitialData];
-
- SDLLogD(@"Hexagon icon button press fired %d", isHexagonOn);
+ [self sdlex_updateShowWithManager:manager];
}];
softButton.softButtonID = @200;
- if (areImagesVisible) {
- softButton.type = SDLSoftButtonTypeImage;
- SDLImage* image = [[SDLImage alloc] init];
- image.value = isHexagonOn ? HexagonOnSoftButtonArtworkName : HexagonOffSoftButtonArtworkName;
- image.imageType = SDLImageTypeDynamic;
- softButton.image = image;
- } else {
- softButton.text = isHexagonOn ? @"➖Hex" : @"➕Hex";
+ if (!imageVisible) {
softButton.type = SDLSoftButtonTypeText;
+ softButton.text = isHexagonOn ? @"➖Hex" : @"➕Hex";
+ return completionHandler(softButton);
}
- return softButton;
+ SDLArtwork *artwork = isHexagonOn ? [self.class sdlex_softButton2OnArtwork] : [self.class sdlex_softButton2OffArtwork];
+ [manager.fileManager uploadArtwork:artwork completionHandler:^(BOOL success, NSString * _Nonnull artworkName, NSUInteger bytesAvailable, NSError * _Nullable error) {
+ softButton.type = success ? SDLSoftButtonTypeImage : SDLSoftButtonTypeText;
+ softButton.image = success ? [[SDLImage alloc] initWithName:artworkName] : nil;
+ return completionHandler(softButton);
+ }];
}
static BOOL isTextOn = YES;
-- (SDLSoftButton *)sdlex_softButton3WithManager:(SDLManager *)manager {
+- (SDLSoftButton *)sdlex_prepareSoftButton3WithManager:(SDLManager *)manager {
SDLSoftButton* softButton = [[SDLSoftButton alloc] initWithHandler:^(SDLOnButtonPress * _Nullable buttonPressNotification, SDLOnButtonEvent * _Nullable buttonEventNotification) {
- if (buttonPressNotification == nil) {
- return;
- }
+ if (buttonPressNotification == nil) { return; }
isTextOn = !isTextOn;
- [self sdlex_showInitialData];
-
- SDLLogD(@"Text visibility soft button press fired");
+ [self sdlex_updateShowWithManager:manager];
}];
+
softButton.softButtonID = @300;
softButton.text = isTextOn ? @"➖Text" : @"➕Text";
softButton.type = SDLSoftButtonTypeText;
@@ -396,46 +394,29 @@ static BOOL isTextOn = YES;
return softButton;
}
-static Boolean areImagesVisible = true;
-- (SDLSoftButton *)sdlex_softButton4WithManager:(SDLManager *)manager {
+- (SDLSoftButton *)sdlex_prepareSoftButton4WithManager:(SDLManager *)manager areImagesVisible:(BOOL)imageVisible {
SDLSoftButton* softButton = [[SDLSoftButton alloc] initWithHandler:^(SDLOnButtonPress * _Nullable buttonPressNotification, SDLOnButtonEvent * _Nullable buttonEventNotification) {
if (buttonPressNotification == nil) { return; }
-
- if (areImagesVisible) {
- [self sdlex_deleteFiles:[self.class sdlex_allArtFileNames] completionHandler:^(BOOL success) {
- if (!success) { return; }
- [self sdlex_showInitialData];
- }];
- } else {
- [self sdlex_uploadFilesWithProgressHandler:[self.class sdlex_allArt] completionHandler:^(BOOL success) {
- if (!success) { return; }
- [self sdlex_showInitialData];
- }];
- }
-
- areImagesVisible = !areImagesVisible;
-
- SDLLogD(@"Image visibility soft button press fired %d", isHexagonOn);
+ self.areImagesVisible = !imageVisible;
+ [self sdlex_updateShowWithManager:manager];
}];
- softButton.text = areImagesVisible ? @"➖Icons" : @"➕Icons";
+ softButton.text = imageVisible ? @"➖Icons" : @"➕Icons";
softButton.softButtonID = @400;
softButton.type = SDLSoftButtonTypeText;
return softButton;
}
-+ (SDLImage *)sdlex_mainGraphicImage {
- SDLImage* image = [[SDLImage alloc] init];
- if (areImagesVisible) {
- image.imageType = SDLImageTypeDynamic;
- image.value = MainGraphicArtworkName;
- } else {
- image.imageType = SDLImageTypeDynamic;
- image.value = MainGraphicBlankArtworkName;
- }
-
- return image;
+- (void)sdlex_prepareMainGraphicImageWithImages:(BOOL)imageVisible completionHandler:(void (^)(SDLImage * _Nullable sdlImage))completionHandler {
+ SDLArtwork *mainGraphicImage = imageVisible ? [self.class sdlex_mainGraphicArtwork] : [self.class sdlex_mainGraphicBlank];
+ [self.sdlManager.fileManager uploadArtwork:mainGraphicImage completionHandler:^(BOOL success, NSString * _Nonnull artworkName, NSUInteger bytesAvailable, NSError * _Nullable error) {
+ if (!success) {
+ SDLLogE(@"Artwork %@ failed to upload", artworkName);
+ return completionHandler(nil);
+ }
+ return completionHandler([[SDLImage alloc] initWithName:artworkName]);
+ }];
}
+ (void)sdlex_sendGetVehicleDataWithManager:(SDLManager *)manager {
@@ -448,136 +429,29 @@ static Boolean areImagesVisible = true;
#pragma mark - Files / Artwork
-+ (NSArray<SDLArtwork *> *)sdlex_allArtAndBlankPlaceholderArt {
- NSMutableArray<SDLArtwork *> *art = [NSMutableArray array];
- [art addObjectsFromArray:[self.class sdlex_allArt]];
- [art addObject:[self.class sdlex_mainGraphicBlank]];
- return art;
-}
-
-+ (NSArray<SDLArtwork *> *)sdlex_allArt {
- NSMutableArray<SDLArtwork *> *art = [NSMutableArray array];
- [art addObjectsFromArray:[self.class sdlex_softButtonArt]];
- [art addObject:[self.class sdlex_mainGraphicArtwork]];
- return art;
-}
-
-+ (NSArray<NSString *> *)sdlex_allArtFileNames {
- NSMutableArray<NSString *> *fileNames = [NSMutableArray array];
- for (SDLArtwork *art in [self.class sdlex_allArt]) {
- [fileNames addObject:art.name];
- }
- return fileNames;
-}
-
-+ (NSArray<SDLArtwork *> *)sdlex_softButtonArt {
- return [[NSArray alloc] initWithObjects:[self.class sdlex_softButton1Artwork], [self.class sdlex_softButton2OnArtwork], [self.class sdlex_softButton2OffArtwork], nil];
-}
-
-+ (NSArray<NSString *> *)sdlex_softButtonArtFileNames {
- NSMutableArray<NSString *> *fileNames = [NSMutableArray array];
- for (SDLArtwork *art in [self.class sdlex_softButtonArt]) {
- [fileNames addObject:art.name];
- }
- return fileNames;
-}
-
+ (SDLArtwork *)sdlex_softButton1Artwork {
- return [SDLArtwork artworkWithImage:[UIImage imageNamed:@"star_softbutton_icon"] name:StarSoftButtonArtworkName asImageFormat:SDLArtworkImageFormatPNG];
+ return [SDLArtwork artworkWithImage:[UIImage imageNamed:@"star_softbutton_icon"] asImageFormat:SDLArtworkImageFormatPNG];
}
+ (SDLArtwork *)sdlex_softButton2OnArtwork {
- return [SDLArtwork artworkWithImage:[UIImage imageNamed:@"hexagon_on_softbutton_icon"] name:HexagonOnSoftButtonArtworkName asImageFormat:SDLArtworkImageFormatPNG];
+ return [SDLArtwork artworkWithImage:[UIImage imageNamed:@"hexagon_on_softbutton_icon"] asImageFormat:SDLArtworkImageFormatPNG];
}
+ (SDLArtwork *)sdlex_softButton2OffArtwork {
- return [SDLArtwork artworkWithImage:[UIImage imageNamed:@"hexagon_off_softbutton_icon"] name:HexagonOffSoftButtonArtworkName asImageFormat:SDLArtworkImageFormatPNG];
+ return [SDLArtwork artworkWithImage:[UIImage imageNamed:@"hexagon_off_softbutton_icon"] asImageFormat:SDLArtworkImageFormatPNG];
}
+ (SDLArtwork *)sdlex_mainGraphicArtwork {
- return [SDLArtwork artworkWithImage:[UIImage imageNamed:@"sdl_logo_green"] name:MainGraphicArtworkName asImageFormat:SDLArtworkImageFormatPNG];
+ return [SDLArtwork artworkWithImage:[UIImage imageNamed:@"sdl_logo_green"] asImageFormat:SDLArtworkImageFormatPNG];
}
+ (SDLArtwork *)sdlex_mainGraphicBlank {
UIGraphicsBeginImageContextWithOptions(CGSizeMake(5, 5), NO, 0.0);
UIImage *blankImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
- SDLArtwork *mainGraphicBlank = [SDLArtwork artworkWithImage:blankImage name:MainGraphicBlankArtworkName asImageFormat:SDLArtworkImageFormatPNG];
- return mainGraphicBlank;
-}
-
-- (void)sdlex_uploadFiles:(NSArray<SDLFile *> *)files completionHandler:(void (^)(BOOL success))completionHandler {
- [self.sdlManager.fileManager uploadFiles:files completionHandler:^(NSError * _Nullable error) {
- if(!error) {
- return completionHandler(true);
- } else {
- SDLLogD(@"Failed file uploads: %@", error.userInfo);
- return completionHandler(false);
- }
- }];
-}
-
-- (void)sdlex_uploadFilesWithProgressHandler:(NSArray<SDLFile *> *)files completionHandler:(void (^)(BOOL success))completionHandler {
- [self.sdlManager.fileManager uploadFiles:files progressHandler:^BOOL(SDLFileName * _Nonnull fileName, float uploadPercentage, NSError * _Nullable error) {
- if (error) {
- SDLLogD(@"The file did not upload: %@", error);
- // You may want to cancel all future file uploads if the last file failed during the upload process
- return NO;
- }
-
- // The file was sent successfully
- // Keep uploading the rest of the files
- return YES;
- } completionHandler:^(NSError * _Nullable error) {
- if(!error) {
- return completionHandler(true);
- } else {
- SDLLogD(@"Failed file uploads: %@", error.userInfo);
- return completionHandler(false);
- }
- }];
-}
-
-- (void)sdlex_deleteFiles:(NSArray<NSString *> *)fileNames completionHandler:(void (^)(BOOL success))completionHandler {
- [self.sdlManager.fileManager deleteRemoteFilesWithNames:fileNames completionHandler:^(NSError * _Nullable error) {
- if(!error) {
- return completionHandler(true);
- } else {
- SDLLogD(@"Failed file deletes: %@", error.userInfo);
- return completionHandler(false);
- }
- }];
-}
-
-- (void)sdlex_prepareRemoteSystem {
- [self.sdlManager sendRequests:@[[self.class sdlex_speakNameCommandWithManager:self.sdlManager], [self.class sdlex_interactionSetCommandWithManager:self.sdlManager], [self.class sdlex_vehicleDataCommandWithManager:self.sdlManager]]
- progressHandler:^(__kindof SDLRPCRequest * _Nonnull request, __kindof SDLRPCResponse * _Nullable response, NSError * _Nullable error, float percentComplete) {
- NSLog(@"Commands sent updated, percent complete %f%%", percentComplete * 100);
- }
- completionHandler:nil];
-
- dispatch_group_t dataDispatchGroup = dispatch_group_create();
- dispatch_group_enter(dataDispatchGroup);
-
- dispatch_group_enter(dataDispatchGroup);
- [self sdlex_uploadFiles:[self.class sdlex_allArtAndBlankPlaceholderArt] completionHandler:^(BOOL success) {
- dispatch_group_leave(dataDispatchGroup);
- if (!success) { return; }
- }];
-
- dispatch_group_enter(dataDispatchGroup);
- [self.sdlManager sendRequest:[self.class sdlex_createOnlyChoiceInteractionSet] withResponseHandler:^(__kindof SDLRPCRequest * _Nullable request, __kindof SDLRPCResponse * _Nullable response, NSError * _Nullable error) {
- // Interaction choice set ready
- dispatch_group_leave(dataDispatchGroup);
- }];
-
- dispatch_group_leave(dataDispatchGroup);
- dispatch_group_notify(dataDispatchGroup, dispatch_get_main_queue(), ^{
- [self sdlex_showInitialData];
- });
+ return [SDLArtwork artworkWithImage:blankImage asImageFormat:SDLArtworkImageFormatPNG];
}
-
#pragma mark - SDLManagerDelegate
- (void)managerDidDisconnect {
@@ -593,19 +467,13 @@ static Boolean areImagesVisible = true;
if (![newLevel isEqualToEnum:SDLHMILevelNone] && (self.firstTimeState == SDLHMIFirstStateNone)) {
// This is our first time in a non-NONE state
self.firstTimeState = SDLHMIFirstStateNonNone;
-
- // Send AddCommands
- [self sdlex_prepareRemoteSystem];
+ [self sdlex_showInitialData];
}
if ([newLevel isEqualToEnum:SDLHMILevelFull] && (self.firstTimeState != SDLHMIFirstStateFull)) {
// This is our first time in a FULL state
self.firstTimeState = SDLHMIFirstStateFull;
- }
-
- if ([newLevel isEqualToEnum:SDLHMILevelFull]) {
- // We're always going to try to show the initial state, because if we've already shown it, it won't be shown, and we need to guard against some possible weird states
- [self sdlex_showInitialData];
+ [self sdlex_updateShowWithManager:self.sdlManager];
}
}
diff --git a/SmartDeviceLink_Example/Info.plist b/SmartDeviceLink_Example/Info.plist
index 46063b5e1..e7ccffb06 100644
--- a/SmartDeviceLink_Example/Info.plist
+++ b/SmartDeviceLink_Example/Info.plist
@@ -15,7 +15,7 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
- <string>5.0.0</string>
+ <string>5.1.1</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>