mirror of https://github.com/oxen-io/session-ios
Merge branch 'charlesmchen/screenLock'
commit
70e95b085e
@ -0,0 +1,295 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import LocalAuthentication
|
||||||
|
|
||||||
|
@objc public class OWSScreenLock: NSObject {
|
||||||
|
|
||||||
|
public enum OWSScreenLockOutcome {
|
||||||
|
case success
|
||||||
|
case cancel
|
||||||
|
// case userCancel
|
||||||
|
// case otherCancel
|
||||||
|
case failure(error:String)
|
||||||
|
// case permanentFailure(error:String)
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc public let screenLockTimeouts = [
|
||||||
|
5 * kSecondInterval,
|
||||||
|
15 * kSecondInterval,
|
||||||
|
30 * kSecondInterval,
|
||||||
|
1 * kMinuteInterval,
|
||||||
|
5 * kMinuteInterval,
|
||||||
|
0
|
||||||
|
]
|
||||||
|
|
||||||
|
@objc public static let ScreenLockDidChange = Notification.Name("ScreenLockDidChange")
|
||||||
|
|
||||||
|
let primaryStorage: OWSPrimaryStorage
|
||||||
|
let dbConnection: YapDatabaseConnection
|
||||||
|
|
||||||
|
private let OWSScreenLock_Collection = "OWSScreenLock_Collection"
|
||||||
|
private let OWSScreenLock_Key_IsScreenLockEnabled = "OWSScreenLock_Key_IsScreenLockEnabled"
|
||||||
|
private let OWSScreenLock_Key_ScreenLockTimeoutSeconds = "OWSScreenLock_Key_ScreenLockTimeoutSeconds"
|
||||||
|
|
||||||
|
// MARK - Singleton class
|
||||||
|
|
||||||
|
@objc(sharedManager)
|
||||||
|
public static let shared = OWSScreenLock()
|
||||||
|
|
||||||
|
private override init() {
|
||||||
|
self.primaryStorage = OWSPrimaryStorage.shared()
|
||||||
|
self.dbConnection = self.primaryStorage.newDatabaseConnection()
|
||||||
|
|
||||||
|
super.init()
|
||||||
|
|
||||||
|
SwiftSingletons.register(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Properties
|
||||||
|
|
||||||
|
@objc public func isScreenLockEnabled() -> Bool {
|
||||||
|
AssertIsOnMainThread()
|
||||||
|
|
||||||
|
if !OWSStorage.isStorageReady() {
|
||||||
|
owsFail("\(logTag) accessed screen lock state before storage is ready.")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return self.dbConnection.bool(forKey: OWSScreenLock_Key_IsScreenLockEnabled, inCollection: OWSScreenLock_Collection, defaultValue: false)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func setIsScreenLockEnabled(value: Bool) {
|
||||||
|
AssertIsOnMainThread()
|
||||||
|
assert(OWSStorage.isStorageReady())
|
||||||
|
|
||||||
|
self.dbConnection.setBool(value, forKey: OWSScreenLock_Key_IsScreenLockEnabled, inCollection: OWSScreenLock_Collection)
|
||||||
|
|
||||||
|
NotificationCenter.default.postNotificationNameAsync(OWSScreenLock.ScreenLockDidChange, object: nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc public func screenLockTimeout() -> TimeInterval {
|
||||||
|
AssertIsOnMainThread()
|
||||||
|
|
||||||
|
if !OWSStorage.isStorageReady() {
|
||||||
|
owsFail("\(logTag) accessed screen lock state before storage is ready.")
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
let defaultTimeout = screenLockTimeouts[0]
|
||||||
|
return self.dbConnection.double(forKey: OWSScreenLock_Key_ScreenLockTimeoutSeconds, inCollection: OWSScreenLock_Collection, defaultValue: defaultTimeout)
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc public func setScreenLockTimeout(_ value: TimeInterval) {
|
||||||
|
AssertIsOnMainThread()
|
||||||
|
assert(OWSStorage.isStorageReady())
|
||||||
|
|
||||||
|
self.dbConnection.setDouble(value, forKey: OWSScreenLock_Key_ScreenLockTimeoutSeconds, inCollection: OWSScreenLock_Collection)
|
||||||
|
|
||||||
|
NotificationCenter.default.postNotificationNameAsync(OWSScreenLock.ScreenLockDidChange, object: nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
AssertIsOnMainThread()
|
||||||
|
|
||||||
|
switch outcome {
|
||||||
|
case .failure(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
|
||||||
|
AssertIsOnMainThread()
|
||||||
|
|
||||||
|
switch outcome {
|
||||||
|
case .failure(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),
|
||||||
|
cancel: @escaping (() -> Void)) {
|
||||||
|
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'."),
|
||||||
|
completion: { (outcome: OWSScreenLockOutcome) in
|
||||||
|
AssertIsOnMainThread()
|
||||||
|
|
||||||
|
switch outcome {
|
||||||
|
case .failure(let error):
|
||||||
|
failure(self.authenticationError(errorDescription: error))
|
||||||
|
case .success:
|
||||||
|
success()
|
||||||
|
case .cancel:
|
||||||
|
cancel()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
private func tryToVerifyLocalAuthentication(localizedReason: String,
|
||||||
|
completion completionParam: @escaping ((OWSScreenLockOutcome) -> Void)) {
|
||||||
|
|
||||||
|
// Ensure completion is always called on the main thread.
|
||||||
|
let completion = { (outcome: OWSScreenLockOutcome) in
|
||||||
|
switch outcome {
|
||||||
|
case .failure(let error):
|
||||||
|
Logger.error("\(self.logTag) local authentication failed with error: \(error)")
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
completionParam(outcome)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let context = screenLockContext()
|
||||||
|
let defaultErrorDescription = NSLocalizedString("SCREEN_LOCK_ENABLE_UNKNOWN_ERROR",
|
||||||
|
comment: "Indicates that an unknown error occurred while using Touch ID/Face ID/Phone Passcode.")
|
||||||
|
|
||||||
|
var authError: NSError?
|
||||||
|
let canEvaluatePolicy = context.canEvaluatePolicy(.deviceOwnerAuthentication, error: &authError)
|
||||||
|
if !canEvaluatePolicy || authError != nil {
|
||||||
|
Logger.error("\(logTag) could not determine if local authentication is supported: \(String(describing: authError))")
|
||||||
|
|
||||||
|
let outcome = self.outcomeForLAError(errorParam: authError,
|
||||||
|
defaultErrorDescription: defaultErrorDescription)
|
||||||
|
switch outcome {
|
||||||
|
case .success:
|
||||||
|
owsFail("\(self.logTag) local authentication unexpected success")
|
||||||
|
completion(.failure(error:defaultErrorDescription))
|
||||||
|
case .cancel, .failure:
|
||||||
|
completion(outcome)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
context.evaluatePolicy(.deviceOwnerAuthentication, localizedReason: localizedReason) { success, evaluateError in
|
||||||
|
if success {
|
||||||
|
Logger.info("\(self.logTag) local authentication succeeded.")
|
||||||
|
completion(.success)
|
||||||
|
} else {
|
||||||
|
let outcome = self.outcomeForLAError(errorParam: evaluateError,
|
||||||
|
defaultErrorDescription: defaultErrorDescription)
|
||||||
|
switch outcome {
|
||||||
|
case .success:
|
||||||
|
owsFail("\(self.logTag) local authentication unexpected success")
|
||||||
|
completion(.failure(error:defaultErrorDescription))
|
||||||
|
case .cancel, .failure:
|
||||||
|
completion(outcome)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Outcome
|
||||||
|
|
||||||
|
private func outcomeForLAError(errorParam: Error?, defaultErrorDescription: String) -> OWSScreenLockOutcome {
|
||||||
|
if let error = errorParam {
|
||||||
|
guard let laError = error as? LAError else {
|
||||||
|
return .failure(error:defaultErrorDescription)
|
||||||
|
}
|
||||||
|
|
||||||
|
if #available(iOS 11.0, *) {
|
||||||
|
switch laError.code {
|
||||||
|
case .biometryNotAvailable:
|
||||||
|
Logger.error("\(self.logTag) local authentication error: biometryNotAvailable.")
|
||||||
|
return .failure(error: NSLocalizedString("SCREEN_LOCK_ERROR_LOCAL_AUTHENTICATION_NOT_AVAILABLE",
|
||||||
|
comment: "Indicates that Touch ID/Face ID/Phone Passcode are not available on this device."))
|
||||||
|
case .biometryNotEnrolled:
|
||||||
|
Logger.error("\(self.logTag) local authentication error: biometryNotEnrolled.")
|
||||||
|
return .failure(error: NSLocalizedString("SCREEN_LOCK_ERROR_LOCAL_AUTHENTICATION_NOT_ENROLLED",
|
||||||
|
comment: "Indicates that Touch ID/Face ID/Phone Passcode is not configured on this device."))
|
||||||
|
case .biometryLockout:
|
||||||
|
Logger.error("\(self.logTag) local authentication error: biometryLockout.")
|
||||||
|
return .failure(error: NSLocalizedString("SCREEN_LOCK_ERROR_LOCAL_AUTHENTICATION_LOCKOUT",
|
||||||
|
comment: "Indicates that Touch ID/Face ID/Phone Passcode is 'locked out' on this device due to authentication failures."))
|
||||||
|
default:
|
||||||
|
// Fall through to second switch
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch laError.code {
|
||||||
|
case .authenticationFailed:
|
||||||
|
Logger.error("\(self.logTag) local authentication error: authenticationFailed.")
|
||||||
|
return .failure(error: NSLocalizedString("SCREEN_LOCK_ERROR_LOCAL_AUTHENTICATION_FAILED",
|
||||||
|
comment: "Indicates that Touch ID/Face ID/Phone Passcode authentication failed."))
|
||||||
|
case .userCancel, .userFallback, .systemCancel, .appCancel:
|
||||||
|
Logger.info("\(self.logTag) local authentication cancelled.")
|
||||||
|
return .cancel
|
||||||
|
case .passcodeNotSet:
|
||||||
|
Logger.error("\(self.logTag) local authentication error: passcodeNotSet.")
|
||||||
|
return .failure(error: NSLocalizedString("SCREEN_LOCK_ERROR_LOCAL_AUTHENTICATION_PASSCODE_NOT_SET",
|
||||||
|
comment: "Indicates that Touch ID/Face ID/Phone Passcode passcode is not set."))
|
||||||
|
case .touchIDNotAvailable:
|
||||||
|
Logger.error("\(self.logTag) local authentication error: touchIDNotAvailable.")
|
||||||
|
return .failure(error: NSLocalizedString("SCREEN_LOCK_ERROR_LOCAL_AUTHENTICATION_NOT_AVAILABLE",
|
||||||
|
comment: "Indicates that Touch ID/Face ID/Phone Passcode are not available on this device."))
|
||||||
|
case .touchIDNotEnrolled:
|
||||||
|
Logger.error("\(self.logTag) local authentication error: touchIDNotEnrolled.")
|
||||||
|
return .failure(error: NSLocalizedString("SCREEN_LOCK_ERROR_LOCAL_AUTHENTICATION_NOT_ENROLLED",
|
||||||
|
comment: "Indicates that Touch ID/Face ID/Phone Passcode is not configured on this device."))
|
||||||
|
case .touchIDLockout:
|
||||||
|
Logger.error("\(self.logTag) local authentication error: touchIDLockout.")
|
||||||
|
return .failure(error: NSLocalizedString("SCREEN_LOCK_ERROR_LOCAL_AUTHENTICATION_LOCKOUT",
|
||||||
|
comment: "Indicates that Touch ID/Face ID/Phone Passcode is 'locked out' on this device due to authentication failures."))
|
||||||
|
case .invalidContext:
|
||||||
|
owsFail("\(self.logTag) context not valid.")
|
||||||
|
break
|
||||||
|
case .notInteractive:
|
||||||
|
owsFail("\(self.logTag) context not interactive.")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return .failure(error:defaultErrorDescription)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func authenticationError(errorDescription: String) -> Error {
|
||||||
|
return OWSErrorWithCodeDescription(.localAuthenticationError,
|
||||||
|
errorDescription)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Context
|
||||||
|
|
||||||
|
private func screenLockContext() -> LAContext {
|
||||||
|
let context = LAContext()
|
||||||
|
|
||||||
|
context.touchIDAuthenticationAllowableReuseDuration = TimeInterval(screenLockTimeout())
|
||||||
|
|
||||||
|
return context
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,17 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
|
@interface OWSScreenLockUI : NSObject
|
||||||
|
|
||||||
|
- (instancetype)init NS_UNAVAILABLE;
|
||||||
|
|
||||||
|
+ (instancetype)sharedManager;
|
||||||
|
|
||||||
|
- (void)setupWithRootWindow:(UIWindow *)rootWindow;
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_END
|
@ -0,0 +1,252 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "OWSScreenLockUI.h"
|
||||||
|
#import "Signal-Swift.h"
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
|
@interface OWSScreenLockUI ()
|
||||||
|
|
||||||
|
// Unlike UIApplication.applicationState, this state is
|
||||||
|
// updated conservatively, e.g. the flag is cleared during
|
||||||
|
// "will enter background."
|
||||||
|
@property (nonatomic) BOOL appIsInactive;
|
||||||
|
@property (nonatomic, nullable) NSDate *appBecameInactiveDate;
|
||||||
|
@property (nonatomic) UIWindow *screenBlockingWindow;
|
||||||
|
@property (nonatomic) BOOL hasUnlockedScreenLock;
|
||||||
|
@property (nonatomic) BOOL isShowingScreenLockUI;
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
#pragma mark -
|
||||||
|
|
||||||
|
@implementation OWSScreenLockUI
|
||||||
|
|
||||||
|
+ (instancetype)sharedManager
|
||||||
|
{
|
||||||
|
static OWSScreenLockUI *instance = nil;
|
||||||
|
static dispatch_once_t onceToken;
|
||||||
|
dispatch_once(&onceToken, ^{
|
||||||
|
instance = [[self alloc] initDefault];
|
||||||
|
});
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (instancetype)initDefault
|
||||||
|
{
|
||||||
|
self = [super init];
|
||||||
|
|
||||||
|
if (!self) {
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
[self observeNotifications];
|
||||||
|
|
||||||
|
OWSSingletonAssert();
|
||||||
|
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)dealloc
|
||||||
|
{
|
||||||
|
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)observeNotifications
|
||||||
|
{
|
||||||
|
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||||
|
selector:@selector(applicationDidBecomeActive:)
|
||||||
|
name:OWSApplicationDidBecomeActiveNotification
|
||||||
|
object:nil];
|
||||||
|
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||||
|
selector:@selector(applicationWillResignActive:)
|
||||||
|
name:OWSApplicationWillResignActiveNotification
|
||||||
|
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];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)setupWithRootWindow:(UIWindow *)rootWindow
|
||||||
|
{
|
||||||
|
OWSAssertIsOnMainThread();
|
||||||
|
OWSAssert(rootWindow);
|
||||||
|
|
||||||
|
[self prepareScreenProtectionWithRootWindow:rootWindow];
|
||||||
|
|
||||||
|
[AppReadiness runNowOrWhenAppIsReady:^{
|
||||||
|
[self ensureScreenProtection];
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma mark - Methods
|
||||||
|
|
||||||
|
- (void)setAppIsInactive:(BOOL)appIsInactive
|
||||||
|
{
|
||||||
|
if (appIsInactive) {
|
||||||
|
if (!_appIsInactive) {
|
||||||
|
// Whenever app becomes inactive, clear this state.
|
||||||
|
self.hasUnlockedScreenLock = NO;
|
||||||
|
|
||||||
|
// Note the time when app became inactive.
|
||||||
|
self.appBecameInactiveDate = [NSDate new];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_appIsInactive = appIsInactive;
|
||||||
|
|
||||||
|
[self ensureScreenProtection];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)ensureScreenProtection
|
||||||
|
{
|
||||||
|
OWSAssertIsOnMainThread();
|
||||||
|
|
||||||
|
if (!AppReadiness.isAppReady) {
|
||||||
|
[AppReadiness runNowOrWhenAppIsReady:^{
|
||||||
|
[self ensureScreenProtection];
|
||||||
|
}];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOL shouldHaveScreenLock = NO;
|
||||||
|
if (self.appIsInactive) {
|
||||||
|
// Don't show 'Screen Lock' if app is inactive.
|
||||||
|
} else if (![TSAccountManager isRegistered]) {
|
||||||
|
// Don't show 'Screen Lock' if user is not registered.
|
||||||
|
} else if (!OWSScreenLock.sharedManager.isScreenLockEnabled) {
|
||||||
|
// Don't show 'Screen Lock' if 'Screen Lock' isn't enabled.
|
||||||
|
} else if (self.hasUnlockedScreenLock) {
|
||||||
|
// Don't show 'Screen Lock' if 'Screen Lock' has been unlocked.
|
||||||
|
} else if (!self.appBecameInactiveDate) {
|
||||||
|
// Show 'Screen Lock' if app hasn't become inactive yet (just launched).
|
||||||
|
shouldHaveScreenLock = YES;
|
||||||
|
} else {
|
||||||
|
OWSAssert(self.appBecameInactiveDate);
|
||||||
|
|
||||||
|
NSTimeInterval screenLockInterval = fabs([self.appBecameInactiveDate timeIntervalSinceNow]);
|
||||||
|
NSTimeInterval screenLockTimeout = OWSScreenLock.sharedManager.screenLockTimeout;
|
||||||
|
OWSAssert(screenLockInterval >= 0);
|
||||||
|
OWSAssert(screenLockTimeout >= 0);
|
||||||
|
if (self.appBecameInactiveDate && screenLockInterval < screenLockTimeout) {
|
||||||
|
// Don't show 'Screen Lock' if 'Screen Lock' timeout hasn't elapsed.
|
||||||
|
shouldHaveScreenLock = NO;
|
||||||
|
} else {
|
||||||
|
// Otherwise, show 'Screen Lock'.
|
||||||
|
shouldHaveScreenLock = YES;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show 'Screen Protection' if:
|
||||||
|
//
|
||||||
|
// * App is inactive and...
|
||||||
|
// * Either 'Screen Protection' or 'Screen Lock' is enabled.
|
||||||
|
BOOL shouldHaveScreenProtection = (self.appIsInactive
|
||||||
|
&& (Environment.preferences.screenSecurityIsEnabled || OWSScreenLock.sharedManager.isScreenLockEnabled));
|
||||||
|
|
||||||
|
BOOL shouldShowBlockWindow = shouldHaveScreenProtection || shouldHaveScreenLock;
|
||||||
|
DDLogVerbose(@"%@, shouldHaveScreenProtection: %d, shouldHaveScreenLock: %d, shouldShowBlockWindow: %d",
|
||||||
|
self.logTag,
|
||||||
|
shouldHaveScreenProtection,
|
||||||
|
shouldHaveScreenLock,
|
||||||
|
shouldShowBlockWindow);
|
||||||
|
self.screenBlockingWindow.hidden = !shouldShowBlockWindow;
|
||||||
|
|
||||||
|
if (shouldHaveScreenLock) {
|
||||||
|
if (!self.isShowingScreenLockUI) {
|
||||||
|
self.isShowingScreenLockUI = YES;
|
||||||
|
|
||||||
|
[OWSScreenLock.sharedManager tryToUnlockScreenLockWithSuccess:^{
|
||||||
|
DDLogInfo(@"%@ unlock screen lock succeeded.", self.logTag);
|
||||||
|
self.isShowingScreenLockUI = NO;
|
||||||
|
self.hasUnlockedScreenLock = YES;
|
||||||
|
[self ensureScreenProtection];
|
||||||
|
}
|
||||||
|
failure:^(NSError *error) {
|
||||||
|
DDLogInfo(@"%@ unlock screen lock failed.", self.logTag);
|
||||||
|
self.isShowingScreenLockUI = NO;
|
||||||
|
|
||||||
|
[self showScreenLockFailureAlertWithMessage:error.localizedDescription];
|
||||||
|
}
|
||||||
|
cancel:^{
|
||||||
|
DDLogInfo(@"%@ unlock screen lock cancelled.", self.logTag);
|
||||||
|
self.isShowingScreenLockUI = NO;
|
||||||
|
|
||||||
|
// Re-show the unlock UI.
|
||||||
|
[self ensureScreenProtection];
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)showScreenLockFailureAlertWithMessage:(NSString *)message
|
||||||
|
{
|
||||||
|
OWSAssertIsOnMainThread();
|
||||||
|
|
||||||
|
[OWSAlerts showAlertWithTitle:NSLocalizedString(@"SCREEN_LOCK_UNLOCK_FAILED",
|
||||||
|
@"Title for alert indicating that screen lock could not be unlocked.")
|
||||||
|
message:message
|
||||||
|
buttonTitle:nil
|
||||||
|
buttonAction:^(UIAlertAction *action) {
|
||||||
|
// After the alert, re-show the unlock UI.
|
||||||
|
[self ensureScreenProtection];
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 'Screen Blocking' window obscures the app screen:
|
||||||
|
//
|
||||||
|
// * In the app switcher.
|
||||||
|
// * During 'Screen Lock' unlock process.
|
||||||
|
- (void)prepareScreenProtectionWithRootWindow:(UIWindow *)rootWindow
|
||||||
|
{
|
||||||
|
OWSAssertIsOnMainThread();
|
||||||
|
OWSAssert(rootWindow);
|
||||||
|
|
||||||
|
UIWindow *window = [[UIWindow alloc] initWithFrame:rootWindow.bounds];
|
||||||
|
window.hidden = YES;
|
||||||
|
window.opaque = YES;
|
||||||
|
window.userInteractionEnabled = NO;
|
||||||
|
window.windowLevel = CGFLOAT_MAX;
|
||||||
|
window.backgroundColor = UIColor.ows_materialBlueColor;
|
||||||
|
window.rootViewController =
|
||||||
|
[[UIStoryboard storyboardWithName:@"Launch Screen" bundle:nil] instantiateInitialViewController];
|
||||||
|
|
||||||
|
self.screenBlockingWindow = window;
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma mark - Events
|
||||||
|
|
||||||
|
- (void)screenLockDidChange:(NSNotification *)notification
|
||||||
|
{
|
||||||
|
[self ensureScreenProtection];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)registrationStateDidChange
|
||||||
|
{
|
||||||
|
OWSAssertIsOnMainThread();
|
||||||
|
|
||||||
|
DDLogInfo(@"registrationStateDidChange");
|
||||||
|
|
||||||
|
[self ensureScreenProtection];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)applicationDidBecomeActive:(NSNotification *)notification
|
||||||
|
{
|
||||||
|
self.appIsInactive = NO;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)applicationWillResignActive:(NSNotification *)notification
|
||||||
|
{
|
||||||
|
self.appIsInactive = YES;
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_END
|
Loading…
Reference in New Issue