Search code examples
androidandroid-jobscheduler

Oreo JobScheduler not working when new picture is taken by the camera


In android Oreo Broadcast Receiver is not working for "android.hardware.action.NEW_PICTURE" so i have to replace it with JobScheduler. Now the problem is PhotoContentsJob is getting called and shows a toast of new Images taken when I restart the app But It doesn't work in the background as soon as new Photo is taken.Please help me how to achieve it. Code attached for reference.

In MainActivity onCreate Method

 if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){
        scheduleCameraJob();
    }

@RequiresApi(api = Build.VERSION_CODES.O)
private void scheduleCameraJob() {
     final Uri MEDIA_URI = Uri.parse("content://" + MediaStore.AUTHORITY + "/");

    JobInfo.Builder builder = new JobInfo.Builder(11,
            new ComponentName(this, PhotosContentJob.class.getName()));
    // Look for specific changes to images in the provider.
    builder.addTriggerContentUri(new JobInfo.TriggerContentUri(
            MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
            JobInfo.TriggerContentUri.FLAG_NOTIFY_FOR_DESCENDANTS));
    // Also look for general reports of changes in the overall provider.
    builder.addTriggerContentUri(new JobInfo.TriggerContentUri(MEDIA_URI, 0));
    builder.setTriggerContentUpdateDelay(1);
    builder.setTriggerContentMaxDelay(100);
    JobInfo myCameraJob  = builder.build();
    JobScheduler jobScheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);
    jobScheduler.schedule(myCameraJob);
}

PhotosContentJob Service --Working Code

@RequiresApi(api = Build.VERSION_CODES.O)
public class PhotosContentJob extends JobService {

SpreadsheetImage spreadsheetImage;
DatabaseHandler databaseHandler;

// Path segments for image-specific URIs in the provider.
static final List<String> EXTERNAL_PATH_SEGMENTS
        = MediaStore.Images.Media.EXTERNAL_CONTENT_URI.getPathSegments();
// The columns we want to retrieve about a particular image.
static final String[] PROJECTION = new String[] {
        MediaStore.Images.ImageColumns._ID, MediaStore.Images.ImageColumns.DATA
};
static final int PROJECTION_ID = 0;
static final int PROJECTION_DATA = 1;
// This is the external storage directory where cameras place pictures.
static final String DCIM_DIR = Environment.getExternalStoragePublicDirectory(
        Environment.DIRECTORY_DCIM).getPath();


JobParameters mRunningParams;

// Check whether this job is currently scheduled.
public static boolean isScheduled(Context context) {
    JobScheduler js = context.getSystemService(JobScheduler.class);
    List<JobInfo> jobs = js.getAllPendingJobs();
    if (jobs == null) {
        return false;
    }
    for (int i=0; i<jobs.size(); i++) {
        if (jobs.get(i).getId() == 11) {
            return true;
        }
    }
    return false;
}

// Cancel this job, if currently scheduled.
public static void cancelJob(Context context) {
    JobScheduler js = context.getSystemService(JobScheduler.class);
    js.cancel(11);
}

@Override
public boolean onStartJob(JobParameters params) {
    Log.e("PhotosContentJob", "JOB STARTED!");
    mRunningParams = params;
    // Instead of real work, we are going to build a string to show to the user.
    Date addedDate=new Date();
    databaseHandler=new DatabaseHandler(getApplicationContext());
    spreadsheetImage=new SpreadsheetImage();

    StringBuilder sb = new StringBuilder();
    // Did we trigger due to a content change?
    if (params.getTriggeredContentAuthorities() != null) {
        boolean rescanNeeded = false;
        if (params.getTriggeredContentUris() != null) {
            // If we have details about which URIs changed, then iterate through them
            // and collect either the ids that were impacted or note that a generic
            // change has happened.
            ArrayList<String> ids = new ArrayList<>();
            for (Uri uri : params.getTriggeredContentUris()) {
                List<String> path = uri.getPathSegments();
                if (path != null && path.size() == EXTERNAL_PATH_SEGMENTS.size()+1) {
                    // This is a specific file.
                    ids.add(path.get(path.size()-1));
                } else {
                    // Oops, there is some general change!
                    rescanNeeded = true;
                }
            }
            if (ids.size() > 0) {
                // If we found some ids that changed, we want to determine what they are.
                // First, we do a query with content provider to ask about all of them.
                StringBuilder selection = new StringBuilder();
                for (int i=0; i<ids.size(); i++) {
                    if (selection.length() > 0) {
                        selection.append(" OR ");
                    }
                    selection.append(MediaStore.Images.ImageColumns._ID);
                    selection.append("='");
                    selection.append(ids.get(i));
                    selection.append("'");
                }
                // Now we iterate through the query, looking at the filenames of
                // the items to determine if they are ones we are interested in.
                Cursor cursor = null;
                boolean haveFiles = false;
                try {
                    cursor = getContentResolver().query(
                            MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
                            PROJECTION, selection.toString(), null, null);
                    while (cursor.moveToNext()) {
                        // We only care about files in the DCIM directory.
                        String dir = cursor.getString(PROJECTION_DATA);
                        if (dir.startsWith(DCIM_DIR)) {
                            if (!haveFiles) {
                                haveFiles = true;
                                sb.append("New photos:\n");
                            }
                            sb.append(cursor.getInt(PROJECTION_ID));
                            sb.append(": ");
                            sb.append(dir);
                            sb.append("\n");

                            spreadsheetImage.ImagePath=dir;
                            spreadsheetImage.AddedOn=addedDate;
                            databaseHandler.AddSpreadSheetImage(spreadsheetImage);
                        }
                    }
                } catch (SecurityException e) {
                    sb.append("Error: no access to media!");
                } finally {
                    if (cursor != null) {
                        cursor.close();
                    }
                }
            }
        } else {
            // We don't have any details about URIs (because too many changed at once),
            // so just note that we need to do a full rescan.
            rescanNeeded = true;
        }
        if (rescanNeeded) {
            sb.append("Photos rescan needed!");
        }
    } else {
        sb.append("(No photos content)");
    }

    Toast.makeText(this, sb.toString(), Toast.LENGTH_LONG).show();
    jobFinished(params, /*reschedule*/false);
    scheduleCameraJob( /*immediate*/false);
    return true;
}
@Override
public boolean onStopJob(JobParameters params) {

    return false;
}

@RequiresApi(api = Build.VERSION_CODES.O)
private void scheduleCameraJob(Boolean Immediate) {
    final Uri MEDIA_URI = Uri.parse("content://" + MediaStore.AUTHORITY + "/");

    JobInfo.Builder builder = new JobInfo.Builder(11,
            new ComponentName(this, PhotosContentJob.class.getName()));
    // Look for specific changes to images in the provider.
    builder.addTriggerContentUri(new JobInfo.TriggerContentUri(
            MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
            JobInfo.TriggerContentUri.FLAG_NOTIFY_FOR_DESCENDANTS));
    // Also look for general reports of changes in the overall provider.
    builder.addTriggerContentUri(new JobInfo.TriggerContentUri(MEDIA_URI, 0));

    if (Immediate) {
        // Get all media changes within a tenth of a second.
        builder.setTriggerContentUpdateDelay(1);
        builder.setTriggerContentMaxDelay(100);
    } else {
        builder.setTriggerContentUpdateDelay(1);
        builder.setTriggerContentMaxDelay(100);
    }

    JobInfo myCameraJob  = builder.build();
    JobScheduler jobScheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);
    int result =jobScheduler.schedule(myCameraJob);
    if (result == JobScheduler.RESULT_SUCCESS) {
        Log.e("JobScheduler"," JobScheduler OK");
    } else {
        Log.e("JobScheduler"," JobScheduler fails");
    }
}
}

Solution

  • Scheduling a JobScheduler based on content observer URI is one time task only. When you receive the changes to your PhotosContentJob you need to reschedule the job again at end of onStartJob(JobParameters params) to continue receiving the updates.

    Based on the documentation:

    Note: TriggerContentUri() cannot be used in combination with setPeriodic() or setPersisted(). To continually monitor for content changes, schedule a new JobInfo before the app’s JobService finishes handling the most recent callback.

    Note:

    Remember to reschedule job on Device reboot too. Since the scheduled Job will not persist across reboot.