Android Question Now - Android 6 ServiceStartAtExact not accurate

wes58

Active Member
Licensed User
Longtime User
With KitKat, Google changed AlarmManager such that if you wanted the Service to start at exactly set time you had to use AlarmManager new method setExact (int type, long triggerAtMillis, PendingIntent operation).
Erel implemented this in B4A as StartServiceAtExact.
Now I have upgraded to android 6.0.1 and (not) to my surprise this function is no longer accurate. I get sometimes couple of minutes (6, 7 min.) difference from the scheduled time.
This of course is the result of Google introducing a "Doze" mode.

I tried to use AlarmManager method setExactAndAllowWhileIdle (not tested yet), but searching the web I found that this doesn't fix the problems.
On stackoverflow http://stackoverflow.com/questions/33432661/alarm-manager-for-background-services I found a Java example that uses WakefulBroadcastReceiver that supposed to work in Doze mode. But I am not sure yet how to implement it. Maybe someone with more Java knowledge (or Erel) could have a look at this - i.e. how to implement WakefulBroadcastReceiver. There is also a link there to the CommonsWare website with an example on Github https://github.com/commonsguy/cwac-wakeful

The code is:
B4X:
public class PollReceiver extends WakefulBroadcastReceiver{
    static final String PERIOD = "period";
    @Override
    public void onReceive(Context context, Intent intent){
         startWakefulService(context,new Intent(context,MyService.class));
         long period = intent.getLongExtra(PERIOD,-1);
         if(period>0){
         scheduleExactAlarm(context,(AlarmManager)context.getSystemService(Context.ALARM_SERVICE),period)
         }
    }

    static void scheduleExactAlarm(Context context,AlarmManager alarms, long period){
    Intent i = new Intent(context,PollReceiver.class);
    PendingIntent pi = PendingIntent.getBroadcast(context,0,i,0);
    if(Build.VERSION.SDK_INT>Build.VERSION_CODES.LOLLIPOP_MR1){
        alarms.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP,
        SystemClock.elapsedRealtime() + period,pi);
    }
}
 

Erel

B4X founder
Staff member
Licensed User
Longtime User
WakefulBroadcastReceiver solves a different problem where the broadcast receiver is started and then the device goes to sleep again.

Try it with this code:
B4X:
Sub SetExactAndAllowWhileIdle (Time As Long, ServiceName As String)
   Dim p As Phone
   If p.SdkVersion < 23 Then
     StartServiceAtExact(ServiceName, Time, True)
   Else
     Dim in As Intent
     in.Initialize("", "")
     in.SetComponent(Application.PackageName & "/." &  ServiceName.ToLowerCase)
     Dim jin As JavaObject = in
     jin.RunMethod("setAction", Array(Null))
     Dim ctxt As JavaObject
     ctxt.InitializeContext
     Dim am As JavaObject = ctxt.RunMethod("getSystemService", Array("alarm"))
     Dim pi As JavaObject
     pi = pi.InitializeStatic("android.app.PendingIntent").RunMethod("getService", _
  Array(ctxt, 1, in, 134217728))
     am.RunMethod("setExactAndAllowWhileIdle", Array(0, Time, pi))
   End If
End Sub
 
Last edited:
Upvote 0

wes58

Active Member
Licensed User
Longtime User
WakefulBroadcastReceiver solves a different problem where the broadcast receiver is started and then the device goes to sleep again.

Try it with this code:
B4X:
Sub SetExactAndAllowWhileIdle (Time As Long, ServiceName As String)
   Dim p As Phone
   If p.SdkVersion < 23 Then
     StartServiceAtExact(ServiceName, Time, True)
   Else
     Dim in As Intent
     in.Initialize("", "")
     in.SetComponent(Application.PackageName & "/." &  ServiceName.ToLowerCase)
     Dim ctxt As JavaObject
     ctxt.InitializeContext
     Dim am As JavaObject = ctxt.RunMethod("getSystemService", Array("alarm"))
     Dim pi As JavaObject
     pi = pi.InitializeStatic("android.app.PendingIntent").RunMethod("getService", _
       Array(ctxt, 1, in, 134217728))
     am.RunMethod("setExactAndAllowWhileIdle", Array(0, Time, pi))
   End If
End Sub
I did it similar way (the way I used to do it for setExact method prior to you implementing it. See below) but I didn't have time to test it. But reading comments on the stackoverflow I am not very optimistic. They say that this method doesn't work.

B4X:
    public void StartServiceAtExactWhileIdle(Long time, Boolean wakeUp){            
        AlarmManager alMan = (AlarmManager)getSystemService(Context.ALARM_SERVICE);
        Intent intent = new Intent(this.getApplicationContext(), this.getClass());
        PendingIntent    penInt = PendingIntent.getService(this.getApplicationContext(), 1, intent, 134217728);
        alMan.setExactAndAllowWhileIdle(wakeUp?AlarmManager.RTC_WAKEUP:AlarmManager.RTC, time, penInt);
    }
I will see if it works tomorrow.
 
Upvote 0

wes58

Active Member
Licensed User
Longtime User
I have tested it and on Android 6 when Alarm Manager Method setExactAndAllowWhileIdle is used, the scheduled service starts is at the set time.
 
Upvote 0

wes58

Active Member
Licensed User
Longtime User
I tried it, and it did not wake my device up
I have been using it for over a week with my application that changes phone profile (i.e. switches data, wifi, bluetooth on/off and change other settings depending on the time of the day). The profiles are changed 3 times a day and the phone wakes up accurately at the set times.
Which code are you using the one that Erel posted or mine? If you are using Erel's, did you compile with SDK ver. 23?

Maybe you can post your code (or part of it).
 
Upvote 0

NeoTechni

Well-Known Member
Licensed User
Longtime User
I used

B4X:
Sub StartServiceAtExact2 (ServiceName As String, Time As Long, DuringSleep As Boolean)
  Dim p As Phone
  If p.SdkVersion < 23 Then
  StartServiceAtExact(ServiceName, Time, DuringSleep)
  Else
  Dim in As Intent, ctxt As JavaObject, pi As JavaObject
  in.Initialize("", "")
  in.SetComponent(Application.PackageName & "/." &  ServiceName.ToLowerCase)
  ctxt.InitializeContext
  Dim am As JavaObject = ctxt.RunMethod("getSystemService", Array("alarm"))
  pi = pi.InitializeStatic("android.app.PendingIntent").RunMethod("getService",  Array(ctxt, 1, in, 134217728))
  am.RunMethod("setExactAndAllowWhileIdle", Array(0, Time, pi))
  End If
End Sub
I used the name so I can just paste over my StartServiceAtExact calls.

It didn't trigger till I turned my screen on
 
Upvote 0

NeoTechni

Well-Known Member
Licensed User
Longtime User
I made an alarm clock that I used for over a year, until Google neutered it with Doze mode.
My alarm clock is a lot more, aggressive, than Google's. As I have narcolepsy, and waking me up gets difficult some times

So I set my alarm for 11 am, and it didn't go off till I turned my screen on at 11:02
 
Upvote 0

wes58

Active Member
Licensed User
Longtime User
I made an alarm clock that I used for over a year, until Google neutered it with Doze mode.
My alarm clock is a lot more, aggressive, than Google's. As I have narcolepsy, and waking me up gets difficult some times

So I set my alarm for 11 am, and it didn't go off till I turned my screen on at 11:02
I know that I asked before about what SDK you compiled your apk with, and I didn't mean what version of android.jar you are using.
Why I ask again? Because I tried your code in my apk and it didn't work. Then I realised that in Manifest I had targetSdkVersion="20". That's why compiled apk used StartServiceAtExact and that's why it wasn't accurate.
I don't use targetSdkVersion="23" because of problems with permissions that google changed in Android 6.
So if you have targetSdkVersion les than 23 than remove the check from your code. That's the only explanation why it doesn't work that I can think of.

I think, that better way would probably be, to check what OS version is apk used on rather than what target SDK version is.
 
Upvote 0

NeoTechni

Well-Known Member
Licensed User
Longtime User
I didnt understand your instructions

I have minsdk as 4
Target is left out but it looks like im targeting 10 so I can keep the : button
 
Upvote 0

wes58

Active Member
Licensed User
Longtime User
I didnt understand your instructions

I have minsdk as 4
Target is left out but it looks like im targeting 10 so I can keep the : button

In my apk. I used my code and it worked without problems, so I did some tests with yours (Erel's) code and I found out that it doesn't work.

My code is:
B4X:
'declare variable for java object
Dim srvAt As JavaObject
'call the function
srvAt.InitializeContext
srvAt.RunMethod("StartServiceAtExactWhileIdle", Array(timeToAlarm, True))


#If JAVA
//import android.app.Service;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.os.PowerManager;

public void StartServiceAtExactWhileIdle(Long time, Boolean wakeUp){ 
   AlarmManager alMan = (AlarmManager)getSystemService(Context.ALARM_SERVICE);
   Intent intent = new Intent(this.getApplicationContext(), this.getClass());
   PendingIntent penInt = PendingIntent.getService(this.getApplicationContext(), 1, intent, 134217728);
   alMan.setExactAndAllowWhileIdle(wakeUp?AlarmManager.RTC_WAKEUP:AlarmManager.RTC, time, penInt);
}
#End If

Edit:
I realised why yours/Erel's code didn't work for me. I used as a ServiceName Me instead of just service module name.
 
Last edited:
Upvote 0

wes58

Active Member
Licensed User
Longtime User
Now I'm even more confused. How do I use that with the existing code?
Sorry to confuse you.
So you have to options:
1. Use your code. What did you use in the sub for ServiceName? Did you use your service module name? Then your code should work. You can't use Me as a ServiceName.
In your code you have: in.SetComponent(Application.PackageName & "/." & ServiceName.ToLowerCase)
If you use Me for ServiceName, than in above line, whatever is replaced be Me will be appended to Application.PackageName & "/." & Me
Do the test if you like
ServiceName = Your_Module_Service_Name
Log("Service Name -" & Application.PackageName & "/." & ServiceName.ToLowerCase)
ServiceName = Me
Log("Service Name Me -" & Application.PackageName & "/." & ServiceName.ToLowerCase)

2. Use my code:
Declare java object variable
Dim srvAt As JavaObject
Add java code between #IfJava .... #End If, at the end of the Service module.

Replace in your code, where you had a call: StartServiceAtExact2(...)
with two lines of code:
srvAt.InitializeContext
srvAt.RunMethod("StartServiceAtExactWhileIdle", Array(timeToAlarm, True))

To confuse you more (hopefully not). When you look at both, mine and Erels code (different variable names) they are similar so both should work. He is using javaObject only. I use JavaObject to call Java Fucntion only:
B4X:
'Context
    ctxt.InitializeContext
'my code
    srvAt.InitializeContext
   
'Intent
    in.Initialize("", "")
    in.SetComponent(Application.PackageName & "/." &  ServiceName.ToLowerCase)
'my java code   
   Intent intent = new Intent(this.getApplicationContext(), this.getClass()); 

'Pending Intent
    pi = pi.InitializeStatic("android.app.PendingIntent").RunMethod("getService",  Array(ctxt, 1, in, 134217728))   
'my java code
   PendingIntent penInt = PendingIntent.getService(this.getApplicationContext(), 1, intent, 134217728);

'Alarm Manager
    Dim am As JavaObject = ctxt.RunMethod("getSystemService", Array("alarm"))
    am.RunMethod("setExactAndAllowWhileIdle", Array(0, Time, pi))
'my java code 
    AlarmManager alMan = (AlarmManager)getSystemService(Context.ALARM_SERVICE);   
    alMan.setExactAndAllowWhileIdle(wakeUp?AlarmManager.RTC_WAKEUP:AlarmManager.RTC, time, penInt);
 
Upvote 0

NeoTechni

Well-Known Member
Licensed User
Longtime User
I mean, what SDK should I target, how does your code use a service name? (and I use the names of the service, not "me")

Shouldn't the java code be encapsulated in something? That doesnt look right...

Is this right?
B4X:
Sub SetAlarm(timeToAlarm As Long)
   'declare variable for java object
   Dim srvAt As JavaObject
   'call the function
   srvAt.InitializeContext
   srvAt.RunMethod("StartServiceAtExactWhileIdle", Array(timeToAlarm, True))
End Sub

#If JAVA
//import android.app.Service;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.os.PowerManager;

public void StartServiceAtExactWhileIdle(Long time, Boolean wakeUp){
  AlarmManager alMan = (AlarmManager)getSystemService(Context.ALARM_SERVICE);
  Intent intent = new Intent(this.getApplicationContext(), this.getClass());
  PendingIntent penInt = PendingIntent.getService(this.getApplicationContext(), 1, intent, 134217728);
  alMan.setExactAndAllowWhileIdle(wakeUp?AlarmManager.RTC_WAKEUP:AlarmManager.RTC, time, penInt);
}
#End If

Does this wake the whole device up? Meaning if I make an APK that just handles this alarm, (so I don't have to target 23 in my main program) will my program's alarm go off once this wakes up?
 
Last edited:
Upvote 0

NeoTechni

Well-Known Member
Licensed User
Longtime User
I get

javac 1.8.0_45
src\com\omnicorp\mew\starter.java:244: error: cannot find symbol
alMan.setExactAndAllowWhileIdle(wakeUp?AlarmManager.RTC_WAKEUP:AlarmManager.RTC, time, penInt);
^
symbol: method setExactAndAllowWhileIdle(int,Long,PendingIntent)
location: variable alMan of type AlarmManager
 
Upvote 0

DonManfred

Expert
Licensed User
Longtime User
Upvote 0

wes58

Active Member
Licensed User
Longtime User
I mean, what SDK should I target, how does your code use a service name? (and I use the names of the service, not "me")

Shouldn't the java code be encapsulated in something? That doesnt look right...

Is this right?
B4X:
Sub SetAlarm(timeToAlarm As Long)
   'declare variable for java object
   Dim srvAt As JavaObject
   'call the function
   srvAt.InitializeContext
   srvAt.RunMethod("StartServiceAtExactWhileIdle", Array(timeToAlarm, True))
End Sub

#If JAVA
//import android.app.Service;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.os.PowerManager;

public void StartServiceAtExactWhileIdle(Long time, Boolean wakeUp){
  AlarmManager alMan = (AlarmManager)getSystemService(Context.ALARM_SERVICE);
  Intent intent = new Intent(this.getApplicationContext(), this.getClass());
  PendingIntent penInt = PendingIntent.getService(this.getApplicationContext(), 1, intent, 134217728);
  alMan.setExactAndAllowWhileIdle(wakeUp?AlarmManager.RTC_WAKEUP:AlarmManager.RTC, time, penInt);
}
#End If

Does this wake the whole device up? Meaning if I make an APK that just handles this alarm, (so I don't have to target 23 in my main program) will my program's alarm go off once this wakes up?
- Read about using Java in B4A - https://www.b4x.com/android/forum/threads/inline-java-code.50141/#content
- The service name I get from "this.getClass()"
- If you used in your application before StartServiceAtExact to handle alarm, function StartServiceAtExactWhileIdle (or from Erel's code SetExactAndAllowWhileIdle) will do exactly the same in Android 6.
- As per Erel's and Don Manfred's comments use correct android.jar.
 
Upvote 0

NeoTechni

Well-Known Member
Licensed User
Longtime User
But I'm using Erel's code and it doesn't work. He seemed to imply it would work without needing a new jar file.

Which jar should I use?
 
Upvote 0
Top