2017-05-01 20:28:37 +02:00
|
|
|
//
|
|
|
|
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
|
|
|
|
//
|
|
|
|
|
|
|
|
import Foundation
|
|
|
|
import Contacts
|
|
|
|
|
|
|
|
@objc protocol SystemContactsFetcherDelegate: class {
|
|
|
|
func systemContactsFetcher(_ systemContactsFetcher: SystemContactsFetcher, updatedContacts contacts: [Contact])
|
|
|
|
}
|
|
|
|
|
|
|
|
@objc
|
|
|
|
class SystemContactsFetcher: NSObject {
|
|
|
|
|
|
|
|
private let TAG = "[SystemContactsFetcher]"
|
|
|
|
|
|
|
|
public weak var delegate: SystemContactsFetcherDelegate?
|
|
|
|
|
|
|
|
public var authorizationStatus: CNAuthorizationStatus {
|
|
|
|
return CNContactStore.authorizationStatus(for: CNEntityType.contacts)
|
|
|
|
}
|
|
|
|
|
2017-05-03 23:03:26 +02:00
|
|
|
public var isAuthorized: Bool {
|
|
|
|
guard self.authorizationStatus != .notDetermined else {
|
|
|
|
assertionFailure("should have called `requestOnce` before this point.")
|
|
|
|
Logger.error("\(TAG) should have called `requestOnce` before checking authorization status.")
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
return self.authorizationStatus == .authorized
|
|
|
|
}
|
|
|
|
|
2017-05-01 20:28:37 +02:00
|
|
|
private let contactStore = CNContactStore()
|
|
|
|
private var systemContactsHaveBeenRequestedAtLeastOnce = false
|
|
|
|
private let allowedContactKeys: [CNKeyDescriptor] = [
|
|
|
|
CNContactFormatter.descriptorForRequiredKeys(for: .fullName),
|
|
|
|
CNContactThumbnailImageDataKey as CNKeyDescriptor, // TODO full image instead of thumbnail?
|
|
|
|
CNContactPhoneNumbersKey as CNKeyDescriptor,
|
|
|
|
CNContactEmailAddressesKey as CNKeyDescriptor
|
|
|
|
]
|
|
|
|
|
|
|
|
public func requestOnce() {
|
|
|
|
AssertIsOnMainThread()
|
|
|
|
|
|
|
|
guard !systemContactsHaveBeenRequestedAtLeastOnce else {
|
|
|
|
Logger.debug("\(TAG) already requested system contacts")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
systemContactsHaveBeenRequestedAtLeastOnce = true
|
|
|
|
self.startObservingContactChanges()
|
|
|
|
|
|
|
|
switch authorizationStatus {
|
|
|
|
case .notDetermined:
|
|
|
|
contactStore.requestAccess(for: .contacts, completionHandler: { (granted, error) in
|
|
|
|
if let error = error {
|
|
|
|
Logger.error("\(self.TAG) error fetching contacts: \(error)")
|
|
|
|
assertionFailure()
|
|
|
|
}
|
|
|
|
|
|
|
|
if !granted {
|
|
|
|
// TODO, make this a one time dismissable admonishment
|
|
|
|
// e.g. remember across launches that the user has dismissed.
|
|
|
|
self.displayMissingContactsPermissionAlert()
|
|
|
|
} else {
|
|
|
|
self.updateContacts()
|
|
|
|
}
|
|
|
|
})
|
|
|
|
case .authorized:
|
|
|
|
// TODO reset onetime admonishment reminder, so that we remind user again (once) if they've since toggled permissions.
|
|
|
|
self.updateContacts()
|
|
|
|
case .denied, .restricted:
|
|
|
|
Logger.debug("\(TAG) contacts were \(self.authorizationStatus)")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public func fetchIfAlreadyAuthorized() {
|
|
|
|
AssertIsOnMainThread()
|
|
|
|
guard authorizationStatus == .authorized else {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
updateContacts()
|
|
|
|
}
|
|
|
|
|
|
|
|
private func displayMissingContactsPermissionAlert() {
|
|
|
|
let foo = UIApplication.shared.frontmostViewController
|
|
|
|
Logger.error("TODO")
|
|
|
|
}
|
|
|
|
|
|
|
|
private func updateContacts() {
|
2017-05-05 21:03:26 +02:00
|
|
|
AssertIsOnMainThread()
|
|
|
|
|
2017-05-01 20:28:37 +02:00
|
|
|
systemContactsHaveBeenRequestedAtLeastOnce = true
|
|
|
|
|
2017-05-05 21:03:26 +02:00
|
|
|
let contactStore = self.contactStore
|
|
|
|
let allowedContactKeys = self.allowedContactKeys
|
|
|
|
|
|
|
|
DispatchQueue.global().async {
|
|
|
|
var systemContacts = [CNContact]()
|
|
|
|
do {
|
|
|
|
let contactFetchRequest = CNContactFetchRequest(keysToFetch: allowedContactKeys)
|
|
|
|
try contactStore.enumerateContacts(with: contactFetchRequest) { (contact, _) -> Void in
|
|
|
|
systemContacts.append(contact)
|
|
|
|
}
|
|
|
|
} catch let error as NSError {
|
|
|
|
Logger.error("\(self.TAG) Failed to fetch contacts with error:\(error)")
|
|
|
|
assertionFailure()
|
2017-05-01 20:28:37 +02:00
|
|
|
}
|
|
|
|
|
2017-05-05 21:03:26 +02:00
|
|
|
let contacts = systemContacts.map { Contact(systemContact: $0) }
|
|
|
|
DispatchQueue.main.async {
|
|
|
|
self.delegate?.systemContactsFetcher(self, updatedContacts: contacts)
|
|
|
|
}
|
|
|
|
}
|
2017-05-01 20:28:37 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
private func startObservingContactChanges() {
|
|
|
|
NotificationCenter.default.addObserver(self,
|
|
|
|
selector: #selector(contactStoreDidChange),
|
|
|
|
name: .CNContactStoreDidChange,
|
|
|
|
object: nil)
|
|
|
|
}
|
|
|
|
|
|
|
|
@objc
|
|
|
|
private func contactStoreDidChange() {
|
|
|
|
updateContacts()
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|