Skip to content

EthereumPhone/TerminalSDK

Repository files navigation

ethOS Terminal SDK

Android library for controlling the ethOS terminal screen and the 3x3 LED array via a clean, coroutine-based Kotlin API. Both subsystems communicate with hidden system services through reflection and gracefully no-op on non-ethOS hardware.

Table of Contents


Installation

Step 1. Add the JitPack repository to your project's settings.gradle.kts (inside the dependencyResolutionManagement block):

dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
    repositories {
        google()
        mavenCentral()
        maven { url = uri("https://jitpack.io") }
    }
}

If your project uses a root build.gradle.kts with allprojects { repositories { ... } } instead, add the maven line there.

Step 2. Add the dependency to your app module's build.gradle.kts:

dependencies {
    implementation("com.github.EthereumPhone:TerminalSDK:0.1.0")
}

Running from source: If you've cloned or forked this repo, include the SDK module directly in settings.gradle.kts:

include(":TerminalSDK")

Then reference it from your app module:

dependencies {
    implementation(project(":TerminalSDK"))
}

Quick Start

class MainActivity : ComponentActivity() {

    private var terminal: TerminalSDK? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // Initialise — safe on non-ethOS devices (display/led will be null)
        terminal = TerminalSDK(this)

        // Show a terminal button on the terminal screen
        lifecycleScope.launch {
            terminal?.showQrOrSend(
                onQrCode = { /* user tapped the QR area */ },
                onSend   = { /* user tapped the Send area */ }
            )
        }

        // Flash the success LED pattern
        terminal?.led?.flashSuccess()
    }

    override fun onPause() {
        super.onPause()
        // Restore the default terminal screen
        lifecycleScope.launch { terminal?.dismissDisplay() }
    }

    override fun onDestroy() {
        super.onDestroy()
        // Release all resources
        terminal?.destroy()
    }
}

Terminal Buttons

The terminal screen is a 428x142 pixel display on the back of the ethOS device. The SDK ships with pre-built button layouts that render as bitmaps and register touch handlers automatically.

Dual-Button Layouts

// QR Scan / Send TXN — left half triggers onQrCode, right half triggers onSend
terminal?.showQrOrSend(
    onQrCode = { /* open QR scanner */ },
    onSend   = { /* open send flow */ }
)

// QR Scan / Send NFT — same split, right half triggers onSendNft
terminal?.showSendNft(
    onQrCode  = { /* open QR scanner */ },
    onSendNft = { /* open NFT send flow */ }
)

Single-Button Layouts

Each renders a full-width button and fires the callback on any tap:

terminal?.showCopy { /* copy address to clipboard */ }

terminal?.showCopiedAddress { /* show copied confirmation */ }

terminal?.showTopUp { /* open top-up flow */ }

terminal?.showSwap { /* open swap flow */ }

terminal?.showLog { /* open block explorer */ }

terminal?.showDetailLog { /* open TX detail in explorer */ }

Status Text

Display arbitrary text on a black background (no touch handler):

terminal?.showBlackText("SENDING TX...")
terminal?.showBlackText("CONFIRMING...")

Dismiss

Restore the default system display (status bar) and remove touch handlers:

terminal?.dismissDisplay()

All show* methods are suspend functions. Call them from a coroutine scope (e.g. lifecycleScope.launch { ... }).


LED Patterns

The 3x3 LED array on the back of the device supports a library of built-in patterns. Each accepts an optional hex colour string — when omitted, the system accent colour is used.

Displaying Patterns

val led = terminal?.led

// Branding
led?.displayChad()                    // ethOS logo, system accent colour
led?.displayChad("#FF00FF")           // ethOS logo, custom magenta

// Directional
led?.displayPlus()                    // + symbol
led?.displayMinus()                   // − symbol
led?.displaySend()                    // arrow up ↑
led?.displayReceive()                 // arrow down ↓
led?.displaySwap()                    // swap indicator

// Status feedback
led?.displaySuccess()                 // green checkmark ✓
led?.displayError()                   // red cross ✗
led?.displayWarning()                 // yellow warning ⚠
led?.displayInfo()                    // info indicator ℹ

// Signing
led?.displaySign()                    // signing indicator

// Clear all LEDs
led?.clear()

Display by Name

You can also display a pattern dynamically by its string name:

led?.displayPattern("success", "#00FF00")
led?.displayPattern("chad")

// Available names:
// "chad", "plus", "minus", "success", "error", "warning",
// "info", "arrowup", "arrowdown", "swap", "sign"

List Available Patterns

val patterns: List<String> = led?.getAvailablePatterns() ?: emptyList()
// ["chad", "plus", "minus", "success", "error", "warning",
//  "info", "arrowup", "arrowdown", "swap", "sign"]

Flash Patterns

Flash patterns display a status indicator briefly (default 1 second), then automatically revert to the chad branding pattern. Useful for confirming actions like successful transactions or errors.

// Flash success for 1 second, then show chad
led?.flashSuccess()

// Flash error for 2 seconds with a custom colour
led?.flashError(color = "#FF0000", durationMs = 2000L)

// All flash variants:
led?.flashSuccess(color = null, durationMs = 1000L)
led?.flashError(color = null, durationMs = 1000L)
led?.flashWarning(color = null, durationMs = 1000L)
led?.flashInfo(color = null, durationMs = 1000L)

Practical Example: Transaction Flow

// 1. Show "sending" on the terminal screen + send pattern on LEDs
terminal?.showBlackText("SENDING TX...")
led?.displaySend()

// 2. Wait for the transaction result...
val result = sendTransaction(...)

// 3. Flash success or error based on outcome
if (result.isSuccess) {
    led?.flashSuccess()
    terminal?.showBlackText("TX CONFIRMED")
} else {
    led?.flashError()
    terminal?.showBlackText("TX FAILED")
}

// 4. After a delay, return to normal
delay(2000)
terminal?.dismissDisplay()

Custom LED Grid

Beyond predefined patterns, you have full control over individual LEDs in the 3x3 grid.

Grid Layout

Hardware IDs:          Grid coordinates:
  0   1   2            [0,0] [0,1] [0,2]
  3   4   5            [1,0] [1,1] [1,2]
  6   7   8            [2,0] [2,1] [2,2]

Set a Single LED

// Set LED at row 0, column 1 to green
led?.setColor(0, 1, "#00FF00")

// Set LED with brightness (0–8)
led?.setColor(1, 1, "#FF0000", brightness = 4)

Set All LEDs to One Colour

// All LEDs blue at default brightness (6)
led?.setAllColor("#0000FF")

// All LEDs white at half brightness
led?.setAllColor("#FFFFFF", brightness = 3)

Custom 3x3 Pattern

Pass a 3x3 array of hex colour strings. Use "#000000" for off:

// X pattern
led?.setCustomPattern(arrayOf(
    arrayOf("#FF0000", "#000000", "#FF0000"),
    arrayOf("#000000", "#FF0000", "#000000"),
    arrayOf("#FF0000", "#000000", "#FF0000"),
))

// Diamond pattern with brightness
led?.setCustomPattern(
    pattern = arrayOf(
        arrayOf("#000000", "#00FFFF", "#000000"),
        arrayOf("#00FFFF", "#000000", "#00FFFF"),
        arrayOf("#000000", "#00FFFF", "#000000"),
    ),
    brightness = 8
)

Adjust Brightness

// Global brightness (0–8)
led?.setBrightness(5)

// Adjust colour brightness programmatically (0–100%)
val dimRed = led?.applyBrightness("#FF0000", 50)  // returns "0x7F0000"

Colour Format

The SDK accepts colours in any of these formats — they're normalised internally:

Format Example
#RRGGBB "#FF0000"
#AARRGGBB "#FFFF0000"
0xRRGGBB "0xFF0000"
0xAARRGGBB "0xFFFF0000"

Building Custom Terminal Screens

The SDK ships with pre-built button layouts, but you can design your own terminal screen with custom buttons, icons, text, and touch regions. The terminal screen is 428x142 pixels — everything you show is an XML layout rendered into a bitmap.

How It Works

  1. Create an XML layout sized to 428x142 px
  2. Render it into a Bitmap using LayoutRenderer (or manually)
  3. Push the bitmap to the terminal screen with showOnDisplay()
  4. Register a touch handler to make regions interactive

Step 1: Create Your XML Layout

Create a layout file in your app's res/layout/ directory. The root must be 428px wide and 142px high with a black background to match the terminal screen:

<!-- res/layout/my_custom_terminal.xml -->
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="428px"
    android:layout_height="142px"
    android:background="#000000">

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <!-- Left button: APPROVE -->
        <ImageView
            android:id="@+id/approve_icon"
            android:layout_width="16dp"
            android:layout_height="16dp"
            android:src="@drawable/ic_check"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toStartOf="@+id/approve_label"
            app:layout_constraintHorizontal_chainStyle="packed"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintHorizontal_bias="0.25" />

        <TextView
            android:id="@+id/approve_label"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginStart="8dp"
            android:text="APPROVE"
            android:textColor="#00FF00"
            android:fontFamily="@font/monomaniac"
            android:textSize="17sp"
            android:textAllCaps="true"
            android:letterSpacing="0.12"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintStart_toEndOf="@+id/approve_icon"
            app:layout_constraintTop_toTopOf="parent" />

        <!-- Right button: REJECT -->
        <ImageView
            android:id="@+id/reject_icon"
            android:layout_width="16dp"
            android:layout_height="16dp"
            android:src="@drawable/ic_close"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toStartOf="@+id/reject_label"
            app:layout_constraintStart_toEndOf="@+id/approve_label"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintHorizontal_bias="0.75" />

        <TextView
            android:id="@+id/reject_label"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginStart="8dp"
            android:text="REJECT"
            android:textColor="#FF0000"
            android:fontFamily="@font/monomaniac"
            android:textSize="17sp"
            android:textAllCaps="true"
            android:letterSpacing="0.12"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toEndOf="@+id/reject_icon"
            app:layout_constraintTop_toTopOf="parent" />

    </androidx.constraintlayout.widget.ConstraintLayout>
</FrameLayout>

Design tips:

  • Always use a #000000 background (OLED-friendly, matches the device)
  • Use the included @font/monomaniac font for the authentic terminal aesthetic, or apply the @style/terminal_text style
  • Keep text uppercase with letter spacing for readability at small sizes
  • Use the system accent colour for icons/labels (see Step 2)
  • The ConstraintLayout dependency is already included in the SDK

Step 2: Render the Layout to a Bitmap

Inflate your layout, apply the system accent colour, and convert it to a bitmap:

fun renderCustomLayout(context: Context): Bitmap {
    val inflater = LayoutInflater.from(context)
    val view = inflater.inflate(R.layout.my_custom_terminal, null)

    // Apply the system accent colour to icons and labels
    val accentColor = Settings.Secure.getInt(
        context.contentResolver,
        "systemui_accent_color",
        0xFFFF0000.toInt()  // default red
    )

    view.findViewById<ImageView>(R.id.approve_icon)
        ?.setColorFilter(accentColor, PorterDuff.Mode.SRC_IN)
    view.findViewById<TextView>(R.id.approve_label)
        ?.setTextColor(accentColor)
    view.findViewById<ImageView>(R.id.reject_icon)
        ?.setColorFilter(accentColor, PorterDuff.Mode.SRC_IN)
    view.findViewById<TextView>(R.id.reject_label)
        ?.setTextColor(accentColor)

    // Measure and layout at the terminal screen resolution
    val width = 428
    val height = 142
    val wSpec = View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY)
    val hSpec = View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY)
    view.measure(wSpec, hSpec)
    view.layout(0, 0, width, height)

    // Draw into a bitmap
    val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565)
    val canvas = Canvas(bitmap)
    view.draw(canvas)
    return bitmap
}

Shortcut: You can also use the SDK's built-in LayoutRenderer as a reference or extend it. The renderer in terminal.renderer already handles inflation, measuring, accent colouring, and bitmap conversion for the built-in layouts.

Step 3: Display It with Touch Handling

Push the bitmap to the terminal screen and register touch zones:

lifecycleScope.launch {
    val bitmap = renderCustomLayout(context)

    // Display the custom screen with a split touch handler
    terminal?.showOnDisplay(bitmap, MiniDisplayTouchHandler.OnTouchListener { x, y, action ->
        if (action != MotionEvent.ACTION_DOWN) return@OnTouchListener

        // Split the 428px width into left/right tap zones
        if (x < 214f) {
            // Left half — APPROVE tapped
            handleApprove()
        } else {
            // Right half — REJECT tapped
            handleReject()
        }
    })
}

Step 4: Clean Up

Always dismiss your custom screen when leaving:

// Restore the default terminal screen
terminal?.dismissDisplay()

Full Example: Custom Approve/Reject Screen

Putting it all together in a ViewModel-driven flow:

class SigningViewModel(
    private val terminal: TerminalSDK?,
    private val context: Context
) : ViewModel() {

    fun showApprovalScreen(onApprove: () -> Unit, onReject: () -> Unit) {
        viewModelScope.launch {
            val bitmap = renderCustomLayout(context)

            terminal?.showOnDisplay(bitmap, MiniDisplayTouchHandler.OnTouchListener { x, _, action ->
                if (action != MotionEvent.ACTION_DOWN) return@OnTouchListener
                if (x < 214f) onApprove() else onReject()
            })

            // Show the sign LED pattern while waiting for user input
            terminal?.led?.displaySign()
        }
    }

    fun dismissApprovalScreen() {
        viewModelScope.launch {
            terminal?.dismissDisplay()
            terminal?.led?.displayChad()
        }
    }
}

Advanced: Three-Zone Touch Layout

You're not limited to a left/right split. Divide the 428px width into any number of zones:

terminal?.showOnDisplay(bitmap, MiniDisplayTouchHandler.OnTouchListener { x, _, action ->
    if (action != MotionEvent.ACTION_DOWN) return@OnTouchListener

    when {
        x < 143f  -> handleLeftButton()    // first third
        x < 286f  -> handleMiddleButton()  // middle third
        else      -> handleRightButton()   // last third
    }
})

Advanced: Programmatic Bitmap (No XML)

If you prefer to skip XML entirely, draw directly onto a Canvas:

fun renderProgrammatic(): Bitmap {
    val bitmap = Bitmap.createBitmap(428, 142, Bitmap.Config.RGB_565)
    val canvas = Canvas(bitmap)
    canvas.drawColor(Color.BLACK)

    val paint = Paint().apply {
        color = Color.GREEN
        textSize = 36f
        isAntiAlias = true
        typeface = Typeface.MONOSPACE
        textAlign = Paint.Align.CENTER
    }

    canvas.drawText("CONNECTED", 214f, 82f, paint)
    return bitmap
}

Custom Bitmaps

For any fully custom content, render any Bitmap to the terminal screen:

val bitmap: Bitmap = ... // your 428x142 bitmap

// Display with no touch handler
terminal?.showOnDisplay(bitmap)

// Display with a touch handler
terminal?.showOnDisplay(bitmap, MiniDisplayTouchHandler.OnTouchListener { x, y, action ->
    if (action == MotionEvent.ACTION_DOWN) {
        if (x < 214f) {
            // Left half tapped
        } else {
            // Right half tapped
        }
    }
})

You can also use the built-in LayoutRenderer to render the SDK's pre-built layouts manually:

val renderer = terminal?.renderer

val bitmap = renderer?.renderQrOrSend()
val bitmap = renderer?.renderBlackText("CUSTOM MESSAGE")

Terminal Screen Power Control

Control the terminal screen power state directly:

// Turn the terminal screen on/off
terminal?.display?.screenOn()
terminal?.display?.screenOff()

// Check current state
val isOn: Boolean = terminal?.display?.isScreenOn() ?: false

Lifecycle Management

Proper lifecycle management prevents resource leaks and ensures the terminal screen returns to its default state when your app is backgrounded or destroyed.

Activity Lifecycle

class MyActivity : ComponentActivity() {

    private var terminal: TerminalSDK? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        terminal = TerminalSDK(this)
    }

    override fun onPause() {
        super.onPause()
        // Restore the system display when app loses focus
        lifecycleScope.launch {
            terminal?.dismissDisplay()
        }
    }

    override fun onDestroy() {
        super.onDestroy()
        // Release all resources — clears LEDs, cancels coroutines,
        // destroys touch handlers
        terminal?.destroy()
    }
}

Jetpack Compose Lifecycle

Use DisposableEffect tied to the composable lifecycle:

@Composable
fun TerminalAwareScreen(terminal: TerminalSDK?) {
    val scope = rememberCoroutineScope()
    val lifecycleOwner = LocalLifecycleOwner.current

    DisposableEffect(lifecycleOwner) {
        val observer = LifecycleEventObserver { _, event ->
            when (event) {
                Lifecycle.Event.ON_RESUME -> {
                    scope.launch { terminal?.showCopy { /* handle tap */ } }
                    terminal?.led?.displayChad()
                }
                Lifecycle.Event.ON_PAUSE -> {
                    scope.launch { terminal?.dismissDisplay() }
                }
                else -> {}
            }
        }
        lifecycleOwner.lifecycle.addObserver(observer)
        onDispose {
            lifecycleOwner.lifecycle.removeObserver(observer)
        }
    }
}

ViewModel Scoped Usage

If you use the SDK from a ViewModel, clean up in onCleared():

class MyViewModel(
    private val terminal: TerminalSDK?
) : ViewModel() {

    fun showSendButton() {
        viewModelScope.launch {
            terminal?.showQrOrSend(
                onQrCode = { /* ... */ },
                onSend   = { /* ... */ }
            )
        }
    }

    override fun onCleared() {
        super.onCleared()
        terminal?.destroy()
    }
}

Architecture Patterns for Production Apps

For larger apps (like the ethOS Wallet Manager), structure your terminal interactions through a repository layer with dependency injection.

Repository Pattern

Wrap the SDK in a repository to decouple UI from hardware control and emit touch events as flows:

sealed interface TerminalEvent {
    object CopyTapped : TerminalEvent
    object QrTapped : TerminalEvent
    object SendTapped : TerminalEvent
    object SwapTapped : TerminalEvent
    object TopUpTapped : TerminalEvent
    object LogTapped : TerminalEvent
}

@Singleton
class TerminalRepository @Inject constructor(
    private val terminal: TerminalSDK?,
    @ApplicationContext private val context: Context
) {
    private val _events = MutableSharedFlow<TerminalEvent>()
    val events: SharedFlow<TerminalEvent> = _events

    private val scope = CoroutineScope(Dispatchers.IO)

    suspend fun showSendButtons() {
        terminal?.showQrOrSend(
            onQrCode = { scope.launch { _events.emit(TerminalEvent.QrTapped) } },
            onSend   = { scope.launch { _events.emit(TerminalEvent.SendTapped) } }
        )
    }

    suspend fun showCopy() {
        terminal?.showCopy {
            scope.launch { _events.emit(TerminalEvent.CopyTapped) }
        }
    }

    suspend fun dismiss() {
        terminal?.dismissDisplay()
    }
}

Dependency Injection with Hilt

Provide the SDK as a nullable singleton — it will be null on non-ethOS devices:

@Module
@InstallIn(SingletonComponent::class)
object TerminalModule {

    @Provides
    @Singleton
    fun provideTerminalSDK(
        @ApplicationContext context: Context
    ): TerminalSDK? {
        return try {
            val sdk = TerminalSDK(context)
            if (sdk.isAvailable) sdk else null
        } catch (e: Exception) {
            null
        }
    }
}

Collecting Events in a ViewModel

@HiltViewModel
class SendViewModel @Inject constructor(
    private val terminalRepository: TerminalRepository
) : ViewModel() {

    init {
        // React to terminal touch events
        viewModelScope.launch {
            terminalRepository.events.collect { event ->
                when (event) {
                    TerminalEvent.QrTapped  -> openQrScanner()
                    TerminalEvent.SendTapped -> navigateToAmountInput()
                    else -> {}
                }
            }
        }
    }

    fun onScreenVisible() {
        viewModelScope.launch {
            terminalRepository.showSendButtons()
        }
    }

    fun onScreenHidden() {
        viewModelScope.launch {
            terminalRepository.dismiss()
        }
    }
}

Screen-Aware Terminal Updates

Update the terminal screen as the user navigates between screens:

@Composable
fun SendScreen(
    terminal: TerminalSDK?,
    viewModel: SendViewModel = hiltViewModel()
) {
    val scope = rememberCoroutineScope()
    val lifecycleOwner = LocalLifecycleOwner.current

    // Update terminal screen when this screen gains/loses focus
    DisposableEffect(lifecycleOwner) {
        val observer = LifecycleEventObserver { _, event ->
            when (event) {
                Lifecycle.Event.ON_RESUME -> viewModel.onScreenVisible()
                Lifecycle.Event.ON_PAUSE  -> viewModel.onScreenHidden()
                else -> {}
            }
        }
        lifecycleOwner.lifecycle.addObserver(observer)
        onDispose {
            lifecycleOwner.lifecycle.removeObserver(observer)
        }
    }

    // ... your UI ...
}

API Reference

TerminalSDK

Property / Method Description
display: TerminalDisplay? Terminal screen controller (null if unavailable)
led: TerminalLED? LED array controller (null if unavailable)
renderer: LayoutRenderer Renders XML layouts to bitmaps for the terminal screen
isAvailable: Boolean true if either display or LED is available
isDisplayAvailable: Boolean true if the terminal screen is available
isLedAvailable: Boolean true if the LED array is available
showQrOrSend(onQrCode, onSend) Show QR/Send dual-button (suspend)
showSendNft(onQrCode, onSendNft) Show QR/Send NFT dual-button (suspend)
showCopy(onCopy) Show Copy button (suspend)
showCopiedAddress(onTap) Show Copied confirmation (suspend)
showTopUp(onTopUp) Show Top Up button (suspend)
showLog(onLog) Show View on Explorer button (suspend)
showDetailLog(onDetailLog) Show View TX in Explorer button (suspend)
showSwap(onSwap) Show Swap button (suspend)
showBlackText(text) Show text on black background (suspend)
showOnDisplay(bitmap, touchListener?) Push any bitmap to the terminal screen (suspend)
dismissDisplay() Restore default terminal screen, remove touch handlers (suspend)
destroy() Release all resources — call in onDestroy()

TerminalLED

Method Description
isAvailable: Boolean Whether the LED subsystem is available
displayChad(color?) Show ethOS branding pattern
displayPlus(color?) / displayMinus(color?) Show +/− pattern
displaySend(color?) / displayReceive(color?) Show arrow up/down pattern
displaySwap(color?) / displaySign(color?) Show swap/sign pattern
displaySuccess(color?) Show success checkmark
displayError(color?) Show error cross
displayWarning(color?) Show warning symbol
displayInfo(color?) Show info symbol
displayPattern(name, color?) Show pattern by string name
flashSuccess(color?, durationMs?) Flash success then revert to chad
flashError(color?, durationMs?) Flash error then revert to chad
flashWarning(color?, durationMs?) Flash warning then revert to chad
flashInfo(color?, durationMs?) Flash info then revert to chad
setColor(row, col, color) Set single LED colour (row/col 0-2)
setColor(row, col, color, brightness) Set single LED with brightness (0-8)
setAllColor(color, brightness?) Set all LEDs to one colour
setCustomPattern(pattern, brightness?) Set arbitrary 3x3 colour pattern
setBrightness(brightness) Set global brightness (0-8)
applyBrightness(hexColor, brightnessPercent) Adjust colour brightness (0-100%)
getAvailablePatterns(): List<String> List all predefined pattern names
getSystemColor(): String? Get cached system accent colour
refreshSystemColor() Re-read the system accent colour
clear() Turn off all LEDs
destroy() Clear LEDs and cancel pending coroutines

TerminalDisplay

Method Description
isAvailable(): Boolean Whether the terminal screen is available (suspend)
isScreenOn(): Boolean Check if terminal screen is on (suspend)
screenOn() / screenOff() Power the terminal screen on/off (suspend)
refresh(bitmap, layerId): Boolean Push a bitmap to a display layer (suspend)
resume(layerId) Restore a display layer (suspend)
registerTouchListener(listener) Register a touch callback
destroyTouchHandler() Remove the current touch callback (suspend)
destroyTouchHandlerSync() Remove touch callback synchronously
finish() Restore status bar + destroy touch handler

Display Layer Constants

Constant Description
ID_STATUSBAR System status bar layer
ID_INCOMINGCALL Incoming call layer
ID_NOTIFICATIONS Notification layer
ID_CLOCK Clock layer
ID_GOOGLEBYE Always-on display (AOD) layer
ID_PERSISTENT Persistent layer (stays until replaced)

Requirements

  • Android API 33+ (minSdk)
  • ethOS device for hardware features — the SDK gracefully no-ops on standard Android devices
  • Kotlin Coroutines — display methods are suspend functions

Contributing

  1. Fork or clone the repository
  2. Open the project in Android Studio
  3. The app module is a demo app that exercises every SDK feature — run it on an ethOS device to test
  4. The TerminalSDK module is the library — make your changes there
  5. Submit a pull request

Project Structure

TerminalSDK/
├── app/                          # Demo / sample application
│   └── src/main/java/.../
│       └── MainActivity.kt       # Full interactive demo
├── TerminalSDK/                  # Library module
│   └── src/main/java/.../
│       ├── TerminalSDK.kt        # Main entry point
│       ├── display/
│       │   ├── TerminalDisplay.kt      # Terminal screen controller
│       │   ├── LayoutRenderer.java     # XML → Bitmap renderer
│       │   └── MiniDisplayTouchHandler.java  # Touch event handling
│       └── led/
│           ├── TerminalLED.kt    # High-level LED controller
│           ├── LedPattern.kt     # Built-in pattern definitions
│           └── LedManager.kt     # Low-level LED proxy
└── README.md

GitHub Repo

{% embed url="https://github.com/EthereumPhone/TerminalSDK" %}

About

A SDK for the dGEN terminal display and LED system

Resources

License

Stars

Watchers

Forks

Packages

No packages published