Merge remote-tracking branch 'private/hotfix/2.23.3'
This commit is contained in:
commit
df685da497
@ -299,28 +299,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
DDLogInfo(@"%@ trying to set is screen lock enabled: %@", self.logTag, @(shouldBeEnabled));
|
||||
|
||||
__weak typeof(self) weakSelf = self;
|
||||
if (shouldBeEnabled) {
|
||||
[OWSScreenLock.sharedManager tryToEnableScreenLockWithCompletion:^(NSError *_Nullable error) {
|
||||
[weakSelf updateTableContents];
|
||||
|
||||
if (error) {
|
||||
[OWSAlerts showAlertWithTitle:NSLocalizedString(@"SCREEN_LOCK_ENABLE_FAILED",
|
||||
@"Title for alert indicating that screen lock could not be enabled.")
|
||||
message:error.localizedDescription];
|
||||
}
|
||||
}];
|
||||
} else {
|
||||
[OWSScreenLock.sharedManager tryToDisableScreenLockWithCompletion:^(NSError *_Nullable error) {
|
||||
[weakSelf updateTableContents];
|
||||
|
||||
if (error) {
|
||||
[OWSAlerts showAlertWithTitle:NSLocalizedString(@"SCREEN_LOCK_DISABLE_FAILED",
|
||||
@"Title for alert indicating that screen lock could not be disabled.")
|
||||
message:error.localizedDescription];
|
||||
}
|
||||
}];
|
||||
}
|
||||
[OWSScreenLock.sharedManager setIsScreenLockEnabled:shouldBeEnabled];
|
||||
}
|
||||
|
||||
- (void)screenLockDidChange:(NSNotification *)notification
|
||||
|
||||
@ -24,7 +24,6 @@ import LocalAuthentication
|
||||
0
|
||||
]
|
||||
|
||||
@objc public static let ScreenLockWasEnabled = Notification.Name("ScreenLockWasEnabled")
|
||||
@objc public static let ScreenLockDidChange = Notification.Name("ScreenLockDidChange")
|
||||
|
||||
let primaryStorage: OWSPrimaryStorage
|
||||
@ -34,10 +33,6 @@ import LocalAuthentication
|
||||
private let OWSScreenLock_Key_IsScreenLockEnabled = "OWSScreenLock_Key_IsScreenLockEnabled"
|
||||
private let OWSScreenLock_Key_ScreenLockTimeoutSeconds = "OWSScreenLock_Key_ScreenLockTimeoutSeconds"
|
||||
|
||||
// We don't want the verification process itself to trigger unlock verification.
|
||||
// Passcode-code only authentication process deactivates the app.
|
||||
private var ignoreUnlockUntilActive = false
|
||||
|
||||
// We temporarily resign any first responder while the Screen Lock is presented.
|
||||
weak var firstResponderBeforeLockscreen: UIResponder?
|
||||
|
||||
@ -53,21 +48,6 @@ import LocalAuthentication
|
||||
super.init()
|
||||
|
||||
SwiftSingletons.register(self)
|
||||
|
||||
NotificationCenter.default.addObserver(self,
|
||||
selector: #selector(didBecomeActive),
|
||||
name: NSNotification.Name.OWSApplicationDidBecomeActive,
|
||||
object: nil)
|
||||
}
|
||||
|
||||
deinit {
|
||||
NotificationCenter.default.removeObserver(self)
|
||||
}
|
||||
|
||||
func didBecomeActive() {
|
||||
SwiftAssertIsOnMainThread(#function)
|
||||
|
||||
ignoreUnlockUntilActive = false
|
||||
}
|
||||
|
||||
// MARK: - Properties
|
||||
@ -83,17 +63,13 @@ import LocalAuthentication
|
||||
return self.dbConnection.bool(forKey: OWSScreenLock_Key_IsScreenLockEnabled, inCollection: OWSScreenLock_Collection, defaultValue: false)
|
||||
}
|
||||
|
||||
private func setIsScreenLockEnabled(value: Bool) {
|
||||
@objc
|
||||
public func setIsScreenLockEnabled(_ value: Bool) {
|
||||
SwiftAssertIsOnMainThread(#function)
|
||||
assert(OWSStorage.isStorageReady())
|
||||
|
||||
let isEnabling = value && !isScreenLockEnabled()
|
||||
|
||||
self.dbConnection.setBool(value, forKey: OWSScreenLock_Key_IsScreenLockEnabled, inCollection: OWSScreenLock_Collection)
|
||||
|
||||
if isEnabling {
|
||||
NotificationCenter.default.postNotificationNameAsync(OWSScreenLock.ScreenLockWasEnabled, object: nil)
|
||||
}
|
||||
NotificationCenter.default.postNotificationNameAsync(OWSScreenLock.ScreenLockDidChange, object: nil)
|
||||
}
|
||||
|
||||
@ -119,74 +95,18 @@ import LocalAuthentication
|
||||
|
||||
// MARK: - Methods
|
||||
|
||||
// On failure, completion is called with an error argument.
|
||||
// On success or cancel, completion is called with nil argument.
|
||||
// Success and cancel can be differentiated by consulting
|
||||
// isScreenLockEnabled.
|
||||
@objc public func tryToEnableScreenLock(completion: @escaping ((Error?) -> Void)) {
|
||||
tryToVerifyLocalAuthentication(localizedReason: NSLocalizedString("SCREEN_LOCK_REASON_ENABLE_SCREEN_LOCK",
|
||||
comment: "Description of how and why Signal iOS uses Touch ID/Face ID/Phone Passcode to enable 'screen lock'."),
|
||||
completion: { (outcome: OWSScreenLockOutcome) in
|
||||
SwiftAssertIsOnMainThread(#function)
|
||||
|
||||
switch outcome {
|
||||
case .failure(let error):
|
||||
completion(self.authenticationError(errorDescription: error))
|
||||
case .unexpectedFailure(let error):
|
||||
completion(self.authenticationError(errorDescription: error))
|
||||
case .success:
|
||||
self.setIsScreenLockEnabled(value: true)
|
||||
completion(nil)
|
||||
case .cancel:
|
||||
completion(nil)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// On failure, completion is called with an error argument.
|
||||
// On success or cancel, completion is called with nil argument.
|
||||
// Success and cancel can be differentiated by consulting
|
||||
// isScreenLockEnabled.
|
||||
@objc public func tryToDisableScreenLock(completion: @escaping ((Error?) -> Void)) {
|
||||
tryToVerifyLocalAuthentication(localizedReason: NSLocalizedString("SCREEN_LOCK_REASON_DISABLE_SCREEN_LOCK",
|
||||
comment: "Description of how and why Signal iOS uses Touch ID/Face ID/Phone Passcode to disable 'screen lock'."),
|
||||
completion: { (outcome: OWSScreenLockOutcome) in
|
||||
SwiftAssertIsOnMainThread(#function)
|
||||
|
||||
switch outcome {
|
||||
case .failure(let error):
|
||||
completion(self.authenticationError(errorDescription: error))
|
||||
case .unexpectedFailure(let error):
|
||||
completion(self.authenticationError(errorDescription: error))
|
||||
case .success:
|
||||
self.setIsScreenLockEnabled(value: false)
|
||||
completion(nil)
|
||||
case .cancel:
|
||||
completion(nil)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@objc public func tryToUnlockScreenLock(success: @escaping (() -> Void),
|
||||
failure: @escaping ((Error) -> Void),
|
||||
unexpectedFailure: @escaping ((Error) -> Void),
|
||||
cancel: @escaping (() -> Void)) {
|
||||
guard !ignoreUnlockUntilActive else {
|
||||
DispatchQueue.main.async {
|
||||
success()
|
||||
}
|
||||
guard CurrentAppContext().isMainAppAndActive else {
|
||||
owsFail("\(self.logTag) \(#function) Unexpected request for 'screen lock' unlock UI while app is inactive.")
|
||||
cancel()
|
||||
return
|
||||
}
|
||||
|
||||
// A popped keyboard breaks our layout and obscures the unlock button.
|
||||
if let firstResponder = UIResponder.currentFirstResponder() {
|
||||
Logger.debug("\(self.logTag) in \(#function) resigning first responder: \(firstResponder)")
|
||||
firstResponder.resignFirstResponder()
|
||||
self.firstResponderBeforeLockscreen = firstResponder
|
||||
}
|
||||
|
||||
tryToVerifyLocalAuthentication(localizedReason: NSLocalizedString("SCREEN_LOCK_REASON_UNLOCK_SCREEN_LOCK",
|
||||
comment: "Description of how and why Signal iOS uses Touch ID/Face ID/Phone Passcode to unlock 'screen lock'."),
|
||||
comment: "Description of how and why Signal iOS uses Touch ID/Face ID/Phone Passcode to unlock 'screen lock'."),
|
||||
completion: { (outcome: OWSScreenLockOutcome) in
|
||||
SwiftAssertIsOnMainThread(#function)
|
||||
|
||||
@ -196,16 +116,6 @@ import LocalAuthentication
|
||||
case .unexpectedFailure(let error):
|
||||
unexpectedFailure(self.authenticationError(errorDescription: error))
|
||||
case .success:
|
||||
// It's important we restore first responder status once the user completes
|
||||
// In some cases, (RegistrationLock Reminder) it just puts the keyboard back where
|
||||
// the user needs it, saving them a tap.
|
||||
// But in the case of an inputAccessoryView, like the ConversationViewController,
|
||||
// failing to restore firstResponder could make the input toolbar disappear until
|
||||
if let firstResponder = self.firstResponderBeforeLockscreen {
|
||||
Logger.debug("\(self.logTag) in \(#function) regaining first responder: \(firstResponder)")
|
||||
firstResponder.becomeFirstResponder()
|
||||
self.firstResponderBeforeLockscreen = nil
|
||||
}
|
||||
success()
|
||||
case .cancel:
|
||||
cancel()
|
||||
@ -255,8 +165,6 @@ import LocalAuthentication
|
||||
return
|
||||
}
|
||||
|
||||
// Use ignoreUnlockUntilActive to suppress unlock verifications.
|
||||
ignoreUnlockUntilActive = true
|
||||
context.evaluatePolicy(.deviceOwnerAuthentication, localizedReason: localizedReason) { success, evaluateError in
|
||||
|
||||
if success {
|
||||
@ -349,7 +257,7 @@ import LocalAuthentication
|
||||
private func screenLockContext() -> LAContext {
|
||||
let context = LAContext()
|
||||
|
||||
// Never re-use recent biometric auth.
|
||||
// Never recycle biometric auth.
|
||||
context.touchIDAuthenticationAllowableReuseDuration = TimeInterval(0)
|
||||
|
||||
if #available(iOS 11.0, *) {
|
||||
|
||||
@ -9,6 +9,29 @@
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
typedef NS_ENUM(NSUInteger, ScreenLockUIState) {
|
||||
ScreenLockUIStateNone,
|
||||
// Shown while app is inactive or background, if enabled.
|
||||
ScreenLockUIStateScreenProtection,
|
||||
// Shown while app is active, if enabled.
|
||||
ScreenLockUIStateScreenLock,
|
||||
};
|
||||
|
||||
NSString *NSStringForScreenLockUIState(ScreenLockUIState value);
|
||||
NSString *NSStringForScreenLockUIState(ScreenLockUIState value)
|
||||
{
|
||||
switch (value) {
|
||||
case ScreenLockUIStateNone:
|
||||
return @"ScreenLockUIStateNone";
|
||||
case ScreenLockUIStateScreenProtection:
|
||||
return @"ScreenLockUIStateScreenProtection";
|
||||
case ScreenLockUIStateScreenLock:
|
||||
return @"ScreenLockUIStateScreenLock";
|
||||
}
|
||||
}
|
||||
|
||||
const UIWindowLevel UIWindowLevel_Background = -1.f;
|
||||
|
||||
@interface OWSScreenLockUI ()
|
||||
|
||||
@property (nonatomic) UIWindow *screenBlockingWindow;
|
||||
@ -18,35 +41,43 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
@property (nonatomic) NSArray<NSLayoutConstraint *> *screenBlockingConstraints;
|
||||
@property (nonatomic) NSString *screenBlockingSignature;
|
||||
|
||||
// Unlike UIApplication.applicationState, this state is
|
||||
// updated conservatively, e.g. the flag is cleared during
|
||||
// "will enter background."
|
||||
@property (nonatomic) BOOL appIsInactive;
|
||||
// Unlike UIApplication.applicationState, this state reflects the
|
||||
// notifications, i.e. "did become active", "will resign active",
|
||||
// "will enter foreground", "did enter background".
|
||||
//
|
||||
// We want to update our state to reflect these transitions and have
|
||||
// the "update" logic be consistent with "last reported" state. i.e.
|
||||
// when you're responding to "will resign active", we need to behave
|
||||
// as though we're already inactive.
|
||||
//
|
||||
// Secondly, we need to show the screen protection _before_ we become
|
||||
// inactive in order for it to be reflected in the app switcher.
|
||||
@property (nonatomic) BOOL appIsInactiveOrBackground;
|
||||
@property (nonatomic) BOOL appIsInBackground;
|
||||
|
||||
@property (nonatomic) BOOL isShowingScreenLockUI;
|
||||
@property (nonatomic) BOOL didLastUnlockAttemptFail;
|
||||
|
||||
// We want to remain in "screen lock" mode while "local auth"
|
||||
// UI is dismissing.
|
||||
// UI is dismissing. So we lazily clear isShowingScreenLockUI
|
||||
// using this property.
|
||||
@property (nonatomic) BOOL shouldClearAuthUIWhenActive;
|
||||
|
||||
// Indicates whether or not the user is currently locked out of
|
||||
// the app. Only applies if OWSScreenLock.isScreenLockEnabled.
|
||||
// the app. Should only be set if OWSScreenLock.isScreenLockEnabled.
|
||||
//
|
||||
// * The user is locked out out by default on app launch.
|
||||
// * The user is locked out by default on app launch.
|
||||
// * The user is also locked out if they spend more than
|
||||
// "timeout" seconds outside the app. When the user leaves
|
||||
// the app, a "countdown" begins.
|
||||
@property (nonatomic) BOOL isScreenLockUnlocked;
|
||||
@property (nonatomic) BOOL isScreenLockLocked;
|
||||
|
||||
// The "countdown" until screen lock takes effect.
|
||||
@property (nonatomic, nullable) NSDate *screenLockCountdownDate;
|
||||
|
||||
// We normally start the "countdown" when the app enters the background,
|
||||
// But we also want to start the "countdown" if the app is inactive for
|
||||
// more than N seconds.
|
||||
@property (nonatomic, nullable) NSTimer *inactiveTimer;
|
||||
@property (nonatomic) UIWindow *rootWindow;
|
||||
|
||||
@property (nonatomic, nullable) UIResponder *rootWindowResponder;
|
||||
|
||||
@end
|
||||
|
||||
@ -72,6 +103,10 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
return self;
|
||||
}
|
||||
|
||||
OWSAssertIsOnMainThread();
|
||||
|
||||
_appIsInactiveOrBackground = [UIApplication sharedApplication].applicationState != UIApplicationStateActive;
|
||||
|
||||
[self observeNotifications];
|
||||
|
||||
OWSSingletonAssert();
|
||||
@ -102,17 +137,13 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
selector:@selector(applicationDidEnterBackground:)
|
||||
name:OWSApplicationDidEnterBackgroundNotification
|
||||
object:nil];
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(registrationStateDidChange)
|
||||
name:RegistrationStateDidChangeNotification
|
||||
object:nil];
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(screenLockDidChange:)
|
||||
name:OWSScreenLock.ScreenLockDidChange
|
||||
object:nil];
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(screenLockWasEnabled:)
|
||||
name:OWSScreenLock.ScreenLockWasEnabled
|
||||
selector:@selector(clockDidChange:)
|
||||
name:NSSystemClockDidChangeNotification
|
||||
object:nil];
|
||||
}
|
||||
|
||||
@ -121,27 +152,50 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
OWSAssertIsOnMainThread();
|
||||
OWSAssert(rootWindow);
|
||||
|
||||
self.rootWindow = rootWindow;
|
||||
|
||||
[self prepareScreenProtectionWithRootWindow:rootWindow];
|
||||
|
||||
// Initialize the screen lock state.
|
||||
//
|
||||
// It's not safe to access OWSScreenLock.isScreenLockEnabled
|
||||
// until the app is ready.
|
||||
[AppReadiness runNowOrWhenAppIsReady:^{
|
||||
[self ensureScreenProtection];
|
||||
self.isScreenLockLocked = OWSScreenLock.sharedManager.isScreenLockEnabled;
|
||||
|
||||
[self ensureUI];
|
||||
}];
|
||||
}
|
||||
|
||||
#pragma mark - Methods
|
||||
|
||||
- (void)tryToActivateScreenLockUponBecomingActive
|
||||
- (void)tryToActivateScreenLockBasedOnCountdown
|
||||
{
|
||||
OWSAssert(!self.appIsInactive);
|
||||
OWSAssert(!self.appIsInBackground);
|
||||
OWSAssertIsOnMainThread();
|
||||
|
||||
if (!self.isScreenLockUnlocked) {
|
||||
// Screen lock is already activated.
|
||||
if (!AppReadiness.isAppReady) {
|
||||
// It's not safe to access OWSScreenLock.isScreenLockEnabled
|
||||
// until the app is ready.
|
||||
//
|
||||
// We don't need to try to lock the screen lock;
|
||||
// It will be initialized by `setupWithRootWindow`.
|
||||
DDLogVerbose(@"%@ tryToActivateScreenLockUponBecomingActive NO 0", self.logTag);
|
||||
return;
|
||||
}
|
||||
if (!OWSScreenLock.sharedManager.isScreenLockEnabled) {
|
||||
// Screen lock is not enabled.
|
||||
DDLogVerbose(@"%@ tryToActivateScreenLockUponBecomingActive NO 1", self.logTag);
|
||||
return;
|
||||
}
|
||||
if (self.isScreenLockLocked) {
|
||||
// Screen lock is already activated.
|
||||
DDLogVerbose(@"%@ tryToActivateScreenLockUponBecomingActive NO 2", self.logTag);
|
||||
return;
|
||||
}
|
||||
if (!self.screenLockCountdownDate) {
|
||||
// We became inactive, but never started a countdown.
|
||||
DDLogVerbose(@"%@ tryToActivateScreenLockUponBecomingActive NO 1", self.logTag);
|
||||
DDLogVerbose(@"%@ tryToActivateScreenLockUponBecomingActive NO 3", self.logTag);
|
||||
return;
|
||||
}
|
||||
NSTimeInterval countdownInterval = fabs([self.screenLockCountdownDate timeIntervalSinceNow]);
|
||||
@ -149,99 +203,108 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
NSTimeInterval screenLockTimeout = OWSScreenLock.sharedManager.screenLockTimeout;
|
||||
OWSAssert(screenLockTimeout >= 0);
|
||||
if (countdownInterval >= screenLockTimeout) {
|
||||
self.isScreenLockUnlocked = NO;
|
||||
DDLogVerbose(@"%@ tryToActivateScreenLockUponBecomingActive YES 1 (%0.3f >= %0.3f)",
|
||||
self.isScreenLockLocked = YES;
|
||||
|
||||
DDLogVerbose(@"%@ tryToActivateScreenLockUponBecomingActive YES 4 (%0.3f >= %0.3f)",
|
||||
self.logTag,
|
||||
countdownInterval,
|
||||
screenLockTimeout);
|
||||
} else {
|
||||
DDLogVerbose(@"%@ tryToActivateScreenLockUponBecomingActive NO 2 (%0.3f < %0.3f)",
|
||||
DDLogVerbose(@"%@ tryToActivateScreenLockUponBecomingActive NO 5 (%0.3f < %0.3f)",
|
||||
self.logTag,
|
||||
countdownInterval,
|
||||
screenLockTimeout);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setAppIsInactive:(BOOL)appIsInactive
|
||||
// Setter for property indicating that the app is either
|
||||
// inactive or in the background, e.g. not "foreground and active."
|
||||
- (void)setAppIsInactiveOrBackground:(BOOL)appIsInactiveOrBackground
|
||||
{
|
||||
_appIsInactive = appIsInactive;
|
||||
OWSAssertIsOnMainThread();
|
||||
|
||||
if (!appIsInactive) {
|
||||
[self tryToActivateScreenLockUponBecomingActive];
|
||||
_appIsInactiveOrBackground = appIsInactiveOrBackground;
|
||||
|
||||
if (appIsInactiveOrBackground) {
|
||||
if (!self.isShowingScreenLockUI) {
|
||||
[self startScreenLockCountdownIfNecessary];
|
||||
}
|
||||
} else {
|
||||
[self tryToActivateScreenLockBasedOnCountdown];
|
||||
|
||||
DDLogInfo(@"%@ setAppIsInactiveOrBackground clear screenLockCountdownDate.", self.logTag);
|
||||
self.screenLockCountdownDate = nil;
|
||||
}
|
||||
|
||||
[self startInactiveTimerIfNecessary];
|
||||
|
||||
[self ensureScreenProtection];
|
||||
[self ensureUI];
|
||||
}
|
||||
|
||||
// Setter for property indicating that the app is in the background.
|
||||
// If true, by definition the app is not active.
|
||||
- (void)setAppIsInBackground:(BOOL)appIsInBackground
|
||||
{
|
||||
if (appIsInBackground && !_appIsInBackground) {
|
||||
[self startScreenLockCountdownIfNecessary];
|
||||
}
|
||||
OWSAssertIsOnMainThread();
|
||||
|
||||
_appIsInBackground = appIsInBackground;
|
||||
|
||||
[self ensureScreenProtection];
|
||||
if (self.appIsInBackground) {
|
||||
[self startScreenLockCountdownIfNecessary];
|
||||
} else {
|
||||
[self tryToActivateScreenLockBasedOnCountdown];
|
||||
}
|
||||
|
||||
[self ensureUI];
|
||||
}
|
||||
|
||||
- (void)startScreenLockCountdownIfNecessary
|
||||
{
|
||||
DDLogVerbose(@"%@ startScreenLockCountdownIfNecessary: %d", self.logTag, self.screenLockCountdownDate != nil);
|
||||
|
||||
if (!self.screenLockCountdownDate) {
|
||||
DDLogVerbose(@"%@ startScreenLockCountdownIfNecessary.", self.logTag);
|
||||
DDLogInfo(@"%@ startScreenLockCountdown.", self.logTag);
|
||||
self.screenLockCountdownDate = [NSDate new];
|
||||
}
|
||||
|
||||
self.didLastUnlockAttemptFail = NO;
|
||||
|
||||
[self clearInactiveTimer];
|
||||
}
|
||||
|
||||
- (void)ensureScreenProtection
|
||||
// Ensure that:
|
||||
//
|
||||
// * The blocking window has the correct state.
|
||||
// * That we show the "iOS auth UI to unlock" if necessary.
|
||||
- (void)ensureUI
|
||||
{
|
||||
OWSAssertIsOnMainThread();
|
||||
|
||||
if (!AppReadiness.isAppReady) {
|
||||
[AppReadiness runNowOrWhenAppIsReady:^{
|
||||
[self ensureScreenProtection];
|
||||
[self ensureUI];
|
||||
}];
|
||||
return;
|
||||
}
|
||||
|
||||
BOOL shouldHaveScreenLock = self.shouldHaveScreenLock;
|
||||
BOOL shouldHaveScreenProtection = self.shouldHaveScreenProtection;
|
||||
ScreenLockUIState desiredUIState = self.desiredUIState;
|
||||
|
||||
BOOL shouldShowBlockWindow = shouldHaveScreenProtection || shouldHaveScreenLock;
|
||||
DDLogVerbose(@"%@, shouldHaveScreenProtection: %d, shouldHaveScreenLock: %d, shouldShowBlockWindow: %d",
|
||||
self.logTag,
|
||||
shouldHaveScreenProtection,
|
||||
shouldHaveScreenLock,
|
||||
shouldShowBlockWindow);
|
||||
if (self.screenBlockingWindow.hidden != !shouldShowBlockWindow) {
|
||||
DDLogInfo(@"%@, %@.", self.logTag, shouldShowBlockWindow ? @"showing block window" : @"hiding block window");
|
||||
}
|
||||
[self updateScreenBlockingWindow:shouldShowBlockWindow shouldHaveScreenLock:shouldHaveScreenLock animated:YES];
|
||||
DDLogVerbose(@"%@, ensureUI: %@", self.logTag, NSStringForScreenLockUIState(desiredUIState));
|
||||
|
||||
if (shouldHaveScreenLock && !self.didLastUnlockAttemptFail) {
|
||||
[self tryToPresentScreenLockUI];
|
||||
[self updateScreenBlockingWindow:desiredUIState animated:YES];
|
||||
|
||||
// Show the "iOS auth UI to unlock" if necessary.
|
||||
if (desiredUIState == ScreenLockUIStateScreenLock && !self.didLastUnlockAttemptFail) {
|
||||
[self tryToPresentAuthUIToUnlockScreenLock];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)tryToPresentScreenLockUI
|
||||
- (void)tryToPresentAuthUIToUnlockScreenLock
|
||||
{
|
||||
OWSAssertIsOnMainThread();
|
||||
|
||||
// If we no longer want to present the screen lock UI, abort.
|
||||
if (!self.shouldHaveScreenLock) {
|
||||
return;
|
||||
}
|
||||
if (self.didLastUnlockAttemptFail) {
|
||||
return;
|
||||
}
|
||||
if (self.isShowingScreenLockUI) {
|
||||
// We're already showing the auth UI; abort.
|
||||
return;
|
||||
}
|
||||
if (self.appIsInactiveOrBackground) {
|
||||
// Never show the auth UI unless active.
|
||||
return;
|
||||
}
|
||||
|
||||
@ -254,9 +317,9 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
self.isShowingScreenLockUI = NO;
|
||||
|
||||
self.isScreenLockUnlocked = YES;
|
||||
self.isScreenLockLocked = NO;
|
||||
|
||||
[self ensureScreenProtection];
|
||||
[self ensureUI];
|
||||
}
|
||||
failure:^(NSError *error) {
|
||||
DDLogInfo(@"%@ unlock screen lock failed.", self.logTag);
|
||||
@ -285,54 +348,37 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
self.didLastUnlockAttemptFail = YES;
|
||||
|
||||
// Re-show the unlock UI.
|
||||
[self ensureScreenProtection];
|
||||
[self ensureUI];
|
||||
}];
|
||||
|
||||
[self ensureScreenProtection];
|
||||
[self ensureUI];
|
||||
}
|
||||
|
||||
- (BOOL)shouldHaveScreenProtection
|
||||
// Determines what the state of the app should be.
|
||||
- (ScreenLockUIState)desiredUIState
|
||||
{
|
||||
// Show 'Screen Protection' if:
|
||||
//
|
||||
// * App is inactive and...
|
||||
// * 'Screen Protection' is enabled.
|
||||
if (!self.appIsInactive) {
|
||||
return NO;
|
||||
} else if (!Environment.preferences.screenSecurityIsEnabled) {
|
||||
return NO;
|
||||
} else {
|
||||
return YES;
|
||||
if (self.isScreenLockLocked) {
|
||||
if (self.appIsInactiveOrBackground) {
|
||||
DDLogVerbose(@"%@ desiredUIState: screen protection 1.", self.logTag);
|
||||
return ScreenLockUIStateScreenProtection;
|
||||
} else {
|
||||
DDLogVerbose(@"%@ desiredUIState: screen lock 2.", self.logTag);
|
||||
return ScreenLockUIStateScreenLock;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)shouldHaveScreenLock
|
||||
{
|
||||
if (![TSAccountManager isRegistered]) {
|
||||
// Don't show 'Screen Lock' if user is not registered.
|
||||
DDLogVerbose(@"%@ shouldHaveScreenLock NO 1.", self.logTag);
|
||||
return NO;
|
||||
} else if (!OWSScreenLock.sharedManager.isScreenLockEnabled) {
|
||||
// Don't show 'Screen Lock' if 'Screen Lock' isn't enabled.
|
||||
DDLogVerbose(@"%@ shouldHaveScreenLock NO 2.", self.logTag);
|
||||
return NO;
|
||||
} else if (self.appIsInBackground) {
|
||||
// Don't show 'Screen Lock' if app is in background.
|
||||
DDLogVerbose(@"%@ shouldHaveScreenLock NO 4.", self.logTag);
|
||||
return NO;
|
||||
} else if (self.isShowingScreenLockUI) {
|
||||
// Maintain blocking window in 'screen lock' mode while we're
|
||||
// showing the 'Unlock Screen Lock' UI.
|
||||
DDLogVerbose(@"%@ shouldHaveScreenLock YES 0.", self.logTag);
|
||||
return YES;
|
||||
} else if (self.appIsInactive) {
|
||||
// Don't show 'Screen Lock' if app is inactive.
|
||||
DDLogVerbose(@"%@ shouldHaveScreenLock NO 5.", self.logTag);
|
||||
return NO;
|
||||
if (!self.appIsInactiveOrBackground) {
|
||||
// App is inactive or background.
|
||||
DDLogVerbose(@"%@ desiredUIState: none 3.", self.logTag);
|
||||
return ScreenLockUIStateNone;
|
||||
}
|
||||
|
||||
if (Environment.preferences.screenSecurityIsEnabled) {
|
||||
DDLogVerbose(@"%@ desiredUIState: screen protection 4.", self.logTag);
|
||||
return ScreenLockUIStateScreenProtection;
|
||||
} else {
|
||||
BOOL shouldHaveScreenLock = !self.isScreenLockUnlocked;
|
||||
DDLogVerbose(@"%@ shouldHaveScreenLock ? %d.", self.logTag, shouldHaveScreenLock);
|
||||
return shouldHaveScreenLock;
|
||||
DDLogVerbose(@"%@ desiredUIState: none 5.", self.logTag);
|
||||
return ScreenLockUIStateNone;
|
||||
}
|
||||
}
|
||||
|
||||
@ -346,7 +392,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
buttonTitle:nil
|
||||
buttonAction:^(UIAlertAction *action) {
|
||||
// After the alert, re-show the unlock UI.
|
||||
[self ensureScreenProtection];
|
||||
[self ensureUI];
|
||||
}];
|
||||
}
|
||||
|
||||
@ -360,15 +406,14 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
OWSAssert(rootWindow);
|
||||
|
||||
UIWindow *window = [[UIWindow alloc] initWithFrame:rootWindow.bounds];
|
||||
window.hidden = YES;
|
||||
window.hidden = NO;
|
||||
window.windowLevel = UIWindowLevel_Background;
|
||||
window.opaque = YES;
|
||||
window.windowLevel = CGFLOAT_MAX;
|
||||
window.backgroundColor = UIColor.ows_materialBlueColor;
|
||||
|
||||
UIViewController *viewController = [UIViewController new];
|
||||
viewController.view.backgroundColor = UIColor.ows_materialBlueColor;
|
||||
|
||||
|
||||
UIView *rootView = viewController.view;
|
||||
|
||||
UIView *edgesView = [UIView containerView];
|
||||
@ -413,7 +458,8 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
self.screenBlockingImageView = imageView;
|
||||
self.screenBlockingButton = button;
|
||||
|
||||
[self updateScreenBlockingWindow:YES shouldHaveScreenLock:NO animated:NO];
|
||||
// Default to screen protection until we know otherwise.
|
||||
[self updateScreenBlockingWindow:ScreenLockUIStateNone animated:NO];
|
||||
}
|
||||
|
||||
// The "screen blocking" window has three possible states:
|
||||
@ -423,13 +469,48 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
// * "Screen Lock, local auth UI presented". Move the Signal logo so that it is visible.
|
||||
// * "Screen Lock, local auth UI not presented". Move the Signal logo so that it is visible,
|
||||
// show "unlock" button.
|
||||
- (void)updateScreenBlockingWindow:(BOOL)shouldShowBlockWindow
|
||||
shouldHaveScreenLock:(BOOL)shouldHaveScreenLock
|
||||
animated:(BOOL)animated
|
||||
- (void)updateScreenBlockingWindow:(ScreenLockUIState)desiredUIState animated:(BOOL)animated
|
||||
{
|
||||
OWSAssertIsOnMainThread();
|
||||
|
||||
self.screenBlockingWindow.hidden = !shouldShowBlockWindow;
|
||||
BOOL shouldShowBlockWindow = desiredUIState != ScreenLockUIStateNone;
|
||||
if (self.rootWindow.hidden != shouldShowBlockWindow) {
|
||||
DDLogInfo(@"%@, %@.", self.logTag, shouldShowBlockWindow ? @"showing block window" : @"hiding block window");
|
||||
}
|
||||
|
||||
// When we show the block window, try to capture the first responder of
|
||||
// the root window before it is hidden.
|
||||
//
|
||||
// When we hide the root window, its first responder will resign.
|
||||
if (shouldShowBlockWindow && !self.rootWindow.hidden) {
|
||||
self.rootWindowResponder = [UIResponder currentFirstResponder];
|
||||
DDLogInfo(@"%@ trying to capture self.rootWindowResponder: %@", self.logTag, self.rootWindowResponder);
|
||||
}
|
||||
|
||||
// * Show/hide the app's root window as necessary.
|
||||
// * Never hide the blocking window (that can lead to bad frames).
|
||||
// Instead, manipulate its window level to move it in front of
|
||||
// or behind the root window.
|
||||
if (shouldShowBlockWindow) {
|
||||
// Show the blocking window in front of the status bar.
|
||||
self.screenBlockingWindow.windowLevel = UIWindowLevelStatusBar + 1;
|
||||
self.rootWindow.hidden = YES;
|
||||
} else {
|
||||
self.screenBlockingWindow.windowLevel = UIWindowLevel_Background;
|
||||
[self.rootWindow makeKeyAndVisible];
|
||||
|
||||
// When we hide the block window, try to restore the first
|
||||
// responder of the root window.
|
||||
//
|
||||
// It's important we restore first responder status once the user completes
|
||||
// In some cases, (RegistrationLock Reminder) it just puts the keyboard back where
|
||||
// the user needs it, saving them a tap.
|
||||
// But in the case of an inputAccessoryView, like the ConversationViewController,
|
||||
// failing to restore firstResponder could hide the input toolbar.
|
||||
DDLogInfo(@"%@ trying to restore self.rootWindowResponder: %@", self.logTag, self.rootWindowResponder);
|
||||
[self.rootWindowResponder becomeFirstResponder];
|
||||
self.rootWindowResponder = nil;
|
||||
}
|
||||
|
||||
UIView *rootView = self.screenBlockingViewController.view;
|
||||
|
||||
@ -437,15 +518,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
NSMutableArray<NSLayoutConstraint *> *screenBlockingConstraints = [NSMutableArray new];
|
||||
|
||||
BOOL shouldShowUnlockButton = (!self.appIsInactive && !self.appIsInBackground && self.didLastUnlockAttemptFail);
|
||||
|
||||
DDLogVerbose(@"%@ updateScreenBlockingWindow. shouldShowBlockWindow: %d, shouldHaveScreenLock: %d, "
|
||||
@"shouldShowUnlockButton: %d.",
|
||||
self.logTag,
|
||||
shouldShowBlockWindow,
|
||||
shouldHaveScreenLock,
|
||||
shouldShowUnlockButton);
|
||||
|
||||
BOOL shouldHaveScreenLock = desiredUIState == ScreenLockUIStateScreenLock;
|
||||
NSString *signature = [NSString stringWithFormat:@"%d %d", shouldHaveScreenLock, self.isShowingScreenLockUI];
|
||||
if ([NSObject isNullableObject:self.screenBlockingSignature equalTo:signature]) {
|
||||
// Skip redundant work to avoid interfering with ongoing animations.
|
||||
@ -481,48 +554,35 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
{
|
||||
OWSAssertIsOnMainThread();
|
||||
|
||||
DDLogInfo(@"showUnlockUI");
|
||||
if (self.appIsInactiveOrBackground) {
|
||||
// This button can be pressed while the app is inactive
|
||||
// for a brief window while the iOS auth UI is dismissing.
|
||||
return;
|
||||
}
|
||||
|
||||
DDLogInfo(@"%@ unlockButtonTapped", self.logTag);
|
||||
|
||||
self.didLastUnlockAttemptFail = NO;
|
||||
|
||||
[self ensureScreenProtection];
|
||||
[self ensureUI];
|
||||
}
|
||||
|
||||
#pragma mark - Events
|
||||
|
||||
- (void)screenLockDidChange:(NSNotification *)notification
|
||||
{
|
||||
[self ensureScreenProtection];
|
||||
}
|
||||
|
||||
- (void)screenLockWasEnabled:(NSNotification *)notification
|
||||
{
|
||||
// When we enable screen lock, consider that an unlock.
|
||||
self.isScreenLockUnlocked = YES;
|
||||
|
||||
DDLogVerbose(@"%@ screenLockWasEnabled", self.logTag);
|
||||
|
||||
[self ensureScreenProtection];
|
||||
}
|
||||
|
||||
- (void)registrationStateDidChange
|
||||
{
|
||||
OWSAssertIsOnMainThread();
|
||||
|
||||
DDLogInfo(@"registrationStateDidChange");
|
||||
|
||||
[self ensureScreenProtection];
|
||||
[self ensureUI];
|
||||
}
|
||||
|
||||
- (void)clearAuthUIWhenActive
|
||||
{
|
||||
// For continuity, continue to present blocking screen in "screen lock" mode while
|
||||
// dismissing the "local auth UI".
|
||||
if (self.appIsInactive) {
|
||||
if (self.appIsInactiveOrBackground) {
|
||||
self.shouldClearAuthUIWhenActive = YES;
|
||||
} else {
|
||||
self.isShowingScreenLockUI = NO;
|
||||
[self ensureScreenProtection];
|
||||
[self ensureUI];
|
||||
}
|
||||
}
|
||||
|
||||
@ -533,12 +593,12 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
self.isShowingScreenLockUI = NO;
|
||||
}
|
||||
|
||||
self.appIsInactive = NO;
|
||||
self.appIsInactiveOrBackground = NO;
|
||||
}
|
||||
|
||||
- (void)applicationWillResignActive:(NSNotification *)notification
|
||||
{
|
||||
self.appIsInactive = YES;
|
||||
self.appIsInactiveOrBackground = YES;
|
||||
}
|
||||
|
||||
- (void)applicationWillEnterForeground:(NSNotification *)notification
|
||||
@ -551,29 +611,27 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
self.appIsInBackground = YES;
|
||||
}
|
||||
|
||||
#pragma mark - Inactive Timer
|
||||
|
||||
- (void)inactiveTimerDidFire
|
||||
// Whenever the device date/time is edited by the user,
|
||||
// trigger screen lock immediately if enabled.
|
||||
- (void)clockDidChange:(NSNotification *)notification
|
||||
{
|
||||
[self startScreenLockCountdownIfNecessary];
|
||||
}
|
||||
DDLogInfo(@"%@ clock did change", self.logTag);
|
||||
|
||||
- (void)startInactiveTimerIfNecessary
|
||||
{
|
||||
if (self.appIsInactive && !self.isShowingScreenLockUI && !self.inactiveTimer) {
|
||||
[self.inactiveTimer invalidate];
|
||||
self.inactiveTimer = [NSTimer weakScheduledTimerWithTimeInterval:45.f
|
||||
target:self
|
||||
selector:@selector(inactiveTimerDidFire)
|
||||
userInfo:nil
|
||||
repeats:NO];
|
||||
if (!AppReadiness.isAppReady) {
|
||||
// It's not safe to access OWSScreenLock.isScreenLockEnabled
|
||||
// until the app is ready.
|
||||
//
|
||||
// We don't need to try to lock the screen lock;
|
||||
// It will be initialized by `setupWithRootWindow`.
|
||||
DDLogVerbose(@"%@ clockDidChange 0", self.logTag);
|
||||
return;
|
||||
}
|
||||
}
|
||||
self.isScreenLockLocked = OWSScreenLock.sharedManager.isScreenLockEnabled;
|
||||
|
||||
- (void)clearInactiveTimer
|
||||
{
|
||||
[self.inactiveTimer invalidate];
|
||||
self.inactiveTimer = nil;
|
||||
// NOTE: this notifications fires _before_ applicationDidBecomeActive,
|
||||
// which is desirable. Don't assume that though; call ensureUI
|
||||
// just in case it's necessary.
|
||||
[self ensureUI];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
Loading…
Reference in New Issue
Block a user