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.
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.
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.
Related
Hello dear community I am teaching myself app programming via Sketchware.
I'm writing a GPS tracker that does the following.
The user sets a time after which the location data is sent as an SMS. Ex: send location data every 60 minutes.
The battery state of charge is constantly monitored and at 30 percent it sends an SMS to the user to draw attention to it.
My problem now is that no SMS is sent when the screen is off. However, the app does not have to be visible. I read a lot of reports about power management and try to get a PARTIAL_WAKE_LOCK all the time. No matter how I try, it always ends in an error. I would be very happy to get help with my problem.
private void initializeLogic() {
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "DoNotSleep");
wl.acquire();
setBG = 0;
VideoView.setVisibility(View.GONE);
setTitle("GPS - Tracker BETA BUILD");
ArabWare = ArabWare.getDefault();
if (geoData.getString("setFirstRun", "").equals("")) {
_setVarDefault();
}
else {
if (geoData.getString("setFirstRun", "").equals("1")) {
_setSplash();
_getStats();
_batState();
_lfzV14();
_testInfo();
}
}
geoData.edit().putString("setFinish", "0").commit();
}
It is not possible for me to implement the PowerManager in the onCreate in Sketchware. Even if I edit the code manually, it always ends up in private void initializeLogic() I'm not getting a wake lock. This instruction PowerManager.WakeLock wl; always leads to an error.
wl cannot be resolved to a variable
I want to send different vibrations to the watch depending on what message I am sending. I am open to use notifications or messages, but I want to control the vibration.
This is for a program so the subject knows if there is a positive response (say 1 quick vibration) or a negative response (a long vibration or two quick vibrations).
Can this be done? I essentially would want two buttons in an app sending different notifications with different vibrations to the watch.
Yes it's possible. Just tag your message or notification with an ID indicating the type of vibration you want to trigger. On the watch make sure to check the ID and create the appropriate vibration pattern.
It might look something like this in your wearable code:
// Get the ID from the message/notification
// Create the vibration pattern
long[] pattern;
if(id == POSITIVE) {
pattern = new long[]{0, 200, 50, 200};
} else if(id == NEGATIVE) {
pattern = new long[]{0, 50};
}
// Trigger the vibration
((Vibrator) getSystemService(VIBRATOR_SERVICE)).vibrate(pattern, -1);
Remember to declare the vibrate permission in your manifest file:
<uses-permission android:name="android.permission.VIBRATE"/>
I'm trying to spoof keystrokes; to be a bit more precise: I'm replaying a number of keystrokes which should all get sent at a certain time - sometimes several at the same time (or at least as close together as reasonably possible).
Implementing this using XTestFakeKeyEvent, I've come across a problem. While what I've written so far mostly works as it is intended and sends the events at the correct time, sometimes a number of them will fail. XTestFakeKeyEvent never returns zero (which would indicate failure), but these events never seem to reach the application I'm trying to send them to. I suspect that this might be due to the frequency of calls being too high (sometimes 100+/second) as it looks like it's more prone to fail when there's a large number of keystrokes/second.
A little program to illustrate what I'm doing, incomplete and without error checks for the sake of conciseness:
// #includes ...
struct action {
int time; // Time where this should be executed.
int down; // Keydown or keyup?
int code; // The VK to simulate the event for.
};
Display *display;
int nactions; // actions array length.
struct action *actions; // Array of actions we'll want to "execute".
int main(void)
{
display = XOpenDisplay(NULL);
nactions = get_actions(&actions);
int cur_time;
int cur_i = 0;
struct action *cur_action;
// While there's still actions to execute.
while (cur_i < nactions) {
cur_time = get_time();
cur_action = actions + cur_i;
// For each action that is (over)due.
while ((cur_action = actions + cur_i)->time <= cur_time) {
cur_i++;
XTestFakeKeyEvent(display, cur_action->code,
cur_action->down, CurrentTime);
XFlush(display);
}
// Sleep for 1ms.
nanosleep((struct timespec[]){{0, 1000000L}}, NULL);
}
}
I realize that the code above is very specific to my case, but I suspect that this is a broader problem - which is also why I'm asking this here.
Is there a limit to how often you can/should flush XEvents? Could the application I'm sending this to be the issue, maybe failing to read them quickly enough?
It's been a little while but after some tinkering, it turned out that my delay between key down and key up was simply too low. After setting it to 15ms the application registered the actions as keystrokes properly and (still) with very high accuracy.
I feel a little silly in retrospect, but I do feel like this might be something others could stumble over as well.
Using the Google CAF Receiver SDK, how do we prevent the receiver from timing out and automatically killing the cast session when we're not using the receiver player?
The standard Google Cast use case is to send media from a device to the cast receiver and have the receiver render the media using a player. The CAF receiver SDK provides this functionality in a beautiful, simple way using the element cast-media-player.
But for those instances when we want to cast from a device and render content where it's not relevant to use the cast-media-player (e.g. an HTML dashboard), how do we keep the receiver alive?
The following custom receiver for example (HAML for brevity), results in the cast session automatically terminating after 5 minutes...
!!! 5
%html
%head
:css
cast-media-player {
display: none;
}
= javascript_include_tag 'https://www.gstatic.com/cast/sdk/libs/caf_receiver/v3/cast_receiver_framework.js'
%body
%cast-media-player
:javascript
const context = cast.framework.CastReceiverContext.getInstance();
const player = context.getPlayerManager();
player.setMessageInterceptor(cast.framework.messages.MessageType.LOAD, loadRequestData => {
...[load custom view]...
return false;
});
context.start();
The receiver log shows the line cast.framework.common.IdleTimeoutManager] timer expired and then shuts down. Example receiver log shown here.
I've tried:
Increasing cast.framework.CastReceiverOptions#maxInactivity to a very large number
Periodically loading new data from the sender
Periodically sending custom messages from the receiver to the sender
Periodically sending custom messages from the sender to the receiver
Any help is very much appreciated!
I ran into the same problem while developing a custom receiver app that does not play media. Here is the solution I implemented:
var idleTime = 0;
const context = cast.framework.CastReceiverContext.getInstance();
const CUSTOM_CHANNEL = '[MY CHANNEL HERE]';
context.addCustomMessageListener(CUSTOM_CHANNEL, function(customEvent) {
var eventData = customEvent.data;
parseCommand(eventData);
idleTime = 0;
});
const options = new cast.framework.CastReceiverOptions();
options.disableIdleTimeout = true;
context.start(options);
var idleInterval = setInterval(timerIncrement, 60000); // 1 minute
function timerIncrement() {
idleTime = idleTime + 1;
if (idleTime > 4) { // 5 minutes
context.stop();
}
}
With CastReveiverOptions I disable idle timeout, which according to the documentation: "If true, the receiver will not set an idle timeout to close receiver if there is no activity. Should only be used for non media apps."
https://developers.google.com/cast/docs/reference/caf_receiver/cast.framework.CastReceiverOptions#constructor_1
Since mine is a "non media app," I believe this is correct usage.
I then set my own time out based on 5 minutes of inactivity in my custom channel.
I figured out an alternative way to stop this which is more efficient than periodically sending a silent clip, but it feels dirty. Basically we have to stop Chromecast's setTimeout from firing and closing the connection due to no media. The quickest solution is to simply re-declare setTimeout as a dummy no-op function before loading the Chromecast receiver script. It does not seem to break anything Chromecast-related in this scenario because it looks like Chromecast's timeouts are all related to video which aren't relevant to this use case.
window._setTimeout = window.setTimeout;
window.setTimeout = function(a, b) {
// disable setTimeout so chromecast won't kill us after 5 minutes...
};
Then in our own app if we need to use a timeout we call _setTimeout instead.
I would be interested if anyone has discovered a better way to achieve this, aside from manually hosting cast_receiver_framework.js with the offending line commented out (which is inside the Wn(a, b) function) or sending a silent clip every few minutes. But self-hosting isn't recommended by Google.
A better solution may be to dig deep in the minified code to work out how Xn(a) is called as that disables the timeout whenever media is playing, and then find a way to call that from within the Chromecast app.
Loading a short inaudible audio clip from the sender to the receiver every 4 minutes seems to do the trick. This should not impact performance much if the file is small. Here is some android code.
MediaMetadata metadata = new MediaMetadata(MediaMetadata.MEDIA_TYPE_MUSIC_TRACK);
MediaInfo mediaInfo = new MediaInfo.Builder("https://some-inaudible-clip.mp3")
.setStreamType(MediaInfo.STREAM_TYPE_BUFFERED)
.setContentType("audio/mpeg")
.setMetadata(metadata)
.build();
RemoteMediaClient remoteMediaClient = castSession.getRemoteMediaClient();
remoteMediaClient.load(mediaInfo, true);
It is possible to send a custom namespace message from the receiver to the sender. That should keep the heartbeat live. However, your use case is not directly supported by the Cast SDK, so you would have to experiment on a solution.
Anybody got this problem, anyway I didn't find an answer. The code is simple:
void CbDlg::OnBnClickedOk()
{
for(int i=0; i<1000; i++)
{
HRSRC hRes = ::FindResource(NULL, MAKEINTRESOURCE(IDR_MAINFRAME), RT_GROUP_ICON);
HGLOBAL hResLoad = ::LoadResource(NULL, hRes);
BYTE* pIconBytes = (BYTE*)::LockResource(hResLoad);
int nId = ::LookupIconIdFromDirectory(pIconBytes, TRUE);
hRes = ::FindResource(NULL, MAKEINTRESOURCE(nId), RT_ICON);
DWORD read = ::SizeofResource(NULL ,hRes);
hResLoad = ::LoadResource(NULL, hRes);
pIconBytes = (BYTE*)::LockResource(hResLoad);
if(pIconBytes != NULL)
{
HICON hIcon = ::CreateIconFromResource(pIconBytes, read, TRUE, 0x00030000);
DWORD e = ::GetLastError();
if(hIcon != NULL)
{
::DestroyIcon(hIcon);
}
}
}
}
If I click the Ok button four times (On my computer), CreateIconFromResource start to return NULL (It worked fine before and I could even draw out the icon). As to the GetLastError, it's always return 6 whatever CreateIconFromResource return NULL or not.
When this problem happened, if I drag the title bar to move, UI crashed, see the pictrue.
Of course you can understand this piece of code is just a demo, my real business need to call CreateIconFromResource thousands of times just like this.
UPDATE:
According to Hans' suggestion, I keep tracking the Handles/USER Objects/GDI objects, and found that USER Objects grows 1000 and GDI objects grows 2000 against each clicking to OK button (handles didn't grow), and GDI objects is 9999 when problem happens. But how to release them correctly, when I finish to use? I didn't use that much at one time, but need to load, release, load again, release again... Just like this demo. As MSDN document, I called DestroyIcon for every HICON. What else do I need to do, to finally release the USER/GDI objects?
I found the answer. The success or failure is all due to MSDN.
It says:
"The CreateIconFromResource function calls CreateIconFromResourceEx passing LR_DEFAULTSIZE|LR_SHARED as flags" AND "Do not use this function(DestroyIcon) to destroy a shared icon"
But It also says:
"When you are finished using the icon, destroy it using the DestroyIcon function" in CreateIconFromResource's document.
Actually, the second statement is WRONG.
So, the solution is, using CreateIconFromResourceEx without LR_SHARED, and DestroyIcon every HICON after using.