summaryrefslogtreecommitdiff
path: root/SmartDeviceLink/private/SDLSoftButtonReplaceOperation.m
blob: 8897f4b1053a7761bdbb9dcf2c8d11897d5478b0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
//
//  SDLSoftButtonReplaceOperation.m
//  SmartDeviceLink
//
//  Created by Joel Fischer on 4/25/19.
//  Copyright © 2019 smartdevicelink. All rights reserved.
//

#import "SDLSoftButtonReplaceOperation.h"

#import "SDLArtwork.h"
#import "SDLConnectionManagerType.h"
#import "SDLFileManager.h"
#import "SDLLogMacros.h"
#import "SDLShow.h"
#import "SDLSoftButton.h"
#import "SDLSoftButtonCapabilities.h"
#import "SDLSoftButtonObject.h"
#import "SDLSoftButtonState.h"

NS_ASSUME_NONNULL_BEGIN

@interface SDLSoftButtonReplaceOperation ()

@property (strong, nonatomic, nullable) SDLSoftButtonCapabilities *softButtonCapabilities;
@property (strong, nonatomic) NSArray<SDLSoftButtonObject *> *softButtonObjects;

@property (weak, nonatomic) id<SDLConnectionManagerType> connectionManager;
@property (weak, nonatomic) SDLFileManager *fileManager;
@property (copy, nonatomic, nullable) NSError *internalError;

@end

@implementation SDLSoftButtonReplaceOperation

- (instancetype)initWithConnectionManager:(id<SDLConnectionManagerType>)connectionManager fileManager:(SDLFileManager *)fileManager capabilities:(nullable SDLSoftButtonCapabilities *)capabilities softButtonObjects:(NSArray<SDLSoftButtonObject *> *)softButtonObjects mainField1:(NSString *)mainField1 {
    self = [super init];
    if (!self) { return nil; }

    _connectionManager = connectionManager;
    _fileManager = fileManager;
    _softButtonCapabilities = capabilities;
    _softButtonObjects = softButtonObjects;
    _mainField1 = mainField1;

    return self;
}

- (void)start {
    [super start];
    if (self.isCancelled) { return; }

    // Check if soft buttons have images and, if so, if the images need to be uploaded
    if (![self sdl_supportsSoftButtonImages]) {
        // The module does not support images
        SDLLogW(@"Soft button images are not supported. Attempting to send text-only soft buttons. If any button does not contain text, no buttons will be sent.");

        // Send text-only buttons if all current states for the soft buttons have text
        __weak typeof(self) weakself = self;
        [self sdl_sendCurrentStateTextOnlySoftButtonsWithCompletionHandler:^(BOOL success) {
            __strong typeof(weakself) strongself = weakself;
            if (!success) {
                SDLLogE(@"Buttons will not be sent because the module does not support images and some of the buttons do not have text");
            }
            [strongself finishOperation];
        }];
    } else if (![self sdl_allStateImagesAreUploaded]) {
        // If there are images in the first soft button state that have not yet been uploaded, send a text-only version of the soft buttons (the text-only buttons will only be sent if all the first button states have text)
        [self sdl_sendCurrentStateTextOnlySoftButtonsWithCompletionHandler:^(BOOL success) {}];

        // Upload images used in the first soft button state
        __weak typeof(self) weakself = self;
        [self sdl_uploadInitialStateImagesWithCompletionHandler:^{
            SDLLogV(@"Finished sending images for the first soft button states");
            // Now that the images have been uploaded, send the soft buttons with images
            __strong typeof(weakself) strongself = weakself;
            [strongself sdl_sendCurrentStateSoftButtonsWithCompletionHandler:^{
                // Finally, upload the images used in the other button states
                __strong typeof(weakself) strongself = weakself;
                [strongself sdl_uploadOtherStateImagesWithCompletionHandler:^{
                    __strong typeof(weakself) strongself = weakself;
                    SDLLogV(@"Finished sending images for the other soft button states");
                    [strongself finishOperation];
                }];
            }];
        }];
    } else {
        // All the images have been uploaded. Send initial soft buttons with images.
        __weak typeof(self) weakself = self;
        [self sdl_sendCurrentStateSoftButtonsWithCompletionHandler:^{
            __strong typeof(weakself) strongself = weakself;
            SDLLogV(@"Finished sending soft buttons with images");
            [strongself finishOperation];
        }];
    }
}


#pragma mark - Uploading Images

/// Upload the initial state images.
/// @param handler Called when all images have been uploaded
- (void)sdl_uploadInitialStateImagesWithCompletionHandler:(void (^)(void))handler {
    NSMutableArray<SDLArtwork *> *initialStatesToBeUploaded = [NSMutableArray array];
    for (SDLSoftButtonObject *object in self.softButtonObjects) {
        if ([self.fileManager fileNeedsUpload:object.currentState.artwork]) {
            [initialStatesToBeUploaded addObject:object.currentState.artwork];
        }
    }

    [self sdl_uploadImages:initialStatesToBeUploaded forStateName:@"Initial" completionHandler:handler];
}

/// Upload the other state images.
/// @param handler Called when all images have been uploaded
- (void)sdl_uploadOtherStateImagesWithCompletionHandler:(void (^)(void))handler {
    NSMutableArray<SDLArtwork *> *otherStatesToBeUploaded = [NSMutableArray array];
    for (SDLSoftButtonObject *object in self.softButtonObjects) {
        for (SDLSoftButtonState *state in object.states) {
            if ([state.name isEqualToString:object.currentState.name]) { continue; }
            if ([self.fileManager fileNeedsUpload:state.artwork]) {
                [otherStatesToBeUploaded addObject:state.artwork];
            }
        }
    }

    [self sdl_uploadImages:otherStatesToBeUploaded forStateName:@"Other" completionHandler:handler];
}

/// Helper method for uploading images
/// @param images The images to upload
/// @param stateName The name of the button states for which the images are being uploaded. Used for logs.
/// @param completionHandler Called when all images have been uploaded
- (void)sdl_uploadImages:(NSArray<SDLArtwork *> *)images forStateName:(NSString *)stateName completionHandler:(void (^)(void))completionHandler {
    if (images.count == 0) {
        SDLLogV(@"No images to upload for %@ states", stateName);
        completionHandler();
        return;
    }

    SDLLogD(@"Uploading images for %@ states", stateName);
    __weak typeof(self) weakself = self;
    [self.fileManager uploadArtworks:[images copy] progressHandler:^BOOL(NSString * _Nonnull artworkName, float uploadPercentage, NSError * _Nullable error) {
        __strong typeof(weakself) strongself = weakself;
        SDLLogV(@"Uploaded %@ states images: %@, error: %@, percent complete: %f.2%%", stateName, artworkName, error, uploadPercentage * 100);
        if (strongself.isCancelled) {
            [strongself finishOperation];
            return NO;
        }

        return YES;
    } completionHandler:^(NSArray<NSString *> * _Nonnull artworkNames, NSError * _Nullable error) {
        if (error != nil) {
            SDLLogE(@"Error uploading %@ states images: %@", stateName, error);
        } else {
            SDLLogD(@"All %@ states images uploaded", stateName);
        }

        completionHandler();
    }];
}


#pragma mark - Sending the Soft Buttons

- (void)sdl_sendCurrentStateSoftButtonsWithCompletionHandler:(void (^)(void))handler {
    if (self.isCancelled) {
        [self finishOperation];
    }

    SDLLogV(@"Preparing to send full soft buttons");
    NSMutableArray<SDLSoftButton *> *softButtons = [NSMutableArray arrayWithCapacity:self.softButtonObjects.count];
    for (SDLSoftButtonObject *buttonObject in self.softButtonObjects) {
        [softButtons addObject:buttonObject.currentStateSoftButton];
    }

    // HAX: Work around a bug in Sync where not sending a main field when sending soft buttons will lock up the head unit for 10-15 seconds.
    SDLShow *show = [[SDLShow alloc] init];
    show.mainField1 = self.mainField1 ?: @"";
    show.softButtons = [softButtons copy];

    [self.connectionManager sendConnectionRequest:show withResponseHandler:^(__kindof SDLRPCRequest * _Nullable request, __kindof SDLRPCResponse * _Nullable response, NSError * _Nullable error) {
        if (error != nil) {
            SDLLogW(@"Failed to update soft buttons with text buttons: %@", error);
        }

        SDLLogD(@"Finished sending text only soft buttons");
        handler();
    }];
}

/**
 Returns text soft buttons representing the current states of the button objects, or returns if _any_ of the buttons' current states are image only buttons.
*/
- (void)sdl_sendCurrentStateTextOnlySoftButtonsWithCompletionHandler:(void (^)(BOOL success))handler {
    if (self.isCancelled) {
        [self finishOperation];
    }

    SDLLogV(@"Preparing to send text-only soft buttons");
    NSMutableArray<SDLSoftButton *> *textButtons = [NSMutableArray arrayWithCapacity:self.softButtonObjects.count];
    for (SDLSoftButtonObject *buttonObject in self.softButtonObjects) {
        SDLSoftButton *button = buttonObject.currentStateSoftButton;
        if (button.text == nil) {
            SDLLogW(@"Attempted to create text buttons, but some buttons don't support text, so no text-only soft buttons will be sent");
            handler(NO);
            return;
        }

        button.image = nil;
        button.type = SDLSoftButtonTypeText;
        [textButtons addObject:button];
    }

    SDLShow *show = [[SDLShow alloc] init];
    show.mainField1 = self.mainField1;
    show.softButtons = [textButtons copy];

    [self.connectionManager sendConnectionRequest:show withResponseHandler:^(__kindof SDLRPCRequest * _Nullable request, __kindof SDLRPCResponse * _Nullable response, NSError * _Nullable error) {
        if (error != nil) {
            SDLLogW(@"Failed to update soft buttons with text buttons: %@", error);
        }

        SDLLogD(@"Finished sending text only soft buttons");
        handler(YES);
    }];
}

#pragma mark - Images

/// Checks all the button states for images that need to be uploaded.
/// @return True if all images have been uploaded; false at least one image needs to be uploaded
- (BOOL)sdl_allStateImagesAreUploaded {
    for (SDLSoftButtonObject *button in self.softButtonObjects) {
        for (SDLSoftButtonState *state in button.states) {
            SDLArtwork *artwork = state.artwork;
            if (![self.fileManager fileNeedsUpload:artwork]) { continue; }
            return NO;
        }
    }

    return YES;
}

- (BOOL)sdl_supportsSoftButtonImages {
    return self.softButtonCapabilities.imageSupported.boolValue;
}

#pragma mark - Property Overrides

- (void)finishOperation {
    SDLLogV(@"Finishing soft button replace operation");
    [super finishOperation];
}

- (nullable NSString *)name {
    return @"com.sdl.softbuttonmanager.replace";
}

- (NSOperationQueuePriority)queuePriority {
    return NSOperationQueuePriorityNormal;
}

- (nullable NSError *)error {
    return self.internalError;
}

@end

NS_ASSUME_NONNULL_END