# dGEN1 Terminal SDK

Android library for controlling the dGEN1 **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-dGEN1 hardware.

### Table of Contents

* [Installation](https://github.com/EthereumPhone/TerminalSDK#installation)
* [Quick Start](https://github.com/EthereumPhone/TerminalSDK#quick-start)
* [Terminal Buttons](https://github.com/EthereumPhone/TerminalSDK#terminal-buttons)
* [LED Patterns](https://github.com/EthereumPhone/TerminalSDK#led-patterns)
* [Flash Patterns](https://github.com/EthereumPhone/TerminalSDK#flash-patterns)
* [Custom LED Grid](https://github.com/EthereumPhone/TerminalSDK#custom-led-grid)
* [Building Custom Terminal Screens](https://github.com/EthereumPhone/TerminalSDK#building-custom-terminal-screens)
* [Custom Bitmaps](https://github.com/EthereumPhone/TerminalSDK#custom-bitmaps)
* [Terminal Screen Power Control](https://github.com/EthereumPhone/TerminalSDK#terminal-screen-power-control)
* [Lifecycle Management](https://github.com/EthereumPhone/TerminalSDK#lifecycle-management)
* [Architecture Patterns for Production Apps](https://github.com/EthereumPhone/TerminalSDK#architecture-patterns-for-production-apps)
* [API Reference](https://github.com/EthereumPhone/TerminalSDK#api-reference)
* [Requirements](https://github.com/EthereumPhone/TerminalSDK#requirements)
* [Contributing](https://github.com/EthereumPhone/TerminalSDK#contributing)

***

### 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 Button

The terminal screen is a 428x142 pixel display on the back of the dGEN1 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 Matrix Patterns

The 3x3 LED array on the front 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 Matrix 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-dGEN1 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)
* **dGEN1 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 dGEN1 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>" %}
