3

I am making an application that notifies the user at certain times of the day. Like an alarm clock. The code works normally in versions prior to Android Oreo.

From what I read, Android Oreo and later versions kill actions in the background and that's why I'm having the error below.

2020-07-13 20:31:06.766 1609-1737/? W/BroadcastQueue: Background execution not allowed: receiving Intent { act=android.intent.action.PACKAGE_REPLACED dat=package:studio.com.archeagemanager flg=0x4000010 (has extras) } to air.br.com.alelo.mobile.android/co.acoustic.mobile.push.sdk.wi.AlarmReceiver

It is as if the BroadcastService is simply not triggered when it should be. But when I open the app, it starts up instantly.

AndroidManifest.xml

<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="owner.custom.permission" />
<permission
    android:name="owner.custom.permission"
    android:protectionLevel="signatureOrSystem">
</permission>

<meta-data
    android:name="com.google.android.gms.version"
    android:value="@integer/google_play_services_version" />

<application
    android:allowBackup="true"
    android:icon="@mipmap/app_icon_new"
    android:label="@string/app_name"
    android:supportsRtl="true"
    android:theme="@style/AppTheme">
    <activity
        android:name=".SplashScreenActivity"
        android:theme="@style/AppCompat.TelaCheia">
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>
    <activity
        android:name=".MainActivity"
        android:label="@string/app_name"
        android:screenOrientation="portrait"
        android:theme="@style/AppTheme.NoActionBar">
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />

            <category android:name="android.intent.category.DEFAULT" />
        </intent-filter>
    </activity>

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

</application>

TabFragEventA.java (Here I put only the function that initiates the alarm)

public void startAlarm(int eventID) {
    String[] NOTIFICATION_TITLES = {"Ocleera Rift", "Ocleera Rift", "Mistmerrow Conflict", "Mistmerrow Conflict",
            "Mistmerrow Conflict", "Nation Construction Quests", "Diamond Shores", "Diamond Shores", "Battle of the Golden Plains",
            "Battle of the Golden Plains", "Karkasse Ridgelands", "Kraken", "The Mirage Isle Fish Fest", "Red Dragon",
            "Abyssal Attack", "Lusca Awakening", "Delphinad Ghostship", "Cattler Wrangler", "Legendary Chef", "+1"};

    String notificationTitle = getString(R.string.contentTitle);
    String notificationText = NOTIFICATION_TITLES[eventID] + getString(R.string.contentNotificationText);

    int alarmHour = localHour.get(eventID);
    int alarmMinute = localMinute.get(eventID);
    // Decrease 5 minutes
    if(alarmMinute == 0) {
        alarmHour = alarmHour - 1;
        alarmMinute = 55;
    } else {
        alarmMinute = alarmMinute - 5;
    }
    // Setting the alarm moment
    Calendar calendar = Calendar.getInstance();
    calendar.setTimeInMillis(System.currentTimeMillis());
    calendar.set(Calendar.HOUR_OF_DAY, alarmHour);
    calendar.set(Calendar.MINUTE, alarmMinute);
    calendar.set(Calendar.SECOND, 0);
    calendar.set(Calendar.MILLISECOND, 0);
    // Analyze if the alarm moment has passed
    Calendar actualTime = Calendar.getInstance();
    if(actualTime.getTimeInMillis() >= calendar.getTimeInMillis()) {
        calendar.add(Calendar.DAY_OF_MONTH, 1);
    }

    Intent intent = new Intent(getActivity().getApplicationContext(), AlarmReceiver.class);
    intent.putExtra("contentTitle", notificationTitle);
    intent.putExtra("contentText", notificationText);
    /*if(eventID == 11 || eventID == 13) {
        intent.putExtra("specificDayWeek", true);
    } else {
        intent.putExtra("specificDayWeek", false);
    }*/
    if(eventID == 12) {
        intent.putExtra("castleSupply", true);
        intent.putIntegerArrayListExtra("castleSupplyDayWeek", (ArrayList<Integer>) CastleSupply);
    }
    else if(eventID == 13) {
        intent.putExtra("castleClaim", true);
        intent.putIntegerArrayListExtra("castleClaimDayWeek", (ArrayList<Integer>) CastleClaim);
    }
    else if(eventID == 14) {
        intent.putExtra("abyssalAttack", true);
        intent.putIntegerArrayListExtra("abyssalDaysWeek", (ArrayList<Integer>) AbyssalDayWeek);
    }
    else if(eventID == 15) {
        intent.putExtra("luscaAwakening", true);
        intent.putIntegerArrayListExtra("luscaAwakeningDayWeek", (ArrayList<Integer>) LuscaAwakening);
    } else {
        intent.putExtra("abyssalAttack", false);
    }

    intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
    // PendingIntent pendingIntent = PendingIntent.getBroadcast(getActivity().getApplicationContext(), eventID, intent, PendingIntent.FLAG_UPDATE_CURRENT);
    PendingIntent pendingIntent = PendingIntent.getBroadcast(getActivity().getApplicationContext(), eventID, intent, 0);
    AlarmManager alarmManager = (AlarmManager) getActivity().getSystemService(Context.ALARM_SERVICE);
    alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(), AlarmManager.INTERVAL_DAY, pendingIntent);
}

AlarmReceiver.java

public class AlarmReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {

        String contentTitle = intent.getStringExtra("contentTitle");
        String contentText = intent.getStringExtra("contentText");

        String CHANNEL_ID = "my_channel_01";
        NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            CharSequence name = "Channel Name";
            int importance = NotificationManager.IMPORTANCE_HIGH;
            NotificationChannel mChannel = new NotificationChannel(CHANNEL_ID, name, importance);
            notificationManager.createNotificationChannel(mChannel);
        }

        PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
        NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(context, CHANNEL_ID)
                .setSmallIcon(R.drawable.icon_notification)
                .setContentTitle(contentTitle)
                .setContentText(contentText)
                .setLargeIcon(BitmapFactory.decodeResource(context.getResources(), R.drawable.icon_notification))
                .setContentIntent(pendingIntent)
                .setAutoCancel(true)
                .setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION))
                .setVibrate(new long[]{800, 500, 600, 300});

        notificationManager.notify(0, notificationBuilder.build());

        Log.d("ALARMRECEIVER", "INSIDE");

    }
}

I would like to know what to do to make the Broadcast Receiver work in the background. Or an alternative to set this alarm clock on Android Oreo + so that it notifies the user even with the application closed or in the background.

3 Answers3

4

Android has a doze mode in which it goes to sleep after some inactivity, Even if you manage to do this on some phones chinese ROMs will trouble you for sure ( in which removing from recent apps works as force stopping application)

For your problem there are solutions Like Work manager , Foregroundservices ,jobscheduler it should work but again can't say for all the ROMs. I think right now there isn't a proper solution to this background processing.

But One thing you can do is sending a FCM notification from server with high priority.

You can see that Facebook and whatsapp can work in background because they are whitelisted by the companies. You can whitelist your application by enabling auto start from settings.But you need to do it manually which isnt a case when we talk about fb and whatsapp

Check this website for more details : https://dontkillmyapp.com/

With this issue Most affected are alarm clocks, health trackers, automation apps, calendars or simply anything which needs to do some job for you at a particular moment when you don’t use your phone.

With Android 6 (Marshmallow), Google has introduced Doze mode to the base Android, in an attempt to unify battery saving across the various Android phones.

Unfortunately, some manufacturers (e.g. Xiaomi, Huawei, OnePlus or even Samsung..) did not seem to catch that ball and they all have their own battery savers, usually very poorly written, saving battery only superficially with side effects.

kelvin
  • 1,480
  • 1
  • 14
  • 28
  • 1
    A server notification is unfortunately not an option for me. I need the user to be notified even when he is without internet. Can you imagine what the android clock uses to notify the user at specific times? – Kelton Holanda Fontenele Jul 16 '20 at 22:52
  • 1
    The app you are talking about is a SYSTEM application they can so whatever they want. Also you can see that Facebook and whatsapp can work in background because they are whitelisted by the companies. You can whitelist your application by enabling auto start from settings.But you need to do it manually which isnt a case when we talk about fb and whatsapp – kelvin Jul 17 '20 at 05:28
  • check this website for more details : https://dontkillmyapp.com/ – kelvin Jul 17 '20 at 05:33
3

Starting from Android oreo and up no more background services are supported. the suggestion from the developer documentation is to use a foreground service. To keep the foreground service running you need to hook it up with a notification. you can configure the notification to hide or not visible in the UI at all lately.

check this solution

@RequiresApi(api = Build.VERSION_CODES.O)
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    context = this;


    String input = intent.getStringExtra("inputExtra");
    createNotificationChannel();


    Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID)
            .setSmallIcon(R.mipmap.ic_launcher)

            .setContentTitle("GPS Service")

            .setContentText("GPS Service")
//                .setLargeIcon(emailObject.getSenderAvatar())
            .setStyle(new NotificationCompat.BigTextStyle()
                    .bigText("Ready\nAndroi " + android.os.Build.VERSION.SDK_INT))
            .setPriority(Notification.PRIORITY_HIGH)
            .setOngoing(true)
            .build();

    startForeground(1, notification);
    getLocation();

    //do heavy work on a background thread


    locationModel = new LocationModel("", "", "", "", "");

    LongOperation longOperation = new LongOperation();
    longOperation.execute();


    //stopSelf();

    return START_STICKY;
}

and in the main activity onCreate

    if (!isMyServiceRunning(ForegroundService.class)) {

        BroadcastReceiver br = new GPSBroadcastReceiver();
        IntentFilter filter = new IntentFilter(CONNECTIVITY_ACTION);
        filter.addAction(Intent.ACTION_BOOT_COMPLETED);

        getApplicationContext().registerReceiver(br, filter);
}

checking whether the service is running

    private boolean isMyServiceRunning(Class<?> serviceClass) {
    ActivityManager manager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
    for (ActivityManager.RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) {
        if (serviceClass.getName().equals(service.service.getClassName())) {
            return true;
        }
    }
    return false;
}

here is an example from one of my projects. Foreground service is the way to go!

enter image description here

some helpful sources.

foreground service example 1

foreground service example 2

repeating alarm manager example

Viroj Fernando
  • 461
  • 4
  • 8
  • Will this service continue to run even when the user closes the application? Sometimes I try to imagine what Android's own clock uses for its alarm clocks. – Kelton Holanda Fontenele Jul 16 '20 at 22:50
  • Systems apps are allowed to run background services. but not for a third party. yes, this will run even the app is closed. I can share the working sample is you like. have you seen the minimized notification on the notification panel? this is the reason for it. – Viroj Fernando Jul 17 '20 at 05:18
  • You said that I can hide the service notification. But everywhere I search for how to hide it, I see that it is not possible to hide. Can you tell me how it should be done to hide the notification that informs the user that the application has a service running? – Kelton Holanda Fontenele Jul 17 '20 at 20:58
  • https://www.spiria.com/en/blog/mobile-development/hiding-foreground-services-notifications-in-android/#:~:text=Here%2C%20the%20trick%20is%20to,flagged%20as%20a%20foreground%20process. not quite hide just minimizing per android developer documents. – Viroj Fernando Jul 20 '20 at 17:52
  • https://stackoverflow.com/a/18281520/4423647 some answers related to you – Viroj Fernando Jul 20 '20 at 17:55
1

If you are writing an Clock apps, use AlarmManager.setAlarmClock. It be allowed to trigger even if the system is in a low-power idle (a.k.a. doze) mode. When you set an AlarmClock, it's visible to the user. And SystemUI may show different icons or alarms.

If you just want to be triggered at certain time to do some work, there is no good way to escape the background limit.

Yong
  • 1,529
  • 12
  • 21