2

I'm building an app to detect iBeacons on Android. The basic functionality is to store the advertised data by the beacon onto the device and upload it to a server. For this purpose, I'm using Android Beacon Library.

To run it in the background on Android O I'm using foreground services. The problem is when the App is in the background for more than 30 mins, after detecting a beacon, when the user exits the beacon region and didExitRegion is called, somehow, the service is automatically killed and it restarts thus no data is uploaded to the server. Also after restarting, on a second didExitRegion call, it stops completely and randomly sometime in the future, it restarts but to do the same loop all over again.

Sequence of events as they happen when app comes in regions after being inactive for around 30min

First Restart after didExit (Image)
Here you can see switching from region 11 to 9. Midway app closed and retriggered instantaneously, however no push was sent

Exit after second didExit (Image)
Next up: when exiting from this region now, app again stops in the background. But this time does not get re-triggered immediately. This is the exact sequence taking place always.

Code Snippets
BeaconScanner.java

        @Override
        public void didExitRegion(Region region) {
            Log.d(TAG, "Exited A Region");
            if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
                notificationHelper.notify(2, notificationHelper.getNotification("BeaconScanner", "Exit Major #"+previousMajor, false));
            else
                utils.dispatchNotification("Exit Major #"+previousMajor, 1);

            Log.e(TAG, "DidSend "+didSend+" has Data "+userData.hasPendingData());
            if(didSend && userData.hasPendingData()) {
                JSONObject data = userData.getJsonFromUser();
                Log.d(TAG, "Timestamp = "+System.currentTimeMillis());
                userData.addTimestamp(""+System.currentTimeMillis());
                userData.requestDataSync(data);
                userData.clearBeaconData();
                Log.d(TAG, data.toString());
                didSend = !didSend;
            }
            previousMajor = -1000;
            lastBeacon = resetBeacon;
        }

User.java

JSONObject getJsonFromUser() {
    Log.d(TAG, "Timestamp as in getJsonFromUser "+timestamp);
    JSONObject json = new JSONObject();
    try {
        json.put("email", email);
        json.put("name", name);
        JSONArray beaconArray = new JSONArray();
        for (Beacon beacon : beaconData){
            beaconArray.put(new JSONObject()
                    .put("major", beacon.getId2().toInt())
                    .put("minor", beacon.getId3().toInt())
                    .put("uuid", beacon.getId1().toString())
            );
        }
        json.put("data", beaconArray);
        Log.d(TAG, timestamp);
        json.put("timestamp", ""+System.currentTimeMillis());
        return json;

    } catch (Exception e){
        Log.e(TAG, e.getMessage());
        Crashlytics.log(e.getLocalizedMessage());
    }
    return json;
}

void requestDataSync(final JSONObject json){
    User.syncing = true;
    Crashlytics.log(1, "User.java", "Requesting Auth Token");
    FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser();
    user.getIdToken(true).addOnCompleteListener(new OnCompleteListener<GetTokenResult>() {
        @Override
        public void onComplete(@NonNull Task<GetTokenResult> task) {
            if(task.isSuccessful()){
                final Task<GetTokenResult> t = task;
                Thread thread = new Thread(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            trustAllHosts();
                            URL url = new URL("https://indpulse.com/generatetoken");
                            HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
                            connection.setHostnameVerifier(DO_NOT_VERIFY);
                            connection.setRequestProperty("Authorization", ""+t.getResult().getToken());
                            connection.setRequestMethod("GET");
                            Log.d(TAG, t.getResult().getToken());
                            connection.setDoOutput(true);
                            connection.connect();
                            BufferedReader br = new BufferedReader(new InputStreamReader(connection.getInputStream()));
                            StringBuilder response = new StringBuilder();
                            String packet;
                            while((packet = br.readLine()) != null){
                                response.append(packet);
                            }
                            JSONObject responseObject = new JSONObject(response.toString());
                            String idToken = responseObject.getString("token");
                            Crashlytics.log(1, "User.java", "Auth Token Acquired");
                            sendData(json, idToken);
                        } catch (MalformedURLException e){
                            Log.e(TAG, "Malformed URL "+e.getLocalizedMessage());
                            Crashlytics.log(e.getLocalizedMessage());
                        } catch (IOException e){
                            Log.e(TAG, "IOExeption "+e.getLocalizedMessage());
                            Crashlytics.log(e.getLocalizedMessage());
                        } catch (JSONException e) {
                            Log.d(TAG,"Json error");
                            Crashlytics.log(e.getLocalizedMessage());
                        }
                    }
                });
                thread.start();
            }
        }
    });

void sendData(JSONObject json, final String idToken){
    final String sJson = json.toString();
    System.out.println(sJson);
    Thread thread = new Thread(new Runnable() {
        @Override
        public void run() {
            try {
                URL url = new URL("https://indpulse.com/android");
                HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
                conn.setRequestMethod("POST");
                conn.setRequestProperty("Authorization", "Bearer "+idToken);
                conn.setDoOutput(true);
                conn.setDoInput(true);
                conn.connect();

                DataOutputStream os = new DataOutputStream(conn.getOutputStream());
                os.writeBytes(sJson);
                os.flush();
                os.close();

                Log.i(TAG, String.valueOf(conn.getResponseCode()));
                Log.i(TAG , conn.getResponseMessage());


                conn.disconnect();
            } catch (Exception e){
                Log.e("BeaconScanner", e.getLocalizedMessage());
                Crashlytics.log(e.getLocalizedMessage());
            }

            User.syncing = false;
        }
    });

    thread.start();
}

EDIT 1

One thing to note is that the beacons have an overlapping region, i.e a beacon scanner will detect 2 beacons in the region. So the nearest beacon is decided by the greatest value of the timeAverageRssi, the bug specifically crops up, after 30 minutes of inactivity there are region switches i.e beacon 1 was the nearest and then beacon 2 becomes the nearest beacon

Arvind Rachuri
  • 138
  • 1
  • 9
  • By any chance can you get a log capture when the app crashes? – davidgyoung Aug 11 '18 at 12:23
  • How do you create your foreground service? Do you make the Android Beacon LIbrary's built-in scanning service a foreground service as described [here](https://altbeacon.github.io/android-beacon-library/foreground-service.html)? Or do you have an independent custom foreground service in your app? Do you make any configuration calls to `beaconManager.setEnableScheduledScanJobs(...)` ? – davidgyoung Aug 11 '18 at 15:46
  • So, I have both. Does it make any difference? Yes, I set beaconManager.setEnableScheduledScanJobs(false). I'll use the 2.15.1 beta 1 version library and see if it fixes the issue – Arvind Rachuri Aug 12 '18 at 05:27
  • No, I don't get any log capture when the app crashes. The service just stops. – Arvind Rachuri Aug 12 '18 at 10:06
  • I would focus on adding more logging to your app and also watch for any operating system log messages around the time the app stops running. We will need more info to figure out what is stopping the app from running. – davidgyoung Aug 12 '18 at 12:54
  • So [here](https://gist.github.com/arealdeadone/eebf63cf1361abb56cd22bc0f1dd427e) is the log of the application, in this at line 246 is the last log of the app, while in the background, after this no scanning happens and `didExit` is also not called. After that, once the app is in the foreground, the logs start coming in again. – Arvind Rachuri Aug 12 '18 at 16:26
  • Also there were these other logs (line 314) when the application quit altogether – Arvind Rachuri Aug 12 '18 at 16:33
  • Unfortunately, I think there are just too many unknown variables to troubleshoot this in this forum. Based on the information provided, this happens only on the OnePlus 5T in specific conditions using a custom app. I would suggest trying to reproduce the symptoms with the library [reference app](https://github.com/AltBeacon/android-beacon-library-reference) to simplify the problem to a smaller public code base. – davidgyoung Aug 12 '18 at 17:09
  • @davidgyoung , the beta version of the library that you provided in the answer, solved my problem. The was getting killed due to some other reasons also. Will the fix implemented in the beta version be present in future versions of the Library? Thanks! – Arvind Rachuri Aug 22 '18 at 16:36
  • Good to hear. This fix will be in the upcoming 2.15.1 release at the end of the month. If this did solve your problem, you may want to accept and comment on the answer below so other folks can more easily find the solution. – davidgyoung Aug 22 '18 at 18:52

1 Answers1

0

I suspect a bug with using foreground services with the Android Beacon Library version 2.15 on Android 8+. While I have not reproduced this myself, the theory is that Android 8+ blocks the usage of an Intent Service used to deliver monitoring callbacks from the beacon scanning service.

I built a proposed fix in library version 2.15.1 beta 1 based on a similar report issue here. Please try this fix and see if it solves your problem.

davidgyoung
  • 63,876
  • 14
  • 121
  • 204
  • Hi, it is still behaving the same. A couple of more details on the issue : * The issue seems to be cropping up only in OnePlus 5T till now of all the phones used to test * The library works fine with Xiaomi Mi Max 2 - with a PixelExperience custom ROM * Also, the app gets restarted because I have a job scheduler which restarts the service when it exits in anyway. Hope these details will provide a bit more insight into the issue – Arvind Rachuri Aug 12 '18 at 09:56
  • Update - This fix worked for me if you are facing the same problem you might want to try this. – Arvind Rachuri Aug 24 '18 at 13:53