After iOS18.0 rejects the call, CXCall's hasConnected is YES, which should be NO.

Dear Apple R&D Team, I want to report a bug of CallKit caused by Voicemail in iOS18.0.

The following are the header files referenced by our code:

#import <CallKit/CXCallObserver.h>
#import <CallKit/CXCall.h>

Our VoIP app uses the callObserver callback function provided by CXCallObserverDelegate to monitor changes in the system phone status (see the attached picture). The problems we encountered are:

  • Before upgrading to iOS18.0, when we rejected a call, we would receive a callObserver callback, and the status value of hasConnected of CXCall was NO;

  • After upgrading to iOS18.0, when we also rejected a call, the status value of hasConnected of CXCall was YES.

So we checked the new features of iOS18.0 and found that it was the influence of the new feature Voicemail.

On iOS18.0 devices, if you reject a call, you will enter the Voicemail state by default, which means that I rejected the call, but callObserver told me that the call was connected, which is inconsistent with the user's intuitive experience and will also cause our App to respond incorrectly (cannot resume audio automatically).

In fact, the Voicemail function is enabled by default for devices upgraded to iOS 18.0. After I reject a call, I have to wait for the caller to hang up before I can receive callObserver telling me that the call has been hung up. This experience is very bad, because when the caller does not hang up the call, our App cannot sense that I have hung up the call and thinks that I am still on the call, but in fact I did hang up the call.

Please refer to the code in the attached picture. According to our understanding, the status should flow like this:

  1. When I receive an incoming call, the callObserver callback will trigger, call.hasConnected is NO and call.hasEnded is NO;

  2. When I reject the call and switch to Voicemail, the callObserver callback will trigger, call.hasConnected should be NO and call.hasEnded should be YES;

  3. When I manually restore the call through Voicemail, the callObserver callback will trigger, call.hasConnected should be YES and call.hasEnded should be NO;

  4. When the caller or I finally hang up the call, the callObserver callback will trigger, call.hasConnected is YES, call.hasEnded is YES;

However, the current status is as follows:

  1. When I receive an incoming call, the callObserver callback will trigger, call.hasConnected is NO and call.hasEnded is NO;

  2. When I reject the call and switch to Voicemail, the callObserver callback will trigger, call.hasConnected is YES and call.hasEnded is NO;

  3. When I manually restore the call through Voicemail, the callObserver callback will not trigger;

  4. Only when the caller finally hangs up, the callObserver callback will trigger, call.hasConnected is YES and call.hasEnded is YES;

Our request is very simple. We need to pause audio when a call comes in and resume audio when the call is hung up or rejected. This is our sample code:

- (void)callObserver:(CXCallObserver *)callObserver callChanged:(CXCall *)call  API_AVAILABLE(ios(10.0))
{
  bool pause_audio = false;
  
  BOOL calling      = !call.onHold && !call.hasConnected && !call.hasEnded;
  BOOL disconnected = !call.onHold && !call.hasConnected && call.hasEnded;
  BOOL connected    = !call.onHold && call.hasConnected && !call.hasEnded;
  BOOL hang_up      = !call.onHold && call.hasConnected && call.hasEnded;

  if (calling) {
    xc_log(XC_LOG_INFO, "A phone call is %s", call.outgoing ? "outgoing" : "incoming");
    pause_audio = true;
  } else if (disconnected) {
    xc_log(XC_LOG_INFO, "An %s phone call has been disconnected", call.outgoing ? "outgoing" : "incoming");
    // action of phone call end, resume audio
  } else if (connected) {
    xc_log(XC_LOG_INFO, "An %s phone call has just been connected", call.outgoing ? "outgoing" : "incoming");
    pause_audio = true;
  } else if (hang_up) {
    xc_log(XC_LOG_INFO, "An %s phone call has been hang up", call.outgoing ? "outgoing" : "incoming");
    // action of phone call end, resume audio
  } else {
    xc_log(XC_LOG_INFO, "Unknown telephony state occured");
  }

  if (pause_audio) {
    dispatch_async(dispatch_get_main_queue(), ^{
      // action of phone call begin, pause audio
    });
  }
}

I would appreciate any help !!!

After iOS18.0 rejects the call, CXCall's hasConnected is YES, which should be NO.
 
 
Q