Work Manager with Broadcast Receiver does not work when the app is closed
Asked Answered
W

3

7

I just want to keep bluetooth on, and to do that I listen the bluetooth state and if it is turned of, the broadcast receiver could enable it. And I want it to run when the app is closed too. So I am trying to run the bluetooth broadcast receiver even after the app is closed (when it is not working). To do that, I learned that I need to use a Work Manager to support all devices. I tried to combine Broadcast Receiver and Work Manager. But I could not manage to make it run when the app is closed.

This is my MainActivity.java In here I enqueued the work request.

package com.example.workmanagersample;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;

import androidx.work.OneTimeWorkRequest;
import androidx.work.WorkManager;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        final OneTimeWorkRequest workRequest = new OneTimeWorkRequest.Builder(MyWorker.class).build();
        WorkManager.getInstance().enqueue(workRequest);

    }
}

The following class is my MyWorker.java In here I registered the receiver.

package com.example.workmanagersample;

import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.bluetooth.BluetoothAdapter;
import android.content.Context;
import android.content.IntentFilter;
import android.support.annotation.NonNull;
import android.support.v4.app.NotificationCompat;
import androidx.work.Worker;
import androidx.work.WorkerParameters;


public class MyWorker extends Worker {
    private BlueToothBroadcastReceiver myReceiver;

    public MyWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) {
        super(context, workerParams);
    }

    /*
     * This method is responsible for doing the work
     * so whatever work that is needed to be performed
     * we will put it here
     *
     * For example, here I am calling the method displayNotification()
     * It will display a notification
     * So that we will understand the work is executed
     * */

    @NonNull
    @Override
    public Result doWork() {
        displayNotification("My Worker", "Hey I finished my work");


        setReceiver();

        return Worker.Result.success();
    }

    /*
     * The method is doing nothing but only generating
     * a simple notification
     * If you are confused about it
     * you should check the Android Notification Tutorial
     * */
    private void displayNotification(String title, String task) {
        NotificationManager notificationManager = (NotificationManager) getApplicationContext().getSystemService(Context.NOTIFICATION_SERVICE);

        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
            NotificationChannel channel = new NotificationChannel("simplifiedcoding", "simplifiedcoding", NotificationManager.IMPORTANCE_DEFAULT);
            notificationManager.createNotificationChannel(channel);
        }

        NotificationCompat.Builder notification = new NotificationCompat.Builder(getApplicationContext(), "simplifiedcoding")
                .setContentTitle(title)
                .setContentText(task)
                .setSmallIcon(R.mipmap.ic_launcher);

        notificationManager.notify(1, notification.build());
    }


    private void setReceiver() {
        myReceiver = new BlueToothBroadcastReceiver();
        IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
        getApplicationContext().registerReceiver(myReceiver, filter);

    }
}

The following class is my BlueToothBroadcastReceiver.java In here I listen if the bluetooth state is changed and I tried to open it if it turned off. It was working when app is running. But I wanted it to work also if the app is closed but I could not achieve it.

package com.example.workmanagersample;

import android.bluetooth.BluetoothAdapter;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;

public class BlueToothBroadcastReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        final String action = intent.getAction();

        if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
            final int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE,
                    BluetoothAdapter.ERROR);
            switch (state) {
                case BluetoothAdapter.STATE_OFF:
                    setBluetooth(true);
                    // Bluetooth has been turned off;
                    break;
                case BluetoothAdapter.STATE_TURNING_OFF:
                    setBluetooth(true);
                    // Bluetooth is turning off;
                    break;
                case BluetoothAdapter.STATE_ON:
                    // Bluetooth has been on
                    break;
                case BluetoothAdapter.STATE_DISCONNECTING:
                    setBluetooth(true);
                    // Bluetooth is turning on
                    break;

                case BluetoothAdapter.STATE_DISCONNECTED:
                    setBluetooth(true);
                    // Bluetooth is turning on
                    break;
            }
        }
    }

    public static boolean setBluetooth(boolean enable) {
        BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
        boolean isEnabled = bluetoothAdapter.isEnabled();
        if (enable && !isEnabled) {
            return bluetoothAdapter.enable();
        }
        else if(!enable && isEnabled) {
            return bluetoothAdapter.disable();
        }
        // No need to change bluetooth state
        return true;
    }
}

Lastly my Manifest file;

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.workmanagersample">

    <uses-permission android:name="android.permission.BLUETOOTH" />
    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <receiver
            android:name=".BlueToothBroadcastReceiver"
            android:enabled="true">
            <intent-filter>
                <action android:name="android.bluetooth.adapter.action.STATE_CHANGED"/>
                <action android:name="android.bluetooth.adapter.action.STATE_OFF"/>
                <action android:name="android.bluetooth.adapter.action.STATE_TURNING_OFF"/>
                <action android:name="android.bluetooth.adapter.action.STATE_ON"/>
                <action android:name="android.bluetooth.adapter.action.STATE_DISCONNECTING"/>
                <action android:name="android.bluetooth.adapter.action.STATE_DISCONNECTED"/>
            </intent-filter>
        </receiver>
    </application>

</manifest>

I choosed to use Work Manager after researching during my weekend but it did not work when I closed the app. Is there anything that I am missing or is there any restriction? If so, how can I solve that? Any help would be really appreciated! Thanks!

Warrantable answered 10/6, 2019 at 2:12 Comment(0)
B
2

WorkManager is intended for executing tasks even if your application is in the background. You can assign some constraints to your Worker classes so that they are executed only when these constraints are met (i.e. having a Constraints on a WiFi connection being available if you need to upload some data to a server).

THIS is why WorkManager uses broadcast receivers (up to API level 22) or JobScheduler: to know when these constraints change.

As other have replied, you need to use a Service (and probably a Foreground Service is a good idea if you need to run this for long time). Few things you should evaluate:

  1. First check the scenarios for Foreground Services. Consider this blog post
  2. Then the documentation on foreground services.
  3. Finally the Bluetooth overview.
Bernettabernette answered 10/6, 2019 at 8:26 Comment(0)
I
0

you need to Keep broadcast receiver running after application is closed and you will Achieve that with Service class you may want to look to this answer

Intendancy answered 10/6, 2019 at 2:24 Comment(5)
As I learned, that does not work for >= Android 6 . First I implemented the same project with IntentService, then Service. But they also did not work and when I searched, I understand that if I need to run it for higher devices I need to use Work Manager. So I ended up with the Work Manager. However that does not work too.Warrantable
Should I put that under this line WorkManager.getInstance().enqueue(workRequest);?Warrantable
see this link medium.com/androiddevelopers/workmanager-basics-beba51e94048Intendancy
Oh, it says that the broadcast receivers are disabled for api 23 and higher. developer.android.com/reference/androidx/work/package-summary what else should I do? There should be a way to listen the bluetooth state changes when the app is closed.Warrantable
Use JobScheduler for that of WorkManager for api 23 >Romaine
M
0

BACKGROUND

Now coming to your implementation, seems like you had some hints because I see you have pending notification. As this part is very close to solution. So in fact you have to use Foreground Service (not just Service as mentioned here also, because it might be terminated by system).

Lastly, I believe you have use case to run this application. Because it might be very power consuming for users and you know what you are doing.

SOLUTION

Implement Foreground Service and start it from the application. Ensure you have notification inside. And your implementation of Receiver subscription.

class LiveBroadcast : Service() {

    private var myReceiver: BlueToothBroadcastReceiver? = null

    override fun onCreate() {

        // Init Forgeround Notification
        val pendingIntent: PendingIntent =
            Intent(this, ExampleActivity::class.java).let { notificationIntent ->
                PendingIntent.getActivity(this, 0, notificationIntent, 0)
            }

        val notification: Notification = Notification.Builder(this, CHANNEL_DEFAULT_IMPORTANCE)
                .setContentTitle(getText(R.string.notification_title))
                .setContentText(getText(R.string.notification_message))
                .setSmallIcon(R.drawable.icon)
                .setContentIntent(pendingIntent)
                .setTicker(getText(R.string.ticker_text))
                .build()

        // Notification ID cannot be 0.
        startForeground(ONGOING_NOTIFICATION_ID, notification)

        // Start your BluetoothReceiver
        myReceiver = new BlueToothBroadcastReceiver()
        getApplicationContext().registerReceiver(
            myReceiver, IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED));
    }

    override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
        return START_STICKY
    }

    override fun onBind(intent: Intent): IBinder? {
        // Pass
    }

    override fun onDestroy() {
        // Pass
    }
}
Mural answered 16/1, 2021 at 8:14 Comment(7)
Your answer is wrong, see this: #50344078 We can start Bluetooth receiver inside doWork ()Cyler
You should see this: youtube.com/…Cyler
@SychiSingh I removed Worker mention from the answer indeed. But background work with Service has different purpose, in your case I think it should foreground service. In any case, my change was working perfectly during testing.Mural
Tell me one thing, forget about this case for a moment. Suppose whenever the network got connected, I want to fetch data from Room DB and all remaining unsynced items, need to upload on Firestore (It should doesn't matter whether an app is open/closed/killed, etc ). My API level support is 21 to 30. What should I use?Cyler
@SychiSingh Just put a foreground notification and all the bg work will happen as intended. You can get "ignore battery optimizations" permissions if you want. Chinese manufacturers are known for stopping bg process when the app is closed to save battery, so you might want to keep the app in the bg without swiping it off from the recent. apart from this, if your code is working perfectly when the app is in foreground, it should work fine if you add a foreground notification.Subsidy
@AnirudhGanesh You haven't answered properly. User may kill the app right? then?Cyler
@SychiSingh if they kill it and if it happens to be a Chinese manufacturer, then yes your app's bg will be stopped irrespective of turning off battery optimizations.When I contacted google, they recommended me to test it with the latest API's since they have already solved it in the later versions (works on oneplus nord). For older versions, you can check their API level and educate them to turn off bg optimizations. Check bitbucket.org/copluk/acr/issues/607 for more info on how to switch off optimizations for various manufacturers. This is needed even if the app is not swiped away!Subsidy

© 2022 - 2024 — McMap. All rights reserved.