What a plugin can do
Plugins run inside Dolphy and use the same design system and hardware access layer as the app.
Compose multi-screen Material 3 Expressive tools with state and callbacks.
Add overlays, FABs, replace screens and hook named actions.
Work with Bluetooth, Wi-Fi, NFC, USB, IR and Android services.
Call Java APIs and use Shell, Root or Shizuku when available.
Plugins are not sandboxed. Read the source and only install code you trust.
Plugin metadata
Put metadata at the beginning of the text file. Dolphy reads it before installation without executing Python.
__id__ = "my_wireless_lab"
__name__ = "My Wireless Lab"
__version__ = "1.0.0"
__author__ = "Developer"
__description__ = "Wireless diagnostics"
__icon__ = "rounded:radar"
__library__ = False
__design_library__ = False
__dependencies__ = ["shared_radio_core"]
__permissions__ = ["ble_spam", "download", "files"]__id__Stable lowercase identifier__name__Name shown in Dolphy__version__Plugin version__author__Developer name__description__Short description__icon__Material Icon: filled, outlined, rounded, sharp or twotone__library__Load before regular plugins as a shared library__design_library__Mark a shared design library__dependencies__Required plugin ids loaded before this plugin__permissions__Capabilities shown before installation; Dolphy also detects direct API usage in the sourceThe file is UTF-8 text and may be at most 48 MiB. String fields must be top-level single- or double-quoted assignments. id is lowercased, invalid characters become underscores and the result is limited to 64 characters. Missing fields fall back to the filename, version 1.0 and icon extension.
.dolphypluginapplication/x-dolphy-pluginUTF-848 MiB max[a-z0-9_-], 64 charsLibrary plugins start before consumers. __dependencies__ declares required plugin ids, determines startup order and prevents a consumer from starting while a dependency is missing or disabled. Java classes and objects may be exported by name and imported by dependent plugins.
A file opened or shared as .dolphyplugin is always executed by the embedded Python runtime. The repository still contains an internal JavaScript compatibility runtime for previously stored .js plugins, but it is not the documented public .dolphyplugin authoring format.
Minimal working plugin
Extend BasePlugin, register a module during load and return a UI tree from screen_main.
__id__ = "hello_dolphy"
__name__ = "Hello Dolphy"
__version__ = "1.0.0"
__icon__ = "rounded:waving_hand"
class HelloDolphy(BasePlugin):
def on_plugin_load(self):
self.add_module(
title="Hello Dolphy",
description="My first plugin",
icon="waving_hand",
screen="main",
section="PLUGINS"
)
def screen_main(self, ui, api, state):
return ui.scaffold(
title="Hello Dolphy",
content=ui.column([
ui.card([
ui.text("It works!", "headlineSmall"),
ui.button("Say hello", self.say_hello)
])
], padding=16, spacing=12)
)
def say_hello(self):
self.toast("Hello from Python")These helpers delegate to the native bridge. api and ui are also available directly on self.
add_module(title, description="", icon="extension", screen="main", section="PLUGINS", order=0)navigate(screen)navigate_app(route)refresh()get_setting(key, default=None)set_setting(key, value)toast(text)hook_screen(route, screen="main", mode="overlay", priority=0)hook_surface(surface, screen="main", mode="overlay", priority=0)provide_service(service, priority=0)hook_action(action, priority=0)load_dex_base64(module, encoded, sha256="")load_dex_file(module, path, sha256="")dex_class(module, class_name)new_dex_instance(module, class_name)export_dex_class(name, module, class_name)export_app_class(name, class_name)export_java_class(name, java_class)import_java_class(name)export_java_object(name, value)import_java_object(name)java_exports()root(command)shizuku(command)shell(command)Built-in sections are INFRARED, BLUETOOTH, OTHER and PLUGINS. Aliases such as ir, bt, ble, misc and localized names are normalized; any other text creates a custom section. order sorts cards, screen chooses the plugin screen, and icon accepts Material icon syntax.
Material 3 Expressive UI
Python describes a UI tree. Dolphy renders native Jetpack Compose components with the current theme and accent color.
ui.scaffoldui.columnui.lazy_columnui.cardui.segmented_listui.function_rowui.buttonui.split_buttonui.icon_buttonui.text_fieldui.switchui.sliderui.tabsui.dropdownui.floating_toolbarui.wavy_progresstext.styleheadlineLarge/Medium/Small, titleLarge/Medium/Small, bodyLarge/Medium/Small and labelLarge/Medium/Small; camelCase and snake_case are accepted.color / tintprimary/accent, muted/secondary, error/red, success/green, warning/orange, or #RGB/#ARGB/#RRGGBB/#AARRGGBB.iconA built-in alias or any bundled Material icon as style:name: filled, outlined, rounded, sharp, twotone or automirrored.image.sourceasset path, asset:// URL, data:image/...;base64 URI or raw Base64. Scale: fit, crop, fill, fillWidth, fillHeight, inside or none.callbacksClick receives no value; text fields receive str, switches and checkboxes bool, sliders float, tabs int, and option controls the selected value.WebViewLoads either url or html, with JavaScript and DOM storage enabled. Treat remote and embedded content as trusted executable UI.Every argument below is supported by the Python runtime. Dimensions and spacing use dp.
scaffold(content, title=None, show_back=True, fab=None, actions=None)top_action(icon, on_click=None)column(children=None, padding=0, spacing=8, fill_max_size=False)row(children=None, padding=0, spacing=8, fill_max_width=True)box(children=None, padding=0, fill_max_size=False)lazy_column(children=None, padding=0, spacing=4, fill_max_size=True)lazy_row(children=None, padding=0, spacing=8, fill_max_width=True)text(text, style="bodyMedium", color=None, max_lines=2147483647)button(text, on_click=None, style="filled", enabled=True, fill_max_width=False)bounce_button(text, on_click=None, enabled=True, fill_max_width=True)split_button(text, on_click=None, on_secondary=None, enabled=True, fill_max_width=True)icon_button(icon, on_click=None, enabled=True)text_field(value="", on_change=None, label="", single_line=True, fill_max_width=True)switch(checked=False, on_change=None, title="", subtitle="", enabled=True)checkbox(checked=False, on_change=None, title="", subtitle="", enabled=True)slider(value=0, on_change=None, minimum=0, maximum=100, title="", steps=0)linear_progress(progress=None, fill_max_width=True)circular_progress(progress=None)wavy_progress(progress=None, size=64)divider(vertical=False)spacer(height=8, width=0)icon(name, size=24, tint=None)image(source, width=None, height=None, scale="fit", corner_radius=0, fill_max_width=False, content_description="")chip(text, selected=False, on_click=None)card(children=None, padding=16, index=None, count=None, on_click=None)function_row(title, description="", icon="extension", icon_tint=None, on_click=None)segmented_list(children=None, spacing=4)settings_row(title, subtitle="", icon=None, on_click=None, trailing=None)tabs(tabs, selected=0, on_select=None)button_group(options, selected, on_select=None)floating_toolbar(items)toolbar_item(icon, on_click=None, selected=False, label="")radio_group(options, selected, on_select=None, title="")dropdown(options, selected, on_select=None, label="", enabled=True)webview(url="", html="", fill_max_size=True, height=320)log_panel(text, max_height=200)alert_dialog(show, title, message, confirm_text="OK", dismiss_text="Cancel", on_confirm=None, on_dismiss=None, cancelable=True)empty()api.registerSettings adds Material 3 Expressive sections directly to Dolphy Settings. Switches and sliders persist in the plugin store and emit setting_changed; nav and card items open plugin screens.
headerswitchslidernavcardimport json
def on_plugin_load(self):
self.api.registerSettings("Radio toolkit", json.dumps([
{"type": "switch", "key": "capture", "title": "Capture packets", "subtitle": "Keep a local trace", "default": True},
{"type": "slider", "key": "power", "title": "Power", "min": 1, "max": 100, "default": 40, "steps": 98},
{"type": "nav", "title": "Advanced", "icon": "tune", "screen": "advanced"}
]), 20)
def on_setting_changed(self, payload):
key = payload["key"]
value = payload["value"]headertitleswitchkey, title, subtitle, default: boolsliderkey, title, subtitle, min, max, default, stepsnavtitle, subtitle, icon, screen; always opens plugin/{pluginId}/{screen}cardtitle, subtitle, icon and optional screenSwitch and slider values use the same per-plugin JSON store as get_setting/set_setting. The host persists a value before calling on_setting_changed({key, value}); sliders emit the event when interaction finishes.
There is no predefined key catalog. The plugin author creates any string key and uses the same spelling when reading, writing or declaring a Settings item. Keys only collide inside the same plugin because every pluginId has a separate store. Prefer stable names such as capture_enabled or radio.power; changing a key creates a new setting.
set_setting("capture_enabled", True)get_setting("capture_enabled", False){"type":"switch", "key":"capture_enabled", ...}keyAny non-empty string chosen by the plugin. There are currently no reserved host keys.valueAny json.dumps-compatible value: None, bool, number, str, list or dict.defaultget_setting returns the supplied default when the key has never been saved.lifetimeValues survive restarts and enable/disable. Deleting the plugin removes its entire settings store.Integrate with existing features
Plugins can participate in Dolphy instead of living on an isolated screen.
Screen hooks work for every route hosted by Dolphy. Action and service hooks work only where the app explicitly dispatches a named integration point. A plugin does not automatically intercept every Kotlin function.
Add an overlay, top or bottom panel, FAB, or replace a route. Wildcards such as ir_* are supported.
Inspect or change data, mark an action handled, or cancel it.
Provide an alternative hardware or logic backend with automatic fallback.
Screen hooks in detail
Routes and patterns are case-insensitive. The asterisk matches any number of characters. If several plugins match, higher priority wins where only one contribution can be used.
overlayRenders a full-size transparent layer over the existing screen.topPlaces plugin UI in a Material surface at the top of the screen.bottomPlaces plugin UI above the bottom navigation area.fabPlaces compact plugin UI at the bottom end with navigation-safe padding.replaceThe highest-priority replacement visually covers the built-in screen. Its underlying Compose screen still exists, so this is UI replacement rather than bytecode patching.ir_*other/ir_*nfc_*other/nfc_*lan_*other/lan_*ble_section/*plugin/**Use the navigation event while developing to inspect the current route. Routes with arguments are reported as their navigation template, for example nfc_result/{id}.
Use replace with other, bluetooth or settings to cover the complete core screen. Bluetooth also exposes three live sub-screen targets, so a plugin can replace All, BLE or Advert independently.
otherbluetoothsettingsbluetooth/allbluetooth/blebluetooth/adverthook_surface targets UI outside an ordinary screen route. bottom_bar can be overlaid or fully replaced. The replacement follows plugin priority and disappears automatically when the plugin is disabled.
hook_surface("bottom_bar", "panel", "replace", 100)surface/bottom_bardef on_plugin_load(self):
self.hook_screen("other", "my_other", "replace", 100)
self.hook_screen("settings", "my_settings", "replace", 100)
self.hook_screen("bluetooth/all", "my_all", "replace", 100)
self.hook_screen("bluetooth/ble", "my_ble", "replace", 100)
self.hook_screen("bluetooth/advert", "my_advert", "replace", 100)def on_plugin_load(self):
self.hook_surface("bottom_bar", "custom_bar", "replace", 100)
def screen_custom_bar(self, ui, api, state):
return ui.floating_toolbar([
ui.toolbar_item("bluetooth", lambda: self.navigate_app("bluetooth"), label="Bluetooth"),
ui.toolbar_item("extension", lambda: self.navigate_app("other"), label="Functions"),
ui.toolbar_item("settings", lambda: self.navigate_app("settings"), label="Settings")
])def on_plugin_load(self):
self.hook_screen("ir_*", "ir_badge", "fab", 50)
self.hook_screen("nfc_tools", "nfc_panel", "bottom", 20)
self.hook_screen("qr_tools", "replacement", "replace", 100)
def screen_ir_badge(self, ui, api, state):
return ui.icon_button("usb", lambda: self.navigate("hardware"))
def screen_hardware(self, ui, api, state):
return ui.scaffold(
title="External hardware",
content=ui.text("Connected")
)
def screen_nfc_panel(self, ui, api, state):
return ui.card([
ui.text("Plugin panel on the existing NFC screen")
])
def screen_replacement(self, ui, api, state):
return ui.scaffold(
title="My QR tools",
content=ui.column([
ui.text("This plugin replaced the built-in QR screen", "headlineSmall"),
ui.button("Open original section", lambda: self.navigate_app("other"))
], padding=16, spacing=12, fill_max_size=True)
)Action hooks and services
Action hooks form a priority-ordered pipeline. Services form a priority-ordered provider list. Both use JSON-compatible payloads.
NoneContinue without changing the action.{"payload": {...}}Replace the payload passed to lower-priority hooks and the built-in implementation.{"handled": true}Mark the action handled by the plugin. Use result to return a value.{"cancel": true}Stop the hook chain and cancel the built-in operation.FalseCancel immediately. True marks the action handled.Dolphy exposes shared infrared transmission plus Bluetooth section selection, All actions, BLE modes, Advert presets and every outgoing BLE advertising packet. The complete exact list is in the hook catalog.
Add a first-class BLE Spam mode
register_ble_mode adds the plugin beside the built-in BLE modes using the same card, active state, shared delay and Stop All behavior. Dolphy sends ble_mode_start, ble_mode_stop and ble_mode_delay_changed only to the owning plugin. The plugin owns its advertising loop and must stop it on both stop and unload.
def on_plugin_load(self):
self.register_ble_mode("lab_beacon", "Lab Beacon", "Custom advertising payload", "radar")
def on_ble_mode_start(self, payload):
if payload["modeId"] == "lab_beacon":
self.start_loop(payload["delayMs"])
def on_ble_mode_delay_changed(self, payload):
self.delay_ms = payload["delayMs"]
def on_ble_mode_stop(self, payload):
self.stop_loop()
def on_plugin_unload(self):
self.stop_loop()Download and apply plugin assets
download_assets requests all files as one batch. Each destination is confined to this plugin’s private directory; maxBytes limits each response and sha256 can verify it. Unless the user disabled confirmation in Plugin settings → Security, Dolphy shows the plugin name, file count and declared total size before network transfer. apply_asset imports a saved file into a stock Bad USB editor, user IR remotes or QR Audio Spoofer custom HTML.
def fetch_remote(self):
files = [{
"url": "https://example.org/remotes/tv.ir",
"path": "remotes/tv.ir",
"sizeBytes": 24576,
"maxBytes": 100000,
"sha256": "published SHA-256"
}]
self.download_assets(files, self.downloaded)
def downloaded(self, result_json):
result = json.loads(str(result_json))
if result.get("ok"):
self.apply_asset("ir_remote", "remotes/tv.ir", "My TV")asset_path(path)Returns the absolute path of a location inside the plugin’s private folder for Java and DEX APIs.download_assets(items, callback)Items accept url, path, sizeBytes, maxBytes, sha256 and headers. The callback receives {ok, files} as JSON text.apply_asset(type, path, title="")Supported types: bad_hid, ir_remote and qr_audio_html. Returns a parsed result dictionary.Example: replace BLE advertising logic
bluetooth.advertising.transmit runs for custom Advert presets and packets generated by every built-in BLE spam mode. Change data to rewrite the packet, return cancel to block it, or handled after transmitting with your own backend.
def on_plugin_load(self):
self.hook_action("bluetooth.advertising.transmit", 100)
self.hook_action("bluetooth.advert.preset.*", 80)
def hook_bluetooth_advertising_transmit(self, payload):
source = payload["metadata"]["source"]
packet = payload["data"]
if self.external_radio_ready():
self.send_external(packet, payload.get("scanResponse"))
return {"handled": True}
packet["includeDeviceName"] = False
return {"payload": payload}def on_plugin_load(self):
self.hook_screen("ir_*", "usb_status", "fab", 50)
self.hook_action("infrared.transmit", 50)
self.provide_service("infrared.transmitter", 100)
def hook_infrared_transmit(self, payload):
payload["frequency"] = self.normalize_frequency(payload["frequency"])
return {"payload": payload}
def service_infrared_transmitter_available(self, payload):
return {"available": self.usb_connected()}
def service_infrared_transmitter_transmit(self, payload):
if not self.usb_connected():
return {"handled": False}
ok = self.send_usb(payload["frequency"], payload["pattern"])
return {"handled": True, "ok": ok}Example: intercept every IR Storm transmission
IR Storm calls the shared infrared.transmit action once for every signal. The action payload does not contain the source screen, so a plugin that targets only IR Storm must remember the latest navigation event.
__id__ = "ir_storm_hook"
__name__ = "IR Storm Hook"
__version__ = "1.0.0"
__icon__ = "rounded:settings_input_antenna"
from java import jarray, jint
class IrStormHook(BasePlugin):
def __init__(self):
super().__init__()
self.current_route = ""
def on_plugin_load(self):
self.hook_action("infrared.transmit", 100)
def on_navigation(self, route):
self.current_route = str(route)
def hook_infrared_transmit(self, payload):
if self.current_route not in ("ir_storm", "other/ir_storm"):
return None
frequency = int(payload["frequency"])
pattern = [int(value) for value in payload["pattern"]]
ok = self.send_to_external_ir(frequency, pattern)
return {
"handled": True,
"result": ok
}
def send_to_external_ir(self, frequency, pattern):
android = self.api.getAndroid()
java_pattern = jarray(jint)(pattern)
return android.irTransmit(frequency, java_pattern)The example sends through Android's built-in ConsumerIrManager without re-entering the hook. For a USB transmitter, replace only send_to_external_ir with the device protocol implemented through api.getAndroid(), Android USB classes or a service provider.
nameSignal or button name when availablefrequencyCarrier frequency in hertzpatternAlternating mark and space durations in microsecondsprotocolProtocol label when knowncodeOriginal encoded value when availabletimingsOriginal timing representation when availableIt intercepts both ir_storm and other/ir_storm. Returning None on every other route preserves ordinary remotes, TV Be Gone and IR Jammer. Remove the route check to intercept every Dolphy IR transmission.
{"payload": payload}Change frequency or pattern, then let Dolphy continue sending.{"handled": true, "result": true}The plugin sent the signal; skip the built-in transmitter and report success.{"cancel": true}Block the signal without sending it.DEX loading and library plugins
Keep a plugin as one readable Python file while embedding compiled Java or Kotlin code and sharing native capabilities with other plugins.
A loaded DEX runs inside the Dolphy process with the app permissions. Always publish the SHA-256, verify it before loading and never execute an untrusted downloaded payload.
Embed and load a raw DEX
Store the raw DEX as Base64 in the .dolphyplugin file. Dolphy removes whitespace, decodes it, verifies SHA-256 and creates the class loader. Raw DEX uses InMemoryDexClassLoader on Android 8+; older Android and ZIP-based JAR/APK payloads use a private read-only code-cache file and DexClassLoader.
__id__ = "shared_radio_core"
__name__ = "Shared Radio Core"
__version__ = "1.0.0"
__library__ = True
DEX_SHA256 = "48c3cb64ad3c0e82b7352549f7568e4e42119fc6b066a050054474e6fffca28c"
DEX_BASE64 = "ZGV4CjAzNQ..."
class SharedRadioCore(BasePlugin):
def on_plugin_load(self):
loader = self.load_dex_base64("radio_core", DEX_BASE64, DEX_SHA256)
bridge_class = loader.loadClass("dev.dolphy.radio.RadioBridge")
constructor = bridge_class.getDeclaredConstructor()
constructor.setAccessible(True)
bridge = constructor.newInstance()
self.export_java_class("radio.bridge.class", bridge_class)
self.export_java_object("radio.bridge", bridge)
def on_plugin_unload(self):
bridge = self.import_java_object("radio.bridge")
if bridge is not None:
bridge.close()load_dex_base64(module, encoded, sha256="")Loads raw DEX, JAR or APK bytes stored as Base64 and returns the module ClassLoader.load_dex_file(module, path, sha256="")Copies and loads a DEX/JAR/APK file. Use plugin-private storage and verify its digest.dex_class(module, class_name)Loads a Class from a module owned by the current plugin.new_dex_instance(module, class_name)Calls an accessible no-argument constructor and returns the Java object.api.getDexClassLoader(module)Returns an already loaded module ClassLoader or None.api.getDependencyClassLoader()Returns an app parent loader which can also resolve modules exported by declared dependencies.Export classes and live objects
Exports are process-local named references. A library owns every class and object it exports; disabling, updating or deleting that library removes its modules and exports from the registry.
__id__ = "radio_consumer"
__name__ = "Radio Consumer"
__dependencies__ = ["shared_radio_core"]
class RadioConsumer(BasePlugin):
def on_plugin_load(self):
self.bridge = self.import_java_object("radio.bridge")
bridge_class = self.import_java_class("radio.bridge.class")
if self.bridge is None or bridge_class is None:
raise RuntimeError("Shared Radio Core did not export its API")
self.add_module("Radio Console", "Uses the shared native bridge", "memory")
def scan(self):
result = self.bridge.scan()
self.toast(str(result))export_dex_class(name, module, class_name)Resolve and publish a class from the current DEX module.export_app_class(name, class_name)Publish a Dolphy or dependency class by its fully qualified name.export_java_class(name, java_class)Publish a Class object already obtained through Java reflection.export_java_object(name, value)Publish a live Java object, adapter, controller or facade.import_java_class(name)Get a named Class export from any enabled dependency.import_java_object(name)Get a named live object export from any enabled dependency.java_exports()Inspect all loaded modules and exported class/object names for diagnostics.Expose Dolphy internals from open source
Use the repository to find a fully qualified JVM class name, then export that Class or wrap it in a stable DEX facade. Consumers should depend on the library facade instead of duplicating fragile reflection code.
def on_plugin_load(self):
self.export_app_class(
"dolphy.main_activity",
"com.droid.dolphy.MainActivity"
)
activity_class = self.import_java_class("dolphy.main_activity")
methods = [method.getName() for method in activity_class.getDeclaredMethods()]Java reflection helpers
These helpers search the app plus DEX modules of declared dependencies. They can inspect private members, read or write fields, call a matching method and construct an object. They do not install a method hook and do not intercept future calls.
find_java_class(class_name)Returns a Class or None using the app and dependency ClassLoaders.inspect_java_class(java_class)Returns className plus inherited fields, methods, constructors, parameter types and static flags.get_java_field(target, field_name)Reads a public or private instance field. Pass a Class for a static field.set_java_field(target, field_name, value)Writes a public or private instance field. Pass a Class for a static field.invoke_java(target, method_name, *args)Calls the first compatible overload by name, argument count and runtime types. Pass a Class for a static method.new_java_instance(java_class, *args)Calls the first compatible declared constructor, including a private constructor.__id__ = "dolphy_reflection_facade"
__name__ = "Dolphy Reflection Facade"
__library__ = True
class ReflectionFacade(BasePlugin):
def on_plugin_load(self):
activity_class = self.find_java_class("com.droid.dolphy.MainActivity")
if activity_class is None:
raise RuntimeError("MainActivity class is unavailable")
description = self.inspect_java_class(activity_class)
self.export_java_class("dolphy.main_activity", activity_class)
self.set_setting("known_methods", [item["name"] for item in description["methods"]])Private names and signatures may change between Dolphy builds. Catch exceptions, declare the library dependency, verify the app version yourself and prefer stable screen, surface, action and service hooks whenever one exists. Overload resolution does not perform arbitrary type conversion.
Class export enables reflection, construction and method calls; it does not rewrite an already compiled Compose function. Use hook_screen for screens, hook_surface("bottom_bar") for the navigation panel, action hooks for logic and service providers for replaceable hardware backends. A DEX library can hide reflection behind a stable facade shared by other plugins.
A .dolphyplugin may be 48 MiB; each decoded DEX/JAR/APK payload may be 32 MiB. Disabling, deleting or updating a library automatically disables enabled dependent plugins first; re-enable consumers when the library is ready. Android cannot unload a Class once code has referenced it. Dolphy drops loaders and exports when the owner stops, but a process restart is the only guaranteed complete unload after an update. Keep export names globally unique and close threads, receivers, sockets and hardware handles in on_plugin_unload.
48 MiB source32 MiB decoded moduleSHA-256read-only code cacheprocess-local exportsAndroid and device APIs
Use high-level Dolphy bridges or import Android Java classes directly.
api.getContext()Android application contextapi.getActivity()Current Activity when availableapi.getAndroid()Files, intents, USB, IR, networking and Android helpersapi.getDevice()BLE scans and device capabilitiesself.shell(command)Regular shell commandself.root(command)Root command when grantedself.shizuku(command)Shizuku command when availableComplete runtime map
These groups mirror the callable surface currently shipped with Dolphy. Open a group to see every public helper name.
self / BasePluginConvenience helpers for modules, navigation, state, screen/action/service hooks and shell access.self.uiBuilds a JSON-compatible UI tree rendered as native Compose Material 3 components.self.apiRegistration, Context/Activity access, persistent JSON settings, execution and batched BLE scanning.self.api.getAndroid()Direct Android integration: storage, assets, media, crypto, IR, classic Bluetooth, RFCOMM, GATT, BLE server/advertiser, Wi-Fi and NSD.self.api.getDevice()Higher-level wireless, NFC, network, Root and Shizuku operations with JSON-oriented results.jclass / Java importsChaquopy can import Android and Java classes directly when a bridge helper is not enough.*Json() → str; use json.loads(...)HEX → even-length hexadecimal textcallbacks → JSON str or primitivescanId/address → resource keystop/release → explicit cleanupBoolean false → unsupported, denied or failed{ok,code,out,err,via} → command resultMost hardware helpers do not open permission prompts. Check hasPermission/capability methods and request Android permissions through Activity APIs when needed. Long-running scans, GATT, RFCOMM, sensors, audio, advertising, BLE servers, NSD and Wi-Fi P2P must be stopped during on_plugin_disable/on_plugin_unload.
getContext()getApplicationContext()getActivity()getClassLoader()getAndroid()getDevice()getPluginId()getDependencies()getDependencyClassLoader()registerModule(section, title, description, icon, screenId, order)registerSettings(title, itemsJson, order=0)registerScreenHook(routePattern, screenId, mode="overlay", priority=0)registerSurfaceHook(surface, screenId, mode="overlay", priority=0)registerService(serviceId, priority=0)registerActionHook(actionPattern, priority=0)loadDexBase64(moduleName, encoded, sha256)loadDexFile(moduleName, path, sha256)getDexClassLoader(moduleName)loadDexClass(moduleName, className)newDexInstance(moduleName, className)exportDexClass(exportName, moduleName, className)exportAppClass(exportName, className)exportJavaClass(exportName, type)importJavaClass(exportName)exportJavaObject(exportName, value)importJavaObject(exportName)listJavaExports()navigate(screenId)refresh()toast(text)getSettingJson(key, fallbackJson)setSettingJson(key, valueJson)shellExec(command)rootExec(command)shizukuExec(command)smartExec(command)bleStartScan(scanId, callback, batchMs=350, maxDevices=128)bleStopScan(scanId)release()Use BasePlugin wrappers where they exist. Direct bridge methods preserve Java return types; settings and command methods exchange JSON text.
import json
class HardwarePlugin(BasePlugin):
def on_plugin_load(self):
self.devices = []
self.api.bleStartScan("nearby", self.on_ble_batch, 350, 128)
def on_ble_batch(self, payload_json):
self.devices = json.loads(str(payload_json))
self.refresh()
def connect_gatt(self, address):
android = self.api.getAndroid()
return android.gattConnect(address, self.on_gatt_event)
def on_gatt_event(self, payload_json):
event = json.loads(str(payload_json))
def on_plugin_unload(self):
self.api.bleStopScan("nearby")
self.api.getAndroid().release()fact_checkAdditional Device API methodsCapabilities included in the exact source audit
deviceInfoJson() -> StringwifiIsEnabled() -> BooleanwifiSetEnabled(enabled: Boolean) -> BooleanwifiOpenSettings() -> UnitwifiIs5GHzBandSupported() -> BooleanwifiIsP2pSupported() -> BooleanbtIsEnabled() -> BooleanbtState() -> IntbtLeScannerAvailable() -> BooleanbtLeAdvertiserAvailable() -> BooleanbtSupportsMultipleAdvertisement() -> BooleanbtSupportsOffloadedFiltering() -> BooleanbtSupportsOffloadedScanBatching() -> BooleanbleScanResultToJson(result: ScanResult) -> StringstopAllBleScans() -> UnitnfcIsAvailable() -> BooleannfcIsEnabled() -> BooleannfcOpenSettings() -> UnitnfcRootSetEnabled(enabled: Boolean, callback: (String) -> Unit) -> UnitnfcWriteNdefRaw(recordsJson: String, callback: (String) -> Unit) -> UnitonNfcTagDiscovered(tag: Tag) -> UnitrootId(callback: (String) -> Unit) -> UnitrootWhich(bin: String, callback: (String) -> Unit) -> UnitrootExists(path: String, callback: (String) -> Unit) -> UnitrootPs(filter: String?, callback: (String) -> Unit) -> UnitrootKill(target: String, callback: (String) -> Unit) -> UnitrootMount(callback: (String) -> Unit) -> UnitrootRemount(rw: Boolean, callback: (String) -> Unit) -> UnitrootDf(callback: (String) -> Unit) -> UnitrootServiceList(callback: (String) -> Unit) -> UnitrootInitDList(callback: (String) -> Unit) -> UnitrootInitDRun(script: String, callback: (String) -> Unit) -> UnitrootExecScript(script: String, callback: (String) -> Unit) -> UnitrootBuildPropGet(key: String, callback: (String) -> Unit) -> UnitrootBuildPropSet(key: String, value: String, callback: (String) -> Unit) -> UnitrootPullToFile(remotePath: String, localFile: java.io.File, callback: (String) -> Unit) -> UnitrootPushFromFile(localFile: java.io.File, remotePath: String, callback: (String) -> Unit) -> UnitrootPmPath(pkg: String, callback: (String) -> Unit) -> UnitrootPmClear(pkg: String, callback: (String) -> Unit) -> UnitrootReboot(mode: String?, callback: (String) -> Unit) -> UnitrootSelinuxSet(mode: String, callback: (String) -> Unit) -> UnitrootMagiskToggleModule(name: String, enable: Boolean, callback: (String) -> Unit) -> UnitrootBtStatus(callback: (String) -> Unit) -> UnitrootBtSetEnabled(enabled: Boolean, callback: (String) -> Unit) -> UnitrootWifiStatus(callback: (String) -> Unit) -> UnitrootWifiSetEnabled(enabled: Boolean, callback: (String) -> Unit) -> UnitrootNfcStatus(callback: (String) -> Unit) -> UnitrootHciconfig(callback: (String) -> Unit) -> UnitrootIwlistScan(iface: String, callback: (String) -> Unit) -> UnitrootWmSizeSet(w: Int, h: Int, callback: (String) -> Unit) -> UnitrootWmSizeReset(callback: (String) -> Unit) -> UnitrootWmDensitySet(d: Int, callback: (String) -> Unit) -> UnitrootWmDensityReset(callback: (String) -> Unit) -> UnitshellQuote(s: String) -> StringextensionBasePluginPlugin helpers
add_module(title, description="", icon="extension", screen="main", section="PLUGINS", order=0)navigate(screen)navigate_app(route)refresh()get_setting(key, default=None)set_setting(key, value)toast(text)hook_screen(route, screen="main", mode="overlay", priority=0)hook_surface(surface, screen="main", mode="overlay", priority=0)hook_action(action, priority=0)provide_service(service, priority=0)load_dex_base64(module, encoded, sha256="")load_dex_file(module, path, sha256="")dex_class(module, class_name)new_dex_instance(module, class_name)export_dex_class(name, module, class_name)export_app_class(name, class_name)export_java_class(name, java_class)import_java_class(name)export_java_object(name, value)import_java_object(name)java_exports()shell(command)root(command)shizuku(command)cloud_downloadBasePluginBLE modes and plugin assets
register_ble_mode(mode_id, title, description="", icon="bluetooth_searching", order=0) -> Booleanasset_path(path) -> String?download_assets(items, on_result) -> Booleanapply_asset(asset_type, path, title="") -> dictmanage_searchBasePluginReflection helpers
find_java_class(class_name) -> Class?inspect_java_class(java_class) -> dictget_java_field(target, field_name) -> Object?set_java_field(target, field_name, value) -> Booleaninvoke_java(target, method_name, *args) -> Object?new_java_instance(java_class, *args) -> ObjectwidgetsUiFactoryNative Compose UI builders
scaffold(content, title=None, show_back=True, fab=None, actions=None)top_action(icon, on_click=None)column(children=None, padding=0, spacing=8, fill_max_size=False)row(children=None, padding=0, spacing=8, fill_max_width=True)box(children=None, padding=0, fill_max_size=False)lazy_column(children=None, padding=0, spacing=4, fill_max_size=True)lazy_row(children=None, padding=0, spacing=8, fill_max_width=True)text(text, style="bodyMedium", color=None, max_lines=2147483647)button(text, on_click=None, style="filled", enabled=True, fill_max_width=False)bounce_button(text, on_click=None, enabled=True, fill_max_width=True)split_button(text, on_click=None, on_secondary=None, enabled=True, fill_max_width=True)icon_button(icon, on_click=None, enabled=True)text_field(value="", on_change=None, label="", single_line=True, fill_max_width=True)switch(checked=False, on_change=None, title="", subtitle="", enabled=True)checkbox(checked=False, on_change=None, title="", subtitle="", enabled=True)slider(value=0, on_change=None, minimum=0, maximum=100, title="", steps=0)linear_progress(progress=None, fill_max_width=True)circular_progress(progress=None)wavy_progress(progress=None, size=64)divider(vertical=False)spacer(height=8, width=0)icon(name, size=24, tint=None)image(source, width=None, height=None, scale="fit", corner_radius=0, fill_max_width=False, content_description="")chip(text, selected=False, on_click=None)card(children=None, padding=16, index=None, count=None, on_click=None)function_row(title, description="", icon="extension", icon_tint=None, on_click=None)segmented_list(children=None, spacing=4)settings_row(title, subtitle="", icon=None, on_click=None, trailing=None)tabs(tabs, selected=0, on_select=None)button_group(options, selected, on_select=None)floating_toolbar(items)toolbar_item(icon, on_click=None, selected=False, label="")radio_group(options, selected, on_select=None, title="")dropdown(options, selected, on_select=None, label="", enabled=True)webview(url="", html="", fill_max_size=True, height=320)log_panel(text, max_height=200)alert_dialog(show, title, message, confirm_text="OK", dismiss_text="Cancel", on_confirm=None, on_dismiss=None, cancelable=True)empty()conversion_pathapiRuntime bridge
getContext()getApplicationContext()getActivity()getClassLoader()getAndroid()getDevice()getPluginId()getDependencies() -> Array<String>getDependencyClassLoader() -> ClassLoaderregisterModule(section, title, description, icon, screenId, order)registerSettings(title, itemsJson, order=0)registerScreenHook(routePattern, screenId, mode="overlay", priority=0)registerSurfaceHook(surface, screenId, mode="overlay", priority=0)registerService(serviceId, priority=0)registerActionHook(actionPattern, priority=0)loadDexBase64(moduleName, encoded, sha256) -> ClassLoaderloadDexFile(moduleName, path, sha256) -> ClassLoadergetDexClassLoader(moduleName) -> ClassLoader?loadDexClass(moduleName, className) -> ClassnewDexInstance(moduleName, className) -> ObjectexportDexClass(exportName, moduleName, className) -> BooleanexportAppClass(exportName, className) -> BooleanexportJavaClass(exportName, type) -> BooleanimportJavaClass(exportName) -> Class?exportJavaObject(exportName, value) -> BooleanimportJavaObject(exportName) -> Object?listJavaExports() -> Stringnavigate(screenId)refresh()toast(text)getSettingJson(key, fallbackJson)setSettingJson(key, valueJson)shellExec(command)rootExec(command)shizukuExec(command)smartExec(command)bleStartScan(scanId, callback, batchMs=350, maxDevices=128)bleStopScan(scanId)release()cloud_syncapiBLE and asset bridge
registerBleMode(modeId, title, description="", icon="bluetooth_searching", order=0) -> BooleanassetPath(path) -> String?downloadAssets(itemsJson, callback) -> BooleanapplyAsset(type, path, title="") -> Stringdata_objectapiReflection bridge
findJavaClass(className) -> Class?inspectJavaClass(type) -> StringgetJavaField(target, fieldName) -> Object?setJavaField(target, fieldName, value) -> BooleaninvokeJava(target, methodName, args) -> Object?newJavaInstance(type, args) -> Objectandroidapi.getAndroid()Android, files and advanced Bluetooth
clipboardGet() -> StringclipboardSet(text: String) -> Booleanvibrate(ms: Long) -> BooleanvibratePattern(patternMs: LongArray, repeat: Int = -1) -> BooleanvibrateCancel() -> UnitnotifyShow(id: Int, title: String, text: String, channelId: String = "dolphy_plugins") -> BooleannotifyCancel(id: Int) -> UnitopenUrl(url: String) -> BooleanshareText(text: String, title: String = "Share") -> BooleanopenSettings(action: String?) -> BooleanstartActivity(action: String, dataUri: String? = null, extrasJson: String? = null) -> Booleandial(number: String) -> BooleanfilesList(sub: String = "") -> StringfilesRead(path: String) -> String?filesWrite(path: String, content: String, append: Boolean = false) -> BooleanfilesDelete(path: String) -> BooleanfilesExists(path: String) -> BooleanfilesWriteBase64(path: String, b64: String) -> BooleanfilesReadBase64(path: String) -> String?filesMkdir(path: String) -> BooleanfilesStat(path: String) -> StringfilesCopy(from: String, to: String) -> BooleanfilesMove(from: String, to: String) -> BooleanfilesAppend(path: String, content: String) -> BooleanfilesAbsolute(path: String) -> String?filesShare(path: String, mime: String? = null, title: String = "Share") -> BooleanfilesOpenWith(path: String, mime: String? = null) -> BooleanexportSandboxToUri(sandboxPath: String, destUri: Uri) -> StringpluginFilesDir() -> FileimportUriToSandbox(uri: Uri, destRelative: String? = null, includeBase64: Boolean = false, maxBytes: Long = 12_000_000L) -> StringassetsList(path: String = "") -> StringassetsExists(path: String) -> BooleanassetsReadText(path: String, maxBytes: Int = 2_000_000) -> String?assetsReadBase64(path: String, maxBytes: Int = 5_000_000) -> String?assetsDataUri(path: String, maxBytes: Int = 5_000_000) -> String?createCameraCaptureTarget(fileName: String? = null) -> Pair? importCameraFile(captureFile: File, destRelative: String? = null, includeBase64: Boolean = false) -> StringhasCamera() -> BooleancryptoHash(algo: String, text: String) -> String?cryptoHashBytes(algo: String, bytes: ByteArray) -> String?cryptoHashFile(algo: String, path: String) -> String?cryptoBase64Encode(text: String) -> StringcryptoBase64Decode(b64: String) -> String?cryptoHexEncode(text: String) -> StringcryptoHexDecode(hex: String) -> String?cryptoRandomHex(bytes: Int = 16) -> StringcryptoUuid() -> StringirHasEmitter() -> BooleanirCarrierFrequenciesJson() -> StringirTransmit(freqHz: Int, pattern: IntArray) -> BooleanusbDevicesJson() -> StringlocationLastJson() -> StringlocationIsEnabled() -> BooleansensorsListJson() -> StringsensorStart(type: Int, onEvent: (String) -> Unit) -> String?sensorStop(id: String) -> UnitsensorStopAll() -> UnitaudioPlayUrl(url: String, onDone: ((String) -> Unit)? = null) -> BooleanaudioPlayFile(path: String) -> BooleanaudioStop() -> UnitaudioTone(toneType: Int = ToneGenerator.TONE_PROP_BEEP, durationMs: Int = 200) -> BooleanbatteryJson() -> StringhasPermission(permission: String) -> BooleanhasFeature(feature: String) -> BooleanfeaturesJson() -> StringdeviceInfoExtendedJson() -> StringconnectivityDetailJson() -> StringbtBondedDevicesJson() -> StringbtStartDiscovery(onDevice: (String) -> Unit) -> BooleanbtStopDiscovery() -> UnitbtOpenSettings() -> BooleanbtAddress() -> StringbtName() -> StringbtSetName(name: String) -> BooleanbtEnable() -> BooleanbtDisable() -> BooleanbtIsEnabled() -> BooleanbtScanMode() -> IntbtDeviceInfoJson(address: String) -> StringbtCreateBond(address: String) -> BooleanbtRemoveBond(address: String) -> BooleanbtGetBondState(address: String) -> IntbtConnectRfcomm(address: String, uuidStr: String, onEvent: (String) -> Unit) -> BooleanbtSendRfcomm(address: String, hex: String) -> BooleanbtSendRfcommText(address: String, text: String) -> BooleanbtDisconnectRfcomm(address: String) -> UnitbtRfcommConnectedJson() -> StringgattConnect(address: String, onEvent: (String) -> Unit) -> BooleangattDisconnect(address: String) -> UnitgattRead(address: String, serviceUuid: String, charUuid: String) -> BooleangattWrite(address: String, serviceUuid: String, charUuid: String, hex: String) -> BooleangattWriteNoResponse(address: String, serviceUuid: String, charUuid: String, hex: String) -> BooleangattEnableNotifications(address: String, serviceUuid: String, charUuid: String, enable: Boolean = true) -> BooleangattRequestMtu(address: String, mtu: Int) -> BooleangattReadRssi(address: String) -> BooleangattSetPreferredPhy(address: String, txPhy: Int, rxPhy: Int, phyOptions: Int = 0) -> BooleangattDiscoverServices(address: String) -> BooleangattServicesJson(address: String) -> StringgattWriteDescriptor(address: String, serviceUuid: String, charUuid: String, descUuid: String, hex: String) -> BooleangattReadDescriptor(address: String, serviceUuid: String, charUuid: String, descUuid: String) -> BooleanbleAdvertiseStart(manufacturerId: Int, payloadHex: String, connectable: Boolean = false, includeName: Boolean = false) -> BooleanbleAdvertiseCustom(manufacturerId: Int?, manufacturerData: String?, serviceUuids: List?, serviceDataUuid: String?, serviceData: String?, includeName: Boolean = false, includeTxPower: Boolean = false, connectable: Boolean = false, mode: Int = AdvertiseSettings.ADVERTISE_MODE_LOW_LATENCY, txPower: Int = AdvertiseSettings.ADVERTISE_TX_POWER_HIGH) -> Boolean bleAdvertiseStop() -> UnitbleServerStart(servicesJson: String, onEvent: (String) -> Unit) -> BooleanbleServerStop() -> UnitbleServerNotify(address: String, serviceUuid: String, charUuid: String, hex: String) -> BooleanbleServerSendResponse(address: String, requestId: Int, status: Int, offset: Int, hex: String?) -> BooleanwifiDetailedInfoJson() -> StringwifiDhcpInfoJson() -> StringwifiChannelsJson() -> StringwifiMacAddress() -> StringwifiIsEnabled() -> BooleanwifiDisconnect() -> BooleanwifiReconnect() -> BooleanwifiAddSuggestion(ssid: String, passphrase: String?) -> BooleannetInterfacesJson() -> StringnsdDiscover(serviceType: String, onEvent: (String) -> Unit) -> BooleannsdStop() -> Unitrelease() -> Unitmemoryapi.getDevice()Wireless, network and elevated device tools
wifiScanResultsJson(maxResults: Int = DEFAULT_WIFI_MAX, minRssi: Int? = null) -> StringwifiStartScan(force: Boolean = false) -> BooleanwifiConnectionInfoJson() -> StringwifiConfiguredNetworksJson() -> StringbtCapabilitiesJson() -> StringbleStartScan(scanId: String, onDeviceJs: (String) -> Unit, batchMs: Long = BLE_BATCH_MS, maxDevices: Int = BLE_MAX_DEVICES) -> BooleanbleStartScanFiltered(pluginId: String, filterName: String?, filterAddress: String?, filterServiceUuid: String?, onResult: (String) -> Unit, batchMs: Long = BLE_BATCH_MS, maxDevices: Int = BLE_MAX_DEVICES) -> BooleanstopBleScan(scanId: String) -> UnitnfcCapabilitiesJson() -> StringnfcSetTagCallback(cb: ((String) -> Unit)?) -> UnitnfcLastTagJson() -> StringnfcReadNdef(callback: (String) -> Unit) -> UnitnfcWriteNdef(textOrUri: String, isUri: Boolean, callback: (String) -> Unit) -> UnitnfcTransceive(techType: String, hexCmd: String, callback: (String) -> Unit) -> UnitnfcMifareClassicRead(sector: Int, keyHex: String?, keyType: String, callback: (String) -> Unit) -> UnitnfcMifareClassicWrite(sector: Int, blockIndex: Int, dataHex: String, keyHex: String?, keyType: String, callback: (String) -> Unit) -> UnitnfcMifareUltralightRead(startPage: Int, callback: (String) -> Unit) -> UnitnfcMifareUltralightWrite(page: Int, dataHex: String, callback: (String) -> Unit) -> UnitirStatusJson() -> StringirToggleStorm() -> BooleanirToggleJammer() -> BooleannetworkActiveJson() -> StringhttpRequest(method: String, url: String, body: String?, headersJson: String?, callback: (String) -> Unit) -> UnithttpDownload(url: String, destFile: java.io.File, headersJson: String?, maxBytes: Long = 50_000_000L, callback: (String) -> Unit) -> UnittcpReachable(host: String, port: Int, timeoutMs: Int, callback: (Boolean) -> Unit) -> UnitpingHost(host: String, timeoutMs: Int, callback: (String) -> Unit) -> UnitportScan(host: String, ports: IntArray, timeoutMs: Int, callback: (String) -> Unit) -> UnitwifiP2pDiscover(callback: (String) -> Unit) -> UnitwifiP2pConnect(address: String, callback: (String) -> Unit) -> UnitwifiP2pDisconnect(callback: (String) -> Unit) -> UnitwifiP2pGroupInfo(callback: (String) -> Unit) -> UnitrootAvailable() -> BooleanrootExec(command: String, callback: (String) -> Unit) -> UnitrootReadText(path: String, maxBytes: Int = 2_000_000, callback: (String) -> Unit) -> UnitrootWriteText(path: String, content: String, callback: (String) -> Unit) -> UnitrootReadBase64(path: String, maxBytes: Int = 2_000_000, callback: (String) -> Unit) -> UnitrootWriteBase64(path: String, b64: String, callback: (String) -> Unit) -> UnitrootList(path: String, callback: (String) -> Unit) -> UnitrootStat(path: String, callback: (String) -> Unit) -> UnitrootMkdir(path: String, callback: (String) -> Unit) -> UnitrootDelete(path: String, recursive: Boolean, callback: (String) -> Unit) -> UnitrootCopy(src: String, dst: String, callback: (String) -> Unit) -> UnitrootMove(src: String, dst: String, callback: (String) -> Unit) -> UnitrootChmod(mode: String, path: String, callback: (String) -> Unit) -> UnitrootChown(owner: String, path: String, callback: (String) -> Unit) -> UnitrootGetprop(key: String?, callback: (String) -> Unit) -> UnitrootSetprop(key: String, value: String, callback: (String) -> Unit) -> UnitrootPackages(filter: String?, callback: (String) -> Unit) -> UnitrootPmInstall(apkPath: String, callback: (String) -> Unit) -> UnitrootPmUninstall(pkg: String, callback: (String) -> Unit) -> UnitrootPmDisable(pkg: String, callback: (String) -> Unit) -> UnitrootPmEnable(pkg: String, callback: (String) -> Unit) -> UnitrootPmGrant(pkg: String, permission: String, callback: (String) -> Unit) -> UnitrootPmRevoke(pkg: String, permission: String, callback: (String) -> Unit) -> UnitrootForceStop(pkg: String, callback: (String) -> Unit) -> UnitrootAmStart(componentOrAction: String, callback: (String) -> Unit) -> UnitrootAmBroadcast(action: String, callback: (String) -> Unit) -> UnitrootSettingsGet(namespace: String, key: String, callback: (String) -> Unit) -> UnitrootSettingsPut(namespace: String, key: String, value: String, callback: (String) -> Unit) -> UnitrootIptables(args: String, callback: (String) -> Unit) -> UnitrootIp(args: String, callback: (String) -> Unit) -> UnitrootIfconfig(callback: (String) -> Unit) -> UnitrootSysctl(key: String?, value: String?, callback: (String) -> Unit) -> UnitrootDmesg(lines: Int, callback: (String) -> Unit) -> UnitrootLogcat(lines: Int, callback: (String) -> Unit) -> UnitrootDumpsys(service: String, callback: (String) -> Unit) -> UnitrootInputTap(x: Int, y: Int, callback: (String) -> Unit) -> UnitrootInputSwipe(x1: Int, y1: Int, x2: Int, y2: Int, durationMs: Int, callback: (String) -> Unit) -> UnitrootInputText(text: String, callback: (String) -> Unit) -> UnitrootInputKeyevent(keycode: Int, callback: (String) -> Unit) -> UnitrootScreencap(destPath: String, callback: (String) -> Unit) -> UnitrootScreenrecord(destPath: String, durationSec: Int, callback: (String) -> Unit) -> UnitrootWmSize(callback: (String) -> Unit) -> UnitrootWmDensity(callback: (String) -> Unit) -> UnitrootSelinux(callback: (String) -> Unit) -> UnitrootMagiskModules(callback: (String) -> Unit) -> UnitrootMagiskInstallModule(zipPath: String, callback: (String) -> Unit) -> UnitrootMacSpoof(iface: String, newMac: String, callback: (String) -> Unit) -> UnitshizukuAvailable() -> BooleanshizukuHasPermission() -> BooleanshizukuRequestPermission() -> UnitshizukuExec(command: String, callback: (String) -> Unit) -> UnitshellExecSmart(command: String, callback: (String) -> Unit) -> UnitCallable SDK reference
Search the complete public API. Every entry includes its exact signature, parameters, return contract, requirements and a Python call example.
A nullable type can become None. Boolean actions report whether Android accepted the request, not whether a remote device completed it. Methods with callbacks finish asynchronously. JSON results remain strings until json.loads is called.
from java import jclass
UsbManager = jclass("android.hardware.usb.UsbManager")
context = self.api.getContext()
usb = context.getSystemService("usb")
result = self.shizuku("id")Complete hook catalog
Every screen route, named action, service operation and event currently emitted by Dolphy.
The Android and Device API lists contain functions a plugin can call. Only the entries below can be intercepted. Registering hook_action("*") does not expose functions that Dolphy never dispatches.
Named action hooks
infrared.transmithook_infrared_transmit(payload)Every send through the shared IR backend: IR Storm, IR Jammer, TV Be Gone, universal and database remotes.bluetooth.section.selecthook_bluetooth_section_select(payload)Before switching between All, BLE and Advert. section or index may be changed.bluetooth.all.device.selecthook_bluetooth_all_device_select(payload)Before selecting a discovered classic Bluetooth device.bluetooth.all.interval.changehook_bluetooth_all_interval_change(payload)When the All interval slider changes. intervalMs may be changed.bluetooth.all.scan.starthook_bluetooth_all_scan_start(payload)Before classic and BLE discovery starts on All.bluetooth.all.starthook_bluetooth_all_start(payload)Before All starts repeated classic pairing requests.bluetooth.all.stophook_bluetooth_all_stop(payload)Before All stops its active operation.bluetooth.ble.delay.changehook_bluetooth_ble_delay_change(payload)Before changing the BLE mode delay. delayMs may be changed.bluetooth.ble.mode.togglehook_bluetooth_ble_mode_toggle(payload)Before toggling an individual BLE mode.bluetooth.ble.kitchen_sink.togglehook_bluetooth_ble_kitchen_sink_toggle(payload)Before toggling Kitchen Sink.bluetooth.ble.stop_allhook_bluetooth_ble_stop_all(payload)Before stopping all BLE modes.bluetooth.ble.section.togglehook_bluetooth_ble_section_toggle(payload)Before toggling every mode in an iOS, Samsung, Android, Windows, Xiaomi or Phantom group.bluetooth.ble.section.openhook_bluetooth_ble_section_open(payload)Before opening a BLE group screen. section may be redirected.bluetooth.advert.preset.addhook_bluetooth_advert_preset_add(payload)Before saving a new Advert preset; all fields may be changed.bluetooth.advert.preset.updatehook_bluetooth_advert_preset_update(payload)Before updating an Advert preset; all fields may be changed.bluetooth.advert.preset.deletehook_bluetooth_advert_preset_delete(payload)Before deleting an Advert preset.bluetooth.advert.preset.starthook_bluetooth_advert_preset_start(payload)Before starting a preset; fields may be changed or the start may be fully handled.bluetooth.advert.preset.stophook_bluetooth_advert_preset_stop(payload)Before stopping an Advert preset.bluetooth.advertising.transmithook_bluetooth_advertising_transmit(payload)Before every outgoing advertising packet from Advert or built-in BLE modes.bluetooth.advertising.stophook_bluetooth_advertising_stop(payload)Before an Advert cycle or built-in BLE advertiser stops; an external backend can stop its own transmission and return handled.Registration uses hook_action(name, priority). Wildcards are supported: bluetooth.*, bluetooth.ble.* and bluetooth.advert.preset.* can intercept whole families.
on_action(action, payload)Generic fallback for every registered action without a matching hook_<normalized_action> method. Dots and other non-alphanumeric characters normalize to underscores.
metadata.source is ble_spam or advert; Advert also includes metadata.preset. data and scanResponse contain includeDeviceName, includeTxPowerLevel, manufacturerData [{id,data}], serviceUuids and serviceData [{uuid,data}]. Binary values are uppercase hex. Returning a modified payload rebuilds the native AdvertiseData.
Service providers and operations
infrared.transmitter.availableservice_infrared_transmitter_available(payload)When Dolphy checks whether any IR transmitter is available.infrared.transmitter.transmitservice_infrared_transmitter_transmit(payload)After action hooks, before the built-in ConsumerIrManager fallback.Registration: provide_service("infrared.transmitter", priority). Service identifiers are exact and do not use wildcard matching.
available → {"available": true}Makes an external transmitter count as available; {"ok": true} or raw True are also accepted.transmit → {"handled": true, "ok": true}Stops provider fallback and reports success. handled false lets the next provider or built-in ConsumerIrManager run.on_service(id, operation, payload)Generic fallback when service_<service>_<operation> is not defined.Events
activity_createon_activity_create(activity)android.app.Activityactivity_destroyon_activity_destroy(activity)android.app.Activitynavigationon_navigation(route)strnfc_tagon_nfc_tag(tag)android.nfc.Tagsetting_changedon_setting_changed(payload){key, value}*on_event(name, payload)Fallback for events without a specific on_<event> methodAll screen routes available to hook_screen
There are 108 routes in the current navigation hosts. Parameterized routes are matched by their template shown below, including braces.
These are live hook targets inside the bluetooth route rather than navigation destinations. They support every hook_screen mode.
bluetooth/allbluetooth/blebluetooth/advertUse hook_surface rather than hook_screen for UI owned by the app shell.
bottom_barsurface/bottom_barroute108 exact route namesUse an exact name or an asterisk pattern
accent_colormain_scaffoldplugin_previewtheme_mode
audio_scannerble_section/{section}bluetoothbluetooth_whisperpairbt_audio_stress_run/{name}/{address}/{rssi}bt_audio_stress_scanflipper_brand/{catEnc}/{brandEnc}flipper_cat/{catEnc}flipper_remote/{catEnc}/{brandEnc}/{remoteEnc}ir_favoritesir_flipper_homeir_jammerir_stormir_tv_homelan_camera_resultslan_camera_scanlan_scannerlan_scanner_runnetwork_diagnostic_hubnfc_audio_spoofernfc_emulator_listnfc_emulator_run/{id}nfc_erasenfc_historynfc_master_keynfc_result/{id}nfc_toolsnfc_trollsnfc_waitnfc_write_contactnfc_write_form/{kind}nfc_write_menunfc_write_waitnrf_scannerotherother/audio_scannerother/bad_usbother/bluetooth_jammerother/bluetooth_whisperpairother/bt_audio_stress_run/{name}/{address}/{rssi}other/bt_audio_stress_scanother/dolphy_chatother/dolphy_chat_conversation/{userId}/{userName}other/dolphy_chat_globalother/dolphy_chat_main/{userName}other/flipper_brand/{catEnc}/{brandEnc}other/flipper_cat/{catEnc}other/flipper_remote/{catEnc}/{brandEnc}/{remoteEnc}other/ir_favoritesother/ir_flipper_homeother/ir_jammerother/ir_stormother/ir_tv_homeother/lan_camera_resultsother/lan_camera_scanother/lan_scannerother/lan_scanner_runother/network_diagnostic_hubother/nfc_audio_spooferother/nfc_emulator_listother/nfc_emulator_run/{id}other/nfc_eraseother/nfc_historyother/nfc_master_keyother/nfc_result/{id}other/nfc_toolsother/nfc_trollsother/nfc_waitother/nfc_write_contactother/nfc_write_form/{kind}other/nfc_write_menuother/nfc_write_waitother/nrf_scannerother/qr_audio_spooferother/qr_detail/{qrId}other/qr_generator_mainother/qr_toolsother/scooter_hackother/smarttv_castother/tv_brand/{brandEnc}other/tv_remote/{brandEnc}/{remoteEnc}other/universal_remote/{categoryId}other/universal_remotes_homeother/user_ir_remote/{remoteId}other/user_ir_remotesother/wifi_printplugin_aboutplugin_file_preview/{uri}plugin_install_helperplugin_managerplugin/{pluginId}/{screenId}qr_audio_spooferqr_detail/{qrId}qr_generator_mainqr_toolssettingssmarttv_casttv_brand/{brandEnc}tv_remote/{brandEnc}/{remoteEnc}universal_remote/{categoryId}universal_remotes_homeuser_ir_remote/{remoteId}user_ir_remoteswifi_print
It cannot currently be intercepted through the public plugin hook API. Dolphy must add invokeActionHooks(name, payload) or invokeServices(service, operation, payload) at that Kotlin call site before plugin authors can hook it.
Lifecycle and state
Release scans, USB connections and background work when the plugin stops.
on_plugin_loadRegister modules, hooks and services.
screen_*Return UI for each plugin screen.
on_eventReceive navigation, NFC and activity events.
on_plugin_unloadStop operations and release resources.
screen_<id>(ui, api, state)Primary renderer. main also accepts create_screen or screen_main. Missing screens render a native error placeholder.callback(value)Python callables passed into UI builders receive only the value type produced by that component. Declaring fewer positional arguments is supported.on_<event>(payload)Specific event handler wins; otherwise on_event(name, payload) is called.hook_<action>(payload)Specific normalized action handler wins; otherwise on_action(action, payload) is called.service_<id>_<operation>(payload)Specific normalized service handler wins; otherwise on_service(id, operation, payload) is called.self.refresh()Invalidates registered plugin UI so screen functions run again with the latest plugin state.on_plugin_loadon_loadonLoadon_plugin_enableon_enableonEnableon_plugin_disableon_disableonDisableon_plugin_unloadon_unloadonUnloadon_<event>on_eventLoad and enable callbacks run when the session starts. Disable and unload callbacks run when it stops. Specific on_<event> handlers take precedence over on_event.
enabled = self.get_setting("enabled", True)
self.set_setting("enabled", not enabled)
self.refresh()
def on_plugin_unload(self):
self.api.bleStopScan("my_scan")Install, update and recover
Dolphy is registered as a .dolphyplugin file handler and shows a safe preview before executing code.
- 1Open the file
Tap a .dolphyplugin file in your file manager and choose Dolphy.
- 2Review metadata
Check the icon, author, version and description on the installation page.
- 3Install
Code starts only after you press Install. Reopening the file updates it.
previewReads metadata and size without executing Python; shows whether the same id is already installed.install / updateSaves the source privately and starts it immediately unless Safe Mode is active. Installing the same id replaces the previous session and keeps its pinned state.enable / disableStarts or stops the runtime and removes all registered modules, settings, screens, services and actions when disabled.pin / sharePin changes ordering. Share exports the original source as application/x-dolphy-plugin with the .dolphyplugin extension.deleteStops the runtime and permanently removes source, metadata, registrations and the plugin's private settings store.startup failureA plugin that cannot start is saved disabled during installation; a corrupt stored source can be quarantined as .broken during restoration.If the app closes while plugin code is running, the next launch disables every plugin and offers a crash log. You can then enable plugins one by one.
The recovery sheet explains that a plugin error closed the app and offers Copy log or Disable Safe Mode. Dismissing it leaves Safe Mode enabled and a persistent item remains on the Plugins screen. While active, every plugin is disabled and cannot be enabled until Safe Mode is turned off.