I use Ably API as a way to communicate between two Android devices (mobile phones). The first one is a "Master" which sending a request to the second ("slave"). the main part of "Slave" is background service which is awaiting for the command from the master. As soon as a request from the master arrives, the slave gets its geo-location and sends it back to the master. The trouble is that while the slave is in the doze mode Ably doesn't work ('onMessage' method doesn't work) until I wake the phone up. At the same time, if I use sms reciever (onRecieve method of mainBroadcastReceiver) with the service of "slave" it works fine even when the phone is "asleep". Is there any ideas how to resolve that issue? Here is the code of my Service.
package ru.volganap.nikolay.kids_monitor_ably;
import android.annotation.SuppressLint;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.location.LocationManager;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.provider.Settings;
import android.telephony.SmsManager;
import android.util.Log;
import android.app.Service;
import android.os.IBinder;
import android.widget.Toast;
import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import androidx.core.app.NotificationCompat;
import io.ably.lib.realtime.AblyRealtime;
import io.ably.lib.realtime.Channel;
import io.ably.lib.realtime.CompletionListener;
import io.ably.lib.types.AblyException;
import io.ably.lib.types.ErrorInfo;
import io.ably.lib.types.Message;
public class KidService extends Service implements KM_Constants {*emphasized text*
private SharedPreferences sharedPrefs;
BroadcastReceiver mainBroadcastReceiver;
private String parent_sender;
FindGeoPos fg;
private NotificationManager notificationManager;
private static final int NOTIFY_ID = 101;
private static final String NOTIFY_CHANNEL_ID = "CHANNEL_ID";
public static final int DEFAULT_NOTIFICATION_ID = 101;
private Channel channel;
public void onCreate() {
super.onCreate();
Log.d(LOG_TAG, "Service: onCreate");
//EventBus eventBus = EventBus.builder().addIndex(new MyEventBusIndex()).build();
sharedPrefs = getSharedPreferences(PREF_ACTIVITY, MODE_PRIVATE);
notificationManager = (NotificationManager) this.getSystemService(this.NOTIFICATION_SERVICE);
try { //Init ABLY
AblyRealtime ablyRealtime = new AblyRealtime(ABLY_API_KEY);
channel = ablyRealtime.channels.get(ABLY_ROOM);
channel.subscribe(PARENT_PHONE, new Channel.MessageListener() {
@Override
public void onMessage(Message messages) {
Log.d(LOG_TAG, "Service - Ably message received: " + messages.data);
if (messages.name.equals(PARENT_PHONE) && messages.data.equals(getResources().getString(R.string.parent_sms))) {
getPosition();
}
}
});
} catch (AblyException e) {
e.printStackTrace();
}
}
@SuppressLint("MissingPermission")
public int onStartCommand(Intent intent, int flags, int startId) {
if (startId > 1) {
stopSelf(startId);
}
Log.d(LOG_TAG, "Service: onStartCommand, starId = " + startId + ", flags = " + flags);
//Send Foreground Notification
sendNotification("KMService","");
if (!EventBus.getDefault().hasSubscriberForEvent(KidService.class)) {
EventBus.getDefault().register(this);
}
//Init SMS Receiver
IntentFilter filter = new IntentFilter();
filter.addAction(ACTION_FROM_BR);
mainBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (intent != null) {
parent_sender = intent.getStringExtra("sender");
Log.d(LOG_TAG, "Service: Get back with Sender-parent");
getPosition();
}
}
};
registerReceiver(mainBroadcastReceiver, filter);
return START_STICKY;
}
public void sendNotification(String Title,String Text) {
Intent notificationIntent = new Intent(getApplicationContext(), MainActivity.class);
//notificationIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
notificationIntent.setAction(Intent.ACTION_MAIN);
PendingIntent contentIntent = PendingIntent.getActivity(getApplicationContext(), 0, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT);
NotificationCompat.Builder builder = new NotificationCompat.Builder(getApplicationContext(), NOTIFY_CHANNEL_ID)
.setOngoing(true)
.setSmallIcon(R.drawable.ic_stat_name)
.setWhen(System.currentTimeMillis())
.setContentIntent(contentIntent)
.setContentTitle(Title)
.setContentText(Text);
//.setPriority(PRIORITY_HIGH);
createChannelIfNeeded(notificationManager);
Notification notification = builder.build();
//notificationManager.notify(NOTIFY_ID, notification);
startForeground(NOTIFY_ID, notification);
}
public static void createChannelIfNeeded(NotificationManager manager) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel notificationChannel = new NotificationChannel(NOTIFY_CHANNEL_ID, NOTIFY_CHANNEL_ID, NotificationManager.IMPORTANCE_DEFAULT);
manager.createNotificationChannel(notificationChannel);
}
}
// Find the location
protected void getPosition () {
if (isLocationEnabled()) {
Log.d(LOG_TAG, "Service: new FindGeoPos call");
Handler handler = new Handler(Looper.getMainLooper());
Runnable myRunnable = new Runnable() {
@Override
public void run() {
fg = new FindGeoPos(getBaseContext());
}
};
handler.post(myRunnable);
} else {
Toast.makeText(getApplicationContext(), "Service: Please turn on your location", Toast.LENGTH_LONG).show();
Intent intent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS);
startActivity(intent);
Log.d(LOG_TAG, "Service: Please turn on your location");
}
}
private boolean isLocationEnabled() {
LocationManager locationManager = (LocationManager)getSystemService(Context.LOCATION_SERVICE);
return locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER) || locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER);
}
@Subscribe//(threadMode = ThreadMode.MAIN)
public void onEvent(EventBus_Kid event){
Log.d(LOG_TAG, "Service: EventBus is worked, position is: " + event.location_message);
fg = null;
parent_sender = sharedPrefs.getString(PARENT_PHONE, "" );
try { // ABLY PUBLISH a message
channel.publish(KID_PHONE, event.location_message, new CompletionListener() {
@Override
public void onSuccess() {
Log.d(LOG_TAG,"Service - onSuccess - Message sent");
}
@Override
public void onError(ErrorInfo reason) {
Log.d(LOG_TAG,"Service - onError - Message not sent, error occurred: " + reason.message);
sendSMSMessage (parent_sender, event.location_message);
}
});
} catch (AblyException e) {
Log.d(LOG_TAG,"Service - AblyException - Message not sent: " + e.toString());
sendSMSMessage (parent_sender, event.location_message);
}
//OkHttp is starting
String user = KID_PHONE;
OkHttpRequest serverReguest = new OkHttpRequest();
serverReguest.serverGetback(user, event.location_message);
}
protected void sendSMSMessage (String phoneNo, String message) {
try {
SmsManager smsManager = SmsManager.getDefault();
smsManager.sendTextMessage(phoneNo, null, message, null, null);
Toast.makeText(getApplicationContext(), "SMS sent.", Toast.LENGTH_LONG).show();
Log.d(LOG_TAG, "Service: SMS sent to number: " + phoneNo + " with message: " + message);
} catch (Exception e) {
Toast.makeText(getApplicationContext(),"SMS faild, please try again later", Toast.LENGTH_LONG).show();
e.printStackTrace();
Log.d(LOG_TAG, "Service: SMS failed. SMS Exception: " + e.toString());
}
}
public void onDestroy() {
super.onDestroy();
Log.d(LOG_TAG, "Service: onDestroy");
unregisterReceiver(mainBroadcastReceiver);
EventBus.getDefault().unregister(this);
channel.unsubscribe();
notificationManager.cancel(DEFAULT_NOTIFICATION_ID);
stopSelf();
}
@Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
return null;
//throw new UnsupportedOperationException("Service: Not yet implemented");
}
}
Ably Engineer here. The issue here is mostly to do with the way android handles services when the app is not in the foreground. Using Ably helps but we cant stop the OS pausing that thread :D.
SMS's are not handled in the same way by the device, the system is not under user-space control. They'll be received no matter what and then your application can handle them.
In this post There is an answer about websockets in a background thread/task. and its more or less the correct way to go about it. There are a few examples out there about how to do this on android in flutter: https://medium.com/stuart-engineering/keep-flutter-running-background-on-android-6ffc85be0234