petermetz / cordova-plugin-ibeacon

An iBeacon plugin for Phonegap / Cordova 3.x and upwards. Supports both iOS and Android (contributions are welcome)
Apache License 2.0
727 stars 372 forks source link

Background iBeacon Detection for Android Lollipop #141

Open cvoisey opened 9 years ago

cvoisey commented 9 years ago

With the latest release of Android, Lillipop, the iBeacon service is now enabled in the background and we should be able to detect an iBeacon even if the app is closed / killed / removed from the Task Manager. I've done a few tests but it appears this is not working. Is there another call I need to make in order to register the iBeacon search is happening in the OS? Or is this something that has to be changed in the Cordova plugin and updated?

jdtaylor91 commented 9 years ago

I have also been unable to get the iBeacon detection working in the background for Android, however it works perfectly for iOS. Please could the Cordova plugin be updated to allow for this?

mrtree1 commented 9 years ago

This project doesn't have any active development going on at the moment, so it mainly relies on pushes for new features. So if you or another developer would like to look at the latest AltBeacon library and see if your feature is supported or supportable, and pushes an update to the project I'd be happy to merge it in.

jdtaylor91 commented 9 years ago

I have taken a look but have not made any progress, i am not familiar enough with cordova plugins and do not have enough time to continue investigating, however i have posted my findings below if anyone at a later stage tries to pick this up - hopefully this may point them in the right direction.

The current implementation for when the iBeaconManager is initialised should be extended to add a new RegionBootstrap, however this would require the LocationManager to implement the BootstrapNotifier interface and would therefore involve making multiple changes. The following code examples may be of use:

The 'Starting an App in the Background' section is worth a read. http://altbeacon.github.io/android-beacon-library/samples.html

Also the following implementation example may be helpful: https://github.com/AltBeacon/android-beacon-library-reference/blob/master/app/src/main/java/org/altbeacon/beaconreference/BeaconReferenceApplication.java

Crashthatch commented 8 years ago

With the help of @jdtaylor91's post above and @mrtree1 I've been looking at this the last few days.

It is possible to create a RegionBootstrap within LocationManager and make LocationManager extend BootstrapNotifier (as suggested above), but that only attaches to the CordovaActivity, not the application, so goes away when the activity dies (eg. is swiped away from the Application Switcher) and stops monitoring for beacons (At least, I think this is why).

The altbeacon suggestion is to use a custom application class instead of attaching to the CordovaActivity (https://github.com/AltBeacon/android-beacon-library/issues/151). I was able to add a BackgroundBeaconApplication class which extends Application, and add android:name="BackgroundBeaconApplication" to the in AndroidManifest.xml. That application is able to set up a RegionBootstrap, and the application is correctly relaunched when it is closed (swiped away) or on boot, and it searches for beacons in the background. However, requiring the plugin to take control of the entire Application, and add to isn't very Cordova friendly (eg. only one plugin could ever have control of the Application etc.)

Instead, I've created a Service (which lives without the CordovaActivity) which extends BootstrapNotifier, contains the callback methods (eg. didEnterRegion) and creates the RegionBootstrap. It is started whenever the application is launched (from LocationManager.initalize) or on device boot using a BroadcastReceiver. I'm not entirely sure why I need to do this- I thought RegionBootstrap was supposed to take care of starting monitoring and kicking off the BootstrapNotifier? It did when I extended Application.

Outstanding problems I had to hard-code the beacon UUID to listen for in the background because it needs to know at device-boot time, before the javascript is run, what to start searching for. This could perhaps be solved by writing to persistent storage when regions are created from js and then loading that in the BackgroundBeaconService and creating RegionBootstraps (Possibly this is going to be handled by altbeacon library in future? https://github.com/AltBeacon/android-beacon-library/issues/137).

There's no way to trigger a javascript callback from the background monitoring because Cordova doesn't exist yet (the activity did not yet start). It's possible to create the CordovaActivity in the BackgroundBeaconService.didEnterRegion() handler, but that starts the app in the foreground and is very intrusive (Who's going to keep an app that reopens itself in the foreground whenever it sees a beacon!?). For my purposes, I'm just going to create a notification in native Java, and have that open the application when clicked, but ideally the plugin would have a way around this that avoids the need to write native Java and is consistent with iOS.

I haven't actually tried doing anything more than printing to the log, or tested whether background monitoring interferes with the existing foreground monitoring yet. I suspect the timeouts will need adjusting, and possibly the activity will need to reset monitoring time (to eg. once per second) while it's in the foreground.

Some exceptions/warnings around creating / crashing / relauching services. Could probably be solved by someone who knows a bit more about Android Service and lifecycle events than me. eg. "I/ActivityManager( 3494): Waited long enough for: ServiceRecord{2f557213 u0 io.cordova.hellocordova/com.unarin.cordova.beacon.BackgroundBeaconService}" and https://gist.github.com/Crashthatch/aaaa8246b8e24f491eba

Code The changes I made to this plugin so far: https://github.com/Crashthatch/cordova-plugin-ibeacon/commit/6ceb5f04b6a4ca8fe31d1c591d75227142cdb3a8

A working bare-bones Cordova app demonstrating use of the plugin. Run using Eclipse, then watching logcat after closing and then swiping away the app from the app-switcher shows the counter and that it's still monitoring every 300 seconds: https://github.com/Crashthatch/background-ibeacon-cordova-example/tree/master/platforms/android

tldr: I've made some changes that you can use to trigger notifications on Android when the app is closed, but more work is needed to merge this into the plugin.

r-cohen commented 8 years ago

After a few tests on Android (lollipop) I have noticed just about the same behaviors as you guys. When the app is paused, then the service works as intended (even when the device is off), and the service is able to call any javascript code registered as beaconDelegate. Unfortunately, as soon as the app is killed, the service which continues to run in background isn't able to run the delegated javascript, which is normal if you think of how Cordova plugins are architectured and considering the lifecycle of the javascript inside the app context. Once the app is killed, that javascript code context doesn't exist anymore, so the native plugin code cannot call it.

@Crashthatch I am also working on a similar Service but most of it is native code and not generic enough to be inside the Cordova plugin. I am basicly triggering notifications and serializing data locally.

Crashthatch commented 8 years ago

@phearme That's consistent with my understanding of what's happening. Do you know how Cordova usually triggers background events? Is there any way to initialize the javascript without opening the UI? I don't know enough about Cordova plugins to know if this is possible?

There's some further discussion in this altBeacon thread, but essentially the same conclusions. If anyone with more knowledge of how Cordova plugins could be made to handle javascript background triggers, please chime in to help @davidgyoung.

r-cohen commented 8 years ago

@Crashthatch The Cordova plugin architecture is a "bridge" interface between the native code and the javascript running inside the webview inside the app. On Android, this webview can be a standard webview component or Crosswalk webview (chromium based webview). To initialize the javascript without opening the UI, you would need to instantiate the webview (without showing it), wait for it to finish loading, and then hook up the webview to the Cordova interface. From that point on, you can call javascript from the native code. Does that sound like what you are looking for?

Crashthatch commented 8 years ago

@phearme Yes, that sounds like what I want to do, but how do I do that without opening the UI? I experimented with using an Intent with CreateActivity and using flags, and also by sending the CordovaActivity to the background when started, but that still caused the app to be visible for a second before it went away. Is there a way to start the CordovaActivity completely in the background without the user's knowledge so that javascript can then be used?

r-cohen commented 8 years ago

@Crashthatch Have you tried setting the theme to Theme.NoDisplay in the onCreate of MainActivity (the CordovaActivity)? Maybe that could help you.

setTheme(android.R.style.Theme_NoDisplay);
r-cohen commented 8 years ago

@Crashthatch just a note: even if you do manage to do this, I don't think you will be able to recall any javascript code which was previously registered as callbacks in the plugin. these are just pointers to functions which don't exist anymore in memory: it's a new instance of your webview, so a new instance of the functions you are registering. I hope you are able to understand this. Although you could run javascript code once the webview is instanciated, you would have to specifically send the whole code, or tell the javascript to load the code which you want to run. The usual javascript callback paradigm which is used in the Cordova interface doesn't do this for you.

davidgyoung commented 8 years ago

I'm no Cordova expert, but my colleagues and I have looked into this before, and we could not find a way to execute JavaScript before the UI appears to initialize the BeaconRegions to look for. I think we have to look for a creative solution to bridge this natively.

I think we could solve this problem with persisting previously-configured BeaconRegions in the Android Beacon Library and automatically loading them back up on device restart. The library could then instantiate a registered Cordova Java bridge class with an empty constructor via reflection, and call a didEnterRegion(...) method on it indicating that the app had been launched via the Android Beacon Library due to beacon detection. This bridge class would be part of the beacon plugin.

The trick is that what would this bridge class do? I think it should do one of two things:

  1. Auto launch the main visible Activity of the application.
  2. Send a notification with a configured message, tapping which does an auto launch of the main visible Activity of the application.

I think #2 is a better option here. (Nobody likes an application just popping up on them!) But #2 adds complexities of configuring the notification particulars in the bridge class before any JavaScript code had been executed. Using the same techniques as above, this configuration info would need to be persisted somewhere from the previous run of the app, and then re-loaded to the bridge class could take action on it.

r-cohen commented 8 years ago

@davidgyoung I vote for number 2, but the whole notification stuff will be handled on the native side anyways I suppose, so it would maybe be wise to pass the option to notify or not, along with notifications options, when listening to a specific region. something like:

var notification = {
  title: "proximity",
  text: "beacon nearby",
  data: region.uuid,
  ...
};
cordova.plugins.locationManager.startMonitoringForRegion(region, notification);

this could be inspired from: https://github.com/katzer/cordova-plugin-local-notifications

Also, the notification should be cleared/cancelled when beacon is not in region anymore. When notification is tapped, application's activity could be started with region data passed as parameter through the startActivity's extras in intent, these could be then fetched from the Cordova app with this plugin for exemple: https://github.com/Tunts/WebIntent/ or this: https://github.com/Initsogar/cordova-webintent

nraboy commented 8 years ago

I can confirm that in Android 6.0 Marshmallow, didDetermineStateForRegion still does not fire in the background.

Anyone come up with a possible solution yet for Android? Works great for iOS.

mahees commented 8 years ago

hi there everyone,

Having same issue, when monitoring and i swipe away the app no events are triggered/listened for.

Im wondering how this is done here, because even though we swipe away the app, push notifications still work - https://github.com/phonegap/phonegap-plugin-push.

I was trying to figure out how i can use https://github.com/katzer/cordova-plugin-background-mode to keep the monitoring going in a background service, but didn't work.

This is a native app that works well, displaying local hello and goodbye notifications when it sees a beacon, even though the app is swiped away, or screen off in pocket. In the running process list i see it as (one process and one service) - https://play.google.com/store/apps/details?id=kr.co.ixsoft.ibeacon.scanner&hl=en

Any help/advise/direction would be helpful.

Thanks!

andersborgabiro commented 8 years ago

This is a rather important feature in my opinion.

I found this description: http://altbeacon.github.io/android-beacon-library/resume-after-terminate.html

See java/org/altbeacon/beacon/startup/RegionBootstrap

This class is not used by the plugin, but maybe by the library. I didn't check that far.

As indicated by one poster. this (probably?) also requires saving and triggering on the looked for regions before any PhoneGap/Cordova code has been performed, so registered regions need to be saved persistently. That then also requires a way to empty the set of saved regions, so they can be changed over time, and not just accumulated. I figure the library doesn't have a limit for how many regions can be monitored, but it's still a good thing to be able to start from scratch when e.g. installing a new version of the app.

And put this way, it's not just for Lollipop, right?

Regards, Anders

davidgyoung commented 8 years ago

The 2.8 beta version of the Android Beacon Library now persists regions. So the next step is to write a Cordova bridge class per my previous comment. If anybody is a Cordova plugin developer who is interested in working on this, please reply here.

andersborgabiro commented 8 years ago

Thanks. That's appreciated.

Are they ever flushed?

I'll let Peter Metz know, unless he already checks this thread. He just upgraded to 2.7.1. I'm Android and PhoneGap/Cordova developer, yet not a plugin developer.

davidgyoung commented 8 years ago

Yes, the monitored regions are flushed if the app exits normally, -- e.g. if the user hits back to exit the app, or if code causes a finish() call on the only activity bound to the beacon scanning service. If the app terminates by being killed due to memory pressure, power off, or being killed by the task switcher, monitored regions are loaded from persistent storage when the app and scanning service starts back up, either automatically or after power-on.

andersborgabiro commented 8 years ago

For my app(s) it would be best if the app was simply launched in a UI-less way, so it could sort out what notifications (if any) should be generated based on monitored UUID region, actual major and minor ID, as well as proximity to the beacon.

Actually how it works today, but after manually launching the app and then the user hiding it.

Kicking notifications from the plugin would not help much, as the plugin would not know what to say, except something like "X wants your attention" or "X wants to start".

Also, regarding local notifications: cordova-plugin-local-notifications is still broken, and no one seems to work on it: "Error: Expected plugin to have ID "org.apache.cordova.device" but got "cordova-plugin-device"" The only one I've found working is cordova-plugin-local-notifications-mm.

davidgyoung commented 8 years ago

You really cannot run JS in the browser in a UI-less way. My proposal above is to run native code in a UI-less way and have a pre-configured mapping in non-volatile storage of beacon Identifier to notification message. Tapping the notification would simply bring up the UI.

petermetz commented 8 years ago

Hi Guys,

I've just finished reading this thread, sorry for the very long inactivity. My ideal solution would be to have this work as it does on iOS, e.g. the whole Cordova app is launched headless and the JavaScript code that one wrote to handle things is evaluated.

If we can't achieve this then I agree with the proposed solutions that we aim to at least cover the most common use cases (send notification) through some declarative configuration.

But I'm new to the thread and therefore still have some hope that we don't have to go there :-)

In the end what we need is a (any) webview implementation that doesn't become useless without an activity + also possible to integrate with Cordova (so that when evaluating the JS code, the Cordova plugins would load without error, and the delegate calls could be received by the JS code.

Did anybody try playing with JX Core? https://github.com/jxcore/jxcore-cordova/blob/master/README.md

My other idea is to try and see if crosswalk webviews can work without the activity(I think they can)

If anybody has time to run some checks on these (or just explain me why I'm wrong :-) ), make it known here please, otherwise I'll try to do it myself (eventually).

Kind regards, Peter

andersborgabiro commented 8 years ago

When my app is in the background it's kicked to check what needs to be done, and it fires notifications when it detects valid beacon associations. Whether the app is then paused I don't know, but it's obvious that Javascript runs and nothing is shown on the display except possible local notifications from my app.

I've set LaunchMode SingleInstance so that when I click on a notification Android doesn't get the idea to relaunch the app.

So I get the impression the same could be achieved after a reboot. It would be easy to test.

petermetz commented 8 years ago

@andersborgabiro This should be fine if your app is in the background. But what about the case when the app is in a killed state and needs to be "kicked", without the user noticing anything?

andersborgabiro commented 8 years ago

I tested cordova-plugin-autostart that does what I need, except it brings the app to the foreground, supposedly because it has to.

I noticed it takes a long time before the app is started, but it is no doubt started, and does its thing while the device is idle, as I got a local notification.

05bca054 commented 7 years ago

Hi petermetz,

First of all awesome piece of work.

Any idea when this solution delivers(Approx. month)?

So that i can take decision whether to move ahead or migrate app to native platform.

Dahkon commented 7 years ago

Any progress on this matter ?