1

I am developing an android app that requires a foreground service to sync data over bluetooth with computers. The foreground service works perfectly during the session where it is first run by the app. However, if I restart my phone, the service will not restart upon reboot, despite me returning START_STICKY within the onStartCommand function. I want it to start as soon as possible upon reboot just like my VPN does. How can I achieve this functionality?

Here is the code in question:

package com.example.app;

import android.app.Service;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothDevice;
import android.content.Intent;
import android.os.IBinder;

import androidx.annotation.Nullable;
import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationManagerCompat;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Set;

import static com.example.app.App.CONNECTED_DEVICES_CHANNEL_ID;

public class BluetoothSyncService extends Service {
    private Utils utils;

    private final BluetoothAdapter BLUETOOTH_ADAPTER = BluetoothAdapter.getDefaultAdapter();
    private final String CONNECTED_PC_GROUP = "connectedPCS";
    private final ArrayList<String> NOTIFIED_PC_ADDRESSES = new ArrayList<>();

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        utils = Utils.getInstance(this);

        NotificationCompat.Builder summaryNotificationBuilder =
                new NotificationCompat.Builder(this,
                        CONNECTED_DEVICES_CHANNEL_ID)
                        .setSmallIcon(R.drawable.ic_placeholder_logo)
                        .setContentTitle("Sync Service Background")
                        .setGroup(CONNECTED_PC_GROUP)
                        .setGroupSummary(true)
                        .setPriority(NotificationCompat.PRIORITY_HIGH)
                        .setOngoing(true);

        int SUMMARY_NOTIFICATION_ID = 69;
        startForeground(SUMMARY_NOTIFICATION_ID, summaryNotificationBuilder.build());

        Thread serviceThread = new Thread(() -> {
            while (true) {
                handleConnectedDevices();
                handleNotifications();
            }
        });
        serviceThread.start();

        return START_STICKY;
    }

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

    private void handleConnectedDevices() {
        Set<BluetoothDevice> pairedDevices = BLUETOOTH_ADAPTER.getBondedDevices();

        // Handle connected PCS
        for (BluetoothDevice device : pairedDevices) {
            if (isConnected(device.getAddress())) {
                int deviceClass = device.getBluetoothClass().getDeviceClass();

                if (deviceClass == BluetoothClass.Device.COMPUTER_LAPTOP |
                        deviceClass == BluetoothClass.Device.COMPUTER_DESKTOP) {
                    if (!utils.inPairedPCS(device.getAddress())) {
                        utils.addToPairedPCS(new PairedPC(device.getName(),
                                device.getAddress(), true));
                    } else {
                        if (utils.getPairedPCByAddress(device.getAddress()) != null) {
                            utils.getPairedPCByAddress(device.getAddress()).setConnected(true);
                            utils.savePairedPCSToDevice();
                        }
                    }
                }
            } else {
                if (utils.inPairedPCS(device.getAddress())) {
                    if (utils.getPairedPCByAddress(device.getAddress()) != null) {
                        utils.getPairedPCByAddress(device.getAddress()).setConnected(false);
                        utils.savePairedPCSToDevice();
                    }
                }
            }
        }
    }

    private void handleNotifications() {
        NotificationManagerCompat notificationManager = NotificationManagerCompat
                .from(this);

        for (PairedPC pairedPC : utils.getPairedPCS()) {
            int CONNECTION_NOTIFICATION_ID = 420;
            if (pairedPC.isConnected()) {
                if (pairedPC.isActive() && !NOTIFIED_PC_ADDRESSES.contains(pairedPC.getAddress())) {
                    NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(
                            this, CONNECTED_DEVICES_CHANNEL_ID)
                            .setSmallIcon(R.drawable.ic_pc)
                            .setContentTitle("Syncing PC")
                            .setContentText(pairedPC.getName())
                            .setGroup(CONNECTED_PC_GROUP)
                            .setPriority(NotificationCompat.PRIORITY_MIN)
                            .setOngoing(true);

                    notificationManager.notify(pairedPC.getAddress(),
                            CONNECTION_NOTIFICATION_ID, notificationBuilder.build());
                    NOTIFIED_PC_ADDRESSES.add(pairedPC.getAddress());
                } else if (!pairedPC.isActive()) {
                    notificationManager.cancel(pairedPC.getAddress(), CONNECTION_NOTIFICATION_ID);
                    NOTIFIED_PC_ADDRESSES.remove(pairedPC.getAddress());
                }
            } else {
                if (NOTIFIED_PC_ADDRESSES.contains(pairedPC.getAddress())) {
                    notificationManager.cancel(pairedPC.getAddress(), CONNECTION_NOTIFICATION_ID);
                    NOTIFIED_PC_ADDRESSES.remove(pairedPC.getAddress());
                }
            }
        }
    }

    private boolean isConnected(String address) {
        Set<BluetoothDevice> pairedDevices = BLUETOOTH_ADAPTER.getBondedDevices();
        for (BluetoothDevice device : pairedDevices) {
            if (device.getAddress().equals(address)) {
                Method method = null;
                try {
                    method = device.getClass().getMethod("isConnected", (Class[]) null);
                } catch (NoSuchMethodException e) {
                    e.printStackTrace();
                }

                boolean connected = false;
                try {
                    assert method != null;
                    Object methodInvocation = method.invoke(device, (Object[]) null);
                    if (methodInvocation != null) {
                        connected = (boolean) methodInvocation;
                    } else {
                        connected = false;
                    }
                } catch (IllegalAccessException | InvocationTargetException e) {
                    e.printStackTrace();
                }

                return connected;
            }
        }
        return false;
    }
}

EDIT:

So I have tried using a broadcast receiver as suggested. Yet it is still not working. Here is the code for the receiver:

package com.example.app;

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

public class BootReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        if (Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) {
            Intent syncServiceIntent = new Intent(context, BluetoothSyncService.class);
            context.startService(syncServiceIntent);
        }
    }
}

And here is my manifest:

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

    <uses-permission android:name="android.permission.BLUETOOTH" />
    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
    <uses-permission android:name="android.permission.READ_SMS" />
    <uses-permission android:name="android.permission.RECEIVE_SMS" />
    <uses-permission android:name="android.permission.SEND_SMS" />
    <uses-permission android:name="android.permission.READ_CALL_LOG" />
    <uses-permission android:name="android.permission.READ_CONTACTS" />
    <uses-permission android:name="android.permission.WRITE_CONTACTS" />
    <uses-permission android:name="android.permission.CALL_PHONE" />
    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>


    <application
        android:allowBackup="false"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:name=".App"
        android:theme="@style/Theme.App">

        <receiver android:name=".BootReceiver" android:enabled="true" android:exported="true">
            <intent-filter>
                <category android:name="android.intent.category.DEFAULT"/>
                <action android:name="android.intent.action.BOOT_COMPLETED"/>
                <action android:name="android.intent.action.QUICKBOOT_POWERON"/>
                <action android:name="com.htc.intent.action.QUICKBOOT_POWERON"/>
            </intent-filter>
        </receiver>

        <activity android:name=".PermissionsActivity" />
        <activity android:name=".MainActivity" />
        <activity android:name=".StartupActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <service android:name=".BluetoothSyncService"/>
    </application>

</manifest>

EDIT 2:

SOLVED! Had to change context.startService(syncServiceIntent); to context.startForegroundService(syncServiceIntent);

Chris Ray
  • 133
  • 1
  • 2
  • 8
  • Your AndroidManifest needs an intent-filter for BOOT_COMPLETED. [Here is an example.](https://stackoverflow.com/a/20441442/850332) – Klox Jul 12 '21 at 21:29
  • "if I restart my phone, the service will not restart upon reboot, despite me returning START_STICKY within the onStartCommand function" -- `START_STICKY` does not cover reboots. It only covers cases where Android terminates your process due to low memory conditions. "I want it to start as soon as possible upon reboot just like my VPN does" -- A VPN is a dedicated type of service (`VpnService`). Your foreground service is going to behave somewhat differently. But, you can try using an `ACTION_BOOT_COMPLETED` receiver and have it call `startForegroundService()`. – CommonsWare Jul 12 '21 at 22:17
  • I have created a boot receiver to start the service on reboot, and configured my manifest with the receiver and permission, however it still wont start on reboot. I have added my manifest and receiver code to an edit. – Chris Ray Jul 12 '21 at 23:46

0 Answers0