Siri Integration: Audio call using Siri?

2k views Asked by At

I need to integrate Siri to make a calls using my app. Siri recognises my app on App Support on Settings. But when Im trying to call some contact saying "call ContactName on MyApp" it just shows me button "open MyApp". IntentHandler.swift contains this function:

class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchForMessagesIntentHandling, INSetMessageAttributeIntentHandling, INStartAudioCallIntentHandling {

    override func handler(for intent: INIntent) -> Any {
        // This is the default implementation.  If you want different objects to handle different intents,
        // you can override this and return the handler you want for that particular intent.

        return self
    }

    func handle(startAudioCall intent: INStartAudioCallIntent, completion: @escaping (INStartAudioCallIntentResponse) -> Swift.Void) {
        let response: INStartAudioCallIntentResponse
        defer {
            completion(response)
        }

        let contacts = (intent.contacts ?? []).filter({ (contact) -> Bool in
            return contact.personHandle?.type == .phoneNumber && contact.personHandle?.value != nil
        })
        // Ensure there is a contact and a handle
        guard contacts.count > 0 else {
            response = INStartAudioCallIntentResponse(code: .failure, userActivity: nil)
            return
        }

        let userActivity = NSUserActivity(activityType: "INStartAudioCallIntent")

        response = INStartAudioCallIntentResponse(code: .continueInApp, userActivity: userActivity)
        completion(response)

    }

    func resolveContacts(forStartAudioCall intent: INStartAudioCallIntent, with completion: @escaping ([INPersonResolutionResult]) -> Void) {
        var resolutionResults = [INPersonResolutionResult]()

        if let recipients = intent.contacts {
            if recipients.count == 0 {
                let response = [INPersonResolutionResult.needsValue()]
                completion(response)
                return
            } else if recipients.count == 1 {
                if let recipient = recipients.first {
                    if self.containContact(displayName: (recipient.displayName)) {
                        resolutionResults.append(INPersonResolutionResult.success(with: recipient))
                    } else {
                        resolutionResults.append(INPersonResolutionResult.unsupported())
                    }
                }
            } else if recipients.count > 1 {
                resolutionResults.append(INPersonResolutionResult.disambiguation(with: recipients))

            } else {
                resolutionResults.append(INPersonResolutionResult.unsupported())
            }
        }
        completion(resolutionResults)
    }
    func confirm(startAudioCall intent: INStartAudioCallIntent, completion: @escaping (INStartAudioCallIntentResponse) -> Swift.Void) {
        let userActivity = NSUserActivity(activityType: "INStartAudioCallIntent")
        let response = INStartAudioCallIntentResponse(code: .ready, userActivity: userActivity)
        completion(response)

    }

    // MARK: - INSendMessageIntentHandling

    // Implement resolution methods to provide additional information about your intent (optional).


    func resolveRecipients(forSendMessage intent: INSendMessageIntent, with completion: @escaping ([INPersonResolutionResult]) -> Void) {
        if let recipients = intent.recipients {

            // If no recipients were provided we'll need to prompt for a value.
            if recipients.count == 0 {
                completion([INPersonResolutionResult.needsValue()])
                return
            }

            var resolutionResults = [INPersonResolutionResult]()
            for recipient in recipients {
                let matchingContacts = [recipient] // Implement your contact matching logic here to create an array of matching contacts
                switch matchingContacts.count {
                case 2  ... Int.max:
                    // We need Siri's help to ask user to pick one from the matches.
                    resolutionResults += [INPersonResolutionResult.disambiguation(with: matchingContacts)]

                case 1:
                    // We have exactly one matching contact
                    resolutionResults += [INPersonResolutionResult.success(with: recipient)]

                case 0:
                    // We have no contacts matching the description provided
                    resolutionResults += [INPersonResolutionResult.unsupported()]

                default:
                    break

                }
            }
            completion(resolutionResults)
        }
    }

    func resolveContent(forSendMessage intent: INSendMessageIntent, with completion: @escaping (INStringResolutionResult) -> Void) {
        if let text = intent.content, !text.isEmpty {
            completion(INStringResolutionResult.success(with: text))
        } else {
            completion(INStringResolutionResult.needsValue())
        }
    }

    // Once resolution is completed, perform validation on the intent and provide confirmation (optional).

    func confirm(sendMessage intent: INSendMessageIntent, completion: @escaping (INSendMessageIntentResponse) -> Void) {
        // Verify user is authenticated and your app is ready to send a message.

        let userActivity = NSUserActivity(activityType: NSStringFromClass(INSendMessageIntent.self))
        let response = INSendMessageIntentResponse(code: .ready, userActivity: userActivity)
        completion(response)
    }

    // Handle the completed intent (required).

    func handle(sendMessage intent: INSendMessageIntent, completion: @escaping (INSendMessageIntentResponse) -> Void) {
        // Implement your application logic to send a message here.

        let userActivity = NSUserActivity(activityType: NSStringFromClass(INSendMessageIntent.self))
        let response = INSendMessageIntentResponse(code: .success, userActivity: userActivity)
        completion(response)
    }

    // Implement handlers for each intent you wish to handle.  As an example for messages, you may wish to also handle searchForMessages and setMessageAttributes.

    // MARK: - INSearchForMessagesIntentHandling

    func handle(searchForMessages intent: INSearchForMessagesIntent, completion: @escaping (INSearchForMessagesIntentResponse) -> Void) {
        // Implement your application logic to find a message that matches the information in the intent.

        let userActivity = NSUserActivity(activityType: NSStringFromClass(INSearchForMessagesIntent.self))
        let response = INSearchForMessagesIntentResponse(code: .success, userActivity: userActivity)
        // Initialize with found message's attributes
        response.messages = [INMessage(
            identifier: "identifier",
            content: "I am so excited about SiriKit!",
            dateSent: Date(),
            sender: INPerson(personHandle: INPersonHandle(value: "[email protected]", type: .emailAddress), nameComponents: nil, displayName: "Sarah", image: nil,  contactIdentifier: nil, customIdentifier: nil),
            recipients: [INPerson(personHandle: INPersonHandle(value: "+1-415-555-5555", type: .phoneNumber), nameComponents: nil, displayName: "John", image: nil,  contactIdentifier: nil, customIdentifier: nil)]
            )]
        completion(response)
    }

    // MARK: - INSetMessageAttributeIntentHandling

    func handle(setMessageAttribute intent: INSetMessageAttributeIntent, completion: @escaping (INSetMessageAttributeIntentResponse) -> Void) {
        // Implement your application logic to set the message attribute here.

        let userActivity = NSUserActivity(activityType: NSStringFromClass(INSetMessageAttributeIntent.self))
        let response = INSetMessageAttributeIntentResponse(code: .success, userActivity: userActivity)
        completion(response)
    }

    func containContact(displayName: String) -> Bool {
        //fetch contacts and check, if exist retun YES else NO
        return true
    }
}
}

        let contacts = (intent.contacts ?? []).filter({ (contact) -> Bool in
            return contact.personHandle?.type == .phoneNumber && contact.personHandle?.value != nil
        })
        // Ensure there is a contact and a handle
        guard contacts.count > 0 else {
            response = INStartAudioCallIntentResponse(code: .failure, userActivity: nil)
            return
        }

        let userActivity = NSUserActivity(activityType: "INStartAudioCallIntent")

        response = INStartAudioCallIntentResponse(code: .continueInApp, userActivity: userActivity)

    }

What I'm doing wrong? what I'm missing?

1

There are 1 answers

5
makboney On BEST ANSWER

Please check the followings:

Set INStartAudioCallIntent for IntentsSupported in Extension's target.

enter image description here

Confirm INStartAudioCallIntentHandling protocol in IntentHandler class provided by default and implement the following methods:

-(void)resolveContactsForStartAudioCall:(INStartAudioCallIntent *)intent
                          withCompletion:(void (^)(NSArray<INPersonResolutionResult *> *resolutionResults))completion{
    NSArray<INPerson *> *recipients = intent.contacts;
    NSMutableArray<INPersonResolutionResult *> *resolutionResults = [NSMutableArray array];
    if (recipients.count == 0) {
        completion(@[[INPersonResolutionResult needsValue]]);
        return;
    }else if(recipients.count==1){
        if ([self contactExist:recipients.firstObject.displayName]) {// check if person contains in contact or not
            [resolutionResults addObject:[INPersonResolutionResult successWithResolvedPerson:recipients.firstObject]];
        }else [resolutionResults addObject:[INPersonResolutionResult unsupported]];
    }else if(recipients.count>1){
        [resolutionResults addObject:[INPersonResolutionResult disambiguationWithPeopleToDisambiguate:recipients]];
    }else{
        [resolutionResults addObject:[INPersonResolutionResult unsupported]];
    }
    completion(resolutionResults);
}

-(void)confirmStartAudioCall:(INStartAudioCallIntent *)intent
                   completion:(void (^)(INStartAudioCallIntentResponse *response))completion{
    NSUserActivity *userActivity = [[NSUserActivity alloc] initWithActivityType:NSStringFromClass([INStartAudioCallIntent class])];
    INStartAudioCallIntentResponse *response = [[INStartAudioCallIntentResponse alloc] initWithCode:INStartAudioCallIntentResponseCodeReady userActivity:userActivity];
    completion(response);
}

-(void)handleStartAudioCall:(INStartAudioCallIntent *)intent
                  completion:(void (^)(INStartAudioCallIntentResponse *response))completion{
    NSUserActivity *userActivity = [[NSUserActivity alloc] initWithActivityType:NSStringFromClass([INStartAudioCallIntent class])];
    INStartAudioCallIntentResponse *response = [[INStartAudioCallIntentResponse alloc] initWithCode:INStartAudioCallIntentResponseCodeContinueInApp userActivity:userActivity];
    completion(response);
}

You may need to share contacts of main applciaiton with extension, use App Grouping. Follow apple document for more information.

Make sure extension target is supporting your device OS.

enter image description here