Several months ago, I wrote about the iPhone's onboard Core Telephony notification system. That post explored how to catch and respond to iPhone interruptions. After writing that, I remained curious about the standard iPhone notification system. I didn't get far. And this is why: The iPhone relies primarily on Darwin Notifications to communicate between applications.
Listening to notifications isn't hard. Just call CFNotificationCenterAddObserver and add your application as a listener. Unfortunately, unlike standard notification centers, the Darwin forbids eavesdropping. You must specify exactly which notification token you want to listen for.
With that roadblock seemingly insurmountable, I moved onto other projects. This week, I stumbled once again across Daniel "Pumpkin" Peebles who encouraged me to try again. So I did.
What I did was this. I went to /System/Library and ran the strings command on just about every file I could find, particularly the frameworks and Core Services folders. I then grepped through those strings for "^com.apple", the pattern that identifies most inter-application notifications.
I then repeated this for /Applications. Finally, I sorted and uniq'ed my results to create as comprehensive a list as possible of Apple notifications. It took many hours but once I got it automated, it ran pretty quickly.
My list contained quite a number of tokens. These included power notifications (fully charged, etc), sync notifications (sync begun, canceled, etc), and other system events. I was going to look for events using a hammer rather than a paintbrush.
I built the following tool using the open SDK toolchain. In this, I feed notifications into a loop, creating an observer for each notification token type. The source code (omitting the actual notification list) follows below.
The results were gratifying in that they allowed me to listen in to my system in a way I hadn't before. I could sense when apps launched and quit, when the battery charger came on line. I could detect when system notifications appeared on-screen, and more. No, this didn't provide a full or exhaustive view -- the notification list is still a work in progress -- but it was at least a step forward in interacting with the iPhone at a deeper level.
#include <stdio.h>
#include <notify.h>
#include <unistd.h>
#include <stdarg.h>
static void callback(CFNotificationCenterRef center, void *observer, CFStringRef name, const void *object, CFDictionaryRef userInfo) {
fprintf(stderr, "Notification intercepted: %s\n", [name UTF8String]);
if (userInfo) CFShow(userInfo);
return;
}
static void signalHandler(int sigraised)
{
printf("\nInterrupted.\n");
_exit(0);
}
void usage(char *appname)
{
printf("%s : options \n", appname);
printf("-s listen to standard notifications\n");
printf("-t listen to core telephony notifications\n");
}
int main(int argc, char **argv)
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
if (argc < 2) {usage(argv[0]); exit(0);}
NSString *readNotifications = NOTSTRING;
NSArray *notifications = [readNotifications componentsSeparatedByString:@"BOUNDARY"];
opterr = 0;
int c;
id results;
BOOL isTelephony = NO;
BOOL isStandard = NO;
while ((c = getopt (argc, argv, "ts")) != -1)
switch (c)
{
case 's':
isStandard = YES;
break;
case 't':
isTelephony = YES;
break;
case '?':
if (isprint (optopt))
fprintf (stderr, "Unknown option `-%c'.\n", optopt);
else
fprintf (stderr,
"Unknown option character `\\x%x'.\n",
optopt);
return 1;
default:
return 0;
}
if ((!isStandard) && (!isTelephony))
{
printf("Listen either to standard (-s), telephony (-t) notifications or both\n");
printf("No notification type selected. Exiting.\n");
exit(-1);
}
if (isStandard)
{
printf("Watching for Standard Notifications (%d notifications)\n", [notifications count]);
for (id *notification in notifications)
{
CFNotificationCenterAddObserver(
CFNotificationCenterGetDarwinNotifyCenter(), //center
NULL, // observer
callback, // callback
notification, // name
NULL, // object
CFNotificationSuspensionBehaviorHold
);
}
}
if (isTelephony)
{
printf("Watching for Core Telephony notifications\n");
id ct = CTTelephonyCenterGetDefault();
CTTelephonyCenterAddObserver(
ct,
NULL,
callback,
NULL,
NULL,
CFNotificationSuspensionBehaviorHold);
}
// Set up a signal handler so we can clean up when we're interrupted from the command line
sig_t oldHandler = signal(SIGINT, signalHandler);
if (oldHandler == SIG_ERR) {
printf("Could not establish new signal handler");
exit(1);
}
// Start the run loop. Now we'll receive notifications.
printf("Starting Notification Scan. ^C to quit.\n");
CFRunLoopRun();
printf("Unexpectedly back from CFRunLoopRun()!\n");
}Update: Thanks August, for pointing out the extraneous UIKit header, which is now removed
true genius.. gets me thinking in new and exciting ways
you are such a great supporter of development
thanks erica
It would be a great help to see the commands needed to generate the comprehensive notification list. Pretty please :-)
Thanks for helping us so much.But it wont work with XCODE.
XCODE think that methods are just errors.
Is it "normal behaviour (and I should try it without XCODE) or I miss something?
===
"_CTTelephonyCenterAddObserver", referenced from:
"_CTTelephonyCenterGetDefault", referenced from:
===
Plz, help! :)
Core Telephony is a private framework. You cannot link to it in Xcode/SDK.
how does one compile this if one cannot use xcode to link the private framework?
Cheers,
Shawn
You need to use the open source OpenSDK
It's great!!
By the way, Can it get UITextFieldTextDidChangeNotification?
It seems to get only ^com.apple notifications.
(I tried, but no callback...)
I want to get them.
Please help?
Would there be a way to use something like this to intercept the USSD balance notifications? I know apps like iBlacklist and PYSL can intercept the SMS and Call notifications so they must be doing something similar.
USSD notifications are part of the core telephony framework. Read the post I linked to in the first paragraph.
So I should be able to build a app that subscribes to the updates. If I see a USSD message come in I need to prevent the popup. Got any ideas? If it can be done on iBlacklist for SMS there has to be a way to do it.
Could you provide the makefile for compiling the code above?
Erica, just wanted to thank you for posting this approach concept and sample code. We are finding some very useful information related to iPhone network connectivity status.
Compiling using a 2.0 Toolchain built as per Saurik required fishing the CoreTelephony/CoreTelephony.h header out of the 1.1.4 tool chain and adding the appropriate -F value to the compile to reference the PrivateFrameworks.
XCode allows you to add your Private Frameworks. Its simple. Right click on Project, Add Frameworks. Now go to your iPhoneOS SDK installation path and there you find a directory named "PrivateFrameworks". Select CoreTelephony and add it to your project.
You are done. Now try to link it.. it works fine and i wrote a SMS listener application that listens for a message and also deletes the message...
Erica fantastic blogs, thank you so much for sharing your wisdom with us all! Faced with the no-background Apps delima, I am trying to work around the problem that the App must always be ACTIVE to "listen" for CoreTelephony Notifications. Any tips, ideas, or clues?
Is there a list of notifications to listen too?