diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 497e51d47..edbb12da4 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -86,6 +86,9 @@ + diff --git a/app/src/main/java/com/termux/api/JobSchedulerAPI.java b/app/src/main/java/com/termux/api/JobSchedulerAPI.java new file mode 100644 index 000000000..fe92f705b --- /dev/null +++ b/app/src/main/java/com/termux/api/JobSchedulerAPI.java @@ -0,0 +1,137 @@ +package com.termux.api; + +import android.app.job.JobInfo; +import android.app.job.JobScheduler; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.media.AudioManager; +import android.os.PersistableBundle; +import android.os.Vibrator; +import android.util.Log; + +import com.termux.api.util.ResultReturner; + +import java.io.File; +import java.io.PrintWriter; + +public class JobSchedulerAPI { + + private static final String LOG_TAG = "JobSchedulerAPI"; + + + static void onReceive(TermuxApiReceiver apiReceiver, Context context, Intent intent) { + + final String scriptPath = intent.getStringExtra("script"); + + final int periodicMillis = intent.getIntExtra("period_ms", 0); + final int jobId = intent.getIntExtra("job_id", 0); + final String networkType = intent.getStringExtra("network"); + final boolean batteryNotLow = intent.getBooleanExtra("battery_not_low", true); + final boolean charging = intent.getBooleanExtra("charging", false); + final boolean idle = intent.getBooleanExtra("idle", false); + final boolean storageNotLow = intent.getBooleanExtra("storage_not_low", false); + + int networkTypeCode = JobInfo.NETWORK_TYPE_NONE; + if (networkType != null) { + switch (networkType) { + case "any": + networkTypeCode = JobInfo.NETWORK_TYPE_ANY; + break; + case "unmetered": + networkTypeCode = JobInfo.NETWORK_TYPE_UNMETERED; + break; + case "cellular": + networkTypeCode = JobInfo.NETWORK_TYPE_CELLULAR; + break; + case "not_roaming": + networkTypeCode = JobInfo.NETWORK_TYPE_NOT_ROAMING; + break; + default: + case "none": + networkTypeCode = JobInfo.NETWORK_TYPE_NONE; + break; + } + } + if (scriptPath == null) { + ResultReturner.returnData(apiReceiver, intent, new ResultReturner.ResultWriter() { + @Override + public void writeResult(PrintWriter out) { + out.println("No script path given"); + } + }); + return; + } + final File file = new File(scriptPath); + final String fileCheckMsg; + if (!file.isFile()) { + fileCheckMsg = "No such file: %s"; + } else if (!file.canRead()) { + fileCheckMsg = "Cannot read file: %s"; + } else if (!file.canExecute()) { + fileCheckMsg = "Cannot execute file: %s"; + } else { + fileCheckMsg = ""; + } + + if (!fileCheckMsg.isEmpty()) { + ResultReturner.returnData(apiReceiver, intent, new ResultReturner.ResultWriter() { + @Override + public void writeResult(PrintWriter out) { + out.println(String.format(fileCheckMsg, scriptPath)); + } + }); + return; + } + + PersistableBundle extras = new PersistableBundle(); + extras.putString(SchedulerJobService.SCRIPT_FILE_PATH, file.getAbsolutePath()); + + + JobScheduler jobScheduler = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE); + // Display pending jobs + for (JobInfo job : jobScheduler.getAllPendingJobs()) { + final JobInfo j = job; + ResultReturner.returnData(apiReceiver, intent, new ResultReturner.ResultWriter() { + @Override + public void writeResult(PrintWriter out) { + out.println(String.format("Pending job %d %s", j.getId(), j.toString())); + } + }); + } + + ComponentName serviceComponent = new ComponentName(context, SchedulerJobService.class); + JobInfo.Builder builder = null; + ; + builder = new JobInfo.Builder(jobId, serviceComponent) + .setExtras(extras) + .setRequiredNetworkType(networkTypeCode) + .setRequiresCharging(charging) + .setRequiresDeviceIdle(idle); + + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) { + builder = builder.setRequiresBatteryNotLow(batteryNotLow); + builder = builder.setRequiresStorageNotLow(storageNotLow); + } + + if (periodicMillis > 0) { + builder = builder.setPeriodic(periodicMillis); + } + + JobInfo job = builder.build(); + + final int scheduleResponse = jobScheduler.schedule(job); + + Log.i(LOG_TAG, String.format("Scheduled job %d to call %s every %d ms - response %d", + jobId, scriptPath, periodicMillis, scheduleResponse)); + ResultReturner.returnData(apiReceiver, intent, new ResultReturner.ResultWriter() { + @Override + public void writeResult(PrintWriter out) { + out.println(String.format("Scheduled job %d to call %s every %d ms - response %d", + jobId, scriptPath, periodicMillis, scheduleResponse)); + } + }); + + } + +} diff --git a/app/src/main/java/com/termux/api/SchedulerJobService.java b/app/src/main/java/com/termux/api/SchedulerJobService.java new file mode 100644 index 000000000..65594a7c6 --- /dev/null +++ b/app/src/main/java/com/termux/api/SchedulerJobService.java @@ -0,0 +1,50 @@ +package com.termux.api; +import android.app.job.JobParameters; +import android.app.job.JobService; +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.os.Build; +import android.os.PersistableBundle; +import android.util.Log; + +public class SchedulerJobService extends JobService { + + private static final String LOG_TAG = "TermuxAPISchedulerJob"; + public static final String SCRIPT_FILE_PATH = "com.termux.api.jobscheduler_script_path"; + + // Constants from TermuxService. + private static final String TERMUX_SERVICE = "com.termux.app.TermuxService"; + private static final String ACTION_EXECUTE = "com.termux.service_execute"; + private static final String EXTRA_EXECUTE_IN_BACKGROUND = "com.termux.execute.background"; + + @Override + public boolean onStartJob(JobParameters params) { + + Log.i(LOG_TAG, "Starting job " + params.toString()); + PersistableBundle extras = params.getExtras(); + String filePath = extras.getString(SCRIPT_FILE_PATH); + + Uri scriptUri = new Uri.Builder().scheme("com.termux.file").path(filePath).build(); + Intent executeIntent = new Intent(ACTION_EXECUTE, scriptUri); + executeIntent.setClassName("com.termux", TERMUX_SERVICE); + executeIntent.putExtra(EXTRA_EXECUTE_IN_BACKGROUND, true); + + Context context = getApplicationContext(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + // https://developer.android.com/about/versions/oreo/background.html + context.startForegroundService(executeIntent); + } else { + context.startService(executeIntent); + } + + Log.i(LOG_TAG, "Started job " + params.toString()); + return false; + } + + @Override + public boolean onStopJob(JobParameters params) { + Log.i(LOG_TAG, "Stopped job " + params.toString()); + return false; + } +} diff --git a/app/src/main/java/com/termux/api/TermuxApiReceiver.java b/app/src/main/java/com/termux/api/TermuxApiReceiver.java index 379b4fd2a..8f0a3cabf 100644 --- a/app/src/main/java/com/termux/api/TermuxApiReceiver.java +++ b/app/src/main/java/com/termux/api/TermuxApiReceiver.java @@ -83,6 +83,9 @@ public void onReceive(Context context, Intent intent) { InfraredAPI.onReceiveTransmit(this, context, intent); } break; + case "JobScheduler": + JobSchedulerAPI.onReceive(this, context, intent); + break; case "Keystore": KeystoreAPI.onReceive(this, intent); break;