package com.doist.jobschedulercompat;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.util.SparseArray;

import java.lang.ref.WeakReference;

import androidx.annotation.NonNull;
import androidx.annotation.RestrictTo;

/** @see android.app.job.JobService */
public abstract class JobService extends Service {
    private Binder binder;

    @NonNull
    @Override
    public final IBinder onBind(Intent intent) {
        if (binder == null) {
            binder = new Binder(this);
        }
        return binder;
    }

    /** @see android.app.job.JobService#onStartJob(android.app.job.JobParameters) */
    public abstract boolean onStartJob(JobParameters params);

    /** @see android.app.job.JobService#onStopJob(android.app.job.JobParameters) */
    public abstract boolean onStopJob(JobParameters params);

    /** @see android.app.job.JobService#jobFinished(android.app.job.JobParameters, boolean) */
    public final void jobFinished(JobParameters params, boolean needsReschedule) {
        if (binder != null) {
            binder.notifyJobFinished(params, needsReschedule);
        }
    }

    /**
     * Proxies callbacks between the scheduler job service that is handling the work and the user's {@link JobService}.
     *
     * All scheduler job services bind to this service to proxy their lifecycle. This allows maintaining parity with
     * JobScheduler's API, while hiding implementation details away from the user.
     */
    @RestrictTo(RestrictTo.Scope.LIBRARY)
    public static class Binder extends android.os.Binder {
        private final WeakReference<JobService> serviceRef;
        private final SparseArray<Callback> callbacks;

        Binder(JobService service) {
            super();
            this.serviceRef = new WeakReference<>(service);
            this.callbacks = new SparseArray<>(1);
        }

        public boolean startJob(JobParameters params, Callback callback) {
            JobService service = serviceRef.get();
            if (service != null) {
                callbacks.put(params.getJobId(), callback);
                return service.onStartJob(params);
            } else {
                return false;
            }
        }

        public boolean stopJob(JobParameters params) {
            JobService service = serviceRef.get();
            if (service != null) {
                callbacks.remove(params.getJobId());
                return service.onStopJob(params);
            } else {
                return false;
            }
        }

        void notifyJobFinished(JobParameters params, boolean needsReschedule) {
            Callback callback = callbacks.get(params.getJobId());
            if (callback != null) {
                callbacks.remove(params.getJobId());
                callback.jobFinished(params, needsReschedule);
            }
        }

        public interface Callback {
            void jobFinished(JobParameters params, boolean needsReschedule);
        }
    }
}

@@ -1489,16 +1489,221 @@
 er.%0A
+     *%0A     * Methods are synchronized to prevent race conditions when updating %7B@link #callbacks%7D.%0A     * There are no guarantees on which thread calls %7B@link #notifyJobFinished(JobParameters, boolean)%7D.%0A
      */%0A
     
@@ -1694,24 +1694,24 @@
 )%7D.%0A     */%0A
-
     @Restric
@@ -2102,32 +2102,45 @@
 %0A        public 
+synchronized 
 boolean startJob
@@ -2349,41 +2349,227 @@
     
-return service.onStartJob(params)
+boolean willContinueRunning = service.onStartJob(params);%0A                if (!willContinueRunning) %7B%0A                    callbacks.remove(params.getJobId());%0A                %7D%0A                return willContinueRunning
 ;%0A  
@@ -2657,16 +2657,29 @@
  public 
+synchronized 
 boolean 
@@ -2975,24 +2975,24 @@
      %7D%0A%0A
-
         
 void not
@@ -2979,24 +2979,37 @@
  %7D%0A%0A        
+synchronized 
 void notifyJ

Fix race condition when proxying in JobService