1

I want to create a job that will be triggered when the device is plugged in, just like broadcast receiver for ACTION_POWER_CONNECTED.

Here is the worker class:

public class ChargerWorker extends Worker {

/* Constants */
    private static final String TAG = "ChargerWorker";
    private static final long TRIGGER_AGE = TimeUnit.MINUTES.toMillis(30);       // At least 30 min old.

    @NonNull
    @Override
    public Result doWork() {
        Log.e(TAG, "Power connection worker of Indoor/Outdoor lib.");
        IndoorOutdoorLogger.v(TAG, "Power connection worker of Indoor/Outdoor lib.");
        Context context = getApplicationContext();

        if (Conditions.isBatteryChargingOnAC(context)) {
            IndoorOutdoorLogger.d(context, TAG, "Power plugged and connected to AC.");
            Log.e(TAG, "Power plugged and connected to AC.");

            if (WiFiConnection.isWifiConnected(context) && WiFiConnection.isCurrentNetworkIndoorRecognized(context)) {
                // In this case an "upgrade" to the confidence level is possible.
                // Only run when the last known detection is old enough.
                DetectionResult latestResult = DetectionResult.loadFromCache(context);
                if (!latestResult.isTimestampValid(TRIGGER_AGE)) {
                    IndoorOutdoorLogger.d(context, TAG, "AC power while connected to a recognized WiFi network, and last detection is old enough, starting detection.");
                    IndoorOutdoorClient client = new IndoorOutdoorClient(context, null);
                    client.startShortDetection(Trigger.POWER);
                }
            }
        }
        return Result.SUCCESS;
    } }

This is how I initialize the work in my onCreate() method:

Constraints constraints = new Constraints.Builder().setRequiresCharging(true).build();
        OneTimeWorkRequest simpleReuquest = new OneTimeWorkRequest.Builder(ChargerWorker.class)
                .setConstraints(constraints)
                .build();
        WorkManager.getInstance().enqueue(simpleReuquest);

When I connect the device to the adapter for the first time, everything works as it should. However when I disconnect the device, and try again, I never reach the doWork function again. Instead I see the following message in the logcat:

E/WorkerWrapper: Status for 031e39f1-bc10-4a35-9341-11453fc0ca21 is SUCCEEDED; not doing any work.

Is it because I use OneTimeWorkRequest? If so how can I schedule it to run every time the device is connected to a power source?

Keselme
  • 3,779
  • 7
  • 36
  • 68

2 Answers2

3

This is because you use OnTimeWorkRequest, from its name you can infer that the request is valid for one time only. Once triggered, it won't be called again, not unless you euqueue it again.
Problem is WorkManager doesn't support any other requests or constraints that are relevant to your need. Take a look at this question.

If you really want to use this Pattern of WorkManger and Requests, i can give you an idea, that relies on the fact that people tend to charge their phone for more than few minutes every time.

You can use a 2 types of WorkRequests combined:

  • OneTimeWorkRequest that is the actual logic of your algorithm. Set the constraints to be only when charging.
    Once triggered, this worker will enqueue a PeriodicWorkRequest without any constraints and "The minimum repeat interval that can be defined is 15 minutes"

  • PeriodicWorkRequest that that will be triggered every 15 minutes and periodically check if the phone is still charging.
    The first time the worker detect that the device is unplugged, it enqueues the OneTimeWorkRequest from above and cancels itself.

This way you won't detect rapid changes of device power states, but it's the best possible in Android Oreo (and above) since apps that target the API level 26 or higher can no longer register broadcast receivers for implicit broadcasts in their manifest

To check whether your device is plugged or not:

You can even detect what kind of charging it is, use this code:

IntentFilter ifilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
Intent batteryStatus = context.registerReceiver(null, ifilter);

// Are we charging / charged?
int status = batteryStatus.getIntExtra(BatteryManager.EXTRA_STATUS, -1);
boolean isCharging = status == BatteryManager.BATTERY_STATUS_CHARGING ||
                 status == BatteryManager.BATTERY_STATUS_FULL;

// How are we charging?
int chargePlug = batteryStatus.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1);
boolean usbCharge = chargePlug == BatteryManager.BATTERY_PLUGGED_USB;
boolean acCharge = chargePlug == BatteryManager.BATTERY_PLUGGED_AC;

taken right from android developer guide

To cancel a WorkRequest:

First, build it with a tag:

OneTimeWorkRequest.Builder(MyWorker.class)
            .setConstraints(myConstraints)
            .addTag(tag)
            .build();

and then do WorkManager.getInstance().cancelAllWorkByTag(tag)

Another option is to enqueue a unique work:

WorkManager.getInstance().enqueueUniquePeriodicWork(
            uniqueWorkName, ExistingPeriodicWorkPolicy.REPLACE, periodicWork);

and then cancel it with WorkManager.getInstance().cancelUniqueWork(workName)

Community
  • 1
  • 1
ronginat
  • 1,910
  • 1
  • 12
  • 23
  • Thank you, I looked at the referenced answer, however they suggest to use BatteryManager and ACTION_POWER_CONNECTED receiver, since I'm targeting Android 8 and above, I can't use implicit broadcasts in the manifest. This is why I wanted to use WorkManager. I will try your another approach with PeriodicWorkRequest. Can you please explain this part: " .. every that period of time even after first triggered, so if you charge your device for twice that amount, the worker will be called twice as well... "? – Keselme Mar 25 '19 at 11:34
  • Sure, lets say you created a `PeriodicWorkRequest` with an interval of 15 minutes. Now, when you charge your device for 30 minutes, your worker will be triggered twice. The `PeriodicWorkRequest` will still be triggered when all of its constraints are true and every interval of 15 minutes. You didn't mention if you need your worker to work just once every charge or not, so i added this approach. – ronginat Mar 25 '19 at 11:40
  • I think the first approach is better, and not so complicated to implement. – ronginat Mar 25 '19 at 11:41
  • I want it to run only once when it was plugged in, I guess I can handle the case when it's charging for more than 15 minutes and it was triggered. – Keselme Mar 25 '19 at 11:43
  • glad i was able to help! – ronginat Mar 25 '19 at 11:45
  • One more thing, I noticed this comment: "I think the first approach is better, and not so complicated to implement." Do you refer here to the approach with two OneTimeWorkRequests? – Keselme Mar 25 '19 at 12:00
  • **Yes**. You see, with the `PeriodicWorkRequest` you still going to need some reference whether your worker has been triggered before for the current charging. So, it might be simpler to use 2 `OneTimeWorkerRequests` – ronginat Mar 25 '19 at 12:22
  • I don't think this approach work, I tried it and when I connect the device it triggers the onCharge worker when I unplugged it, it didn't trigger the other worker. So when I tried to plug in it again I got the message: "Status for bda5ca96-2a5d-42ad-9aee-bdaa23ed3176 is SUCCEEDED; not doing any work". – Keselme Mar 25 '19 at 13:16
0

You Should to use PeriodicWorkRequest that let your Work manager work many time on Time you Choose Fays Minutes Hours as you want