Skip to content

Commit

Permalink
Add CronAPI
Browse files Browse the repository at this point in the history
This implements a cron-like API which uses AlarmManager and WorkManager

WorkManager is used to keep phone awake during task execution, and to
be able to handle constraints - including stopping the task if
constraint is no longer met
  • Loading branch information
lvogt committed Feb 4, 2024
1 parent 4159c62 commit 017579f
Show file tree
Hide file tree
Showing 13 changed files with 972 additions and 5 deletions.
10 changes: 10 additions & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ android {
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8

coreLibraryDesugaringEnabled true
}

applicationVariants.all { variant ->
Expand All @@ -64,9 +66,17 @@ android {
}

dependencies {
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.2.3'

implementation 'com.google.android.material:material:1.4.0'
implementation 'androidx.biometric:biometric:1.2.0-alpha03'
implementation "androidx.work:work-runtime:2.6.0"
implementation "androidx.concurrent:concurrent-futures:1.1.0"
implementation 'androidx.media:media:1.4.3'
implementation 'com.cronutils:cron-utils:9.2.0'
implementation 'com.google.code.gson:gson:2.10.1'
implementation "com.google.guava:guava:24.1-android"
implementation 'com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava'

implementation 'com.termux.termux-app:termux-shared:8c1749ef96'
// Use if below libraries are published locally by termux-app with `./gradlew publishReleasePublicationToMavenLocal` and used with `mavenLocal()`.
Expand Down
24 changes: 23 additions & 1 deletion app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
<uses-permission android:name="android.permission.REQUEST_DELETE_PACKAGES" />
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
<uses-permission android:name="android.permission.SEND_SMS" />
<uses-permission android:name="android.permission.SET_WALLPAPER" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
Expand Down Expand Up @@ -141,7 +142,28 @@
android:grantUriPermissions="true"
android:exported="true" />


<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
android:exported="false"
tools:node="merge">
<meta-data
android:name="androidx.work.WorkManagerInitializer"
android:value="androidx.startup"
tools:node="remove" />
</provider>


<receiver android:name=".cron.CronReceiver"
android:exported="false">
</receiver>

<receiver android:name=".cron.CronBootReceiver"
android:exported="false">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>

<receiver android:name=".TermuxApiReceiver"
android:exported="false" />
Expand Down
17 changes: 14 additions & 3 deletions app/src/main/java/com/termux/api/TermuxAPIApplication.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,19 @@

import android.app.Application;
import android.content.Context;

import android.util.Log;
import androidx.annotation.NonNull;
import androidx.work.Configuration;
import com.termux.api.util.ResultReturner;
import com.termux.shared.logger.Logger;
import com.termux.shared.termux.TermuxConstants;
import com.termux.shared.termux.crash.TermuxCrashUtils;
import com.termux.shared.termux.settings.preferences.TermuxAPIAppSharedPreferences;


public class TermuxAPIApplication extends Application {
public class TermuxAPIApplication extends Application implements Configuration.Provider {

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

Expand All @@ -28,12 +31,20 @@ public void onCreate() {
}

public static void setLogConfig(Context context, boolean commitToFile) {
Logger.setDefaultLogTag(TermuxConstants.TERMUX_API_APP_NAME.replaceAll(":", ""));
Logger.setDefaultLogTag(TermuxConstants.TERMUX_API_APP_NAME.replace(":", ""));

// Load the log level from shared preferences and set it to the {@link Logger.CURRENT_LOG_LEVEL}
TermuxAPIAppSharedPreferences preferences = TermuxAPIAppSharedPreferences.build(context);
if (preferences == null) return;
preferences.setLogLevel(null, preferences.getLogLevel(true), commitToFile);
}

@NonNull
@Override
public Configuration getWorkManagerConfiguration() {
return new Configuration.Builder()
.setJobSchedulerJobIdRange(10_000, 11_000)
.setMinimumLoggingLevel(Log.INFO)
.build();
}
}
6 changes: 5 additions & 1 deletion app/src/main/java/com/termux/api/TermuxAPIConstants.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.termux.api;

import com.termux.shared.termux.TermuxConstants;
import static com.termux.shared.termux.TermuxConstants.TERMUX_API_PACKAGE_NAME;
import static com.termux.shared.termux.TermuxConstants.TERMUX_PACKAGE_NAME;

Expand All @@ -14,4 +13,9 @@ public class TermuxAPIConstants {
/** The Uri authority for Termux:API app file shares */
public static final String TERMUX_API_FILE_SHARE_URI_AUTHORITY = TERMUX_PACKAGE_NAME + ".sharedfiles"; // Default: "com.termux.sharedfiles"

public static final String TERMUX_API_CRON_ALARM_SCHEME = "com.termux.api.cron.alarm";

public static final String TERMUX_API_CRON_CONSTRAINT_SCHEME = "com.termux.api.cron.constraint";

public static final String TERMUX_API_CRON_EXECUTION_RESULT_SCHEME = "com.termux.api.cron.exec";
}
4 changes: 4 additions & 0 deletions app/src/main/java/com/termux/api/TermuxApiReceiver.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import android.provider.Settings;
import android.widget.Toast;

import com.termux.api.apis.CronAPI;
import com.termux.api.apis.AudioAPI;
import com.termux.api.apis.BatteryStatusAPI;
import com.termux.api.apis.BrightnessAPI;
Expand Down Expand Up @@ -84,6 +85,9 @@ private void doWork(Context context, Intent intent) {
}

switch (apiMethod) {
case "Cron":
CronAPI.onReceive(this, context, intent);
break;
case "AudioInfo":
AudioAPI.onReceive(this, context, intent);
break;
Expand Down
93 changes: 93 additions & 0 deletions app/src/main/java/com/termux/api/apis/CronAPI.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package com.termux.api.apis;

import android.content.Context;
import android.content.Intent;
import com.termux.api.TermuxApiReceiver;
import com.termux.api.cron.CronTab;
import com.termux.api.cron.CronEntry;
import com.termux.api.cron.CronScheduler;
import com.termux.api.util.ResultReturner;
import com.termux.shared.logger.Logger;

import java.util.List;
import java.util.Locale;

public class CronAPI {

public static final String LOG_TAG = "CronAPI";

private CronAPI() {
/* static class */
}

public static void onReceive(TermuxApiReceiver apiReceiver, Context context, Intent intent) {
Logger.logDebug(LOG_TAG, "onReceive");

if (intent.getBooleanExtra("list", false)) {
handleList(apiReceiver, intent);
} else if (intent.getIntExtra("info", -1) != -1) {
handleInfo(apiReceiver, intent, intent.getIntExtra("info", -1));
} else if (intent.getBooleanExtra("reschedule", false)) {
handleRescheduleAll(apiReceiver, context, intent);
} else if (intent.getBooleanExtra("delete_all", false)) {
handleDeleteAll(apiReceiver, context, intent);
} else if (intent.getIntExtra("delete", -1) != -1) {
handleDelete(apiReceiver, context, intent, intent.getIntExtra("delete", -1));
} else {
handleAddJob(apiReceiver, context, intent);
}
}

private static void handleAddJob(TermuxApiReceiver apiReceiver, Context context, Intent intent) {
try {
CronEntry entry = CronTab.add(intent);
CronScheduler.scheduleAlarmForJob(context, entry);
ResultReturner.returnData(apiReceiver, intent, out -> out.println(entry.describe()));
} catch (Exception e) {
Logger.logError(LOG_TAG, e.getMessage());
Logger.logStackTrace(LOG_TAG, e);
ResultReturner.returnData(apiReceiver, intent, out -> out.println(e.getMessage()));
}
}

private static void handleList(TermuxApiReceiver apiReceiver, Intent intent) {
ResultReturner.returnData(apiReceiver, intent, out -> out.println(CronTab.print()));
}

private static void handleInfo(TermuxApiReceiver apiReceiver, Intent intent, int id) {
CronEntry entry = CronTab.getById(id);
if (entry != null) {
ResultReturner.returnData(apiReceiver, intent, out -> out.println(entry.describe()));
} else {
ResultReturner.returnData(apiReceiver, intent, out ->
out.println(String.format(Locale.getDefault(), "Cron job with id %d not found", id)));
}
}

private static void handleRescheduleAll(TermuxApiReceiver apiReceiver, Context context, Intent intent) {
for (CronEntry entry : CronTab.getAll()) {
CronScheduler.scheduleAlarmForJob(context, entry);
}
ResultReturner.returnData(apiReceiver, intent, out -> out.println("All cron jobs have been rescheduled"));
}

private static void handleDeleteAll(TermuxApiReceiver apiReceiver, Context context, Intent intent) {
List<CronEntry> entries = CronTab.clear();
for (CronEntry entry : entries) {
CronScheduler.cancelAlarmForJob(context, entry);
}
ResultReturner.returnData(apiReceiver, intent, out -> out.println("All cron jobs deleted"));
}

private static void handleDelete(TermuxApiReceiver apiReceiver, Context context, Intent intent, int id) {
CronEntry entry = CronTab.delete(id);
if (entry != null) {
CronScheduler.cancelAlarmForJob(context, entry);
ResultReturner.returnData(apiReceiver, intent, out ->
out.println(String.format(Locale.getDefault(), "Deleted cron job with id %d", id)));
} else {
ResultReturner.returnData(apiReceiver, intent, out ->
out.println(String.format(Locale.getDefault(), "Cron job with id %d not found?", id)));
}
}
}
2 changes: 2 additions & 0 deletions app/src/main/java/com/termux/api/apis/JobSchedulerAPI.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.termux.api.apis;

import android.annotation.SuppressLint;
import android.app.job.JobInfo;
import android.app.job.JobParameters;
import android.app.job.JobScheduler;
Expand Down Expand Up @@ -208,6 +209,7 @@ private static void cancelJob(TermuxApiReceiver apiReceiver, Intent intent, JobS



@SuppressLint("SpecifyJobSchedulerIdRange")
public static class JobSchedulerService extends JobService {

public static final String SCRIPT_FILE_PATH = TermuxConstants.TERMUX_API_PACKAGE_NAME + ".jobscheduler_script_path";
Expand Down
24 changes: 24 additions & 0 deletions app/src/main/java/com/termux/api/cron/CronBootReceiver.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.termux.api.cron;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import com.termux.shared.data.IntentUtils;
import com.termux.shared.logger.Logger;

public class CronBootReceiver extends BroadcastReceiver {

private static final String LOG_TAG = "CronBootReceiver";

@Override
public void onReceive(Context context, Intent intent) {
if (!intent.getAction().equals(Intent.ACTION_BOOT_COMPLETED)) {
Logger.logDebug(LOG_TAG, "Unknown intent Received:\n" + IntentUtils.getIntentString(intent));
return;
}

for (CronEntry entry : CronTab.getAll()) {
CronScheduler.scheduleAlarmForJob(context, entry);
}
}
}
Loading

0 comments on commit 017579f

Please sign in to comment.