Broadcast Receiver not being invoked in Headless JS (Native Module in React Native)

Viewed 278

I've written a native android module in React Native that consists of a broadcast receiver to listen to ACTION_SCREEN_OFF, ACTION_SCREEN_ON and ACTION_USER_PRESENT. I've created a headless service that is called by the receiver which executes the JS code, but the receiver is not being triggered to begin with.

phoneScreenOnOffModule.java:

package com.tracker;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.HeadlessJsTaskService;
import com.facebook.react.bridge.ReactMethod;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.util.Log;
import java.util.Map;
import java.util.HashMap;

public class phoneScreenOnOffModule extends ReactContextBaseJavaModule {

   private ReactApplicationContext reactContext;

   phoneScreenOnOffModule(ReactApplicationContext context) {
       super(context);
       reactContext=context;
       registerBroadcastReceiver();
   }

   @Override
   public String getName() {
       return "phoneScreenOnOffModule";
   }

   private final BroadcastReceiver phoneScreenReceiver = new BroadcastReceiver() {
       @Override
       public void onReceive(Context context, Intent intent) {
           if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {
                Log.d("StackOverflow", "screen_off");
            } else if (intent.getAction().equals(Intent.ACTION_SCREEN_ON)) {
                Log.d("StackOverflow", "screen_on");
            } else if (intent.getAction().equals(Intent.ACTION_USER_PRESENT)) {
                Log.d("StackOverflow", "user_present");
            }
            Intent serviceIntent = new Intent(context, phoneScreenService.class);
            context.startService(serviceIntent);
            HeadlessJsTaskService.acquireWakeLockNow(context);
       }
   };

   private void registerBroadcastReceiver() {
       IntentFilter filter = new IntentFilter();
       filter.addAction(Intent.ACTION_SCREEN_OFF);
       filter.addAction(Intent.ACTION_SCREEN_ON);
       filter.addAction(Intent.ACTION_USER_PRESENT);
       reactContext.registerReceiver(phoneScreenReceiver, filter);
   }   
}

phoneScreenService.java:

package com.tracker;
import android.content.Intent;
import android.os.Bundle;
import com.facebook.react.HeadlessJsTaskService;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.jstasks.HeadlessJsTaskConfig;
import javax.annotation.Nullable;

public class phoneScreenService extends HeadlessJsTaskService {

  @Override
  protected @Nullable HeadlessJsTaskConfig getTaskConfig(Intent intent) {
    Bundle extras = intent.getExtras();
    if (extras != null) {
      return new HeadlessJsTaskConfig(
          "phoneScreen",
          Arguments.fromBundle(extras),
          5000, // timeout for the task
          true // optional: defines whether or not  the task is allowed in foreground. Default is false
        );
    }
    return null;
  }
}

Manifest:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
  package="com.tracker">

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

    <application
      android:name=".MainApplication"
      android:label="@string/app_name"
      android:icon="@mipmap/ic_launcher"
      android:roundIcon="@mipmap/ic_launcher_round"
      android:allowBackup="false"
      android:theme="@style/AppTheme">
      <service android:name="com.tracker.phoneScreenService" />
      <receiver
        android:name="com.tracker.phoneScreenReceiver"
        android:enabled="true"
        android:exported="true"
        android:permission="android.permission.RECEIVE_BOOT_COMPLETED"
        android:label="phoneScreenReceiver">
        <intent-filter>
          <action android:name="android.intent.action.BOOT_COMPLETED" />
          <action android:name="android.intent.action.SCREEN_OFF" />
          <action android:name="android.intent.action.SCREEN_ON" />
          <action android:name="android.intent.action.USER_PRESENT" />
          <category android:name="android.intent.category.HOME" />
          <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
      </receiver>
      <activity
        android:name=".MainActivity"
        android:label="@string/app_name"
        android:configChanges="keyboard|keyboardHidden|orientation|screenSize|uiMode"
        android:launchMode="singleTask"
        android:windowSoftInputMode="adjustResize">
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
      </activity>
    </application>
</manifest>

Relevant JS code:

import { AppRegistry } from 'react-native';

const phoneScreen = async (data) => {
  console.log("works", data);
}
AppRegistry.registerHeadlessTask('phoneScreen', () => phoneScreen);
1 Answers

My problem is much similar to yours, my broadcast receiver needs to listen if there is a wifi connection. I create a Worker class to invoke the headless service. I think my approach will also solve your problem.

Here is the code

BackgroundModule.java

package com.packagename; // replace com.your-app-name with your app’s name

import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import java.util.Map;
import java.util.HashMap;
import android.util.Log;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.content.BroadcastReceiver;
import android.content.IntentFilter;
import android.net.ConnectivityManager;
import  android.net.wifi.WifiManager;

public class BackgroundModule extends ReactContextBaseJavaModule {
    private Context mContext;

    BackgroundModule(ReactApplicationContext context) {
        super(context);
        this.mContext = context;
    }

    @Override
    public String getName() {
        return "BackgroundModule";
    }

    @ReactMethod
    public void startBackgroundService() {
        Log.d("TAG", "Start background service");
        // Intent service = new Intent(this.mContext, BackgroundService.class);

        
        BroadcastReceiver br = new NetworkChangeReceiver();
        //change below according to you Intent filter
       IntentFilter filter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
        filter.addAction(WifiManager.SUPPLICANT_CONNECTION_CHANGE_ACTION);
        this.mContext.registerReceiver(br, filter);
    }
}

NetworkChangeReceiver.java

package com.packagename;

import android.app.ActivityManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.net.ConnectivityManager;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkInfo;
import android.os.Build;
import java.util.List;
import android.util.Log;
import  android.net.wifi.WifiManager;
import com.facebook.react.HeadlessJsTaskService;
import androidx.work.OneTimeWorkRequest;
import androidx.work.WorkManager;

public class NetworkChangeReceiver extends BroadcastReceiver {

    private OneTimeWorkRequest request;

    @Override
    public void onReceive(final Context context, final Intent intent) {
        
        Log.d("TAG", "onReceive");
        //run the worker here 
        //you can add more logic and run the worker accordingly

       //invoking workmanager sample
        request = new OneTimeWorkRequest.Builder(BackgroundWorker.class).build();
                    WorkManager.getInstance().enqueue(request);
        
    }

}

BackgroundWorker.java

package com.packagename;

import androidx.work.Worker;
import androidx.work.WorkerParameters;
import android.content.Context;
import android.util.Log;
import androidx.annotation.NonNull;
import android.content.Intent;
import android.os.Bundle;

public class BackgroundWorker extends Worker {
    private Context mContext;

    public BackgroundWorker(@NonNull Context context, @NonNull WorkerParameters params) {
        super(context, params);
        mContext = context;
    }

    @Override
    public Result doWork() {

        // Do the work here--in this case, upload the images.
        Log.d("TAG", "working...");

        Intent service = new Intent(this.mContext.getApplicationContext(), HeadlessAction.class);
        Bundle bundle = new Bundle();

        bundle.putString("foo", "bar");
        service.putExtras(bundle);

        this.mContext.getApplicationContext().startService(service);
        
        Log.d("TAG", "service code completed");

        // Indicate whether the work finished successfully with the Result
        return Result.success();
    }

}

HeadlessService.java

package com.rnbackgroundapp;

import android.content.Intent;
import android.os.Bundle;
import com.facebook.react.HeadlessJsTaskService;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.jstasks.HeadlessJsTaskConfig;
import javax.annotation.Nullable;

public class HeadlessAction extends HeadlessJsTaskService {

  @Override
  protected @Nullable HeadlessJsTaskConfig getTaskConfig(Intent intent) {
    Bundle extras = intent.getExtras();
    if (extras != null) {
      return new HeadlessJsTaskConfig(
          "HeadlessAction",
          Arguments.fromBundle(extras),
          5000, // timeout for the task
          false // optional: defines whether or not  the task is allowed in foreground. Default is false
        );
    }
    return null;
  }
}

FYI you need to add dependencies to your build.gradle. please check this link for more details

Related