Skip to content

Polling alarms can repeat in background indefinitely if app crashes #188

@byencho

Description

@byencho

Is this a support request?
No

Describe the bug
Under certain conditions (described below) the alarm set for foreground polling is not cancelled when the app is closed and this can result in that polling period being used to wake the app up in the background.

To reproduce
There is a particular setup required here:

  • The SDK is set to "offline" mode on app startup (using offline(true)) and not set to "online" mode until there is foreground UI. (This is done to prevent the possibility of any network activity in the background).
  • Background polling is disabled using disableBackgroundUpdating(true).
  • Streaming is disabled with stream(false).
  • pollingIntervalMillis is set to a low value (like the minimum of 5 minutes).

When the app is first foregrounded, the PollingUpdater will register a recurring alarm using the pollingIntervalMillis (which in this case is 5 minutes). Under normal circumstances, when the app moves to the background this alarm will be cancelled (as expected). This is because the ForegroundChecker will trigger onBecameBackground inside ConnectivityManager:

            @Override
            public void onBecameBackground() {
                synchronized (ConnectivityManager.this) {
                    if (isInternetConnected(application) && !setOffline &&
                            connectionInformation.getConnectionMode() != backgroundMode) {
                        throttler.cancel();
                        attemptTransition(backgroundMode);
                    }
                }
            }

The attemptTransition(backgroundMode) will then disable all polling since background polling is disabled:

    private synchronized void attemptTransition(ConnectionMode nextState) {
        ...
        switch (nextState) {
            case SHUTDOWN:
            case BACKGROUND_DISABLED:
            case SET_OFFLINE:
            case OFFLINE:
                initialized = true;
                callInitCallback();
                stopPolling(); // <------------- Here
                stopStreaming();
                break;

However, none of this code runs if the app shuts down in the foreground unexpectedly for any reason (such as an app crash). This leaves the 5 minute alarm in place and it wakes the app back up in the background. However, because the process is running in the background and is initialized in "offline" mode, the alarm also is not cancelled and is therefore allowed to repeat. Alarms are always typically cancelled when the SDK is initialized when ConnectivityManager.startUp is called, but this logic is not run in offline mode:

    synchronized boolean startUp(LDUtil.ResultCallback<Void> onCompleteListener) {
        initialized = false;
        if (setOffline) {
            initialized = true;
            updateConnectionMode(ConnectionMode.SET_OFFLINE);
            voidSuccess(onCompleteListener);
            return false; // <------ Stopped here in offline mode
        }

        boolean connected = isInternetConnected(application);

        if (!connected) {
            initialized = true;
            updateConnectionMode(ConnectionMode.OFFLINE);
            voidSuccess(onCompleteListener);
            return false;
        }

        initCallback = onCompleteListener;
        eventProcessor.start();
        throttler.attemptRun(); // <------- This would reset the alarms if allowed to run.
        return true;
    }

Expected behavior
Polling alarms are always cancelled and reset each time the SDK is initialized (even in offline mode).

More generally, if alarms were not used to implement foreground polling then it would not be possible for foreground polling to accidentally trigger an app to wake up in the background in the first place.

Logs
N/A

SDK version
3.1.4 (and also confirmed with current version 3.2.0)

Language version, developer tools
Kotlin / Java

OS/platform
Android 13

Additional context
N/A

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions