mirror of
https://github.com/oxen-io/session-ios.git
synced 2023-12-13 21:30:14 +01:00
Add screen lock feature.
This commit is contained in:
parent
245769ce21
commit
1612642c26
|
@ -204,6 +204,7 @@
|
||||||
34D1F0BA1F8800D90066283D /* OWSAudioMessageView.m in Sources */ = {isa = PBXBuildFile; fileRef = 34D1F0B91F8800D90066283D /* OWSAudioMessageView.m */; };
|
34D1F0BA1F8800D90066283D /* OWSAudioMessageView.m in Sources */ = {isa = PBXBuildFile; fileRef = 34D1F0B91F8800D90066283D /* OWSAudioMessageView.m */; };
|
||||||
34D1F0BD1F8D108C0066283D /* AttachmentUploadView.m in Sources */ = {isa = PBXBuildFile; fileRef = 34D1F0BC1F8D108C0066283D /* AttachmentUploadView.m */; };
|
34D1F0BD1F8D108C0066283D /* AttachmentUploadView.m in Sources */ = {isa = PBXBuildFile; fileRef = 34D1F0BC1F8D108C0066283D /* AttachmentUploadView.m */; };
|
||||||
34D1F0C01F8EC1760066283D /* MessageRecipientStatusUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34D1F0BF1F8EC1760066283D /* MessageRecipientStatusUtils.swift */; };
|
34D1F0C01F8EC1760066283D /* MessageRecipientStatusUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34D1F0BF1F8EC1760066283D /* MessageRecipientStatusUtils.swift */; };
|
||||||
|
34D2CCD4206294B900CB1A14 /* OWSScreenLock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34D2CCD3206294B900CB1A14 /* OWSScreenLock.swift */; };
|
||||||
34D5CCA91EAE3D30005515DB /* AvatarViewHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 34D5CCA81EAE3D30005515DB /* AvatarViewHelper.m */; };
|
34D5CCA91EAE3D30005515DB /* AvatarViewHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 34D5CCA81EAE3D30005515DB /* AvatarViewHelper.m */; };
|
||||||
34D8C0271ED3673300188D7C /* DebugUIMessages.m in Sources */ = {isa = PBXBuildFile; fileRef = 34D8C0241ED3673300188D7C /* DebugUIMessages.m */; };
|
34D8C0271ED3673300188D7C /* DebugUIMessages.m in Sources */ = {isa = PBXBuildFile; fileRef = 34D8C0241ED3673300188D7C /* DebugUIMessages.m */; };
|
||||||
34D8C0281ED3673300188D7C /* DebugUITableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 34D8C0261ED3673300188D7C /* DebugUITableViewController.m */; };
|
34D8C0281ED3673300188D7C /* DebugUITableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 34D8C0261ED3673300188D7C /* DebugUITableViewController.m */; };
|
||||||
|
@ -809,6 +810,7 @@
|
||||||
34D1F0BB1F8D108C0066283D /* AttachmentUploadView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AttachmentUploadView.h; sourceTree = "<group>"; };
|
34D1F0BB1F8D108C0066283D /* AttachmentUploadView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AttachmentUploadView.h; sourceTree = "<group>"; };
|
||||||
34D1F0BC1F8D108C0066283D /* AttachmentUploadView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AttachmentUploadView.m; sourceTree = "<group>"; };
|
34D1F0BC1F8D108C0066283D /* AttachmentUploadView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AttachmentUploadView.m; sourceTree = "<group>"; };
|
||||||
34D1F0BF1F8EC1760066283D /* MessageRecipientStatusUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageRecipientStatusUtils.swift; sourceTree = "<group>"; };
|
34D1F0BF1F8EC1760066283D /* MessageRecipientStatusUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageRecipientStatusUtils.swift; sourceTree = "<group>"; };
|
||||||
|
34D2CCD3206294B900CB1A14 /* OWSScreenLock.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OWSScreenLock.swift; sourceTree = "<group>"; };
|
||||||
34D5CCA71EAE3D30005515DB /* AvatarViewHelper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AvatarViewHelper.h; sourceTree = "<group>"; };
|
34D5CCA71EAE3D30005515DB /* AvatarViewHelper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AvatarViewHelper.h; sourceTree = "<group>"; };
|
||||||
34D5CCA81EAE3D30005515DB /* AvatarViewHelper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AvatarViewHelper.m; sourceTree = "<group>"; };
|
34D5CCA81EAE3D30005515DB /* AvatarViewHelper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AvatarViewHelper.m; sourceTree = "<group>"; };
|
||||||
34D8C0231ED3673300188D7C /* DebugUIMessages.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DebugUIMessages.h; sourceTree = "<group>"; };
|
34D8C0231ED3673300188D7C /* DebugUIMessages.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DebugUIMessages.h; sourceTree = "<group>"; };
|
||||||
|
@ -1961,14 +1963,15 @@
|
||||||
34A9105E1FFEB113000C4745 /* OWSBackup.h */,
|
34A9105E1FFEB113000C4745 /* OWSBackup.h */,
|
||||||
34A9105F1FFEB114000C4745 /* OWSBackup.m */,
|
34A9105F1FFEB114000C4745 /* OWSBackup.m */,
|
||||||
340FC8C6204DE64D007AEB0F /* OWSBackupAPI.swift */,
|
340FC8C6204DE64D007AEB0F /* OWSBackupAPI.swift */,
|
||||||
340FC8CF205BF2FA007AEB0F /* OWSBackupIO.h */,
|
|
||||||
340FC8CE205BF2FA007AEB0F /* OWSBackupIO.m */,
|
|
||||||
340FC8BE204DB7D1007AEB0F /* OWSBackupExportJob.h */,
|
340FC8BE204DB7D1007AEB0F /* OWSBackupExportJob.h */,
|
||||||
340FC8BF204DB7D2007AEB0F /* OWSBackupExportJob.m */,
|
340FC8BF204DB7D2007AEB0F /* OWSBackupExportJob.m */,
|
||||||
340FC8C920517B84007AEB0F /* OWSBackupImportJob.h */,
|
340FC8C920517B84007AEB0F /* OWSBackupImportJob.h */,
|
||||||
340FC8C820517B84007AEB0F /* OWSBackupImportJob.m */,
|
340FC8C820517B84007AEB0F /* OWSBackupImportJob.m */,
|
||||||
|
340FC8CF205BF2FA007AEB0F /* OWSBackupIO.h */,
|
||||||
|
340FC8CE205BF2FA007AEB0F /* OWSBackupIO.m */,
|
||||||
340FC8CB20518C76007AEB0F /* OWSBackupJob.h */,
|
340FC8CB20518C76007AEB0F /* OWSBackupJob.h */,
|
||||||
340FC8CC20518C76007AEB0F /* OWSBackupJob.m */,
|
340FC8CC20518C76007AEB0F /* OWSBackupJob.m */,
|
||||||
|
34D2CCD3206294B900CB1A14 /* OWSScreenLock.swift */,
|
||||||
4579431C1E7C8CE9008ED0C0 /* Pastelog.h */,
|
4579431C1E7C8CE9008ED0C0 /* Pastelog.h */,
|
||||||
4579431D1E7C8CE9008ED0C0 /* Pastelog.m */,
|
4579431D1E7C8CE9008ED0C0 /* Pastelog.m */,
|
||||||
450DF2041E0D74AC003D14BE /* Platform.swift */,
|
450DF2041E0D74AC003D14BE /* Platform.swift */,
|
||||||
|
@ -3236,6 +3239,7 @@
|
||||||
4521C3C01F59F3BA00B4C582 /* TextFieldHelper.swift in Sources */,
|
4521C3C01F59F3BA00B4C582 /* TextFieldHelper.swift in Sources */,
|
||||||
34B3F87E1E8DF1700035BE1A /* InboxTableViewCell.m in Sources */,
|
34B3F87E1E8DF1700035BE1A /* InboxTableViewCell.m in Sources */,
|
||||||
340FC8AC204DAC8D007AEB0F /* PrivacySettingsTableViewController.m in Sources */,
|
340FC8AC204DAC8D007AEB0F /* PrivacySettingsTableViewController.m in Sources */,
|
||||||
|
34D2CCD4206294B900CB1A14 /* OWSScreenLock.swift in Sources */,
|
||||||
340FC8C5204DE223007AEB0F /* DebugUIBackup.m in Sources */,
|
340FC8C5204DE223007AEB0F /* DebugUIBackup.m in Sources */,
|
||||||
340FC8AE204DAC8D007AEB0F /* OWSSoundSettingsViewController.m in Sources */,
|
340FC8AE204DAC8D007AEB0F /* OWSSoundSettingsViewController.m in Sources */,
|
||||||
4579431E1E7C8CE9008ED0C0 /* Pastelog.m in Sources */,
|
4579431E1E7C8CE9008ED0C0 /* Pastelog.m in Sources */,
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
//
|
//
|
||||||
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
|
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
import PromiseKit
|
import PromiseKit
|
||||||
|
@ -63,9 +63,9 @@ class SyncPushTokensJob: NSObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
Logger.warn("\(self.TAG) uploading tokens to account servers. pushToken: \(pushToken), voipToken: \(voipToken)")
|
Logger.warn("\(self.TAG) uploading tokens to account servers. pushToken: \(pushToken), voipToken: \(voipToken)")
|
||||||
return self.accountManager.updatePushTokens(pushToken:pushToken, voipToken:voipToken).then {
|
return self.accountManager.updatePushTokens(pushToken: pushToken, voipToken: voipToken).then {
|
||||||
Logger.info("\(self.TAG) successfully updated push tokens on server")
|
Logger.info("\(self.TAG) successfully updated push tokens on server")
|
||||||
return self.recordPushTokensLocally(pushToken:pushToken, voipToken:voipToken)
|
return self.recordPushTokensLocally(pushToken: pushToken, voipToken: voipToken)
|
||||||
}
|
}
|
||||||
}.then {
|
}.then {
|
||||||
Logger.info("\(self.TAG) completed successfully.")
|
Logger.info("\(self.TAG) completed successfully.")
|
||||||
|
@ -108,9 +108,7 @@ class SyncPushTokensJob: NSObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (didTokensChange) {
|
if (didTokensChange) {
|
||||||
DispatchQueue.main.async {
|
NotificationCenter.default.postNotificationNameAsync(SyncPushTokensJob.PushTokensDidChange, object: nil)
|
||||||
NotificationCenter.default.post(name: SyncPushTokensJob.PushTokensDidChange, object: nil)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Promise(value: ())
|
return Promise(value: ())
|
||||||
|
|
|
@ -91,6 +91,10 @@
|
||||||
self.title = NSLocalizedString(@"SETTINGS_NAV_BAR_TITLE", @"Title for settings activity");
|
self.title = NSLocalizedString(@"SETTINGS_NAV_BAR_TITLE", @"Title for settings activity");
|
||||||
|
|
||||||
[self updateTableContents];
|
[self updateTableContents];
|
||||||
|
|
||||||
|
dispatch_async(dispatch_get_main_queue(), ^{
|
||||||
|
[self showPrivacy];
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)viewWillAppear:(BOOL)animated
|
- (void)viewWillAppear:(BOOL)animated
|
||||||
|
|
|
@ -22,6 +22,8 @@ NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
self.title = NSLocalizedString(@"SETTINGS_PRIVACY_TITLE", @"");
|
self.title = NSLocalizedString(@"SETTINGS_PRIVACY_TITLE", @"");
|
||||||
|
|
||||||
|
[self observeNotifications];
|
||||||
|
|
||||||
[self updateTableContents];
|
[self updateTableContents];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,6 +32,19 @@ NS_ASSUME_NONNULL_BEGIN
|
||||||
[self updateTableContents];
|
[self updateTableContents];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (void)observeNotifications
|
||||||
|
{
|
||||||
|
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||||
|
selector:@selector(screenLockDidChange:)
|
||||||
|
name:OWSScreenLock.ScreenLockDidChange
|
||||||
|
object:nil];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)dealloc
|
||||||
|
{
|
||||||
|
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
||||||
|
}
|
||||||
|
|
||||||
#pragma mark - Table Contents
|
#pragma mark - Table Contents
|
||||||
|
|
||||||
- (void)updateTableContents
|
- (void)updateTableContents
|
||||||
|
@ -152,6 +167,27 @@ NS_ASSUME_NONNULL_BEGIN
|
||||||
}]];
|
}]];
|
||||||
[contents addSection:twoFactorAuthSection];
|
[contents addSection:twoFactorAuthSection];
|
||||||
|
|
||||||
|
// BOOL showScreenLockUI = OWSScreenLock.sharedManager.isScreenLockSupported;
|
||||||
|
//#ifdef DEBUG
|
||||||
|
// showScreenLockUI = YES;
|
||||||
|
//#endif
|
||||||
|
BOOL showScreenLockUI = YES;
|
||||||
|
if (showScreenLockUI) {
|
||||||
|
OWSTableSection *screenLockSection = [OWSTableSection new];
|
||||||
|
screenLockSection.headerTitle = NSLocalizedString(
|
||||||
|
@"SETTINGS_SCREEN_LOCK_SECTION_TITLE", @"Title for the 'screen lock' section of the privacy settings.");
|
||||||
|
screenLockSection.footerTitle = NSLocalizedString(
|
||||||
|
@"SETTINGS_SCREEN_LOCK_SECTION_FOOTER", @"Footer for the 'screen lock' section of the privacy settings.");
|
||||||
|
[screenLockSection
|
||||||
|
addItem:[OWSTableItem
|
||||||
|
switchItemWithText:NSLocalizedString(@"SETTINGS_SCREEN_LOCK_SWITCH_LABEL",
|
||||||
|
@"Label for the 'enable screen lock' switch of the privacy settings.")
|
||||||
|
isOn:OWSScreenLock.sharedManager.isScreenLockEnabled
|
||||||
|
target:self
|
||||||
|
selector:@selector(isScreenLockEnabledDidChange:)]];
|
||||||
|
[contents addSection:screenLockSection];
|
||||||
|
}
|
||||||
|
|
||||||
self.contents = contents;
|
self.contents = contents;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -257,6 +293,48 @@ NS_ASSUME_NONNULL_BEGIN
|
||||||
[self.navigationController pushViewController:vc animated:YES];
|
[self.navigationController pushViewController:vc animated:YES];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (void)isScreenLockEnabledDidChange:(UISwitch *)sender
|
||||||
|
{
|
||||||
|
BOOL shouldBeEnabled = sender.isOn;
|
||||||
|
|
||||||
|
if (shouldBeEnabled == OWSScreenLock.sharedManager.isScreenLockEnabled) {
|
||||||
|
DDLogError(@"%@ ignoring redundant screen lock.", self.logTag);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
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];
|
||||||
|
}
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)screenLockDidChange:(NSNotification *)notification
|
||||||
|
{
|
||||||
|
DDLogInfo(@"%@ %s", self.logTag, __PRETTY_FUNCTION__);
|
||||||
|
|
||||||
|
[self updateTableContents];
|
||||||
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_END
|
NS_ASSUME_NONNULL_END
|
||||||
|
|
|
@ -284,6 +284,10 @@ typedef NS_ENUM(NSInteger, CellState) { kArchiveState, kInboxState };
|
||||||
}
|
}
|
||||||
|
|
||||||
[self updateBarButtonItems];
|
[self updateBarButtonItems];
|
||||||
|
|
||||||
|
dispatch_async(dispatch_get_main_queue(), ^{
|
||||||
|
[self settingsButtonPressed:nil];
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)viewDidAppear:(BOOL)animated
|
- (void)viewDidAppear:(BOOL)animated
|
||||||
|
|
377
Signal/src/util/OWSScreenLock.swift
Normal file
377
Signal/src/util/OWSScreenLock.swift
Normal file
|
@ -0,0 +1,377 @@
|
||||||
|
//
|
||||||
|
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import LocalAuthentication
|
||||||
|
|
||||||
|
@objc public class OWSScreenLock: NSObject {
|
||||||
|
|
||||||
|
let TAG = "[OWSScreenLock]"
|
||||||
|
|
||||||
|
public enum OWSScreenLockOutcome {
|
||||||
|
case success
|
||||||
|
case cancel
|
||||||
|
case failure(error:String)
|
||||||
|
}
|
||||||
|
|
||||||
|
@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"
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc public func isScreenLockEnabled() -> Bool {
|
||||||
|
AssertIsOnMainThread()
|
||||||
|
|
||||||
|
return self.dbConnection.bool(forKey: OWSScreenLock_Key_IsScreenLockEnabled, inCollection: OWSScreenLock_Collection, defaultValue: false)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func setIsScreenLockEnabled(value: Bool) {
|
||||||
|
AssertIsOnMainThread()
|
||||||
|
|
||||||
|
self.dbConnection.setBool(value, forKey: OWSScreenLock_Key_IsScreenLockEnabled, inCollection: OWSScreenLock_Collection)
|
||||||
|
|
||||||
|
NotificationCenter.default.postNotificationNameAsync(OWSScreenLock.ScreenLockDidChange, object: nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// @objc public func isScreenLockSupported() -> Bool {
|
||||||
|
// AssertIsOnMainThread()
|
||||||
|
//
|
||||||
|
// let context = screenLockContext()
|
||||||
|
//
|
||||||
|
// var authError: NSError?
|
||||||
|
// let result = context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &authError)
|
||||||
|
// guard authError == nil else {
|
||||||
|
// Logger.error("\(TAG) could not determine if screen lock is supported: \(String(describing: authError))")
|
||||||
|
// return false
|
||||||
|
// }
|
||||||
|
// return result
|
||||||
|
// }
|
||||||
|
|
||||||
|
// @objc public func supportedBiometryType() -> LABiometryType {
|
||||||
|
// AssertIsOnMainThread()
|
||||||
|
//
|
||||||
|
// let context = screenLockContext()
|
||||||
|
//
|
||||||
|
// var authError: NSError?
|
||||||
|
// let result = context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &authError)
|
||||||
|
// guard authError == nil else {
|
||||||
|
// Logger.error("\(TAG) could not determine if screen lock is supported: \(String(describing: authError))")
|
||||||
|
// return .none
|
||||||
|
// }
|
||||||
|
// guard let biometryType = context.biometryType else {
|
||||||
|
// return .none
|
||||||
|
// }
|
||||||
|
// return biometryType
|
||||||
|
// }
|
||||||
|
|
||||||
|
// 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(defaultReason: NSLocalizedString("SCREEN_LOCK_REASON_ENABLE_SCREEN_LOCK",
|
||||||
|
comment: "Description of how and why Signal iOS uses Touch ID/Face ID to enable 'screen lock'."),
|
||||||
|
touchIdReason: NSLocalizedString("SCREEN_LOCK_REASON_ENABLE_SCREEN_LOCK_TOUCH_ID",
|
||||||
|
comment: "Description of how and why Signal iOS uses Touch ID to enable 'screen lock'."),
|
||||||
|
faceIdReason: NSLocalizedString("SCREEN_LOCK_REASON_ENABLE_SCREEN_LOCK_FACE_ID",
|
||||||
|
comment: "Description of how and why Signal iOS uses Face ID 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(defaultReason: NSLocalizedString("SCREEN_LOCK_REASON_DISABLE_SCREEN_LOCK",
|
||||||
|
comment: "Description of how and why Signal iOS uses Touch ID/Face ID to disable 'screen lock'."),
|
||||||
|
touchIdReason: NSLocalizedString("SCREEN_LOCK_REASON_DISABLE_SCREEN_LOCK_TOUCH_ID",
|
||||||
|
comment: "Description of how and why Signal iOS uses Touch ID to disable 'screen lock'."),
|
||||||
|
faceIdReason: NSLocalizedString("SCREEN_LOCK_REASON_DISABLE_SCREEN_LOCK_FACE_ID",
|
||||||
|
comment: "Description of how and why Signal iOS uses Face ID 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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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(defaultReason: String,
|
||||||
|
touchIdReason: String,
|
||||||
|
faceIdReason: 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.TAG) enable screen lock 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 or Face ID.")
|
||||||
|
|
||||||
|
var authError: NSError?
|
||||||
|
let canEvaluatePolicy = context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &authError)
|
||||||
|
if !canEvaluatePolicy || authError != nil {
|
||||||
|
Logger.error("\(TAG) could not determine if screen lock is supported: \(String(describing: authError))")
|
||||||
|
|
||||||
|
let outcome = self.outcomeForLAError(errorParam: authError,
|
||||||
|
defaultErrorDescription: defaultErrorDescription)
|
||||||
|
switch outcome {
|
||||||
|
case .success:
|
||||||
|
owsFail("\(self.TAG) unexpected success")
|
||||||
|
completion(.failure(error:defaultErrorDescription))
|
||||||
|
case .cancel, .failure:
|
||||||
|
completion(outcome)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var localizedReason = defaultReason
|
||||||
|
if #available(iOS 11.0, *) {
|
||||||
|
if context.biometryType == .touchID {
|
||||||
|
localizedReason = touchIdReason
|
||||||
|
} else if context.biometryType == .faceID {
|
||||||
|
localizedReason = faceIdReason
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: localizedReason) { success, evaluateError in
|
||||||
|
if success {
|
||||||
|
Logger.info("\(self.TAG) enable screen lock succeeded.")
|
||||||
|
completion(.success)
|
||||||
|
} else {
|
||||||
|
let outcome = self.outcomeForLAError(errorParam: evaluateError,
|
||||||
|
defaultErrorDescription: defaultErrorDescription)
|
||||||
|
switch outcome {
|
||||||
|
case .success:
|
||||||
|
owsFail("\(self.TAG) 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: I'm not sure this is necessary.
|
||||||
|
if #available(iOS 11.0, *) {
|
||||||
|
switch laError.code {
|
||||||
|
case .biometryNotAvailable:
|
||||||
|
Logger.error("\(self.TAG) local authentication error: biometryNotAvailable.")
|
||||||
|
return .failure(error: NSLocalizedString("SCREEN_LOCK_ERROR_LOCAL_AUTHENTICATION_NOT_AVAILABLE",
|
||||||
|
comment: "Indicates that Touch ID/Face ID are not available on this device."))
|
||||||
|
case .biometryNotEnrolled:
|
||||||
|
Logger.error("\(self.TAG) local authentication error: biometryNotEnrolled.")
|
||||||
|
return .failure(error: NSLocalizedString("SCREEN_LOCK_ERROR_LOCAL_AUTHENTICATION_NOT_ENROLLED",
|
||||||
|
comment: "Indicates that Touch ID/Face ID is not configured on this device."))
|
||||||
|
case .biometryLockout:
|
||||||
|
Logger.error("\(self.TAG) local authentication error: biometryLockout.")
|
||||||
|
return .failure(error: NSLocalizedString("SCREEN_LOCK_ERROR_LOCAL_AUTHENTICATION_LOCKOUT",
|
||||||
|
comment: "Indicates that Touch ID/Face ID 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.TAG) local authentication error: authenticationFailed.")
|
||||||
|
return .failure(error: NSLocalizedString("SCREEN_LOCK_ERROR_LOCAL_AUTHENTICATION_FAILED",
|
||||||
|
comment: "Indicates that Touch ID/Face ID authentication failed."))
|
||||||
|
case .userCancel, .userFallback, .systemCancel, .appCancel:
|
||||||
|
Logger.info("\(self.TAG) local authentication cancelled.")
|
||||||
|
return .cancel
|
||||||
|
case .passcodeNotSet:
|
||||||
|
Logger.error("\(self.TAG) local authentication error: passcodeNotSet.")
|
||||||
|
return .failure(error: NSLocalizedString("SCREEN_LOCK_ERROR_LOCAL_AUTHENTICATION_PASSCODE_NOT_SET",
|
||||||
|
comment: "Indicates that Touch ID/Face ID passcode is not set."))
|
||||||
|
case .touchIDNotAvailable:
|
||||||
|
Logger.error("\(self.TAG) local authentication error: touchIDNotAvailable.")
|
||||||
|
return .failure(error: NSLocalizedString("SCREEN_LOCK_ERROR_LOCAL_AUTHENTICATION_NOT_AVAILABLE",
|
||||||
|
comment: "Indicates that Touch ID/Face ID are not available on this device."))
|
||||||
|
case .touchIDNotEnrolled:
|
||||||
|
Logger.error("\(self.TAG) local authentication error: touchIDNotEnrolled.")
|
||||||
|
return .failure(error: NSLocalizedString("SCREEN_LOCK_ERROR_LOCAL_AUTHENTICATION_NOT_ENROLLED",
|
||||||
|
comment: "Indicates that Touch ID/Face ID is not configured on this device."))
|
||||||
|
case .touchIDLockout:
|
||||||
|
Logger.error("\(self.TAG) local authentication error: touchIDLockout.")
|
||||||
|
return .failure(error: NSLocalizedString("SCREEN_LOCK_ERROR_LOCAL_AUTHENTICATION_LOCKOUT",
|
||||||
|
comment: "Indicates that Touch ID/Face ID is 'locked out' on this device due to authentication failures."))
|
||||||
|
case .invalidContext:
|
||||||
|
owsFail("\(self.TAG) context not valid.")
|
||||||
|
break
|
||||||
|
case .notInteractive:
|
||||||
|
owsFail("\(self.TAG) context not interactive.")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
// This switch may fall through.
|
||||||
|
}
|
||||||
|
return .failure(error:defaultErrorDescription)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func authenticationError(errorDescription: String) -> Error {
|
||||||
|
return OWSErrorWithCodeDescription(.localAuthenticationError,
|
||||||
|
errorDescription)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Context
|
||||||
|
|
||||||
|
private func screenLockContext() -> LAContext {
|
||||||
|
let context = LAContext()
|
||||||
|
|
||||||
|
// if #available(iOS 11, *) {
|
||||||
|
// context.biometryType = [.touchId,.faceId]
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Time interval for accepting a successful Touch ID or Face ID device unlock (on the lock screen) from the past.
|
||||||
|
//
|
||||||
|
// TODO: Review.
|
||||||
|
context.touchIDAuthenticationAllowableReuseDuration = TimeInterval(5.0)
|
||||||
|
|
||||||
|
// Don't set context.maxBiometryFailures.
|
||||||
|
//
|
||||||
|
// TODO: Review.
|
||||||
|
|
||||||
|
return context
|
||||||
|
}
|
||||||
|
// /// Fallback button title.
|
||||||
|
// /// @discussion Allows fallback button title customization. If set to empty string, the button will be hidden.
|
||||||
|
// /// A default title "Enter Password" is used when this property is left nil.
|
||||||
|
// @property (nonatomic, nullable, copy) NSString *localizedFallbackTitle;
|
||||||
|
//
|
||||||
|
// /// Cancel button title.
|
||||||
|
// /// @discussion Allows cancel button title customization. A default title "Cancel" is used when
|
||||||
|
// /// this property is left nil or is set to empty string.
|
||||||
|
// @property (nonatomic, nullable, copy) NSString *localizedCancelTitle NS_AVAILABLE(10_12, 10_0);
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// typedef NS_ENUM(NSInteger, LAAccessControlOperation)
|
||||||
|
// {
|
||||||
|
// /// Access control will be used for item creation.
|
||||||
|
// LAAccessControlOperationCreateItem,
|
||||||
|
//
|
||||||
|
// /// Access control will be used for accessing existing item.
|
||||||
|
// LAAccessControlOperationUseItem,
|
||||||
|
//
|
||||||
|
// /// Access control will be used for key creation.
|
||||||
|
// LAAccessControlOperationCreateKey,
|
||||||
|
//
|
||||||
|
// /// Access control will be used for sign operation with existing key.
|
||||||
|
// LAAccessControlOperationUseKeySign,
|
||||||
|
//
|
||||||
|
// /// Access control will be used for data decryption using existing key.
|
||||||
|
// LAAccessControlOperationUseKeyDecrypt NS_ENUM_AVAILABLE(10_12, 10_0),
|
||||||
|
//
|
||||||
|
// /// Access control will be used for key exchange.
|
||||||
|
// LAAccessControlOperationUseKeyKeyExchange NS_ENUM_AVAILABLE(10_12, 10_0),
|
||||||
|
// } NS_ENUM_AVAILABLE(10_11, 9_0) __WATCHOS_AVAILABLE(3.0) __TVOS_AVAILABLE(10.0);
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// typedef NS_ENUM(NSInteger, LAPolicy)
|
||||||
|
// {
|
||||||
|
// /// Device owner is going to be authenticated using a biometric method (Touch ID or Face ID).
|
||||||
|
// ///
|
||||||
|
// /// @discussion Biometric authentication is required. If the biometry is not available, not enrolled,
|
||||||
|
// /// or locked out, then the evaluation of this policy will fail with LAErrorBiometryNotAvailable,
|
||||||
|
// /// LAErrorBiometryNotEnrolled or LAErrorBiometryLockout. Biometry can't be used without a passcode,
|
||||||
|
// /// i.e. when biometry is available, enrolled and not locked out but passcode is not set, then
|
||||||
|
// /// the evaluation will fail with LAErrorPasscodeNotSet.
|
||||||
|
// ///
|
||||||
|
// /// Touch ID authentication dialog contains a cancel button with default title "Cancel"
|
||||||
|
// /// which can be customized using localizedCancelTitle property, and a fallback button with
|
||||||
|
// /// default title "Enter Password" which can be customized using localizedFallbackTitle
|
||||||
|
// /// property. Fallback button is initially hidden and shows up after first unsuccessful
|
||||||
|
// /// Touch ID attempt. Tapping either button causes evaluatePolicy call to fail, returning
|
||||||
|
// /// a distinct error code: LAErrorUserCancel or LAErrorUserFallback.
|
||||||
|
// ///
|
||||||
|
// /// Face ID authentication begins with animating HUD. If it succeeds at first attempt,
|
||||||
|
// /// the HUD will disappear and no other UI is shown. If the first attempt fails, then
|
||||||
|
// /// LocalAuthentication will show a dialog with two buttons: "Cancel" and "Try Face ID Again".
|
||||||
|
// /// After second failure, the buttons are "Cancel" and "Enter Password" with the same
|
||||||
|
// // semantics as in the case of Touch ID.
|
||||||
|
// ///
|
||||||
|
// /// Biometric authentication will get locked after 5 unsuccessful attempts. After that,
|
||||||
|
// /// users have to unlock it by entering passcode. The passcode can be entered either at
|
||||||
|
// /// Lock Screen or even in app by the means of LAPolicyDeviceOwnerAuthentication.
|
||||||
|
// /// The Lock Screen unlock is preferred user experience because we generaly don't want users to
|
||||||
|
// /// enter their passcode at app's request.
|
||||||
|
// LAPolicyDeviceOwnerAuthenticationWithBiometrics NS_ENUM_AVAILABLE(10_12_2, 8_0) __WATCHOS_AVAILABLE(3.0) __TVOS_AVAILABLE(10.0) = kLAPolicyDeviceOwnerAuthenticationWithBiometrics,
|
||||||
|
//
|
||||||
|
// /// Device owner is going to be authenticated by biometry or device passcode.
|
||||||
|
// ///
|
||||||
|
// /// @discussion Biometric or passcode authentication is required. If the biometry is available, enrolled and
|
||||||
|
// /// not locked out, users are asked for it first. Otherwise they are asked to enter device
|
||||||
|
// /// passcode. If passcode is not enabled, policy evaluation will fail with LAErrorPasscodeNotSet.
|
||||||
|
// ///
|
||||||
|
// /// Biometric authentication dialog behaves similarly as the one used by
|
||||||
|
// /// LAPolicyDeviceOwnerAuthenticationWithBiometrics. However, instead of "Enter Password"
|
||||||
|
// /// button there is "Enter Passcode" button which, when tapped, switches the authentication
|
||||||
|
// /// method and allows users to enter device passcode.
|
||||||
|
// ///
|
||||||
|
// /// Passcode authentication will get locked after 6 unsuccessful attempts with progressively
|
||||||
|
// /// increased backoff delay.
|
||||||
|
// LAPolicyDeviceOwnerAuthentication NS_ENUM_AVAILABLE(10_11, 9_0) = kLAPolicyDeviceOwnerAuthentication
|
||||||
|
}
|
|
@ -1537,6 +1537,48 @@
|
||||||
/* Title for the 'scan QR code' view. */
|
/* Title for the 'scan QR code' view. */
|
||||||
"SCAN_QR_CODE_VIEW_TITLE" = "Scan QR Code";
|
"SCAN_QR_CODE_VIEW_TITLE" = "Scan QR Code";
|
||||||
|
|
||||||
|
/* Title for alert indicating that screen lock could not be disabled. */
|
||||||
|
"SCREEN_LOCK_DISABLE_FAILED" = "Screen Lock Could Not Be Disabled";
|
||||||
|
|
||||||
|
/* Title for alert indicating that screen lock could not be enabled. */
|
||||||
|
"SCREEN_LOCK_ENABLE_FAILED" = "Screen Lock Could Not Be Enabled";
|
||||||
|
|
||||||
|
/* Indicates that an unknown error occurred while using Touch ID or Face ID. */
|
||||||
|
"SCREEN_LOCK_ENABLE_UNKNOWN_ERROR" = "Touch ID and Face ID could not be accessed.";
|
||||||
|
|
||||||
|
/* Indicates that Touch ID/Face ID authentication failed. */
|
||||||
|
"SCREEN_LOCK_ERROR_LOCAL_AUTHENTICATION_FAILED" = "Authentication with Touch ID or Face ID failed.";
|
||||||
|
|
||||||
|
/* Indicates that Touch ID/Face ID is 'locked out' on this device due to authentication failures. */
|
||||||
|
"SCREEN_LOCK_ERROR_LOCAL_AUTHENTICATION_LOCKOUT" = "Touch ID or Face ID is locked out; too many failures.";
|
||||||
|
|
||||||
|
/* Indicates that Touch ID/Face ID are not available on this device. */
|
||||||
|
"SCREEN_LOCK_ERROR_LOCAL_AUTHENTICATION_NOT_AVAILABLE" = "Touch ID or Face ID is not available on this device.";
|
||||||
|
|
||||||
|
/* Indicates that Touch ID/Face ID is not configured on this device. */
|
||||||
|
"SCREEN_LOCK_ERROR_LOCAL_AUTHENTICATION_NOT_ENROLLED" = "Touch ID or Face ID is not configured.";
|
||||||
|
|
||||||
|
/* Indicates that Touch ID/Face ID passcode is not set. */
|
||||||
|
"SCREEN_LOCK_ERROR_LOCAL_AUTHENTICATION_PASSCODE_NOT_SET" = "Touch ID or Face ID is not available; no passcode has been set.";
|
||||||
|
|
||||||
|
/* Description of how and why Signal iOS uses Touch ID/Face ID to disable 'screen lock'. */
|
||||||
|
"SCREEN_LOCK_REASON_DISABLE_SCREEN_LOCK" = "Use Touch ID or Face ID to disable Screen Lock.";
|
||||||
|
|
||||||
|
/* Description of how and why Signal iOS uses Face ID to disable 'screen lock'. */
|
||||||
|
"SCREEN_LOCK_REASON_DISABLE_SCREEN_LOCK_FACE_ID" = "Use Face ID to disable Screen Lock.";
|
||||||
|
|
||||||
|
/* Description of how and why Signal iOS uses Touch ID to disable 'screen lock'. */
|
||||||
|
"SCREEN_LOCK_REASON_DISABLE_SCREEN_LOCK_TOUCH_ID" = "Use Touch ID to disable Screen Lock.";
|
||||||
|
|
||||||
|
/* Description of how and why Signal iOS uses Touch ID/Face ID to enable 'screen lock'. */
|
||||||
|
"SCREEN_LOCK_REASON_ENABLE_SCREEN_LOCK" = "Use Touch ID or Face ID to lock access to Signal.";
|
||||||
|
|
||||||
|
/* Description of how and why Signal iOS uses Face ID to enable 'screen lock'. */
|
||||||
|
"SCREEN_LOCK_REASON_ENABLE_SCREEN_LOCK_FACE_ID" = "Use Face ID to lock access to Signal.";
|
||||||
|
|
||||||
|
/* Description of how and why Signal iOS uses Touch ID to enable 'screen lock'. */
|
||||||
|
"SCREEN_LOCK_REASON_ENABLE_SCREEN_LOCK_TOUCH_ID" = "Use Touch ID to lock access to Signal.";
|
||||||
|
|
||||||
/* No comment provided by engineer. */
|
/* No comment provided by engineer. */
|
||||||
"SEARCH_BYNAMEORNUMBER_PLACEHOLDER_TEXT" = "Search by name or number";
|
"SEARCH_BYNAMEORNUMBER_PLACEHOLDER_TEXT" = "Search by name or number";
|
||||||
|
|
||||||
|
@ -1747,6 +1789,15 @@
|
||||||
/* Remove metadata section header */
|
/* Remove metadata section header */
|
||||||
"SETTINGS_REMOVE_METADATA_TITLE" = "Metadata";
|
"SETTINGS_REMOVE_METADATA_TITLE" = "Metadata";
|
||||||
|
|
||||||
|
/* Footer for the 'screen lock' section of the privacy settings. */
|
||||||
|
"SETTINGS_SCREEN_LOCK_SECTION_FOOTER" = "Lock Signal access with iOS Touch ID or Face ID.";
|
||||||
|
|
||||||
|
/* Title for the 'screen lock' section of the privacy settings. */
|
||||||
|
"SETTINGS_SCREEN_LOCK_SECTION_TITLE" = "Screen Lock";
|
||||||
|
|
||||||
|
/* Label for the 'enable screen lock' switch of the privacy settings. */
|
||||||
|
"SETTINGS_SCREEN_LOCK_SWITCH_LABEL" = "Screen Lock";
|
||||||
|
|
||||||
/* No comment provided by engineer. */
|
/* No comment provided by engineer. */
|
||||||
"SETTINGS_SCREEN_SECURITY" = "Enable Screen Security";
|
"SETTINGS_SCREEN_SECURITY" = "Enable Screen Security";
|
||||||
|
|
||||||
|
|
|
@ -41,6 +41,7 @@ typedef NS_ENUM(NSInteger, OWSErrorCode) {
|
||||||
OWSErrorCodeImportBackupError = 777418,
|
OWSErrorCodeImportBackupError = 777418,
|
||||||
// A non-recoverable while importing or exporting a backup.
|
// A non-recoverable while importing or exporting a backup.
|
||||||
OWSErrorCodeBackupFailure = 777419,
|
OWSErrorCodeBackupFailure = 777419,
|
||||||
|
OWSErrorCodeLocalAuthenticationError = 777420,
|
||||||
};
|
};
|
||||||
|
|
||||||
extern NSString *const OWSErrorRecipientIdentifierKey;
|
extern NSString *const OWSErrorRecipientIdentifierKey;
|
||||||
|
|
Loading…
Reference in a new issue