7

With ios4.x I can use code below to get the message when get the "kCTMessageReceivedNotification" notification

CTTelephonyCenterAddObserver( ct, NULL, callback,NULL,NULL, CFNotificationSuspensionBehaviorHold); 

if ([notifyname isEqualToString:@"kCTMessageReceivedNotification"])//receive message
    {

        NSDictionary *info = (NSDictionary *)userInfo;
        CFNumberRef msgID = (CFNumberRef)[info objectForKey:@"kCTMessageIdKey"];
        int result;
        CFNumberGetValue((CFNumberRef)msgID, kCFNumberSInt32Type, &result);   
        Class CTMessageCenter = NSClassFromString(@"CTMessageCenter");
        id mc = [CTMessageCenter sharedMessageCenter];
        id incMsg = [mc incomingMessageWithId: result];}

But with ios5 I can't do it as incMsg is nil,so what can i do to get the message?

Thanks

dustdn
  • 418
  • 2
  • 6
  • 18
  • yeah, I'm seeing this message "unknown CommCenter[31] : removing received message 2147483648" pop up before my notification handler gets to run. it's like the messages are cleared out as soon as the (new iOS 5) notification center gets them. I also tried calling `[mc allIncomingMessages]` and it was totally empty. – Nate May 28 '12 at 05:52
  • Then do you know how I can get the message? I haven't soloved it.Thanks. – dustdn May 29 '12 at 01:45

3 Answers3

8

Here's what I found ...

Just looking at the dumped private APIs, it looks like ChatKit.framework could help. Take a look at CKSMSService.h

or CKMadridService.h for iMessage messages.

I did quickly attempt to swizzle my own method in, for a couple methods in CKSMSService:

- (void)_receivedMessage: (id)arg1 replace:(BOOL)arg2 replacedRecordIdentifier:(int)arg3 postInternalNotification:(BOOL)arg4;

- (void)_receivedMessage: (id)arg1 replace:(BOOL)arg2 postInternalNotification:(BOOL)arg3;

but on iOS 5.0.1 I didn't see either of those get called (maybe my error?). So, I tried to just get the message directly from the sqlite SMS database. Note ... I didn't build the full app, to register for notifications. I'm assuming your code to get the kCTMessageReceivedNotification is ok ... it just doesn't give you the SMS content anymore. So, if you put the following code in your notification handler, you should be able to see the message text:

- (NSString *) mostRecentSMS  { 
    NSString *text = @"";

    sqlite3 *database;
    if(sqlite3_open([@"/private/var/mobile/Library/SMS/sms.db" UTF8String], &database) == SQLITE_OK) {
        sqlite3_stmt *statement;

        // iOS 4 and 5 may require different SQL, as the .db format may change
        const char *sql4 = "SELECT text from message ORDER BY rowid DESC";  // TODO: different for iOS 4.* ???
        const char *sql5 = "SELECT text from message ORDER BY rowid DESC";

        NSString *osVersion =[[UIDevice currentDevice] systemVersion];         
        if([osVersion hasPrefix:@"5"]) {
            // iOS 5.* -> tested
            sqlite3_prepare_v2(database, sql5, -1, &statement, NULL);
        } else {
            // iOS != 5.* -> untested!!!
            sqlite3_prepare_v2(database, sql4, -1, &statement, NULL);
        }

        // Use the while loop if you want more than just the most recent message
        //while (sqlite3_step(statement) == SQLITE_ROW) {
        if (sqlite3_step(statement) == SQLITE_ROW) {
            char *content = (char *)sqlite3_column_text(statement, 0);
            text = [NSString stringWithCString: content encoding: NSUTF8StringEncoding];
            sqlite3_finalize(statement);
        }

        sqlite3_close(database);
    }
    return text;
}    

Now, make sure this app is installed in /Applications/. If you just build this app, and install normally with Xcode, you'll get a permission denied error opening the sqlite database, because of app sandboxing.

My code snippet just gets the most recent text content. Here's an example of doing a little more with the database. Look at the QuerySMS method.

Also, here's a link on the database format of sms.db. You can find what else you need in there. Or, just copy the sms.db to your computer, and browse it with something like the Firefox SQLiteManager plugin. Good luck!

Update: some information from a question I posted on multi-process SQLite thread safety on iOS

Community
  • 1
  • 1
Nate
  • 31,017
  • 13
  • 83
  • 207
  • Thank you very much. I will have a try. – dustdn Jun 15 '12 at 05:17
  • @dustdn, Also, since I only built the half of the app that wasn't working for you (getting sms content), I'm not 100% sure if there's any timing issues if you use this code immediately after you receive the notification. I'm still not 100% clear on whether sqlite is thread-safe on iOS. I ran the code `int safe = sqlite3_threadsafe();` and got back a non-zero result (2), but I'm not sure that means it's thread-safe. Anyway, you may need to use a little delay, or watch the return value of the sqlite calls to check for `SQLITE_MISUSE`, which I think would indicate thread-safety problems. – Nate Jun 15 '12 at 07:25
  • 1
    If it's helpful I believe you can trigger that code using the iOS equiv. of Mac OS X's "WatchPaths" option for launchdaemon, and launch only content in a directory (i.e. the SMS directory) is changed. I haven't tested it (I used a cron job), but I found a link with some example code: http://developer.apple.com/library/ios/#samplecode/DocInteraction/Listings/Classes_DirectoryWatcher_m.html – Orwellophile Oct 21 '12 at 12:56
  • @Nate nice find, quick question though will apple police let an app that can read the users texts on the market assuming this is just a part of a bigger app and not the entire app? – Dnaso Jan 06 '13 at 04:27
  • @Dnaso, **no**, but that doesn't even matter. This will not even function properly in an App Store app, because app store apps are *sandboxed*, installed in `/var/mobile/Applications`. They will not be able to read the sms.db file. So, it's not just a matter of what Apple's reviewers will allow. The limitation is technical. – Nate Jan 06 '13 at 08:50
  • @Nate that sucks I want to be able to read peoples texts (well not me have lets say when some one texts them they get an alternate alert from my app... is that even possible? i know it is via android – Dnaso Jan 15 '13 at 19:24
  • @Dnaso, it's possible, as I explained, **if** you have a jailbreak app installed outside the sandbox. It's not, otherwise, as far as I know. Doing this doesn't really fit with Apple's philosophy. Android's is different. Frankly, I don't want 3rd party apps to be able to read the system text messages, *unless* I've decided to jailbreak, in which case I'm acknowledging that I've given up much of Apple's built-in security. – Nate Jan 16 '13 at 10:37
  • @nate, what if someone wanted to make an app that correlates with the fact that people get away messages , say for a special alert, etc i mean the intent of the app would be obvious. Apple is a little TOO anal if you ask me, but I do agree it does prevent people from doing ceazy stuff – Dnaso Jan 19 '13 at 03:56
  • @Dnaso, if your app has specialized messages that it wants to receive, Apple has Push Notifications for that. There's no need to open up all of my text messages to a 3rd-party developer just to let my app receive *alerts*. I agree that Apple is too restrictive, but on this particular item, I don't have a problem with their choice. – Nate Jan 19 '13 at 04:37
  • @nate i meant special alerts as an example, i wanted to do something else like have an away message or something in that ball park like if im at a meeting and someone texts me i can send an autoreply something of that nature but if apple wont let you know whos texting you, its stupid on apples part, its kind of ridiculous – Dnaso Jan 19 '13 at 23:26
2

I manged to get the last message on a non-jailbroken iOS8 device:

  1. Get CKDBMessage.h from ChatKit headers and add the file to your project.
  2. Sign up to kCTMessageReceivedNotification through CTTelephonyCenterAddObserver
  3. Use this function to get the info of the last received message:

    void SmsReceived()
    {
        NSLog(@"GOT SMS");
    
        //open IMDPersistence framework
        void *libHandle =     dlopen("/System/Library/PrivateFrameworks/IMDPersistence.framework/IMDPersistence", RTLD_NOW);
    
        //make/get symbol from framework + name
        IMDMessageRecordGetMessagesSequenceNumber = (int (*)())dlsym(libHandle, "IMDMessageRecordGetMessagesSequenceNumber");
    
        // get id of last SMS from symbol
        int lastID = IMDMessageRecordGetMessagesSequenceNumber();
        NSLog(@"%d", lastID);
    
        // close (release?) framework -> needed??
        dlclose(libHandle);
    
    
        // get message object
        dlopen("/System/Library/PrivateFrameworks/ChatKit.framework/ChatKit", RTLD_LAZY);
        Class CKDBMessageClass = NSClassFromString(@"CKDBMessage");// objc_getClass("CKDBMessage");
        CKDBMessage *msg = [[CKDBMessageClass alloc] initWithRecordID:lastID];
    
        NSString *text = msg.text;
        NSLog(@"text: %@", text);
    }
    
Aviel Gross
  • 9,770
  • 3
  • 52
  • 62
  • Can you please share project on git hub? Where to find CKDBMessage.h and how to sign up for "kCTMessageReceivedNotification" – Durgaprasad May 29 '15 at 06:45
  • This still works on iOS 7, but I found that you need a slight delay after receiving the kCTMessageReceivedNotification notification. Else you will miss the SMS just received. I use a delay of 0.1 sec, with a [self performSelector .. afterDelay:0.1]; – RickJansen Feb 16 '16 at 09:49