Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

pull notification locally on jailbroken device

Since the iOS framework doesn't allow local notifications to execute code before they are posted, I'm looking for a way to achieve it on a jailbroken device.

  • Is there built in functionally on a jailbroken device to schedule code execution with no need for the user to interact?
  • The code should download updates and determine if the user should receive a notification.
  • I don't want to use push notifications, which requires an external server to push them to the user.

Update

Well, I've managed to create a daemon which launches on start-up and keeps itself running. However, posting notifications requires the UIApplication object. According to the documentation this singleton is created by the UIApplicationMain() method which, for a regular application is called by main(). Since I want the notification be posted by a daemon, the singleton is nil.

Can I create an instance of UIApplication? Or post the notification any other way?

I've tried calling UIApplicationMain() and then posting the notification in the app delegate, as well as killing the application, but this shows a black screen for a moment; I guess its launching the application. Moreover, it causes the daemon to crash when app launching is impossible (when the phone is yet to fully boot).

Here is a sketch of the code

int main(){
   if(launchedBySpringBoard || launchedBynotification)
      UIApplicationMain(...);
   else if(launchedByDaeamon)
      StartRunLoop();
}

void triggerdByRunLoopEveryXhours(){
    downloadData();
    if(isNewData())
       postNotification();
}
like image 949
Kirill Kulakov Avatar asked Feb 22 '13 13:02

Kirill Kulakov


2 Answers

... Or post the notification any other way?

Yes. You can make this work with a background (launch) daemon that triggers a notification (not necessarily a UILocalNotification). When the notification shows the user an alert, your daemon can then decide to open a normal UI application (or not).

Build a Launch Daemon.

This is the best tutorial I've found. The launch daemon starts when the phone boots, and runs all the time as a non-graphical background process. From there, you can schedule your check for updates. (I have a HelloDaemon class which does all its work in the run: method):

int main(int argc, char *argv[]) {
    @autoreleasepool {
        HelloDaemon* daemon = [[HelloDaemon alloc] init];
        
        // start a timer so that the process does not exit.
        NSTimer* timer = [[NSTimer alloc] initWithFireDate: [NSDate date]
                                                  interval: 1.0
                                                    target: daemon
                                                  selector: @selector(run:)
                                                  userInfo: nil
                                                   repeats: NO];
        
        NSRunLoop* runLoop = [NSRunLoop currentRunLoop];
        [runLoop addTimer: timer forMode: NSDefaultRunLoopMode];
        [runLoop run];
    }    
    return 0;
}

Daemons can use NSTimer normally, so schedule another timer (within run:) to check for updates to download whenever you want.

Notify User from Daemon

If the daemon decides that the user should be notified, then you can either:

1) open the full UI application.

#include <dlfcn.h>
#define SBSERVPATH "/System/Library/PrivateFrameworks/SpringBoardServices.framework/SpringBoardServices"

-(void) openApp {
  
    // the SpringboardServices.framework private framework can launch apps,
    //  so we open it dynamically and find SBSLaunchApplicationWithIdentifier()
    void* sbServices = dlopen(SBSERVPATH, RTLD_LAZY);
    int (*SBSLaunchApplicationWithIdentifier)(CFStringRef identifier, Boolean suspended) = dlsym(sbServices, "SBSLaunchApplicationWithIdentifier");
    int result = SBSLaunchApplicationWithIdentifier(CFSTR("com.mycompany.AppName"), false);
    dlclose(sbServices);
}

This code requires the com.apple.springboard.launchapplications entitlement for your daemon to use it successfully. See here for adding an entitlement. You'd need an entitlements.xml file for your daemon executable, like this:

<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
    <dict>
        <key>com.apple.springboard.launchapplications</key>
        <true/>
    </dict>
</plist>

2) show a simple alert window from your daemon, notifying the user of the event, and prompting them to open the UI app

#include "CFUserNotification.h"

-(void) showAlert {
  
    NSMutableDictionary* dict = [NSMutableDictionary dictionary];
    [dict setObject: @"Alert!" forKey: (__bridge NSString*)kCFUserNotificationAlertHeaderKey];
    [dict setObject: @"Updates Ready!" forKey: (__bridge NSString*)kCFUserNotificationAlertMessageKey];
    [dict setObject: @"View" forKey:(__bridge NSString*)kCFUserNotificationDefaultButtonTitleKey];
    [dict setObject: @"Cancel" forKey:(__bridge NSString*)kCFUserNotificationAlternateButtonTitleKey];
    
    SInt32 error = 0;
    CFUserNotificationRef alert =
    CFUserNotificationCreate(NULL, 0, kCFUserNotificationPlainAlertLevel, &error, (__bridge CFDictionaryRef)dict);
    
    CFOptionFlags response;
    // we block, waiting for a response, for up to 10 seconds
    if((error) || (CFUserNotificationReceiveResponse(alert, 10, &response))) {
        NSLog(@"alert error or no user response after 10 seconds");
    } else if((response & 0x3) == kCFUserNotificationAlternateResponse) {
        // user clicked on Cancel ... just do nothing
        NSLog(@"cancel");
    } else if((response & 0x3) == kCFUserNotificationDefaultResponse) {
        // user clicked on View ... so, open the UI App
        NSLog(@"view");
        [self openApp];
    }
    CFRelease(alert);
}

You'll need a CFUserNotification.h header to use the code the way I did above. You can find one by googling, or see one here. This older wiki document also shows some good information for using CFUserNotification from iOS apps.

The answer I linked to from KennyTM above also shows how you can make your alert popup show, even if the device is locked.

like image 153
Nate Avatar answered Sep 16 '22 14:09

Nate


First of all, let me say that BigLex is giving quite interesting information. However, I never tried to write a deamon for jailbroken iphone. So, I am not aware of limitations (and it looks like there are some - like UIApplication sharedApplication is nil.

Couple of thoughts:

Backgrounding

1) In the case if you plan to distribute through Cydia (meaning that applications will end up being on the system volume) you can use two undocument background modes:

"continuos" (this one will keep running in the background) "unboundedTaskCompletion" (this one will have unlimited time, if you will do [UIApplication beginBackgroundTaskWithExpirationHandler]

You can take a look at example Info.plist here, which uses continouse.

2) There are other ways to get permanent background (which don't require even device to be jailbroken).

As example, common method is to run silent audio on the loop. Here is example how to do this.

Just be aware that this method won't be accept to App Store.

3) In the case, if you device to go with route 1) or 2), you will have access to [UIApplication sharedApplication) to post local notifications

4) You may be interested to take a look at the Backgrounder. I believe it implemented backgrounding capabilities for jailbroken devices. However, it may be outdate.

Daemon problems with UIApplication

5) Regarding problems with daemon. If you read carefully that article you will see

The first thing to note is that it is not good to use the UIApplication class to start your daemon (it takes more memory than we need), so we are going to write our own main method.

So, the code there was optimized for a memory. However, I am pretty sure that you can replace it with the common iOS application code:

int main(int argc, char *argv[])
{
    @autoreleasepool {
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

As result, I think you should have UIApplication singleton and should be able to post local notification.

Yeah... It will eat up addition X kilobytes of memory, but who cares (if you aren't running 100 of such daemons)

like image 35
Victor Ronin Avatar answered Sep 18 '22 14:09

Victor Ronin