11

I am developing a network monitor app that runs in background as a service. Is it possible to get a notification/call when the screen is turned on or off?

It exists in Android by using the following code:

private void registerScreenOnOffReceiver()
{
   IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_ON);
   filter.addAction(Intent.ACTION_SCREEN_OFF);
   registerReceiver(screenOnOffReceiver, filter);
}

screenOnOffReceiver is then called when screen is turned on/off. Is there a similar solution for iOS?

Edit: The best I've found so far is UIApplicationProtectedDataWillBecomeUnavailable ( Detect if iPhone screen is on/off ) but it require the user to enable Data Protection (password protection) on the device.

Community
  • 1
  • 1
Sunkas
  • 9,542
  • 6
  • 62
  • 102

3 Answers3

16

You can use Darwin notifications, to listen for the events. I'm not 100% sure, but it looks to me, from running on a jailbroken iOS 5.0.1 iPhone 4, that one of these events might be what you need:

com.apple.iokit.hid.displayStatus
com.apple.springboard.hasBlankedScreen
com.apple.springboard.lockstate

Update: also, the following notification is posted when the phone locks (but not when it unlocks):

com.apple.springboard.lockcomplete

To use this, register for the event like this (this registers for just one event, but if that doesn't work for you, try the others):

CFNotificationCenterAddObserver(CFNotificationCenterGetDarwinNotifyCenter(), //center
                                NULL, // observer
                                displayStatusChanged, // callback
                                CFSTR("com.apple.iokit.hid.displayStatus"), // event name
                                NULL, // object
                                CFNotificationSuspensionBehaviorDeliverImmediately);

where displayStatusChanged is your event callback:

static void displayStatusChanged(CFNotificationCenterRef center, void *observer, CFStringRef name, const void *object, CFDictionaryRef userInfo) {
    NSLog(@"event received!");
    // you might try inspecting the `userInfo` dictionary, to see 
    //  if it contains any useful info
    if (userInfo != nil) {
        CFShow(userInfo);
    }
}

If you really want this code to run in the background as a service, and you're jailbroken, I would recommend looking into iOS Launch Daemons. As opposed to an app that you simply let run in the background, a launch daemon can start automatically after a reboot, and you don't have to worry about iOS rules for apps running tasks in the background.

Let us know how this works!

Nate
  • 31,017
  • 13
  • 83
  • 207
  • Thanks a lot! Just what I was looking for! Tried it on a non-jailbreaked iPhone 5 and the callback gets called! However userInfo is always nil, perhaps because is not jailbreaked? – Sunkas Jan 08 '13 at 10:31
  • I'm trying to avoid not having to jailbreak and this solution it definitely one step forward. Now I just have to find a way of determine if the callback is due to a screen off or on event. com.apple.springboard.lockstate was called when the phone was locked and unlocked. The other two when the screen turned on and off. – Sunkas Jan 08 '13 at 10:33
  • 1
    You might try also checking for `com.apple.springboard.lockcomplete`. I *believe* that one is only called when the screen is locked, not when it's unlocked. So, that might help you identify which situation you're detecting. As far as `userInfo` being nil, I doubt that's because you're not jailbroken. Not all notifications populate that `userInfo` dictionary. It's basically an optional payload. So, it very well could be empty. It can be used for events that want to provide additional details. I was hoping that it would tell you screen **on** vs. **off**, but I guess not. – Nate Jan 08 '13 at 11:05
  • Thanks again. com.apple.springboard.lockcomplete only fired when turning the screen off! – Sunkas Jan 08 '13 at 11:45
  • @openfrog, I haven't tried, so I don't know. However, these functions are [in the Public APIs](https://developer.apple.com/library/ios/#documentation/CoreFoundation/Reference/CFNotificationCenterRef/Reference/reference.html). Regarding the actual event name **strings**, like `com.apple.iokit.hid.displayStatus` ... would they reject your app for using those? Maybe. If you want to try, you could *obfuscate* those strings, so that someone running the `strings` command on your binary won't find a match. You **definitely** can't get the daemon into the app store, but maybe the event detection? – Nate May 19 '13 at 20:53
  • @Nate I tried above solution , it works on NON_Jailborken device BUT only if my app is RUNNING. If my app enteres background state , my app didn't receive any events from above notifications. Can you tell me how can I receive those events when my app is in background ? Thanks – iOSAppDev Aug 01 '13 at 05:36
  • 1
    @iOSAppDev, you can keep your app running for longer than normal iOS rules permit, by either making your app a *Launch Daemon*, as I mention above, or [seeing the undocumented UIBackgroundModes described in this answer](http://stackoverflow.com/a/15798083/119114). The background modes are undocumented APIs, and won't be approved for App Store apps, but they don't require jailbreaking to use them. – Nate Aug 01 '13 at 06:20
  • @Nate Thanks for reply. So that means I can't submit the app to App Store even if I use undocumented UIBackgroundModes , right ? – iOSAppDev Aug 01 '13 at 06:26
  • I would guess that they would reject it. – Nate Aug 01 '13 at 06:38
  • Thanks Nate for your reply.Really appreciate your help – iOSAppDev Aug 01 '13 at 07:37
2

Using the lower-level notify API you can query the lockstate when a notification is received:

#import <notify.h>

int notify_token;
notify_register_dispatch("com.apple.springboard.lockstate", &notify_token, dispatch_get_main_queue(), ^(int token) {
    uint64_t state = UINT64_MAX;
    notify_get_state(token, &state);
    NSLog(@"com.apple.springboard.lockstate = %llu", state);
});

Of course your app will have to start a UIBackgroundTask in order to get the notifications, which limits the usefulness of this technique due to the limited runtime allowed by iOS.

Nick Dowell
  • 2,030
  • 17
  • 17
-2

While iPhone screen is locked appdelegate method "- (void)applicationWillResignActive:(UIApplication *)application" will be called you can check that. Hope it may help you.

Exploring
  • 925
  • 6
  • 18
  • The check must work even if the app is not itself in the foreground. I assume applicationWillResignActive: is only called when the application is in the foreground? – Sunkas Jan 07 '13 at 08:17
  • Yikes, in that case you are going to have to reverse engineer the private APIs to try to find a notification for it. There is no public way to do it. Furthermore you will have to jailbreak the device in order for it to even run in the background continuously like that. There are a few ways to kick off a daemon listed in the book Hacking and Securing iOS Applications. – borrrden Jan 07 '13 at 08:19
  • Ok. Thanks for the tip. Actually so far I have not needed to jailbreak the device to be able to running a long lived background service. Just "declare" it as VOIP application and iOS wont shut it down. – Sunkas Jan 07 '13 at 08:26