Tomacco

Docs Android

Android#

Build Android apps on Linux and run them on the real Android Emulator or on a phone. This covers native Kotlin and Java, Flutter, and Swift on Android.

On this page (32)

Tested on: an Android 16 (API 36) x86_64 emulator. Physical ARM64 phones and live Play submission have not been qualified.

From zero to a running app#

  1. Install the tools. You need the Android SDK command-line tools, a JDK 17 or newer and Gradle. The starter was verified with Gradle 9.3.1 and JDK 17. Unpack the command-line tools to SDK/cmdline-tools/latest, because that is where Tomacco looks for sdkmanager.
  2. Tell Tomacco where they are. The desktop has the same under SDK & tool paths.

    tomaccoctl call settings.configure '{"android_sdk":"/absolute/Android/Sdk","java_home":"/usr/lib/jvm/default","executables":{"gradle":"/usr/bin/gradle"}}'
    tomaccoctl doctor
    
  3. Install SDK packages. Package installation accepts licenses only when you say so. android.sdk_packages lists what is available.

    tomaccoctl call android.sdk_install '{"packages":["platform-tools","emulator","platforms;android-36","build-tools;36.0.0","system-images;android-36;google_apis;x86_64"],"accept_licenses":true}'
    
  4. Get a device. Create and start an emulator, or connect a phone with USB debugging authorized.

    tomaccoctl call android.avd_create '{"name":"First","system_image":"system-images;android-36;google_apis;x86_64","profile_id":"pixel-11"}'
    tomaccoctl call android.avd_start '{"name":"First"}'
    tomaccoctl call device.list '{"platform":"android"}'
    
  5. Create, build and run. Wait for each returned job with job.wait; see jobs.

    tomaccoctl call project.create '{"path":"/absolute/NewApp","kind":"android","name":"NewApp","organization":"dev.example"}'
    tomaccoctl call project.execute '{"path":"/absolute/NewApp","platform":"android","action":"build","format":"apk"}'
    tomaccoctl call project.run '{"path":"/absolute/NewApp","platform":"android","device":"emulator-5554"}'
    
  6. Look at it.

    tomacco --project /absolute/NewApp --workspace runtime
    

Projects#

project.inspect finds a Gradle project at the project root or in android/. A project's own Gradle wrapper takes precedence over the configured Gradle, and its own build configuration stays as it is. Flutter uses the installed Flutter CLI.

The starter has a native Android UI and a persistent counter. It uses the highest installed SDK platform, and the Android Gradle Plugin 9.1.1.

kind: "compose" creates the same counter in Kotlin with Jetpack Compose and Material 3, with a versions catalog in gradle/libs.versions.toml and one Compose UI test in app/src/androidTest. It needs Android platform 36. Run the test with project.execute and "action":"instrumented-test". A new project built, passed its UI test on an Android 16 emulator and ran with project.run.

Coverage and Gradle Managed Devices#

project.inspect lists test_setup for a Gradle build, read from the build files: per module the build types with enableUnitTestCoverage, enableAndroidTestCoverage or the older testCoverageEnabled, whether it applies Kover or the jacoco plugin, and its Gradle Managed Devices and device groups.

project.execute with "coverage":true adds the report tasks the build enables: for test createDebugUnitTestCoverageReport, koverXmlReportDebug (or koverXmlReport for a module without Android) and jacocoTestReport; for instrumented-test createDebugAndroidTestCoverageReport (or createDebugCoverageReport with testCoverageEnabled). A build that enables none answers coverage_not_configured with the line to add. testing.coverage then reads the reports; see the agent guide.

tomaccoctl call project.execute '{"path":"/absolute/MyApp","action":"test","coverage":true}'
tomaccoctl call project.execute '{"path":"/absolute/MyApp","action":"instrumented-test","coverage":true,"device":"emulator-5554"}'

managed_device runs the instrumented tests on a Gradle Managed Device the build files declare under testOptions { managedDevices { ... } }: a device's name runs NAMEDebugAndroidTest, a group's GROUPGroupDebugAndroidTest, and all allDevicesDebugAndroidTest. shards splits the tests across that many devices with the Android Gradle plugin's android.experimental.androidTest.numManagedDeviceShards. Gradle downloads the system image and starts the emulator itself; Tomacco does not manage those emulators. An unknown name answers managed_device_not_found with the names the build declares. coverage is refused with managed_device.

tomaccoctl call project.execute '{"path":"/absolute/MyApp","action":"instrumented-test","managed_device":"pixel2api30","shards":2}'

The managed device commands are unit-tested only; no Gradle Managed Device run has been made. Without one, Tomacco splits work across devices itself: project.execute runs the connected tests on the one device device names, and testing.run with shard:true splits UI scenarios across several running devices.

What ran: on a copy of examples/hello-android with enableUnitTestCoverage and enableAndroidTestCoverage in its debug build type, a unit test and an instrumented test, Android Gradle plugin 9.1.1, Gradle 9.3.1 and an API 36 x86_64 emulator, a test job and an instrumented-test job with coverage wrote both JaCoCo reports, and testing.coverage read and merged them. The Kover and jacoco plugin tasks are unit-tested only.

React Native, Expo and Capacitor#

project.inspect names the framework under javascript, with the package manager its lockfile implies, whether node_modules has the framework, and how project.run will run the Android app.

These runs are covered by tests with stand-in tools; no React Native, Expo or Capacitor app was built with them yet.

Flutter#

project.execute with "action":"test" runs flutter test --reporter expanded --file-reporter=json:build/tomacco/flutter_test.json: the log stays readable and the JSON reporter's file is kept as an artifact, even when tests fail. testing.results on the project reads it into the summary it gives for JUnit results, one suite per test file. A failed golden-file comparison is listed under golden_failures with the golden's name, flutter_test's message (such as Pixel test failed, 1.20%, 480px diff detected.) and the paths of the diff images it wrote to failures/: masterImage, testImage, isolatedDiff and maskedDiff.

tomaccoctl call project.execute '{"path":"/absolute/MyApp","action":"test"}'
tomaccoctl call testing.results '{"path":"/absolute/MyApp","job_id":"JOB_ID"}'

"action":"instrumented-test" with a device runs the project's integration_test directory on that device: flutter test integration_test -d SERIAL, its JSON reporter writing build/tomacco/integration_test.json, which testing.results reads the same way. Without device it answers device_required, and without an integration_test directory integration_test_missing. For a Gradle project, device sets ANDROID_SERIAL for the connected tests.

tomaccoctl call project.execute '{"path":"/absolute/MyApp","action":"instrumented-test","device":"emulator-5554"}'

Tested with a stand-in flutter that writes the JSON reporter's events; Flutter is not installed here.

A Flutter session from project.run (and the one project.watch keeps) runs flutter run --machine. While it runs, job.get shows under result.flutter what Flutter reported about it:

Flutter reports only what it serves, so a key can be missing. Without a DevTools URI, dart devtools opens DevTools, which asks for the vm_service_http_uri. The addresses are on this computer; for a device, Flutter forwards the port.

tomaccoctl call job.get '{"job_id":"RUN_JOB_ID"}'

Tested with a stand-in flutter that prints these events.

Kotlin Multiplatform and Compose Multiplatform#

project.inspect adds the kinds kotlin-multiplatform and, with the org.jetbrains.compose plugin, compose-multiplatform. multiplatform lists each module's targets (androidTarget, iosArm64, jvm and so on) and the Xcode project of the iOS app, such as iosApp/iosApp.xcodeproj.

Tested on fixture projects only; no Kotlin Multiplatform app was built here.

Dependency updates#

project.dependency_updates compares the versions a Gradle build declares with Maven metadata, without running Gradle or changing a file. It reads gradle/libs.versions.toml and the build files, asks Google Maven, Maven Central and the Gradle Plugin Portal in that order, and returns a job whose report.json lists every dependency and plugin with its source line, latest stable and latest version and a status: update_available, prerelease_update_available, up_to_date, managed (no version, such as libraries under a BOM), dynamic, unresolved ($variable), not_found or unreachable.

tomaccoctl call project.dependency_updates '{"path":"/absolute/NewApp"}' --wait

Without a network it answers offline at once. repositories replaces the defaults with your own https:// or file:/// repositories. It ran against the Compose starter online.

The same job checks the other package managers a project has, each entry marked with its ecosystem:

ecosystems in the report names each one checked, with its error when its tool is missing or failed; the job fails only when none could be checked. Online, npm outdated (npm 11.19) and git ls-remote against GitHub gave the expected entries for a small package.json and Package.resolved; the pub path ran only with a stand-in flutter.

Static analysis#

project.analyze runs the analysers a project already has and returns a job whose report.json lists every finding as an issue with tool, file (relative to the project), line, column, severity (error, warning or info), code and message, with totals per severity and passed (no errors, and every analyser ran).

tools limits the run to dart, eslint or tsc. Findings do not fail the job; it fails only when no analyser produced a result.

tomaccoctl call project.analyze '{"path":"/absolute/MyApp"}' --wait

Tested with stand-in analysers that print the formats above; no real Flutter or JavaScript project was analysed here.

Inspect an APK or AAB#

android.package_inspect reads a package file without a device: package name, version name and code, minimum, target and compile SDK, permissions, native ABIs and the signing certificates with their SHA-256 digests.

tomaccoctl call android.package_inspect '{"path":"/absolute/NewApp/app/build/outputs/apk/debug/app-debug.apk"}'

Size of an APK or AAB#

android.size_report breaks a package down by size without a device: the total, each category (dex, native, resources, assets, manifest, signature), each ABI, screen density and bundle module, and the largest files. summary.estimate.by_abi_density adds up what one device would get without other ABIs' libraries and other densities' resources; it uses the compressed sizes in the file, and Play recompresses, so treat it as an estimate. baseline compares with an earlier build: delta has the change of every total and the files that grew, shrank, appeared or went away the most.

tomaccoctl call android.size_report '{"path":"/absolute/app-release.aab","baseline":"/absolute/previous/app-release.aab"}'

Install an AAB#

device.install with one .aab runs Google's bundletool as a job: build-apks --connected-device builds the APK set for that device, and install-apks installs it. The APK set is deleted when the job ends.

tomaccoctl call settings.configure '{"executables":{"bundletool":"/absolute/bundletool-all.jar"}}'
tomaccoctl call device.install '{"platform":"android","device":"emulator-5554","paths":["/absolute/app-release.aab"]}'

See and control the device#

The desktop shows the device's own video stream, so the status bar, keyboard and animations are the real ones. This needs scrcpy 4.1, configured through settings.configure. Tomacco finds scrcpy-server beside the executable, in its ../share/scrcpy directory or in /usr/share/scrcpy, and runtime.start can name a matching server JAR with server. Other scrcpy wire versions are not accepted silently.

tomaccoctl call runtime.start '{"platform":"android","device":"emulator-5554"}'
tomacco --project /absolute/app --workspace runtime

In the desktop, tap and drag inside the picture, type with the keyboard, or use Home, Back, Rotate and Screenshot. The picture keeps its real aspect ratio, and the margins around it are not forwarded as touches. Unicode text goes through the device clipboard. To replace a whole field, use testing.setup and testing.action with fill.

For agents, the same device is reached by runtime.input, runtime.capture and runtime.hierarchy; see display sessions. Without a display session, use device.capture, device.logs, android.hierarchy, android.input, android.record, android.mirror, android.ports, android.permission, android.deep_link, android.performance, android.settings, android.display, android.notifications, android.app_files, android.emulator, android.sensors, android.app_links and android.network_capture.

android.input types ASCII text with adb shell input text. Other text, such as Grüße or emoji, cannot go that way, so Tomacco pastes it: it starts the configured scrcpy 4.1 server briefly without video, sets the device clipboard and presses paste into the focused field. This needs scrcpy configured and Android 7 (API 24) or newer, replaces what was on the device clipboard, and answers text_unsupported with a remedy when it cannot work.

android.screen saves the screen as a PNG at once, without a job, for previews that refresh; device.capture keeps evidence with a job. Checked on the API 36 x86_64 emulator.

android.record records the screen as a job, for up to 180 seconds; job.send with command stop ends it early, and the video is still pulled to path. The stop interrupts screenrecord on the device, which then finishes the file. On the API 36 x86_64 emulator a recording stopped after 6 seconds gave a playable H.264 MP4.

tomaccoctl call android.record '{"device":"emulator-5554","path":"/absolute/clip.mp4","seconds":180}'
tomaccoctl call job.send '{"job_id":"RECORD_JOB","command":"stop"}'

device.logs with bundle_id keeps only that app's lines. On Android 12 (API 31) and newer it filters by the app's user ID, so the capture can start before the app and continues across restarts. On older Android it follows the process ID of the running app, so launch the app first.

tomaccoctl call device.logs '{"platform":"android","device":"emulator-5554","bundle_id":"dev.example.newapp","duration":30}'

device.info reads a device's properties and, under health, what matters at a glance in one read-only call: Android version, SDK level and security patch, battery level, state, health and temperature, free and total storage on /data, screen size and density in pixels and dp (an override from android.display counts), and uptime in seconds. A part the device does not report, such as the battery of a device without one, is null. Read on the API 36 x86_64 emulator.

tomaccoctl call device.info '{"platform":"android","device":"emulator-5554"}'

device.label gives a device a nickname or pins it. device.list then shows the nickname as name, keeps the earlier name as original_name, sets pinned and lists pinned devices first. An emulator is named by its AVD, so the nickname stays when its serial changes (checked on the API 36 x86_64 emulator); avd:NAME names one that is off. An empty nickname or pinned:false removes the label, and a call with neither only reads.

tomaccoctl call device.label '{"device":"emulator-5554","nickname":"Small phone","pinned":true}'
tomaccoctl call device.label '{"device":"avd:Tomacco-pixel-11","nickname":""}'

Device settings for tests#

android.settings sets what changes an app's look and motion, so UI tests and screenshots start from a known state. Leave out what should stay as it is; the result reads every setting back. A value Android accepts but does not apply answers setting_not_applied with what it reads instead.

tomaccoctl call android.settings '{"device":"emulator-5554","dark_mode":"on","font_scale":1.3,"animations":false}'
tomaccoctl call android.settings '{"device":"emulator-5554","bundle_id":"dev.example.newapp","app_locale":"de-DE"}'
tomaccoctl call android.settings '{"device":"emulator-5554"}'

Store screenshots#

android.store_screenshots captures an installed app's store screenshots as one job: on each device, for each app language and for light and dark mode, every screen in order. A screen is reached by stopping and launching the app and running its steps, which are testing.run scenario steps; settle_ms waits before the capture.

tomaccoctl call android.store_screenshots '{"bundle_id":"dev.example.newapp","devices":["emulator-5554"],"locales":["en-US","de-DE"],"themes":["light","dark"],"screens":[{"name":"home"},{"name":"settings","steps":[{"action":"click","selector":{"text":"Settings"}}]}],"directory":"/absolute/screenshots"}'

Orientation, display size and density#

android.display locks the screen orientation and overrides the display size or density, for layouts on other screens without another emulator. The result reads all three back, and a value Android did not apply answers setting_not_applied.

tomaccoctl call android.display '{"device":"emulator-5554","orientation":"landscape"}'
tomaccoctl call android.display '{"device":"emulator-5554","width":720,"height":1600,"density":320}'
tomaccoctl call android.display '{"device":"emulator-5554","reset":true}'

Notifications#

android.notifications lists what Android is showing, so a test can check that its app posted the right notification. Each entry has the key, package, ID, tag, channel, flags and, when set, title, text, sub-text and big text. bundle_id keeps one app's.

tomaccoctl call android.notifications '{"device":"emulator-5554","bundle_id":"dev.example.newapp"}'
tomaccoctl call android.notifications '{"device":"emulator-5554","action":"open"}'
tomaccoctl call android.notifications '{"device":"emulator-5554","action":"snooze","key":"0|dev.example.newapp|7|null|10200","seconds":600}'

App data files#

android.app_files lists or pulls the private files of a debuggable app, such as its databases and shared preferences, through run-as. Android allows that only for debuggable builds, so a release build answers app_not_debuggable. Paths are relative to the app's data directory.

tomaccoctl call android.app_files '{"device":"emulator-5554","bundle_id":"dev.example.newapp"}'
tomaccoctl call android.app_files '{"device":"emulator-5554","bundle_id":"dev.example.newapp","action":"pull","path":"databases","output":"/absolute/out/databases.tar"}'

android.app_links checks an app's Android App Links: which web domains it asks Android to verify, whether they are verified, and which app a link opens.

tomaccoctl call android.app_links '{"device":"emulator-5554","bundle_id":"dev.example.newapp"}'
tomaccoctl call android.app_links '{"device":"emulator-5554","bundle_id":"dev.example.newapp","action":"verify","seconds":30}'
tomaccoctl call android.app_links '{"device":"emulator-5554","bundle_id":"dev.example.newapp","action":"resolve","url":"https://example.com/item/1"}'
tomaccoctl call android.app_links '{"device":"emulator-5554","bundle_id":"dev.example.newapp","action":"set","state":"approved"}'
tomaccoctl call android.deep_link '{"device":"emulator-5554","url":"https://example.com/item/1"}'

Network capture#

android.network_capture records an emulator's network traffic, or only what one app sends, and summarises it. It needs opt_in:true, because the capture holds every packet, contents included.

tomaccoctl call android.network_capture '{"device":"emulator-5554","action":"start","opt_in":true,"bundle_id":"dev.example.newapp"}'
tomaccoctl call android.network_capture '{"device":"emulator-5554","action":"stop","path":"/absolute/newapp.pcap"}'
tomaccoctl call android.network_capture '{"action":"summary","path":"/absolute/newapp.pcap"}'

Sound#

On Android 11 and newer, output sound is forwarded to the computer by default when a native session connects. The Sound button (Ton) switches this without restarting the app, emulator or video. The desktop remembers the choice. Agents pass audio:false to runtime.start, or control a session:

tomaccoctl call runtime.audio '{"session":"SESSION_ID","action":"status"}'
tomaccoctl call runtime.audio '{"session":"SESSION_ID","action":"start"}'
tomaccoctl call runtime.audio '{"session":"SESSION_ID","action":"stop"}'

Emulators#

android.avd_create takes an installed system image and a device profile from catalog.list. A profile sets the display's resolution and density, not the vendor's firmware. android.avd_profile refuses to change a running emulator. Tomacco keeps its emulators in its own registry.

android.avd_configure stores gpu, vulkan, memory_mb and cores for a stopped emulator. It keeps app data and backs up the old configuration. Changed hardware cold-boots.

tomaccoctl call android.avd_configure '{"name":"First","gpu":"host","vulkan":"off","memory_mb":4096,"cores":4}'
tomaccoctl call android.avd_start '{"name":"First","headless":true}'

android.avd_start can override gpu and vulkan for one run. Choosing the software GPU keeps CPU acceleration; the older software flag turns that off too. Use the host renderer only after checking its picture and stability on your machine. An AVD with the default auto renderer that nobody chose starts with swiftshader where doctor finds no hardware graphics driver; a start with a window needs X11 (DISPLAY) and is refused with emulator_no_display without it. On a computer with less than 10 GiB of memory or at most four processors, new emulators get less of both. Linux distributions and hardware has the details.

The desktop's Omarchy panel can boot an emulator without its own window and show just the phone; see the desktop guide.

Snapshots, location, battery and network#

android.emulator drives a running emulator through its console with adb emu. Snapshots keep the whole running state, so a test can return to a logged-in screen in seconds instead of booting.

tomaccoctl call android.emulator '{"device":"emulator-5554","action":"snapshot-save","name":"logged-in"}'
tomaccoctl call android.emulator '{"device":"emulator-5554","action":"snapshot-list"}'
tomaccoctl call android.emulator '{"device":"emulator-5554","action":"snapshot-load","name":"logged-in"}'
tomaccoctl call android.emulator '{"device":"emulator-5554","action":"snapshot-delete","name":"logged-in"}'
tomaccoctl call android.emulator '{"device":"emulator-5554","action":"location","latitude":48.2082,"longitude":16.3738}'
tomaccoctl call android.emulator '{"device":"emulator-5554","action":"battery","battery_level":15,"charging":false}'
tomaccoctl call android.emulator '{"device":"emulator-5554","action":"network","network_speed":"edge","network_delay":"gprs"}'

Quick Boot#

A Tomacco-managed emulator resumes where it stopped instead of booting. android.emulator with action stop first saves its Quick Boot snapshot, and the next android.avd_start loads it. The start's result says how under boot: {"mode":"quick","snapshot":"default_boot"}, or cold with the reason.

tomaccoctl call android.emulator '{"device":"emulator-5554","action":"stop"}'
tomaccoctl call android.avd_start '{"name":"First","headless":true}'
tomaccoctl call android.avd_start '{"name":"First","headless":true,"cold_boot":true}'

Camera image#

camera_image makes the emulator's camera show a PNG or JPEG instead of its scene, for apps that scan QR codes, barcodes or documents. camera_facing picks back (the default) or front.

tomaccoctl call android.avd_start '{"name":"First","camera_image":"/absolute/qr-code.png"}'

Foldables, tablets, TV and Wear#

android.avd_create with a foldable profile, such as pixel-11-pro-fold, galaxy-z-fold8 or galaxy-z-fold8-ultra, gives the AVD a hinge: the display is the open inner screen and the folded outer screen is its first display region, as in the SDK's own foldable definitions. android.sensors then changes its posture. Tablet profiles, such as galaxy-tab-s12-plus, set the tablet's resolution and density on a phone image.

tomaccoctl call android.avd_create '{"name":"Fold","system_image":"system-images;android-36;google_apis;x86_64","profile_id":"pixel-11-pro-fold"}'
tomaccoctl call android.sensors '{"device":"emulator-5554","action":"posture","posture":"half-open"}'

Sensors and fingerprint#

android.sensors sets the emulator's simulated sensors through its console.

tomaccoctl call android.sensors '{"device":"emulator-5554","action":"status"}'
tomaccoctl call android.sensors '{"device":"emulator-5554","action":"orientation","orientation":"landscape"}'
tomaccoctl call android.sensors '{"device":"emulator-5554","action":"shake"}'
tomaccoctl call android.sensors '{"device":"emulator-5554","action":"proximity","near":true}'
tomaccoctl call android.sensors '{"device":"emulator-5554","action":"light","lux":20000}'
tomaccoctl call android.sensors '{"device":"emulator-5554","action":"set","sensor":"pressure","values":[990]}'
tomaccoctl call android.sensors '{"device":"emulator-5554","action":"fingerprint","finger_id":1}'

Every device at once#

android.each runs the same steps on every connected Android device, or on the devices you list, as one job. Devices run at the same time; on each device the steps run in order and stop at the first failure. A step that returns a job, such as an install, is followed to its end.

tomaccoctl call android.each '{"steps":[{"operation":"device.install","arguments":{"paths":["/absolute/app-debug.apk"]}},{"operation":"android.launch","arguments":{"bundle_id":"dev.example.newapp"}}]}'

Phones#

Enable USB debugging and authorize the computer. Everything else works the same as on the emulator.

Wireless debugging#

Android 11 and newer pair over Wi-Fi by QR code. android.pair makes a one-time code for the phone's Developer options › Wireless debugging › Pair device with QR code; wait then pairs and connects as soon as the phone offers it on the local network (adb's mDNS discovery). Nothing is stored; the password lives only in the code.

tomaccoctl call android.pair '{"path":"/absolute/pair.png"}'
tomaccoctl call android.pair '{"action":"wait","service":"tomacco-…","password":"…","timeout":60}'

Swift on Android#

Swift compiles to native Android code. SwiftUI and UIKit do not come along: they are Apple's UI frameworks. Tomacco's starter therefore has native Android widgets and a Swift core linked through JNI.

The tested toolchain is Swift 6.4, its matching Android SDK and NDK 30. Follow the official Swift setup for the downloads. swift.sdk_install requires a checksum for an HTTPS download, and swift.status reports the selected SDK and whether its sysroot is ready.

tomaccoctl call settings.configure '{"android_ndk":"/absolute/Android/Sdk/ndk/30.0.16248370","swift_android_sdk":"swift-6.4.0-RELEASE_android"}'
tomaccoctl call swift.android_configure '{}'
tomaccoctl call project.create '{"path":"/absolute/SwiftCounter","kind":"swift-android","name":"SwiftCounter","organization":"dev.example"}'
tomaccoctl call project.execute '{"path":"/absolute/SwiftCounter","platform":"android","action":"build"}'
tomaccoctl call swift.execute '{"path":"/absolute/SwiftCounter/Swift","platform":"linux","action":"test"}'
tomaccoctl call project.run '{"path":"/absolute/SwiftCounter","platform":"android","device":"emulator-5554"}'

Wait for swift.android_configure before building. It runs the SDK's own script to link the NDK; repeat it after moving either one.

App performance#

profile.startup measures cold, warm or hot launch times over several runs with their median and p90. profile.frames counts janky, slow and frozen frames and the frame time percentiles while you use the app or a UI test scenario runs. profile.memory reads PSS, RSS and heap sizes and can pull a heap dump. profile.usage measures CPU time and estimated battery use over an interval. profile.budget checks the results against limits, and UI test scenarios can measure and check them too. profile.compare shows how each result changed from a saved baseline. Macrobenchmark modules and Baseline Profiles have their own operations: profile.macrobenchmark, profile.benchmark_results, profile.baseline_profile, profile.baseline_rules, profile.baseline_install and profile.baseline_compare, and project.create can add the module to a Compose starter. See app performance in the agent guide.

Debugging, tests, profiling and release#

These are the same for agents and the desktop. See the sections in the agent guide:

View this page as Markdown on GitHub