I am developing an app that logs down the screen on/off events. It's a kind of smartphone usage analysis app. All the app does is to write down a log like this:
2015 July 25 at 03:54:12 PM - Screen on
2015 July 25 at 03:59:38 PM - Screen off
2015 July 25 at 04:20:52 PM - Screen on
2015 July 25 at 04:22:32 PM - Screen off
...
2015 July 26 at 10:20:32 AM - Screen on
2015 July 26 at 10:22:11 AM - Screen off
2015 July 26 at 11:30:38 AM - Screen on
2015 July 26 at 10:31:02 AM - Screen off
...
"Screen on": user press home button (or power button) and enter passcode/unlock password if there is any.
"Screen off": user press power button to turn off the screen.
I was able to find some way to do this this on Android using the broadcast receiver to capture the events sent by the system. But in iOS there seems to be a problem since iOS only allows background services to run several minutes, I am not even sure if I can detect the "screen on/off" event on iOS.
I did some researches on this and found some articles, but those weren't help much:
http://www.macworld.com/article/1164616/how_ios_multitasking_really_works.html
Lock Unlock events iphone
My question is "Is it possible to make an app like this in iOS (latest version - 8.4) ?"
Thanks.
It may not be possible to meet all your requirements within the published, non jail broken iOS device using the background service. I can see the notifications come across, I'm just not sure about the backgrounding.
Since others have been saying it's not possible, I'm digging a little deeper here to see just how much can be done.
Because iOS is currently restricted to a small number of background modes (situations where events are delivered in the background), or a mode where your app is granted a few minutes of time after the user navigates away from your app, the primary issue is going to be tricking the system into allowing your app to get time in the background when needed.
There are several background modes, described in the Programming Guide to Background Execution. If you can, for example, send push notifications periodically to awaken the app to "download content", you may be able to get some time periodically as the system sees fit to do so.
The background Daemon is a possible solution, but only for your own use, but not via the App Store. The official take on this is in the App Store Review Guidelines -- the relevant section is 2.8 (presumably you'd get your daemon on by having the app install it "behind the scenes"):
2.8 Apps that install or launch other executable code will be rejected
There may be some system logs that iOS keeps for itself; if you can gain access to those, you'd have your data. I doubt, however, that this is available programmatically from a non jail broken phone.
I was able to test out some Swift (2.0) code that uses the Darwin Notifications mentioned in one of the Stack Overflow discussions that your original question led to: Lock / Unlock Events for iPhone. I didn't actually run in the background, but I did verify that the events are eventually delivered, even if the app isn't running when the actual lock event takes place. When my app is switched in, the notification gets called. So, if you were able to get time from the system, you'd get the (delayed) notification when Apple's algorithms decide to give you time.
The code snippet (I stuffed it into some random app) that allows the listening is as follows:
import UIKit
import CoreFoundation
class MainViewController: UIViewController, UIWebViewDelegate {
override func viewDidLoad() {
super.viewDidLoad()
// CoreFoundation compatible types
var cfstr: CFString = "com.apple.iokit.hid.displayStatus" as NSString
var notificationCenter = CFNotificationCenterGetDarwinNotifyCenter()
CFNotificationCenterAddObserver(notificationCenter, nil,
{ (noti: CFNotificationCenter!, aPtr: UnsafeMutablePointer<Void>, aStr: CFString!, bPtr: UnsafePointer<Void>, aDict: CFDictionary!) -> () in
print("got notification") }, cfstr, nil, CFNotificationSuspensionBehavior.DeliverImmediately)
}
// [... more stuff ...]
}
If you can use the C or objective C code i guess this code might help you.
notify_register_dispatch("com.apple.iokit.hid.displayStatus", ¬ify_token, dispatch_get_main_queue(), ^(int token) {
uint64_t state = UINT64_MAX;
notify_get_state(token, &state);
//notify_cancel(token);
debug("com.apple.iokit.hid.displayStatus = %llu", state);
});
Provided that you are able to run your app in background modes.
State will provide the screen on off status
Related
I'd like to implement solution to actively notify user upon incoming Apple wallet pass update. I have implemented all the passkit server api and most of the stuff seems to work ok. The only thing is that the incoming notifications are available only "quietly" to the user. This means while the update appears on the lock screen and notification centre, the device is not waking up the screen nor vibration/sound is played and no banner is being shown.
I tried different approaches to update and even while browsing for similar cases I found barely one place mentioning no banners for pass update (yet the problem there seems more complex and with no solution - https://developer.apple.com/forums/thread/65986) stating the banners.
I am able to see two kind of Notification Centre updates
one for arbitrary field change using changeMessage (with/without optional %# string):
sample update changes fragment from pass.json (notification banner is then presented in a usual way in Notification Centre, but just silent delivery)
{
"eventTicket" : {
"headerFields" : [{
"key" : "code",
"value" : "The Code",
"changeMessage": "Code changed to: %#"
}]
}
second for relevantDate and/or location fields updated where the notification banner is presented on top of Notification Centre showing pass on click - still the notification delivery is silent (no lock screen wake up nor sound/vibration).
sample update changes fragment from pass.json:
{
"locations": [{
"longitude" : 20.000,
"relevantText" : "the location!",
"latitude" : 50.000
}]
}
I checked wallet notification settings and all three options (Lock Screen, Notification Centre and Banners) are selected. I am looking either for some similar problem being solved, some guidelines in regard to active (lock scree wake up/sound/vibration) pass update notification or at least some clear description describing if/how/why this feature is not possible.
UPDATE:
the above is valid for iPhone 6s device with iOS 13.5.2
on iPhone 8 with iOS 13.7 for changeMessage update the same changes do trigger locked screen wake up and also badges are showing up, but still no sound/vibration. Also for relevantDate updates no locked screen wakeup nor banners/sound/vibrations.
The behaviour you describe in your update is the current behaviour of wallet. Relevant date and location messages have always been passive as they are designed to make locating the card easier when your are in the right place at the right time (E.g. at the airport on the day of your flight, or in the queue at Starbucks).
Change message updates are considered active updates. These used to vibrate and/or make a sound when they arrived, but Apple reduced this to simply waking the phone and displaying on the Lock Screen several releases ago. There is nothing that you can do as an issuer, or the customer can do on their phone to change this behaviour.
The best advice I can give is to feedback to Apple with your use case and why making a sound or vibrating would provide a better experience to users.
Recently, the Chinese Ministry of Industry and Information Technology (MIIT) requested that CallKit functionality be deactivated in all apps available on the China App Store. During our review, we found that your app currently includes CallKit functionality and has China listed as an available territory in iTunes Connect.
Now, Question is what next, Which kind of changes require in app
If there isn't any way, How can i remove china from Apple store.
Please share your suggestion if anyone faced this kind of problem.
Regards,
My approach to this issue was inspired by this response on the Apple Developer forums. The general developer consensus right now seems to be that App Review is not giving specific recommendations nor are they currently explaining or requiring a specific technical solution. I think that as long as you can explain to App Review how you’re disabling CallKit for users in China, that would be acceptable.
I updated my app as I discuss below and it passed App Store review first try and we re-released in China on July 24, 2018.
When I submitted my updated app to the App Store, I included a short message in the reviewer info section saying
"In this version and onwards, we do not use CallKit features for users in China. We detect the user's region using NSLocale."
My app was approved 12hr later without any questions or comments from the App Review team.
Detecting users in China
In my app, I use NSLocale to determine if the user is in China. If so, I do not initialize or use CallKit features in my app.
- (void)viewDidLoad {
[super viewDidLoad];
NSLocale *userLocale = [NSLocale currentLocale];
if ([userLocale.countryCode containsString: #"CN"] || [userLocale.countryCode containsString: #"CHN"]) {
NSLog(#"currentLocale is China so we cannot use CallKit.");
self.cannotUseCallKit = YES;
} else {
self.cannotUseCallKit = NO;
// setup CallKit observer
self.callObserver = [[CXCallObserver alloc] init];
[self.callObserver setDelegate:self queue:nil];
}
}
To test this, you can change the region in Settings > General > Language and Region > Region. When I set Region to 'China' but left language set as English, [NSLocale currentLocale] returned "en_CN".
I was using CXCallObserver to observe the state of a call initiated from my app. My general workaround when I could not use CallKit to monitor the call was:
save the NSDate when the call begins
observer for UIApplicationDidBecomeActiveNotification with a UIBackgroundTask with expiration handler (my app already has background modes enabled)
when the app returns from the background, check the elapsed time and if it is was than 5s and less than 90 minutes, assume the call ended and save it (I needed to track call duration).
If the backgroundTaskExpirationHandler is called, assume the call ended and save the end time.
I decided to wait til at least 5s had elapsed because I noticed that -applicationDidBecomeActive was often called once or twice as the call began, usually within the first 1-3 seconds.
Go to “Pricing and Availability” in iTunes Connect.
Availability” (Click blue button Edit).
Deselect China in the list “Deselect” button.
Click “Done”.
Situation:
Since our users have updated their iOS to 11 and/or WatchOS to 4, our iOS app doesn't seem to fire any scheduled timers when the app gets started by our WatchOS app. Maybe we are doing something wrong when starting our main app from the WatchOS app.
Context & code:
Our WatchOS app is a companion app that lets the user start/stop our iPhone app in the background by pressing a button. We do this by using:
func startMainApp() {
guard WCSession.default().isReachable == true else {
print("Watch is not reachable")
return
}
var data = [String : AnyObject]()
data[WatchActions.actionKey()] = NSNumber.init(value: WatchActions.startApp.rawValue as Int)
WCSession.default().sendMessage(data, replyHandler: { (result: [String : Any]) in
let resultNumber = result[WatchActions.resultKey()] as? NSNumber
let resultBool = resultNumber!.boolValue
if resultBool == true {
self.setModeActivated()
} else {
self.setModeActivationFailed()
}
}) { (error: Error) in
if (error as NSError).code != 7012 {
print("start app error: \(error.localizedDescription)")
self.setModeActivationFailed()
}
}
}
Then in our main app, we receive the message and start our base controller:
func session(_ session: WCSession, didReceiveMessage message: [String : Any], replyHandler: #escaping ([String : Any]) -> Void) {
if let actionNumber : NSNumber = message[WatchActions.actionKey()] as? NSNumber {
if let watchAction : WatchActions = WatchActions(rawValue: actionNumber.intValue) {
switch(watchAction) {
case .isAppActive:
let result = BaseController.sharedInstance.sleepAndWakeUpController.isAwake()
replyHandler([WatchActions.resultKey() : NSNumber.init(value: result as Bool)])
return
case .startApp:
AudioController.sharedInstance().playActivatedSound()
let isRunningOnForeground = ApplicationStateHelper.isActive()
if isRunningOnForeground == false {
BaseController.sharedInstance.start(inBackground: true)
}
let result = true
replyHandler([WatchActions.resultKey() : NSNumber.init(value: result as Bool)])
DDLogInfo("[APPLE WATCH] [didReceiveMessage] [.startApp]")
return
}
}
}
replyHandler([WatchActions.resultKey() : NSNumber.init(value: false as Bool)])
return
}
Everything seems to work as before, we correctly get GPS locations, all our processes get started, however, Timer objects that get started, don't fire.
This worked perfectly before on iOS 10, so I suspect this has something to do with iOS 11 background states that work differently. However, I cannot seem to find any documentation of this.
Extra info:
On iOS 10, when we started our main app this way, the app got visible in the multitask view on the iPhone. Now on iOS 11, it isn't visible in the multitask view, however it does run on background. I successfully see local notifications that I schedule on the background, I can debug through the active code and when tapping the app icon, the app is immediately available.
Our WatchOS app has the deployment target of 2.0
Debugged via XCode with device connected, using Debug-->Attach to PID or Name-->Entered app name. Then start our app from the Apple Watch and debug.
Reproducable on iOS 11.0.3 with WatchOS 4.0 on iPhone 6
Questions:
What is the best way to start our main app from the watch app? Has something changed in iOS 11/WatchOS 4 regarding to background states? Can I find documentation of this? Could this be an iOS bug?
All I can offer you is a confirmation that this behavior did in fact change from iOS 10 to iOS 11. It is my suspicion that the behavior on iOS 10 (and earlier?) was incorrect. Apple doesn't have any qualms about changing behavior that was inadvertent/what they deem incorrect even if developer's come to rely on the behavior (I'm pretty sure I used this behavior on my last watch project).
The fact is that the UIApplication's state when launched by a message from the watch is background. Timer's aren't supposed to run when the application is in the background unless using particular background execution modes/background task. That fact is pretty well known and is usually encountered quite early on in an iOS developer's career. The fact that timer's would run in the background when launched from the watch was, I can surmise, a mistake.
I do not know your use case, i.e. why you were relying on those timers, but one thing you can do that is quite simple is to create an empty background task that will get you a little more time when the app is launched.
var backgroundTask: UIBackgroundTaskIdentifier?
backgroundTask = UIApplication.shared.beginBackgroundTask(withName: "app Start Task", expirationHandler: {
guard let task = backgroundTask else { return }
UIApplication.shared.endBackgroundTask(task)
})
let timer = Timer(timeInterval: 1, repeats: true) { (timer) in
print("Running")
}
If you need a more consistent, longer running solution, you may need to leverage your location updates as an opportunity to do whatever work the timer is currently for. There are plenty of other background modes to pursue as well.
Summary of your questions:
Q: What is the best way to start our main app from the watch app?
A: Your proposed code is a great way to launch the companion app.
Q: Has something changed in iOS 11/WatchOS 4 regarding to background states?
A: No, especially in regards to timers. The different behavior is likely a correction.
Q: Can I find documentation of this?
A: I can't. Sometimes you can squeeze this information out of apple engineers on the forums or via the code level support questions through your developer account or go to WWDC.
Q: Could this be an iOS bug?
A: The earlier behavior was likely the bug.
When app is closed and not running in background then location never track in iOS 11 it's not an iWatchOS 4 and iOS 11 Bug.
Changes to location tracking in iOS 11
follow this documentation link: Changes to location tracking in iOS 11
iOS 11 also makes some major changes to existing APIs. One of the affected areas is location tracking.
If your app only uses location while the app is in the foreground, as most apps do, you might not have to change anything at all; however, if it’s one of those apps that continuously track user’s location throughout the day, you should probably book some time this summer for making some changes in how you do the tracking and testing possible usage scenarios.
For example, let’s consider those two apps again; the continuous background location and the significant location change monitoring app. Suppose the user goes for a run with the continuous background location app. They’ll go on the run, they’ll come back, they’ll see the solid arrow the whole time, and when they look at their map, they’ll see every twist and turn they took. When they install that app that uses significant location change monitoring, they’ll see the same thing, a solid arrow. As far as the user is aware, this app is probably receiving the same amount of information as their run tracking app. So if users are misinterpreting our signals, we decided the best way to fix this was to adjust how we indicate location usage.
watchOS has access to many of the same technologies found in iOS apps; however, even if a technology is available, you may not be able to use it in quite the same way you did on iPhone.
Do not use background execution modes for a technology. In general, Watch apps are considered foreground apps; they run only while the user interacts with one of their interfaces. As a result, the corresponding WatchKit extension cannot take advantage of most background execution modes to perform tasks. might be help this documentation:
Leveraging iOS Technologies for watch
I am currently using the Xamarin geolocation plugin found here:
https://github.com/jamesmontemagno/GeolocatorPlugin
To perform location services in an app I am building using Xamarin Forms (PCL).
I believe I have added in the relevant permission settings to allow for this.
The GPS works great while the app is active and locked (but with app in the foreground). However when the app is pushed to the background on iOS by clicking the "home" button, it still tracks the user and highlights the "App is Using Your Location" message as I would expect, however after a certain amount of time between 30-40 minutes, this message disappears, and the GPS appears to stop tracking the user until they bring the app back to the foreground.
Once the app has been brought to the foreground, it can be backgrounded once again for another 30-40 minutes to repeat the cycle.
I have ensured that the locator object allows background updates:
public static Plugin.Geolocator.Abstractions.IGeolocator locator;
locator = CrossGeolocator.Current;
locator.AllowsBackgroundUpdates = true;
locator.DesiredAccuracy = 20;
A call to .PausesLocationUpdatesAutomatically shows that this is false (which I believe is the default).
Edit
I have the following keys to info.plist:
NSLocationWhenInUseUsageDescription
NSLocationAlwaysUsageDescription
And enabled background location updates:
However I have not enabled background fetching as Girish has in the answers, is this something I need to do?
Please check whether you have enabled the background mode for location update.
There are a handful of posts discussing how Game Center's push notifications were fairly unreliable in the sandbox. However, the sandbox is obfuscated with iOS 9 so, I'm not sure why my Game Center push notifications are so unreliable.
When I reply to the active exchange, the sender is rarely notified.
[exchange replyWithLocalizableMessageKey:#"EXCHANGE_REPLY" arguments:#[] data:data completionHandler:^(NSError *error) {
if (error)
{
NSLog(#"");
}
}];
On the senders device, if I refresh the match data, I'll see a pending reply. If I process the reply, everything works.
The same goes for this method:
- (void)sendExchangeToParticipants:(NSArray<GKTurnBasedParticipant *> *)participants
data:(NSData *)data
localizableMessageKey:(NSString *)key
arguments:(NSArray<NSString *> *)arguments
timeout:(NSTimeInterval)timeout
completionHandler:(void(^__nullable)(GKTurnBasedExchange *exchange, NSError *error))completionHandler
At this point, I'm thinking my best option is to run my own push notification logic to trigger updating match data. That or I've read that sending reminders is more reliable though I believe there are throttling limits around that.
Update
I've tried using only devices and not the simulator. Same issue. Looks like it's a pretty well known problem though. It's even noted in this book on page 766.
Update
Sending reminders didn't help.
Update
Often when replying to an exchange, I'll get this error from GameKit.
The connection to service named com.apple.gamed was interrupted, but the message was sent over an additional proxy and therefore this proxy has become invalid.
Exchanges has until Oct 2020 never actually worked as needed, nor as specified, due to a bug in the Apple backend. Now however, an Apple engineer seem to suggest it has been fixed - asking me to verify that it works. Which I intend to do ASAP (I just need to update Xcode) using my public project: https://github.com/Gatada/TurnBasedGameFlow
FURTHER DETAIL
A turn based exchange relies on the turn holder being notified when the exchange is completed, so the turn holder can resolve it (submit it to Game Center). This notification however, was never pushed to the turn holder.
As a result of this bug, the games we made had to rely on the turn holder re-loading the game after the exchange completes, and our code had to gracefully handle the turn submission failing due to game data being out-of-sync (caused by the completed exchange).
I had a one-on-one Game Center session with Apple during WWDC 2020, where I reported this issue with hard evidence (after all, this bug had been around since 2010) which convinced the Apple engineer. It took them 3 months to get back to me, and another 3 months for me to get back to them - hehe, bringing us to now.