Pushkit/Callkit with unlocked SIM before first unlock

We have a problem in a scenario that SIM lock is disabled so after a phone reboots it has the Internet connection but it is still locked.

When you call into the VOIP app the app is not being launched as the result (it seems reasonable because it wouldn't be able to access the keychain items etc...) but the OS still seem to enforce the rule that the app needs to report the new incoming call.

When we then unlock the app we can see no more pushkit pushes are arriving (dropped on the floor in the console) but we get the three initial pushes that were send during the locked phase right after the app launch.

Answered by DTS Engineer in 840582022

We have a problem in a scenario that SIM lock is disabled so after a phone reboots it has the Internet connection but it is still locked.

When you call into the VOIP app the app is not being launched as the result (it seems reasonable because it wouldn't be able to access the keychain items etc...)

So first off, yes, this can happen. There are a small number of edge cases (one of which is voip) where the system may launch the app prior to first unlock. This behavior has never been documented and can be somewhat inconsistent, however, it's is also VERY old (it comes the original, iOS 4, voip architecture). Because of that, my advice to voip developers has long been:

  • Don't assume/rely on it always happening. As you've noted the behavior is somewhat odd and there are situations where it will not occur.

  • Be prepared for it to happen and handle it as "gracefully" as you can/choose. Depending on your app architecture, that could mean handling the call completely or simply reporting and then ending the call.

One note on this point:

(it seems reasonable because it wouldn't be able to access the keychain items etc...)

That is not correct. The keychain provides the "Always" accessibility attribute, the point of which is specifically to allow access prior to first unlock. While that attribute is marked as deprecated, that's primarily to highlight that it should not be used for most cases, not to warn of it's active removal*. Also, note that URLFileProtection.none allows file to be accessed prior to first unlock.

*In order to remove the key, we'd need to actively prevent exactly the case we're discussing here... at which point you wouldn't need the key.

The big issue to be aware of for this edge case is that your app can ONLY rely on accessing data (files or keys) that is directly controls and has specifically marked (using the APIs above). Note that specifically means that you CANNOT use NSUserDefaults.

Moving to here:

but the OS still seem to enforce the rule that the app needs to report the new incoming call.

While bugs are always possible, I don't think that's what happening here. In every case I've looked at, what actually happened is that your app WAS launched but issue with it's internal logic meant that it never created a PKPushRegistry object, so the system then terminated it a short time (~7s) later.

In terms of next steps, here is what I would actually recommend:

  • Install the FaceTime and APNs debug profiles on your test device. This isn't absolutely necessary but there's no reason not to get the best possible data from the beginning. More to the point, IF this is a bug you don't want to end up needing to run the test all over again to get the data the engineering team needs.

  • Turn your device completely off and then leave it off for "awhile", the longer the better (this is a great time to go eat lunch). From past experience, part of what can make sysdiagnose analysis difficult is clearly differentiating different launch cycle from each other, so the goal of the long delay here is simply to create a time gap in the log so it's easy to see when the device started up.

  • Turn the device on and reproduce the issue "a few times", making a note of the time you performed each test. In your case, I would first call the device (to confirm cell connectivity) without answering (to avoid unlock), then send a series of pushes space 1-2 minutes apart.

  • After your last push wait a minutes or so, then unlock the device and open your app to validate the multiple delivery issue you described.

  • Wait "awhile" (2-3 minutes), then trigger and collect a sysdiagnose using the directions from either of the profile installation instructions.

Once you've got a sysdiagnose, there are a few things I'd look at:

(1) Check for crashs from your app in the crashes_and_spins directory. Note that the absence of logs does not necessarily your app was not launched or terminated, as there are other edge cases/complications that can prevent collection.

(2) Take a look at the log yourself. I can't easily summarize how to do this kind of analysis, however, there's a forum thread here that's includes examples of my own analysis. Here are a few other suggestions/tips:

  • It's easy to overlook, Console.app has a popup button in the bottom left corner than controls the time window your actually looking at in the log. Make sure you extend that to cover the whole test window.

  • The critical daemons to this process are "apsd" (handles APNS messages), "callservicesd" (manages PushKit and CallKit), and "runningboardd" (launches/manages apps on behalf of other daemons).

  • Searching for your apps bundle ID can be a good way to quickly filter down to the messages that are actually relevant to your app.

  • Combining those two points, the "flow" you'll often use is to use your bundle id to find time points that might be interesting, then looking at the messages before and after that point to see what actually happened.

(3) If you've looked over the log and you haven't found an answer or think this could be a bug, then please file a bug on this and post the bug number back here. Once I've got the number, I'll take a look and see if I can figure out what's going on.

Finally, a quick explanation on what's happening here:

When we then unlock the app we can see no more pushkit pushes are arriving (dropped on the floor in the console) but we get the three initial pushes that were send during the locked phase right after the app launch.

What's going on here is a side effect of the architecture pattern callservicesd (and many of other daemons) use to deliver messages. Here is what's actually going on:

  1. Push arrives to apsd. apsd passes that push to callservicesd.

  2. callservicesd creates the object it uses to track and manage it's interaction with "you". Note that is NOT tied to a specific launch of your app.

  3. callservicesd adds the new push to the list of pushes it's managing for your app.

  4. callservicesd notices that your app is not running and asks runningboardd to launch your app.

  5. Your app launches and create it's PKPushRegistry object. That object creates an XPC connection to callservicesd.

  6. callservicesd accepts the connection, ties that new connection to the object in #2, and then delivers all the messages it queued up.

That sequence if what can cause message to "pile up" the way you're describing.

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

We have a problem in a scenario that SIM lock is disabled so after a phone reboots it has the Internet connection but it is still locked.

When you call into the VOIP app the app is not being launched as the result (it seems reasonable because it wouldn't be able to access the keychain items etc...)

So first off, yes, this can happen. There are a small number of edge cases (one of which is voip) where the system may launch the app prior to first unlock. This behavior has never been documented and can be somewhat inconsistent, however, it's is also VERY old (it comes the original, iOS 4, voip architecture). Because of that, my advice to voip developers has long been:

  • Don't assume/rely on it always happening. As you've noted the behavior is somewhat odd and there are situations where it will not occur.

  • Be prepared for it to happen and handle it as "gracefully" as you can/choose. Depending on your app architecture, that could mean handling the call completely or simply reporting and then ending the call.

One note on this point:

(it seems reasonable because it wouldn't be able to access the keychain items etc...)

That is not correct. The keychain provides the "Always" accessibility attribute, the point of which is specifically to allow access prior to first unlock. While that attribute is marked as deprecated, that's primarily to highlight that it should not be used for most cases, not to warn of it's active removal*. Also, note that URLFileProtection.none allows file to be accessed prior to first unlock.

*In order to remove the key, we'd need to actively prevent exactly the case we're discussing here... at which point you wouldn't need the key.

The big issue to be aware of for this edge case is that your app can ONLY rely on accessing data (files or keys) that is directly controls and has specifically marked (using the APIs above). Note that specifically means that you CANNOT use NSUserDefaults.

Moving to here:

but the OS still seem to enforce the rule that the app needs to report the new incoming call.

While bugs are always possible, I don't think that's what happening here. In every case I've looked at, what actually happened is that your app WAS launched but issue with it's internal logic meant that it never created a PKPushRegistry object, so the system then terminated it a short time (~7s) later.

In terms of next steps, here is what I would actually recommend:

  • Install the FaceTime and APNs debug profiles on your test device. This isn't absolutely necessary but there's no reason not to get the best possible data from the beginning. More to the point, IF this is a bug you don't want to end up needing to run the test all over again to get the data the engineering team needs.

  • Turn your device completely off and then leave it off for "awhile", the longer the better (this is a great time to go eat lunch). From past experience, part of what can make sysdiagnose analysis difficult is clearly differentiating different launch cycle from each other, so the goal of the long delay here is simply to create a time gap in the log so it's easy to see when the device started up.

  • Turn the device on and reproduce the issue "a few times", making a note of the time you performed each test. In your case, I would first call the device (to confirm cell connectivity) without answering (to avoid unlock), then send a series of pushes space 1-2 minutes apart.

  • After your last push wait a minutes or so, then unlock the device and open your app to validate the multiple delivery issue you described.

  • Wait "awhile" (2-3 minutes), then trigger and collect a sysdiagnose using the directions from either of the profile installation instructions.

Once you've got a sysdiagnose, there are a few things I'd look at:

(1) Check for crashs from your app in the crashes_and_spins directory. Note that the absence of logs does not necessarily your app was not launched or terminated, as there are other edge cases/complications that can prevent collection.

(2) Take a look at the log yourself. I can't easily summarize how to do this kind of analysis, however, there's a forum thread here that's includes examples of my own analysis. Here are a few other suggestions/tips:

  • It's easy to overlook, Console.app has a popup button in the bottom left corner than controls the time window your actually looking at in the log. Make sure you extend that to cover the whole test window.

  • The critical daemons to this process are "apsd" (handles APNS messages), "callservicesd" (manages PushKit and CallKit), and "runningboardd" (launches/manages apps on behalf of other daemons).

  • Searching for your apps bundle ID can be a good way to quickly filter down to the messages that are actually relevant to your app.

  • Combining those two points, the "flow" you'll often use is to use your bundle id to find time points that might be interesting, then looking at the messages before and after that point to see what actually happened.

(3) If you've looked over the log and you haven't found an answer or think this could be a bug, then please file a bug on this and post the bug number back here. Once I've got the number, I'll take a look and see if I can figure out what's going on.

Finally, a quick explanation on what's happening here:

When we then unlock the app we can see no more pushkit pushes are arriving (dropped on the floor in the console) but we get the three initial pushes that were send during the locked phase right after the app launch.

What's going on here is a side effect of the architecture pattern callservicesd (and many of other daemons) use to deliver messages. Here is what's actually going on:

  1. Push arrives to apsd. apsd passes that push to callservicesd.

  2. callservicesd creates the object it uses to track and manage it's interaction with "you". Note that is NOT tied to a specific launch of your app.

  3. callservicesd adds the new push to the list of pushes it's managing for your app.

  4. callservicesd notices that your app is not running and asks runningboardd to launch your app.

  5. Your app launches and create it's PKPushRegistry object. That object creates an XPC connection to callservicesd.

  6. callservicesd accepts the connection, ties that new connection to the object in #2, and then delivers all the messages it queued up.

That sequence if what can cause message to "pile up" the way you're describing.

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

I tried to install the profiles, but they both seem to be the same profile - Facetime and Call Activity Logging

I turned off the phone around 19:50 CET then 20:00:00 Boot 20:01:00 Push 1 20:02:00 Push 2 20:03:00 Push 3 20:04:00 Push 4 20:05:30 Unlock 20:07:00 Push 5 20:08:00 Launch app and run sysdiagnose

from what I can see in my application log it was only launched at 20:08:02 and then almost immediately (1s or so) it got 3 voip pushes which we reported to callkit immediately. It was never launched to the background because for this we fire a local notification in the debug build and it wasn't shown

No other voip pushes has been received and no new crashlogs seem to be on the device

What is the recommended way to detect that we are launched before first unlock? Store an item in the keychain with Always flag and one with AfterFirstUnlock and check for their existence upon launch?

What determines if the VOIP app is launched before first unlock or not because it seems that some apps are being launched as the result of the voip push and some are not.

actually, when checking crashes_and_spins there are crashes 20:01, 20:02 and 20:03... my bad

I tried to install the profiles, but they both seem to be the same profile - Facetime and Call Activity Logging

Yeah, that's fine. What actually happens here is that at some point in the past we had different profile the included slightly different logging behavior but at some point the team decided to just consolidate on to one profile for "everything". However, these profile are very widely used in very different context (for example, App Development vs. AppleCare), so it's easier to post the same profile with two names instead of trying to explain that one case should now use the "other" profile.

actually, when checking crashes_and_spins there are crashes 20:01, 20:02 and 20:03... my bad

Good. So, it sounds like the system side of this is working correctly.

What is the recommended way to detect that we are launched before first unlock?

So, my first and most important recommendation is that you NOT think about this issue in terms of any specific "state". Yes, that's what's triggering the specific failure here, but you'd be having exactly the same problem if an app configuration or keychain accessibility issue meant that your app couldn't get data it "needed". You should certainly include this as one of the cases you test, but the solution should NOT be focused on any particular edge case.

The right way to approach this is:

  1. Determine exactly what information your app REQUIRES to successfully handle a call.

  2. Set the accessibility of that data such that you can access it at whatever device state you want to support.

  3. Build your "base" call handling infrastructure such that it can meet the CallKit requirements without ANY of the information from #1. Typically, that means reporting a call and then failing that call (because it can't actually connect it).

If you want an example of why I recommend #3, take a look at this forum thread (and this is one among many). Reporting and then failing a call may not be ideal, but it's a LOT better than silently crashing a few times and then having your app stop working at all.

That leads to here:

Store an item in the keychain with Always flag and one with AfterFirstUnlock and check for their existence upon launch?

If you want to complete calls prior to first unlock then, yes, you'd store that data (#1) at "Always". However, it's also valid to keep the data as "AfterFirstUnlock" and automatically fallback on #3.

What determines if the VOIP app is launched before first unlock or not because it seems that some apps are being launched as the result of the voip push and some are not.

I'd have to look at the log to answer that. I'm not aware of the system making and specific "choice", however, there are lots of edge cases and details the can cause different behavior. For example, apsd manages it's connection to the production and sandbox servers as entirely separate implementation "stacks". In practice, that means development apps using the push sandbox tend to be somewhat less "reliable" than production apps as an indirect side effect of how apsd is managing the sandbox connection.

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

Pushkit/Callkit with unlocked SIM before first unlock
 
 
Q