Allows you to write and execute arbitrary Java code. The code is interpreted by the BeanShell interpreter, which uses a syntax similar to older versions of Java (pre-Java 5).
Click the magnifying glass icon next to the code field to get help writing your code. You can use the built-in AI to generate the code for you directly in the editor, or you can copy the system instructions to use with your preferred external AI (like ChatGPT, Gemini, etc.).
Two special variables are always available for you to use in your code: context and tasker.
The context variable is an Android Context object from a Service. Because it is not from an Activity, you must add the flag FLAG_ACTIVITY_NEW_TASK when starting new activities to avoid errors.
The tasker variable is a helper object that provides methods to interact with Tasker and the system. The available methods are:
String getVariable(String name)
null if the variable is not set.
String data = tasker.getVariable("http_data");
void setVariable(String name, Object value)
tasker.setVariable("LastLocation", "Home");
void setJavaVariable(String name, Object value)
tasker.setJavaVariable("myList", new ArrayList());
Object getJavaVariable(String name)
In BeanShell, a variable that has not been set (e.g., an injected Java variable from a previous action that didn't run) is not `null`. It is a special value: `void`. If you only check `myVar == null`, your script will crash with an 'Undefined variable' error if `myVar` was never set. To safely check for the existence and non-null value of a variable, you MUST check for both conditions.
/* Correct and Safe Example: */
if (myInjectedVar == null || myInjectedVar == void) {
/* The variable is either not set or has been explicitly set to null. */
tasker.log("Variable is not available.");
}
/* Incorrect and Unsafe Example: */
if (myInjectedVar == null) {
/* This line will CRASH if the variable was never set. */
}
java.util.Map<java.lang.String, java.lang.Object> getJavaVariables()
java.util.Map<java.lang.String, java.lang.Object> getLocalJavaVariables()
java.util.Map<java.lang.String, java.lang.Object> getGlobalJavaVariables()
void clearGlobalJavaVariables()
tasker.clearGlobalJavaVariables();
void log(String message, ...)
System.out.println().
log(String message)log(String message, String filePath)log(String message, String filePath, String level)log(String message, String filePath, String level, String tag)tasker.log("Processing item number " + i);
Disposable logAndToast(CharSequence message, ...)
log() function followed by showToast() with the provided text. Useful for debugging when you need immediate on-screen feedback.
logAndToast(CharSequence message)logAndToast(CharSequence message, String filePath)tasker.logAndToast("Operation Complete");
Disposable showToast(CharSequence text, ... )
showToast(CharSequence text): Standard toast.showToast(CharSequence text, CharSequence title): Toast with a title.showToast(CharSequence text, CharSequence title, Drawable icon): Toast with a title and an icon.tasker.showToast("Hello World");
IBinder getShizukuService(String name)
IBinder cs = tasker.getShizukuService("clipboard");
NotificationListenerService getNotificationListener()
null if the service is not available, so your code must handle this.
if (tasker.getNotificationListener() != null) { ... }
Observable<NotificationUpdate> getNotificationUpdates()
com.joaomgcd.taskerm.helper.NotificationUpdate events. You can subscribe to this stream to react to notification changes in real-time.
NotificationUpdate object has exactly two methods:
boolean getCreated(): Returns true if the notification was posted (created), and false if the notification was removed.android.service.notification.StatusBarNotification getStatusBarNotification(): Returns the android.service.notification.StatusBarNotification object that was either posted or removed.boolean callTask(String taskName, HashMap params)
This method asynchronously starts the specified task. It returns true if the task was successfully queued to run, and false otherwise. It does not wait for the task to complete and does not provide a result from the task.
taskName: The exact name of the Tasker task to run.params: A HashMap where keys are local variable names (without the '%' prefix) and values are the string values to pass to the task. Can be null if no parameters are needed.
import java.util.HashMap;
/* Create a map to hold the parameters. */
params = new HashMap();
params.put("device_name", "Living Room TV");
params.put("command", "volume_up");
/* Call the 'Control Device' task with the specified parameters. */
boolean started = tasker.callTask("Control Device", params);
if (started) {
tasker.log("Successfully started the 'Control Device' task.");
} else {
tasker.log("Failed to start the 'Control Device' task.");
}
String sendCommand(String command)
tasker.sendCommand("my_custom_command=:=value");
AccessibilityService getAccessibilityService()
null otherwise.
List<AccessibilityNodeInfo> getChildrenRecursive(AccessibilityNodeInfo node) to easily retrieve all child elements. You can use it with accessibilityService.getRootInActiveWindow() to get all elements on the screen.
if (tasker.getAccessibilityService() != null) { ... }
Observable<AccessibilityEvent> getAccessibilityEvents()
Example: Creating a background text monitor
This example creates a persistent monitor that toasts any text typed into an EditText field in any app. It uses a global Java variable to handle cleanup so you can stop it later.
import android.view.accessibility.AccessibilityEvent;
import io.reactivex.functions.Consumer;
import io.reactivex.functions.Predicate;
import io.reactivex.functions.Action;
import io.reactivex.subjects.CompletableSubject;
import java.util.List;
import com.joaomgcd.taskerm.action.java.JavaCodeException;
/*
* Clean up any previous instance of this script by triggering the old signal.
* We use getJavaVariable safely to check if the global variable exists.
*/
oldSignal = tasker.getJavaVariable("accKillSignal");
if (oldSignal != null && oldSignal != void) oldSignal.onComplete();
/* Create a new kill signal subject. */
killSignal = CompletableSubject.create();
/*
* Store it as a Global Java Variable.
* Another task can retrieve this using 'accKillSignal' and call .onComplete() to stop this monitor.
*/
tasker.setJavaVariable("accKillSignal", killSignal);
/* Get the event stream. */
events = tasker.getAccessibilityEvents();
if (events == null) throw new JavaCodeException("Accessibility Service not available.");
/*
* Subscribe using takeUntil to handle the stop signal.
* Use doFinally to clean up the global variable when the subscription ends.
*/
disposable = events.takeUntil(killSignal.toObservable())
.doFinally(new Action() {
run() {
/* Clear the global variables so they don't linger in memory. */
tasker.setJavaVariable("accKillSignal", null);
tasker.setJavaVariable("accDisposable", null);
}
})
.filter(new Predicate() {
boolean test(Object obj) {
AccessibilityEvent event = (AccessibilityEvent) obj;
/* Filter for text changes. */
if (event.getEventType() != AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED) return false;
/* Filter for EditText. */
if (event.getClassName() == null) return false;
if (!"android.widget.EditText".contentEquals(event.getClassName())) return false;
return true;
}
}).subscribe(new Consumer() {
accept(Object obj) {
AccessibilityEvent event = (AccessibilityEvent) obj;
List textList = event.getText();
/* Check for valid text. */
if (textList == null) return;
if (textList.isEmpty()) return;
tasker.showToast("Input: " + textList.get(0));
}
});
/* CRITICAL: Store the subscription in a global variable so it's not garbage collected */
tasker.setJavaVariable("accDisposable", disposable);
Example: Stopping the monitor
Run this code in a separate action (or task) to stop the monitor created above.
import io.reactivex.subjects.CompletableSubject;
/* Retrieve the global signal variable. */
killSignal = tasker.getJavaVariable("accKillSignal");
/* Check if it exists and is valid. */
if (killSignal != null && killSignal != void) {
/* Trigger completion. This stops the subscription in the other task. */
killSignal.onComplete();
tasker.showToast("Monitor Stopped");
} else {
tasker.showToast("Monitor was not running");
}
Object implementClass(...)
Intent.class) and an implementation of the ClassImplementation interface. Your implementation's run method will be called for every method invoked on the created object.com.joaomgcd.taskerm.action.java.ClassImplementation. Remember to import this class at the top of your script.run method parameters:
Callable superCaller: This is the most important parameter. It is a handle to the original method that was called. To execute the original method, you must call superCaller.call(). The value returned by .call() is the original method's return value. This allows you to run code before or after the original logic.String methodName: The name of the method being called (e.g., "putExtra").Object[] args: An array of arguments passed to that method.run method signature without a return type or access modifier. The line must be written literally as run(Callable superCaller, String methodName, Object[] args){ ... }.1. Basic Implementation (No-Argument Constructor)
Object implementClass(java.lang.Class<?>, com.joaomgcd.taskerm.action.java.ClassImplementation)
2. Implementation with Specific Constructor
Object implementClass(java.lang.Class<?>, com.joaomgcd.taskerm.action.java.ClassImplementation, java.lang.Class<?>[], java.lang.Object[])
/* Import the necessary classes. */
import com.joaomgcd.taskerm.action.java.ClassImplementation;
import android.content.Intent;
import java.util.concurrent.Callable;
/* Create a proxied Intent object. */
intent = tasker.implementClass(Intent.class, new ClassImplementation(){
run(Callable superCaller, String methodName, Object[] args){
tasker.log("Method Logger: about to call " + methodName);
/* Execute the original method and capture its result. */
Object result = superCaller.call();
tasker.log("Method Logger: finished " + methodName +". Got result: " + result);
/* Return the original result to preserve functionality. */
return result;
}
});
/*
* Now, when you use this 'intent' object, every method call will be logged.
*/
intent.putExtra("aaa","coool");
intent.getStringExtra("aaa");
BroadcastReceiver
import com.joaomgcd.taskerm.action.java.ClassImplementation;
import android.content.BroadcastReceiver;
import android.content.IntentFilter;
import android.content.Intent;
import java.util.concurrent.Callable;
import io.reactivex.subjects.CompletableSubject;
screenOffSignal = CompletableSubject.create();
filter = new IntentFilter(Intent.ACTION_SCREEN_OFF);
screenOffReceiver = tasker.implementClass(BroadcastReceiver.class, new ClassImplementation(){
run(Callable superCaller, String methodName, Object[] args){
/*
* Check for the specific abstract method we want to implement.
* For any other method, we MUST call superCaller to preserve
* the original behavior (e.g., for toString(), equals(), etc.).
*/
if (!methodName.equals("onReceive")) {
return superCaller.call();
}
/* This is our custom implementation for the 'onReceive' method. */
tasker.log("Screen just went OFF!");
screenOffSignal.onComplete();
return null; /* onReceive is a void method. */
}
});
context.registerReceiver(screenOffReceiver, filter);
try {
screenOffSignal.blockingAwait();
tasker.log("...Signal received!");
} finally {
context.unregisterReceiver(screenOffReceiver);
}
import com.joaomgcd.taskerm.action.java.ClassImplementation;
import android.widget.ArrayAdapter;
import android.content.Context;
import java.util.concurrent.Callable;
// We want to call the ArrayAdapter(Context context, int resource, T[] objects) constructor.
constructorTypes = new Class[]{ Context.class, int.class, Object[].class };
String[] myItems = new String[]{"Item 1", "Item 2", "Item 3"};
constructorArgs = new Object[]{ context, android.R.layout.simple_list_item_1, myItems };
myAdapter = tasker.implementClass(
ArrayAdapter.class,
new ClassImplementation(){
run(Callable superCaller, String methodName, Object[] args){
if (methodName.equals("getCount")) {
tasker.log("ArrayAdapter.getCount() was called!");
}
return superCaller.call();
}
},
constructorTypes,
constructorArgs
);
tasker.log("Adapter count is: " + myAdapter.getCount());
TaskForHelper getTask()
TaskForHelper object has the following methods:
String getName(): Gets the name of the current task.int getTaskId(): Gets the ID of the running task.int getProjectId(): Gets the project ID that contains the task.java.lang.Integer getProfileId(): Gets the eventual profile id of the profile that launched the running task. May be null.List<ActionForHelper> getActions(): Gets a list of all actions in the task.int getActionCount(): Gets the number of actions in this task.int getCurrentActionIndex(): Gets the 0-based index of this Java Code action.ActionForHelper getNextAction(): Gets the next action in the task.ActionForHelper getPreviousAction(): Gets the previous action in the task.ActionForHelper object has the following methods:
int getCode(): Gets the action's internal code.String getName(): Gets the action's display name.Bundle getTaskVariables()
Bundle vars = tasker.getTaskVariables();
String toJson(Object object, ...)
toJson(Object object): Converts to a compact, non-pretty JSON string.toJson(Object object, boolean pretty): If pretty is true, the JSON will be formatted for readability.String prettyJson = tasker.toJson(myObject, true);
void doWithActivity(Consumer consumer)
This is the solution for running APIs that require an `Activity` and will not work with the default `Service` context. A prime example is showing an `AlertDialog` or any other UI element, which will crash if attempted from a service.
How it works:activity.finish() inside your code when you are done. If you don't, the invisible activity will linger in the background. For asynchronous UI like a Dialog, this means calling finish() inside the dialog's button listeners.This example correctly demonstrates all the rules: it shows a dialog, pauses the script until the user clicks a button, and safely closes the temporary activity.
import java.util.function.Consumer;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import io.reactivex.subjects.SingleSubject;
/* Use a SingleSubject to wait for the dialog's result. */
resultSignal = SingleSubject.create();
/* Create a Consumer to build and show the dialog. */
myActivityConsumer = new Consumer() {
accept(Object activity) {
final Activity currentActivity = (Activity) activity;
onClickListener = new DialogInterface.OnClickListener() {
onClick(DialogInterface dialog, int which) {
String result = "cancel";
if (which == DialogInterface.BUTTON_POSITIVE) {
result = "ok";
}
resultSignal.onSuccess(result);
currentActivity.finish(); /* CRITICAL: Finish activity here! */
}
};
AlertDialog.Builder builder = new AlertDialog.Builder(currentActivity);
builder.setTitle("Confirmation");
builder.setMessage("Do you want to proceed?");
builder.setPositiveButton("OK", onClickListener);
builder.setNegativeButton("Cancel", onClickListener);
builder.setCancelable(false);
builder.create().show();
}
};
/* Execute the consumer to show the dialog. */
tasker.doWithActivity(myActivityConsumer);
/* Block the script and wait for the signal from the button listener. */
userChoice = resultSignal.blockingGet();
return userChoice;
Single<Intent> getWithActivityForResult(Intent intent)
This is the standard way to get data from other apps that require user interaction, like picking a file, selecting a contact, or choosing an account.
How it works:You must subscribe to the `Single` to get the result. The most common way to do this is by using .blockingGet(), which pauses the script until the result is available.
import android.content.Intent;
/* Create an intent to pick any file. */
intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.setType("*/*");
/* Start the activity and get the Single placeholder. */
resultSingle = tasker.getWithActivityForResult(intent);
/* Block the script and wait for the user to pick a file. */
resultIntent = resultSingle.blockingGet();
/* Extract the data (e.g., the file's URI) from the result. */
fileUri = resultIntent.getData();
return fileUri.toString();
Example: Advanced Usage (Adding a Timeout)
The flexibility of returning a `Single` allows you to add operators like `timeout`.
import android.content.Intent;
import java.util.concurrent.TimeUnit;
intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.setType("*/*");
resultSingle = tasker.getWithActivityForResult(intent);
try {
/* Wait for the result, but with a 30-second timeout. */
resultIntent = resultSingle.timeout(30, TimeUnit.SECONDS).blockingGet();
return resultIntent.getData().toString();
} catch (Exception e) {
tasker.log("User did not pick a file within 30 seconds.");
return "timeout";
}
String convertToRealFilePath(Uri uri)
Use Case: Android's modern security model often provides `content://` URIs instead of direct file paths (e.g., when you pick a file). This function acts as a bridge for tools or code that require a traditional, absolute path like `/storage/emulated/0/Download/MyFile.pdf`.
IMPORTANT: This conversion is not guaranteed to succeed and may return `null`, especially if the URI points to a cloud file (like from Google Drive) or other virtual content. You MUST always check if the result is `null`.
Example: Picking a File and Getting Its Real Path
import android.content.Intent;
import android.net.Uri;
/* 1. Get the result Intent from the file picker. */
intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.setType("*/*");
resultIntent = tasker.getWithActivityForResult(intent).blockingGet();
if (resultIntent == null) return "User cancelled.";
/* 2. Get the content URI from the result. */
contentUri = resultIntent.getData();
if (contentUri == null) return "Could not get file URI.";
tasker.log("Got content URI: " + contentUri);
/* 3. Attempt to convert the content URI to a real file path. */
realPath = tasker.convertToRealFilePath(contentUri);
/* 4. CRITICAL: Check if the conversion succeeded. */
if (realPath != null) {
tasker.log("Successfully converted to real path: " + realPath);
return realPath;
} else {
tasker.log("Failed to convert URI to a real path.");
return "Could not get real file path.";
}
You can also access Java objects that were created and returned by other Tasker Java actions (like Java Function) within your code.
The RxJava2 library is available for handling asynchronous operations like waiting for events, performing background work, or running code after a delay. The action will automatically wait for your reactive stream to complete before proceeding.
You must end your RxJava chain with a blocking operator (like blockingAwait(), blockingGet(), or blockingFirst()). This is what tells the action to wait for your asynchronous operation to finish.
Waiting for Events with Subjects (Recommended)
To wait for an event from a callback (like in a `BroadcastReceiver` or another listener), the cleanest approach is to use a Subject. You create a subject, use it within your callback implementation to signal that an event has occurred, and then block the script until that signal is received. This is much simpler than using complex operators like Completable.create().
Example: Using a Subject to wait for a signal
import io.reactivex.subjects.CompletableSubject;
/* Create a subject that will act as our signal. */
mySignal = CompletableSubject.create();
/*
* In another part of your code (e.g., inside the 'run' method of an
* implemented BroadcastReceiver), you would signal completion like this:
*
* mySignal.onComplete();
*
* See the `implementClass` BroadcastReceiver example for a practical use case.
*/
/* This line will block and wait until onComplete() is called. */
mySignal.blockingAwait();
tasker.log("Signal received, script can now continue.");
Simple Delays and Timers
For basic delays, using a simple timer is still appropriate.
Example: Wait for 3 seconds
import java.util.concurrent.TimeUnit;
import io.reactivex.Completable;
/* Wait for 3 seconds on a background thread. */
Completable.timer(3, TimeUnit.SECONDS).blockingAwait();
/* This code will run after the 3-second delay. */
tasker.log("3 seconds have passed.");
Example: Get a value after a delay
import java.util.concurrent.TimeUnit;
import io.reactivex.Single;
import io.reactivex.functions.Function;
/* Get the string "Hello" after a 1 second delay. */
result = Single.timer(1, TimeUnit.SECONDS)
.map(new Function() {
apply(Object aLong) {
return "Hello";
}
})
.blockingGet();
return result; /* Returns "Hello" */
The value from your code's return statement will be stored in the output variable specified in the "Return" field. Any variables you declare inside your code are temporary and will be discarded when the action finishes.
The scope (local or global) of the returned value is determined by the capitalization of the variable name you choose:
This is ideal for passing complex data, like a List or custom object, to another Java-based action. The actual Java object is stored in memory.
my_object, resultslist): The object is stored locally and is only available within the current task.myGlobalObject, persistentList): The object is stored globally and persists across different tasks.
myGlobalObject is valid, but MyGlobalObject is not).Important: Global Java objects persist in memory. If you no longer need one, you should manually clear it using the tasker.clearGlobalJavaVariables() method or the Java Function action to prevent memory leaks.
The object's .toString() method is called, and the resulting text is stored in a Tasker variable.
%result, %http_data): The variable is local to the current task.%WIFI). These are typically reserved for Tasker's built-in read-only variables.You can use the following code to set the %wifi variable to the name of currently connected Wifi Network.
import android.content.Context;
import android.net.wifi.WifiManager;
import android.net.wifi.WifiInfo;
/* Get the WifiManager service. */
WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
/* If WiFi manager is unavailable or WiFi is disabled, exit. */
if (wifiManager == null || !wifiManager.isWifiEnabled()) return null;
/* Get information about the currently connected WiFi network. */
WifiInfo wifiInfo = wifiManager.getConnectionInfo();
if (wifiInfo == null) return null;
/* Get the raw SSID string. */
String ssid = wifiInfo.getSSID();
if (ssid == null) return null;
/* Remove surrounding quotes from the SSID if they exist. */
if (ssid.startsWith("\"") && ssid.endsWith("\"")) ssid = ssid.substring(1, ssid.length() - 1);
/* Return the cleaned SSID. */
return ssid;
%wifi