15

I have used GPS location updates in my application. I want to detect if the iOS device is in sleep mode so that I can turn off the GPS location updates and optimize the battery use. I have already tried pausesLocationupdates in iOS 6, but it does not work as desired. I want to turn off the GPS location updates as soon as the device goes to sleep mode. I want to detect the lock/unlock event in the device.

Is there any way to achieve this functionality ?

so far I got the darwin notifications as given below

-(void)registerForall
{
    //Screen lock notifications
    CFNotificationCenterAddObserver(CFNotificationCenterGetDarwinNotifyCenter(), //center
                                    NULL, // observer
                                    displayStatusChanged, // callback
                                    CFSTR("com.apple.iokit.hid.displayStatus"), // event name
                                    NULL, // object
                                    CFNotificationSuspensionBehaviorDeliverImmediately);


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

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

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

}
//call back
static void displayStatusChanged(CFNotificationCenterRef center, void *observer, CFStringRef name, const void *object, CFDictionaryRef userInfo)
{
    NSLog(@"IN Display status changed");
    NSLog(@"Darwin notification NAME = %@",name);


}

I am able to get the darwin notifications when device is locked/unlocked, but the real problem is how identify between if the notification has come from locking or the unlocking of the device. Console logs are:

 LockDetectDemo[2086] <Warning>: IN Display status changed
 LockDetectDemo[2086] <Warning>: Darwin notification NAME = com.apple.springboard.lockcomplete
 LockDetectDemo[2086] <Warning>: IN Display status changed
 LockDetectDemo[2086] <Warning>: Darwin notification NAME = com.apple.springboard.lockstate
 LockDetectDemo[2086] <Warning>: IN Display status changed
 LockDetectDemo[2086] <Warning>: Darwin notification NAME = com.apple.springboard.hasBlankedScreen
 LockDetectDemo[2086] <Warning>: IN Display status changed
 LockDetectDemo[2086] <Warning>: Darwin notification NAME = com.apple.iokit.hid.displayStatus

Any private API would also suffice. Thanks in advance.

allprog
  • 16,540
  • 9
  • 56
  • 97
Rohit Kashyap
  • 934
  • 2
  • 8
  • 23

8 Answers8

20

Apps are not allowed to listen to device lock notifications now!.

I had receive this:

Dear developer,

We have discovered one or more issues with your recent submission for "xxxx". To process your submission, the following issues must be corrected:

Unsupported operation - Apps are not allowed to listen to device lock notifications.

Once these issues have been corrected, use Xcode or Application Loader to upload a new binary to iTunes Connect. Choose the new binary on the app’s Details page in My Apps on iTunes Connect, and click Submit for Review.

Regards,

The App Store team
April 26 2017 at 10:56

Community
  • 1
  • 1
Jules
  • 631
  • 6
  • 14
19

I solved it like this:

//call back
static void displayStatusChanged(CFNotificationCenterRef center, void *observer, CFStringRef name, const void *object, CFDictionaryRef userInfo)
{
    // the "com.apple.springboard.lockcomplete" notification will always come after the "com.apple.springboard.lockstate" notification
    CFStringRef nameCFString = (CFStringRef)name;
    NSString *lockState = (NSString*)nameCFString;
    NSLog(@"Darwin notification NAME = %@",name);
    
    if([lockState isEqualToString:@"com.apple.springboard.lockcomplete"])
    {
        NSLog(@"DEVICE LOCKED");
        //Logic to disable the GPS
    }
    else
    {
        NSLog(@"LOCK STATUS CHANGED");
        //Logic to enable the GPS
    }
}

-(void)registerforDeviceLockNotif
{
    //Screen lock notifications
    CFNotificationCenterAddObserver(CFNotificationCenterGetDarwinNotifyCenter(), //center
                                    NULL, // observer
                                    displayStatusChanged, // callback
                                    CFSTR("com.apple.springboard.lockcomplete"), // event name
                                    NULL, // object
                                    CFNotificationSuspensionBehaviorDeliverImmediately);
    
    CFNotificationCenterAddObserver(CFNotificationCenterGetDarwinNotifyCenter(), //center
                                    NULL, // observer
                                    displayStatusChanged, // callback
                                    CFSTR("com.apple.springboard.lockstate"), // event name
                                    NULL, // object
                                    CFNotificationSuspensionBehaviorDeliverImmediately);
}

Note: the "com.apple.springboard.lockcomplete" notification will always come after the "com.apple.springboard.lockstate" notification

Update

The order of the two notifications can no longer be relied upon, as of recent versions of iOS

Community
  • 1
  • 1
Rohit Kashyap
  • 934
  • 2
  • 8
  • 23
  • Why so complicated? applicationDidEnterBackground is immediately called when lock button is pressed. – AlexWien Jul 09 '13 at 23:03
  • Because app if app already runs in the background, I need to detect the exact state, device is locked or unlocked. – Rohit Kashyap Jul 18 '13 at 06:22
  • a simple boolean flag which you persist will work, too. store that flag in didEnterbackground, ans willEnterForeground – AlexWien Jul 18 '13 at 08:25
  • But the requirement is to detect the screen lock/unlock events irrespective of the state of the app background or foreground. Example: The lock or unlock events when my app runs in the background and user may use other apps. – Rohit Kashyap Jul 18 '13 at 08:39
  • ok, but this then has nothing to with GPS and the app (as described) itself, it then serves as a lock/unlock tracker app – AlexWien Jul 18 '13 at 08:40
  • 1
    It is, based on the events, I want to pause/resume the location updates in the app to prevent the battery drain. – Rohit Kashyap Jul 18 '13 at 08:43
  • you should pause it in DidEnterBackground, and start it in willEnterForeground, where is the benefit of your approach? – AlexWien Jul 18 '13 at 09:14
  • My app runs the location updates even in background. Location updates are irrespective of the background/foreground state. – Rohit Kashyap Jul 18 '13 at 09:58
  • myne, too. but if you want to save energy in background, the location updates are respective of background state. your statements are contradicting. – AlexWien Jul 18 '13 at 10:32
  • 2
    Its unnecessary discussion now, the updates are respective of the use case, My app uses the location updates even in the background and I have a handler for it, when user locks the device (app in background), I want to pause the location updates. – Rohit Kashyap Jul 18 '13 at 10:43
  • 8
    From Eskimo@apple here: https://devforums.apple.com/message/912689#912689: These notification keys are undocumented, and thus considered private API. Also, from a compatibility standpoint, relying on them isn't a significant improvement over the previously-described techniques. btw The notification keys that are considered public API are listed in , with that caveat that not all of these make sense on iOS. – coneybeare Oct 29 '13 at 13:32
  • 2
    Is there a way to distinguish display status changes between when you press the power button and when the device gets a notification? – Jack Shultz Jun 14 '15 at 13:13
  • If anyone need to use NSString for the event name: delete the CFSTR and replace it with (__bridge CFStringRef)yourNSStringConstant – OhadM Feb 11 '16 at 09:18
  • 1
    @Jimmy I can not able to detect the event when app is in background state? – Paras Joshi Feb 18 '16 at 14:04
  • 1
    This is no more allowed by apple. https://forums.developer.apple.com/thread/76325 – Sandy May 01 '17 at 17:33
7

/* Register app for detecting lock state */

 -(void)registerAppforDetectLockState {

     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);
     if(state == 0) {
        NSLog(@"unlock device");
     } else {
        NSLog(@"lock device");
     }

     NSLog(@"com.apple.springboard.lockstate = %llu", state);
     UILocalNotification *notification = [[UILocalNotification alloc]init];
     notification.repeatInterval = NSDayCalendarUnit;
     [notification setAlertBody:@"Hello world!! I come becoz you lock/unlock your device :)"];
     notification.alertAction = @"View";
     notification.alertAction = @"Yes";
     [notification setFireDate:[NSDate dateWithTimeIntervalSinceNow:1]];
     notification.soundName = UILocalNotificationDefaultSoundName;
     [notification setTimeZone:[NSTimeZone  defaultTimeZone]];

     [[UIApplication sharedApplication] presentLocalNotificationNow:notification];

  });

 }
Nits007ak
  • 743
  • 7
  • 15
  • I think you can use this method..it can be helpful njoy..!! – Nits007ak Jul 24 '14 at 07:27
  • just import #import notify.h before using this code else will give compilation error – Nits007ak Sep 24 '14 at 08:37
  • @VanditMehta Vandit - I see you have posted the question about background detection to multiple SO questions. Did you ever find a solution that works in the background for detecting locked state? – Praxiteles Jun 01 '17 at 20:07
4

Here's a better solution

#import <notify.h>

#define kNotificationNameDidChangeDisplayStatus                 @"com.apple.iokit.hid.displayStatus"

@interface YourClass ()
{    
    int _notifyTokenForDidChangeDisplayStatus;
}

@property (nonatomic, assign, getter = isDisplayOn) BOOL displayOn;
@property (nonatomic, assign, getter = isRegisteredForDarwinNotifications) BOOL registeredForDarwinNotifications;

@end

- (void)registerForSomeNotifications
{
    //
    // Display notifications
    //

    __weak YourClass *weakSelf = self;

    uint32_t result = notify_register_dispatch(kNotificationNameDidChangeDisplayStatus.UTF8String,
                                               &_notifyTokenForDidChangeDisplayStatus,
                                               dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0l),
                                               ^(int info) {
                                                   __strong YourClass *strongSelf = weakSelf;

                                                   if (strongSelf)
                                                   {
                                                       uint64_t state;
                                                       notify_get_state(_notifyTokenForDidChangeDisplayStatus, &state);

                                                       strongSelf.displayOn = (BOOL)state;
                                                   }
                                               });
    if (result != NOTIFY_STATUS_OK)
    {
        self.registeredForDarwinNotifications = NO;
        return;
    }

    self.registeredForDarwinNotifications = YES;
}

- (void)unregisterFromSomeNotifications
{
    //
    // Display notifications
    //

    uint32_t result = notify_cancel(_notifyTokenForDidChangeDisplayStatus);
    if (result == NOTIFY_STATUS_OK)
    {
        self.registeredForDarwinNotifications = NO;
    }
}
arturgrigor
  • 9,361
  • 4
  • 31
  • 31
  • 1
    Thanks for posting this! As far as I can tell the other method doesn't actually explicitly tell you what the screen or lock state is changing to (the userInfo dictionary is always nil), and doing it based on order is problematic, especially since the order in which I get those notifications is actually reversed from what the post claimed they would be -- possibly a new behavior in iOS 7. – Bri Bri Dec 16 '13 at 02:41
4

For your particular use case checking the screen brightness can be useful.

var isScreenLocked: Bool {
    return UIScreen.main.brightness == 0.0
}
rockdaswift
  • 9,613
  • 5
  • 40
  • 46
2

I got a solution for check lock button was press or put app in background mode.

App cycle for lock press and put app in background mode-

WHEN LOCK PRESS

applicationWillResignActive

applicationDidEnterBackground

WHEN UNLOCK PRESS

applicationWillEnterForeground

applicationDidBecomeActive

///////////////////// WHEN PUT IN BACKGROUND

applicationWillResignActive

applicationDidEnterBackground

WHEN FORGROUND

applicationWillEnterForeground

applicationDidBecomeActive

you can observe in both scenario same method are calling. In this case this is difficult task to get pressed lock button or put app in background. A small hack is there when we press lock button

applicationWillResignActive

applicationDidEnterBackground

these methods will called immediately but when we put app in background there is a time interval of millisecond between both methods. we can get the time difference and put condition on it. like....

var dateResignActive : Date?
var dateAppDidBack : Date?
func applicationWillResignActive(_ application: UIApplication) {

    dateResignActive = Date()

}
func applicationDidEnterBackground(_ application: UIApplication) {

    dateAppDidBack = Date()

}
func applicationDidBecomeActive(_ application: UIApplication) {

    let el1 = getCurrentMillis(date: dateResignActive!)
    let el2 = getCurrentMillis(date: dateAppDidBack!)
    let diff = el2 - el1

    if diff < 10 { //// device was locked // 10 is aprox
        // device was locked

    }
    else {
        let elapsed = Int(Date().timeIntervalSince(date!))
        if elapsed > 15 { // put app in background
        }
    }

}
func getCurrentMillis(date : Date)->Int64 {
    return Int64(date.timeIntervalSince1970 * 1000)
}

**This code is tested in iPhone X(Notch) and iPhone 6(Home button device). Because notch device and home button device have small difference in above two method calling.**  
Vijay Patidar
  • 111
  • 1
  • 9
0

Jimmy provided a great solution but it's safer to pass in (__bridge const void *)(self) as an observer.

CFNotificationCenterAddObserver(CFNotificationCenterGetDarwinNotifyCenter(),
                                (__bridge const void *)(self),
                                displayStatusChanged,
                                CFSTR("com.apple.springboard.lockcomplete"),
                                NULL,
                                CFNotificationSuspensionBehaviorDeliverImmediately);

This allows you to properly remove the observer.

0

On a device that uses content protection, protected files are stored in an encrypted form and made available only at certain times, usually when the device is unlocked. This notification lets your app know that the device is about to be locked and that any protected files it is currently accessing might become unavailable shortly.

You can subscribe to notifications for applicationProtectedDataWillBecomeUnavailable which is very likely triggered when user has just locked their iPhone.

I have never done it myself, but it’s just an alternate solution...

mfaani
  • 33,269
  • 19
  • 164
  • 293