Home

April 11th, 2014 by Blake Lucchesi

Digging deeper into iOS Local and Push Notifications

This week at SendHub I spent a lot of time trying to fine-tune the way our app alerts users when someone is calling them. The goal is to provide our users with an experience that matches the default iPhone ringing experience as close as possible.

Matching the Native Phone Ringing Experience on iOS Means:

  1. The phone should wake up and show an alert when a call comes in
  2. Swiping the alert should open our app so the user can answer or dismiss the call
  3. Our app plays an audible ringing if the phone is not set to silent (via the hardware mute button)
  4. If the phone is set to silent, it will show the alert and vibrate instead of playing the audible ring
  5. Tapping one of the volume buttons or the power button will mute the ringing sound
  6. After answering the call, the ringing noise should stop so that it doesn’t interrupt the phone call
  7. If the caller ends the call before its answered, we should stop the alert


#1 The phone should wake up and show an alert when a call comes in

Apple gives us two options for showing alerts on the iPhone when our application is in the background. The first is a Push Notification, a message sent via network connection from our server, to Apple’s servers and then to the iPhone. The other option is a Local Notification (UILocalNotification), initiated by code running inside our application. While either of these two methods will work, we’d actually prefer to use a local notification for a few reasons but the main one is timely delivery.

Being a registered VoIP application, we already have a low latency connection between our server and our app that we can use to trigger a local notification. This would be much faster and more reliable than push notifications which have no guarantee of delivery and can be delayed to conserve network activity.

#2 Swiping the alert should open our app so the user can answer or dismiss the call

Both Push and Local Notifications give us the ability to accomplish this. So it’s a push! (I know, I couldn’t help it).1

#3 Our app plays an audible ringing if the phone is not set to silent (via the hardware mute button)

Because we’re registered as a VoIP app, iOS allows us to play audio while the app is in the background via CoreAudio. So at first, this seemed like a great way to make our phone ring. We would have full control of when we start and stop playing the sound file; we could even determine how loud we want to play it. However, it falls short for us in a few critical areas as outlined in #4 and #5 below. Therefore, we go back to the Push vs Local notification decision because they both allow us to play an audio file bundled with our application.

#4 If the phone is set to silent, it will show the alert and vibrate instead of playing the audible ring

Apple has done us a favor here by providing us with only a single method to vibrate the phone.2 The approach is to use a notification with a sound. By specifying a sound file as part of the notification, iOS will determine whether it should play the audio or vibrate the phone. This is determined by the position of the hardware mute button on the side fo the phone.

#5 Tapping one of the volume buttons or the power button will mute the ringing sound

If we use a push or local notification to play the incoming ring audio, iOS adheres to the default muting behavior automatically. This means that a simple tap on one of the volume buttons or the power button will silence the incoming ring sound. If we had tried to play our incoming ring sound using CoreAudio the user would have to hold the volume down button much longer until the volume was completely muted, just as if they were listening to their music using Pandora or Spotify.

#6 After answering the call, the ringing noise should stop so that it doesn’t interrupt the phone call

Once a push notification has been received by the operating system, there is no way for us to stop the audio played along with the notification. I thought that I might be able to trick the phone into canceling previous notifications from our app by sending another notification with the badge key set to 0, but my efforts were fruitless.

Luckily, if we generate the notification via UILocalNotification we have the flexibility to stop specific notifications or all the local notifications we’ve initiated using either of the two methods supplied via UIApplication.

And when local notifications are cancelled via those methods, the banner alert disappears and the audio silences immediately. Exactly what we’re looking for.

#7 If the caller ends the call before its answered, we should stop the alert

Just like above in #6 we must use a local notification so that we can stop the ringing sound before the end of the sound clip.

Summary

After a week of ups and downs playing with the different Apple APIs I’m quite satisfied with the results of my work. As we look back through our list of desired behaviors it becomes quite clear that using a single Local Notification provides our users with a very nice incoming call experience.

Update May 11, 2014:

Added a few inline footnotes with corrections based on more investigation. My overall approach has gone unchanged but I thought it would be important to clarify a few points that I originally made.

Footnotes:


  1. After doing some more thorough testing, there appears to be a bug in iOS 7. When you swipe/act on a local notification from the lock screen the audible sound that is played with the local notification cannot be cut short. It will continue playing until the whole audio clip is played. This bug occurs in many other VoIP applications I’ve tested as well (Skype, Google Hangouts, etc). This results in a short overlap of the ringing sound and the phone call, which is certainly less than ideal. See full details from the radar report I filed.

  2. After digging in a bit further I was able to find a way to make the phone vibrate using the System Sound Services located in the AudioToolbox framework. AudioServicesPlaySystemSound(kSystemSoundID_Vibrate);

comments powered by Disqus