iOS accessing AddressBook Contacts via UnitTest; how to set permissions? - ios

This question pertains to using the iPhone Simulator with Unit Tests. I have written a series of tests that test storing of our data, merged or not merged with data we can access from the user's contacts, depending on whether or not the contact exists. I would like a way to set the permissions such that I can test (A) when the user gives permission to access the contacts and (B) when the user denies access to the contacts. What I would like is a way, in Unit Tests only, to hard-code the permission value. I don't want to prompt for it, since that would block the unit test from running with the additional hardship that the permission remains set to that value forever.
So I am reaching out to the SO community to see who else might be testing their code's interaction with address book contacts by controlling the permissions in a Unit Test. Does anyone have a recipe which allows me test both sides of the user giving and denying access to the Address Book contacts?
I am using the XCTestCase parent class. I would be open to using something else if that helped solved this problem.
I have read through all the related SO questions and answers in this area. They are focused on helping people with writing the permission-asking routines and accessing the address book contacts in their application. I know how to do that part. I am specifically talking about how to fake the address book permissions in a unit test.

At first as I do this
1) in app ->
if (ABAddressBookGetAuthorizationStatus() == kABAuthorizationStatusDenied) ...
if (ABAddressBookGetAuthorizationStatus() == kABAuthorizationStatusRestricted) ...
2) for test this issues I use class of OCMock, for predefined values and methods
link here - http://ocmock.org

if (ABAddressBookGetAuthorizationStatus() == kABAuthorizationStatusDenied ||
ABAddressBookGetAuthorizationStatus() == kABAuthorizationStatusRestricted){
} else if (ABAddressBookGetAuthorizationStatus() == kABAuthorizationStatusAuthorized){
} else{
ABAddressBookRequestAccessWithCompletion(ABAddressBookCreateWithOptions(NULL, nil), ^(bool granted, CFErrorRef error) {
if (!granted){
return;
}
});
}
i found the preceding code on a handy little website called:
www.raywenderlich.com

// Address Book Authorization grant
if (ABAddressBookGetAuthorizationStatus() == kABAuthorizationStatusDenied ||
ABAddressBookGetAuthorizationStatus() == kABAuthorizationStatusRestricted)
{
NSLog(#"Denied");
}
else if (ABAddressBookGetAuthorizationStatus() == kABAuthorizationStatusAuthorized)
{
NSLog(#"Authorized");
}
else
{
ABAddressBookRequestAccessWithCompletion(ABAddressBookCreateWithOptions(NULL, nil), ^(bool granted, CFErrorRef error)
{
if (!granted) {
NSLog(#"Just denied");
return;
}
NSLog(#"Just authorized");
});
NSLog(#"Not determined");
}

For Unit testing
Nsstring *authorizationStatus;
if (ABAddressBookGetAuthorizationStatus() == kABAuthorizationStatusDenied ||
ABAddressBookGetAuthorizationStatus() == kABAuthorizationStatusRestricted){
//1
NSLog(#"Denied");
authorizationStatus = #"Denied";
} else if (ABAddressBookGetAuthorizationStatus() == kABAuthorizationStatusAuthorized){
NSLog(#"Authorized");
authorizationStatus = #"Authorized";
} else{ //ABAddressBookGetAuthorizationStatus() == kABAuthorizationStatusNotDetermined
//3
NSLog(#"Not determined");
authorizationStatus = #"Not determined";
}
XCTAssertTrue(authorizationStatus, #"Authorized");

Related

QuickBlox video chat: QBRequest.logInWithUserEmail vs QBChat.instance().connectWithUser

I have a simple QuickBlox chat app built by following the iOS tutorial:
http://quickblox.com/developers/Sample-webrtc-ios#Sources
I've successfully created a user and logged them in. However, I run into an error when I try to initiate a session: "You have to be logged in in order to use Chat API".
let newSession: QBRTCSession = QBRTCClient.instance().createNewSessionWithOpponents(["12498970"], withConferenceType: QBRTCConferenceType.Video)
I'm able to resolve this by adding QBChat.instance().connectWithUser each time I open it:
QBChat.instance().connectWithUser(user!) { (error) in
if error != nil {
print("error: \(error)")
}
else {
print("login to chat succeeded")
}
}
But somehow this seems weird because I have to either cache the password or prompt the user to enter their password each time the app opens. It seems strange that the QBSession.currentSession().currentUser is still valid, but the QBChat user has been invalidated. What is the best practice for accomplishing this? In all the samples, the passwords are hardcoded. This doesn't seem like a great solution.
I ended up following examples in Q-municate, which is an app the Quickblox folks built to basically demonstrate their whole package, as well as provide an actual solution for whatever your chat needs are. I have some other custom stuff and don't need a lot of the functionality so I'm still trying to dig through the details of how they implement it. The link to Q-municate:
http://quickblox.com/developers/Q-municate#1._Get_the_source_code.
In their login flow, they use the QMApi module written for Q-municate:
[[QMApi instance] loginWithEmail:email
password:password
rememberMe:weakSelf.rememberMeSwitch.on
completion:^(BOOL success)
{
[SVProgressHUD dismiss];
if (success) {
[[QMApi instance] setAutoLogin:weakSelf.rememberMeSwitch.on
withAccountType:QMAccountTypeEmail];
[weakSelf performSegueWithIdentifier:kTabBarSegueIdnetifier
sender:nil];
}
}];
In loginWithEmail, their settingsManager caches this login:
[weakSelf.settingsManager setLogin:email andPassword:password];
which is actually just a way to cache the password in SSKeyChain.
[SSKeychain setPassword:password forService:kQMAuthServiceKey account:login];
Later, when you return to the app, they call autologin:
if (!self.isAuthorized) {
if (self.settingsManager.accountType == QMAccountTypeEmail && self.settingsManager.password && self.settingsManager.login) {
NSString *email = self.settingsManager.login;
NSString *password = self.settingsManager.password;
[self loginWithEmail:email password:password rememberMe:YES completion:completion];
}
else if (self.settingsManager.accountType == QMAccountTypeFacebook) {
[self loginWithFacebook:completion];
}
else {
if (completion) completion(NO);
}
}
else {
if (completion) completion(YES);
}
where self.settingsManager.password pulls the password from SSKeychain:
NSString *password = [SSKeychain passwordForService:kQMAuthServiceKey account:self.login];
autoLogin is called when the main chat tab is loaded. That makes our classic call to connectToChat:
[[QMApi instance] autoLogin:^(BOOL success) {
if (!success) {
[[QMApi instance] logoutWithCompletion:^(BOOL succeed) {
//
[weakSelf performSegueWithIdentifier:#"SplashSegue" sender:nil];
}];
} else {
// subscribe to push notifications
[[QMApi instance] subscribeToPushNotificationsForceSettings:NO complete:^(BOOL subscribeToPushNotificationsSuccess) {
if (!subscribeToPushNotificationsSuccess) {
[QMApi instance].settingsManager.pushNotificationsEnabled = NO;
}
}];
[weakSelf connectToChat];
}
}];
So technically the docs are doing the right thing by logging in to chat every time the app opens and chat is no longer connected. There's just a much more complex but secure way to store that password so the user doesn't have to reenter it.
TLDR: The way it works in my code (and in swift) is:
On login:
QBRequest.logInWithUserEmail(email, password: password, successBlock: { (response, user) in
SSKeychain.setPassword(password, forService: "kMyAppLoginServiceKey", account: email)
}) { (errorResponse) in
print("Error: \(errorResponse)")
self.simpleAlert("Could not log in", defaultMessage: nil, error: nil)
}
Whenever the chat view loads:
if !QBChat.instance().isConnected() {
QBRTCClient.initializeRTC()
QBRTCClient.instance().addDelegate(self)
let user = QBSession.currentSession().currentUser
let password = SSKeychain.passwordForService("kMyAppLoginServiceKey", account: user?.email!)
user!.password = password
QBChat.instance().addDelegate(self)
QBChat.instance().connectWithUser(user!) { (error) in
if error != nil {
print("error: \(error)")
}
else {
print("login to chat succeeded")
}
}
}

CMMotionActivityManager authorization status

I'm trying to figure out a way to handle the authorization statuses for Motion activity
Here's what I came up with so far :
manager = CMMotionActivityManager()
manager.queryActivityStartingFromDate(now, toDate: now, toQueue: NSOperationQueue.mainQueue(),
withHandler: { (activities: [CMMotionActivity]?, error: NSError?) -> Void in
if(error != nil){
if(error!.code != Int(CMErrorMotionActivityNotAuthorized.rawValue)){
print("CMErrorMotionActivityNotAuthorized")
}else if(error!.code != Int(CMErrorMotionActivityNotEntitled.rawValue)){
print("CMErrorMotionActivityNotEntitled")
}else if(error!.code != Int(CMErrorMotionActivityNotAvailable.rawValue)){
print("CMErrorMotionActivityNotAvailable")
}
}
})
One problem though :
When I deny the app permission to motion activity (via settings), I get CMErrorMotionActivityNotEntitled
(I believe I should be getting CMErrorMotionActivityNotAuthorized instead)
Any ideas why ? or at least what's the proper way of doing this ?
Perhaps you are getting CMErrorMotionActivityNotAuthorized. You'll never know, with your code, because your code does not ask what code you are getting. It asks what code you are not getting:
if(error!.code != Int(CMErrorMotionActivityNotAuthorized.rawValue)){
print("CMErrorMotionActivityNotAuthorized")
}else if(error!.code != Int(CMErrorMotionActivityNotEntitled.rawValue)){
print("CMErrorMotionActivityNotEntitled")
}else if(error!.code != Int(CMErrorMotionActivityNotAvailable.rawValue)){
print("CMErrorMotionActivityNotAvailable")
}
The != operator means is not. So you are doing a series of checks about what the code is not. It's hard to see how you can get any useful information by asking that question. It might make more sense to ask what the code is, which would involve using the == operator.

Address book change callback registered with ABAddressBookRegisterExternalChangeCallback is never called (iOS 8)

I've found plenty of examples around this but after reading the entire ABAddressBook documentation I'm still not able to figure out why, in my case, my change callback is not being called. I simply set up an address book and register a callback function for it.
I can access the address book just fine, but the callback function is never called no matter how much I change contacts in the Contacts app and then reopen my app. Is there any reason that the callback would never be called? I've already made sure I don't release the address book or unregister the callback.
The init code:
// Set up address book API.
CFErrorRef *error = NULL;
_addressBook = ABAddressBookCreateWithOptions(NULL, error);
if (error) {
NSLog(#"Could not initialize address book: %#", CFBridgingRelease(CFErrorCopyFailureReason(*error)));
} else {
ABAddressBookRegisterExternalChangeCallback(_addressBook, RogerAddressBookChangeCallback, (__bridge void *)self);
NSLog(#"Registered callback");
}
The callback function:
void RogerAddressBookChangeCallback(ABAddressBookRef addressBook, CFDictionaryRef info, void *context) {
NSLog(#"Address book change");
ABAddressBookRevert(addressBook);
RogerAddressBook *instance = (__bridge RogerAddressBook *)context;
[instance import];
}
I see the log output Registered callback but never Address book change.
Actually the code for ABAddressBook is written in C. So you may find difficulties in using the Original ABAddressBook Framework.
So I suggest for using an third party library (Which is just an makeover of C to Obj-C) to access contacts and the Contacts Change.
Here is the link for an popular library https://github.com/Alterplay/APAddressBook
Using the above framework you could easily observe the changes in Address book.
Observe address book external changes
// start observing
[addressBook startObserveChangesWithCallback:^
{
NSLog(#"Address book changed!");
}];
// stop observing
[addressBook stopObserveChanges];
This library also has lot of options like sorting, filtering etc.
Access to address book requires user authorization. If authorization status is kABAuthorizationStatusNotDetermined, your code fails silently, returning non-nil result and producing no error.
I have next code to create address book:
- (ABAddressBookRef)newAddressBookRef
{
ABAuthorizationStatus authorizationStatus = ABAddressBookGetAuthorizationStatus();
if (authorizationStatus == kABAuthorizationStatusAuthorized)
{
ABAddressBookRef addressBookRef = nil;
CFErrorRef error;
addressBookRef = ABAddressBookCreateWithOptions(NULL, &error);
return addressBookRef;
}
return nil;
}
And next code to explicitly request address book access (usually performed on application start).
typedef void(^AddressBookHelperAccessRequestCompletionHandler)(BOOL accessGiven);
- (void)requestAccessToAddressBook:(ABAddressBookRef)addressBookRef withCompletionHandler:(AddressBookHelperAccessRequestCompletionHandler)completionHandler
{
ABAuthorizationStatus authorizationStatus = ABAddressBookGetAuthorizationStatus();
switch (authorizationStatus)
{
case kABAuthorizationStatusNotDetermined:
{
// Request access permissions for even for NULL address book reference.
// When permissions have not been granted yet, all address book references will be equal to NULL
ABAddressBookRequestAccessWithCompletion(addressBookRef, ^(bool granted, CFErrorRef error)
{
if (granted)
{
[self registerForAddressBookChanges];
}
if (completionHandler)
{
completionHandler(granted);
}
});
break;
}
case kABAuthorizationStatusDenied:
case kABAuthorizationStatusRestricted:
[self showNoContactsAccessAlert];
default:
{
if (completionHandler)
{
completionHandler(authorizationStatus == kABAuthorizationStatusAuthorized);
}
break;
}
}
}

Store users contacts in NSMutable array after access is granted for address book

I'm using this in my viewWillAppear: to ask the user for permission/access(as apple requires) to their address book. When they have allowed access, they can proceed to an "invites" page, where they can browse through (their own)contacts that already have existing accounts on my app. Basically, I'm just going to take their contacts and put it into a UITableView. SO, I MUST GET ALL THEIR CONTACTS AND ADD IT TO AN array of type NSMutableArray. In the following code, where it says "//ADD ALL OF USERS CONTACTS TO ARRAY HERE" I need some code. What do I put there?
- (void)viewWillAppear:(BOOL)animated
{
// Request authorization to Address Book
ABAddressBookRef addressBookRef = ABAddressBookCreateWithOptions(NULL, NULL);
if (ABAddressBookGetAuthorizationStatus() == kABAuthorizationStatusNotDetermined) {
ABAddressBookRequestAccessWithCompletion(addressBookRef, ^(bool granted, CFErrorRef error) {
if (granted) {
// First time access has been granted, add user's contacts to array
// ADD ALL OF USERS CONTACTS TO ARRAY HERE
} else {
// User denied access
// Display an alert telling user that they must allow access in order to proceed to "invites" page
}
});
}
else if (ABAddressBookGetAuthorizationStatus() == kABAuthorizationStatusAuthorized) {
// The user has previously given access, grab all contacts
// ADD ALL OF USERS CONTACTS TO ARRAY HERE
}
else {
// The user has previously denied access
// Display an alert telling user that they must allow access in order to proceed to "invites" page
}
}
You want ABAddressBookCopyArrayOfAllPeople().

Catch 22 according to the documentation for address book use with iOS 6

According to the documentation with iOS6 an address book should be created using ABAddressBookCreateWithOptions.
It also says if the caller does not have access to the db then this method will return null.
However access is requested by calling ABAddressBookRequestAccessWithCompletion, which takes an ABAddressBookRef as a parameter.
So according to the documentation you can't get an ABAddressBookRef in the first place if you don't have access, but to get access you have to have an ABAddressBookRef to pass as a parameter.
Eh. Catch 22? How do you create an ABAddressBookRef then?
Been googling for some example/tutorial code for this but haven't found any.
TIA
I use code like this:
if (ABAddressBookCreateWithOptions) {
_book = ABAddressBookCreateWithOptions(NULL, NULL);
if (ABAddressBookGetAuthorizationStatus() == kABAuthorizationStatusNotDetermined) {
ABAddressBookRequestAccessWithCompletion(_book, ^(bool granted, CFErrorRef error) {
dispatch_async(dispatch_get_main_queue(), ^{
if (granted && !error) {
ABAddressBookRevert(_book);
}
});
});
}
} else {
_book = ABAddressBookCreate();
}
where _book is my ABAddressBookRef.
If my app has been denied access then _book will be NULL and you can't access the address book.
See my answer here:
https://stackoverflow.com/a/13671852/341994
ABAddressBookRequestAccessWithCompletion is useless and should be ignored.
If you need to know the authorization status, call ABAddressBookGetAuthorizationStatus.
If the authorization is undetermined (the user has never run this app), just attempt to access the database. This will cause the authorization alert to appear.
If the user has authorized or denied access, ABAddressBookRequestAccessWithCompletion does nothing of any value so there's no point calling it.

Resources