diff --git a/Signal/src/AppDelegate.m b/Signal/src/AppDelegate.m index 25831337f..c048c3160 100644 --- a/Signal/src/AppDelegate.m +++ b/Signal/src/AppDelegate.m @@ -189,6 +189,9 @@ static NSString *const kURLHostVerifyPrefix = @"verify"; } [OWSScreenLockUI.sharedManager setupWithRootWindow:self.window]; + [[OWSWindowManager sharedManager] setupWithRootWindow:self.window + screenBlockingWindow:OWSScreenLockUI.sharedManager.screenBlockingWindow]; + [OWSScreenLockUI.sharedManager startObserving]; // Ensure OWSContactsSyncing is instantiated. [OWSContactsSyncing sharedManager]; diff --git a/Signal/src/ViewControllers/CallViewController.swift b/Signal/src/ViewControllers/CallViewController.swift index 20c373205..7130f7da9 100644 --- a/Signal/src/ViewControllers/CallViewController.swift +++ b/Signal/src/ViewControllers/CallViewController.swift @@ -24,19 +24,19 @@ class CallViewController: OWSViewController, CallObserver, CallServiceObserver, let contactsManager: OWSContactsManager - // MARK: Properties + // MARK: - Properties let thread: TSContactThread let call: SignalCall var hasDismissed = false - // MARK: Views + // MARK: - Views var hasConstraints = false var blurView: UIVisualEffectView! var dateFormatter: DateFormatter? - // MARK: Contact Views + // MARK: - Contact Views var contactNameLabel: MarqueeLabel! var contactAvatarView: AvatarImageView! @@ -44,7 +44,7 @@ class CallViewController: OWSViewController, CallObserver, CallServiceObserver, var callStatusLabel: UILabel! var callDurationTimer: Timer? - // MARK: Ongoing Call Controls + // MARK: - Ongoing Call Controls var ongoingCallView: UIView! @@ -59,14 +59,14 @@ class CallViewController: OWSViewController, CallObserver, CallServiceObserver, // call. // var textMessageButton: UIButton! - // MARK: Incoming Call Controls + // MARK: - Incoming Call Controls var incomingCallView: UIView! var acceptIncomingButton: UIButton! var declineIncomingButton: UIButton! - // MARK: Video Views + // MARK: - Video Views var remoteVideoView: RemoteVideoView! var localVideoView: RTCCameraPreviewView! @@ -84,7 +84,7 @@ class CallViewController: OWSViewController, CallObserver, CallServiceObserver, } } - // MARK: Settings Nag Views + // MARK: - Settings Nag Views var isShowingSettingsNag = false { didSet { @@ -96,7 +96,7 @@ class CallViewController: OWSViewController, CallObserver, CallServiceObserver, var settingsNagView: UIView! var settingsNagDescriptionLabel: UILabel! - // MARK: Audio Source + // MARK: - Audio Source var hasAlternateAudioSources: Bool { Logger.info("\(TAG) available audio sources: \(allAudioSources)") @@ -129,7 +129,7 @@ class CallViewController: OWSViewController, CallObserver, CallServiceObserver, } } - // MARK: Initializers + // MARK: - Initializers @available(*, unavailable, message: "use init(call:) constructor instead.") required init?(coder aDecoder: NSCoder) { @@ -155,7 +155,7 @@ class CallViewController: OWSViewController, CallObserver, CallServiceObserver, } } - // MARK: View Lifecycle + // MARK: - View Lifecycle override func viewDidDisappear(_ animated: Bool) { super.viewDidDisappear(animated) @@ -968,6 +968,13 @@ class CallViewController: OWSViewController, CallObserver, CallServiceObserver, preferences.setIsCallKitPrivacyEnabled(preferences.isCallKitPrivacyEnabled()) } + func didTapLeaveCall(sender: UIGestureRecognizer) { + guard sender.state == .recognized else { + return + } + OWSWindowManager.shared().leaveCallView() + } + // MARK: - CallObserver internal func stateDidChange(call: SignalCall, state: CallState) { @@ -996,7 +1003,7 @@ class CallViewController: OWSViewController, CallObserver, CallServiceObserver, self.updateCallUI(callState: call.state) } - // MARK: CallAudioServiceDelegate + // MARK: - CallAudioServiceDelegate func callAudioService(_ callAudioService: CallAudioService, didUpdateIsSpeakerphoneEnabled isSpeakerphoneEnabled: Bool) { SwiftAssertIsOnMainThread(#function) @@ -1140,13 +1147,4 @@ class CallViewController: OWSViewController, CallObserver, CallServiceObserver, updateLocalVideoTrack(localVideoTrack: localVideoTrack) updateRemoteVideoTrack(remoteVideoTrack: remoteVideoTrack) } - - // MARK: - Event Handlers - - func leaveCallViewTapped(sender: UIGestureRecognizer) { - guard sender.state == .recognized else { - return - } - OWSWindowManager.shared().leaveCallView() - } } diff --git a/Signal/src/util/OWSScreenLockUI.h b/Signal/src/util/OWSScreenLockUI.h index 90317d713..d8fad7c96 100644 --- a/Signal/src/util/OWSScreenLockUI.h +++ b/Signal/src/util/OWSScreenLockUI.h @@ -6,12 +6,16 @@ NS_ASSUME_NONNULL_BEGIN @interface OWSScreenLockUI : NSObject +@property (nonatomic, readonly) UIWindow *screenBlockingWindow; + - (instancetype)init NS_UNAVAILABLE; + (instancetype)sharedManager; - (void)setupWithRootWindow:(UIWindow *)rootWindow; +- (void)startObserving; + @end NS_ASSUME_NONNULL_END diff --git a/Signal/src/util/OWSScreenLockUI.m b/Signal/src/util/OWSScreenLockUI.m index b83f60cba..8f92c49a9 100644 --- a/Signal/src/util/OWSScreenLockUI.m +++ b/Signal/src/util/OWSScreenLockUI.m @@ -77,10 +77,6 @@ NS_ASSUME_NONNULL_BEGIN OWSAssertIsOnMainThread(); - _appIsInactiveOrBackground = [UIApplication sharedApplication].applicationState != UIApplicationStateActive; - - [self observeNotifications]; - OWSSingletonAssert(); return self; @@ -126,10 +122,16 @@ NS_ASSUME_NONNULL_BEGIN [self createScreenBlockingWindowWithRootWindow:rootWindow]; OWSAssert(self.screenBlockingWindow); - [[OWSWindowManager sharedManager] setupWithRootWindow:rootWindow screenBlockingWindow:self.screenBlockingWindow]; +} + +- (void)startObserving +{ + _appIsInactiveOrBackground = [UIApplication sharedApplication].applicationState != UIApplicationStateActive; + + [self observeNotifications]; // Default to screen protection until we know otherwise. - [self updateScreenBlockingWindow:ScreenLockUIStateNone animated:NO]; + [self updateScreenBlockingWindow:ScreenLockUIStateScreenProtection animated:NO]; // Initialize the screen lock state. // @@ -393,9 +395,6 @@ NS_ASSUME_NONNULL_BEGIN self.screenBlockingWindow = window; self.screenBlockingViewController = viewController; - - // Default to screen protection until we know otherwise. - [self updateScreenBlockingWindow:ScreenLockUIStateNone animated:NO]; } // The "screen blocking" window has three possible states: diff --git a/Signal/src/util/OWSWindowManager.m b/Signal/src/util/OWSWindowManager.m index 9545c6177..063f30cf1 100644 --- a/Signal/src/util/OWSWindowManager.m +++ b/Signal/src/util/OWSWindowManager.m @@ -151,10 +151,11 @@ const int kReturnToCallWindowHeight = 20.f; UILabel *label = [UILabel new]; label.text = NSLocalizedString(@"CALL_WINDOW_RETURN_TO_CALL", @"Label for the 'return to call' banner."); label.textColor = [UIColor whiteColor]; - // TODO: Dynamic type? + // System UI doesn't use dynamic type; neither do we. label.font = [UIFont ows_regularFontWithSize:14.f]; [rootView addSubview:label]; [label autoCenterInSuperview]; + OWSAssert(!self.returnToCallLabel); self.returnToCallLabel = label; window.rootViewController = viewController; @@ -180,6 +181,7 @@ const int kReturnToCallWindowHeight = 20.f; UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:viewController]; navigationController.navigationBarHidden = YES; + OWSAssert(!self.callNavigationController); self.callNavigationController = navigationController; window.rootViewController = navigationController; @@ -271,63 +273,43 @@ const int kReturnToCallWindowHeight = 20.f; OWSAssert(self.callViewWindow); OWSAssert(self.screenBlockingWindow); + // To avoid bad frames, we never want to hide the blocking window, so we manipulate + // its window level to "hide" it behind other windows. The other windows have fixed + // window level and are shown/hidden as necessary. + // + // Note that we always "hide" before we "show". if (self.isScreenBlockActive) { // Show Screen Block. - [self hideRootWindowIfNecessary]; - [self hideReturnToCallWindowIfNecessary]; - [self hideCallViewWindowIfNecessary]; - [self showScreenBlockWindowIfNecessary]; + [self ensureRootWindowHidden]; + [self ensureReturnToCallWindowHidden]; + [self ensureCallViewWindowHidden]; + [self ensureScreenBlockWindowShown]; } else if (self.callViewController && self.isCallViewActive) { // Show Call View. - [self hideRootWindowIfNecessary]; - [self hideReturnToCallWindowIfNecessary]; - [self showCallViewWindowIfNecessary]; - [self hideScreenBlockWindowIfNecessary]; + [self ensureRootWindowHidden]; + [self ensureReturnToCallWindowHidden]; + [self ensureCallViewWindowShown]; + [self ensureScreenBlockWindowHidden]; } else if (self.callViewController) { // Show Root Window + "Return to Call". - [self showRootWindowIfNecessary]; - [self showReturnToCallWindowIfNecessary]; - [self hideCallViewWindowIfNecessary]; - [self hideScreenBlockWindowIfNecessary]; + [self ensureRootWindowShown]; + [self ensureReturnToCallWindowShown]; + [self ensureCallViewWindowHidden]; + [self ensureScreenBlockWindowHidden]; } else { // Show Root Window - [self showRootWindowIfNecessary]; - [self hideReturnToCallWindowIfNecessary]; - [self hideCallViewWindowIfNecessary]; - [self hideScreenBlockWindowIfNecessary]; + [self ensureRootWindowShown]; + [self ensureReturnToCallWindowHidden]; + [self ensureCallViewWindowHidden]; + [self ensureScreenBlockWindowHidden]; } - - DDLogVerbose(@"%@ rootWindow: %d %f", self.logTag, self.rootWindow.hidden, self.rootWindow.windowLevel); - DDLogVerbose(@"%@ returnToCallWindow: %d %f", - self.logTag, - self.returnToCallWindow.hidden, - self.returnToCallWindow.windowLevel); - DDLogVerbose(@"%@ callViewWindow: %d %f", self.logTag, self.callViewWindow.hidden, self.callViewWindow.windowLevel); - DDLogVerbose(@"%@ screenBlockingWindow: %d %f", - self.logTag, - self.screenBlockingWindow.hidden, - self.screenBlockingWindow.windowLevel); - - dispatch_async(dispatch_get_main_queue(), ^{ - DDLogVerbose(@"%@ ...rootWindow: %d %f", self.logTag, self.rootWindow.hidden, self.rootWindow.windowLevel); - DDLogVerbose(@"%@ ...returnToCallWindow: %d %f", - self.logTag, - self.returnToCallWindow.hidden, - self.returnToCallWindow.windowLevel); - DDLogVerbose( - @"%@ ...callViewWindow: %d %f", self.logTag, self.callViewWindow.hidden, self.callViewWindow.windowLevel); - DDLogVerbose(@"%@ ...screenBlockingWindow: %d %f", - self.logTag, - self.screenBlockingWindow.hidden, - self.screenBlockingWindow.windowLevel); - }); } -- (void)showRootWindowIfNecessary +- (void)ensureRootWindowShown { OWSAssertIsOnMainThread(); @@ -368,7 +350,7 @@ const int kReturnToCallWindowHeight = 20.f; self.rootFrontmostViewController = nil; } -- (void)hideRootWindowIfNecessary +- (void)ensureRootWindowHidden { OWSAssertIsOnMainThread(); @@ -390,7 +372,7 @@ const int kReturnToCallWindowHeight = 20.f; self.rootWindow.hidden = YES; } -- (void)showReturnToCallWindowIfNecessary +- (void)ensureReturnToCallWindowShown { OWSAssertIsOnMainThread(); @@ -414,7 +396,7 @@ const int kReturnToCallWindowHeight = 20.f; self.returnToCallWindow.hidden = NO; } -- (void)hideReturnToCallWindowIfNecessary +- (void)ensureReturnToCallWindowHidden { OWSAssertIsOnMainThread(); @@ -426,7 +408,7 @@ const int kReturnToCallWindowHeight = 20.f; [self.returnToCallLabel.layer removeAllAnimations]; } -- (void)showCallViewWindowIfNecessary +- (void)ensureCallViewWindowShown { OWSAssertIsOnMainThread(); @@ -438,7 +420,7 @@ const int kReturnToCallWindowHeight = 20.f; [self.callViewWindow.rootViewController becomeFirstResponder]; } -- (void)hideCallViewWindowIfNecessary +- (void)ensureCallViewWindowHidden { OWSAssertIsOnMainThread(); @@ -449,7 +431,7 @@ const int kReturnToCallWindowHeight = 20.f; self.callViewWindow.hidden = YES; } -- (void)showScreenBlockWindowIfNecessary +- (void)ensureScreenBlockWindowShown { OWSAssertIsOnMainThread(); @@ -461,7 +443,7 @@ const int kReturnToCallWindowHeight = 20.f; [self.screenBlockingWindow.rootViewController becomeFirstResponder]; } -- (void)hideScreenBlockWindowIfNecessary +- (void)ensureScreenBlockWindowHidden { OWSAssertIsOnMainThread();