Search code examples
androidandroid-serviceandroid-broadcastreceiver

Android 8.0 - BroadcastReceiver not receiving after a reboot


I am developing an app. I am facing a problem. The thing is my code is as follows:

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

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

<application
    android:allowBackup="true"
    android:icon="@mipmap/ic_launcher_x"
    android:label="@string/app_name"
    android:fullBackupContent="@xml/backup_rules"
    android:theme="@style/AppTheme.NoActionBar">
    ...
    <service android:name=".service.NotificationService" />

    <receiver
        android:name=".receiver.StartupReceiver"
        android:enabled="true">
        <intent-filter>
            <action android:name="android.intent.action.BOOT_COMPLETED" />
        </intent-filter>
    </receiver>

    <receiver android:name=".receiver.UpgradeReceiver">
        <intent-filter>
            <action android:name="android.intent.action.MY_PACKAGE_REPLACED" />
        </intent-filter>
    </receiver>
    ...
</application>

</manifest>

StartupReceiver:

public class StartupReceiver extends BroadcastReceiver {

@Override
public void onReceive(Context context, Intent intent) {
        if (intent.getAction() != null && intent.getAction().equals(Intent.ACTION_BOOT_COMPLETED)) {
            ...
            NotificationService.setNotificationServiceAlarm(context, new Prefs(context).getNotifs());
        }
        else
            Log.i("No" , "Boot");
    }
}

UpgradeReceiver:

public class UpgradeReceiver extends BroadcastReceiver {

@Override
public void onReceive(Context context, Intent intent) {
    Log.i("In" , UpgradeReceiver.class.getSimpleName());
    NotificationService.setNotificationServiceAlarm(context , new Prefs(context).getNotifs());
}
}

NotificationService:

public class NotificationService extends IntentService {

    private static final String TAG = "NotificationsService";
    Prefs prefs;
    Notification.Builder builder;

    public NotificationService() {
        super(TAG);
    }

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

    @Override
    protected void onHandleIntent(Intent intent) {
        CheckConnection checkNetwork = new CheckConnection(this);
        if (!checkNetwork.isNetworkAvailable()) {
            return;
        }

        ...

        try {
            WeatherInfo weather;
            weather = new Request(this).getItems(city, units);
            weatherNotification(weather);
        } catch (IOException e) {
            Log.e(TAG, "Error get weather", e);
        }
    }

    public static Intent newIntent(Context context) {
        Log.i("Trigger" , "newIntent");
        return new Intent(context, NotificationService.class);
    }

    public static void setNotificationServiceAlarm(Context context,
                                                   boolean isNotificationEnable) {
        Log.i("In" , "Notification Service Alarm");
        Intent intent = NotificationService.newIntent(context);
        PendingIntent pendingIntent = PendingIntent.getService(context, 0, intent,
                PendingIntent.FLAG_UPDATE_CURRENT);
        AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
        long intervalMillis = AlarmManager.INTERVAL_HOUR;
        if (alarmManager != null)
            if (isNotificationEnable) {
                alarmManager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP,
                        SystemClock.elapsedRealtime(),
                        intervalMillis,
                        pendingIntent);
            } else {
                alarmManager.cancel(pendingIntent);
                pendingIntent.cancel();
            }
    }

    private void weatherNotification(WeatherInfo weather) {
        Intent intent = new Intent(this, WeatherActivity.class);
        PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, 0);

        ...

        NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
        assert notificationManager != null;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            String id = "w01", name = getString(R.string.weather_notification_title);
            int importance = NotificationManager.IMPORTANCE_MIN;
            String desc = getString(R.string.weather_notification_description);

            NotificationChannel channel = new NotificationChannel(id, name, importance);
            channel.setDescription(desc);
            notificationManager.createNotificationChannel(channel);
            builder = new Notification.Builder(this , id);
        }
        else
            builder = new Notification.Builder(this);

        ...
        Notification notification = builder.build();
        notificationManager.notify(0 , notification);
    }
}

The problem is that NotificationService as such works on its own, but this does not get called when the app is upgraded or the phone is rebooted. The fact is this is happening on Android O. As far as I remember, this used to work on Android 7.1 before I upgraded to Android 8.

EDIT 1: After trying JobIntentService, here is how my code looks now:

UPdated Manifest:

...
<service android:name=".service.ReactivationService"
            android:permission="android.permission.BIND_JOB_SERVICE"/>
...

Reactivation Service:

public class ReactivationService extends JobIntentService {

    public static final int JOB_ID = 0x01;

    public static void enqueueWork(Context context, Intent work) {
        enqueueWork(context, ReactivationService.class, JOB_ID, work);
    }

    @Override
    protected void onHandleWork(@NonNull Intent intent) {
        Log.i("In" , "onHandleWork()");
        ....

            NotificationService.setNotificationServiceAlarm(this, new Prefs(this).getNotifs());
    }
}

Startup Receiver:

public class StartupReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        if (intent.getAction() != null && intent.getAction().equals(Intent.ACTION_BOOT_COMPLETED)) {
            Log.i("In" , "getAction()");
            ReactivationService.enqueueWork(context, intent);
        }
        else
            Log.i("No" , "Boot");
    }
}

Upgrade Receiver:

public class UpgradeReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        if (intent.getAction() != null && intent.getAction().equals(Intent.ACTION_MY_PACKAGE_REPLACED)) {
            Log.i("In" , "getAction()");
            ReactivationService.enqueueWork(context, intent);
        }
        else
            Log.i("No" , "Upgrade");
    }
}

Now my problem is, the ACTION_MY_PACKAGE_REPLACED works fine and shows up the notification, but ACTION_BOOT_COMPLETED is not showing up the notification. What could be the reason, as I am using the same Service for both the receivers.


Solution

  • Starting from Android O, you can not start a service from a background app without getting an exception:

    java.lang.IllegalStateException: Not allowed to start service Intent (my_service) : app is in background

    If you still need to launch a service at device start up, you can now use the new JobIntentService.

    Change your IntentService to a JobIntentService:

    public class ReactivationService extends JobIntentService {
    
        public static final int JOB_ID = 0x01;
    
        public static void enqueueWork(Context context, Intent work) {
            enqueueWork(context, ReactivationService.class, JOB_ID, work);
        }
    
        @Override
        protected void onHandleWork(@NonNull Intent intent) {
            // do your stuff here
        }
    
    }
    

    Use it as follow in your StartupReceiver:

    public class StartupReceiver extends BroadcastReceiver {
    
        @Override
        public void onReceive(Context context, Intent intent) {
            if (Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) {
                ReactivationService.enqueueWork(context, new Intent());
            }
        }
    
    }
    

    Declare your service inside the manifest:

    <service android:name=".ReactivationService"
             android:permission="android.permission.BIND_JOB_SERVICE"/>
    

    And that’s it. This will either directly start the service (when running on pre-O platforms) or enqueue work for it as a job (when running on O and later). No matter what the platform is, everything you pass in enqueueWork will ultimately appears in onHandleWork.