FrameworkScheduler.java

package com.birbit.android.jobqueue.scheduling;

import android.annotation.TargetApi;
import android.app.job.JobInfo;
import android.app.job.JobParameters;
import android.app.job.JobScheduler;
import android.app.job.JobService;
import android.content.ComponentName;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.PersistableBundle;
import android.support.annotation.Nullable;

import com.birbit.android.jobqueue.log.JqLog;
import com.birbit.android.jobqueue.network.NetworkUtil;

import java.util.UUID;

/**
 * Scheduler implementation that uses the frameworks' scheduler API.
 */
@TargetApi(21)
class FrameworkScheduler extends Scheduler {
    private static final String KEY_UUID = "uuid";
    private static final String KEY_ID = "id";
    private static final String KEY_DELAY = "delay";
    private static final String KEY_NETWORK_STATUS = "networkStatus";

    private JobScheduler jobScheduler;
    private static SharedPreferences preferences;
    private ComponentName componentName;
    // set when service invokes, cleared when service dies
    @Nullable private JobService jobService;
    private final Class<? extends FrameworkJobSchedulerService> serviceImpl;

    FrameworkScheduler(Class<? extends FrameworkJobSchedulerService> serviceImpl) {
        this.serviceImpl = serviceImpl;
    }

    void setJobService(@Nullable JobService jobService) {
        this.jobService = jobService;
    }

    private static SharedPreferences getPreferences(Context context) {
        synchronized (FrameworkScheduler.class) {
            if (preferences == null) {
                preferences = context.getSharedPreferences("jobqueue_fw_scheduler",
                        Context.MODE_PRIVATE);
            }
            return preferences;
        }
    }

    private ComponentName getComponentName() {
        if (componentName == null) {
            componentName = new ComponentName(getApplicationContext().getPackageName(),
                    serviceImpl.getCanonicalName());
        }
        return componentName;
    }

    /**
     * Creates a new ID for the job info. Can be overridden if you need to provide different ids not
     * to conflict with the rest of your application.
     *
     * @return A unique integer id for the next Job request to be sent to system scheduler
     */
    private int createId() {
        synchronized (FrameworkScheduler.class) {
            final SharedPreferences preferences = getPreferences(getApplicationContext());
            final int id = preferences.getInt(KEY_ID, 0) + 1;
            preferences.edit().putInt(KEY_ID, id).commit();
            return id;
        }
    }

    private JobScheduler getJobScheduler() {
        if (jobScheduler == null) {
            jobScheduler = (JobScheduler) getApplicationContext()
                    .getSystemService(Context.JOB_SCHEDULER_SERVICE);
        }
        return jobScheduler;
    }

    @Override
    public void request(SchedulerConstraint constraint) {
        JobScheduler jobScheduler = getJobScheduler();

        final int id = createId();
        JobInfo.Builder builder = new JobInfo.Builder(id, getComponentName())
                .setExtras(toPersistentBundle(constraint))
                .setPersisted(true);
        switch (constraint.getNetworkStatus()) {
            case NetworkUtil.UNMETERED:
                builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED);
                break;
            case NetworkUtil.METERED:
                builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY);
                break;
            default:
                builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_NONE);
                builder.setRequiresDeviceIdle(true);
                break;
        }
        if (constraint.getOverrideDeadlineInMs() != null) {
            builder.setOverrideDeadline(constraint.getOverrideDeadlineInMs());
        }
        int scheduled = jobScheduler.schedule(builder.build());
        JqLog.d("[FW Scheduler] scheduled a framework job. Success? %s id: %d" +
                " created id: %d", scheduled > 0, scheduled, id);
    }

    @Override
    public void onFinished(SchedulerConstraint constraint, boolean reschedule) {
        if (JqLog.isDebugEnabled()) {
            JqLog.d("[FW Scheduler] on finished job %s. reschedule:%s", constraint, reschedule);
        }
        JobService service = this.jobService;
        if (service == null) {
            JqLog.e("[FW Scheduler] scheduler onfinished is called but i don't have a job service");
            return;
        }

        Object data = constraint.getData();
        if (data instanceof JobParameters) {
            JobParameters params = (JobParameters) data;
            service.jobFinished(params, reschedule);
        } else {
            JqLog.e("[FW Scheduler] cannot obtain the job parameters");
        }

    }

    @Override
    public void cancelAll() {
        JqLog.d("[FW Scheduler] cancel all");
        getJobScheduler().cancelAll();
    }

    private static PersistableBundle toPersistentBundle(SchedulerConstraint constraint) {
        PersistableBundle bundle = new PersistableBundle();
        // put boolean is api 22
        bundle.putString(KEY_UUID, constraint.getUuid());
        bundle.putInt(KEY_NETWORK_STATUS, constraint.getNetworkStatus());
        bundle.putLong(KEY_DELAY, constraint.getDelayInMs());
        return bundle;
    }

    private static SchedulerConstraint fromBundle(PersistableBundle bundle) {
        SchedulerConstraint constraint = new SchedulerConstraint(bundle.getString(KEY_UUID));
        if (constraint.getUuid() == null) {
            // backward compatibility
            constraint.setUuid(UUID.randomUUID().toString());
        }
        constraint.setNetworkStatus(bundle.getInt(KEY_NETWORK_STATUS, NetworkUtil.DISCONNECTED));
        constraint.setDelayInMs(bundle.getLong(KEY_DELAY, 0));
        return constraint;
    }

    boolean onStartJob(JobParameters params) {
        SchedulerConstraint constraint = fromBundle(params.getExtras());
        if (JqLog.isDebugEnabled()) {
            JqLog.d("[FW Scheduler] start job %s %d", constraint, params.getJobId());
        }
        constraint.setData(params);
        return start(constraint);
    }

    boolean onStopJob(JobParameters params) {
        SchedulerConstraint constraint = fromBundle(params.getExtras());
        return stop(constraint);
    }
}