Android: Start Service at certain Battery Level - android

I want to start an IntentService if the battery level reaches a certain value while charging.
However, I dont want to register a BroadcastReceiver for android.intent.action.BATTERY_CHANGED in the AndroidManifest.xml because i need the service only started at a certain level (e.g. 40%) and only while charging.
Another idea would be starting a service that runs as long as the phone is charged and register a receiver programmatically.
A third idea would be to periodically start a service with AlarmManager and check battery level.
What is the best way?
Solution:
I have implemented the fourth idea from answer below.
final int PI_REQUEST_CODE = 123456;
int pref_BatteryUpdatePeriod = 120000; // 2 minutes
// set repeating alarm manager
Intent monitorIntent = new Intent(this, CheckBatteryReceiver.class);
PendingIntent pendingIntent = PendingIntent.getBroadcast(this.getApplicationContext(), PI_REQUEST_CODE, monitorIntent, 0);
AlarmManager alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE);
alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, Calendar.getInstance().get(Calendar.MILLISECOND), pref_BatteryUpdatePeriod, pendingIntent);
and the broadcast receiver for the alarm:
public class CheckBatteryReceiver extends BroadcastReceiver {
private int targetBatterylevel = 40;
#Override
public void onReceive(Context context, Intent intent) {
// get sticky intent
Intent batteryStatusIntent = context.getApplicationContext()
.registerReceiver(null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
int batteryLevel = batteryStatusIntent.getIntExtra(BatteryManager.EXTRA_LEVEL, 50);
// evaluate battery level
if(batteryLevel >= targetBatterylevel){
// start service to stop charging
} else {
// continue charging
}
}
}
Note: I had to use context.getApplicationContext() instead of context otherwise the app would crash with an exception that I can't register a receiver within a broadcastreceiver

I dont want to register a BroadcastReceiver for android.intent.action.BATTERY_CHANGED in the AndroidManifest.xml because i need the service only started at a certain level (e.g. 40%) and only while charging.
That does not work anyway, as you cannot register for that broadcast in the manifest.
Another idea would be starting a service that runs as long as the phone is charged and register a receiver programmatically.
Yuck.
A third idea would be to periodically start a service with AlarmManager and check battery level.
A fourth idea is to use AlarmManager, as you suggest, but bypass the service at the outset. Just have AlarmManager invoke a BroadcastReceiver. It can check the battery level by calling context.registerReceiver(null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED)) (on the context passed into onReceive()). The Intent returned by registerReceiver() will be the last broadcast of ACTION_BATTERY_CHANGED. If the battery level is at or below your desired threshold, then start your IntentService. You may need to adopt the WakefulBroadcastReceiver or WakefulIntentService pattern if your alarm will wake up the device. Also allow the user to choose the polling period, so they can trade off precision for better device performance.
I would go with the fourth idea.

It doesn't sound too bad to have a service running, reacting to android.intent.action.BATTERY_CHANGED as long as it doesn't do anything when you don't need it to. That is, have a check for the interesting battery level and charging state early and just exit if you have nothing to do.
If you want to complicate things, register a mini service which only has the task of starting your main service when conditions are right. However, I don't see what benefit that would have.

public class Main extends Activity {
private TextView batteryTxt;
private BroadcastReceiver mBatInfoReceiver = new BroadcastReceiver(){
#Override
public void onReceive(Context ctxt, Intent intent) {
int level = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0);
batteryTxt.setText(String.valueOf(level) + "%");
}
};
#Override
public void onCreate(Bundle b) {
super.onCreate(b);
setContentView(R.layout.main);
contentTxt = (TextView) this.findViewById(R.id.batteryTxt);
this.registerReceiver(this.mBatInfoReceiver, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
}
}

Related

Android 8.0 Oreo AlarmManager with broadcast receiver and implicit broadcast ban

I have critical reminders that are set via the Alarm Manager (It should function the same way as an alarm clock application).
Previously I had the following in my Android Manifest:
<receiver android:name="com.example.app.AlarmReceiver" >
<intent-filter>
<action android:name="${packageName}.alarm.action.trigger"/>
</intent-filter>
</receiver>
The broadcast receiver:
public class AlarmReceiver extends BroadcastReceiver {
#Override public void onReceive(
final Context context,
final Intent intent) {
// WAKE LOCK
// BUILD NOTIFICATION etc...
}
}
How the alarm is set:
final PendingIntent operation = PendingIntent.getBroadcast(
mContext,
requestCode,
intent,
PendingIntent.FLAG_CANCEL_CURRENT);
if (PlatformUtils.hasMarshmallow()) {
alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, triggerAtMillis, operation);
} else {
alarmManager.setExact(AlarmManager.RTC_WAKEUP, triggerAtMillis, operation);
}
}
With Android 8.0 I can no longer use an implicit broadcast as defined in the Manifest. That's fine, the alternative given is to register it manually like so:
final BroadcastReceiver receiver = new AlarmReceiver();
final IntentFilter intentFilter = new IntentFilter(ALARM_RECEIVER_INTENT_TRIGGER);
context.registerReceiver(receiver, intentFilter);
This does not seem logical to me.
The alarm receiver will be tied to the lifetime of the context. This causes an issue when say the application is killed due to memory pressure or when the device is restarted. I need my alarms to fire every time as they are critical for the health of the user.
Even if I listen to "android.intent.action.BOOT_COMPLETED" and register my alarm receiver the app is killed shortly after and no alarm is fired. I also don't see my alarm via
adb shell dumpsys alarm
How do I create a custom broadcast receiver that receives an implicit broadcast to fire an alarm while targeting Android O (8.0)? Can someone enlighten me with a code example or link to documentation. How does Timely or any other alarm clock app function while targeting O?
Revise your code slightly to make the broadcast explicit rather than implicit and you'll be fine (assuming this is an Activity reference or some other Context):
Intent intent = new Intent(ALARM_RECEIVER_INTENT_TRIGGER);
intent.setClass(this, AlarmReceiver.class);
The restriction in Oreo is on implicit broadcast Intent registration, which is to say it you are sending it broadcasts will only action, category, or data specified. You make it an explicit broadcast by specifying the class which is to receive the broadcast.
If you guys are used to check if the alarm has already been registered don't forget to do the same on this verification:
public boolean isAlarmBroadcastRegistered(Context context, String action, Class clazz) {
Intent intent = new Intent(action);
intent.setClass(context, clazz);
return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_NO_CREATE) != null;
}

Notification Listener Service does not work after app is crashed

I have a problem on my app and I want to report this bug.
I develope the app which can crawls notifications using NotificationListenerService.
It works well.
But NotificationListenerService class has the problem I think.
Because, If the app is crashed, app can't crawl the notification at all,
UNTIL the phone reboots.
Is anyone who can solve this problem??
Please help me.
The bug is very clear!! But It is not easy to find the solution ....
If do you have already permissions then:
In your service class or another service/activity you can switch the "component hability" to listen notifications:
public void tryReconnectService() {
toggleNotificationListenerService();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
ComponentName componentName =
new ComponentName(getApplicationContext(), NotificationReaderV2Service.class);
//It say to Notification Manager RE-BIND your service to listen notifications again inmediatelly!
requestRebind(componentName);
}
}
/**
* Try deactivate/activate your component service
*/
private void toggleNotificationListenerService() {
PackageManager pm = getPackageManager();
pm.setComponentEnabledSetting(new ComponentName(this, NotificationReaderV2Service.class),
PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP);
pm.setComponentEnabledSetting(new ComponentName(this, NotificationReaderV2Service.class),
PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP);
}
Your notification listener, is a SERVICE, it can be killed by System, you can do your service as FOREGROUND to drastically decrease the probability that the system will kill your service.
#Override
public void onListenerConnected() {
super.onListenerConnected();
Log.d(TAG, "Service Reader Connected");
Notification not = createNotification();
NotificationManager mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
if (mNotificationManager != null) {
mNotificationManager.notify(NOTIFICATION_ID, not);
}
startForeground(NOTIFICATION_ID, not);
//Alarm to auto - send Intents to Service to reconnect, you can ommit next line.
alarmIt();
}
If do you like so more "safe", you can to programming not-friendly battery alarms, try to use inexact alarms please, the user's battery will be happy:
private void alarmIt() {
Log.d(TAG, "ALARM PROGRAMMATED at"+HotUtils.formatDate(new Date()));
Calendar now = Calendar.getInstance();
now.setTimeInMillis(System.currentTimeMillis());
now.set(Calendar.MINUTE, now.get(Calendar.MINUTE) + 1);
Intent intent = new Intent(this, NotificationReaderV2Service.class);
intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
intent.setAction(REBIND_ACTION);
PendingIntent pendingIntent = PendingIntent.getService(this, 0,
intent, 0);
AlarmManager manager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
//The alarms that are repeated are inaccurate by default, use RTC_WAKE_UP at your convenience.
//Alarm will fire every minute, CHANGE THIS iF DO YOU CAN, you can't use less than 1 minute to repeating alarms.
manager.setRepeating(AlarmManager.RTC_WAKEUP, now.getTimeInMillis(), 1000 * 60 * 1, pendingIntent);
}
and next read the Intent to reconnect service binding:
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d(TAG, "Notification service onStartCommandCalled");
if (intent!=null && !HotUtils.isNullOrEmpty(intent.getAction()) && intent.getAction().equals(REBIND_ACTION)){
Log.d(TAG, "TRYING REBIND SERVICE at "+HotUtils.formatDate(new Date()));
tryReconnectService();//switch on/off component and rebind
}
//START_STICKY to order the system to restart your service as soon as possible when it was killed.
return START_STICKY;
}
Keep in mind that doing all these steps you can sure that your service will be killed anyway by the system but this code will restart the service and make it harder to kill it.
Maybe, you should consider using PARTIAL_WAKE_LOCK with your service and execute it in a process independently (:remote) if you want even more certainty (Maybe this is useless)
I would like to add a common error that is often followed, NEVER override the onBind and onUnbind method or overwrite the INTENT ACTION.
This will cause your service to not be connected and never run onListenerConnected
Keep the Intent as it is, in most cases you do not need to edit it.
I see exactly the same on this. The only "solution" I've found was to have the notification listener running in a separate process. Then if the rest of the app crashes it doesn't stop the listener. So it's only then specifically notification listener service crashes that require the reboot.
Seems a terrible and over complicated solution though.
I had the same problem. Here are few things that I did and now it works wonderfully for me.
Override onStartCommand, call super and return START_STICKY;
Override onNotificationRemoved, call super and add a toast so that you know in android itself that you service has not died yet whenever you swipe a notification.
Exclude your app from Battery saving list (Settings-> Battery-> Power Saving Exclusion)
Post this the service never dies even after the main app's crash. I dont need to reboot now to restart it.

IntentService is killed when app is stopped

I've this IntentService:
public class ServiceUpdateNewResults extends IntentService{
private void setAlarmToCheckUpdates() {
Calendar calendar = Calendar.getInstance();
//calendar.add(Calendar.DAY_OF_YEAR, 1); //dema
//calendar.set(Calendar.HOUR_OF_DAY, 22); //a les 10
calendar.add(Calendar.SECOND, 20);
Intent myIntent = new Intent(this.getApplicationContext(), ReceiverCheckUpdates.class);
PendingIntent pendingIntent = PendingIntent.getBroadcast(this.getApplicationContext(), 0, myIntent,0);
AlarmManager alarmManager = (AlarmManager)this.getApplicationContext().getSystemService(this.getApplicationContext().ALARM_SERVICE);
alarmManager.set(AlarmManager.RTC, calendar.getTimeInMillis(), pendingIntent);
}
public ServiceUpdateNewResults() {
super("ServiceUpdateNewResults");
}
#Override
protected void onHandleIntent(Intent intent) {
//fem les coses.
//Toast.makeText(this.getApplicationContext(), "holaa", Toast.LENGTH_LONG).show();
setAlarmToCheckUpdates();
Log.d("debugging","hello");
}
}
And this is calling a BroadCastReceiver every 20 seconds, which ends up calling this service, and this is going to happen "forever". (in a future it will be 1 day, not 20 seconds).
This is the Receiver:
public class ReceiverCheckUpdates extends BroadcastReceiver{
Context context;
#Override
public void onReceive(Context context, Intent intent){
this.context = context;
Intent service1 = new Intent(context, ServiceUpdateNewResults.class);
context.startService(service1);
}
}
This is working perfectly, but if I stop the app from Android settings, the service is also stopped. I want to avoid this. I want that if the App is closed, the service should keep working.
Is it possible?
Actually, when is a service killed ?
if I stop the app from Android settings, the service is also stopped
If by "stop the app from Android settings", you mean that you press the Force Stop button, your app will not run again until something manually runs one of your components (e.g., user launches your activity). More specifically in this case, your alarms are unscheduled.
I want to avoid this.
Then do not press the "Force Stop" button.
I want that if the App is closed, the service should keep working.
In any non-"Force Stop" scenario, the alarms will keep firing (at least until the device falls asleep, given your current implementation).
The IntentService is part of your app. If the system destroys your app, it will destroy the IntentService. You can reduce the chances of this happening by putting the IntentService in a separate process, but you can't stop the system from destroying an IntentService. You can make it highly unlikely that the system will destroy a Service; to do that, you use a "foreground" Service. However, you should avoid doing this unless you really really need to. In addition, you can't have a foreground IntentService, so you'll have to add your own background Handler and HandlerThread.

delay in alarmmanager .. when two alarms exists simultaneously

I have scheduled multiple alarm managers to send intents at different times.
I took care to pass unique ID, context and extras to both the pending intents.
below func handles the alarm calls.
.
public void handle(int duration, int id){
Intent intent = new Intent("package.SET");
intent.putExtra ("package.id", Id);
AlarmManager amg = (AlarmManager)Context.getSystemService(Context.ALARM_SERVICE);
PendingIntent pis = PendingIntent.getBroadcast(Context,Id, intent, FLAG_ONE_SHOT);
amg.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + time, pis); }
there are 2 functions calling the alarm
public void callalarm(int time){
handle(time, UNIQUE_ID1);}
and
public void callalarm2(int time){
handle(time, UNIQUE_ID2);}
I took care that unique ID1 and Unique_ID2 are different.
The broadcast receiver handles the alarm and executes another code.
Is there a possibility that Callalarm1 and callalarm2 interfere with eachother .
I have registered the receiver using registerReceiver function and not in android manifest file.
IntentFilter ARFilter = new IntentFilter();
ARFilter.addAction("package.SET");
context.registerReceiver(AR, ARFilter);
In the AR that extends broadcast receiver, i use the id to define the action.
public BroadcastReceiver AR= new BroadcastReceiver()
{ public void onReceive(Context context, Intent intent)
{ // i do some stuff here which is confidential
}}
The problem is that, I get a delay in the alarms. Is there a reason why there is a delay ?
As of What I know, The Alarm MAnager cannot fire at right times when the task burden on the processor is heavy.
In other words, the broadcast receiver does not receive intent on time since the alarm manager adds the broadcast to the queue which is sent with some delay..
Precise information is helpful.

Disable update on battery percentage

I have a service that performs background updates.
I want to give the user the the option to disable the updates when their battery percentage reaches a certain level.
From my research, I'm going to use a receiver in the onCreate method of my Service class, eg:
public class MainService extends Service
{
#Override
public void onCreate()
{
this.registerReceiver(this.BatInfoReceiver, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
}
private BroadcastReceiver BatInfoReceiver = new BroadcastReceiver(){
#Override
public void onReceive(Context arg0, Intent intent) {
int level = intent.getIntExtra("level", 0);
}
};
}
I'm assuming that the best practice is to leave the service running, check the battery level in the service, and not perform the CPU-intensive code, based on the percentage?
I don't actually need to stop the service itself and start it up again, based on the battery percentage?
UPDATE:
This seems to be a better solution, but not 100% sure. I registered a BroadcastReceiver in the AndroidManifest:
<receiver android:name="BatteryReceiver" />
Then created a BroadcastReceiver:
public class BatteryReceiver extends BroadcastReceiver
{
#Override
public void onReceive(final Context context, final Intent intent)
{
final int currentBatteryPercent = intent.getIntExtra("level", 0);
final int disableBatteryPercent = Integer.parseInt(PreferenceManager.getDefaultSharedPreferences(context).getString("batteryPercent", 0);
//AlarmReceiver is the service that performs the background updates
final ComponentName component = new ComponentName(context, AlarmReceiver.class);
if (currentBatteryPercent < disableBatteryPercent)
{
context.getPackageManager().setComponentEnabledSetting(component, PackageManager.COMPONENT_ENABLED_STATE_DISABLED , PackageManager.DONT_KILL_APP);
}
else
{
context.getPackageManager().setComponentEnabledSetting(component, PackageManager.COMPONENT_ENABLED_STATE_ENABLED , PackageManager.DONT_KILL_APP);
}
}
}
That's right. What you will typically do is schedule some broadcast intent for an update (perhaps through an AlarmManager). When you get the notification that there is a low battery, you will stow this away in your service, and then before doing an update, check to ensure that the battery isn't too low. here is a good tutorial of watching the battery level. You shouldn't do much when handling this intent, just stick the battery level somewhere and (before doing an update) make sure it's appropriately "full."
Your update is a really bad way to stop an app. In general, asking the package manager to stop your component is much more of a hack than a solution. Instead, you should move some code into the component that actually does the updating, and store / update the information for the battery info there. Before you do an update, you check the battery level and don't proceed unless it's at a level where you feel comfortable updating the app. Using a broadcast receiver is fine, but you need to store the level somewhere (perhaps a sharedprefs). Instead, this is why putting the receiver within the service where you do the updating is probably best.
Is it possible to manage this requirement with a broadcastReceiver instead of running a service continuously ?
public void onReceive(final Context context, final Intent intent) {
if(intent != null && intent.getAction() != null) {
String action = intent.getAction();
if(action.equals(Intent.ACTION_BOOT_COMPLETED)) {
// Set alarm
}
else if(action.equals(Intent.ACTION_BATTERY_LOW)) {
setLocationAlarmReceiverEnabled(context, false);
}
else if(action.equals(Intent.ACTION_BATTERY_OKAY)) {
setLocationAlarmReceiverEnabled(context, true);
}
}
}

Categories

Resources