Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to listen for power connected Android > 8

Tags:

android

kotlin

I have a simple Android Kotlin app, and part of what it does is listen for when power is connected and disconnected and perform an action

This is my old code, and it worked totally fine while targeting devices below Oreo.

AndroidManifest.xml

<receiver android:name=".ChargingUtil$PlugInReceiver">
    <intent-filter>
        <action android:name="android.intent.action.ACTION_POWER_CONNECTED" />
        <action android:name="android.intent.action.ACTION_POWER_DISCONNECTED" />
    </intent-filter>
</receiver>

ChargingUtil.kt

class ChargingUtil (context: Context){

    /*... Some other charging-related functions here ... */

    class PlugInReceiver : BroadcastReceiver() {

        override fun onReceive(context: Context, intent: Intent) {
            Log.d("thisistest", "Power was changed")
            // Here I do some logic with `intent.action`
        }
    }
}

There have been some changes to how to implement Broadcasts, in later Android versions: https://developer.android.com/guide/components/broadcasts

What I've tried so far:

  • I tried following this documentation, but their implementation is actually the same as my current code (which only works below Android 8).
  • I also found this question, but the only solution, was to periodically check if power is connected or not. I don't think that is so viable for me, since my app needs to know instantly when the charging state is changed.

So my question is:

How to call a function when power is connected/ disconnected? While taking account of the additional restrictions that systems running Android 8 or later impose on manifest-declared receivers.

Note: I am using Kotlin, and would like to avoid the use of deprecated packages


I am a bit of a noob when it comes to Android, so sorry if there is actually an obvious solution what I just missed. Thanks in advance for any help.

like image 266
Alicia Avatar asked Jun 11 '19 08:06

Alicia


People also ask

What is BroadcastReceiver Android?

A broadcast receiver (receiver) is an Android component which allows you to register for system or application events. All registered receivers for an event are notified by the Android runtime once this event happens.

What is IntentFilter Android?

An intent filter is an expression in an app's manifest file that specifies the type of intents that the component would like to receive. For instance, by declaring an intent filter for an activity, you make it possible for other apps to directly start your activity with a certain kind of intent.


3 Answers

The question you have referred already has the answer.

Use ACTION_BOOT_COMPLETED to start a foreground service after boot. This service should register ACTION_POWER_CONNECTED broadcast. You can also start this service in a separate process. As soon as the power is connected/disconnected, you will receive the broadcast in service class where you can run your required method.

public class MyService extends Service {
    private String TAG = this.getClass().getSimpleName();

    @Override
    public void onCreate() {
        Log.d(TAG, "Inside onCreate() API");
        if (Build.VERSION.SDK_INT >= 26) {
            NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(this);
            mBuilder.setSmallIcon(R.drawable.ic_launcher);
            mBuilder.setContentTitle("Notification Alert, Click Me!");
            mBuilder.setContentText("Hi, This is Android Notification Detail!");
            NotificationManager mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);

            // notificationID allows you to update the notification later on.
            mNotificationManager.notify(100, mBuilder.build());
            startForeground(100, mBuilder.mNotification);

            IntentFilter filter1=new IntentFilter();
            filter1.addAction(Intent.ACTION_POWER_CONNECTED);
            registerReceiver(myBroadcastReceiver,filter1);
        }
    }


    @Override
    public int onStartCommand(Intent resultIntent, int resultCode, int startId) {
        Log.d(TAG, "inside onStartCommand() API");
        return startId;
    }


    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.d(TAG, "inside onDestroy() API");
    }

    @Override
    public IBinder onBind(Intent intent) {
        // TODO Auto-generated method stub
        return null;
    }

       BroadcastReceiver myBroadcastReceiver =new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                // call your method
            }
        };
}

You can also register a JobScheduler with setRequiresCharging true. this will start the call JobScheduler's job when the power state is changed.

ComponentName serviceComponent = new ComponentName(context, TestJobService.class);
JobInfo.Builder builder = new JobInfo.Builder(0, serviceComponent);
builder.setMinimumLatency(1 * 1000); // wait at least
builder.setOverrideDeadline(3 * 1000); // maximum delay
builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED); 
builder.setRequiresDeviceIdle(true); 
builder.setRequiresCharging(true);
JobScheduler jobScheduler = context.getSystemService(JobScheduler.class);
jobScheduler.schedule(builder.build());
like image 189
Deˣ Avatar answered Oct 23 '22 09:10

Deˣ


Starting from Android 8.0 (API level 26 and higher), you can't use static receivers to receive most Android system broadcasts read here ,

A BroadcastReceiver is either a static receiver or a dynamic receiver, depending on how you register it:

  • To register a receiver statically, use the <receiver> element in your AndroidManifest.xml file. Static receivers are also called manifest-declared receivers.

  • To register a receiver dynamically, use the app context or activity context. The receiver receives broadcasts as long as the registering context is valid, meaning as long as the corresponding app or activity is running. Dynamic receivers are also called context-registered receivers.

So you need to register your Receiver dynamically , go to AndroidManifest.xml and delete the <receiver> tag , and register it at your activity .

private MyBatteryReceiver mReceiver = new MyBatterReceiver();

Then use IntentFilter for the power actions :

IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_POWER_DISCONNECTED);
filter.addAction(Intent.ACTION_POWER_CONNECTED)

All you have left is to register and dismiss your register

this.registerReceiver(mReceiver, filter); // using activity context.
this.unregisterReceiver(mReceiver); // implement on onDestroy().
like image 29
Ben Shmuel Avatar answered Oct 23 '22 11:10

Ben Shmuel


Thanks to @Derek's answer, plus a couple of changes I got this to work. Posting the working solution to here, in case it's any help to anyone else.

PowerConnectionReciever.kt

import android.content.Intent
import android.content.BroadcastReceiver
import android.os.IBinder
import android.content.IntentFilter
import android.app.Service
import android.content.Context

class PowerConnectionService : Service() {

    private var connectionChangedReceiver: BroadcastReceiver = 
        object : BroadcastReceiver() {
        override fun onReceive(context: Context, intent: Intent) {
            // This block gets run whenever the power connection state is changed
            when {
                intent.action == Intent.ACTION_POWER_CONNECTED ->
                    powerWasConnected()
                intent.action == Intent.ACTION_POWER_DISCONNECTED ->
                    powerWasDisconnected()
            }
        }
    }

    override fun onCreate() {
        val connectionChangedIntent = IntentFilter()
        connectionChangedIntent.addAction(Intent.ACTION_POWER_CONNECTED)
        connectionChangedIntent.addAction(Intent.ACTION_POWER_DISCONNECTED)
        registerReceiver(connectionChangedReceiver, connectionChangedIntent)
    }

    override fun onStartCommand(
        resultIntent: Intent, resultCode: Int, startId: Int): Int {
        return startId
    }

    override fun onDestroy() {
        super.onDestroy()
        unregisterReceiver(connectionChangedReceiver)
    }

    override fun onBind(intent: Intent): IBinder? {
        return null
    }

    private fun powerWasConnected() {
        // Do whatever you need to do when the power is connected here
    }
    private fun powerWasDisconnected() {
        // And here, do whatever you like when the power is disconnected
    }
}

Then within my MainActivity.kt file, added this function, which is called within the onCreate hook.

private fun startPowerConnectionListener() {
        val serviceComponent = ComponentName(this, PowerConnectionService::class.java)
        val builder = JobInfo.Builder(0, serviceComponent)
        builder.setMinimumLatency((200)) // wait time
        builder.setOverrideDeadline((200)) // maximum delay
        val jobScheduler = this.getSystemService(JobScheduler::class.java)
        jobScheduler.schedule(builder.build())
    }

The only other changes that I needed, were in the AndroidMainifest.xml

A permission for the FOREGROUND_SERVICE is added as a first-level item:

<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />

And of course it's also required to register the new PowerConnectionService, this goes within the relevant activity node

<service
    android:name=".PowerConnectionService"
    android:permission="android.permission.BIND_JOB_SERVICE"
    android:exported="true">
</service>
like image 35
Alicia Avatar answered Oct 23 '22 09:10

Alicia