5

This is my very first post here, so please don't blame me if I'm not as complete and clear as I have to be.

The issue

I am new to React native and I recently began to develop a react native app which could read my incoming SMS's aloud. I already achieved to retrieve the incoming messages and to read them aloud... But only if the app is the foreground.

So, could you please advise me some libraries or tutorials on the subject ?

I'm working on a Nokia 5 with Android 9.

I currently use the following libraries :

  1. React-native-android-sms-listener to retrieve the incoming messages.
  2. React-native-tts to read the content aloud.

What I already tried

I'm searching the Internet for more than a week now (includig Stack Overflow and this example question) and I can't find what I'm looking for. I already tried React-native-background-timer and React-native-background-job. But I couldn't never get a background timer working and React-native-background-job allows tasks to be executed every 15 minutes only (due to the Android limitations).

So I read many articles like this one explaining how to use Headless JS and other libraries until I found this codeburst tutorial today, explaining how to develop a background service to record audio calls. I tried to adapt it, but the background service never starts.

My code

I must tell you that I don't have any knowledge in Java, so the native code below may contain mistakes, even if it is based on tutorials and the React native documentation.

Currently, when the app is launched, the service IncomingSMSService is called. This service, developed following the Codeburst tutorial referenced above, relies on Headless JS and a JS function that listen to the incoming messages and then read them aloud thanks to React-native-tts.

Here is these two files :

IncomingSMSService.java

package com.ava.service;

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;

public class IncomingSMSService extends HeadlessJsTaskService {
  @Override
  protected HeadlessJsTaskConfig getTaskConfig(Intent intent) {
    Bundle extras = intent.getExtras();

    if (extras != null) {
    return new HeadlessJsTaskConfig(
      "HandleIncomingSMS",
      Arguments.fromBundle(extras),
      5000,
      true
      );
    }
    return null;
  }
}

HandleIncomingSMS.js

import { AppRegistry } from 'react-native';
import SmsListener  from 'react-native-android-sms-listener';
import Tts from 'react-native-tts';

const HandleIncomingSMS = async (taskData) => {
  SmsListener.addListener(message => {
    Tts.getInitStatus().then(() => {
      Tts.speak(`New message from number ${message.originatingAddress} : ${message.body}`);
    });
  });
}

AppRegistry.registerHeadlessTask('HandleIncomingSMS', () => HandleIncomingSMS));

These pieces of code are called in a BroadcastReceiver here (IncomingSMSReceiver.java) :

package com.ava.receiver;

import android.app.ActivityManager;
import android.app.ActivityManager.RunningAppProcessInfo;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import com.ava.service.IncomingSMSService;
import com.facebook.react.HeadlessJsTaskService;
import java.util.List;

public final class IncomingSMSReceiver extends BroadcastReceiver {
  @Override
  public final void onReceive(Context context, Intent intent) {
    if (!isAppOnForeground((context))) {
      Intent service = new Intent(context, IncomingSMSService.class);
      context.startService(service);
      HeadlessJsTaskService.acquireWakeLockNow(context);
    }
  }

  private boolean isAppOnForeground(Context context) {
    ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
    List<ActivityManager.RunningAppProcessInfo> appProcesses =
    activityManager.getRunningAppProcesses();
    if (appProcesses == null) {
        return false;
    }
    final String packageName = context.getPackageName();
    for (ActivityManager.RunningAppProcessInfo appProcess : appProcesses) {
        if (appProcess.importance ==
        ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND &&
         appProcess.processName.equals(packageName)) {
            return true;
        }
    }
    return false;
  }
}

I also requested the good permissions in my AndroidManifest file, and I registered the service like so :

   <service
    android:name="com.ava.service.IncomingSMSService"
    android:enabled="true"
    android:label="IncomingSMSService"
  />
  <receiver android:name="com.ava.receiver.IncomingSMSReceiver">
    <intent-filter android:priority="0">
      <action android:name="android.provider.Telephony.SMS_RECEIVED"/>
    </intent-filter>
  </receiver>

What am I doing wrong ? I don't even see service in the Running services tab of the Android Developer options... Any ideas ?

Thanks in advance for your help.

UPDATE (01/06/2019)

After reading or watching several tutorials like this one or this video, I managed to get my app working in the foreground. It now displays a persistent notification.

BUT, I don't know how I can "link" my service and my Broadcsat Receiver to this notification (for now, the service is called only if the app is in foreground).

Here is my updated code :

// IncomingSMSService

package com.ava.service;

import android.graphics.Color;
import android.app.Notification;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.ContextWrapper;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.content.Intent;
import android.os.Handler;
import android.os.IBinder;
import android.support.v4.app.NotificationCompat;

import com.facebook.react.HeadlessJsTaskService;
import com.ava.MainActivity;
import com.ava.R;

public class IncomingSMSService extends Service {
    private NotificationManager notifManager;
    private String CHANNEL_ID = "47";
    private int SERVICE_NOTIFICATION_ID = 47;

    private Handler handler = new Handler();
    private Runnable runnableCode = new Runnable() {
        @Override
        public void run() {
            Context context = getApplicationContext();
            Intent myIntent = new Intent(context, IncomingSMSEventService.class);
            context.startService(myIntent);
            HeadlessJsTaskService.acquireWakeLockNow(context);
            handler.postDelayed(this, 2000);
        }
    };


    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        createNotificationChannel();
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
    }

    public void createNotificationChannel() {
      NotificationChannel notificationChannel = new NotificationChannel(CHANNEL_ID, "General", notifManager.IMPORTANCE_HIGH);
      notificationChannel.enableLights(true);
      notificationChannel.setLightColor(Color.RED);
      notificationChannel.setShowBadge(true);
      notificationChannel.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC);
      getManager().createNotificationChannel(notificationChannel);
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        this.handler.post(this.runnableCode);
        Intent notificationIntent = new Intent(this, MainActivity.class);
        PendingIntent contentIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);
        Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID)
                .setContentTitle("Ava")
                .setContentText("Listening for new messages...")
                .setSmallIcon(R.mipmap.ic_launcher_round)
                .setContentIntent(contentIntent)
                .setOngoing(true)
                .build();
        startForeground(SERVICE_NOTIFICATION_ID, notification);
        return START_NOT_STICKY;
    }

    private NotificationManager getManager() {
       if (notifManager == null) {
          notifManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
       }
       return notifManager;
   }
}

My headlessJS task :

// HandleIncomingSMS.js

import SmsListener from 'react-native-android-sms-listener';
import Tts from 'react-native-tts';
import Contacts from 'react-native-contacts';
import { text } from 'react-native-communications';

module.exports = async () => {
  // To lower other applications' sounds
  Tts.setDucking(true);

  // Prevent the TTS engine from repeating messages multiple times
  Tts.addEventListener('tts-finish', (event) => Tts.stop());

  SmsListener.addListener(message => {
    Contacts.getAll((err, contacts) => {
      if (err) throw err;

      const contactsLoop = () => {
        contacts.forEach((contact, index, contacts) => {
          // Search only for mobile numbers
          if (contact.phoneNumbers[0].label === 'mobile') {
            // Format the contact number to be compared with the message.oritignatingAddress variable
            let contactNumber = contact.phoneNumbers[0].number.replace(/^00/, '+');
            contactNumber = contactNumber.replace(/[\s-]/g, '');
            // Phone numbers comparison
            if (contactNumber === message.originatingAddress) {
              if (contact.familyName !== null) {
                Tts.speak(`Nouveau message de ${contact.givenName} ${contact.familyName} : ${message.body}`);
              } else {
                // If the contact doesn't have a known family name, just say his first name
                Tts.speak(`Nouveau message de ${contact.givenName} : ${message.body}`);
              }
            } else if (contactNumber !== message.originatingAddress && index === contacts.length) {
              // If the number isn't recognized and if the contacts have been all checked, just say the phone number
              Tts.speak(`Nouveau message du numéro ${message.originatingAddress} : ${message.body}`);
            }
          }
        });
      }
      contactsLoop();
      // Redirect to the SMS app
      text(message.originatingAddress, message = false);
    });
  });
}

I also added the good permissions in my AndroidManifest.xml file like the following :

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

I made some progress but I am still stuck, so if you have any idea, please share them ! Thank you !

  • Not Java expert neither, but I think that your headless task should do the actual code/sms processing. Right now it is just adding an event listener. – Cristiano Coelho May 17 '19 at 12:52
  • You might also want to make sure the server is indeed running by watching the logs on Android Studio / Logcat – Cristiano Coelho May 17 '19 at 12:53
  • dude, have you managed to fix it, I am struggling too! –  Jan 23 '20 at 16:16
  • Nope... I searched the web, GitHub, SO, but I do not have any clue... Apart from this tutorial : https://medium.com/@inkdrop/a-simple-way-to-run-js-in-background-thread-on-react-native-8fff345576da. I didn't try it yet, but this might be a workaround. Let me know if it worked! Thank you – belgianGeek Jan 24 '20 at 17:57
  • Did you find a workaround or solution @belgianGeek?? If you find a solution or workaround please attach your solution as an answer. – MD. IBRAHIM KHALIL TANIM Sep 09 '20 at 09:19
  • I didn't find a fix since my first post and I completely abandoned React native for this project. I hope you'll find a solution on your side. – belgianGeek Sep 14 '20 at 07:12

0 Answers0