# 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>" %}


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.freedomfactory.io/sdks/dgen1-terminal-sdk.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
