I need help. There is an application for Xamarin Android. Inside it, a service is started that works with the AltBeacon library.
In this service, a thread is created, in which beacons are constantly being scanned. Service started as StartForeground(...).
The service should work constantly, so it was decided to run it in a separate process, because after a while the android system stops allocating
memory and service terminates.
If you run the application and the service in one process, everything works fine. Service works, beacons are scanned.
But as soon as I run it in a separate process (using the Process =: myProcess attribute), the scanning not works.
The DidRangeBeaconsInRegion method does not work for the IRangeNotifier implementation object.
It simply does not work, there are no exceptions.
Brief code base:
public class BeaconsWorker : Java.Lang.Object, IBeaconConsumer
{
private string[] guids;
private readonly Context context;
private readonly BeaconManager beaconManager;
private readonly RangeNotifier rangeNotifier;
private readonly List<BeaconEntry> beacons;
public Context ApplicationContext
{
get { return context.ApplicationContext; }
}
public BeaconsWorker(Context context, string[] guids, int scanTime)
{
...
this.context = context;
rangeNotifier = new RangeNotifier();
beaconManager = BeaconManager.GetInstanceForApplication(context);
beaconManager.SetForegroundBetweenScanPeriod(1000);
beaconManager.SetForegroundScanPeriod(1000);
beaconManager.SetBackgroundMode(false);
var beaconParser = new BeaconParser();
beaconParser.SetBeaconLayout("...");
beaconManager.BeaconParsers.Add(beaconParser);
rangeNotifier.DidRangeBeaconsInRegionComplete += OnBeaconsRanging;
beaconManager.SetRangeNotifier(rangeNotifier);
}
public bool BindService(Intent intent, IServiceConnection serviceConnection, [GeneratedEnum] Bind flags)
{
return context.BindService(intent, serviceConnection, flags);
}
public void OnBeaconServiceConnect()
{
foreach (var region in beaconManager.RangedRegions.ToList())
beaconManager.StopRangingBeaconsInRegion(region);
for (int i = 0; i < guids.Length; i++)
{
var uuid = Identifier.Parse(guids[i]);
var region = new Region("R" + i, uuid, null, null);
beaconManager.StartRangingBeaconsInRegion(region);
}
}
public void UnbindService(IServiceConnection serviceConnection)
{
context.UnbindService(serviceConnection);
}
public async Task<BeaconEntry> GetLocationResult()
{
beaconManager.Bind(this);
await Task.Delay(scanTime * 1000);
beaconManager.Unbind(this);
...
return result;
}
private void OnBeaconsRanging(object sender, RangeEventArgs e)
{
lock (beacons)
foreach (var item in e.Beacons)
{
var beacon = new BeaconEntry()
{
BeaconGUID = item.Id1.ToString(),
BeaconMajor = Int32.Parse(item.Id2.ToString()),
BeaconMinor = Int32.Parse(item.Id3.ToString())
};
beacons.Add(beacon);
}
}
private class RangeEventArgs : EventArgs
{
public Region Region { get; set; }
public ICollection<Beacon> Beacons { get; set; }
}
private class RangeNotifier : Java.Lang.Object, IRangeNotifier
{
public event EventHandler<RangeEventArgs> DidRangeBeaconsInRegionComplete;
public void DidRangeBeaconsInRegion(ICollection<Beacon> beacons, Region region)
{
OnDidRangeBeaconsInRegion(beacons, region);
}
private void OnDidRangeBeaconsInRegion(ICollection<Beacon> beacons, Region region)
{
DidRangeBeaconsInRegionComplete?.Invoke(this, new RangeEventArgs { Beacons = beacons, Region = region });
}
}
It is possible to set up the Android Beacon Library to run in a separate process. You can read the basic configuration instructions here:
https://github.com/AltBeacon/android-beacon-library/pull/479
This multi-process setup was successfully tested with library version 2.11. Version 2.12, however, included significant rework to support Android 8, and I have not tested multi-process support with versions 2.12+, so use those versions with caution. Your best bet is to use version 2.11.
The instructions linked above are written for Android apps built using standard Java or Kotlin development toolset with Android Studio or Gradle. Clearly modifications are needed to make this work with Xamarin. Since I am not a Xamarin expert, it's hard for me to help more.
Related
I'm using the AltBeacon library for detecting iBeacons in my Android app. The code I have works on the following devices:
Xiaomi MI9 (Android 10.0)
Motorola Moto G4 (Android 6.0.1)
Huawei P Smart (Android 8.0)
Samsung Galaxy S8 (Android 9.0)
However, the same code doesn't work for a OnePlus 6 (Android 10, OxygenOS 10.3.2). It doesn't detect any beacons in my app. I tried to detect the beacons using an other app (Locate), that works. The creator of the AltBeacon library told me that Locate uses the AltBeacon library, so the beacons are detectable. This means my code setup is wrong. Can you help me by finding out what is wrong with my setup?
I checked (e.g.) this answer, although it didn't fix my problem. I turned debugging on for the BeaconManager but nothing interesting came out of that (an example at the bottom of this question).
In the ViewModel I call the MyStateManager. It contains a List regionsInRange, which contains beacons that are in range. I left out some code because I think it is irrelevant. If you feel like I left out too much, I will add it.
public class MyStateManager implements BootstrapNotifier {
private static final MyStateManager instance = new MyStateManager();
private final MyBeaconHelper myBeaconHelper;
// ViewModel accessess this List to retrieve the beacons that are found.
public final List<Region> regionsInRange = new ArrayList<>();
private PresenceRegistrationStateManager() {
presenceRegistrationBeaconHelper = new PresenceRegistrationBeaconHelper(this);
updateScanningRegions();
}
#Override
public Context getApplicationContext() {
return MyApplication.getAppContext();
}
#Override
public void didEnterRegion(Region region) {
//Empty method
}
#Override
public void didExitRegion(Region region) {
//Empty method
}
#Override
public void didDetermineStateForRegion(int status, Region region) {
if (status == OUTSIDE) {
regionsInRange.remove(region);
} else {
if (!regionsInRange.contains(region)) {
regionsInRange.add(region);
}
}
updateState();
}
public static MyStateManager getInstance() {
return instance;
}
public void updateState() {
// Own implementation here
}
private void updateScanningRegions() {
// add all the regions here
}
}
In addition, this is the MyBeaconHelper:
public class MyBeaconHelper implements BeaconConsumer, Serializable {
private transient final RegionBootstrap regionBootstrap;
private List<Region> scanRegions = new ArrayList<>();
public MyBeaconHelper(BootstrapNotifier bootstrapNotifier) {
BeaconManager beaconManager = BeaconManager.getInstanceForApplication(getApplicationContext());
beaconManager.getBeaconParsers().clear();
beaconManager.getBeaconParsers().add(new BeaconParser().setBeaconLayout("m:2-3=0215,i:4-19,i:20-21,i:22-23,p:24-24"));
LogManager.setVerboseLoggingEnabled(true);
beaconManager.bind(this);
regionBootstrap = new RegionBootstrap(bootstrapNotifier, new ArrayList<>());
}
#Override
public void onBeaconServiceConnect() {
//Empty method
}
#Override
public Context getApplicationContext() {
return MyApplication.getAppContext();
}
#Override
public void unbindService(ServiceConnection serviceConnection) {
getApplicationContext().unbindService(serviceConnection);
}
#Override
public boolean bindService(Intent intent, ServiceConnection serviceConnection, int i) {
return getApplicationContext().bindService(intent, serviceConnection, i);
}
public void updateScanRegions(List<Region> newRegions) {
for (Region oldRegion : this.scanRegions) {
if (!newRegions.contains(oldRegion)) {
regionBootstrap.removeRegion(oldRegion);
}
}
for (Region newRegion : newRegions) {
if (!this.scanRegions.contains(newRegion)) {
regionBootstrap.addRegion(newRegion);
}
}
this.scanRegions = newRegions;
}
}
When I turned debugging on for the BeaconManager, it showed me this a lot of times:
2020-03-31 11:57:30.181 25259-25259/com.my.app D/CycledLeScanner: starting a new scan cycle
2020-03-31 11:57:30.181 25259-25259/com.my.app D/CycledLeScanner: We are already scanning and have been for 1134 millis
2020-03-31 11:57:30.181 25259-25259/com.my.app D/CycledLeScanner: Waiting to stop scan cycle for another 1100 milliseconds
2020-03-31 11:57:30.181 25259-25259/com.my.app D/CycledLeScanner: Scan started
2020-03-31 11:57:31.213 25259-25259/com.my.app D/CycledLeScanner: Waiting to stop scan cycle for another 69 milliseconds
2020-03-31 11:57:31.323 25259-25259/com.my.app D/CycledLeScanner: Done with scan cycle
It keeps printing these lines over and over again...
The log messages shown (these are for OnePlus, yes?) indicate that BLE scanning is started. Do you see any log lines showing hex bytes of the packets detected? If BLE scanning is actually functioning you should. You may want to compare the logs output by the other devices.
Are you certain proper location permission has been granted to your app on the OnePlus? You can check in Settings -> Apps - > You App -> Permissions. Also confirm Bluetooth is on and location is on for the global phone settings (but if Locate works on the same device, this should not be a problem .)
It is not clear if this is related, but the use of beaconManager.bind() at the same time as RegionBootstrap is unnecessary and may cause conflicts. The code appears to not use the BeaconConsumer interface that is called back by the bind method. I suggest you remove the bind call, the use of BeaconConsumer and remove all that interface's callback methods just to be sure.
Help is needed. Thanks to David for the last answer to the question -AltBeacon service in separate android process.
I'm trying to implement on the Xamarin Android - AltBeacon Library in the service and in a separate process. (A separate process is needed to ensure that scanning works constantly, round the clock.
What would the android not cut the scanning after a certain time, after the phone goes into sleep mode).
1. What did I do after David's instructions -
-added attributes in the service definition:
[Service (Name = SERVICE_NAME, Process = ": myProcess", Enabled = true, Exported = false, IsolatedProcess = false)]
installed a new version of the library - AltBeacon 2.11. To date, Xamarin does not have version 2.11. The latest version is 2.7
I downloaded the AltBeacon v2.11 native library and wrapped it with Managed Callable Wrappers (MCW). [(https://developer.xamarin.com/guides/android/advanced_topics/binding-a-java-library/)][1].
As a result, I got a full library on Xamarin.
- Connected this library to the project. For testing, I removed the Process: myProcess attribute and ran the application.
In one process, everything works well. The service works and finds the bicons. Perfectly!
But -
As soon as I started the service in another process I stopped getting the bicons.
After the other method that triggers this is OnBeaconServiceConnect ().
public void OnBeaconServiceConnect()
{
for (int i = 0; i < guids.Length; i++)
{
var uuid = Identifier.Parse(guids[i]);
var region = new Region("R" + i, uuid, null, null);
beaconManager.StartRangingBeaconsInRegion(region);
}
}
It turns out that the method does not work in the Range Notifier object =(
A rough example of the implementation of the service:
public class myService:Service
{
private BeaconWorker beaconWorker;
public myService()
{
beaconWorker=new beaconWorker(DroidApplication.CurrentInstanceApp);
}
public void MainMethod()
{
var notification = ...Build();
StartForeground(s_id, notification);
while(true)
{
StartMainWork(guids)
}
}
public void StartMainWork(string guid)
{
beaconWorker.GetResult();
}
}
/////////////////////////////////////
public class BeaconWorker:IBeaconConsumer
{
List<Beacon> Beacons;
private Context context;
private RangeNotifier rangeNotifier;
private BeaconManager beaconManager;
//This is main configuring scanning
public BeaconWorker(Context context)
{
Context = context;
this.guids = ...;
rangeNotifier = new RangeNotifier();
BeaconManager.SetDebug(true);
beaconManager =
BeaconManager.GetInstanceForApplication(context);
beaconManager.SetForegroundBetweenScanPeriod(1000);
beaconManager.SetForegroundScanPeriod(1000);
beaconManager.SetBackgroundMode(false);
var beaconParser = new BeaconParser();
beaconParser.SetBeaconLayout("...");
beaconManager.BeaconParsers.Add(beaconParser);
beaconManager.SetRangeNotifier(rangeNotifier);
beaconManager.ApplySettings();
}
//Method that do bind
public void GetResult()
{
beaconManager.Bind(this);
Task.Wait(3000);
beaconManager.UnBind(this);
}
//Implement IBeaconConsumer BindService
public bool BindService(Intent p0, IServiceConnection p1, int p2)
{
return context.BindService(p0, p1, Bind.AutoCreate);
}
public void OnBeaconServiceConnect()
{
for (int i = 0; i < guids.Length; i++)
{
var uuid = Identifier.Parse(guids[i]);
var region = new Region("R" + i, uuid, null, null);
beaconManager.StartRangingBeaconsInRegion(region);
}
}
private class RangeNotifier
{
//THIS METHOD DOES NOT INVOKE =(
public void DidRangeBeaconsInRegion(ICollection<Beacon> beacons, Region region)
{
this.Beacons = beacons;
}
}
}
In the logs of the phone there is such information:
Time Device Name Type PID Tag Message
01-18 18:52:25.370 AGM A8 Warning 17056 BeaconManager Ranging/Monitoring may not be controlled from a separate BeaconScanner process. To remove this warning, please wrap this call in: if (beaconManager.isMainProcess())
Time Device Name Type PID Tag Message
01-18 18:52:25.370 AGM A8 Debug 17056 BeaconManager we have a connection to the service now
Time Device Name Type PID Tag Message
01-18 18:52:25.361 AGM A8 Debug 17056 BeaconManager consumer count is now: 1
Time Device Name Type PID Tag Message
01-18 18:52:25.363 AGM A8 Info 17024 BeaconService beaconService version 2.11 is starting up on the main process
I know about the verification David pointed out in the answer to IsMainProcess() method. But how can I call this check if all this happens in the service itself and not in the main application process?
Tell me where I'm wrong. What else can I add?
In the article https://github.com/AltBeacon/android-beacon-library/pull/479
on how much I realized that it makes no sense to specify a RangeNotifier in a separate process. Is it so?
I will be grateful for any help! Thank you!
I want to know, does any way exist to communicate with system during instrumentation test execution.
For example:
I have a phone with IR port on onboard & I can work with it through private SDK, also I can tune it with my application. In my Instrumentation test cases I want test app behavior based on external events which I want to configure before test separate test execution.
It's looks like
#Test
public void test() throws Exception {
setupExternalCondition(condition1_ON); // setup external transiver
assertNotNull(IR.read());
assertTrue(assertIR.write());
setupExternalCondition(condition1_OFF);
assertNotNull(IR.read());
assertFalse(IR.write());
}
It's very simple example but there is a lot of "conditions", and sdk updating frequencies to high. I can't do all of this verification manually, and can't ask "transiver&SDK team" make a mock states list for writing just a unit test for coverage. So I want somehow inject external component execution to TestRuner for receiving events(or testName before test case execution) on local machine(or CI machine) to setup external condition.
Simple solution(I think) to run a tcp server on appUnderTest and request external condition change - I am not sure does it possible, and not sure about stable connection(wifi), so may be it's possible to do over adb.
Any suggestions?
P.S: test device has root permissions.
So, find not bad but not ideal solution.
Still wait for better proposition, if not may be this answer will be helpful for someone;
For "building the bridge" between local machine and AndroidJUnitTest I add next class to tests:
class IPCServiceBridge extends BroadcastReceiver {
private static final String FILTER_ID = "IPC_SERVICE";
private static IPCServiceBridge sInstance;
private boolean mIsPermitted;
#Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals("ipc.service.action")) {
mIsPermitted = true;
}
}
public static IPCServiceBridge getInstance() {
if (sInstance == null) {
sInstance = new IPCServiceBridge();
IntentFilter filter = new IntentFilter();
filter.addAction("ipc.service.action");
Context context = InstrumentationRegistry.getContext();
context.registerReceiver(sInstance, filter);
}
return sInstance;
}
public void sendIpcCommand(String commandName) {
try {
int i = 30;
mIsPermitted = false;
while (i > 0) {
pub("request:" + commandName);
Thread.sleep(1000);
if (mIsPermitted) {
break;
}
i--;
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
if (!mIsPermitted) {
throw new RuntimeException("IPC service does not respond");
}
}
private static void pub(String msg) {
Log.e(FILTER_ID, msg);
}
}
Than I start adb logcat -s "filter_name", parse and check which condition should be applied for InsttUnit test. When conditions is ready i send back broadcast receiver with required action.
#Test
public void test2() throws Exception {
IPCServiceBridge.getInstance().sendIpcCommand("CONDITION#123");
}
Work good, but I'm not sure that it will be super stable.
I am using AltBeacon Android Library (I reproduced issue with v2.9.2; and also with v2.11) for integrating with iBeacon devices provided by Onyx and kontact.io.
The library seems to work very well, but I seem to have an issue with it for which I could not find an acceptable solution.
Here are some more details about how I use AltBeacon Library and about the issue:
Device is stationary near the beacon
Bluetooth on
Application runs in foreground
The BeaconManager is configured to scan in foreground mode with the following settings:
BeaconManager.setRegionExitPeriod(30000L);
beaconManager.setBackgroundBetweenScanPeriod(120000L);
beaconManager.setForegroundScanPeriod(5000L);
beaconManager.setForegroundBetweenScanPeriod(10000L);
beaconManager.getBeaconParsers().add(
new BeaconParser().setBeaconLayout("m:2-3=0215,i:4-19,i:20-21,i:22-23,p:24-24"));
Application sets the BeaconManager in foreground mode
beaconManager.setBackgroundMode(false);
Application bounds to the BeaconManager
beaconManager.bind(…)
When onBeaconServiceConnect() is triggered, the application starts monitoring beacons in specific regions (the list of beacons I want to monitor is known, static; I use a list of regions, one different region for each beacon I want to monitor)
beaconManager.startMonitoringBeaconsInRegion(region);
When device enters beacon region (didEnterRegion() is called) application starts ranging for entered region
beaconManager.startRangingBeaconsInRegion(region);
Beacon is detected (didRangeBeaconsInRegion() is called for corresponding beacon)
Application switched beacon scanning to background mode:
beaconManager.setBackgroundMode(true);
After a few minutes, the didExitRegion() is called even if the device and the beacon were not moved and the application remained in the same state.
I have found two Stackoverflow issues which describe the same issue:
AltBeacon unstable for OnyxBeacons, cycling through didEnterRegion and didExitRegion repeatedly
http://stackoverflow.com/questions/40835671/altbeacon-reference-app-and-multiple-exit-entry-calls
The workaround that I currently use is the one suggested in the Stackoverflow issues:
I have updated beacon Advertising Frequency value from 1000 ms to 100 ms.
Once the frequency is increased, everything seems to work fine, but
the solution is not acceptable because the battery life of the beacon is
drastically impaired.
All the beacon scanning is performed in background (i.e. no Activity is used):
import android.Manifest;
import android.bluetooth.BluetoothAdapter;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.RemoteException;
import android.support.annotation.NonNull;
import org.altbeacon.beacon.Beacon;
import org.altbeacon.beacon.BeaconConsumer;
import org.altbeacon.beacon.BeaconManager;
import org.altbeacon.beacon.BeaconParser;
import org.altbeacon.beacon.Identifier;
import org.altbeacon.beacon.MonitorNotifier;
import org.altbeacon.beacon.RangeNotifier;
import org.altbeacon.beacon.Region;
import org.altbeacon.beacon.powersave.BackgroundPowerSaver;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
public class BeaconDataProvider implements BeaconConsumer, RangeNotifier, MonitorNotifier {
private final Logger LOGGER = LogFactory.get(this);
private final Context applicationContext;
private final BeaconIdentifierFactory beaconIdentifierFactory;
private final BeaconScanningListener beaconScanningListener;
private BeaconManager beaconManager;
private Collection<Region> targetedRegions;
/**
* This field is used for improving battery consumption. Do not remove it.
*/
#SuppressWarnings({"unused", "FieldCanBeLocal"})
private BackgroundPowerSaver backgroundPowerSaver;
public BeaconDataProvider(Context applicationContext, BeaconIdentifierFactory beaconIdentifierFactory,
BeaconScanningListener beaconScanningListener) {
LOGGER.v("BeaconDataProvider - new instance created.");
this.applicationContext = applicationContext;
this.beaconIdentifierFactory = beaconIdentifierFactory;
this.beaconScanningListener = beaconScanningListener;
beaconManager = BeaconManager.getInstanceForApplication(applicationContext);
LOGGER.v("BeaconManager hashCode=%s", beaconManager.hashCode());
BeaconManager.setRegionExitPeriod(30000L);
beaconManager.setBackgroundBetweenScanPeriod(120000L);
beaconManager.setForegroundScanPeriod(5000L);
beaconManager.setForegroundBetweenScanPeriod(10000L);
beaconManager.getBeaconParsers().add(
new BeaconParser().setBeaconLayout("m:2-3=0215,i:4-19,i:20-21,i:22-23,p:24-24"));
backgroundPowerSaver = new BackgroundPowerSaver(applicationContext);
}
public void setBackgroundMode() {
LOGGER.i("setBackgroundMode()");
beaconManager.setBackgroundMode(true);
}
public void setForegroundMode() {
LOGGER.i("setForegroundMode()");
beaconManager.setBackgroundMode(false);
}
public boolean checkAvailability() {
return android.os.Build.VERSION.SDK_INT >= 18 && applicationContext.getPackageManager()
.hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE);
}
public boolean isBluetoothEnabled() {
BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
boolean result = mBluetoothAdapter != null && mBluetoothAdapter.isEnabled();
LOGGER.i("isBluetoothEnabled() -> %s", result);
return result;
}
public boolean isLocationPermissionGranted(Context context) {
return (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) || (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
&& context.checkSelfPermission(Manifest.permission.ACCESS_COARSE_LOCATION)
== PackageManager.PERMISSION_GRANTED);
}
public void startScanning(Collection<BeaconIdentifier> targetedBeacons) {
LOGGER.i("startScanning()");
if (!beaconManager.isBound(this)) {
this.targetedRegions = getRegionsForTargetedBeacons(targetedBeacons);
beaconManager.bind(this);
}
else {
LOGGER.i("Scanning already started.");
}
}
#NonNull
private List<Region> getRegionsForTargetedBeacons(Collection<BeaconIdentifier> beaconIdentifiers) {
List<Region> regions = new ArrayList<>();
for (BeaconIdentifier beaconIdentifier : beaconIdentifiers) {
try {
Region region = new Region(beaconIdentifier.getRegionId(), Identifier.parse(beaconIdentifier.getUuid()),
Identifier.parse(String.valueOf(beaconIdentifier.getMajor())),
Identifier.parse(String.valueOf(beaconIdentifier.getMinor())));
regions.add(region);
}
catch (Exception e) {
LOGGER.e("Caught exception.", e);
LOGGER.w("Failed to create region for beaconIdentifier=%s", beaconIdentifier.getCallParamRepresentation());
}
}
return regions;
}
public void stopScanning() {
LOGGER.i("stopScanning()");
if (beaconManager.isBound(this)) {
for (Region region : targetedRegions) {
try {
beaconManager.stopMonitoringBeaconsInRegion(region);
}
catch (RemoteException e) {
LOGGER.e("Caught exception", e);
}
}
beaconManager.unbind(this);
}
}
#Override
public void didEnterRegion(Region region) {
LOGGER.v("didEnterRegion(region=%s)", region);
beaconScanningListener.onEnterRegion(region.getUniqueId());
try {
beaconManager.startRangingBeaconsInRegion(region);
}
catch (RemoteException e) {
LOGGER.e("Caught Exception", e);
}
}
#Override
public void didExitRegion(Region region) {
LOGGER.v("didExitRegion(region=%s)", region);
beaconScanningListener.onExitRegion(region.getUniqueId());
try {
beaconManager.stopRangingBeaconsInRegion(region);
}
catch (RemoteException e) {
LOGGER.e("Error", e);
}
}
#Override
public void didDetermineStateForRegion(int state, Region region) {
LOGGER.v("didDetermineStateForRegion(state=%s, region=%s)", state, region);
}
#Override
public void didRangeBeaconsInRegion(Collection<Beacon> beacons, Region region) {
LOGGER.v("didRangeBeaconsInRegion(size=%s, region=%s, regionUniqueId=%s)", beacons.size(), region,
region.getUniqueId());
if (beacons.size() > 0) {
beaconScanningListener.onBeaconsInRange(beaconIdentifierFactory.from(beacons, region.getUniqueId()));
}
}
#Override
public void onBeaconServiceConnect() {
LOGGER.v("onBeaconServiceConnect()");
beaconManager.addRangeNotifier(this);
beaconManager.addMonitorNotifier(this);
for (Region region : targetedRegions) {
try {
beaconManager.startMonitoringBeaconsInRegion(region);
}
catch (RemoteException e) {
LOGGER.e("Caught exception", e);
}
}
}
#Override
public Context getApplicationContext() {
return applicationContext;
}
#Override
public void unbindService(ServiceConnection serviceConnection) {
LOGGER.v("unbindService()");
applicationContext.unbindService(serviceConnection);
}
#Override
public boolean bindService(Intent intent, ServiceConnection serviceConnection, int i) {
LOGGER.v("bindService()");
return applicationContext.bindService(intent, serviceConnection, i);
}
}
public class BeaconIdentifier {
private final String uuid;
private final int major;
private final int minor;
private String regionId;
public BeaconIdentifier(String uuid, int major, int minor) {
this.uuid = uuid;
this.major = major;
this.minor = minor;
}
public int getMinor() {
return minor;
}
public int getMajor() {
return major;
}
public String getUuid() {
return uuid;
}
public String getCallParamRepresentation() {
return (uuid + "_" + major + "_" + minor).toUpperCase();
}
public String getRegionId() {
return regionId;
}
public void setRegionId(String regionId) {
this.regionId = regionId;
}
#Override
public boolean equals(Object o) {
if (o != null) {
if (o instanceof BeaconIdentifier) {
BeaconIdentifier other = (BeaconIdentifier) o;
return this == other || (this.uuid.equalsIgnoreCase(other.uuid)
&& this.major == other.major && this.minor == other.minor);
}
else {
return false;
}
}
else {
return false;
}
}
#Override
public int hashCode() {
int result = 17;
result = 31 * result + (uuid != null ? uuid.toUpperCase().hashCode() : 0);
result = 31 * result + major;
result = 31 * result + minor;
return result;
}
#Override
public String toString() {
return "BeaconIdentifier{" +
"uuid='" + uuid + '\'' +
", major=" + major +
", minor=" + minor +
", regionId='" + regionId + '\'' +
'}';
}
}
The BeaconDataProvider is used as a single instance per application; It is instantiated by Dagger 2 when the Android Application is created. It has #ApplicationScope lifecycle.
The beacon scanning is first started`in foreground mode from an Android IntentService:
beaconDataProvider.setForegroundMode();
beaconDataProvider.startScanning(targetedBeacons);
Once the device enters the region and the beacon is detected, beacon scanning is switched to background mode:
beaconDataProvider.setBackgroundMode();
At first I thought there was something wrong with the Onyx Beacons I was using, but I could reproduce the same issue with the Kontact IO Beacons.
Do you have any suggestions?
Am I miss-using the AltBeacon Android Library?
Thanks,
Alin
The fundamental cause of a call to didExitRegion() is the fact that no BLE beacon advertisement packets matching the region were received by the Android bluetooth stack in the previous 10 seconds. (Note: This value is configurable with BeaconManager.setRegionExitPeriod(...).)
There are several things that could be causing these spurious didExitRegion() calls:
A beacon is not advertising frequently enough.
A beacon is advertising with a very low radio signal.
There is too much radio noise in the vicinity for reliable detections.
The receiving device has a poor bluetooth antenna design causing weaker signals to not get detected.
The receiving device is too far away to reliably detect the beacon.
The foregroundScanPeriod or backgroundScanPeriod is set too short to get a guaranteed detection
Given the setup you've described, I suspect that when you have the beacon transmitting at 1Hz, some combination of 1-4 is causing the problem. You will have to experiment with each of these variables to see if you can isolate the problem to one predominant issue. But again, more than one may be at play at the same time.
Understand that even under good conditions only 80-90 percent of beacons packets transmitted over the air are received by a typical Android device. Because of this, if you have a setup where only 1-5 beacon packets are typically received in a 10 second period, you'll still sometimes get exit events if you get unlucky and a few packets in a row get corrupted by radio noise. There is no way to guarantee this won't happen. You can just make it statistically more unlikely by setting up your system so under nominal conditions it receives as many packets as possible in a 10 second period, so this becomes more unlikely.
Increasing the advertising rate is the easiest way to fix this, because it gives you more statistical chances of getting packets detected in any 10 second period. But as you have seen, there is a tradeoff in terms of battery life.
If you want do preserve battery life but don't care about the time it takes to get a didExitRegion callback, then you may want to modify BeaconManager.setRegionExitPeriod(...) to 30,000 milliseconds or more until the problem goes away.
The above discussion is specific to the configuration of the Android Beacon Library, the same theoretical ideas apply to any beacon detection framework including iOS Core Location. You sometimes see spurious exit events with that framework as well.
I think the problem is here:
beaconManager.setForegroundScanPeriod(5000L);
beaconManager.setForegroundBetweenScanPeriod(10000L);
You should generally set the scanPeriod to 5100 ms or more, because beacons that advertise have a slight chance of being missed if their transmission is always on the boundary of when you start and stop scanning.
So try:
beaconManager.setForegroundScanPeriod(5100L);
beaconManager.setForegroundBetweenScanPeriod(10000L);
Hope it helps. Let me know if works.
As a workaround to this issue, I have implemented some extra logic to consider a didExitRegion() event only if the corresponding didEnterRegion() is not called in a certain time interval (5 minutes in my case, but this can be adjusted).
I would like to know the purpose of foloowing two files:
frameworks/base/core/java/android/app/IActivityWatcher.aidl
[description: Callback interface to watch the user's traversal through activities.]
frameworks/base/core/java/android/app/IProcessObserver.aidl
[no description]
I am trying to build an app wherein user can decide which apps can be run during particular period of time (say, from 10am till 4pm).
Is there any way where my app will get notified if one the apps specified by the user starts? This way my app can send kill command (I am assuming that root access is available.)
It seems that IActivityWatcher has been removed beginning with JellyBean, in order to monitor which Activity is running foreground, you can use IProcessObserver as following:
mActivityManagerNative = ActivityManagerNative.getDefault();
if (mActivityManagerNative != null) {
try {
mActivityManagerNative.registerProcessObserver(mProcessObserver);
} catch (RemoteException e) {
Log.e("TAG", "onCreate() RemoteException!");
}
}
private IProcessObserver.Stub mProcessObserver = new IProcessObserver.Stub() {
#Override
public void onForegroundActivitiesChanged(int pid, int uid, boolean foregroundActivities) {
doWhatUWantHere();
}
#Override
public void onImportanceChanged(int pid, int uid, int importance) {
}
#Override
public void onProcessDied(int pid, int uid) {
}
};
P.S.
You can use following code snippets to get the package name of foreground running Activity:
private String getForegroundPackage() {
ActivityManager am = (ActivityManager)getSystemService(Context.ACTIVITY_SERVICE);
List<RecentTaskInfo> taskInfo = am.getRecentTasks(1,
ActivityManager.RECENT_IGNORE_UNAVAILABLE);
return taskInfo.isEmpty()
? null : taskInfo.get(0).baseIntent.getComponent().getPackageName();
}