Friday, July 1, 2016

iOS 10 CallKit Directory Extension - blocking and identifying callers

(I created an updated version of this blog post to reflect the shipping version of the API and to link some sample code. http://iphoneramble.blogspot.com/2017/05/ios-10-callkit-directory-extension.html https://github.com/dvshelley/CallDirectory )

New in iOS 10, Apple introduced CallKit. One of the things it does is provide an extension interface that allows your app to provide a block list and a caller directory.

Apple just briefly mentioned this feature in the "Whats new in Cocoa Touch" session 205 at WWDC 2016. All the information they provide is on slides 270 to 273.

https://developer.apple.com/videos/play/wwdc2016/205/
http://asciiwwdc.com/2016/sessions/205

Here is what Apple's api looks like in iOS 10 beta 1:

public class CXCallDirectoryExtensionContext : NSExtensionContext {
    public func addBlockingEntry(withNextSequentialPhoneNumber phoneNumber: String)
    public func addIdentificationEntry(withNextSequentialPhoneNumber phoneNumber: String,
                                                                           label: String)
}

Here is Apple's documentation: https://developer.apple.com/reference/callkit/cxcalldirectoryprovider?language=objc

Unfortunately Apple doesn't provide any sample code for this API. When I did a Google search, I found a bunch of confused people. And then this Chinese guy named Collin. Google Translate to the rescue!
http://colin1994.github.io/2016/06/17/Call-Directory-Extension-Study/
https://translate.google.com/translate?hl=en&sl=zh-CN&u=http://colin1994.github.io/2016/06/17/Call-Directory-Extension-Study/&prev=search

Collin does a pretty good job of walking you through creating a sample project. I'm going to reproduce some of it here (in English) and add a few things I learned. His focus was a blocking app. I want to write an app that can provide a caller directory.

How to create your own directory

1. Create a project. Xcode -> File -> New -> Project. Pick any type. It doesn't really matter.

2. Add an extension target. Xcode -> File -> New -> Target. Pick Application Extension on the left column and then Call Directory Extension.

One thing to note here. This extension and the app must be 64-bit only. That means this will only run on an iPhone 5s or later. (No iPhone 5 or iPhone 5c)

3. Open up the extension that will be CallDirectoryHandler.m by default. Edit the last two methods to insert the directory information that you want. I did all my code in Objective-C.

- (NSArray *) retrievePhoneNumbersToIdentify {
    // retrieve list of phone numbers to identify
    // good formats: "+ 18010001111", 18010001111, 1(801)0001111, 1(801)000-1111, 1 801 000 1111
    // bad formats: 8010001111, 0001111
    return @[@"18010001111"];
}

- (NSArray *) retrievePhoneNumberIdentificationLabels {
    // retrieve list of phone numbers identification labels
    return @[@"Johnny Appleseed"];
}

4. Run the app on your iPhone

5. Switch over to the iOS Settings App and go to Phone -> Call Blocking & Identification and give permission to use your app.

6. Make your test phone call


In my experimenting, I learned a few key things.

a) You must switch the permissions for the app off and back on every time you run the app in order to force an update of the data.
b) You must provide a full country code and area code with the phone number. Look at my sample code for examples of what works and what does not work.
c) You do NOT need to have an entry in the call blocking list to get the call directory bits to work.
d) Of course, if you already have a contact in Apple's Contacts app with the incoming phone number, that gets priority.
e) If Apple has found the incoming phone number somewhere already and wants to give you a "maybe" suggestion, that gets priority over your app. What that meant for me is that deleting my home phone number from all my contacts was not good enough for testing. I had to get more creative with coming up with a test phone number.
f) The label text that you see will have the following format: Caller ID:  You can change the app name part of the label by changing app's Bundle Name.


Problems

There are a few problems with this API as I see it. First, there is no explanation or description with the api! It would be very helpful if Apple had described a bit how the lifecycle of the extension works.

For example, when does the extension get queried for its list of phone numbers? Switching the permission for the app on and off seems to work. But that really isn't practical for users, is it. I'm assuming that Apple doesn't expect the customer to go over to the settings app and flip that switch after every time the app runs and updates the phone list.

Is there a permission api that can be checked to see if the user had turned off access to extension? It would be useful to know if the extension was not being used, and therefore didn't need to keep the data up to date.

I'm going to keep working on this and see if I can come up with something useful.

7 comments:

  1. The CXCallDirectoryManager class in CallKit.framework offers two key related APIs which you may call from your iOS app:

    -reloadExtensionWithIdentifier:completionHandler:, which requests to have your app extension's data reloaded by the system. Call this whenever you have new blocking or identification data to provide to the system.

    -getEnabledStatusForExtensionWithIdentifier:completionHandler:, which allows your app to query whether its extension has been enabled in Settings. Call this from within your app before requesting to reload data, and perhaps prompt the user to first enable your extension in Settings in order to begin loading data.

    ReplyDelete
  2. This comment has been removed by the author.

    ReplyDelete
  3. This comment has been removed by the author.

    ReplyDelete
  4. Would u mind sharing your full code (github?)?

    ReplyDelete
  5. Sample Code: https://developer.apple.com/library/content/samplecode/Speakerbox/History/History.html#//apple_ref/doc/uid/TP40017290-RevisionHistory-DontLinkElementID_1

    ReplyDelete
  6. i am trying to block.
    reloadExtensionWithIdentifier:completionHandler: is not working when input number provided by text field.

    ReplyDelete
  7. I am trying to block contacts & checked speaker box provided by apple also but not blocking calls please help me.... how can I do it

    ReplyDelete