Challenge: Using JobService on Lolipop

Hello,
I have got a problem with setPeriodic() methods. This method says that

You have no control over when within this interval this job will be executed, only the guarantee that it will be executed at most once within this interval.

Then, The book says
setPeriodic(1000 * 60 * 15); → it is “long” value that 900.000. But my logcat shows me that

16:45:58.024 ----> Connection.FlickrFetchr: Received json: {“photos”:{photo":[{“id”:“id_number”
16:45:58.028 ---->PollService.PollService: resultId ----------------------->: id_number
16:45:58.028 ---->PollService.PollService: Got a old result id_number
16:46:28.049 ---->PollService.PollJobService: onStartJob: ------>
16:46:28.050---->PollService.PollService: lastResultId ----------------------->: id_number
16:46:29.123—>Connection.FlickrFetchr: Received json: {“photos”:{“photo”:[{“id”:“id_number”
16:46:29.134 ---->PollService.PollService: resultId ----------------------->: id_number
16:46:29.134 ---->PollService.PollService: Got a new result id_number
16:46:29.144 -------> onReceive Result : -1
16:46:29.144 -------> BroadcastReceivers.NotificationReceiver: Not : Notification(pri=0 contentView=null vibrate=null sound=content://settings/system/notification_sound defaults=0x0 flags=0x10 color=0x00000000 vis=PRIVATE)

16:46:29.152 ---->PollService.PollJobService: onStartJob: ------>
16:46:29.155---->PollService.PollService: lastResultId ----------------------->: id_number
16:46:29.927—>Connection.FlickrFetchr: Received json: {“photos”:{“photo”:[{“id”:“id_number”
16:46:29.930 ---->PollService.PollService: resultId ----------------------->: id_number
16:46:29.930 ---->PollService.PollService: Got a new result id_number
the list is so long…

and setPeriodic() gives the notification per a seconds. not 15 min. Why ?

My first class is PollServiceUtils

public class PollServiceUtils {
  

    private static final String TAG = PollService.class.getName();


    //Notification channelId
    private static final String CHANNEL_ID = "my_channel_01";

    
    public static final String ACTION_SHOW_NOTIFICATION = "com.example.lscodex.photogallery.SHOW_NOTIFICATION";

    
    public static final String PERM_PRIVATE = "com.example.lscodex.photogallery.PRIVATE";
    public static final String REQUEST_CODE = "REQUEST_CODE";
    public static final String NOTIFICATION = "NOTIFICATION";


    public static Intent newIntent(Context context) {
        return new Intent(context, PollService.class);
    }

    //------------------------------------------------------------------------------------------>>>>
    //Jobschedule calls that methods
    public static void pollFickr(Context context) {

        //checking for bacground network
        if (!isNetworkAvailableAndConnected(context)) {
            return;
        }

        String query = QueryPreferences.getStoredQuery(context);                 
        String lastResultId = QueryPreferences.getPrefLasResultId(context);      
        Log.d(TAG, "lastResultId \t----------------------->: " + lastResultId);
        List<GalleryItem> galleryItemList;     

        if (query == null) {  
            galleryItemList = new FlickrFetchr().fetchRecentPhotos();
        } else {                                                  
            galleryItemList = new FlickrFetchr().searchPhotos(query);
        }

        if (galleryItemList.size() == 0) {                             
            return;                                                   
        }


        Intent i = MainActivity.newIntent(context);
      
        PendingIntent pi = PendingIntent.getActivity(context, 0, i, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_ONE_SHOT);

        String resultId = galleryItemList.get(0).getmId();
        Log.d(TAG, "resultId \t----------------------->: " + resultId);

       
        if (resultId.equals(lastResultId)) {
            Log.d(TAG, "Got a old result \t" + resultId);
        } else {

            Log.d(TAG, "Got a new result \t" + resultId);
            //notification
           /* setNotification(pi, context);
           context.sendBroadcast(new Intent(ACTION_SHOW_NOTIFICATION),PERM_PRIVATE);*/

            Uri alarmSound = getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
            Notification mBuilder = new NotificationCompat.Builder(context, CHANNEL_ID)
                    .setSmallIcon(R.drawable.ic_manic_material_product_icon_192px)
                    .setContentTitle("Resimler Geldi..").setContentText("Bakmak ister misiniz?")
                    .setContentIntent(pi).setSound(alarmSound)
                    .setShowWhen(true).setAutoCancel(true).build();

        
            showBackgroundNotification(context, 0, mBuilder);

        }
        QueryPreferences.setPrefLasResultId(context, resultId);

    }

    private static void showBackgroundNotification(Context context, int requestCode, Notification notificationCompat) {
        Intent i = new Intent(ACTION_SHOW_NOTIFICATION);
        i.putExtra(REQUEST_CODE, requestCode);
        i.putExtra(NOTIFICATION, notificationCompat);
        context.sendOrderedBroadcast(i, PERM_PRIVATE, null, null, Activity.RESULT_OK, null, null);
    }

    private static boolean isNetworkAvailableAndConnected(Context context) {

        ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);

        boolean isNetworkAvailable = cm.getActiveNetworkInfo() != null;             //networkInfo representing current network connection.
        boolean isNetworkConnected = isNetworkAvailable && cm.getActiveNetworkInfo().isConnected();
      
        NetworkInfo activeNetworkInfo = cm.getActiveNetworkInfo();
        return activeNetworkInfo != null && activeNetworkInfo.isConnected();

    }


}

PollService class is that

public class PollService extends IntentService {
    private static final String TAG = PollService.class.getName();

         public static final long POLL_INTERVAL_MS = TimeUnit.MINUTES.toMillis(15);

    //---------------------------------------------->

    /**
     * Creates an IntentService.  Invoked by your subclass's constructor.
     *
     * @param name Used to name the worker thread, important only for debugging.
     */
    public PollService(String name) {
        super(TAG);
    }


    @RequiresApi(api = Build.VERSION_CODES.O)
    @Override
    protected void onHandleIntent(@Nullable Intent intent) {
        PollServiceUtils.pollFickr(this);

    }

    //todo - AlarmManager -2
    public static void setServiceAlarm(Context context, boolean isOn) {

        Intent i = PollServiceUtils.newIntent(context);                                          
        PendingIntent pi = PendingIntent.getService(context, 0, i, 0);         
        AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
        if (isOn) {              
            assert alarmManager != null;
            alarmManager.setRepeating(AlarmManager.ELAPSED_REALTIME, SystemClock.elapsedRealtime(), POLL_INTERVAL_MS, pi); 
          
        } else {                 
            assert alarmManager != null;
            alarmManager.cancel(pi);            
            pi.cancel();                      
        }

             QueryPreferences.setAlarmOn(context,isOn);


    }

    public static boolean isServiceAlarmOn(Context context) {
        Intent i = PollServiceUtils.newIntent(context);
        PendingIntent pi = PendingIntent.getService(context, 0, i, PendingIntent.FLAG_NO_CREATE);
        return pi != null;
    }

}

PollJobService class is

@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public class PollJobService extends JobService {
    private static final String TAG = PollJobService.class.getName();

    private static final int JOB_ID = 1;

    //------------------------------------------------------------------->>>>


    @Override
    public void onCreate() {
        super.onCreate();

    }

    @Override
    public void onDestroy() {
        super.onDestroy();

    }

    @Override
    public boolean onStartJob(JobParameters params) {
        new PollTask().execute(params);
        Log.d(TAG, "onStartJob: ------>");
        return true;
    }

    @Override
    public boolean onStopJob(JobParameters params) {
        
        Log.d(TAG, "onStopJob: ------>");
        return true;
          }

    //ASYNC-TASK
    private class PollTask extends AsyncTask<JobParameters, Void, JobParameters> {
        @Override
        protected JobParameters doInBackground(JobParameters... params) {
            JobParameters jobParams = params[0];
            // Poll flickr for ner images
            try {
                PollServiceUtils.pollFickr(PollJobService.this);
                jobFinished(jobParams, true);
            } catch (Exception ex) {
                jobFinished(jobParams, true); 
               
            }
            return params[0];
        }

    }


    @RequiresApi(api = Build.VERSION_CODES.N)
    public static void setServiceAlarm(Context context, boolean isOn) {
        JobScheduler scheduler = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
        Log.d(TAG, "HERE setServiceAlarm-1? -------------;>: ");
        if (isOn) {
            Log.d(TAG, "HERE setServiceAlarm-2? -------------;>: " + isOn);
            Log.d(TAG, "MILLI SECOND \t: " + PollService.POLL_INTERVAL_MS);


            JobInfo jobInfo = new JobInfo.Builder(JOB_ID, new ComponentName(context, PollJobService.class))
                    .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY).setPersisted(true)
                    .setPeriodic(PollService.POLL_INTERVAL_MS)
                    .build();                             
          
            scheduler.schedule(jobInfo);
        } else {
            scheduler.cancel(JOB_ID);
            Log.d(TAG, "HERE setServiceAlarm-2? -------------;>: " + isOn);
        }
        //shared preference set Alarm Also
        QueryPreferences.setAlarmOn(context,isOn);
    }

    public static boolean isServiceAlarm(Context context) {
        JobScheduler scheduler = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
        boolean hasbeenSchedule = false;
        assert scheduler != null;
        for (JobInfo jobInfo : scheduler.getAllPendingJobs()) {
            if (jobInfo.getId() == JOB_ID) {
                Log.d(TAG, "HERE isServiceAlarm? -------------;>: " + jobInfo.getId());
                hasbeenSchedule = true;
                break;
            }
        }
        return hasbeenSchedule;
    }

}

Here’s my Notification Receiver class

public class NotificationReceiver extends BroadcastReceiver {
    private static final String TAG = NotificationReceiver.class.getName();



    //--------------------------------------------------------------------------->>>>



    @Override
    public void onReceive(Context context, Intent intent) {

        Log.d(TAG, "onReceive Result : " + getResultCode());

        if(getResultCode() != Activity.RESULT_OK){
            return;           
        }

        int requestCode = intent.getIntExtra(PollServiceUtils.REQUEST_CODE,0);
        Notification not = intent.getParcelableExtra(PollServiceUtils.NOTIFICATION);;
        Log.d(TAG, "Not \t: " + not);
        NotificationManagerCompat notifiManagerCompat = NotificationManagerCompat.from(context);
        notifiManagerCompat.notify(requestCode,not);

    }
}

in PhotoGalleryFragment my two methods that called classes.

   //Pollservice and JobService setAlarm and isOnAlarm

private void setPollorJobScheduleServiceAlarm(boolean isOn) {

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
        Log.d(TAG, "SET POLLJOBSERVICE ? -------------;>: ");
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            PollJobService.setServiceAlarm(getActivity(), isOn);
        }
    } else {
        Log.d(TAG, "SET ALARM MANAGER ? -------------;>: ");
        PollService.setServiceAlarm(getActivity(), isOn);
    }
}

//PollService and JobService isAlarm "ON"
private boolean isPollServiceorJobsScheduleAlarmOn() {

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
        Log.d(TAG, "Get PollJOBSERVICE ? -------------;>: " + true);
        return PollJobService.isServiceAlarm(getActivity());
    } else {
        Log.d(TAG, "GET ALARM MANAGER ? -------------;>: ");
        return PollService.isServiceAlarmOn(getActivity());
    }

}

I found is that JobService is wrong.

   @Override
    public boolean onStopJob(JobParameters params) {
    
        Log.d(TAG, "onStopJob: ------>");
        return false;   // here, now is good
          }

and in AsyncTask

try {
                PollServiceUtils.pollFickr(PollJobService.this);
                jobFinished(jobParams, true);
            } catch (Exception ex) {
                jobFinished(jobParams, false);    // here, now is good

That’s all. cheers!

Be careful with setPeriodic(…). Since Build.VERSION.SDK_INT = N, minimum period is 15 minutes. However, there are ways to test the code with less period: stackoverflow

Finally, I got solution by this thread on Using JobService on Lolipop. Thanks guys.