summaryrefslogtreecommitdiff
path: root/SmartDeviceLink/SDLLockScreenPresenter.m
blob: 51e7b0749c2fcef77817e24806dd737e81d54840 (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
//
//  SDLLockScreenPresenter.m
//  SmartDeviceLink-iOS
//
//  Created by Joel Fischer on 7/15/16.
//  Copyright © 2016 smartdevicelink. All rights reserved.
//

#import "SDLLockScreenPresenter.h"

#import "SDLLockScreenRootViewController.h"
#import "SDLLogMacros.h"
#import "SDLStreamingMediaManagerConstants.h"


NS_ASSUME_NONNULL_BEGIN

@interface SDLLockScreenPresenter ()

@property (strong, nonatomic, nullable) UIWindow *lockWindow;
@property (assign, nonatomic, readwrite) BOOL presented;

@end


@implementation SDLLockScreenPresenter

#pragma mark - Lifecycle

- (instancetype)init {
    self = [super init];
    if (!self) { return nil; }

    _presented = NO;

    return self;
}

- (void)stop {
    self.presented = NO;

    if (!self.lockWindow) {
        return;
    }

    // Remove the lockscreen if presented
    [self sdl_dismissWithCompletionHandler:^{
        self.lockWindow = nil;
    }];
}

- (void)updateLockScreenToShow:(BOOL)show {
    if (show == self.presented) { return; }
    self.presented = show;

    if (show) {
        [self sdl_presentLockscreenWithCompletionHandler:^{
            if (self.presented) { return; }

            SDLLogV(@"The lockscreen has been presented but needs to be dismissed");
            [self sdl_dismissWithCompletionHandler:nil];
        }];
    } else {
        [self sdl_dismissWithCompletionHandler:^{
            if (!self.presented) { return; }

            SDLLogV(@"The lockscreen has been dismissed but needs to be presented");
            [self sdl_presentLockscreenWithCompletionHandler:nil];
        }];
    }
}


#pragma mark - Present Lock Window

- (void)sdl_presentWithCompletionHandler:(void (^ _Nullable)(void))completionHandler {
    SDLLogD(@"Trying to present lockscreen");
    __weak typeof(self) weakSelf = self;
    dispatch_async(dispatch_get_main_queue(), ^{
        if (UIApplication.sharedApplication.applicationState != UIApplicationStateActive) {
            // If the the `UIWindow` is created while the app is backgrounded and the app is using `SceneDelegate` class (iOS 13+), then the window will not be created correctly. Wait until the app is foregrounded before creating the window.
            SDLLogV(@"Application is backgrounded. The lockscreen will not be shown until the application is brought to the foreground.");
            if (completionHandler == nil) { return; }
            return completionHandler();
        }
        [weakSelf sdl_presentLockscreenWithCompletionHandler:completionHandler];
    });
}

- (void)sdl_presentLockscreenWithCompletionHandler:(void (^ _Nullable)(void))completionHandler {
    if (!self.lockWindow) {
        self.lockWindow = [self.class sdl_createUIWindow];
        self.lockWindow.backgroundColor = [UIColor clearColor];
        self.lockWindow.windowLevel = UIWindowLevelAlert + 1;
        self.lockWindow.rootViewController = [[SDLLockScreenRootViewController alloc] init];
    }

    SDLLogD(@"Presenting the lockscreen window");
    [self.lockWindow makeKeyAndVisible];

    if ([self sdl_isPresented]) {
        // Call this right before attempting to present the view controller make sure we are not already animating, otherwise the app may crash.
        SDLLogV(@"The lockscreen is already being presented");
        if (completionHandler == nil) { return; }
        return completionHandler();
    }

    // Let ourselves know that the lockscreen will present so we can pause video streaming for a few milliseconds - otherwise the animation to show the lockscreen will be very janky.
    [[NSNotificationCenter defaultCenter] postNotificationName:SDLLockScreenManagerWillPresentLockScreenViewController object:nil];

    [self.lockWindow.rootViewController presentViewController:self.lockViewController animated:YES completion:^{
        // Tell everyone we are done so video streaming can resume
        [[NSNotificationCenter defaultCenter] postNotificationName:SDLLockScreenManagerDidPresentLockScreenViewController object:nil];

        if (completionHandler == nil) { return; }
        return completionHandler();
    }];
}


#pragma mark - Dismiss Lock Window

- (void)sdl_dismissWithCompletionHandler:(void (^ _Nullable)(void))completionHandler {
    __weak typeof(self) weakSelf = self;
    dispatch_async(dispatch_get_main_queue(), ^{
        if (UIApplication.sharedApplication.applicationState != UIApplicationStateActive) {
            SDLLogV(@"Application is backgrounded. The lockscreen will not be dismissed until the app is brought to the foreground.");
            if (completionHandler == nil) { return; }
            return completionHandler();
        }
        [weakSelf sdl_dismissLockscreenWithCompletionHandler:completionHandler];
    });
}

- (void)sdl_dismissLockscreenWithCompletionHandler:(void (^ _Nullable)(void))completionHandler {
    if (self.lockViewController == nil) {
        SDLLogW(@"Attempted to dismiss lockscreen, but lockViewController is not set");
        if (completionHandler == nil) { return; }
        return completionHandler();
    }

    if ([self sdl_isBeingDismissed]) {
        // Make sure we are not already animating, otherwise the app may crash
        SDLLogV(@"The lockscreen is already being dismissed");
        if (completionHandler == nil) { return; }
        return completionHandler();
    }

    // Let ourselves know that the lockscreen will dismiss so we can pause video streaming for a few milliseconds - otherwise the animation to dismiss the lockscreen will be very janky.
    [[NSNotificationCenter defaultCenter] postNotificationName:SDLLockScreenManagerWillDismissLockScreenViewController object:nil];

    SDLLogD(@"Hiding the lockscreen window");
    __weak typeof(self) weakSelf = self;
    [self.lockViewController dismissViewControllerAnimated:YES completion:^{
        [weakSelf.lockWindow setHidden:YES];

        // Tell everyone we are done so video streaming can resume
        [[NSNotificationCenter defaultCenter] postNotificationName:SDLLockScreenManagerDidDismissLockScreenViewController object:nil];

        if (completionHandler == nil) { return; }
        return completionHandler();
    }];
}


#pragma mark - Custom Presented / Dismissed Getters

- (BOOL)sdl_isPresented {
    return (self.lockViewController.isViewLoaded && (self.lockViewController.view.window || self.lockViewController.isBeingPresented) && self.lockWindow.isKeyWindow);
}

- (BOOL)sdl_isBeingDismissed {
    return (self.lockViewController.isBeingDismissed || self.lockViewController.isMovingFromParentViewController);
}


#pragma mark - Window Helpers

/// If the app is using `SceneDelegate` class (iOS 13+), then the `UIWindow` must be initalized using the active `UIWindowScene`. Otherwise, the newly created window will not appear on the screen even though it is added to the `UIApplication`'s `windows` stack.
+ (UIWindow *)sdl_createUIWindow {
    if (@available(iOS 13.0, *)) {
        for (UIScene *scene in UIApplication.sharedApplication.connectedScenes) {
             // The scene is either foreground active / inactive, background, or unattached. If the latter three, we don't want to do anything with them. Also check that the scene is for the application and not an external display or CarPlay.
            if (scene.activationState != UISceneActivationStateForegroundActive ||
                ![scene.session.role isEqualToString:UIWindowSceneSessionRoleApplication] ||
                ![scene isKindOfClass:[UIWindowScene class]]) {
                continue;
            }

            return [[UIWindow alloc] initWithWindowScene:(UIWindowScene *)scene];
        }
    }

    return [[UIWindow alloc] initWithFrame:UIScreen.mainScreen.bounds];
}

@end

NS_ASSUME_NONNULL_END