Sergey Aslanov
Sergey Aslanov

Reputation: 713

Microsoft Teams incoming call event

I would like to create a app which will be informed when a user gets a call in MS Teams. I mean I want to subscribe something on event of incoming call and then do something based on information of incoming call. Is this possible? So far I do not see any events in SDK.

Upvotes: 9

Views: 4853

Answers (2)

final
final

Reputation: 233

I investigated this issue for three days or so. These are my findings:

  • The MS Graph API is too slow. See https://learn.microsoft.com/en-us/graph/webhooks#latency. callRecord Notifications have a guaranteed latency of <60 minutes. Also, callRecords are only created after the call has finished, so they're useless for incoming calls.
  • I didn't want to write a MS Teams bot for this. I don't want my code to sit between each and every call just to get some information. Also, I think that bots would not work for calling a user's personal number, only for service accounts (Call queues / Auto Attendants).
  • The MS Teams client (I only checked on Windows) does not write the phone number into any file before the call is answered. By watching storage.json, you can figure out more or less reliable whether the phone is currently ringing, but without the calling number.
  • The indexedDB cache files eventually contain the calling number, but only once the call is answered. Also, I didn't find a library to read IndexedDB files that are on disk.
  • I did not find any third party software that could do this. Some paid Team apps can do this for calls to service accounts (e.g. Landis Contact Center)

The only thing I could come up with was to read the text out of the notification window itself. After a lot of trial, error and pain, I managed to get this:

//needs a COM reference to UIAutomationClient
//using UIAutomationClient;


[DllImport("User32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern bool IsWindowVisible(IntPtr hWnd);
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool IsWindow(IntPtr hWnd);
[DllImport("user32.dll", EntryPoint = "FindWindow", SetLastError = true)]
internal static extern IntPtr FindWindowByCaption(IntPtr ZeroOnly, string lpWindowName);

public void GetTeamsCallNotifications() {
    do {
        var teamsNotificationWindowHandle = FindWindowByCaption(IntPtr.Zero, "Microsoft Teams Notification");
        try {
            var pUIAutomation = new CUIAutomation();
            var windowElement = pUIAutomation.ElementFromHandle(teamsNotificationWindowHandle);
            var trueCond = pUIAutomation.CreateTrueCondition();
            while (IsWindow(teamsNotificationWindowHandle)) {
                //incoming call window has: two buttons (type 50000), a ISO phone number in a group-field (50026), and no text field (50020)
                if (IsWindowVisible(teamsNotificationWindowHandle)) {
                    var elementArray = windowElement.FindAll(TreeScope.TreeScope_Descendants, trueCond);
                    string number = "";
                    int noButtonsFound = 0;
                    var debugFields = new List<string>();
                    for (int i = 0; i < elementArray.Length; i++)
                    {
                        var element = elementArray.GetElement(i);
                        debugFields.Add($"{element.CurrentControlType}={element.CurrentName}");
                        if (element.CurrentControlType == 50000)
                            noButtonsFound++;
                        if (element.CurrentControlType == 50026 && System.Text.RegularExpressions.Regex.IsMatch(element.CurrentName, @"^\+[1-9][0-9 ]+$"))
                            number = element.CurrentName.Replace(" ", "");
                    }
                    Debug.WriteLine(string.Join(";", debugFields) + "\r\n");
                    if (noButtonsFound == 2 && !string.IsNullOrEmpty(number))
                        Console.WriteLine(number + " is ringing");
                }
                Thread.Sleep(500);
            }
        }
        catch { }
        Thread.Sleep(5000); //Teams is probably closed, need a new window handle
    } while (true);
}

Some comments:

  • The Teams Notification Window always exists when MS Teams runs, it's just either hidden or visible
  • There only ever exists one Teams Notification Window, even when multiple notifications are shown. windowElement.FindAll will give you all the elements of all notifications.
  • The code is pretty light-weight

Limitations of this code:

  • Windows only, not centralized (i.e. needs to run on every client)
  • A change in the layout of MS Teams could break it
  • It's unknown whether the call was answered or not.
  • A second notification of any kind will break it temporarily (until the second notification disappears).

You can improve on the last limitation if you're willing to accept other limitation. For instance, you can just search all text fields for a phone number. But then the code will trigger if someone sends you a text message containing a phone number. Or you can find the call notification pretty reliably if you know the display language of the Teams client by looking at the caption of the answer / decline buttons.

Upvotes: 2

John
John

Reputation: 30586

There seems to now be a feature that may suit this.

Call records to provide usage and diagnostic information about the calls and online meetings that occur within your organization when using Microsoft Teams or Skype for Business.
...

Using webhooks and the MS Graph Subscription API, you can receive a continuous feed of call records as they are created.

Upvotes: 2

Related Questions