Android SDK reference
Every public class, function, property, listener and error of ai.graine:inapp-android 0.8.0 (Kotlin and Java), grouped by what you use it for: signatures, parameters, what each returns, the errors it raises, and what to expect.
Every public declaration of the Android SDK, ai.graine:inapp-android 0.8.0 — Kotlin first, and callable from Java (@JvmStatic, @JvmOverloads, GraineCallback) — grouped by what you use it for. Each entry says what it is for, then gives its signature, its parameters, what it returns, the errors it can raise and an example; each type lists its fields with what to expect. The same API across every platform, topic by topic, is the SDK reference; installing and setting up is Android.
Requirements, install, permissions and initialisation
| Artifact | ai.graine:inapp-android:0.10.3 on Maven Central |
|---|---|
| Requires | Android 7.0 (API 24) and up; Java 17 bytecode (Android Gradle Plugin 8 or later); built against compileSdk 36 with Kotlin 2.1.20. No AndroidX, OkHttp or Compose dependency: the only runtime dependency is kotlin-stdlib. Its R8/ProGuard rules ship inside it. |
| Calls need | Firebase Cloud Messaging in your app (google-services.json, its Gradle plugin and the messaging library) for calls that ring — the SDK then registers for pushes itself. The SDK is compiled against Firebase and does not depend on it |
| WebRTC for calls | Optional: ai.graine:inapp-android-rtc, at the same version (Adding WebRTC calls) |
| In the dashboard | Add app:// to the agent's Allowed domains (Agent → Embed & Widgets); without it every request is refused |
Gradle (Kotlin DSL) — app/build.gradle.kts
What the library's manifest merges into your app
| Entry | Why |
|---|---|
android.permission.INTERNET | The voice engine and the conversation's connection |
android.permission.RECORD_AUDIO | The agent hears the user (voice and calls). A runtime permission: the SDK asks |
android.permission.MODIFY_AUDIO_SETTINGS | A call routes its audio — earpiece, loudspeaker or a headset, Bluetooth included (no Bluetooth permission is needed) |
android.permission.POST_NOTIFICATIONS | The ring, the ongoing call and a missed call are notifications. A runtime permission on Android 13 and up: your app asks |
android.permission.USE_FULL_SCREEN_INTENT | A ring that fills a locked screen. On Android 14 and up Google Play grants it by default only to apps whose main purpose is calling or alarms; without it the ring is a heads-up notification, still ringing |
android.permission.FOREGROUND_SERVICE | The call runs in a foreground service |
android.permission.FOREGROUND_SERVICE_MICROPHONE | That service keeps the microphone with the screen off (type microphone) |
ai.graine.inapp.GraineCallActivity | The call screen: its own task (so ending a call returns the user to where they were), shown over the lock screen, not exported |
ai.graine.inapp.GraineChatActivity | The chat on a screen of its own (GraineChatActivity.open), above the keyboard; not exported |
ai.graine.inapp.GraineCallService | The call's foreground service, foregroundServiceType="microphone", not exported |
ai.graine.inapp.GraineCallReceiver | The notifications' Decline and Hang up, not exported |
ai.graine.inapp.GraineMessagingService | With Firebase Cloud Messaging in the app, receives the push token and Graine's call pushes, so the app needs no messaging service of its own. Priority -1: a messaging service your app declares outranks it. Not exported; without Firebase nothing starts it |
What the SDK asks at run time: only RECORD_AUDIO — at voice.start() for the voice bar (forward your activity's onRequestPermissionsResult to voice.onRequestPermissionsResult), and from the call screen when a call is placed or after Answer (a refusal ends the call FAILED; no answer within 60 seconds too). Your app asks for notifications on Android 13 and up; without them, a ring with your app closed or in the background cannot show and ends MISSED. Your app offers the full-screen setting on Android 14 and up:
Android · Kotlin — onboarding
Notification channels the SDK creates in calls.configure (visible in the phone's settings): Incoming calls (high importance, the ringtone, vibration, shown on the lock screen), Incoming calls (app ringtone) (used while the SDK plays the brand's Graine or Soft ring), Missed calls and Ongoing calls.
Not using calls? On Android 14 and up, Google Play asks apps that declare USE_FULL_SCREEN_INTENT or FOREGROUND_SERVICE_MICROPHONE to justify them. An app that never places or receives calls removes them in its own manifest:
XML — AndroidManifest.xml (an app without calls)
Adding WebRTC calls
The SDK's native calls — the ones the user places (startCall, a “Call” button) and the ones Graine rings and the user answers — can carry their audio over WebRTC instead of the WebSocket. It is one more module, opt-in per app build.
Gradle (Kotlin DSL) — app/build.gradle.kts
Nothing else changes — no code, no permission to ask for, no setting: the calls find the module by themselves, and the ring, the call screen, “Connecting…”, the listeners and the outcomes are as before. It is for native calls only; the voice bar's audio follows GraineConfig.transport and the dashboard's Call audio.
| Module | ai.graine:inapp-android-rtc on Maven Central, at the same version as ai.graine:inapp-android |
|---|---|
| What it brings | A prebuilt WebRTC library — Google's WebRTC, BSD 3-Clause, its symbols renamed so it never clashes with another copy in the app — which your build fetches from Maven Central like any dependency; the SDK itself still has none. Its R8 rules ship inside the module. |
| Size, per phone | An app bundle delivers one ABI: about 7.4 MB more installed on 64-bit ARM (about 2.5–3.3 MB more to download), 4.3 MB on 32-bit ARM, 8.2–8.4 MB on x86; and about 0.5 MB of classes. |
| Permission | Its manifest adds android.permission.ACCESS_NETWORK_STATE: a normal permission, granted at install — nothing is asked. |
| Licences | graine-inapp-rtc/THIRD-PARTY-NOTICES.md (WebRTC, BSD 3-Clause): include it in your app's open-source licences screen. |
| In the dashboard | The agent's In-app call audio (agent → Calls & scheduling): WebRTC (recommended), or Standard, which puts every install's calls back on the WebSocket whatever the app chose — from each app's next start, with no release. |
| In the app | GraineCallsConfig.callTransport: AUTO, the default — WebRTC when the module is in the app and the dashboard allows it; WEBSOCKET; or WEBRTC (GraineCallTransport). |
| Which one carried a call | calls.transport — "webrtc" or "websocket" — and, for a call that tried WebRTC and runs on the WebSocket, calls.transportFallbackReason (the reasons). Read them once the call is ACTIVE. |
What the person notices. The same ring, the same call screen, the same “Connecting…”. On a weak or changing network the agent's voice holds up instead of stalling and catching up: a jitter buffer, loss concealment and the phone's own echo canceller. The call is prepared at the tap — or while the ring is showing — so it connects as fast as before. Over WebRTC the agent's voice arrives at telephone bandwidth today: steadier through a weak network, narrower than the WebSocket's on a perfect one.
When WebRTC cannot set up a call before any audio flows — a network that blocks it, a server that turns it down, no answer within the time allowed (about 12 seconds in all) — the call carries on over the WebSocket inside the same call: no second ring, no new call screen, no error; “Connecting…” shows a moment longer. A call the agent cannot take at all (no credit left, the agent switched off) ends with that reason, exactly as without the module. Once audio flows, a call never moves: a network drop is retried once, then the call ends as dropped (ENDED, FAILED, “The call's connection was lost.”).
Initialise
Android · Kotlin — AcmeApp.kt
Jetpack Compose hosts the views with AndroidView(factory = { GraineAgentBarView(it) }). Everything that touches a view runs on the main thread; nothing the SDK does blocks it. From Java, every entry point works with only the arguments you need (Notes for Java).
Install and the first answer
Configure the agent once, in Application.onCreate, then reach it anywhere with GraineInApp.get(). The SDK loads the agent's settings from Graine once per launch; until they arrive it uses the last ones kept on the device, so a call that wakes a closed app is drawn in the brand's design. Know they have arrived with GraineInApp.session or GraineListener.onSessionReady.
GraineInApp.init
Call it once per agent in Application.onCreate to start the SDK.
It starts loading the agent's settings at once. Without a name it makes the default agent, which GraineInApp.get() returns; an app with several agents names the others (Several agents). Asking again for a name already set up gives you that same agent; the configuration passed the second time is ignored.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
application | Application | required | Your Application. |
config | GraineConfig | required | The agent's publishable key, the base URL and the options (GraineConfig). |
name | String | DEFAULT | The name to make the agent under; leave it out (or pass an empty name) for the default agent. |
Returns — GraineInApp — the agent (GraineInApp).
Errors — none. A key or allowed-domain problem surfaces when the settings load, in GraineListener.onError.
GraineInApp
Use it for everything the SDK does: it is the agent, and the way to its chat, its voice bar, calls and bookings.
class GraineInApp private constructor(…)
Made once with GraineInApp.init and reached anywhere with GraineInApp.get(). Who the user is, their memory choice, the screen and the call engine belong to the app and are shared by every agent; the rest is this agent's.
What to expect — Everything that touches a view runs on the main thread, and callbacks arrive on it with exactly one of value and error set; nothing touches the network on it. A request made before the agent's settings have loaded waits for them.
| Property | Type | Meaning |
|---|---|---|
val config: GraineConfig | GraineConfig | Read it for the configuration this agent was made with. |
val name: String | String | Read it to know which agent this is: DEFAULT unless init was given another name. |
var session: SessionConfig? | SessionConfig? | Read it for the agent's settings once they have loaded this launch (SessionConfig); null before. Read-only. |
val voice: GraineVoice | GraineVoice | Use it to drive the voice bar's call (GraineVoice). |
val calls: GraineCalls | GraineCalls | Use it for native calls — the app's one call engine, whichever agent a call is with (GraineCalls). |
val chat: GraineChat | GraineChat | Use it to drive this agent's chat (GraineChat). |
var pendingWidget: JSONObject? | JSONObject? | Read it for the component the agent drew that is still waiting for an answer — kept because the view that draws it may not have been on screen when it arrived; null when there is none. Read-only. |
val memoryEnabled: Boolean | Boolean | Read it to show the user's memory choice: their last choice on this install, else true. |
val installId: String | String | Read it for this install's ID: a random UUID made on first launch and kept while the app is installed (not backed up, so a restored phone is a new install). Graine counts a user's devices by it; the agent never sees it. |
val currentScreen: GraineScreen? | GraineScreen? | Read it for the screen as last reported, on any agent (GraineScreen). |
val frictionRules: FrictionRules | FrictionRules | Read it for the stuck-user rules in force now — the defaults, the dashboard's and your app's (FrictionRules). |
val design: ResolvedDesign? | ResolvedDesign? | Read it for the design being drawn: a preview when one is set, else this launch's, else the last one kept on the device; null from a server without designs (ResolvedDesign). |
val schedulingRules: GraineSchedulingRules | GraineSchedulingRules | Read it for the booking rules and how Graine rings (GraineSchedulingRules) — this launch's, else the last ones kept, else the defaults with booking off. |
val bookingAvailable: Boolean | Boolean | Read it to decide whether to offer booking (GraineInApp.bookingAvailable). |
companion · const val DEFAULT = "default" | String | Use it to name the default agent explicitly. |
companion · val all: Map<String, GraineInApp> | Map<String, GraineInApp> | Read it to list every agent init made, by name, in the order they were made. |
Its functions: init, get, find, identify, setMemory, memories, forgetMemory, setScreen, updateField, removeField, clearScreen, setVariables, currentVariables, track, tap, reportEvent, noteInteraction, configureFriction, registerAction, registerDeferredAction, checkActions, clearPendingWidget, answerWidget, presentBooking, scheduleCall, scheduledCalls, cancelScheduledCall, themeTokens, previewDesign, addListener, removeListener — each with its parameters, what it returns, its errors and an example.
GraineConfig
Pass it to GraineInApp.init to say which agent to start and how.
data class GraineConfig @JvmOverloads constructor(…)
What to expect — Made once, at launch. Optional fields have the defaults shown; nothing in it is sent to Graine except what the descriptions say.
| Field | Type | Default | Meaning |
|---|---|---|---|
baseUrl | String | required | Where Graine is: "https://www.graine.ai" unless your workspace is self-hosted. |
publishableKey | String | required | Selects the agent: its publishable key from Agent → Embed & Widgets. Not a secret, and it cannot place a call on its own — app:// must be in the agent's Allowed domains. |
variables | Map<String, Any?> | emptyMap() | Prompt variables your app knows at build time. Values from identify() and setVariables() take precedence over these. |
transport | Transport? | null | Use it only to force how the voice bar's audio travels (Transport); null lets the dashboard's Call audio setting decide. |
appVersion | String? | null | Use it to tell builds apart: it lands on the call record (and the device registration). Left out, your app's version name. |
endOnBackground | Boolean | true | Ends a live voice-bar call when the app goes to the background (after two seconds there). Keep it on. |
timing | Timing | Timing() | Load and connect timing for very slow networks (GraineConfig.Timing); the defaults suit almost every app. |
debug | Boolean | false | Turn it on in debug builds to log what the SDK and the voice engine say to logcat under the tag Graine — “the agent did not speak” is answered in one read of it. |
friction | FrictionConfig? | null | Your app's own stuck-user rules, over the dashboard's (FrictionConfig) — for screens only your app knows about. |
fonts | Map<String, Typeface> | emptyMap() | Fonts your app already bundles, by the family names the dashboard offers — mapOf("Inter" to Typeface.createFromAsset(assets, "fonts/Inter.ttf")). A family mapped here is never downloaded; every other family the design uses the SDK fetches and keeps on disk, using the system font until it arrives. |
captureTaps | Boolean | true | Lets the SDK notice taps on labelled controls (what TalkBack reads, a view's ID, a button's words) as tap:<name>, at most twice a second and only while a screen is reported, so the agent knows what the user just pressed. It only watches; every touch reaches your app as before. |
chatSize | GraineChatSize? | null | How big the chat window opens in this app, over the dashboard's Window size (GraineChatSize); a size passed to chat.open(size) wins for that open. |
GraineConfig.Timing
Pass it in GraineConfig.timing only for an app used on very slow networks, to give the voice engine longer to load.
data class Timing @JvmOverloads constructor(…)
The voice bar's engine loads in the background when you call voice.attach. The SDK watches that it keeps moving rather than giving it one flat timer, and reloads it when it stalls.
What to expect — Every value is in milliseconds. The defaults suit almost every app.
| Field | Type | Default | Meaning |
|---|---|---|---|
stallMs | Long | 15_000 | How long loading may go without anything arriving before the SDK reloads the engine (a dead connection, a captive portal). |
bootMs | Long | 15_000 | How long the engine may take to say it is ready once it has fully arrived, before the SDK reloads it. |
hardCapMs | Long | 120_000 | The longest one load may take in all, however slowly it keeps arriving. |
retryDelaysMs | List<Long> | listOf(1_000, 2_000, 4_000, 8_000, 15_000, 15_000) | How long to wait before each reload (each varied by up to a quarter, so phones coming back from the same outage do not reload together). After the last, loading stops: Connectivity.FAILED, and voice.retry() starts over. |
endWatchdogMs | Long | 4_000 | How long voice.end() waits for the call to confirm it hung up before the SDK ends it itself. |
slowConnectMs | Long | 4_000 | How long the user may wait on a connect before the voice bar says “Still connecting…” (GraineVoice.slowConnect). |
SessionConfig
Read it to know what the dashboard says about the agent, once the settings have loaded.
data class SessionConfig(…)
GraineInApp.session, and the argument of GraineListener.onSessionReady.
What to expect — Loaded once per launch; null in session until then. A setting an older server does not send means its default: the user_id trait, 2 devices per user, booking off. Missing fields are empty, never a crash.
| Field | Type | Default | Meaning |
|---|---|---|---|
agentId | String | required | Use it in logs and support requests: the agent's ID in Graine. |
webcallAgentId | String | required | The ID of the agent's voice conversations in the app (the same as agentId when the agent has no separate one) — for logs; the SDK uses it itself. |
appearance | JSONObject | required | The agent's appearance settings from the dashboard, as served — what the SDK's views draw from when Graine sends no design. |
variables | Map<String, Any?> | required | The agent's default prompt variables from the dashboard; your own variables win over them. |
pageVersion | String? | required | The voice engine's current version: the SDK keeps the loaded engine until it changes; null when Graine does not say. Include it in support requests. |
declaredActions | Set<String> | required | Use it to check your handlers: the actions declared on the agent. One declared and not registered is refused when the agent asks for it (GraineInApp.checkActions). |
friction | JSONObject? | null | The dashboard's stuck-user rules, as served; your app's GraineConfig.friction applies over them. |
design | ResolvedDesign? | null | The design the brand chose, as light and dark tokens (ResolvedDesign); null from a server that predates designs. |
wsHost | String? | null | Where the agent's conversations connect, when Graine says; null means the default. |
identity | SessionIdentity? | null | Which trait names the user, and how many devices they may use (SessionIdentity); null from a server that predates it. |
memoryEnabled | Boolean? | null | Read it to know if the agent keeps memories of its users: true or false, or null when Graine did not say. |
schedulingRules | GraineSchedulingRules | GraineSchedulingRules() | The booking rules and how Graine rings (GraineSchedulingRules); the defaults, with booking off, from an older server. |
| Property | Type | Meaning |
|---|---|---|
val preferredTransport: Transport | Transport | Read it for the dashboard's Call audio setting: WEBRTC when it asks for high-quality audio, else WEBSOCKET. |
val accent: String? | String? | Read it for the dashboard's accent colour (#RRGGBB), when one is set. |
Identity, memory and conversations that continue
Tell the agent who the user is, so it can remember them, ring them, and continue their conversation. Where the agent remembers its users, a signed-in user who comes back finds the chat where they left it: it opens with the earlier messages (restored, oldest first; an earlier call's as one “Voice call · 6:05 PM” row under the day it happened) and the agent continues from them; the chat window's header then offers New conversation. Who the user is belongs to the app: identifying on any agent identifies them for every agent.
GraineInApp.identify
Call it after the user signs in, and with null when they sign out.
Traits become prompt variables and land on the call record, masked on the device. The trait the agent's Who is the user? setting names (user_id unless the dashboard chose email, phone or a custom trait) is also how Graine tells your users apart — what lets the agent remember a user and ring them — and it goes only to Graine, never into the prompt beyond the trait itself. A placeholder ("guest", "0", a masked value) identifies nobody.
Identity is taken at the start of each conversation, so a change takes effect from the next one. null signs out of every agent: this device is released for that user (it stops being rung for them and stops counting toward their devices), the chat they were having is cleared, and later conversations are anonymous. Identifying a different user does the same for the previous one.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
traits | Map<String, Any?>? | required | Who the user is, by trait name — mapOf("user_id" to user.id, "name" to user.firstName). null signs out. |
memory | Boolean? | null | false: this user's conversations are neither remembered nor recalled; the choice is kept on this install for them. null keeps their last choice — on until they make one. |
Returns — nothing.
Errors — none.
GraineInApp.setMemory
Call it when the user changes their memory choice in your privacy settings.
The same as identify with the same traits and memory = enabled, for every agent; kept on this install for this user.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
enabled | Boolean | required | true to remember this user's conversations, false to stop. |
Returns — nothing.
Errors — none.
GraineInApp.memories
Call it to show the user what the agent remembers about them, on a privacy screen.
What the agent remembers about the signed-in user, newest first, at most 100, already redacted. Only from a device that user is using.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
done | GraineCallback<List<GraineMemory>> | required | Hears the list (GraineMemory), or the error, once, on the main thread. |
Returns — nothing; the answer arrives in done. Example value (in done): [GraineMemory(id="m_31", text="Prefers a call after 6 PM", createdAt=Mon Oct 12 09:15:00 UTC 2026, updatedAt=null)].
Errors — in done's GraineError: IDENTITY_REQUIRED (“Nobody is signed in: identify() the user first.”) with nobody signed in; DEVICE_LIMIT from a device the user is not using the agent on; RATE_LIMITED, NETWORK, UNAVAILABLE, INVALID_KEY, ORIGIN_REFUSED.
GraineInApp.forgetMemory
Call it when the user asks the agent to forget them.
Deletes everything the agent remembers about the signed-in user — only from a device that user is using. The conversation is part of what is forgotten: once it succeeds, the chat on screen is cleared too; a refusal leaves it as it was.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
done | GraineCallback<Int>? | null | Hears how many memories were deleted, or the error, once, on the main thread. |
Returns — nothing; the count arrives in done. Example value (in done): 3.
Errors — in done's GraineError: IDENTITY_REQUIRED (“Nobody is signed in: identify() the user first.”), DEVICE_LIMIT, RATE_LIMITED, NETWORK, UNAVAILABLE, INVALID_KEY, ORIGIN_REFUSED.
GraineChat.newConversation
Call it to give the user a fresh conversation instead of continuing the last one.
The chat hangs up, Graine lets go of the conversation it kept for this agent and user (so the next chat does not continue it), and the lines on screen and any waiting to go are cleared; when the user was in the chat, a fresh chat opens with the welcome and the starters. Memories the agent keeps about the user stay as they are. With nobody signed in there is nothing kept to let go of, so only the screen is cleared. The SDK's chat window calls it from the New conversation button in its header, which it shows only while a conversation continued from before is on screen.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
done | GraineCallback<Unit>? | null | Hears Unit once it is done, or the error, on the main thread. |
Returns — nothing; the result arrives in done.
Errors — in done's GraineError: DEVICE_LIMIT, NETWORK, RATE_LIMITED, UNAVAILABLE, … — the chat is then as it was, connected again.
GraineCallback
Pass one to a request to hear its answer: the value, or why there is none.
fun interface GraineCallback<T>
Every request the SDK makes to Graine on the user's behalf answers through one: memories, bookings, cancelling.
What to expect — Called once, on the main thread, with exactly one of value and error set. In Kotlin, a trailing lambda { value, error -> }; in Java, (value, error) -> { … }.
Its functions: onResult — each with its parameters, what it returns, its errors and an example.
GraineCallback.onResult
Implement it to receive a request's answer.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
value | T? | required | The answer, when the request succeeded; null otherwise. |
error | GraineError? | required | Why there is no answer (GraineError); null when it succeeded. |
Returns — nothing.
Errors — none.
GraineMemory
Read it to show one thing the agent remembers about the user.
data class GraineMemory @JvmOverloads constructor(val id: String, val text: String, val createdAt: java.util.Date? = null, val updatedAt: java.util.Date? = null)
What to expect — From memories(): newest first, at most 100, already redacted. Only for the signed-in user, from one of their devices. The dates are moments; show them in the device's time zone.
| Field | Type | Default | Meaning |
|---|---|---|---|
id | String | required | Use it as a stable key when you list memories. |
text | String | required | Show it: what is remembered, in a sentence, already redacted. |
createdAt | java.util.Date? | null | When it was first remembered; null when Graine did not say. |
updatedAt | java.util.Date? | null | When it last changed; null when it never did or Graine did not say. |
SessionIdentity
Read it to know which trait names the user, and on how many devices they may use the agent.
data class SessionIdentity(val field: String, val devicesPerUser: Int)
SessionConfig.identity.
What to expect — From an older server that does not say: the user_id trait and 2 devices.
| Field | Type | Default | Meaning |
|---|---|---|---|
field | String | required | The trait identify must carry for memory and booking to work: user_id, email, phone or a custom one such as member_id. |
devicesPerUser | Int | required | How many devices one user may use the agent on, 1–5; a device beyond it gets DEVICE_LIMIT. |
Chat
The typed conversation with the agent: GraineInApp.get().chat, or GraineInApp.get("support").chat for another agent. The voice bar switches to it when the dashboard offers chat (Appearance → mode: voice, chat or both); GraineChatView and GraineChatActivity show it as a whole screen. Everything the agent can do on a call works here too — it sees the screen you report, runs your actions and draws its components — and each of its replies streams into a single bubble. A line typed while the chat is connecting — or reconnecting after a drop, which it does by itself while the user is in it — is sent as soon as it can be, never lost. One conversation at a time across every agent: opening the chat ends a voice-bar call, starting a call ends the chat, and a native call holds the chat until it is over.
Where Graine rings this app, the chat window shows the user's booked calls first in the conversation, with Book a call where booking is available (Booking with the SDK's sheet).
GraineChat
Use it to open, send to and follow one agent's chat.
class GraineChat internal constructor(…) : ChatSource
GraineInApp.get().chat. What the voice bar's chat window and GraineChatView draw.
What to expect — Main thread. Changes reach GraineListener.onChatState and onChatMessage, and every change listener. messages empties on sign-out, when another user signs in, after a successful forgetMemory() and on newConversation().
| Property | Type | Meaning |
|---|---|---|
var connected: Boolean | Boolean | Read it to know the conversation is up: lines go out as they are typed. Read-only. |
var connecting: Boolean | Boolean | Read it to show “Connecting…”: it is being opened, or reopened after a drop. Read-only. |
val reconnecting: Boolean | Boolean | Read it to say “Reconnecting…”, not “Offline”: the conversation dropped — or a first try to open it failed — while the user is in the chat, and it is being made again; true until it is connected, gives up (failed) or is closed. connecting is true with it. |
override var failed: Boolean | Boolean | Read it to show that nothing could connect it, after every retry; a send or connect tries again. Read-only. |
override val messages: List<GraineChatTurn> | List<GraineChatTurn> | Read it to draw the conversation, oldest first (GraineChatTurn). |
override val online: Boolean | Boolean | Read it to show “Online”: the chat is connected. |
override val typing: Boolean | Boolean | Read it to draw a typing indicator: from the user's message until the agent's first words or component, the chat closing, or 12 seconds. |
var upcomingCalls: List<GraineScheduledCall> | List<GraineScheduledCall> | Read it for the user's calls still to come, soonest first, at most three — the chat window's Upcoming calls card. Fetched when the chat opens and after the agent finishes a reply, only where Graine rings this app. Read-only. |
var openedSize: GraineChatSize? | GraineChatSize? | Read it for the size the last open asked for, until close; null follows GraineConfig.chatSize, then the dashboard's. Read-only. |
companion · const val CLOSE_UNSEEN_MS = 15_000L | Long | How long a conversation no window shows is kept for the next screen to take before it is closed — 15 seconds. |
companion · const val UPCOMING_EVERY_MS = 10_000L | Long | How often, at most, the upcoming calls are fetched again unless asked — 10 seconds. |
Its functions: open, connect, send, close, clear, newConversation, shown, addChangeListener, removeChangeListener, refreshUpcomingCalls, cancelUpcomingCall — each with its parameters, what it returns, its errors and an example.
GraineChat.open
Call it from your own “Chat with us” button to open the chat.
Connects the chat. A GraineAgentBarView on screen moves to its chat, where the agent offers one, and draws it at size — this open's own, until close(); null follows GraineConfig.chatSize, then the dashboard's Window size.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
size | GraineChatSize? | null | How big the chat opens this time: COMPACT, LARGE or FULL (GraineChatSize). |
Returns — nothing.
Errors — none.
GraineChat.connect
Call it to connect the chat yourself — for a chat screen of your own that is not a GraineChatView.
Opens the conversation if it is not open or opening, and fetches the upcoming calls. A chat window on screen does this by itself.
Parameters — none.
Returns — nothing.
Errors — none.
GraineChat.send
Call it to send the user's message from a chat screen of your own.
On screen at once; sent now when the chat is up, or as soon as it is (a message typed while not connected is SENDING). Typing is the user asking again: a chat that gave up connects again. Blank lines are ignored.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
text | String | required | What the user typed. |
Returns — nothing.
Errors — none.
GraineChat.close
Call it when the user leaves your chat screen.
Ends the conversation: the agent is told it is over and no reconnect follows. What was said stays visible; a later connect or send begins a fresh conversation below it.
Parameters — none.
Returns — nothing.
Errors — none.
GraineChat.clear
Call it to clear the lines on screen — when your app starts a new topic, for example.
Only the screen clears; the conversation itself goes on — connected stays connected, a message still waiting for the connection is still sent, and nothing Graine keeps is touched: a conversation that continues comes back with the next history, and the agent's memories stay. To start a fresh conversation, use newConversation; to forget, forgetMemory.
Parameters — none.
Returns — nothing.
Errors — none.
GraineChat.shown
Call it from a chat screen of your own when it comes on screen (true) and leaves it (false).
The chat connects while it is on screen, and is kept for 15 seconds after it leaves, for the next screen to take, before it closes. GraineChatView does this by itself.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
visible | Boolean | required | true when your chat screen came on screen, false when it left. |
Returns — nothing.
Errors — none.
GraineChat.addChangeListener
Call it to redraw a chat screen of your own whenever anything the chat shows changes.
The listener runs on the main thread after every change — a line arriving or growing, the connection coming up or dropping, the upcoming calls.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
listener | Runnable | required | What to run on each change. |
Returns — nothing.
Errors — none.
GraineChat.removeChangeListener
Call it when your chat screen goes away, to stop hearing changes.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
listener | Runnable | required | The listener you added. |
Returns — nothing.
Errors — none.
ChatSource
Implement it to show a conversation your app runs itself in the SDK's chat views.
interface ChatSource
What a chat window draws from — GraineAgentBarView.chat and GraineChatView.source. The SDK's own is GraineChat, the default everywhere.
What to expect — Called on the main thread. typing and failed default to false, and shown does nothing unless you override it.
| Property | Type | Meaning |
|---|---|---|
val messages: List<GraineChatTurn> | List<GraineChatTurn> | Provide the conversation, oldest first. |
val online: Boolean | Boolean | Provide true while connected: the window says “Online”, otherwise “Connecting…”. |
val typing: Boolean | Boolean | Provide true while the agent is working on a reply: the window draws its typing indicator. |
val failed: Boolean | Boolean | Provide true when nothing could connect the conversation: the window says so, and a send tries again. |
Its functions: send, shown, addChangeListener, removeChangeListener — each with its parameters, what it returns, its errors and an example.
ChatSource.send
Implement it to send the line the user typed in the chat window.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
text | String | required | What the user typed. |
Returns — nothing.
Errors — none.
ChatSource.shown
Implement it to open your conversation when a chat window shows it, and let it go when none does.
Called with true when a chat window showing this conversation comes on screen, false when it leaves. Does nothing unless overridden.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
visible | Boolean | required | Whether a window now shows it. |
Returns — nothing.
Errors — none.
ChatSource.addChangeListener
Implement it so the chat window hears every change to your conversation.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
listener | Runnable | required | Run it, on the main thread, after every change. |
Returns — nothing.
Errors — none.
ChatSource.removeChangeListener
Implement it so a chat window can stop hearing changes.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
listener | Runnable | required | The listener to stop running. |
Returns — nothing.
Errors — none.
GraineChatTurn
Read it to draw one message of the chat.
data class GraineChatTurn @JvmOverloads constructor(…)
What to expect — One per message, oldest first. The agent's message updates in place (same id) while live, then stops. A user's message typed while the chat is not connected is SENDING and goes as soon as it is; FAILED means every reconnect was spent — the next message, or a retry, sends it. Restored messages arrive once, when the chat opens, for a signed-in user on an agent with memory on, on one of their devices: at most 50, oldest first, delivered, never live, with their original times; the welcome is not sent then. Restored lines never reach onChatMessage.
| Field | Type | Default | Meaning |
|---|---|---|---|
id | String | required | Use it as a stable key: t1, t2, … for the life of the conversation on screen. |
role | Role | required | Who said it: Role.CUSTOMER (the user) or Role.AGENT (Role). |
text | String | required | What was said; an agent's message may be only a component. |
at | java.util.Date | required | When the message began; show it in the device's time zone. |
delivery | Delivery | Delivery.SENT | Where a user's message is (GraineChatTurn.Delivery); an agent's is always SENT. |
proactive | Boolean | false | Use it to badge a closed chat: the agent said this without being asked. |
component | GraineComponent? | null | The component the agent drew with this message (GraineComponent is GraineWidget), when it drew one. |
live | Boolean | false | Draw the words as provisional while true: the agent's message is still arriving. |
restored | Boolean | false | true for a message from an earlier conversation, restored when the chat opened: draw it as history, with its own time. |
channel | Channel | Channel.CHAT | Where it was said (GraineChatTurn.Channel): a chat window draws a run of voice messages as one “Voice call” row, never their words. |
GraineChatTurn.Delivery
Read it to show where one of the user's messages is.
enum class Delivery { SENDING, SENT, FAILED }
What to expect — Only meaningful on the user's messages: SENDING → SENT; FAILED once every reconnect was spent.
| Case | Meaning |
|---|---|
SENDING | The user typed it while the chat was offline; it goes out as soon as the chat connects. Show it as pending. |
SENT | On its way to the agent. |
FAILED | Not sent, because the chat could not reconnect. Offer a retry: a retry, or the user's next message, makes the chat try again. |
GraineChatTurn.Channel
Read it to tell a typed message from one said on a call.
enum class Channel { CHAT, VOICE }
What to expect — VOICE only on a restored message.
| Case | Meaning |
|---|---|
CHAT | Typed, in the chat. |
VOICE | Said on a call with the agent — draw a run of them as one “Voice call” row (ChatRows lays them out). |
Role
Read it to know who said a line.
enum class Role { AGENT, CUSTOMER }
| Case | Meaning |
|---|---|
AGENT | The agent. |
CUSTOMER | The person using your app. |
ChatRows
Use it to lay out a chat of your own the way the SDK's chat window draws it: a bubble per message, one row for an earlier call, and a line where the day changes.
object ChatRows
From GraineInApp.get().chat.messages. A conversation all from today is a bubble per message. A conversation that continues from before gets a day line — Today, Yesterday or a date — before its first row and wherever the phone's day changes between rows, and a run of an earlier call's messages on one day becomes one “Voice call · 6:05 PM” row, never the call's transcript. The SDK's own chat window draws exactly these rows. Every function is static, so Java calls ChatRows.build(messages) as Kotlin does.
What to expect — Pure: no state, no network, any thread. Days are the phone's, in its time zone unless you pass another.
| Property | Type | Meaning |
|---|---|---|
const val TODAY = "today" | String | A day line's day for today. |
const val YESTERDAY = "yesterday" | String | A day line's day for yesterday. |
const val DATE = "date" | String | A day line's day for an older day, which its date names. |
Its functions: build, dayLabel, callLabel, callSpoken, offersNewConversation, offersStarters — each with its parameters, what it returns, its errors and an example.
ChatRows.Row
Read it for one row of a chat window: a day line, an earlier call, or a message.
sealed class Row
From ChatRows.build, oldest first. Branch on its three kinds with when.
| Case | Fields | Meaning |
|---|---|---|
Day | day: String, date: String, at: java.util.Date | A day line: day is TODAY, YESTERDAY or DATE; date the phone's local date, YYYY-MM-DD; at the first message's time on it. Show ChatRows.dayLabel(row). |
Voice | id: String, at: java.util.Date, first: Int, last: Int | An earlier call, one row for a run of its messages on one day: id a stable key (voice- and its first message's ID), at its first message's time, first and last the positions in the messages of the first and last it stands for. Show ChatRows.callLabel with the time. |
Turn | turn: GraineChatTurn | One message: draw turn as a bubble (GraineChatTurn). |
| Property | Type | Meaning |
|---|---|---|
Day.key: String | String | A day line's word as Graine writes it — "today", "yesterday" or the local date: a stable key for the row. |
ChatRows.build
Call it for the rows to draw in a chat of your own, in order.
A day line goes before the first row when it is not from today, and wherever the day changes between rows; a run of an earlier call's messages on one day is one Voice row (a day line never sits inside one); every other message is a Turn. Build again whenever the chat changes (addChangeListener).
| Parameter | Type | Default | Meaning |
|---|---|---|---|
turns | List<GraineChatTurn> | required | The conversation, oldest first — chat.messages. |
now | java.util.Date | java.util.Date() | What counts as today and yesterday; now when left out. |
zone | java.util.TimeZone | the phone's | Whose days. |
Returns — List<ChatRows.Row> (ChatRows.Row). Example value: [Day(day=yesterday, date=2026-10-14, …), Voice(id=voice-t1, …, first=0, last=5), Day(day=today, …), Turn(turn=GraineChatTurn(id=t7, …))].
Errors — none.
ChatRows.dayLabel
Call it for the words on a day line.
“Today”, “Yesterday”, else the date in the phone's own language and order — “Tue 14 Oct” — with the year when it is not this year.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
day | ChatRows.Row.Day | required | The day line. |
now | java.util.Date | java.util.Date() | What counts as this year. |
zone | java.util.TimeZone | the phone's | Whose calendar. |
locale | java.util.Locale | the phone's | The language and order of the date. |
Returns — String. Example value: "Tue 14 Oct".
Errors — none.
ChatRows.callLabel
Call it for the words on an earlier call's row.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
clock | String | required | The call's time (Voice.at) in the phone's short time — 6:05 PM or 18:05. |
Returns — String — “Voice call · ” and the time. Example value: "Voice call · 6:05 PM".
Errors — none.
ChatRows.callSpoken
Call it for what a screen reader says for an earlier call's row.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
clock | String | required | The same time as callLabel's. |
Returns — String. Example value: "Voice call at 6:05 PM".
Errors — none.
ChatRows.offersNewConversation
Call it to decide whether your chat's header offers New conversation.
New conversation belongs to a conversation that continues from before: it is offered while restored messages are on screen, and calls chat.newConversation().
| Parameter | Type | Default | Meaning |
|---|---|---|---|
turns | List<GraineChatTurn> | required | The messages on screen. |
Returns — Boolean — true when any of them is restored. Example value: true.
Errors — none.
ChatRows.offersStarters
Call it to decide whether your chat shows the brand's starters.
The starters belong to a new chat: not once the user has said something, nor on a conversation that continues from before.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
turns | List<GraineChatTurn> | required | The messages on screen. |
Returns — Boolean — true while none of them is the user's and none is restored. Example value: false.
Errors — none.
GraineChatSize
Pass it to choose how big the chat window opens.
enum class GraineChatSize(val wire: String)
What to expect — Which size wins: the size passed to chat.open(size), then GraineConfig.chatSize, then the agent's Window size in the dashboard, else compact. An open's size lasts until the chat closes. On a phone, large is a sheet over most of the screen (the app dimmed behind it, a handle to drag it closed, Back too) and full the whole screen; on a tablet both open a panel, 420 dp wide, in the corner. A word from a newer design that the SDK does not know is passed over.
| Property | Type | Meaning |
|---|---|---|
val wire: String | String | The design's word for it — compact, large or full. |
| Case | Meaning |
|---|---|
COMPACT | The window above the voice bar. |
LARGE | On a phone, a sheet covering most of the screen; on a tablet, a panel in the corner. |
FULL | The whole screen. |
Its functions: from — each with its parameters, what it returns, its errors and an example.
GraineChatSize.from
Call it to read a size word — from your own settings, for example — into a GraineChatSize.
Case and surrounding spaces do not matter.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
wire | Any? | required | The word: "compact", "large" or "full". |
Returns — GraineChatSize? — null for anything else (absent, or a word newer than this SDK). Example value: GraineChatSize.LARGE.
Errors — none.
GraineChatView
Use it to show the chat as a whole screen inside your own layout.
class GraineChatView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : FrameLayout(context, attrs), GraineListener
The text conversation, drawn by the SDK in the brand's design — header, bubbles, composer, suggestions — with the agent's components right under the line that asked for them, and the booked calls first in the conversation. The conversation opens when the view appears and closes shortly after the view leaves the screen. Built from standard Android views, so it needs neither AndroidX nor Compose.
What to expect — Main thread. Add it to any layout, or open GraineChatActivity to show it as a screen of its own.
| Constructor parameter | Type | Default | Meaning |
|---|---|---|---|
context | Context | required | The activity it is shown in. |
attrs | AttributeSet? | null | From a layout file; null in code. |
| Property | Type | Meaning |
|---|---|---|
var source: ChatSource | ChatSource | Set it to choose the conversation drawn: the default agent's chat unless set — another agent's (GraineInApp.get("support").chat), drawn in that agent's design, or one your app runs itself. |
var scheme: String? | String? | Set it to force "light", "dark" or "auto" (the device); null follows the design. |
var onClose: (() -> Unit)? | (() -> Unit)? | Set it to hear the close button — finish your screen there. Unset, the button does nothing. |
val widgetView: GraineWidgetView | GraineWidgetView | The components the agent draws in the chat, between the messages and the composer (GraineWidgetView). |
GraineChatActivity
Use it to open the chat on a screen of its own, with no layout of yours.
class GraineChatActivity : Activity()
GraineChatView full screen, in the brand's design, above the keyboard, with the system bars in the chat's colours; closed by its close button or Back. Declared in the SDK's manifest; nothing for your app to add.
What to expect — Open it with GraineChatActivity.open; it shows the agent you name, or the default.
Its functions: open — each with its parameters, what it returns, its errors and an example.
GraineChatActivity.open
Call it from any “Chat with us” entry point to open the chat screen.
From anything with a context; a non-activity context starts it in a new task. A name no agent was made under opens the default agent's chat, with a warning in the log.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
context | Context | required | An activity, or any context of your app. |
agent | String | GraineInApp.DEFAULT | Which agent's chat, by the name init was given; the default agent when left out. |
Returns — nothing.
Errors — none.
The voice bar
The agent talking inside your app, beside the screen it talks about. Attach the voice engine once with voice.attach(activity) — it loads idle in the background, so a tap costs only the call — and place a GraineAgentBarView (or a GraineLauncherView) in your layout: the bar places and ends calls by itself, shows captions, the connection's state and the chat beside the call, all in the agent's design. GraineVoice's state and the listener's events are for an app that draws its own controls. The bar says the network's state out loud — “Weak connection”, “No connection”, “Reconnecting…” — and, while a call comes up or the agent works out its answer, shows the dashboard's loader in the brand's words.
GraineVoice
Use it to place and end the voice bar's call from controls of your own, and to read its state.
class GraineVoice internal constructor(…)
GraineInApp.get().voice. Attach the engine once with attach(activity); the bar starts and ends calls itself.
What to expect — Main thread. The state below changes as the call moves, and each change reaches GraineListener. One conversation at a time: starting a call closes an open chat, and a native call holds the line.
| Property | Type | Meaning |
|---|---|---|
var connected: Boolean | Boolean | Read it to show the call as live. Read-only. |
var connecting: Boolean | Boolean | Read it to show “Connecting…”: a call is being placed. Read-only. |
var muted: Boolean | Boolean | Read it to show the mute control as on. Read-only. |
var ready: Boolean | Boolean | Read it to know the engine has loaded and a call can start at once (a start before is held until it has). Read-only. |
var quality: ConnectionQuality | ConnectionQuality | Read it to show the connection in one word (ConnectionQuality). Read-only. |
var online: Boolean? | Boolean? | Read it to know whether the device is online, as the engine sees it; null before it knows. Read-only. |
var reconnecting: Boolean | Boolean | Read it to show “Reconnecting…”: the network interrupted the call and it is being re-established. Read-only. |
var connectivity: Connectivity | Connectivity | Read it to know whether the engine loaded (Connectivity). Read-only. |
var error: String? | String? | Read it to tell your team something must be fixed before calls can work — a key, or an agent not set up for calls; null otherwise. Also sent to onError; cleared when the next call is placed. Never show it to the user. Read-only. |
val connectStage: ConnectStage? | ConnectStage? | Read it to say where a call being placed is — registering, dialling, retrying, switching (ConnectStage); null outside a connect. |
val slowConnect: Boolean | Boolean | Read it to say “Still connecting…” honestly: the user has waited past GraineConfig.Timing.slowConnectMs (4 s) on a call they placed, or on a bar open while the engine loads. |
val callFailed: Boolean | Boolean | Read it to say “Couldn’t connect · tap to retry”: the last start could not be connected on either audio path. start() tries again; cleared by the next start or a call that connects. |
val phase: CallPhase | CallPhase | Read it for what the call is doing in one word (CallPhase) — the bar says “Thinking…” from it. |
var captions: List<GraineCaption> | List<GraineCaption> | Read it for what has been said on the call, oldest first — the agent's turns and the user's (GraineCaption). A live line is replaced as its words arrive, not appended; the last 12 are kept, and a new call starts empty. onCaption hears each line as it comes. Read-only. |
var captionsOn: Boolean | Boolean | Read it to draw your captions (CC) control: the switch, on by default. While it is off the voice bar hides the line being said; captions keeps what was said either way. Read-only; set it with setCaptionsOn. |
val attached: Boolean | Boolean | Read it to know the engine is mounted: attach was called, and detach not since. |
companion · const val MIC_REQUEST = 0x6E7A | Int | The request code the SDK uses for the microphone prompt — what onRequestPermissionsResult recognises. |
Its functions: attach, detach, start, end, setMuted, setCaptionsOn, clearCaptions, retry, onRequestPermissionsResult, viewOpened, viewClosed, answerWidget, reportEvent — each with its parameters, what it returns, its errors and an example.
GraineVoice.attach
Call it once, from your first activity, to load the voice engine so a call can start at once.
The engine loads idle — nothing is dialled and the microphone is not used — so a start() later costs only the call. Once is enough: from then on the engine follows the user, moving to whichever of your activities is in front, and a call in progress carries on across them. Calling it again from another activity is the same move, never a reload.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
activity | Activity | required | The activity in front. |
Returns — nothing.
Errors — none.
GraineVoice.detach
Call it only when your app no longer wants the voice bar at all — a sign-out that removes the agent, for example.
Ends a call in progress and unloads the engine. attach loads it again.
Parameters — none.
Returns — nothing.
Errors — none.
GraineVoice.start
Call it from your own “Talk” control to place the voice bar's call.
Asks for the microphone first if the app does not hold it — forward your activity's onRequestPermissionsResult to onRequestPermissionsResult so the call goes ahead when it is granted. Before the engine is ready the start is held and placed when it is. It ends the chat (its lines stay) and another agent's call.
Parameters — none.
Returns — nothing.
Errors — none as an exception: before attach it reports “GraineVoice.attach(activity) must be called before start().” to onError; while a native call is live, “A call is already in progress.”. A refused microphone is onMicDenied("NotAllowedError").
GraineVoice.end
Call it from your own hang-up control.
Hangs up. If the call does not confirm it within GraineConfig.Timing.endWatchdogMs (4 s), the SDK ends it itself.
Parameters — none.
Returns — nothing.
Errors — none.
GraineVoice.setMuted
Call it from your own mute control.
Stops sending the user's voice without ending the call.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
value | Boolean | required | true to stop sending the user's voice, false to send it again. |
Returns — nothing.
Errors — none.
GraineVoice.setCaptionsOn
Call it from your own captions (CC) control to show or hide the line being said.
The voice bar shows the line being said only while the switch is on. The call goes on either way, and captions keeps what was said. On by default; your choice holds for the rest of the launch.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
on | Boolean | required | true to show the captions, false to hide them. |
Returns — nothing.
Errors — none.
GraineVoice.clearCaptions
Call it to forget what has been said on the call — from a “Clear” control on a transcript of yours, say.
Empties captions without ending the call; the voice bar's caption clears too. A new call starts empty by itself.
Parameters — none.
Returns — nothing.
Errors — none.
GraineVoice.retry
Call it from a “Try again” control when the engine could not load (connectivity is FAILED).
Reloads the engine and starts the reload schedule over (GraineConfig.Timing.retryDelaysMs).
Parameters — none.
Returns — nothing.
Errors — none.
GraineVoice.onRequestPermissionsResult
Call it from your activity's onRequestPermissionsResult, so a call waiting for the microphone can go ahead.
When the user refuses, the waiting call is dropped and onMicDenied("NotAllowedError") fires.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
requestCode | Int | required | As Android passes it to your activity. |
grantResults | IntArray | required | As Android passes them to your activity. |
Returns — Boolean — true when the request was the SDK's (MIC_REQUEST) and is handled; pass anything else on. Example value: true.
Errors — none.
GraineVoice.viewOpened
Call it when a voice interface of your own — anything but a GraineAgentBarView, which says so itself — comes on screen.
While one of your views is open, the user is waiting on the voice: an engine still loading behind it is their wait, reported as slowConnect (“Still connecting…”) past Timing.slowConnectMs. Call it in pairs with viewClosed; several views of yours count as one while any is open. Main thread.
Parameters — none.
Returns — nothing.
Errors — none.
GraineVoice.viewClosed
Call it when the voice interface of yours from viewOpened leaves the screen.
When it was the last one, and no GraineAgentBarView is on screen either, a failed connect and its stage are no longer news: callFailed, connectStage and slowConnect clear. A close with nothing open is ignored.
Parameters — none.
Returns — nothing.
Errors — none.
GraineCaption
Read it for one line of what was said on the voice bar's call — to draw a transcript of your own.
data class GraineCaption(val role: Role, val text: String, val live: Boolean)
GraineVoice.captions, oldest first.
What to expect — A live line is replaced as its words arrive, not appended; the last 12 lines are kept, and a new call starts empty. The captions switch (captionsOn) hides the bar's caption, never this list.
| Field | Type | Default | Meaning |
|---|---|---|---|
role | Role | required | Who said it: Role.AGENT or Role.CUSTOMER (Role). |
text | String | required | The words so far. |
live | Boolean | required | true while the words are still arriving: draw them as provisional. |
GraineAgentBarView
Place it in your layout to give the user the voice bar.
class GraineAgentBarView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : LinearLayout(context, attrs), GraineListener
What the user sees during a call, drawn exactly as the dashboard's Garage previews it: the avatar (the design's glyph, the brand's logo whole on its plate, or the first letter), the heading and a status line with a meter that moves at the design's pace, the microphone, and the end button. Above it, the line being said on a card of its own (while voice.captionsOn), and above that the components the agent draws (widgetView), so a card the agent shows moves with the bar. Where the dashboard offers chat, the bar switches between the call and the chat. Plain platform widgets: no AndroidX, Compose or Material.
What to expect — The design is the source for everything; the properties below override one thing each. From a server without designs, the bar is Graine Classic in the dashboard's accent and corners.
| Constructor parameter | Type | Default | Meaning |
|---|---|---|---|
context | Context | required | The activity it is shown in. |
attrs | AttributeSet? | null | From a layout file; null in code. |
| Property | Type | Meaning |
|---|---|---|
var accent: String? | String? | Set it (#RRGGBB) to colour the avatar, the microphone and the meter; null uses the design's accent. |
var name: String? | String? | Set it to change the name beside the status; null uses the dashboard's heading, else “Assistant”. |
var avatarUrl: String? | String? | Set it to a picture of your own for the avatar, filling its circle (yours to crop); null uses the design's glyph, else the logo, else the first letter. |
var scheme: String? | String? | Set it to force "light", "dark" or "auto" (the device); null follows the design. |
var radius: Int? | Int? | Set it (dp) to round the caption card (the bar takes it plus 8, at most 26); null uses the design's. |
var onClose: (() -> Unit)? | (() -> Unit)? | Set it to hide the bar (or show your launcher again) when the user presses end with no call to end, or once a call has hung up and its goodbye has played. Unset, the bar stays. |
var draggable: Boolean | Boolean | Set it to false to keep the bar where you put it; true (the default) lets the user drag it up and down, and a tap still taps. |
var placementOffset: Int | Int | Read it for where the user left the bar, in dp above its anchor; set it to restore a position you kept (clamped to the screen once laid out). |
var onPlacementChange: ((bottomOffset: Int) -> Unit)? | ((bottomOffset: Int) -> Unit)? | Set it to keep the user's chosen position: called when they let go somewhere new — save the value and give it back to placementOffset later. The SDK stores nothing. |
var keyboardAware: Boolean | Boolean | Leave it true so the bar rises above the soft keyboard; turn it off only if your layout already moves it. |
val widgetView: GraineWidgetView | GraineWidgetView | The components the agent draws, mounted above the caption so they move with the bar (GraineWidgetView). |
var graine: GraineInApp | GraineInApp | Set it to talk to another of your agents — bar.graine = GraineInApp.get("sales") (attach that agent's voice once too); the default agent unless set. Set it as the screen opens; changing it never moves a call that is already live to the new agent. |
var chat: ChatSource? | ChatSource? | Set it to choose the chat the bar opens beside the call: the agent's own GraineChat by default, a conversation your app runs itself, or null for the call alone whatever the dashboard offers. |
Its functions: resetPlacement — each with its parameters, what it returns, its errors and an example.
GraineAgentBarView.resetPlacement
Call it to put the bar back where you placed it — from a “Reset position” setting, for example.
Moves the bar back to its anchor and tells onPlacementChange (with 0).
Parameters — none.
Returns — nothing.
Errors — none.
GraineLauncherView
Place it in your layout to give the user a floating button that starts the call — or opens what you choose.
class GraineLauncherView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : View(context, attrs), GraineListener
The floating button, drawn the way the brand designed it: 56 dp, in the accent, with the design's glyph, the brand's logo (whole, on its plate) or the first letter of the name, in the design's shape — circle, squircle, rounded or plain square, or a pill as wide as its label — with the design's depth. A tap starts the call, and ends one that is live; setOnClickListener replaces that. Place it yourself, like any view (bottom-end over the content is usual); it measures its own size, shadow room included.
What to expect — Main thread. Without a design (an older server) it is a circle in the dashboard's accent with its logo.
| Constructor parameter | Type | Default | Meaning |
|---|---|---|---|
context | Context | required | The activity it is shown in. |
attrs | AttributeSet? | null | From a layout file; null in code. |
| Property | Type | Meaning |
|---|---|---|
var label: String? | String? | Set it to change the pill's words and what TalkBack reads; null uses the dashboard's launcher label, else “Talk to us”. |
var accent: String? | String? | Set it (#RRGGBB) to change the fill; null uses the design's accent, else the dashboard's. |
var scheme: String? | String? | Set it to force "light", "dark" or "auto"; null follows the design. |
var graine: GraineInApp? | GraineInApp? | Set it to call another of your agents, in its design — GraineInApp.get("sales"); the default agent unless set. |
Its functions: setOnClickListener — each with its parameters, what it returns, its errors and an example.
GraineLauncherView.setOnClickListener
Call it to make the launcher open something of yours — the voice bar, the chat — instead of starting the call.
Replaces the default tap (start or end the call) with your own listener.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
l | OnClickListener? | required | What a tap does now. |
Returns — nothing.
Errors — none.
GraineLoaderView
Use it to draw the brand's loader in a waiting state of your own.
class GraineLoaderView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : View(context, attrs)
How waiting looks, from the dashboard's Appearance → loader: dots, pulse, ring, bars, or the brand's logo breathing (“mark”, which is the pulse when no logo is uploaded), at the chosen size and speed. Drawn on a canvas, with no resources or animation library. The bar and the components use it themselves.
What to expect — Call configure with a look from Loaders.resolve.
| Constructor parameter | Type | Default | Meaning |
|---|---|---|---|
context | Context | required | The activity it is shown in. |
attrs | AttributeSet? | null | From a layout file; null in code. |
Its functions: configure — each with its parameters, what it returns, its errors and an example.
GraineLoaderView.configure
Call it to set what the loader draws — the kind, colour, size and speed.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
look | LoaderLook | required | The loader to draw (LoaderLook) — usually Loaders.resolve(session.appearance.opt("loader")). |
color | Int | required | The colour to draw in, as an ARGB colour int: the look's own color, else your accent. |
track | Int | required | The ring's unlit part, as an ARGB colour int. |
sizeDp | Int? | null | A size of your own, in dp, over the look's — for a small loader in a status line. |
logoUrl | String? | null | The brand's logo, for the mark kind. |
Returns — nothing.
Errors — none.
LoaderLook
Read it to draw the dashboard's loader yourself, with every value worked out.
data class LoaderLook(val kind: String, val color: String?, val px: Int, val periodMs: Long, val text: LoaderText, val skeleton: Boolean)
From Loaders.resolve.
| Field | Type | Default | Meaning |
|---|---|---|---|
kind | String | required | What to draw: dots, pulse, ring, bars or mark. |
color | String? | required | The colour chosen (#RRGGBB); null means use the accent. |
px | Int | required | The loader's size, in dp: 14, 20 or 28. |
periodMs | Long | required | How long one animation cycle takes: 1600, 1100 or 700 ms. |
text | LoaderText | required | The words for each waiting moment, every one filled in (LoaderText). |
skeleton | Boolean | required | true to draw a component's placeholder shape while it loads. |
LoaderStyle
Read it for the loader exactly as chosen in the dashboard, before defaults are applied.
data class LoaderStyle(val kind: String? = null, val color: String? = null, val size: String? = null, val speed: String? = null, val text: LoaderText? = null, val skeleton: Boolean? = null)
From Loaders.normalise; only known words survive. null means “use the default”.
| Field | Type | Default | Meaning |
|---|---|---|---|
kind | String? | null | dots, pulse, ring, bars or mark. |
color | String? | null | "accent" or a #RRGGBB colour. |
size | String? | null | small, medium or large. |
speed | String? | null | calm, normal or quick. |
text | LoaderText? | null | The brand's own words for the waiting moments. |
skeleton | Boolean? | null | Whether components show a placeholder shape while they load. |
LoaderText
Read it for the words shown in each moment the user waits.
data class LoaderText(val connecting: String? = null, val reconnecting: String? = null, val thinking: String? = null, val component: String? = null)
What to expect — null means the default: “Connecting…”, “Reconnecting…”, “Thinking…”, “Loading…”. A word the brand chose is at most 40 characters, often not English — show it as it is.
| Field | Type | Default | Meaning |
|---|---|---|---|
connecting | String? | null | While a call comes up. |
reconnecting | String? | null | While a dropped call is re-established, or a connect retries. |
thinking | String? | null | While the agent works out its answer. |
component | String? | null | While a component the agent announced loads. |
Its functions: forMoment — each with its parameters, what it returns, its errors and an example.
LoaderText.forMoment
Call it to get the words for one waiting moment by name.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
moment | String | required | "connecting", "reconnecting", "thinking" or "component". |
Returns — String? — the words for that moment, or null (a moment with no words of its own, or an unknown name). Example value: "Thinking…".
Errors — none.
Loaders
Use it to turn the dashboard's loader setting into something you can draw.
object Loaders
| Property | Type | Meaning |
|---|---|---|
val KINDS: List<String> | List<String> | The loader kinds the SDK draws: dots, pulse, ring, bars, mark. |
val SIZES: List<String> | List<String> | The sizes: small, medium, large (14, 20 and 28 dp). |
val SPEEDS: List<String> | List<String> | The speeds: calm, normal, quick (1600, 1100 and 700 ms a cycle). |
val MOMENTS: List<String> | List<String> | The waiting moments that have words: connecting, reconnecting, thinking, component. |
const val DEFAULT_KIND = "dots" | String | The kind drawn when none is chosen. |
const val TEXT_MAX = 40 | Int | The longest a moment's words may be, in characters; longer words are cut. |
val DEFAULT_TEXT: LoaderText | LoaderText | The words used where the brand chose none: “Connecting…”, “Reconnecting…”, “Thinking…”, “Loading…”. |
Its functions: normalise, resolve — each with its parameters, what it returns, its errors and an example.
Loaders.normalise
Call it to read the dashboard's loader setting, keeping only the words the SDK knows.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
raw | Any? | required | The appearance's loader value, as served (a JSONObject). |
Returns — LoaderStyle (LoaderStyle) — anything unknown or malformed is dropped. Example value: LoaderStyle(kind=ring, color=accent, size=null, speed=quick, text=null, skeleton=null).
Errors — none.
Loaders.resolve
Call it to get the loader to draw, with every default applied.
Takes the raw setting (anything — junk resolves to the default dots) or a LoaderStyle already read.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
raw | Any? | required | The appearance's loader value, as served. |
s | LoaderStyle | required | A style from Loaders.normalise (the second form). |
Returns — LoaderLook (LoaderLook). Example value: LoaderLook(kind=dots, color=null, px=20, periodMs=1100, text=LoaderText(…), skeleton=false).
Errors — none.
Connectivity
Read it to know whether the voice engine has loaded.
enum class Connectivity { LOADING, READY, RETRYING, FAILED }
What to expect — LOADING → READY; a stalled load goes RETRYING (the SDK reloads it on its own schedule) and, after the last reload, FAILED.
| Case | Meaning |
|---|---|
LOADING | The engine is loading; a start now is placed as soon as it is ready. |
READY | Loaded: a call can start at once. |
RETRYING | Loading stalled and the SDK is trying again. |
FAILED | Every reload was spent: offer “Try again” (voice.retry()). |
ConnectionQuality
Read it to tell the user when the network is the problem.
enum class ConnectionQuality { EXCELLENT, GOOD, POOR, LOST, UNKNOWN }
What to expect — Reported as it changes (onQuality). The same thresholds on every Graine SDK and the website.
| Case | Meaning |
|---|---|
EXCELLENT | Under 150 ms round trip, 30 ms jitter and 2 % loss. |
GOOD | Usable: at or over one of those, under the poor marks. |
POOR | 400 ms round trip, 80 ms jitter or 8 % loss, or worse — say “Weak connection”. |
LOST | Nothing is getting through. |
UNKNOWN | No measurement yet. |
Its functions: classify — each with its parameters, what it returns, its errors and an example.
ConnectionQuality.classify
Call it to rate a connection you measured yourself the way the SDK does.
The worst of the three decides: any one alone can ruin a call.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
rttMs | Double? | required | The round-trip time, in milliseconds. |
jitterMs | Double? | required | The jitter, in milliseconds. |
loss | Double? | required | The packet loss, from 0 to 1. |
Returns — ConnectionQuality — UNKNOWN when all three are null. Example value: ConnectionQuality.GOOD.
Errors — none.
ConnectStage
Read it to say where a call being placed is, so the user waits instead of giving up.
enum class ConnectStage(val wire: String)
What to expect — Only during a connect; null outside one. A stage newer than this SDK is ignored. Nothing could carry the call at all is not a stage: it is GraineVoice.callFailed.
| Property | Type | Meaning |
|---|---|---|
val wire: String | String | The engine's word for the stage. |
| Case | Meaning |
|---|---|
REGISTERING | Setting up the line (a cold start). |
DIALLING | The call is on its way to the agent. |
RETRYING | The first attempt went unanswered and a fresh connection is being made — on mobile data, usually a connection the phone dropped while idle. |
SWITCHING | The call is moving to the other audio path. |
Its functions: from — each with its parameters, what it returns, its errors and an example.
ConnectStage.from
Call it to read a stage word into a ConnectStage.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
s | String? | required | The word: registering, dialling, retrying or switching. |
Returns — ConnectStage? — null for anything else (failed, or a stage newer than this SDK). Example value: ConnectStage.DIALLING.
Errors — none.
CallPhase
Read it for what the voice bar's call is doing, in one word — to draw your own status line.
enum class CallPhase { IDLE, CONNECTING, RECONNECTING, THINKING, SPEAKING, LISTENING }
| Case | Meaning |
|---|---|
IDLE | No call; ready to start. |
CONNECTING | The engine is loading, or a call is being placed. |
RECONNECTING | The engine failed and is being reloaded, or the network dropped the call. |
THINKING | The user finished a sentence and the agent has not answered yet. |
SPEAKING | The agent is talking. |
LISTENING | The agent is waiting for the user. |
Its functions: of — each with its parameters, what it returns, its errors and an example.
CallPhase.of
Call it to work out the phase from state you track yourself, the way the bar does.
First match wins: reconnecting (or the engine retrying or failed) → RECONNECTING; connecting or not ready → CONNECTING; not connected → IDLE; the agent speaking → SPEAKING; waiting for the agent → THINKING; else LISTENING.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
reconnecting | Boolean | required | The call is being re-established after a drop. |
connectivity | Connectivity | required | Whether the engine loaded. |
connecting | Boolean | required | A call is being placed. |
ready | Boolean | required | The engine is ready. |
connected | Boolean | required | The call is live. |
agentSpeaking | Boolean | required | The agent's line is arriving. |
awaitingAgent | Boolean | required | The user finished a line and the agent has not begun its answer. |
Returns — CallPhase. Example value: CallPhase.LISTENING.
Errors — none.
Transport
Pass it in GraineConfig.transport only to force how the voice bar's audio travels.
enum class Transport(val wire: String)
What to expect — Leave transport null and the dashboard's Call audio setting decides. onTransport says which path carries a call, and why it is not the one asked for.
| Property | Type | Meaning |
|---|---|---|
val wire: String | String | The setting's word: ws or rtc. |
| Case | Meaning |
|---|---|
WEBSOCKET | The standard path: the audio on the conversation's own connection. It works on any network; on one that drops packets, the audio drops out too. |
WEBRTC | High-quality audio, with a jitter buffer and the device's echo canceller; falls back to WEBSOCKET. |
Its functions: from — each with its parameters, what it returns, its errors and an example.
Transport.from
Call it to read a transport word into a Transport.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
s | String? | required | The word: "rtc" or "ws". |
Returns — Transport — WEBRTC for "rtc", WEBSOCKET for anything else. Example value: Transport.WEBRTC.
Errors — none.
Several agents in one app
A support agent in Help, a sales agent on Plans — each with its own publishable key, made under its own name with GraineInApp.init(application, config, name = "sales") and reached with GraineInApp.get("sales"). Each agent has its own settings, chat, voice bar, actions, and scheduling and memory requests, sent with its own key. The install ID, who the user is (identify on one is for all; identify(null) signs out of all), the memory choice, the screen and the call engine are the app's. One conversation at a time across every agent. Views take the agent: GraineAgentBarView.graine, GraineChatView.source = GraineInApp.get("sales").chat, GraineChatActivity.open(context, agent), GraineWidgetView.graine, GraineLauncherView.graine; calls.startCall(activity, reason, variables, agent) calls a named one, and a ring Graine sends rings with the agent it is for. An app with one agent names none.
GraineInApp.get
Call it anywhere to reach an agent you made with init.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
name | String | DEFAULT | The name the agent was made under; the default agent when left out. |
Returns — GraineInApp — the agent.
Errors — IllegalStateException when there is none: “GraineInApp.init(application, config) has not been called” for the default agent, or “No agent is named "sales": GraineInApp.init(application, config, name = "sales") first”. Use find when it may not exist.
GraineInApp.find
Call it to reach an agent that may not have been made.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
name | String | required | The name the agent was made under. |
Returns — GraineInApp? — the agent, or null when init made none under that name. Example value: null.
Errors — none.
Screen context, actions and components
Tell the agent what the user is looking at, and let it act in your app through handlers you register. The screen is the app's: reported on any agent, every agent has it and sends it on while it has a live conversation. Values are masked on the device before anything leaves it — card and account numbers, government ID numbers, phone numbers and email addresses, plus your own rules — so report real values. The agent's components (product lists, plan comparisons, choices, time slots, information cards) are drawn by GraineWidgetView, which the voice bar and the chat already hold; a view of your own answers them with answerWidget.
GraineInApp.setScreen
Call it on every meaningful change of what the user can see, so the agent talks about the right thing.
An unchanged screen is not resent. The first change goes at once and later ones at most every 400 ms, the latest always last — a screen updated on every keystroke is neither flooded nor stalled. After a pause, the agent names the field that is INVALID or EMPTY instead of asking “need a hand?”. null clears it.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
next | GraineScreen? | required | What the user can see now (GraineScreen), or null. |
Returns — nothing.
Errors — none.
GraineInApp.updateField
Call it when one field changes, so a screen described once need not be described again on every keystroke.
The field replaces the one with the same name on the current screen — or is added, if the screen has none — and the agent gets the update like any other screen change. Before any screen has been reported it does nothing.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
field | GraineField | required | The field as it is now (GraineField). |
Returns — nothing.
Errors — none.
GraineInApp.removeField
Call it when a field leaves the screen.
Nothing happens when it was never reported.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
name | String | required | The field's name. |
Returns — nothing.
Errors — none.
GraineInApp.clearScreen
Call it when the user leaves a screen — in the screen's onStop or onDestroy.
With screenId, only if that screen is still the one reported — a screen closing late must not erase the one that replaced it. null clears whatever is there.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
screenId | String? | null | The screen's screen ID, to clear only that one. |
Returns — nothing.
Errors — none.
GraineInApp.setVariables
Call it when your app learns something the agent's prompt uses — a plan, a cart total.
Merged over what is there, not replaced: null under a key removes it, null for the whole map clears them all. Numbers and booleans are written out as text; nested maps and lists are dropped. Masked on the device. Reaches the next call, and a live voice-bar call picks it up. A conversation carries up to 12 variables, about 1 KB — past that, the biggest are left off first, who_called and call_reason last.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
values | Map<String, Any?>? | required | Prompt variables by name. |
Returns — nothing.
Errors — none.
GraineInApp.currentVariables
Call it to check, while you build, exactly which prompt variables the agent will receive.
In order of precedence, each later one winning: the dashboard's defaults, GraineConfig.variables, setVariables, then the signed-in user's traits (without the trait that identifies them).
Parameters — none.
Returns — Map<String, String>. Example value: {plan=gold, cart_total=42, name=Asha}.
Errors — none.
GraineInApp.track
Call it to tell the agent about something the user did that informs without interrupting — a coupon applied, a filter changed.
It rides along with the screen, among the user's recent events (the last ten are kept), for every agent. Masked on the device.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
name | String | required | The event's name, such as "coupon_applied". |
data | Map<String, Any?> | emptyMap() | Details of the event, by name. |
coalesce | Boolean | false | true for context rather than news: it waits for the next screen update instead of sending one of its own (taps work this way). |
Returns — nothing.
Errors — none.
GraineInApp.tap
Call it to tell the agent the user tapped a control the SDK cannot name by itself — a custom-drawn button, say.
Reported as tap:<name> among the screen's recent events, at most twice a second and only while a screen is reported — a tap means something only against the screen it was on. Labelled controls are noticed by themselves (GraineConfig.captureTaps).
| Parameter | Type | Default | Meaning |
|---|---|---|---|
name | String | required | The control's name as the agent should read it: one line, at most 40 characters kept. |
Returns — nothing.
Errors — none.
GraineInApp.reportEvent
Call it when something happens that the agent may want to speak up about — a payment failing, a form being rejected.
It reaches whichever conversation is live — the voice-bar call or the chat — with the current screen sent first, so the agent reads the event against the right one. When no conversation is live there is no one to tell, and the event is discarded.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
name | String | required | What happened, in a word, such as "payment_failed". |
detail | String? | null | A sentence about it for the agent. |
data | Map<String, Any?> | emptyMap() | Details, by name. |
Returns — nothing.
Errors — none.
GraineVoice.reportEvent
Call it to tell the voice-bar call about something the agent may want to speak up about.
Only during a voice-bar call; prefer GraineInApp.reportEvent, which reaches whichever conversation is live.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
name | String | required | What happened, in a word. |
detail | String? | null | A sentence about it. |
data | Map<String, Any?> | emptyMap() | Details, by name. |
Returns — nothing.
Errors — none.
GraineInApp.noteInteraction
Call it when the user does something that is not a field edit — scrolls a list, opens a picker — so the SDK does not think they are stuck.
Restarts the clock that notices a user stuck on a step. Field edits do not count: someone typing one character every forty seconds is exactly who is stuck.
Parameters — none.
Returns — nothing.
Errors — none.
GraineInApp.configureFriction
Call it to change your app's own stuck-user rules while it runs.
Replaces your app's layer of rules — the same layer as GraineConfig.friction — over the dashboard's. Safe at any time; null leaves the dashboard's and the defaults.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
override | FrictionConfig? | required | Your app's rules (FrictionConfig), or null. |
Returns — nothing.
Errors — none.
GraineInApp.registerAction
Call it to let the agent do one thing in your app — open a screen, apply a filter, change a setting.
Declare the action on the agent in the dashboard too: a handler with no declaration is never offered, and a declared action with no handler is refused (“This app build does not implement "…".”). The handler runs on the main thread (it will usually touch your UI) with the agent's arguments, and must answer within six seconds (a little sooner on a native call), or the agent is told it was not done (“The app did not answer within 6 seconds.”). A handler that throws answers error with its message. Defining an action again under the same name replaces its handler. Your app's own screen list (GraineScreen.availableActions) narrows what is offered on a screen.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
name | String | required | The action's name, exactly as it is declared on the agent (Embed & Widgets → Actions). |
handler | (JSONObject) -> ActionResult | required | Runs the action with the agent's arguments and says how it went (ActionResult). Tell the truth: the agent speaks about exactly this. |
Returns — () -> Unit — call it to unregister this handler.
Errors — none.
GraineInApp.registerDeferredAction
Call it instead of registerAction when the answer is not ready when the handler returns — a network call, a confirmation the user taps.
Your handler is called on the main thread; call answer exactly once, from whichever thread finishes the work. You still have six seconds: after that the agent has been told the action did not complete, so a late answer is ignored, and so is any answer after the first.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
name | String | required | The action's name, as declared on the agent. |
handler | (args: JSONObject, answer: (ActionResult) -> Unit) -> Unit | required | Starts the action and calls answer with how it went. |
Returns — () -> Unit — call it to unregister this handler.
Errors — none.
GraineInApp.checkActions
Call it once at start-up, while you build, to see which of your actions the agent can actually call.
Compares your handlers with the actions declared on the agent. Read it after the settings load (onSessionReady); before, nothing is declared yet. The SDK also logs a warning for declared actions with no handler.
Parameters — none.
Returns — Map<String, List<String>> with three keys — usable (registered and declared), undeclared (registered, never offered), unhandled (declared, refused when asked). Example value: {usable=[set_language], undeclared=[], unhandled=[open_screen]}.
Errors — none.
GraineInApp.clearPendingWidget
Call it to forget a component the agent drew that you no longer want shown — answered elsewhere, or from a call that is over.
Clears pendingWidget. Answering or dismissing a component, and a new call starting, do it by themselves.
Parameters — none.
Returns — nothing.
Errors — none.
GraineScreen
Pass it to setScreen to describe what the user can see.
data class GraineScreen @JvmOverloads constructor(…)
What to expect — Values are masked on the device before they leave it — report real ones; a screen that pre-redacts only blinds the agent. An unchanged screen is not sent again; the first change goes at once and later ones at most every 400 ms.
| Field | Type | Default | Meaning |
|---|---|---|---|
screen | String | required | A stable ID for the screen, such as "checkout_address"; keep it constant — friction rules and clearScreen match on it. |
title | String? | null | The screen's title as the user sees it — what the agent calls the screen. |
journey | Journey? | null | Where the screen sits in a flow (GraineScreen.Journey), so the agent can say “step 2 of 3”. |
fields | List<GraineField> | emptyList() | The inputs on the screen (GraineField): what lets the agent name the field that is wrong. |
error | String? | null | A screen-level error banner, as shown — distinct from a field's own. |
data | Map<String, Any?> | emptyMap() | Anything else the agent should know about the screen, by name. |
availableActions | List<String>? | null | Which of your registered actions work on this screen: null offers every registered action, an empty list none. |
GraineScreen.Journey
Pass it in GraineScreen.journey to say where a screen sits in a flow.
data class Journey @JvmOverloads constructor(val name: String, val step: Int? = null, val of: Int? = null, val next: String? = null)
| Field | Type | Default | Meaning |
|---|---|---|---|
name | String | required | The flow's name, such as "Checkout". |
step | Int? | null | Which step this screen is, counting from 1. |
of | Int? | null | How many steps the flow has. |
next | String? | null | What comes next, such as "Payment" — so the agent can say what is left. |
GraineField
Pass it in GraineScreen.fields (or to updateField) to describe one input.
data class GraineField @JvmOverloads constructor(val name: String, val label: String? = null, val value: String? = null, val status: Status? = null, val error: String? = null, val optional: Boolean = false)
What to expect — Its status — empty, filled, invalid, pending or locked — is what lets the agent name the field that is wrong.
| Field | Type | Default | Meaning |
|---|---|---|---|
name | String | required | A stable ID for the field, by which updateField and removeField find it. |
label | String? | null | The label the user sees — what the agent calls the field. |
value | String? | null | The real value; it is masked on the device on its way out. Never pre-redact it. |
status | Status? | null | Where the field stands (GraineField.Status). |
error | String? | null | The validation message actually shown — what the agent can offer to fix. |
optional | Boolean | false | true when leaving it blank is fine, so the agent does not count it as work left. |
GraineField.Status
Use it to say where a field stands.
enum class Status(val wire: String) { EMPTY("empty"), FILLED("filled"), INVALID("invalid"), PENDING("pending"), LOCKED("locked") }
What to expect — INVALID and EMPTY (when not optional) are what make the agent speak up about a field.
| Property | Type | Meaning |
|---|---|---|
val wire: String | String | The word sent to the agent. |
| Case | Meaning |
|---|---|
EMPTY | Nothing entered yet. |
FILLED | Entered and accepted. |
INVALID | Entered and refused — set error to the message shown. |
PENDING | Being checked — a code being verified, an upload in progress. |
LOCKED | Not the user's to change; the agent does not ask them to. |
ActionResult
Return it from an action handler to tell the agent what happened.
data class ActionResult(val status: Status, val message: String = "", val data: Map<String, Any?> = emptyMap())
What to expect — The agent speaks about exactly this, so tell the truth: ok only when it really happened. refused with a reason is more useful than a false success.
| Field | Type | Default | Meaning |
|---|---|---|---|
status | Status | required | How it went (ActionResult.Status). |
message | String | "" | What the agent is told — not shown to the user; the agent decides how to say it. |
data | Map<String, Any?> | emptyMap() | Structured details the agent can use in its reply. |
Its functions: ok, refused, error — each with its parameters, what it returns, its errors and an example.
ActionResult.Status
Read it to know how an action went.
enum class Status(val wire: String) { OK("ok"), REFUSED("refused"), ERROR("error") }
| Property | Type | Meaning |
|---|---|---|
val wire: String | String | The word sent to the agent. |
| Case | Meaning |
|---|---|
OK | It happened; the agent tells the user it is done. |
REFUSED | The action will not be done, with the reason — often the most helpful thing to tell the agent. |
ERROR | It was tried and failed. |
ActionResult.ok
Return it when the action happened.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
message | String | "" | What the agent is told happened. |
data | Map<String, Any?> | emptyMap() | A structured result for the agent. |
Returns — ActionResult with status OK.
Errors — none.
ActionResult.refused
Return it when the action will not happen, and say why.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
message | String | required | Why not, for the agent to explain. |
Returns — ActionResult with status REFUSED.
Errors — none.
ActionResult.error
Return it when the action was tried and failed.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
message | String | required | What went wrong, for the agent. |
Returns — ActionResult with status ERROR.
Errors — none.
Masking
Use it to mask a value shape only your app knows, before anything leaves the device.
object Masking
Applied to screens, variables, traits and events before they leave the device: card and account numbers, government ID numbers, phone numbers and email addresses are replaced with [CARD], [PAN], [AADHAAR], [PHONE] and [EMAIL]; rules you add apply after them.
What to expect — One set of rules for the whole app. Graine masks again on arrival; masking here means the value is never sent at all.
Its functions: mask, addRule, clearCustomRules, deep — each with its parameters, what it returns, its errors and an example.
Masking.mask
Call it to see how a text will look once the SDK masks it — in a test, for example.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
input | String | required | The text to mask. |
Returns — String — the text with every match replaced. Example value: "Call me on [PHONE]".
Errors — none.
Masking.addRule
Call it once at start-up to mask a value shape only your app knows — an account number, say.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
pattern | Regex | required | What to mask. |
token | String | "[REDACTED]" | What replaces each match. |
Returns — nothing.
Errors — none.
Masking.clearCustomRules
Call it to remove every rule you added (the built-in ones stay).
Parameters — none.
Returns — nothing.
Errors — none.
Masking.deep
Call it to mask every text inside a JSON value the way the SDK does.
Keys are kept; every string value, at any depth of a JSONObject or JSONArray, is masked. Numbers, booleans and null pass through.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
value | Any? | required | A String, JSONObject, JSONArray or anything else. |
Returns — Any? — a masked copy of the same shape. Example value: {"note":"Reach me at [EMAIL]"}.
Errors — none.
FrictionConfig
Pass it in GraineConfig.friction (or to configureFriction) to tune when the SDK decides a user is stuck.
data class FrictionConfig @JvmOverloads constructor(…)
Your app's layer of stuck-user rules, over the dashboard's (Embed & Widgets → friction), over the built-in defaults. When a user sits on one screen with a field outstanding through a stretch of a live conversation, the SDK tells the agent — which can offer help — and your app hears GraineListener.onFriction. Most apps set nothing here; use it for screens only your app knows about — a terms page nobody is stuck on, a code-entry step where twenty seconds is already long.
What to expect — Every field is optional; null inherits the layer below.
| Field | Type | Default | Meaning |
|---|---|---|---|
enabled | Boolean? | null | Set it to false to switch stuck-user detection off for the whole app. |
idleAfterMs | Long? | null | How long on one screen counts as stuck — 45 seconds by default; 0 or less switches the rule off. |
cooldownMs | Long? | null | The least time between two signals on the same screen — 60 seconds by default. |
cooldownSeconds | Int? | null | How long the agent waits between offers of help it makes unasked — 20 seconds by default. |
maxPerSession | Int? | null | How many unasked offers of help the agent may make in one conversation — 3 by default. |
openLauncher | Boolean? | null | Passed on in FrictionSignal.openLauncher for an app that opens its own launcher when the user looks stuck. |
message | String? | null | What the agent is told instead of the built-in sentence; {title}, {screen}, {seconds}, {field} and {error} are filled in. |
requireOutstandingField | Boolean? | null | true by default: count only while a field is outstanding (invalid, or empty and not optional), from when it became so. false counts time on the screen at all. |
screens | Map<String, Screen> | emptyMap() | Rules for single screens, by the screen ID your app reports (FrictionConfig.Screen). |
Its functions: fromJson — each with its parameters, what it returns, its errors and an example.
FrictionConfig.Screen
Use it in FrictionConfig.screens to change the rules on one screen.
data class Screen(val ignore: Boolean? = null, val idleAfterMs: Long? = null, val message: String? = null)
| Field | Type | Default | Meaning |
|---|---|---|---|
ignore | Boolean? | null | true turns stuck-user detection off on this screen — someone reading terms is not stuck. |
idleAfterMs | Long? | null | How long counts as stuck on this screen; 0 or less switches the rule off here. |
message | String? | null | What the agent is told on this screen instead of the sentence above it. |
FrictionConfig.fromJson
Call it to read a friction document — the dashboard's format — into a FrictionConfig.
Reads enabled, idle (false, or { afterMs }), cooldownMs, nudge (cooldownSeconds, maxPerSession, openLauncher), message, requireOutstandingField and screens; unknown keys are dropped, and a number may be written as text.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
o | JSONObject? | required | The document, such as SessionConfig.friction. |
Returns — FrictionConfig? — null for null. Example value: FrictionConfig(enabled=true, idleAfterMs=30000, …).
Errors — none.
FrictionRules
Read it to see the stuck-user rules actually in force — the defaults, the dashboard's and your app's together.
data class FrictionRules(…)
GraineInApp.frictionRules.
| Field | Type | Default | Meaning |
|---|---|---|---|
enabled | Boolean | true | Whether stuck-user detection is on. |
idleAfterMs | Long? | 45_000 | How long on one screen counts as stuck; null when the rule is off. |
cooldownMs | Long | 60_000 | The least time between two signals on one screen. |
cooldownSeconds | Int | 20 | How long the agent waits between unasked offers of help. |
maxPerSession | Int | 3 | How many unasked offers of help the agent may make in one conversation. |
openLauncher | Boolean | false | Passed on in each FrictionSignal. |
message | String | "" | The sentence the agent is told; empty uses the built-in one. |
requireOutstandingField | Boolean | true | Whether only time with a field outstanding counts. |
screens | Map<String, FrictionConfig.Screen> | emptyMap() | The rules for single screens. |
Its functions: forScreen — each with its parameters, what it returns, its errors and an example.
FrictionRules.forScreen
Call it to see the rules on one screen, with that screen's own rules applied.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
id | String? | required | The screen's screen ID. |
Returns — FrictionRules.ForScreen (FrictionRules.ForScreen). Example value: ForScreen(ignore=false, idleAfterMs=45000, message=).
Errors — none.
FrictionRules.ForScreen (type)
Read it for the rules on one screen.
data class ForScreen(val ignore: Boolean, val idleAfterMs: Long?, val message: String)
| Field | Type | Default | Meaning |
|---|---|---|---|
ignore | Boolean | required | true when nothing is detected on this screen. |
idleAfterMs | Long? | required | How long counts as stuck here; null when the rule is off. |
message | String | required | The sentence the agent is told here; empty uses the built-in one. |
FrictionSignal
Read it in onFriction to react when the user looks stuck — badge a control, count it.
data class FrictionSignal(val name: String, val screen: String, val detail: String, val data: Map<String, Any?>, val cooldownSeconds: Int, val maxPerSession: Int, val openLauncher: Boolean)
What to expect — At most once per visit to a screen, and never twice on one screen within the cooldown. The agent has already been told.
| Field | Type | Default | Meaning |
|---|---|---|---|
name | String | required | What kind of signal: stalled_on_step. |
screen | String | required | The screen ID the user is stuck on. |
detail | String | required | The sentence the agent was given, such as “The customer has not touched the "Delivery address" screen for 45s. The "Postal code" field is showing: We don't deliver there yet.”. |
data | Map<String, Any?> | required | seconds, field and error: how long, which field, and its error or status. |
cooldownSeconds | Int | required | The pacing for the agent's unasked offers of help. |
maxPerSession | Int | required | How many unasked offers the agent may make in one conversation. |
openLauncher | Boolean | required | Your rules' openLauncher: open your launcher when true, if you want. |
GraineWidgetView
Place it in your layout to draw the agent's components outside the voice bar and the chat.
class GraineWidgetView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : LinearLayout(context, attrs), GraineListener
The component the agent put on screen, drawn by the SDK: a product list in five looks (gallery, cards, list, spotlight, compact), a plan comparison, an information card, chips for choices and quick actions, calendar slots — and, for a kind this build cannot draw, the agent's own fallback sentence. Where it goes follows one rule: a product list is the screen, a choice to finish slides up as a sheet, a fact sits inline as a card. A tap answers the agent with what was chosen and the sentence that is the user's turn; a component that names an action of yours runs it first. Closing is not answering: the agent is told quietly, and reopen() puts it back. GraineAgentBarView and GraineChatView hold one already.
What to expect — Main thread. Everything visual comes from the agent's design; a component's own style still wins over it, and the properties below override one thing each.
| Constructor parameter | Type | Default | Meaning |
|---|---|---|---|
context | Context | required | The activity it is shown in. |
attrs | AttributeSet? | null | From a layout file; null in code. |
| Property | Type | Meaning |
|---|---|---|
var accent: String? | String? | Set it (#RRGGBB) to colour buttons and badges; null uses the component's own, then the design's. |
var radius: Int? | Int? | Set it (dp) to round the corners; null uses the component's own, then its platform's, then the design's. |
var scheme: String? | String? | Set it to force "light", "dark" or "auto"; null follows the design. |
var theme: Theme? | Theme? | Set it to override single colours (GraineWidgetView.Theme); null uses the design. |
var onChange: (() -> Unit)? | (() -> Unit)? | Set it to hear every change of what is drawn — to show your own “Show that again”, say. |
val widget: GraineWidget? | GraineWidget? | Read it for the component on screen, if any (GraineWidget). |
val answered: GraineAnswer? | GraineAnswer? | Read it for what the user last chose, kept as a line until the next component or a dismiss (GraineAnswer). |
val closedWidget: GraineWidget? | GraineWidget? | Read it for the last component closed without an answer — reopen() puts it back. |
val isLoading: Boolean | Boolean | Read it to know a component is on its way and its loader is showing. |
var graine: GraineInApp? | GraineInApp? | Set it to draw another agent's components (GraineInApp.get("support")); the default agent unless set. |
Its functions: present, presentPending, dismiss, reopen — each with its parameters, what it returns, its errors and an example.
GraineWidgetView.Theme
Pass it in GraineWidgetView.theme to override single colours of the components.
data class Theme(val surface: String? = null, val border: String? = null, val text: String? = null, val muted: String? = null, val onAccent: String? = null)
What to expect — Each a #RRGGBB colour; null keeps the design's.
| Field | Type | Default | Meaning |
|---|---|---|---|
surface | String? | null | The component's background. |
border | String? | null | Its outlines and separators. |
text | String? | null | Its main text. |
muted | String? | null | Its secondary text. |
onAccent | String? | null | Text and icons drawn on the accent. |
GraineWidgetView.present
Call it to draw a component you received some other way — a preview, a test — exactly as if the agent sent it.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
frame | JSONObject | required | The component as the agent sends it: widget_id, kind, data, fallback_text, style, on_select. |
Returns — nothing.
Errors — none.
GraineWidgetView.presentPending
Call it to show the loader for a component on its way — in a preview of your own.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
hint | JSONObject | required | What is coming, as the agent announces it (its kind, for the loader's shape). |
Returns — nothing.
Errors — none.
GraineWidgetView.dismiss
Call it to close the component without choosing — from your own close control.
The agent is told quietly, so it does not go on describing options nobody can see and does not speak up because someone closed a card. reopen() puts it back.
Parameters — none.
Returns — nothing.
Errors — none.
GraineWidgetView.reopen
Call it from your own “Show that again” control.
Puts back the last component the user closed, exactly as it was, without asking the agent again.
Parameters — none.
Returns — nothing.
Errors — none.
GraineInApp.answerWidget
Call it from a component view of your own when the user picks something, to answer the agent on whichever conversation is live.
It answers the voice-bar call while one is live, otherwise the chat while it is connected — the structured answer, then summary as the user's turn, exactly as the SDK's own component views send them. summary is the sentence the agent hears as the user's turn (“The gold plan, please”); without it the answer lands and the conversation does not move. data goes to your webhook. It clears pendingWidget. Main thread. GraineWidgetView answers for itself.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
widgetId | String | required | The component's id (GraineWidget.id). |
data | Map<String, Any?> | required | What was chosen, structured — for your webhooks and the agent. |
summary | String | required | The sentence that is the user's turn. |
Returns — nothing.
Errors — none.
GraineVoice.answerWidget
Call it from a component view of your own when the user picks something, to answer the agent.
Only the voice-bar call; prefer GraineInApp.answerWidget, which answers whichever conversation is live — the call or the chat. summary is the sentence the agent hears as the user's turn (“Chose the gold plan”); without it the tap lands and the conversation does not move. GraineWidgetView does this itself.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
widgetId | String | required | The component's id (GraineWidget.id). |
data | Map<String, Any?> | required | What was chosen, structured — for your webhooks and the agent. |
summary | String | required | The sentence that is the user's turn. |
Returns — nothing.
Errors — none.
GraineWidget
Read it to draw a component the agent sent, in a view of your own.
data class GraineWidget(val id: String, val kind: String, val data: JSONObject, val fallbackText: String, val style: WidgetStyle, val onSelect: WidgetSelectAction)
Carried by GraineWidgetView.widget and GraineChatTurn.component (where it is called GraineComponent).
What to expect — Pictures given as root-relative addresses are already made absolute. A kind you do not know is a card with fallbackText.
| Field | Type | Default | Meaning |
|---|---|---|---|
id | String | required | The component's ID: what an answer refers to. |
kind | String | required | What it is: product_list, plan_comparison, choices, quick_actions, calendar_slots, info_card — or a kind the agent declares. |
data | JSONObject | required | What to draw: the items, choices or slots. |
fallbackText | String | required | What the agent would have said instead — draw it for a kind you cannot. |
style | WidgetStyle | required | How the dashboard asked for it to look (WidgetStyle), already checked. |
onSelect | WidgetSelectAction | required | What your app does on a tap, beyond telling the agent (WidgetSelectAction). |
Its functions: adopt — each with its parameters, what it returns, its errors and an example.
GraineWidget.adopt
Call it to turn the component JSON from onWidget into a GraineWidget.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
raw | JSONObject? | required | The component as GraineListener.onWidget hands it over. |
baseUrl | String | required | Your GraineConfig.baseUrl, to make root-relative pictures absolute. |
Returns — GraineWidget? — null when it names no widget_id (a component that cannot be answered is not one to draw). Example value: GraineWidget(id=w1, kind=choices, …).
Errors — none.
GraineAnswer
Read it for what the user chose on a component.
data class GraineAnswer(val widget: GraineWidget, val data: Map<String, Any?>, val summary: String)
GraineWidgetView.answered.
| Field | Type | Default | Meaning |
|---|---|---|---|
widget | GraineWidget | required | The component that was answered. |
data | Map<String, Any?> | required | What was chosen, structured — for your webhooks. |
summary | String | required | The sentence the agent heard as the user's turn. |
WidgetAnswer
Read it for an answer to a component: what was chosen and the sentence the agent hears.
data class WidgetAnswer(val data: Map<String, Any?>, val summary: String)
| Field | Type | Default | Meaning |
|---|---|---|---|
data | Map<String, Any?> | required | What was chosen, structured. |
summary | String | required | The sentence the agent hears as the user's turn. |
WidgetStyle
Read it to draw a component in the look the dashboard chose for it.
data class WidgetStyle(…)
Chosen on the component in the dashboard and carried with it. Only known words survive; null means the default. The words are the dashboard's own, so a view of your own compares against the same words.
| Field | Type | Default | Meaning |
|---|---|---|---|
preset | String? | null | The overall look: gallery, cards, list, spotlight or compact (cards when none is chosen). |
imageSize | String? | null | How big pictures are: small, medium or large. |
density | String? | null | How much room rows get: comfortable or compact. |
button | String? | null | How a row's button looks: filled, outline or row (the whole row is the button). |
facts | Boolean? | null | Whether a row's extra facts are shown. |
columns | Int? | null | 1 or 2 columns, for the gallery and cards looks. |
surface | String? | null | Where it goes: auto, screen, sheet or card. |
radius | Int? | null | Its corner rounding, 0–24 dp. |
accent | String? | null | Its own accent colour, #RRGGBB. |
platform | String? | null | Which phone's shapes to use: auto, ios, material or oneui. |
select | String? | null | Whether the user picks one or many. |
maxSelect | Int? | null | How many may be picked with many, 2–8. |
WidgetSelectAction
Read it to know what your app should do when the user taps a component's item.
data class WidgetSelectAction(val path: String? = null, val action: String? = null, val args: Map<String, String>? = null, val say: String? = null)
Beyond telling the agent: one of your registered actions, or a screen path for your open_screen action, with arguments filled from the tapped item ({id}, {title}, …).
| Field | Type | Default | Meaning |
|---|---|---|---|
path | String? | null | A screen in your app to open, run through your open_screen action. |
action | String? | null | The registered action to run. |
args | Map<String, String>? | null | Its arguments; {name} placeholders are filled from the tapped item. |
say | String? | null | What the agent hears about the tap. |
| Property | Type | Meaning |
|---|---|---|
val isEmpty: Boolean | Boolean | Read it to know the component names no action and no path: a tap only answers the agent. |
Calls the user places
Native calls, like WhatsApp: the user calls the agent with startCall, or the agent rings the user (Calls Graine rings). The call runs in your app's own process, with the platform's microphone and echo canceller; it rings with a call notification and a full-screen call screen over the lock screen, keeps going with the screen off in a microphone foreground service, and reports what became of it with the call's ID for your server. One call at a time, and never alongside a voice-bar call: starting one ends the other.
What a call does.
- Its audio travels over the WebSocket — or over WebRTC, prepared at the tap, when the app includes the WebRTC module (Adding WebRTC calls); a call WebRTC cannot set up carries on over the WebSocket inside the same call. The timings below are the WebSocket's; over WebRTC, a network drop is retried once, then the call ends
ENDEDwithFAILEDand “The call's connection was lost.”. - It starts at once on the agent's settings already kept on the device (this launch's, or the last ones), and refreshes them behind the call instead of waiting for the network first.
- Opening the call has 15 seconds: then it fails —
GraineCallOutcomeFAILED,error“The call could not connect within 15 seconds.”, and the call screen says “Call failed”. - Once connected, nothing at all arriving for 50 seconds — two 25-second keep-alive periods, which a live connection always answers — ends the call:
ENDEDwithendReasonFAILEDanderror“Nothing arrived for 50 seconds: the connection was lost.”; the call screen says “Call failed”. - Sending never holds the call up on a slow network: at most 300 ms of the user's audio waits to be sent, the oldest dropped first when the line falls behind; nothing else is ever dropped.
- Hanging up lets go of the microphone at once, and a close the network cannot carry is forced within about a second.
- The prompt's variables travel inside the call's one-time connection pass, not in the connection's address; a direct connection, used when no pass can be had, still carries them in its address.
- A microphone that stops mid-call is rebuilt once; if it cannot be, the call ends
ENDEDwithFAILEDand “The microphone stopped working.”. If the phone takes the call's audio (a phone call ringing, another app's call or player), the call holds — nothing is sent or played — until it comes back; answering a phone call during the hold ends the agent's call (ENDED,LOCAL).
GraineCalls
Use it for native calls: configure them, place one, control it, and read its state.
class GraineCalls internal constructor(…)
GraineInApp.get().calls — the app's one call engine, whichever agent it is reached through: startCall names the agent a call is with, and a ring Graine sends rings with the agent it is for, in its design. Configure it once in Application.onCreate; hand Firebase's token and messages to the companion functions; place calls from an activity in front.
What to expect — Main thread, except handlePush. Listeners are called on the main thread.
| Property | Type | Meaning |
|---|---|---|
var config: GraineCallsConfig | GraineCallsConfig | Read it for how calls look and ring now (GraineCallsConfig). Read-only; set it with configure. |
var state: GraineCallState | GraineCallState | Read it to know where the call is (GraineCallState); changes reach onCallState. Read-only. |
var call: GraineCall? | GraineCall? | Read it for the call in progress (GraineCall), or null. Read-only. |
var muted: Boolean | Boolean | Read it to show the mute control: the call goes on, nothing the user says is sent. Read-only. |
var speaker: Boolean | Boolean | Read it to show the loudspeaker control: on, or the call is on a headset (if one is connected) or the earpiece. Read-only. |
var agentSpeaking: Boolean | Boolean | Read it to animate the agent's avatar while its voice is heard. Read-only. |
var caption: Triple<Role, String, Boolean>? | Triple<Role, String, Boolean>? | Read it for the last line on the call — who, the words, still arriving — for a screen that opens mid-call. Read-only. |
var connectedAt: Long | Long | Read it to show the call's length: when it connected, in SystemClock.elapsedRealtime() milliseconds (which keep counting in deep sleep); 0 before. Read-only. |
val callAgent: GraineInApp? | GraineInApp? | Read it for the agent the call in progress is with — for a call screen of your own to draw in its design; null with no call. |
val transport: String? | String? | Read it for how the call in progress carries its audio: "webrtc" or "websocket"; null with no call. Decided when the call starts — at the tap, or when its ring arrives — and final once it is ACTIVE: read it then. Calls in both directions (Adding WebRTC calls). |
val transportFallbackReason: String? | String? | Read it for why a call that would have used WebRTC runs on the WebSocket instead: mint_failed, register_failed, no_response, no_answer, ice_timeout, error, dashboard or unavailable (each explained); null for every other call, and with no call. Final once the call is ACTIVE. |
companion · const val ACTION_ANSWER | String | "ai.graine.inapp.action.ANSWER_CALL": your own call screen was opened to answer (the notification's Answer). |
companion · const val ACTION_SHOW | String | "ai.graine.inapp.action.SHOW_CALL": your call screen was opened to show the call. |
companion · const val ACTION_DECLINE | String | "ai.graine.inapp.action.DECLINE_CALL": the ring notification's Decline, handled by the SDK. |
companion · const val ACTION_HANGUP | String | "ai.graine.inapp.action.HANG_UP": the ongoing-call notification's Hang up, handled by the SDK. |
companion · const val ACTION_MISSED | String | "ai.graine.inapp.action.SHOW_MISSED_CALL": your call screen was opened on a missed call. |
companion · const val ACTION_CALL_BACK | String | "ai.graine.inapp.action.CALL_BACK": your call screen was opened to call the agent back after a missed call. |
companion · const val EXTRA_CALL_ID | String | "ai.graine.inapp.extra.CALL_ID": which call an intent is about. |
companion · const val EXTRA_AGENT | String | "ai.graine.inapp.extra.AGENT": which agent (its init name) that call is with. |
companion · const val EXTRA_CALLER_NAME | String | "ai.graine.inapp.extra.CALLER_NAME": who a missed call was from, for the missed screen. |
companion · const val EXTRA_REASON | String | "ai.graine.inapp.extra.REASON": why a missed call was placed, carried into the call back. |
companion · const val MISSED_REASON = "missed_call" | String | What a call back tells the agent (as call_reason) when the missed call had no reason of its own. |
companion · const val MIC_REQUEST = 0x6E7B | Int | The request code the SDK uses for the call's microphone prompt — what onRequestPermissionsResult recognises. |
val callPermissions: GraineCallPermissions | GraineCallPermissions | Read it for the call permissions the phone holds now (GraineCalls.callPermissions). |
val callSetupProblems: List<GraineCallSetupProblem> | List<GraineCallSetupProblem> | Read it for what the app's setup still lacks for calls, each with its fix (GraineCalls.callSetupProblems). |
Its functions: configure, startCall, end, setMuted, setSpeaker, onRequestPermissionsResult, onNewToken, handlePush, answer, decline, callBack, addListener, removeListener, requestCallPermissions, openCallSettings — each with its parameters, what it returns, its errors and an example.
GraineCalls.configure
Call it once in Application.onCreate, after GraineInApp.init, to set how calls ring and what they show.
Creates the notification channels (Incoming calls, Incoming calls (app ringtone), Missed calls, Ongoing calls). Configure before a push can arrive.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
config | GraineCallsConfig | required | The name, icon, ringtone, call screen, ring time and how calls carry their audio (GraineCallsConfig). |
Returns — nothing.
Errors — none.
GraineCallsConfig
Pass it to calls.configure to set how calls ring and what they show.
data class GraineCallsConfig @JvmOverloads constructor(…)
What to expect — Set once, at launch. The ring's look and words otherwise come from the agent's design (Garage → Call screen).
| Field | Type | Default | Meaning |
|---|---|---|---|
displayName | String | "Assistant" | The name on the ring and the call screen when the push names no caller (a call Graine rang uses the agent's call name). |
smallIcon | Int | 0 | A monochrome drawable for the notifications' status-bar icon; 0 uses the platform's call icon. |
ringtone | Uri? | null | Your own ring; it wins over the design's ring. null uses the design's (the phone's ringtone, or the brand's Graine or Soft tone). |
callActivity | Class<out Activity>? | null | Your own call screen; null uses the SDK's GraineCallActivity. Yours draws calls.state and calls the same functions, and forwards its permission answers to calls.onRequestPermissionsResult. |
ringTimeoutMs | Long | 30_000 | How long a call from your own server rings before it is missed. A call Graine rang rings for the agent's own ring time (GraineSchedulingRules.ringTimeoutSeconds). |
callTransport | GraineCallTransport | GraineCallTransport.AUTO | How calls carry their audio, in both directions (GraineCallTransport): AUTO, WebRTC when the app includes ai.graine:inapp-android-rtc and the dashboard allows it, else the WebSocket; WEBSOCKET; or WEBRTC. A call WebRTC cannot set up carries on over the WebSocket within the same call. |
GraineCallTransport
Pass it in GraineCallsConfig.callTransport to choose how native calls carry their audio.
enum class GraineCallTransport { AUTO, WEBSOCKET, WEBRTC }
For calls in both directions — the ones the user places with startCall, and the ones Graine rings and the user answers. WebRTC needs the module ai.graine:inapp-android-rtc in the app (Adding WebRTC calls); without it every call uses the WebSocket, as before.
What to expect — Decided when a call starts — at the tap, or when its ring arrives — and the call is prepared there. The agent's In-app call audio set to Standard in the dashboard puts calls on the WebSocket whatever you chose. A call WebRTC cannot set up before any audio flows carries on over the WebSocket inside the same call — no second ring, no error; once audio flows it never moves. calls.transport says which one carried the call, and calls.transportFallbackReason why it is not WebRTC.
| Case | Meaning |
|---|---|
AUTO | The default: WebRTC when the module is in the app and the dashboard allows it, else the WebSocket. |
WEBSOCKET | Always the WebSocket, even with the module in the app. |
WEBRTC | WebRTC wherever it can carry the call; the WebSocket only when the module is missing or WebRTC cannot set up this call. |
Why a call is on the WebSocket — calls.transportFallbackReason, set only for a call that would have used WebRTC and runs on the WebSocket; null for every other call. Read it once the call is ACTIVE.
| Value | Meaning |
|---|---|
mint_failed | The call's WebRTC session could not be set up: refused, rate-limited, or unreachable. |
register_failed | Graine's media service did not accept the connection, or never answered it. |
no_response | The call got no reply at all. |
no_answer | Replies came; the answer did not. |
ice_timeout | Answered, but the audio path never connected — a network that blocks WebRTC. |
error | Anything else before the audio flowed. |
dashboard | The agent's In-app call audio is Standard. |
unavailable | The app asked for WEBRTC, and this build has no WebRTC module. |
GraineCalls.startCall
Call it from a “Call us” button to let the user call the agent.
The call screen opens and the call connects — at once, on the settings already kept on the device. reason becomes the prompt's call_reason (and who_called is customer); variables are this call's own prompt variables, over the SDK's, masked on the device. It ends any voice-bar call first, and asks for the microphone from the call screen if the app does not hold it (no answer within 60 seconds fails the call).
| Parameter | Type | Default | Meaning |
|---|---|---|---|
activity | Activity | required | The activity in front — the microphone service may only start from there. |
reason | String? | null | Why the user is calling — the agent hears it as call_reason. Blank is none. |
variables | Map<String, Any?> | emptyMap() | Prompt variables for this call only. |
agent | String | GraineInApp.DEFAULT | Which agent to call, by the name init was given. |
Returns — String? — the call's ID (call_ and a UUID), which every outcome carries; null when a call is already live or no agent has that name (the log says so). Example value: "call_6f1c2a9e-7d1b-4c47-9a6e-2f0d8b1c5e44".
Errors — IllegalStateException when called off the main thread. A call that cannot connect is a FAILED outcome, not an exception.
GraineCalls.end
Call it from your own hang-up control.
Hangs up (ENDED, LOCAL) — or, while the call rings, declines it. The microphone is released at once.
Parameters — none.
Returns — nothing.
Errors — none.
GraineCalls.setMuted
Call it from your own mute control.
Stops sending the user's voice without ending the call; listeners hear onCallControls.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
value | Boolean | required | true to stop sending the user's voice, false to send it again. |
Returns — nothing.
Errors — none.
GraineCalls.setSpeaker
Call it from your own loudspeaker control.
Off, the call goes back to a headset if one is connected (Bluetooth first), else the earpiece; the choice is made again whenever a headset is connected or removed.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
on | Boolean | required | true for the loudspeaker. |
Returns — nothing.
Errors — none.
GraineCalls.onRequestPermissionsResult
Call it from your own call screen's onRequestPermissionsResult, so a call waiting for the microphone can go ahead.
The SDK asks for the microphone from the call screen when it does not have it; the SDK's GraineCallActivity forwards by itself. A refusal fails the call (“The microphone permission was denied.”).
| Parameter | Type | Default | Meaning |
|---|---|---|---|
requestCode | Int | required | As Android passes it to your activity. |
grantResults | IntArray | required | As Android passes them to your activity. |
Returns — Boolean — true when the request was the SDK's (MIC_REQUEST). Example value: true.
Errors — none.
GraineCall
Read it for the call in progress: who placed it, why, and its ID.
data class GraineCall @JvmOverloads constructor(val callId: String, val direction: Direction, val callerName: String?, val reason: String?, val variables: Map<String, String>, val hosted: Boolean = false)
calls.call, and the argument of onIncomingCall and onCallState.
What to expect — It carries only what the ring shows: nothing personal.
| Field | Type | Default | Meaning |
|---|---|---|---|
callId | String | required | The ID every outcome carries: from the push for an incoming call; made by startCall for one the user places. |
direction | Direction | required | Who placed it (GraineCall.Direction). |
callerName | String? | required | Read it for the name on the ring: the push's caller_name when it has one, otherwise GraineCallsConfig.displayName. |
reason | String? | required | Read it for why the call was placed: the push's reason, or the one passed to startCall — the prompt sees it as call_reason. |
variables | Map<String, String> | required | The call's own prompt variables: who_called, call_reason and the push's variables. |
hosted | Boolean | false | true when Graine rang it — a call booked by the agent, the user or your team; the ring says “Scheduled call”, and Graine hears the outcome too. |
GraineCall.Direction
Read it to know who placed the call.
enum class Direction { INCOMING, OUTGOING }
| Case | Meaning |
|---|---|
INCOMING | The agent rang the user (a push). |
OUTGOING | The user called the agent (startCall). |
GraineCallState
Read it to know where the one native call is.
enum class GraineCallState { IDLE, RINGING, CONNECTING, ACTIVE, ENDED }
What to expect — IDLE → RINGING (an incoming call) or CONNECTING (a call placed) → ACTIVE → ENDED, then IDLE again once reported. One call at a time: a second ring while a call is live is reported MISSED (busy).
| Case | Meaning |
|---|---|
IDLE | No call. |
RINGING | The agent is calling the user (incoming only); show Answer and Decline. |
CONNECTING | The call was answered, or placed by the user, and is connecting. |
ACTIVE | The agent and the user can hear each other. |
ENDED | Over — reported once, then IDLE. |
GraineCallOutcome
Read it to send what became of a call to your server.
data class GraineCallOutcome @JvmOverloads constructor(…)
Reported once per call, with the call's ID, to GraineCallListener.onCallOutcome.
What to expect — ANSWERED, then ENDED with who ended it (and durationSeconds) or FAILED with why; or DECLINED, MISSED (busy when another call was live) or FAILED alone. hosted means Graine rang it, and the SDK has already told Graine. Do not rely on its order relative to the call's state changes.
| Field | Type | Default | Meaning |
|---|---|---|---|
kind | Kind | required | What became of the call (GraineCallOutcome.Kind). |
endReason | EndReason? | null | Who ended a call that connected (GraineCallOutcome.EndReason) — with ENDED; null otherwise. |
error | String? | null | Why, in a sentence: why it never connected (with FAILED) or why the connection broke (with ENDED and FAILED) — for example “The call could not connect within 15 seconds.” or “Nothing arrived for 50 seconds: the connection was lost.”; null otherwise. |
callId | String | "" | The call's ID — the same one onCallOutcome carries beside it. |
durationSeconds | Int? | null | How long a call that connected lasted, in whole seconds — with ENDED; null otherwise. |
hosted | Boolean | false | true when Graine rang it (a call booked with the agent): Graine hears this outcome too. |
busy | Boolean | false | true for a MISSED ring refused because another call was live. |
| Property | Type | Meaning |
|---|---|---|
val wire: String | String | One word for your server: answered, declined, missed, ended or failed. |
val outcome: String | String | The same word as wire. |
val reason: String? | String? | Why, in one word or sentence: who ended a call (local, remote, failed) with ENDED, the error with FAILED, busy for a refused ring; null otherwise. |
companion · val ANSWERED: GraineCallOutcome | GraineCallOutcome | An ANSWERED outcome, for tests and your own call screen. |
companion · val DECLINED: GraineCallOutcome | GraineCallOutcome | A DECLINED outcome. |
companion · val MISSED: GraineCallOutcome | GraineCallOutcome | A MISSED outcome. |
Its functions: ended, failed — each with its parameters, what it returns, its errors and an example.
GraineCallOutcome.Kind
Read it to branch on what became of a call.
enum class Kind(val wire: String) { ANSWERED("answered"), DECLINED("declined"), MISSED("missed"), ENDED("ended"), FAILED("failed") }
| Property | Type | Meaning |
|---|---|---|
val wire: String | String | The word for your server. |
| Case | Meaning |
|---|---|
ANSWERED | The user picked up an incoming call; ENDED or FAILED follows. |
DECLINED | The user declined it. |
MISSED | Nobody answered within the ring time — or another call was live (busy). |
ENDED | The call connected and has now finished; endReason tells you which side ended it. |
FAILED | The call never connected: error says why. |
GraineCallOutcome.EndReason
Read it to know who ended a call that connected.
enum class EndReason(val wire: String) { LOCAL("local"), REMOTE("remote"), FAILED("failed") }
| Property | Type | Meaning |
|---|---|---|
val wire: String | String | The word for your server. |
| Case | Meaning |
|---|---|
LOCAL | This app hung up — the user, end(), or a phone call answered during a hold. |
REMOTE | The agent hung up. |
FAILED | The connection broke mid-call, nothing arrived for 50 seconds, or the microphone stopped; error says which. |
GraineCallOutcome.ended
Call it to make an ENDED outcome — in a test, or for a call screen of your own.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
reason | EndReason | required | Who ended it. |
error | String? | null | Why, for EndReason.FAILED. |
Returns — GraineCallOutcome with kind ENDED. Example value: GraineCallOutcome(kind=ENDED, endReason=REMOTE, error=null, …).
Errors — none.
GraineCallOutcome.failed
Call it to make a FAILED outcome — in a test, or for a call screen of your own.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
error | String | required | Why the call never connected. |
Returns — GraineCallOutcome with kind FAILED. Example value: GraineCallOutcome(kind=FAILED, error=The call could not connect within 15 seconds., …).
Errors — none.
GraineCallActivity
Leave it as your call screen, or name your own in GraineCallsConfig.callActivity.
class GraineCallActivity : Activity(), GraineCallListener
The SDK's call screen — over the lock screen, for the ring and the call — drawn in the brand's design from the agent's call settings (Garage → Call screen), in six moments: a call Graine rang ringing (“Scheduled call”); any other call ringing, with Decline and Answer and a halo growing from the avatar; connecting; on the call, with the timer (or Listening / Speaking), the caption and the design's controls in its order; ended (“Call ended”, or “Call failed”, “Declined”), closing in a second; and missed, with Close and Call back, closing in 30 seconds. The last design is kept on the device, so a push that wakes a closed app draws the same screen.
What to expect — Declared in the library's manifest; your app does not start it. Closing it never ends the call — the ongoing notification brings it back.
GraineCallService
Nothing to call: it is what keeps a call going with the screen off.
class GraineCallService : Service()
The call's foreground service — a microphone foreground service — so the call keeps its microphone and its process with the screen off and no activity open, with the ongoing-call notification (and Hang up) as the way back to it. Started from the call screen, and stopped with the call.
What to expect — Declared in the library's manifest and started by the SDK; your app does not start or call it. An app without calls removes it (Requirements).
GraineCallReceiver
Nothing to call: it handles the notifications' Decline and Hang up.
class GraineCallReceiver : BroadcastReceiver()
What to expect — Declared in the library's manifest; your app does not call it.
Permissions and the setup check
What a call needs from the phone, and from your app's setup, in one place. The SDK never asks on its own at launch: it asks for the microphone on the call screen when a call starts without it, and requestCallPermissions asks earlier, when your app chooses — from a transparent screen of the SDK's own, so no activity forwards a permission result. callSetupProblems says what your setup still lacks; each problem is also logged once in logcat (Calls setup (code): …) from configure on. A problem is never an exception: calls still work where they can.
GraineCalls.callPermissions
Read it to see which call permissions the phone holds now.
The microphone; the ring's notifications (Android 13+ asks for POST_NOTIFICATIONS; before 13 they are on unless the customer turned them off in Settings); and the full-screen ring (Android 14+: NotificationManager.canUseFullScreenIntent(), Settings only; before 14 granted with the app; before 10 not needed). What each ask came to is kept on the device, so a permission refused for good reads BLOCKED. Any thread.
Returns — a GraineCallPermissions. Read it again in onResume: the customer may have changed Settings.
Errors — none.
GraineCalls.requestCallPermissions
Call it at a moment you choose — onboarding, a "Call me" screen — to ask for what a call needs.
Asks for the call permissions the system's dialog can still ask for — the microphone, and notifications on Android 13+ — from a transparent screen of the SDK's own over activity (by default the app's screen in front), then answers them as they stand after, on the main thread. Nothing to ask, or no screen in front: the answer comes at once. One ask at a time — a second request waits for the first one's answer. The full-screen ring has no dialog: offer openCallSettings(GraineCallPermission.FULL_SCREEN). The SDK never asks on its own at launch.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
activity | Activity? | null | The screen to ask over; null, the app's screen in front. |
done | GraineCallPermissionsCallback | required | Gets the permissions after the customer answers. |
Returns — nothing; done gets a GraineCallPermissions.
Errors — none.
GraineCalls.openCallSettings
Call it to send the customer to the Settings page that changes a call permission.
Opens the full-screen ring's own page on Android 14+ (Settings.ACTION_MANAGE_APP_USE_FULL_SCREEN_INTENT), the app's notification settings for notifications, or the app's page (Permissions → Microphone) for the microphone — and the app's page whenever the first cannot open.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
item | GraineCallPermission | required | The permission the customer should change. |
activity | Activity? | null | Where to open it from; null, the app's screen in front (else a new task). |
Returns — Boolean: true when a page opened. Example value: true.
Errors — none.
GraineCalls.callSetupProblems
Read it to learn what your app's setup still lacks for calls, in plain words.
What the app's setup still lacks for calls, read now — each a stable code and a message with the exact fix; empty when nothing is missing. Any thread.
| Code | When |
|---|---|
manifest_entry_missing | The merged manifest lacks a call entry — a permission (RECORD_AUDIO, POST_NOTIFICATIONS, USE_FULL_SCREEN_INTENT, FOREGROUND_SERVICE_MICROPHONE, …), the call screen, GraineCallPermissionActivity, GraineCallService (type microphone) or GraineCallReceiver. The message names them. |
no_push_token | No Firebase token reached the SDK 15 seconds after configure, on this launch or an earlier one: calls can't ring. The message says why — Firebase Cloud Messaging not in the app, Firebase not set up (google-services.json, the Google services plugin), its automatic token creation turned off, or the reason Firebase gave — or, for an app with its own messaging service, that no token was handed to GraineCalls.onNewToken. |
notifications_not_allowed | Notifications are not allowed (or turned off): a call can't ring while the app is closed or in the background. |
full_screen_not_allowed | The full-screen ring is not allowed: a locked phone rings with a heads-up notification instead. |
microphone_blocked | The microphone is refused for good: only Settings can let calls hear the customer. |
hosted_ringing_off | A session is known and no configured agent's workspace has push credentials for Android: Graine can't ring this phone. |
Returns — List<GraineCallSetupProblem>.
Errors — none.
GraineCallPermission
Use it to name a call permission.
enum class GraineCallPermission(val wire: String) — a permission a WhatsApp-style call needs. wire is the word every Graine SDK uses.
What to expect — Three permissions, the same on every SDK.
| Case | Meaning |
|---|---|
MICROPHONE | "microphone" — the customer's voice. |
NOTIFICATIONS | "notifications" — a call that rings while the app is closed or in the background. |
FULL_SCREEN | "fullScreen" — a ring that fills a locked screen. |
| Property | Type | Meaning |
|---|---|---|
val wire: String | String | The word every Graine SDK uses for it. |
GraineCallPermission.of(word) reads one by its word — microphone, notifications, fullScreen (full_screen and any case too); null for any other.
GraineCallPermissionStatus
Read it to tell whether a call permission is allowed, can still be asked for, or only Settings can change it.
enum class GraineCallPermissionStatus(val wire: String) — where a call permission stands.
What to expect — GRANTED and NOT_NEEDED need nothing; DENIED means requestCallPermissions can ask; BLOCKED means only Settings can change it — offer openCallSettings.
| Case | Meaning |
|---|---|
GRANTED | "granted" — allowed. |
DENIED | "denied" — not allowed yet: asking may change it. |
BLOCKED | "blocked" — refused for good, or only allowed in Settings (the full-screen ring on Android 14+, notifications turned off). |
NOT_NEEDED | "notNeeded" — this phone needs no such permission (the full-screen ring before Android 10). |
| Property | Type | Meaning |
|---|---|---|
val wire: String | String | The word every Graine SDK uses for it. |
val isSatisfied: Boolean | Boolean | Granted, or nothing to grant. |
GraineCallPermissions
Read it to see every call permission at once.
data class GraineCallPermissions(val microphone: GraineCallPermissionStatus, val notifications: GraineCallPermissionStatus, val fullScreen: GraineCallPermissionStatus)
What to expect — A snapshot: read callPermissions again for the latest. allGranted is the one check before offering calls.
| Property | Type | Meaning |
|---|---|---|
val microphone | GraineCallPermissionStatus | The microphone. |
val notifications | GraineCallPermissionStatus | The ring's notifications. |
val fullScreen | GraineCallPermissionStatus | The full-screen ring. |
val allGranted: Boolean | Boolean | Every permission is granted or not needed: calls can ring, fill the locked screen and connect. |
permissions[GraineCallPermission.MICROPHONE] reads one by name (operator fun get).
GraineCallPermissionsCallback
Implement it (or pass a lambda) to hear the permissions once asking is over.
onResult runs once, on the main thread, with the permissions as they stand after the customer answered. From Java: calls.requestCallPermissions(activity, permissions -> { … }).
GraineCallSetupProblem
Read it to show or log what your app's setup still lacks.
data class GraineCallSetupProblem(val code: String, val message: String) — one thing the app's setup still lacks for calls, from callSetupProblems.
What to expect — The code is stable and the same on every SDK (the list is under GraineCalls.callSetupProblems); the message is a sentence for the developer, with the exact fix.
| Property | Type | Meaning |
|---|---|---|
val code: String | String | Stable: act on it, or count it. |
val message: String | String | What is missing and how to fix it, in plain words. |
Calls Graine rings
The agent rings the user: a call the agent booked in a conversation, one the user booked (Booking with the SDK's sheet), one your team booked — or one your own server sends. With your push credentials uploaded in Workspace settings → In-app calling and Firebase Cloud Messaging in the app, nothing else is needed — the SDK's own messaging service (ai.graine.inapp.GraineMessagingService, merged from its manifest) receives every new token and every message, and the SDK reads the current token when it starts: the SDK registers the device with every agent's key (on every new token, when what it says changes — another user, a new time zone, a new app version — and otherwise at most once a day), rings each call Graine sends exactly as your own pushes ring, and reports what became of it. The ring is a call-style notification that keeps ringing until answered, declined or timed out, with a full-screen call screen; with your app in front, the call screen opens at once. A ring nobody answered leaves a Missed calls notification and the call screen on “Missed call”, both with Call back. With the WebRTC module in the app (Adding WebRTC calls), a call Graine rang carries its audio over WebRTC as a call the user places does — prepared while the phone rings — and calls.transport and calls.transportFallbackReason say how it went.
The push your own server sends (an FCM data message, nothing personal): type: "agent_call", call_id (required — every outcome carries it back), reason (the prompt's call_reason), caller_name (what the ring shows), variables (a JSON object as a string: more prompt variables), and agent_id (which of the app's agents the call is with; with several agents and none named, the default agent rings, and the log says so).
Already have your own FirebaseMessagingService? Android delivers each message to one service, and yours outranks the SDK's (declared at priority -1): hand Graine's over, two lines. A debug build logs it when your service receives the messages instead of the SDK's.
GraineCalls.onNewToken
Call it from a FirebaseMessagingService of your own, in onNewToken — one line — so Graine can ring this install. Without one, the SDK's own messaging service receives each new token, and the SDK reads the current one when it starts.
The token is kept on the device and registered with Graine — by every agent the app runs, each with its own key — when the workspace has Graine ring its users (Workspace settings → In-app calling); with no push credentials uploaded there, nothing is sent. Safe before GraineInApp.init.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
context | Context | required | The messaging service (any context of your app). |
token | String | required | The new Firebase Cloud Messaging token; a blank one is ignored. |
Returns — nothing.
Errors — none. A registration that fails is tried again later; the log says why.
GraineCalls.handlePush
Call it first thing in the onMessageReceived of a FirebaseMessagingService of your own, so a call rings. Without one, the SDK's own messaging service calls it for every message.
When the message is a call — Graine's own ring, or one your server sent — the SDK rings it (or reports it MISSED as busy when another call is live; a repeat of the call in progress is ignored) and the answer is true; your own messages answer false. Safe on the messaging service's thread. The companion form works before the SDK is made (it rings once GraineInApp.init has run in Application.onCreate); the instance form is GraineInApp.get().calls.handlePush(data).
| Parameter | Type | Default | Meaning |
|---|---|---|---|
context | Context | required | The messaging service (the companion form only). |
data | Map<String, String> | required | The message's data, exactly as the messaging service received it. |
Returns — Boolean — true when it was a call (ringing, refused as busy, or malformed) and you should do nothing more with it. Example value: true.
Errors — none. A malformed call (no call_id) is answered true and logged.
GraineCalls.answer
Call it from a call screen of your own to pick up the ringing call.
Reported ANSWERED; the call connects, after asking for the microphone if needed. The SDK's call screen and the notification's Answer do this themselves.
Parameters — none.
Returns — nothing.
Errors — none.
GraineCalls.decline
Call it from a call screen of your own to decline the ringing call.
Reported DECLINED. For a call Graine rang, nothing rings that user again for 6 hours.
Parameters — none.
Returns — nothing.
Errors — none.
GraineCalls.callBack
Call it from a missed-call screen's “Call back”, so the user calls the agent back.
Places a call with the missed call's agent. The agent hears reason — else missed_call (a call Graine rang keeps its own reason with Graine). Clears the missed-call notification. The SDK's missed screen and notification do this themselves.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
activity | Activity | required | The activity in front. |
missedCallId | String? | required | The missed call's ID, so the call back goes to the same agent; null uses the default agent. |
reason | String? | null | Why the user calls back; missed_call when left out. |
Returns — String? — the new call's ID, or null when another call is live. Example value: "call_8a20d3c1-5b6e-4f7a-9c2d-1e0f3a4b5c6d".
Errors — none.
Booking with the SDK's sheet
“Call me at…” with no screen of your own: presentBooking shows the SDK's booking sheet over an activity, in the agent's design, and Graine rings this phone at the time the user picks. Offer it from your own button, or let the user find it in the chat.
What the sheet shows. A header, “Book a call”, with “{call name} will call you” under it — the agent's call name (Calls & scheduling), else its chat title, else its heading — and a close button. Day: a chip for each day from today to the furthest day the agent allows — “Today”, “Tomorrow”, then the weekday and date in the phone's words, such as “Thu 16 Oct” — scrolling sideways; a day with no time left is not offered. Time: Android's own time picker in spinner mode, inside the SDK's own dialog (no Material), in the phone's 12- or 24-hour clock, its minute wheel in 5-minute steps (00, 05 … 55; on a phone whose picker has no such wheel, the minute picked is rounded to 5), held to the times the chosen day offers — between earliest(now), rounded up to the next 5 minutes, and latest(now); a time the day does not have moves to the nearest it has. Under it, the phone's time zone and the chosen time, such as “Asia/Kolkata · Tue 14 Oct, 6:00 PM”. What's it about? (optional): one line, at most 120 characters. Then Book call, with a spinner while it books. Once booked: “Booked — {call name} will call you on {day}, {time}” and Done.
A plain platform dialog — the SDK has no dependencies: a sheet up from the bottom on a phone, a centred panel on a tablet (the shorter side 600 dp or more), the app dimmed behind it. Back, a tap outside or the close button closes it — not while a booking is on its way. If the agent's settings arrive while it is open, the offer follows them.
A refusal stays in the sheet as one line under the button, in these words:
| Code | The line |
|---|---|
TOO_SOON | Pick a time at least N minutes from now |
TOO_FAR | Pick a time within N days |
OUTSIDE_WINDOW | That time is outside the hours we can call — pick another |
TOO_MANY_PENDING | You already have N calls booked — cancel one first |
SCHEDULING_OFF | Booking a call isn't available right now |
IDENTITY_REQUIRED | Sign in to book a call |
| anything else | Couldn't book just now — try again |
N is the agent's own number, singular for one (“1 minute”, “1 day”, “1 call”). After TOO_SOON the offer moves on to what is bookable now. An agent that cannot be booked now opens a sheet that shows the SCHEDULING_OFF or IDENTITY_REQUIRED line, with Book call disabled — so check bookingAvailable before offering it.
In the chat. Where booking is available, the chat window — the voice bar's, GraineChatView, GraineChatActivity — shows Book a call under the booked calls in its Upcoming calls card, or on its own in the card's place when none are booked; never when the design hides the booked-calls card (Garage → Chat window → Booked calls). It opens this sheet, and a booking — from the sheet or from scheduleCall — refreshes the card.
GraineInApp.bookingAvailable
Read it to decide whether to show your own “Call me” button.
true when all hold: Graine rings this phone (the workspace's hosted calling is on, and its platforms are empty or name Android), the agent lets users book (Calls & scheduling), and the user is identified with the trait the agent's Who is the user? setting names — otherwise a booking would be refused with IDENTITY_REQUIRED. It is what the SDK's own “Book a call” follows. Each agent answers for itself. Read it again after identify and once the settings have loaded.
Parameters — none.
Returns — Boolean. Example value: true.
Errors — none.
GraineInApp.presentBooking
Call it from your own “Call me” button to let the user book a call in the SDK's sheet.
Shows the booking sheet (above) over activity, on the main thread. One sheet at a time: while one is open, a second call answers null at once. An agent that cannot be booked opens a sheet that says why, and the log warns you.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
activity | Activity | required | The activity to show the sheet over. |
reason | String? | null | Pre-fills What's it about?, which the user can edit. The agent gets it with the call; it is never included in the push that rings the phone. |
done | GraineCallback<GraineScheduledCall?> | required | Called once the sheet closes: with the booked call, or with null when the user closed it without booking. |
Returns — nothing; the call arrives in done. Example value (in done): GraineScheduledCall(id=6f1c2a9e-…, at=Thu Oct 16 18:00:00 GMT+05:30 2026, status=scheduled, source=sdk, reason=Talk through my order).
Errors — none in done: a refusal stays in the sheet as its line. done hears null at once when the activity is finishing or another sheet is open.
GraineBookingDialog
Use it to show the booking sheet your own way — with another agent, or from code that manages its own dialogs.
class GraineBookingDialog @JvmOverloads constructor(activity: Activity, graine: GraineInApp = GraineInApp.get(), reason: String? = null, done: GraineCallback<GraineScheduledCall?>? = null) : Dialog(activity)
The booking sheet itself, as a plain platform Dialog — what presentBooking shows. Make it, then show() it; it places itself (a sheet on a phone, a centred panel on a tablet) and draws itself in the agent's design, following the design and who is signed in while it is open.
What to expect — Main thread. done hears once, when it closes: the booked call, or null. Closed while a booking is on its way, it waits for that booking's answer. Unlike presentBooking, it does not stop you showing two at once.
Its functions: constructor — each with its parameters, what it returns, its errors and an example.
GraineBookingDialog (constructor)
Use it to make a booking sheet you show yourself.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
activity | Activity | required | The activity to show the sheet over. |
graine | GraineInApp | GraineInApp.get() | The agent the call is booked with, in its design and with its rules. |
reason | String? | null | Fills What's it about?. |
done | GraineCallback<GraineScheduledCall?>? | null | Hears the booked call, or null, once the sheet closes. |
Returns — a GraineBookingDialog; call show() to show it.
Errors — IllegalStateException when graine is left out and no default agent was made.
The scheduled-calls list
Book calls from your own interface, list the user's upcoming calls and cancel one — the same calls the sheet and the chat's Upcoming calls card show. Graine rings this phone at the booked time.
GraineSchedulingRules
Read it to build your own booking screen — which times the agent accepts, and whether users may book at all.
data class GraineSchedulingRules @JvmOverloads constructor(…)
GraineInApp.schedulingRules (and SessionConfig.schedulingRules): the rules for booking calls, and for the calls Graine rings.
What to expect — A property: no request. This launch's rules, else the last ones kept on the device (a push that woke the app), else the defaults — no booking, no hosted ringing. scheduleCall refuses on the device first — SCHEDULING_OFF, TOO_SOON (before earliest(now) or not in the future), TOO_FAR (after latest(now)) — and Graine then checks the calling hours and the calls already waiting.
| Field | Type | Default | Meaning |
|---|---|---|---|
usersCanBook | Boolean | false | Read it to know whether users may book calls from the app at all (Calls & scheduling). |
minLeadMinutes | Int | 5 | The least notice a booking needs, in minutes — use it with earliest(). |
maxDaysAhead | Int | 14 | How far ahead a booking may be, in days — use it with latest(). |
maxPendingPerUser | Int | 3 | How many upcoming calls a user may have at once; beyond it, TOO_MANY_PENDING. |
ringTimeoutSeconds | Int | 45 | How long a call Graine rings keeps ringing before it is missed, 20–60 seconds. |
callName | String? | null | The caller name the dashboard gives this agent's calls, when one is set. |
hostedRinging | Boolean | false | Read it to know whether Graine rings this app's users itself (the workspace uploaded its push credentials). |
platforms | List<String> | emptyList() | The platforms Graine rings: "ios", "android"; empty means every one. |
| Property | Type | Meaning |
|---|---|---|
val ringsAndroid: Boolean | Boolean | Read it to know whether Graine rings this app on Android: hosted ringing on, and platforms empty or naming Android. |
companion · const val RING_SECONDS_MIN = 20 | Int | The shortest ring the dashboard allows, in seconds. |
companion · const val RING_SECONDS_MAX = 60 | Int | The longest ring the dashboard allows, in seconds. |
Its functions: earliest, latest, parse — each with its parameters, what it returns, its errors and an example.
GraineSchedulingRules.earliest
Call it for the soonest time a booking is accepted, to start your time picker there.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
now | java.util.Date | java.util.Date() | The moment to count from; now when left out. |
Returns — java.util.Date — now plus minLeadMinutes. Example value: Thu Oct 15 18:05:00 GMT+05:30 2026.
Errors — none.
GraineSchedulingRules.latest
Call it for the latest time a booking is accepted, to end your time picker there.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
now | java.util.Date | java.util.Date() | The moment to count from; now when left out. |
Returns — java.util.Date — now plus maxDaysAhead days. Example value: Thu Oct 29 18:00:00 GMT+05:30 2026.
Errors — none.
GraineSchedulingRules.parse
Call it to read the rules from the agent's settings document yourself — in a test, or on your server's copy.
Reads the settings' calls object: hosted, platforms, ringTimeoutSeconds (held to 20–60), callName and scheduling (usersCanBook, minLeadMinutes, maxDaysAhead, maxPendingPerUser). Anything missing keeps its default.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
raw | Any? | required | The calls object (a JSONObject), or anything else for the defaults. |
Returns — GraineSchedulingRules — the defaults (no booking, no hosted ringing) when there is nothing to read. Example value: GraineSchedulingRules(usersCanBook=true, minLeadMinutes=5, maxDaysAhead=14, maxPendingPerUser=3, ringTimeoutSeconds=45, callName=Acme, hostedRinging=true, platforms=[android]).
Errors — none.
GraineInApp.scheduleCall
Call it to book a call from your own booking screen.
Books a call from the agent for the signed-in user (or, with nobody signed in, this install) at at — “call me tomorrow at 6”. It waits for the agent's settings, then refuses on the device — before anything is sent — what the rules already settle, in the booking sheet's words: SCHEDULING_OFF, “Booking a call isn't available right now”; TOO_SOON, “Pick a time at least N minutes from now” (“1 minute” for one); TOO_FAR, “Pick a time within N days”. Graine then checks what only it knows: the calling hours, in the user's time zone, and the calls already waiting. The time is sent with the phone's offset and time zone. reason (up to 120 characters) is stored with the call for the agent and is never included in the push that rings the phone. An open chat's Upcoming calls card shows the booking at once.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
at | java.util.Date | required | When the call should ring: between schedulingRules.earliest(now) and latest(now). |
reason | String? | null | What the call is about, at most 120 characters — the agent hears it when it rings. |
done | GraineCallback<GraineScheduledCall>? | null | Hears the booked call, or why not, once, on the main thread. |
Returns — nothing; the call arrives in done (GraineScheduledCall). Example value (in done): GraineScheduledCall(id=6f1c2a9e-7d1b-4c47-9a6e-2f0d8b1c5e44, at=Thu Oct 16 18:00:00 GMT+05:30 2026, status=scheduled, source=sdk, reason=Talk through my order).
Errors — in done's GraineError — on the device: SCHEDULING_OFF, TOO_SOON, TOO_FAR; from Graine: OUTSIDE_WINDOW, TOO_MANY_PENDING, IDENTITY_REQUIRED, DEVICE_LIMIT, INVALID_REQUEST, RATE_LIMITED, NETWORK, UNAVAILABLE (“The booking was accepted but not described.” when Graine booked it without describing it).
GraineInApp.scheduledCalls
Call it to list the user's upcoming calls in your own interface.
Lists the upcoming calls booked for the signed-in user — or for this install when nobody is signed in — with the soonest first.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
done | GraineCallback<List<GraineScheduledCall>> | required | Hears the list, or the error, once, on the main thread. |
Returns — nothing; the list arrives in done. Example value (in done): [GraineScheduledCall(id=6f1c2a9e-…, at=Thu Oct 16 18:00:00 GMT+05:30 2026, status=scheduled, source=agent, reason=null)].
Errors — in done's GraineError: NETWORK, RATE_LIMITED, UNAVAILABLE, INVALID_KEY, ORIGIN_REFUSED.
GraineInApp.cancelScheduledCall
Call it when the user calls off one of their booked calls.
Only a call booked for this user or install; any other is NOT_FOUND. An open chat's card drops it too once it is gone.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
id | String | required | The call's id, from scheduleCall, scheduledCalls or presentBooking. |
done | GraineCallback<GraineCallStatus>? | null | Hears the call's status afterwards, or the error. |
Returns — nothing; the status arrives in done — CANCELLED when Graine does not say otherwise. Example value (in done): GraineCallStatus.CANCELLED.
Errors — in done's GraineError: INVALID_REQUEST (“No call id.”) for a blank id, NOT_FOUND, NETWORK, RATE_LIMITED, UNAVAILABLE.
GraineChat.refreshUpcomingCalls
Call it after you book or cancel a call somewhere the chat does not see, so its Upcoming calls card shows it.
Fetches the upcoming calls again now. The card refreshes itself when the chat opens, after the agent replies (at most every 10 seconds), and after a booking made with scheduleCall or the sheet. Only where Graine rings this app.
Parameters — none.
Returns — nothing.
Errors — none.
GraineChat.cancelUpcomingCall
Call it from a chat screen of your own to cancel one of the card's calls.
The call's row says “Cancelling…” until it is done, then goes; a failure says “Couldn’t cancel · tap to retry”. The SDK's chat window does this from its Cancel button.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
id | String | required | The call's id, from chat.upcomingCalls. |
Returns — nothing.
Errors — none.
GraineScheduledCall
Read it to show a booked call: when it rings, where it is, and whether it can still be cancelled.
data class GraineScheduledCall @JvmOverloads constructor(…)
A call the agent will make to this user, booked for later: Graine rings it at at, through the app's call screen.
What to expect — From scheduleCall (status SCHEDULED), scheduledCalls(), presentBooking and the chat's upcoming calls. at is a moment: show it in the device's time zone. A status or source newer than the SDK arrives as Unknown, carrying its word.
| Field | Type | Default | Meaning |
|---|---|---|---|
id | String | required | Use it to cancel the call — Graine's ID for it, a UUID. |
at | java.util.Date? | required | When it rings; null only when Graine's time could not be read. |
status | GraineCallStatus | GraineCallStatus.SCHEDULED | Where it is (GraineCallStatus). |
source | GraineCallSource? | null | Who booked it (GraineCallSource); null when Graine does not say. |
reason | String? | null | Why, as it was booked; null when none was given (or Graine keeps it to itself). |
| Property | Type | Meaning |
|---|---|---|
val isUpcoming: Boolean | Boolean | Use it to keep a call in an upcoming list: still to come — scheduled, or ringing now. |
val canCancel: Boolean | Boolean | Use it to show a Cancel control: it can still be called off — it has not rung yet. |
GraineCallStatus
Read it to show where a booked call is.
sealed class GraineCallStatus(val wire: String)
What to expect — SCHEDULED → RINGING → ANSWERED → COMPLETED; or DECLINED, MISSED, FAILED, CANCELLED, EXPIRED, NO_DEVICE. A status newer than this SDK arrives as Unknown(raw), carrying its word: give every when an else.
| Property | Type | Meaning |
|---|---|---|
val wire: String | String | Graine's word for the status — scheduled, no_device, … — for logs and your server. |
companion · val KNOWN: List<GraineCallStatus> | List<GraineCallStatus> | Every status this build knows — for a settings screen or a test. |
| Case | Meaning |
|---|---|
SCHEDULED | Booked, waiting for its time; it can still be cancelled. |
RINGING | Ringing the user now. |
ANSWERED | The user picked up. |
COMPLETED | The call took place and is over. |
DECLINED | The user declined it; nothing rings them again for 6 hours. |
MISSED | It rang out unanswered. |
FAILED | It could not be placed. |
CANCELLED | Called off before it rang. |
EXPIRED | Its time passed while it could not ring — more than 10 minutes late, or no opening left in the calling hours. |
NO_DEVICE | Graine had no device of this user's to ring — none seen in the last 30 days. |
Unknown(raw) | A status newer than this SDK, carrying Graine's word in wire. |
Its functions: from — each with its parameters, what it returns, its errors and an example.
GraineCallSource
Read it to show who booked a call.
sealed class GraineCallSource(val wire: String)
What to expect — A source newer than this SDK arrives as Unknown(raw), carrying its word.
| Property | Type | Meaning |
|---|---|---|
val wire: String | String | Graine's word for the source — sdk for APP. |
| Case | Meaning |
|---|---|
AGENT | The agent booked it in a conversation. |
APP | Your app booked it (scheduleCall, the booking sheet). |
DASHBOARD | Your team booked it in the dashboard. |
API | Your server booked it through the API. |
Unknown(raw) | A source newer than this SDK, carrying Graine's word. |
Its functions: from — each with its parameters, what it returns, its errors and an example.
GraineCallStatus.from
Call it to read a status word from your server (or Graine's webhooks) into a GraineCallStatus.
Case and surrounding spaces do not matter.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
wire | String | required | Graine's word, such as "no_device". |
Returns — GraineCallStatus — a known status, or Unknown(wire) for a word newer than this SDK. Example value: GraineCallStatus.NO_DEVICE.
Errors — none.
GraineCallSource.from
Call it to read a source word into a GraineCallSource.
sdk and app are APP; case and surrounding spaces do not matter.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
wire | String | required | Graine's word, such as "dashboard". |
Returns — GraineCallSource — a known source, or Unknown(wire). Example value: GraineCallSource.DASHBOARD.
Errors — none.
Design
The brand chooses a design in the Garage — a look and a few knobs — and the agent's settings carry it already worked out, as two complete sets of tokens, light and dark. The SDK's views draw what the tokens say; a look added in the dashboard later reaches your app without a release. Nothing from the network is trusted: a value that is missing or malformed takes Graine Classic's (the look every Graine surface had before designs), and reading never throws. Use these types to draw your own interface in the brand's design, or to preview a design.
GraineInApp.themeTokens
Call it to draw your own interface in the brand's design, in the right scheme.
The brand's fixed light or dark when it chose one, else whichever the phone is in — context's own, so an activity that forces night mode gets dark. Read it again when the phone switches.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
context | Context? | null | Whose light or dark setting to follow; the application's when left out. |
scheme | String? | null | Your own choice — "light", "dark" or "auto" — which wins over the design's. |
Returns — ThemeTokens? (ThemeTokens) — null without a design (an older server). Example value: ThemeTokens(v=1, preset=graine-classic, scheme=light, …).
Errors — none.
GraineInApp.previewDesign
Call it to show a design before the brand saves it — a preview screen, a screenshot, a test.
Every SDK view redraws in design instead of the session's; null goes back to the session's.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
design | ResolvedDesign? | required | The design to draw (ResolvedDesign), or null. |
Returns — nothing.
Errors — none.
ResolvedDesign
Read it for the brand's design: the knobs it turned, and the light and dark tokens to draw.
data class ResolvedDesign(val config: DesignConfig, val light: ThemeTokens, val dark: ThemeTokens)
GraineInApp.design and SessionConfig.design.
What to expect — When Graine sends only one of light and dark, the other uses the same tokens.
| Field | Type | Default | Meaning |
|---|---|---|---|
config | DesignConfig | required | The knobs the brand turned (DesignConfig). |
light | ThemeTokens | required | The tokens to draw in light (ThemeTokens). |
dark | ThemeTokens | required | The tokens to draw in dark. |
Its functions: pick, toJson, parse — each with its parameters, what it returns, its errors and an example.
ResolvedDesign.pick
Call it to choose which tokens to draw, light or dark.
The brand's fixed scheme when it chose one; under “auto” (or none) the phone's — light when the phone's is unknown. Pick again whenever the phone switches.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
deviceDark | Boolean? | required | Whether the phone is in dark mode; null when unknown. |
override | String? | null | Your own choice — "light", "dark" or "auto" — which wins. |
Returns — ThemeTokens. Example value: ThemeTokens(…, scheme=dark, …).
Errors — none.
ResolvedDesign.toJson
Call it to log the design, or hand it to another part of your app.
Parameters — none.
Returns — JSONObject — { config, light, dark }, as the settings spell them.
Errors — none.
ResolvedDesign.parse
Call it to read a design document — from your own preview tool, say.
Reads a JSONObject, or its JSON text. A value missing or malformed takes Graine Classic's.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
raw | Any? | required | The design: { config, light, dark }. |
Returns — ResolvedDesign? — null when it is absent or is not a design. Example value: ResolvedDesign(config=DesignConfig(preset=graine-classic, …), light=…, dark=…).
Errors — none: it never throws.
DesignConfig
Read it to show which choices the brand made in the Garage.
data class DesignConfig @JvmOverloads constructor(…)
The knobs the brand turned. Only scheme changes what the SDK draws (which token set); the rest are already inside the tokens and are kept for an app that wants to show them.
| Field | Type | Default | Meaning |
|---|---|---|---|
preset | String | Design.DEFAULT_PRESET | The design the brand started from, such as graine-classic. |
accent | String? | null | The accent colour the brand chose. |
scheme | String? | null | "light", "dark" or "auto"; absent means auto. |
roundness | Double? | null | How rounded corners are, as the brand set it. |
font | String? | null | The body typeface the brand chose. |
headingFont | String? | null | The heading typeface. |
density | String? | null | How tight the spacing is. |
shadow | String? | null | How depth is drawn. |
motion | String? | null | How much things move. |
neutral | String? | null | The neutral palette the greys come from. |
glass | Boolean? | null | Whether surfaces are translucent. |
bubbles | String? | null | How chat bubbles look. |
launcherIcon | String? | null | The launcher's glyph. |
launcherShape | String? | null | The launcher's shape. |
iconStyle | String? | null | The style of the SDK's icons. |
chrome | String? | null | "none" or "window", when the brand turned the knob; ThemeTokens.chrome is what is drawn. |
chat | Map<String, Any?>? | null | The chat window's knobs as the brand set them (Garage → Chat window); ThemeTokens.chat is what is drawn. |
call | Map<String, Any?>? | null | The call screen's knobs as the brand set them (Garage → Call screen); ThemeTokens.call is what is drawn. |
Its functions: toJson — each with its parameters, what it returns, its errors and an example.
DesignConfig.toJson
Call it to log the brand's choices, or hand them on.
Parameters — none.
Returns — JSONObject — the knobs, as the settings spell them; absent ones are left out.
Errors — none.
ThemeTokens
Read it for everything you need to draw in the brand's design, in one scheme.
data class ThemeTokens @JvmOverloads constructor(…)
Every value already worked out: flat numbers (dp, sp, ms) and #RRGGBB colours. From themeTokens(), or ResolvedDesign.light and dark.
What to expect — Resolved from the agent's design when the settings load; a missing or malformed value takes Graine Classic's. Reading them never throws.
| Field | Type | Default | Meaning |
|---|---|---|---|
v | Int | required | The tokens' format version, for logs. |
preset | String | required | The design the tokens come from, such as graine-classic. |
scheme | String | required | "light" or "dark". |
colors | ThemeColors | required | Every colour (ThemeColors). |
font | ThemeFont | required | The typefaces, sizes, weights, spacing and line heights (ThemeFont). |
shape | ThemeShape | required | Corner radii, outline width and the launcher's shape (ThemeShape). |
spacing | ThemeSpacing | required | The spacing unit, card padding and gaps (ThemeSpacing). |
elevation | ThemeElevation | required | How depth is drawn: shadows, or a hard offset (ThemeElevation). |
motion | ThemeMotion | required | How long and how things move (ThemeMotion). |
icons | ThemeIcons | required | The icons' style and stroke, and the launcher's glyph (ThemeIcons). |
glass | ThemeGlass? | required | Translucent surfaces (ThemeGlass); null for opaque ones. |
bubbles | String | required | How chat bubbles look: filled, outline or minimal. |
chrome | String | "none" | "none" or "window": with window the chat window, the caption card and every component card open as desktop windows, with a title bar, three coloured dots and a monospaced title. |
chat | ChatTokens? | null | The chat window's values (ChatTokens); null from a server that predates them, when the SDK works them out from colors. |
call | CallTokens? | null | The call screen's values (CallTokens); null from an older server, when the SDK works them out. |
| Property | Type | Meaning |
|---|---|---|
val isDark: Boolean | Boolean | Read it to know these are the dark tokens (scheme == "dark"). |
Its functions: toJson — each with its parameters, what it returns, its errors and an example.
ThemeTokens.toJson
Call it to log the tokens, or hand them to another part of your app.
Parameters — none.
Returns — JSONObject — the tokens as the settings spell them.
Errors — none.
ThemeColors
Read it for every colour the brand's surfaces are drawn in.
data class ThemeColors(…)
What to expect — Each a #RRGGBB colour.
| Field | Type | Default | Meaning |
|---|---|---|---|
background | String | required | Behind everything — a full-screen chat's page. |
surface | String | required | Cards, the voice bar and sheets. |
surfaceRaised | String | required | Surfaces above a surface: a component card, a selected chip's backing. |
text | String | required | Main text. |
textMuted | String | required | Secondary text: captions, labels, times. |
border | String | required | Outlines and separators. |
accent | String | required | The brand colour: buttons, the launcher, the microphone. |
onAccent | String | required | Text and icons drawn on the accent. |
accentSoft | String | required | A pale tint of the accent, for chips, selected rows and the focus ring. |
success | String | required | Good news: online dots, confirmations. |
warning | String | required | Cautions: a weak connection. |
danger | String | required | Errors, the end-call button, a refusal line. |
userBubble | String | required | The user's chat bubbles. |
userBubbleText | String | required | Text in the user's bubbles. |
agentBubble | String | required | The agent's chat bubbles. |
agentBubbleText | String | required | Text in the agent's bubbles. |
scrim | String | required | The dimming behind a sheet or dialog (drawn at 40 %). |
ThemeFont
Read it for the brand's typefaces and type scale.
data class ThemeFont(val body: String, val heading: String, val scale: FontScale, val weights: FontWeights, val letterSpacing: FontPair, val lineHeight: FontPair)
| Field | Type | Default | Meaning |
|---|---|---|---|
body | String | required | The body typeface's family: "system" is the platform's own font; any other is a family your app may bundle (GraineConfig.fonts) or the SDK fetches. |
heading | String | required | The headings' typeface's family, the same way. |
scale | FontScale | required | Type sizes (FontScale). |
weights | FontWeights | required | Weights (FontWeights). |
letterSpacing | FontPair | required | Letter spacing for body and titles, in em (FontPair). |
lineHeight | FontPair | required | Line height for body and titles, as a multiple of the size. |
FontScale
Read it for the brand's type sizes.
data class FontScale(val caption: Double, val body: Double, val title: Double, val display: Double)
What to expect — In sp.
| Field | Type | Default | Meaning |
|---|---|---|---|
caption | Double | required | Captions, labels and times. |
body | Double | required | Body text and messages. |
title | Double | required | Titles and headers. |
display | Double | required | The largest text, such as the call screen's name. |
FontWeights
Read it for the brand's type weights.
data class FontWeights(val body: Int, val strong: Int, val title: Int)
What to expect — CSS weights, 100–900.
| Field | Type | Default | Meaning |
|---|---|---|---|
body | Int | required | Body text. |
strong | Int | required | Emphasis, buttons and labels. |
title | Int | required | Titles. |
FontPair
Read it for one number for body text and one for titles.
data class FontPair(val body: Double, val title: Double)
What to expect — Letter spacing in em, or line height as a multiple of the size, as its field says.
| Field | Type | Default | Meaning |
|---|---|---|---|
body | Double | required | For body text. |
title | Double | required | For titles. |
ThemeShape
Read it for the brand's corners, outlines and launcher shape.
data class ThemeShape(val radius: ThemeRadius, val borderWidth: Double, val launcher: String)
| Field | Type | Default | Meaning |
|---|---|---|---|
radius | ThemeRadius | required | The corner radii (ThemeRadius). |
borderWidth | Double | required | The width of every outline, in dp. |
launcher | String | required | The launcher's shape: circle, squircle, rounded-square, square or pill. |
ThemeRadius
Read it for the brand's corner radii.
data class ThemeRadius(val sm: Double, val md: Double, val lg: Double, val xl: Double)
What to expect — In dp.
| Field | Type | Default | Meaning |
|---|---|---|---|
sm | Double | required | Small corners: chips, a plain-square launcher. |
md | Double | required | Fields and buttons. |
lg | Double | required | Cards and panels. |
xl | Double | required | Sheets and the voice bar. |
ThemeSpacing
Read it for the brand's spacing.
data class ThemeSpacing(val unit: Double, val cardPadding: Double, val gap: Double)
What to expect — In dp.
| Field | Type | Default | Meaning |
|---|---|---|---|
unit | Double | required | The base spacing step. |
cardPadding | Double | required | The padding inside a card. |
gap | Double | required | The gap between items in a card. |
ThemeElevation
Read it for how the brand draws depth.
data class ThemeElevation(val card: ThemeShadow?, val floating: ThemeShadow?, val hardOffset: ThemeOffset?)
| Field | Type | Default | Meaning |
|---|---|---|---|
card | ThemeShadow? | required | A card's shadow (ThemeShadow); null for none. |
floating | ThemeShadow? | required | The shadow of what floats — the voice bar, the launcher, sheets; null for none. |
hardOffset | ThemeOffset? | required | A solid offset drawn instead of shadows (ThemeOffset); null unless the design draws depth that way. |
ThemeShadow
Read it to draw one of the brand's soft shadows.
data class ThemeShadow(val y: Double, val blur: Double, val spread: Double, val opacity: Double, val color: String)
Design.androidShadow turns one into what Android can draw.
| Field | Type | Default | Meaning |
|---|---|---|---|
y | Double | required | How far down the shadow falls, in dp. |
blur | Double | required | How soft it is, in dp. |
spread | Double | required | How far it spreads, in dp. |
opacity | Double | required | How strong it is, 0–1, applied to color. |
color | String | required | Its colour, #RRGGBB. |
ThemeOffset
Read it to draw the brand's hard offset — a solid shadow in the text colour.
data class ThemeOffset(val x: Double, val y: Double)
What to expect — In dp; drawn in colors.text, with an outline to match.
| Field | Type | Default | Meaning |
|---|---|---|---|
x | Double | required | How far right. |
y | Double | required | How far down. |
ThemeMotion
Read it to animate in the brand's way — or not at all.
data class ThemeMotion(val durationMs: Int, val easing: String, val bounce: Double)
What to expect — The phone's own “Remove animations” wins over any number here (Design.motionMs).
| Field | Type | Default | Meaning |
|---|---|---|---|
durationMs | Int | required | How long a transition runs; 0 means none at all. |
easing | String | required | How it moves: standard, emphasized, spring, linear or none. |
bounce | Double | required | How much a spring overshoots, 0–1. |
ThemeIcons
Read it to draw icons in the brand's style.
data class ThemeIcons(val style: String, val stroke: Double, val launcher: LauncherGlyph?)
| Field | Type | Default | Meaning |
|---|---|---|---|
style | String | required | outline, filled, duotone or sharp. |
stroke | Double | required | The stroke width of outline icons, 0.5–4. |
launcher | LauncherGlyph? | required | The launcher's glyph (LauncherGlyph); null shows the brand's logo. |
LauncherGlyph
Read it to draw the launcher's glyph yourself.
data class LauncherGlyph(val id: String, val path: String, val stroke: Boolean)
The voice bar draws it from its path; the launcher fetches it as an image (Design.glyphUrl).
| Field | Type | Default | Meaning |
|---|---|---|---|
id | String | required | The glyph's name, such as waveform — what Design.glyphUrl asks for. |
path | String | required | Its drawing: SVG path data on a 24 × 24 grid. |
stroke | Boolean | required | true to stroke the path, false to fill it. |
ThemeGlass
Read it to draw the brand's translucent surfaces.
data class ThemeGlass(val blur: Double, val surfaceOpacity: Double)
What to expect — Surfaces are colors.surface at surfaceOpacity, with what is behind blurred where the platform can.
| Field | Type | Default | Meaning |
|---|---|---|---|
blur | Double | required | How much what is behind is blurred, in dp. |
surfaceOpacity | Double | required | How opaque the surface is, 0–1. |
ChatTokens
Read it to draw a chat window of your own in the brand's chat design.
data class ChatTokens(…)
ThemeTokens.chat: the chat window's values in one scheme, as the brand set them in the Garage → Chat window. Colours are #RRGGBB.
| Field | Type | Default | Meaning |
|---|---|---|---|
title | String | required | The header's title; empty uses the agent's heading. |
subtitle | String | required | The header's second line while online; empty says “Online”. |
avatar | String | required | The header's mark: logo, glyph or none. |
placeholder | String | required | The message field's placeholder, such as “Message…”. |
showTimestamps | Boolean | required | Whether each message shows its time. |
suggestions | List<String> | required | Up to four conversation starters, shown before the first message; a tap sends one. |
background | String | required | Behind the messages. |
pattern | String | required | none, or dots over the messages in patternColor. |
patternColor | String | required | The pattern's colour. |
headerBackground | String | required | The header's fill (and the status bar's, on the chat screen). |
headerText | String | required | The header's title colour. |
headerSubtext | String | required | The header's second line colour. |
composerBackground | String | required | The message field's fill. |
composerText | String | required | The message field's text. |
composerPlaceholder | String | required | The message field's placeholder colour. |
composerBorder | String | required | The message field's outline. |
size | GraineChatSize | GraineChatSize.COMPACT | How big the window opens (Window size), unless the app or an open says otherwise. |
showUpcomingCalls | Boolean | true | Whether the card of the user's booked calls — and Book a call — shows at the top of the chat (Booked calls). |
upcomingTitle | String | "" | That card's title; empty says “Upcoming call” or “Upcoming calls”. |
showHistory | Boolean | true | Whether the earlier conversation is shown (Earlier messages); off, the agent still has it. |
newConversation | Boolean | true | Whether the header offers New conversation; off, chat.newConversation() still works. |
CallTokens
Read it to draw a call screen of your own in the brand's call design.
data class CallTokens(…)
ThemeTokens.call: the call screen's values in one scheme (Garage → Call screen). Colours are #RRGGBB. Before any session — a call that wakes a closed app on a first launch — the SDK draws Graine Classic's dark call screen.
| Field | Type | Default | Meaning |
|---|---|---|---|
background | String | required | The screen's background (the gradient's top). |
backgroundTo | String? | required | The gradient's bottom; null for a solid background. |
text | String | required | The name and main text. |
textMuted | String | required | The status line and labels. |
avatar | String | required | What the avatar shows: logo, glyph or initials. |
avatarShape | String | required | circle, squircle or rounded-square. |
avatarFill | String | required | The avatar's fill. |
avatarText | String | required | The avatar's initials or glyph colour. |
buttonFill | String | required | A control's fill (mute, loudspeaker). |
buttonIcon | String | required | A control's icon. |
buttonActiveFill | String | required | A control's fill while on (muted, the loudspeaker). |
buttonActiveIcon | String | required | A control's icon while on. |
endFill | String | required | The end-call button's fill. |
endIcon | String | required | The end-call button's icon. |
endShape | String | required | The end-call button: round or pill. |
acceptFill | String | required | The Answer button's fill. |
acceptIcon | String | required | The Answer button's icon. |
declineFill | String | required | The Decline button's fill. |
declineIcon | String | required | The Decline button's icon. |
callbackFill | String | required | The Call back button's fill, on a missed call. |
callbackIcon | String | required | The Call back button's icon. |
captions | Boolean | required | Whether the screen shows what is being said. |
timer | Boolean | required | Whether it shows how long the call has lasted. |
controls | List<String> | required | The in-call controls, in order: mute, speaker, chat, end (an end button is always drawn). |
ringtone | String | required | The ring: system (the phone's), graine or soft. |
statusText | CallStatusText | required | The screen's words (CallStatusText). |
CallStatusText
Read it for the call screen's words, as the brand wrote them.
data class CallStatusText @JvmOverloads constructor(val ringing: String, val connecting: String, val ended: String, val scheduled: String? = null, val missed: String? = null)
What to expect — Absent from an older server, scheduled and missed read “Scheduled call” and “Missed call” through scheduledText and missedText.
| Field | Type | Default | Meaning |
|---|---|---|---|
ringing | String | required | While a call rings, such as “Incoming call”. |
connecting | String | required | While it connects, such as “Connecting…”. |
ended | String | required | Once a call that went well is over, such as “Call ended”. |
scheduled | String? | null | While a call Graine rang at a booked time rings; read scheduledText. |
missed | String? | null | For a ring nobody answered; read missedText. |
| Property | Type | Meaning |
|---|---|---|
val scheduledText: String | String | Show it while a call Graine rang rings (booked by the agent, the user or your team). |
val missedText: String | String | Show it for a ring nobody answered — on the screen and in the missed-call notification. |
companion · const val DEFAULT_SCHEDULED = "Scheduled call" | String | The words used when the brand wrote none for scheduled. |
companion · const val DEFAULT_MISSED = "Missed call" | String | The words used when the brand wrote none for missed. |
Design (object)
Use it for the design's arithmetic — parsing colours, the launcher's corners, the glyph's address, motion.
object Design
| Property | Type | Meaning |
|---|---|---|
const val DEFAULT_PRESET = "graine-classic" | String | The design used for everything Graine leaves out: Graine Classic. |
const val GLYPH_ASSET_VERSION = 2 | Int | The launcher-glyph image version in glyphUrl; a changed drawing gets a new number. |
const val LAUNCHER_SIZE = 56.0 | Double | The launcher's height (and width, unless it is a pill), in dp. |
const val LAUNCHER_GLYPH = 0.46 | Double | The launcher's glyph or logo, as a share of its height. |
val LAUNCHER_SHAPES: List<String> | List<String> | The launcher's shapes: circle, squircle, rounded-square, square, pill. |
val BUBBLES: List<String> | List<String> | The chat bubble styles: filled, outline, minimal. |
val EASINGS: List<String> | List<String> | The motion curves: standard, emphasized, spring, linear, none. |
val ICON_STYLES: List<String> | List<String> | The icon styles: outline, filled, duotone, sharp. |
val CHROMES: List<String> | List<String> | How a surface that holds content is framed: none (plain) or window (a desktop window). |
val CLASSIC_LIGHT: ThemeTokens | ThemeTokens | Graine Classic in light — what the SDK draws when there is no design. |
val CLASSIC_DARK: ThemeTokens | ThemeTokens | Graine Classic in dark. |
Its functions: normaliseHex, glyphUrl, launcherCorner, motionMs, androidShadow, fontFamily, fontFor — each with its parameters, what it returns, its errors and an example.
Design.normaliseHex
Call it to read any hex colour into the #RRGGBB form the tokens use.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
value | Any? | required | A colour such as "#abc", "AABBCC" or "#aabbcc". |
Returns — String? — "#RRGGBB" in capitals, or null for anything that is not a hex colour. Example value: "#AABBCC".
Errors — none.
Design.glyphUrl
Call it for the address of the launcher's glyph as an image, to show it in a view of your own.
The ID is lower-cased, the colour written as capital hex without “#”, the size held to 16–512 physical pixels (dp × density, so it is sharp on every screen), and the stroke held to 0.5–4 in quarter steps.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
origin | String? | required | Your GraineConfig.baseUrl; Graine's when null. |
id | String? | required | The glyph's LauncherGlyph.id. |
color | String? | required | The colour to draw it in (#RRGGBB); white when not a colour. |
sizePx | Double | required | Its size in physical pixels; 0 asks for 96. |
stroke | Double | 2.0 | The stroke width, for a stroked glyph. |
Returns — String — the image's address. Example value: "https://www.graine.ai/api/embed/glyph?id=waveform&c=FFFFFF&s=72&w=2&v=2".
Errors — none.
Design.launcherCorner
Call it for the launcher's corner radius at a height, to draw a launcher of your own in the brand's shape.
circle and pill are fully round; squircle is 30 % of the height; rounded-square the design's large radius; square its small one — never more than half the height.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
shape | String? | required | The launcher's shape (ThemeShape.launcher). |
height | Double | required | The launcher's height, in dp. |
radius | ThemeRadius | required | The design's radii (ThemeShape.radius). |
Returns — Double — the corner radius, in dp. Example value: 16.8 (a 56 dp squircle).
Errors — none.
Design.motionMs
Call it for how long a transition of yours should run, honouring both the design and the phone.
The design's duration — or 0 (none) when the design says none, when there is no design, or when the phone has animations off.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
motion | ThemeMotion? | required | The design's motion (ThemeTokens.motion). |
animatorScale | Float | required | The system's animator duration scale (Settings.Global.ANIMATOR_DURATION_SCALE); 0 is “Remove animations”. |
Returns — Long — milliseconds. Example value: 200.
Errors — none.
Design.androidShadow
Call it to draw one of the brand's shadows the way Android can.
Android draws depth as an elevation and a shadow colour's alpha (which it multiplies by the theme's own); this works out both so the shadow lands where the design meant.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
s | ThemeShadow? | required | The shadow (ThemeElevation.card or floating). |
Returns — Pair<Double, Double> — the elevation in dp and the shadow colour's alpha, 0–1; (0, 0) for no shadow. Example value: (8.0, 0.84).
Errors — none.
Design.fontFamily
Call it to know which typeface family to ask your app for.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
name | String? | required | A design family (ThemeFont.body or heading). |
Returns — String? — the family, or null for the platform's own font ("system" or empty). Example value: "Inter".
Errors — none.
Design.fontFor
Call it to find your bundled typeface for a design family.
Matched without regard to case.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
fonts | Map<String, T> | required | Your fonts by family name — usually GraineConfig.fonts. |
family | String? | required | The design family. |
Returns — T? — your font, or null when you have none for it. Example value: Typeface@3e1a….
Errors — none.
NativeAppearance
Read it for the dashboard's appearance settings, worked out for a phone — when Graine sends no design.
data class NativeAppearance @JvmOverloads constructor(val accent: String, val heading: String, val logoUrl: String?, val radius: Double, val logoPlate: LogoPlate = LogoPlate.ACCENT)
From Appearance.resolve.
| Field | Type | Default | Meaning |
|---|---|---|---|
accent | String | required | The accent, always a valid #rgb or #rrggbb. |
heading | String | required | The panel's heading (such as “Talk to our AI assistant”) — not the assistant's name. |
logoUrl | String? | required | The brand's logo, as an absolute address; null when none is set. |
radius | Double | required | Corner rounding, held to the largest your view can draw (maxRadius). |
logoPlate | LogoPlate | LogoPlate.ACCENT | What the logo sits on where it stands in for the launcher glyph (LogoPlate). |
Appearance
Use it to read the dashboard's appearance settings and colours when Graine sends no design.
object Appearance
| Property | Type | Meaning |
|---|---|---|
const val DEFAULT_ACCENT = "#318CE7" | String | The accent used when none is set. |
const val DEFAULT_ORIGIN = "https://www.graine.ai" | String | Where logo addresses point when no origin is given. |
Its functions: resolve, isDark, palette, luminance, argb, tint, encodeURIComponent — each with its parameters, what it returns, its errors and an example.
Appearance.resolve
Call it to work out the dashboard's appearance for a view of your own, with your values as the fallback.
The dashboard decides; your values fill only what it leaves out. The accent may arrive without its “#”.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
appearance | JSONObject? | required | SessionConfig.appearance. |
accent | String | required | Your accent, used when the dashboard sets none. |
heading | String | required | Your heading, used when the dashboard sets none. |
radius | Double? | null | Your corner radius, used when the dashboard sets none (else 18). |
maxRadius | Double? | null | The largest radius your view can draw (at most 48). |
origin | String? | null | Your GraineConfig.baseUrl, for the logo's address. |
Returns — NativeAppearance (NativeAppearance). Example value: NativeAppearance(accent=#318CE7, heading=Talk to our AI assistant, logoUrl=null, radius=18.0, logoPlate=ACCENT).
Errors — none.
Appearance.isDark
Call it to decide light or dark for an appearance setting.
"auto" (or none) follows the phone, not a default of the dashboard's.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
theme | String? | required | "light", "dark" or "auto". |
deviceDark | Boolean? | required | Whether the phone is in dark mode; null when unknown (light). |
Returns — Boolean — true for dark. Example value: true.
Errors — none.
Appearance.palette
Call it for the colours to draw a component in, from the appearance settings.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
appearance | JSONObject? | required | SessionConfig.appearance. |
deviceDark | Boolean? | required | Whether the phone is in dark mode. |
accent | String? | required | The accent, to choose a readable colour on it. |
Returns — WidgetPalette (WidgetPalette). Example value: WidgetPalette(surface=#FFFFFF, border=#E5E4E0, text=#101318, muted=#5F6672, onAccent=#FFFFFF, mode=light).
Errors — none.
Appearance.luminance
Call it to know how light a colour is — to pick readable text on it.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
hex | String | required | A #rrggbb colour. |
Returns — Double — its relative luminance (WCAG), 0 for black to 1 for white; 0 for anything that is not a colour. Example value: 0.2126.
Errors — none.
Appearance.argb
Call it to turn a hex colour into an Android colour int.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
hex | String? | required | #rgb, #rrggbb or #aarrggbb, with or without the “#”. |
Returns — Int? — the ARGB colour, or null for anything else. Example value: -13530905 (0xFF318CE7).
Errors — none.
Appearance.tint
Call it for a colour at partial strength — a badge or a selected row's fill.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
hex | String? | required | A #rrggbb colour; a slate grey for anything else. |
alpha | Double | required | How strong, 0–1. |
Returns — Int — the ARGB colour. Example value: 0x1F318CE7.
Errors — none.
Appearance.encodeURIComponent
Call it to put a value into a web address the way a browser would.
Everything but letters, digits and - _ . ! ~ * ' ( ) is escaped, as UTF-8.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
s | String | required | The text to encode. |
Returns — String. Example value: "a%20b%2Fc".
Errors — none.
WidgetPalette
Read it for the colours to draw a component in.
data class WidgetPalette(val surface: String, val border: String, val text: String, val muted: String, val onAccent: String, val mode: String)
From Appearance.palette. Colours are #RRGGBB.
| Field | Type | Default | Meaning |
|---|---|---|---|
surface | String | required | The component's background. |
border | String | required | Its outlines. |
text | String | required | Its main text. |
muted | String | required | Its secondary text. |
onAccent | String | required | Text on the accent — chosen by how light the accent is, so a yellow accent still has a readable label. |
mode | String | required | "light" or "dark", after resolving "auto" against the phone. |
LogoPlate
Read it to know what the brand's logo sits on where it stands in for the launcher glyph.
enum class LogoPlate(val wire: String)
The dashboard picks it from the logo's pixels when the logo is uploaded, and the brand can change it (Appearance → Logo).
| Property | Type | Meaning |
|---|---|---|
val wire: String | String | The setting's word: accent or white. |
| Case | Meaning |
|---|---|
ACCENT | The shape's own accent fill — for a white or light logo made for a coloured background. The default. |
WHITE | White with a hairline in the border colour — for a coloured or dark logo, or an opaque image. |
Its functions: from — each with its parameters, what it returns, its errors and an example.
LogoPlate.from
Call it to read the logo-plate setting.
Exactly "white" is WHITE; anything else — "WHITE", a number, nothing — is ACCENT.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
value | Any? | required | The appearance's logoPlate value. |
Returns — LogoPlate. Example value: LogoPlate.WHITE.
Errors — none.
Logo
Use it to draw the brand's logo whole, on its plate — in a launcher or avatar of your own.
object Logo
The logo is fitted — whole, proportions kept, centred — into the largest square its shape holds, never cropped to fill it.
| Property | Type | Meaning |
|---|---|---|
const val PLATE_WHITE = "#FFFFFF" | String | A white plate's fill. |
const val ON_WHITE = "#101318" | String | A pill launcher's label colour on the white plate. |
const val CLASSIC_HAIRLINE = "#E5E7EB" | String | The hairline under a white plate when there is no design. |
Its functions: box, plate — each with its parameters, what it returns, its errors and an example.
Logo.box
Call it for the size of the square the logo is fitted into, inside a shape.
80 % of the shape for rounded-square and square; 70 % for the rest (the largest square a circle holds; a squircle is treated as one; a pill by its height) — rounded down, so its corners never poke out.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
shape | String? | required | The shape: circle, squircle, rounded-square, square or pill. |
size | Double | required | The shape's size across (a pill: its height), in your units. |
Returns — Int — the square's side, in the same units, at least 1. Example value: 39 (a 56 dp circle).
Errors — none.
Logo.plate (function)
Call it for what to draw under the logo: the fill, and an outline or none.
WHITE is white with an outline of at least 1 in border; ACCENT is the accent with no outline. Use ACCENT for anything other than the logo image: a glyph or a letter always sits on the accent colour.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
plate | LogoPlate | required | The logo's plate. |
accent | String | required | The accent colour, #RRGGBB. |
border | String | required | The outline colour for a white plate. |
borderWidth | Double | 1.0 | The outline's width. |
Returns — Logo.Plate (Logo.Plate). Example value: Plate(fill=#FFFFFF, outline=Stroke(width=1.0, color=#E5E4E0)).
Errors — none.
Logo.Stroke (type)
Read it for an outline: its width and colour.
data class Stroke(val width: Double, val color: String)
| Field | Type | Default | Meaning |
|---|---|---|---|
width | Double | required | In the shape's units. |
color | String | required | #RRGGBB. |
Logo.Plate (type)
Read it for what to draw under a logo.
data class Plate(val fill: String, val outline: Stroke?)
| Field | Type | Default | Meaning |
|---|---|---|---|
fill | String | required | The plate's colour, #RRGGBB. |
outline | Stroke? | required | Its outline; null for none. |
Going live
- Debug logging.
GraineConfig(debug = BuildConfig.DEBUG): in debug builds the SDK logs to logcat under the tagGraine— what the voice engine says, the call's steps — so “the agent did not speak” is answered in one read of it. Leave it off in release builds. - Allowed domains.
app://must be in the agent's Allowed domains (Agent → Embed & Widgets) for the app's requests to be accepted. - Google Play. On Android 14 and up, Play asks apps that declare
USE_FULL_SCREEN_INTENTor the microphone foreground service to justify them; calls need both. An app that never uses calls removes them (Requirements). Ask forPOST_NOTIFICATIONSduring onboarding — without it the phone cannot ring. - R8 / ProGuard. The library's rules ship inside it; nothing to add.
- Calls Graine rings need your push credentials uploaded in Workspace settings → In-app calling, and Firebase Cloud Messaging in the release build — the SDK registers for pushes itself; an app with its own messaging service keeps the two lines in it (Calls Graine rings).
- WebRTC for calls. Keep
ai.graine:inapp-android-rtcat the SDK's version, put its notices in your open-source licences screen, and watchcalls.transportFallbackReasonon calls that ran on the WebSocket — a network that blocks WebRTC shows asice_timeout(Adding WebRTC calls). - The version.
GraineVersion.NAMEsays which SDK build the app runs; it is also sent with the device registration and lands on the call record. Include it when you ask for support.
GraineVersion
Read it to know which version of the SDK your app runs — include it when you ask for support.
object GraineVersion
| Property | Type | Meaning |
|---|---|---|
const val NAME: String | String | The SDK's version: "0.10.3" in this release. It lands on the call record to tell builds apart; nothing secret. |
Events and listeners
What the SDK tells your app as it happens. GraineListener (on an agent) carries the voice bar's call, the chat, actions, friction and errors; GraineCallListener (on calls) carries native calls. Every method has a default that does nothing, so you override only what you use — in Kotlin or Java. All are called on the main thread.
GraineInApp.addListener
Call it to start hearing an agent's events.
Kept until removed. The SDK's own views add themselves; this is for your app.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
l | GraineListener | required | The listener (GraineListener). |
Returns — nothing.
Errors — none.
GraineInApp.removeListener
Call it to stop hearing an agent's events — when the screen that listens goes away.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
l | GraineListener | required | The listener you added. |
Returns — nothing.
Errors — none.
GraineListener
Implement it to hear an agent's events — the voice bar's call, the chat, actions, friction and errors.
interface GraineListener
What to expect — Implement only the methods you need; the others do nothing. Called on the main thread.
Its functions: onSessionReady, onCallState, onCaption, onWidget, onWidgetPending, onEnded, onMicDenied, onQuality, onNetwork, onReconnecting, onConnectProgress, onTransport, onConnectivity, onActionRequested, onActionCompleted, onFriction, onError, onChatState, onChatMessage — each with its parameters, what it returns, its errors and an example.
GraineListener.onSessionReady
Implement it to know the agent's settings have loaded — to offer what depends on them, such as booking.
Once per launch, when the settings arrive (GraineInApp.session from then on).
| Parameter | Type | Default | Meaning |
|---|---|---|---|
session | SessionConfig | required | The agent's settings (SessionConfig). |
Returns — your implementation returns nothing; the SDK calls it on the main thread when it happens.
Errors — none.
GraineListener.onCallState
Implement it to follow the voice bar's call in controls of your own.
Fires when either changes.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
connected | Boolean | required | The call is live. |
connecting | Boolean | required | A call is being placed. |
Returns — your implementation returns nothing; the SDK calls it on the main thread when it happens.
Errors — none.
GraineListener.onCaption
Implement it to show what is being said on the voice bar's call.
A line being said, again as its words arrive (live) and once more when it is final. voice.captions keeps the last 12.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
role | Role | required | Who is speaking: AGENT or CUSTOMER. |
text | String | required | The words so far. |
live | Boolean | required | true while the words are still arriving. |
Returns — your implementation returns nothing; the SDK calls it on the main thread when it happens.
Errors — none.
GraineListener.onWidget
Implement it to draw the agent's components yourself, instead of with GraineWidgetView.
The agent drew a component — on the voice-bar call or in the chat. Read it with GraineWidget.adopt, and answer it with GraineInApp.answerWidget, which answers whichever conversation is live.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
widget | JSONObject | required | The component as the agent sent it. |
Returns — your implementation returns nothing; the SDK calls it on the main thread when it happens.
Errors — none.
GraineListener.onWidgetPending
Implement it to show your own loader while a component is on its way.
A component is coming and its content is loading.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
hint | JSONObject | required | What is coming (its kind, for the loader's shape). |
Returns — your implementation returns nothing; the SDK calls it on the main thread when it happens.
Errors — none.
GraineListener.onEnded
Implement it to know the voice bar's call is over.
Once per call, however it ended.
Parameters — none.
Returns — your implementation returns nothing; the SDK calls it on the main thread when it happens.
Errors — none.
GraineListener.onMicDenied
Implement it to explain how to allow the microphone when the user refused it.
The microphone was refused, or there is no usable one; the call does not start.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
reason | String | required | NotAllowedError (refused) or NotFoundError (no usable microphone). |
Returns — your implementation returns nothing; the SDK calls it on the main thread when it happens.
Errors — none.
GraineListener.onQuality
Implement it to show the user when the network is the problem.
One word for the connection; fires only when the word changes.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
quality | ConnectionQuality | required | The connection now (ConnectionQuality). |
Returns — your implementation returns nothing; the SDK calls it on the main thread when it happens.
Errors — none.
GraineListener.onNetwork
Implement it to know the device went offline or came back.
As the voice engine sees it.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
online | Boolean | required | true when back online. |
Returns — your implementation returns nothing; the SDK calls it on the main thread when it happens.
Errors — none.
GraineListener.onReconnecting
Implement it to show “Reconnecting…” while a dropped call is re-established.
The network interrupted the call and it is being re-established; not an end.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
active | Boolean | required | true while it is reconnecting. |
Returns — your implementation returns nothing; the SDK calls it on the main thread when it happens.
Errors — none.
GraineListener.onConnectProgress
Implement it to say where a call being placed is, and when it is slow or could not connect.
Fires when any of the three changes; the same as voice.connectStage, slowConnect and callFailed.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
stage | ConnectStage? | required | Where the connect is (ConnectStage); null outside one. |
slow | Boolean | required | The user has waited past Timing.slowConnectMs: say “Still connecting…”. |
failed | Boolean | required | The call could not be connected at all: offer to try again. |
Returns — your implementation returns nothing; the SDK calls it on the main thread when it happens.
Errors — none.
GraineListener.onTransport
Implement it to know which audio path carries the voice-bar call, and why it is not the one asked for.
Fires when a call's path is settled.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
transport | Transport | required | The path carrying the call. |
requested | Transport | required | The path asked for (your config, else the dashboard's). |
reason | String? | required | Why they differ, in a word — for example not_configured (high-quality audio is not enabled for the workspace) or unreachable; null when they do not. |
message | String? | required | A sentence about it, for your logs. |
Returns — your implementation returns nothing; the SDK calls it on the main thread when it happens.
Errors — none.
GraineListener.onConnectivity
Implement it to follow the voice engine loading — to show a retry when it fails.
Fires on every change. When every reload is spent, onError also says “The assistant could not load. Check your connection and try again.”
| Parameter | Type | Default | Meaning |
|---|---|---|---|
state | Connectivity | required | Loading, ready, retrying or failed (Connectivity). |
attempt | Int | required | How many reloads have been made. |
reason | String? | required | Why it is retrying or failed, in a word — stalled, boot_timeout, load_cap, http-<status>, load-error:<code>, or session (the agent's settings could not load); null otherwise. |
Returns — your implementation returns nothing; the SDK calls it on the main thread when it happens.
Errors — none.
GraineListener.onActionRequested
Implement it to know the agent asked for one of your actions — to log it, or show progress.
Before your handler runs.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
name | String | required | The action's name. |
Returns — your implementation returns nothing; the SDK calls it on the main thread when it happens.
Errors — none.
GraineListener.onActionCompleted
Implement it to know what an action answered the agent.
Your handler's answer, or the SDK's when it was late, threw or was not registered.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
name | String | required | The action's name. |
result | ActionResult | required | What the agent was told (ActionResult). |
Returns — your implementation returns nothing; the SDK calls it on the main thread when it happens.
Errors — none.
GraineListener.onFriction
Implement it to react when the user looks stuck — badge a control, count it.
On one screen, with a field outstanding, through a stretch of a live conversation. The agent has already been told.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
signal | FrictionSignal | required | What was noticed (FrictionSignal). |
Returns — your implementation returns nothing; the SDK calls it on the main thread when it happens.
Errors — none.
GraineListener.onError
Implement it to see misconfiguration — a key, an allowed domain — in your logs; never show it to the user.
Loud for you, invisible to the user. The reasons are listed under Errors.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
message | String | required | What is wrong, in a sentence. |
Returns — your implementation returns nothing; the SDK calls it on the main thread when it happens.
Errors — none.
GraineListener.onChatState
Implement it to follow the chat's connection in a chat screen of your own.
The text conversation connected, is connecting or reconnecting, or is off; chat.reconnecting tells a reconnect from a first connect.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
connected | Boolean | required | The chat is up. |
connecting | Boolean | required | It is being opened, or reopened after a drop. |
Returns — your implementation returns nothing; the SDK calls it on the main thread when it happens.
Errors — none.
GraineListener.onChatMessage
Implement it to hear the chat's lines as they come — to badge a closed chat, or log them.
The agent's turn as it streams in (live, the words so far), then once more when it is complete; the user's line when they send it. Restored lines are not repeated here.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
role | Role | required | Who said it. |
text | String | required | The words so far. |
live | Boolean | required | true while the agent's words are still arriving. |
Returns — your implementation returns nothing; the SDK calls it on the main thread when it happens.
Errors — none.
GraineCalls.addListener
Call it to start hearing native calls' events.
Kept until removed. One engine for the whole app: you hear every call, whichever agent it is with.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
l | GraineCallListener | required | The listener (GraineCallListener). |
Returns — nothing.
Errors — none.
GraineCalls.removeListener
Call it to stop hearing native calls' events.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
l | GraineCallListener | required | The listener you added. |
Returns — nothing.
Errors — none.
GraineCallListener
Implement it to hear native calls: rings, state, outcomes, captions and controls.
interface GraineCallListener
What to expect — Implement only the methods you need; the others do nothing. Called on the main thread.
Its functions: onIncomingCall, onCallState, onCallOutcome, onCallCaption, onAgentSpeaking, onCallControls — each with its parameters, what it returns, its errors and an example.
GraineCallListener.onIncomingCall
Implement it to know an agent's call started ringing — for your own interface or analytics.
The SDK shows the ring itself.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
call | GraineCall | required | The ringing call (GraineCall). |
Returns — your implementation returns nothing; the SDK calls it on the main thread when it happens.
Errors — none.
GraineCallListener.onCallState
Implement it to drive a call screen of your own.
The call moved.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
state | GraineCallState | required | Where the call is now (GraineCallState). |
call | GraineCall? | required | The call; null once it is back to IDLE. |
Returns — your implementation returns nothing; the SDK calls it on the main thread when it happens.
Errors — none.
GraineCallListener.onCallOutcome
Implement it to send what became of each call to your server.
Once per call (an answered call also reports how it ended).
| Parameter | Type | Default | Meaning |
|---|---|---|---|
callId | String | required | The call's ID: your server's call_id, Graine's for a ring it sent, or the one startCall returned. |
outcome | GraineCallOutcome | required | What became of it (GraineCallOutcome). |
Returns — your implementation returns nothing; the SDK calls it on the main thread when it happens.
Errors — none.
GraineCallListener.onCallCaption
Implement it to show what is being said on the call.
The agent's words as they are said (live), then the line once its turn is complete; the user's likewise.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
role | Role | required | Who is speaking. |
text | String | required | The words so far. |
live | Boolean | required | true while the line is still arriving. |
Returns — your implementation returns nothing; the SDK calls it on the main thread when it happens.
Errors — none.
GraineCallListener.onAgentSpeaking
Implement it to animate the agent's avatar while its voice is heard.
Called each time the agent starts or stops being heard.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
speaking | Boolean | required | true while it is heard. |
Returns — your implementation returns nothing; the SDK calls it on the main thread when it happens.
Errors — none.
GraineCallListener.onCallControls
Implement it to keep your own mute and loudspeaker controls in step.
Muted or the loudspeaker changed — from your app, or from the call screen.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
muted | Boolean | required | The user's voice is not being sent. |
speaker | Boolean | required | The loudspeaker is on. |
Returns — your implementation returns nothing; the SDK calls it on the main thread when it happens.
Errors — none.
Errors
A request that asks Graine on the user's behalf answers its callback with a GraineError carrying a code. Branch on the code; show the message, which is written for the user and never carries the identifier, traits or a push token. Misconfiguration of the voice bar and chat arrives in GraineListener.onError instead, and calling the SDK before init throws IllegalStateException — a mistake in the app, not a refusal.
GraineError
Read it in a callback to decide what to do about a refused request.
data class GraineError @JvmOverloads constructor(val code: GraineErrorCode, val message: String, val retryAfterSeconds: Int? = null)
What to expect — Passed by every request that asks Graine. A refusal made on the device before anything was sent carries the same codes.
| Field | Type | Default | Meaning |
|---|---|---|---|
code | GraineErrorCode | required | Branch on it: the reason in one stable word (GraineErrorCode). |
message | String | required | Show it: Graine's sentence (or the SDK's), fit for the user. |
retryAfterSeconds | Int? | null | Use it to wait before trying again: how long Graine asked, with RATE_LIMITED; null when it did not say. |
GraineErrorCode
Read it to branch on why a request was refused.
sealed class GraineErrorCode(val wire: String)
What to expect — A code newer than this SDK arrives as Unknown(raw), carrying its word: give every when an else.
| Property | Type | Meaning |
|---|---|---|
val wire: String | String | Graine's word for the code — too_soon, network, … — for logs. |
companion · val KNOWN: List<GraineErrorCode> | List<GraineErrorCode> | Every code this build knows. |
| Case | Meaning |
|---|---|
INVALID_KEY | The publishable key is not one, or belongs to no agent. |
ORIGIN_REFUSED | The agent does not allow apps yet: add app:// to its Allowed domains. |
RATE_LIMITED | Too many requests: wait retryAfterSeconds when Graine said how long. |
INVALID_REQUEST | A field is missing or malformed — a blank call ID, say. |
IDENTITY_REQUIRED | The request needs a signed-in user: identify() with the trait the agent's Who is the user? setting names. |
DEVICE_LIMIT | The user has already reached the agent's limit of devices. |
SCHEDULING_OFF | Booking is switched off for this agent (Calls & scheduling). |
TOO_SOON | Earlier than schedulingRules.earliest(now). |
TOO_FAR | Later than schedulingRules.latest(now). |
OUTSIDE_WINDOW | Outside the agent's calling hours. |
TOO_MANY_PENDING | The user already has the most calls waiting that the agent allows. |
NOT_FOUND | No such booking for this user or install. |
UNAVAILABLE | Graine could not answer just now. Worth trying again later. |
NETWORK | Nothing came back: the phone is offline, or Graine could not be reached. Worth trying again. |
Unknown(raw) | A code this build does not know, as Graine sent it. |
Its functions: from — each with its parameters, what it returns, its errors and an example.
GraineErrorCode.from
Call it to read an error word — from your server, say — into a GraineErrorCode.
Case and surrounding spaces do not matter.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
wire | String | required | The word, such as "too_soon". |
Returns — GraineErrorCode — a known code, or Unknown(wire). Example value: GraineErrorCode.TOO_SOON.
Errors — none.
GraineCallsHostException
Catch it only if you build a React Native or Flutter bridge yourself: it names a wrong call option.
class GraineCallsHostException(val code: String, message: String) : IllegalArgumentException(message)
Thrown by GraineCallsHost.configure when an option is wrong, with a sentence that names it (“displayName is required”, “ringTimeoutMs must be a number”). A native Android app never sees it.
| Property | Type | Meaning |
|---|---|---|
val code: String | String | The platform error's code: invalid_options. |
message | String | The sentence naming the option that is wrong — for the developer. |
| Code | wire | HTTP | Means | Raised by | Fix |
|---|---|---|---|---|---|
INVALID_KEY | invalid_key | 401 | The publishable key is not one, or belongs to no agent | Every request to Graine | Copy the key again from Agent → Embed & Widgets → Install |
ORIGIN_REFUSED | origin_refused | 403 | app:// is missing from the agent's Allowed domains | Every request | Add app:// |
RATE_LIMITED | rate_limited | 429 | Too many requests from this install, key or address | Every request | Wait retryAfterSeconds, then retry |
INVALID_REQUEST | invalid_request | 400 | A field is missing or malformed — a time without its offset, a reason over 120 characters, a blank ID | scheduleCall, cancelScheduledCall, device registration | Fix the request |
IDENTITY_REQUIRED | identity_required | 400 | No user is identified, or the identifier is a placeholder | memories, forgetMemory, bookings that need a user | identify() with the identifier first |
DEVICE_LIMIT | device_limit | 403 | This install is not one of the user's allowed devices | memories, forgetMemory, newConversation, scheduleCall | Sign out on another device, raise Devices per user, or Reset devices for the user |
SCHEDULING_OFF | scheduling_off | 403 (on the device first) | Users may not book from the app | scheduleCall | Turn it on, or hide booking when bookingAvailable is false |
TOO_SOON | too_soon | 400 (on the device first) | Earlier than earliest(now) | scheduleCall | Offer times from earliest(now) |
TOO_FAR | too_far | 400 (on the device first) | Later than latest(now) | scheduleCall | Offer times up to latest(now) |
OUTSIDE_WINDOW | outside_window | 400 | The time falls outside the agent's calling hours, in the user's time zone | scheduleCall | Show the message (it names a time that works); offer another time |
TOO_MANY_PENDING | too_many_pending | 409 | The user already has the most calls waiting | scheduleCall | Cancel one first |
NOT_FOUND | not_found | 404 | No such booking for this user or install | cancelScheduledCall | Read scheduledCalls() again |
UNAVAILABLE | unavailable | 503 (504 on a timeout) | Graine could not answer just now — including a booking refused because the agent is switched off or out of credit | Every request | Retry with backoff; check the agent is on and the workspace has credit |
NETWORK | network | — | No answer came back: offline, or the connection dropped | Every request | Retry when online |
Unknown(raw) | the word | other | A code newer than the SDK | Every request | Log the code and message; show the message |
When the voice bar or chat cannot start, the reason arrives through GraineListener.onError, not as a coded error: key_missing, key_malformed (publishable keys start with pk_live_), key_unknown (copy the key again); no_origin (add app://); no_domains_configured (the list is empty); origin_not_allowed; not_configured (high-quality audio is not enabled for the workspace; calls use the standard path); and, when every reload of the voice engine is spent, “The assistant could not load. Check your connection and try again.”. A refused microphone arrives as onMicDenied("NotAllowedError") (or NotFoundError for no usable microphone). A native call that cannot go on is not an error at all: it is its outcome (GraineCallOutcome).
Rate limits and quotas
| Limit | Value | Where it applies |
|---|---|---|
| Bookings from one install | 10 an hour | scheduleCall, the booking sheet, and the agent's bookings in that install's conversations |
| Bookings in a workspace | 1,000 an hour | All in-app bookings |
| Calls waiting per user | 3 by default (1–10) | At most this many upcoming calls per user |
| Devices per user | 2 by default (1–5) | Devices per user; idle devices free their place after 30 days |
| Rings per user per day | 3 by default (1–10) | Ring each user at most, in the user's own day |
| Quiet period after a decline | 6 hours | No ring for that user |
| Notice and horizon | 5 minutes (2–1440) · 14 days (1–30) | With at least this much notice · Booked at most this far ahead |
| Ring time · re-ring | 45 s (20–60) · once after 10 min (0–120) | Ring for · After a missed call, ring once more after |
| Lateness | 10 minutes | A booked call more than 10 minutes late is not rung |
| Booking reason · variables | 120 characters · 12 variables, about 1 KB | Bookings and calls |
| Memories listed | At most 100, newest first | memories() |
| Conversation kept | 50 messages, 7 days after the last | Conversations that continue |
| Requests per key and address, per minute | Starting a conversation 30 (and 300 per key); high-quality audio sessions 10 (120 per key); booking, listing, cancelling, device registration, memory list and sign-out 30 (300 per key); call outcomes 60 (600 per key); forgetting memory 10 (120 per key); listing memories also 30 per install | Answered RATE_LIMITED with Retry-After |
| Action handlers · component data | 6 seconds · 8 seconds | After that, the agent is told the action was not done; a component shows its authored content |
Lower-level building blocks
Public so a custom interface — or a React Native or Flutter bridge — can reuse them; an integration does not need them.
GraineCallsHost
Use it only to build a React Native or Flutter bridge to the native call engine; a native Android app uses GraineInApp.get().calls.
object GraineCallsHost
The one object Graine's React Native and Flutter call packages drive the native calls through, in plain values — strings, numbers, booleans, null, lists and string-keyed maps. The packages only pass values; every rule — the options, the snapshot, where events go, how actions are answered — is here. Several agents: configure names the others under agents, startCall takes {agent}, and the snapshot and the incoming, outcome and action events say which agent (agent, "default" for the default one).
What to expect — Main thread: a call from another thread moves there first (configure, restore, snapshot, handlePush, startCall, addSink and needsMicrophonePermission wait for their answer), and events are sent on it. Events: state (the snapshot, at most once per turn of the main loop, only when it changed), outcome, incoming and action.
Its functions: restore, configure, snapshot, enableVoIPPushes, handlePush, onNewToken, startCall, answer, decline, end, setMuted, setSpeaker, setIdentity, setMemory, installId, setVariables, setScreen, setActions, resolveAction, addSink, removeSink, onRequestPermissionsResult, onNewIntent, needsMicrophonePermission, microphoneDenied — each with its parameters, what it returns, its errors and an example. Permissions and the setup check, for a bridge, are below.
GraineCallsHost: permissions and the setup check
Use them only from a bridge: GraineCalls' permissions and setup check, as plain values.
| Function | What it does |
|---|---|
GraineCallsHost.callPermissions(context) | {microphone, notifications, fullScreen}, each granted, denied, blocked or notNeeded — safe before anything is configured. |
GraineCallsHost.requestCallPermissions(context, activity, done) | Asks, over activity, for what the system's dialog can still ask for, from the SDK's own transparent screen; done gets the same map after, on the main thread. |
GraineCallsHost.openCallSettings(context, activity, item) | Opens Settings for microphone, notifications or fullScreen (full_screen and any case too); false for any other word. |
GraineCallsHost.callSetupProblems() | [{code, message}]: what the app's setup still lacks — empty when nothing is, and before anything is configured. |
Returns — as above. Errors — none.
GraineCallsHost.restore
Call it at a cold start, before any JavaScript or Dart runs, to apply the options the last configure kept.
So a launch woken by a push can ring before your bridge has configured anything. handlePush does this by itself.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
context | Context | required | Any context of your app. |
Returns — Boolean — true when something was restored; false when nothing was kept, or this launch is already configured. Example value: true.
Errors — none.
GraineCallsHost.configure
Call it from your bridge to configure the calls, and keep the options for the next cold start.
The first call makes the SDK's agent (GraineInApp.init); later ones update the calls. The options:
| Option | Type | Default | Meaning |
|---|---|---|---|
publishableKey | string | required | The default agent's publishable key. |
displayName | string | required | The name on the ring and the call screen when the push names nobody. |
baseUrl | string | "https://www.graine.ai" | Where Graine is. |
ringtoneSound | string or null | null | A res/raw sound name for the ring. |
ringTimeoutMs | number | 30000 | How long a call from your server rings; anything not above 0 is the default. |
appVersion | string or null | null | Lands on the call record. |
callScreen | "sdk" or "app" | "sdk" | Who draws the call while the app is open: the SDK's call screen, or your app's own. |
callTransport | "auto", "websocket" or "webrtc" | "auto" | How calls carry their audio (GraineCallsConfig.callTransport); WebRTC needs the WebRTC module in the app. Exactly these words: anything else is refused. |
debug | boolean | false | The SDK's debug log (GraineConfig.debug). |
android | { smallIcon } | none | A drawable name for the status-bar icon. |
ios | { iconTemplate, includesCallsInRecents } | none | The iOS options, checked here too so one set of options is valid on both platforms. |
agents | { name: { publishableKey, baseUrl? } } | none | Any further agents the app uses, each with its own publishable key. |
Unknown keys are ignored and null is the same as absent; a known key of the wrong type is refused, naming it.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
context | Context | required | Any context of your app. |
options | Map<*, *>? | required | The options above. |
Returns — nothing.
Errors — GraineCallsHostException (invalid_options) naming the option that is wrong — “publishableKey is required”, “callScreen must be "sdk" or "app"”, “callTransport must be "auto", "websocket" or "webrtc"”.
GraineCallsHost.snapshot
Call it to draw the call screen in your bridge's interface.
The call as a call screen draws it, every key always present: state (idle, ringing, connecting, active, ended), callId, direction (incoming, outgoing), reason, callerName, muted, speakerOn, agentSpeaking, caption ({role, text, live}), connectedAt (milliseconds since the epoch), error (why the last call failed), agent, transport (webrtc or websocket, once decided) and transportFallbackReason (why a call that would have used WebRTC runs on the WebSocket). With no call, everything but error is empty.
Parameters — none.
Returns — Map<String, Any?>. Example value: {state=active, callId=call_6f1c…, direction=outgoing, reason=billing_help, callerName=null, muted=false, speakerOn=false, agentSpeaking=true, caption={role=agent, text=Sure…, live=true}, connectedAt=1792049400000, error=null, agent=default, transport=webrtc, transportFallbackReason=null}.
Errors — none.
GraineCallsHost.enableVoIPPushes
Nothing to do on Android: iOS registers for VoIP pushes here, and Android apps hand their pushes to handlePush.
Parameters — none.
Returns — nothing.
Errors — none.
GraineCallsHost.handlePush
Call it from a messaging service of your app's own with each push's data, before any JavaScript or Dart runs. Without one, the SDK's own messaging service hands every push to the engine itself.
When the push woke the app and nothing has set up calls yet in this launch, it first applies the call options saved on the device, so the call still rings.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
context | Context | required | The messaging service. |
data | Map<*, *>? | required | The push's data, {String: String}, as received. |
Returns — Boolean — true when it was a call (ringing, refused as busy, or malformed) and the app should do nothing more with it. Example value: true.
Errors — none.
GraineCallsHost.onNewToken
Call it from a messaging service of your app's own with each new token. Without one, the SDK receives each token itself.
The same as GraineCalls.onNewToken. Safe before anything is configured.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
context | Context | required | The messaging service. |
token | String | required | The new Firebase Cloud Messaging token. |
Returns — nothing.
Errors — none.
GraineCallsHost.startCall
Call it from your bridge when the user calls the agent.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
activity | Activity? | required | The activity in front — the call's microphone may only start from there. |
options | Map<*, *>? | required | {reason?, variables?, agent?}: why the user calls (the agent's call_reason), prompt variables for this call, and which configured agent (the default one when left out). |
Returns — String? — the call's ID; null when the calls are not configured, a call is live, there is no activity, or no agent has that name. Example value: "call_6f1c2a9e-7d1b-4c47-9a6e-2f0d8b1c5e44".
Errors — none.
GraineCallsHost.answer
Call it from your bridge's call screen to pick up the ringing call.
Parameters — none.
Returns — nothing.
Errors — none.
GraineCallsHost.decline
Call it from your bridge's call screen to decline the ringing call.
Parameters — none.
Returns — nothing.
Errors — none.
GraineCallsHost.end
Call it from your bridge's hang-up control.
Hangs up — or, while it rings, declines.
Parameters — none.
Returns — nothing.
Errors — none.
GraineCallsHost.setMuted
Call it from your bridge's mute control.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
muted | Boolean | required | true to stop sending the user's voice, false to send it again. |
Returns — nothing.
Errors — none.
GraineCallsHost.setSpeaker
Call it from your bridge's loudspeaker control.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
on | Boolean | required | true for the loudspeaker; false for a headset or the earpiece. |
Returns — nothing.
Errors — none.
GraineCallsHost.setIdentity
Call it from your bridge when the user signs in or out.
The raw traits, as GraineInApp.identify takes them: the SDK masks them and reads the identity itself. null signs out.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
traits | Map<*, *>? | required | Who the user is, by trait name. |
Returns — nothing.
Errors — none.
GraineCallsHost.setMemory
Call it from your bridge when the user changes their memory choice.
GraineInApp.setMemory. Before the SDK's agent exists, the choice is held and applied when it is made.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
enabled | Boolean | required | true to remember this user's conversations, false to stop. |
Returns — nothing.
Errors — none.
GraineCallsHost.installId
Call it for this install's ID — the one ID the native SDK, React Native and Flutter share on a device.
GraineInApp.installId. Safe before anything is configured.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
context | Context | required | Any context of your app. |
Returns — String — a UUID. Example value: "9b2e7c4a-1f3d-4e8a-b6c5-0d7f2a9e4b13".
Errors — none.
GraineCallsHost.setVariables
Call it from your bridge to set the prompt variables your app knows.
The variables become exactly this map: a key the last map set and this one leaves out is removed.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
values | Map<*, *>? | required | Prompt variables by name; null clears them. |
Returns — nothing.
Errors — none.
GraineCallsHost.setScreen
Call it from your bridge whenever what the user can see changes.
The screen as {screen, title?, journey?, fields?, error?, data?, availableActions?} — the fields of GraineScreen. null, or no screen name, clears it.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
screen | Map<*, *>? | required | What the user can see now. |
Returns — nothing.
Errors — none.
GraineCallsHost.setActions
Call it from your bridge to say which actions your JavaScript or Dart answers.
Each request for one of them is sent as an action event to the sink that last listed a non-empty set, and waits for resolveAction — within the SDK's own six seconds. Names no longer listed are unregistered.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
names | List<*>? | required | The action names your code answers. |
sink | Int? | required | Your sink's token, from addSink. |
Returns — nothing.
Errors — none.
GraineCallsHost.resolveAction
Call it from your bridge with the answer to an action request.
The first answer wins; a later one is dropped.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
requestId | String | required | The request's requestId, from the action event. |
result | Map<*, *>? | required | {status: "ok" | "refused" | "error", message?, data?}. |
Returns — nothing.
Errors — none.
GraineCallsHost.addSink
Call it from your bridge to start receiving the calls' events.
Outcomes and rings that came while nothing listened (the last 20) are delivered to the first sink at once; the state never waits — read snapshot(). An action goes only to the sink that last listed actions.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
sink | Sink | required | Receives each event (GraineCallsHost.Sink). |
Returns — Int — the sink's token, for setActions and removeSink. Example value: 1.
Errors — none.
GraineCallsHost.removeSink
Call it from your bridge to stop receiving events.
Any action request it was still holding unanswered is refused, so the agent is not left waiting.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
token | Int | required | The token addSink returned. |
Returns — nothing.
Errors — none.
GraineCallsHost.onRequestPermissionsResult
Call it from the activity that draws the call, when your app draws it (callScreen: "app").
The SDK asks for the microphone from that screen; forwarding the answer lets the call go ahead.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
requestCode | Int | required | As Android passes it. |
grantResults | IntArray | required | As Android passes them. |
Returns — Boolean — true when the request was the SDK's. Example value: true.
Errors — none.
GraineCallsHost.onNewIntent
Call it from the activity that draws the call's onNewIntent, when your app draws it (callScreen: "app").
The ring's Answer opens that activity with the call in its intent; an activity already running (single-top) receives it as a new intent instead, which this makes its own intent.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
activity | Activity | required | The activity that draws the call. |
intent | Intent? | required | The new intent. |
Returns — Boolean — true when it was a call's. Example value: true.
Errors — none.
GraineCallsHost.needsMicrophonePermission
Call it before placing or answering a call, when your app draws the call and cannot hear a permission answer in its own activity.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
context | Context | required | Any context of your app. |
Returns — Boolean — true when your app draws the call (callScreen: "app") and has no microphone permission yet: ask first. Example value: false.
Errors — none.
GraineCallsHost.microphoneDenied
Call it when the user refused the microphone a call needed.
No call is placed or answered, and the snapshot's error says “The microphone permission was denied.”.
Parameters — none.
Returns — nothing.
Errors — none.
GraineCallsHost.Sink
Implement it in your bridge to receive the calls' events.
fun interface Sink
What to expect — Main thread. Events: state (the snapshot), outcome ({callId, outcome, reason?, error?, durationSeconds?, hosted?, agent?}), incoming ({callId, reason, callerName, hosted?, agent?}) and action ({requestId, name, arguments, agent?}).
Its functions: onEvent — each with its parameters, what it returns, its errors and an example.
GraineCallsHost.Sink.onEvent
Implement it to pass each event to your JavaScript or Dart.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
event | String | required | state, outcome, incoming or action. |
payload | Map<String, Any?> | required | What the event carries, in plain values. |
Returns — nothing.
Errors — none: a sink that throws is logged, and the other sinks still hear the event.
Placement
Use it to place a draggable bar of your own the way the voice bar is placed.
object Placement
The bar is anchored to the bottom; the user drags it, and the result is an offset above that anchor, held so the bar never leaves the screen. Units are yours; the SDK passes dp, so an offset kept on one phone means the same on another.
| Property | Type | Meaning |
|---|---|---|
const val DRAG_SLOP = 8.0 | Double | A drag that moved less than this in both directions is a tap — and the tap must still work. |
Its functions: clampOffset, offsetDuringDrag, isTap — each with its parameters, what it returns, its errors and an example.
Placement.Bounds
Pass it to Placement's functions to say how much room the bar has.
data class Bounds(val windowHeight: Double, val baseBottom: Double, val barHeight: Double, val topMargin: Double = 48.0)
| Field | Type | Default | Meaning |
|---|---|---|---|
windowHeight | Double | required | The screen's height. |
baseBottom | Double | required | Where the bar sits with no offset. |
barHeight | Double | required | The bar's height as last measured; 0 before the first layout (64 is assumed). |
topMargin | Double | 48.0 | Room to keep clear at the top — the status bar, a notch. |
Placement.clampOffset
Call it to hold an offset to where the bar can be.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
offset | Double | required | The offset above the anchor. |
b | Bounds | required | The room the bar has. |
Returns — Int — the offset, between 0 and the highest the bar can go; 0 for a value that is not a number. Example value: 120.
Errors — none.
Placement.offsetDuringDrag
Call it while the user drags, for the bar's offset now.
Where the drag started plus how far the finger moved up (dy grows downward), held to the screen.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
startOffset | Double | required | The offset when the drag began. |
dy | Double | required | How far the finger has moved, downward positive. |
b | Bounds | required | The room the bar has. |
Returns — Int — the offset. Example value: 80.
Errors — none.
Placement.isTap
Call it when the finger lifts, to know whether it was a tap rather than a drag.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
dx | Double | required | How far the finger moved across. |
dy | Double | required | How far it moved down. |
Returns — Boolean — true when it moved less than DRAG_SLOP both ways. Example value: true.
Errors — none.
KeyboardLift
Use it to keep a bottom-anchored view of your own above the keyboard.
object KeyboardLift
How far it must rise: the keyboard's height less whatever the window already shrank — right under adjustResize, adjustPan and edge-to-edge alike.
Its functions: liftFor — each with its parameters, what it returns, its errors and an example.
KeyboardLift.liftFor
Call it when the keyboard shows or hides, for how far to raise your view.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
keyboardHeight | Double | required | The keyboard's height (0 when hidden). |
windowShrink | Double | required | How much the window already shrank for it. |
Returns — Int — how far to raise it, never below 0. Example value: 280.
Errors — none.
WidgetLook
Read it for a component's look with every default applied — to draw one in a view of your own.
data class WidgetLook(…)
The preset's defaults, with the component's own choices on top.
| Field | Type | Default | Meaning |
|---|---|---|---|
preset | String | required | gallery, cards, list, spotlight or compact. |
imageSize | String | required | small, medium or large. |
density | String | required | comfortable or compact. |
button | String | required | filled, outline or row. |
facts | Boolean | required | Whether rows show their extra facts. |
columns | Int | required | 1, or 2 for gallery and cards when chosen. |
surface | String | required | Where it goes — screen, sheet or card — auto already decided by the kind. |
radius | Int? | null | Its own corner rounding, when it has one. |
accent | String? | null | Its own accent, when it has one. |
platform | String | required | Which phone's shapes: as chosen; auto is decided when it is drawn. |
select | String | required | one or many. |
maxSelect | Int | required | How many may be picked with many (3 by default). |
PlatformGrammar
Read it for the shapes people on a given phone already know — to draw a component in that phone's style.
data class PlatformGrammar(…)
What a platform's design style changes; the colours stay the brand's. radius is used only for a component pinned to a platform; under auto the design's radius wins.
| Field | Type | Default | Meaning |
|---|---|---|---|
radius | Int | required | A card's corner radius, in dp. |
chipRadius | Int | required | A chip's corner radius (999 for fully round). |
buttonRadius | Int | required | A button's corner radius (999 for fully round). |
pillButtons | Boolean | required | Whether buttons are pills. |
separatorInset | Int | required | How far row separators are inset, in dp. |
grabberWidth | Int | required | A sheet's drag handle width, in dp. |
grabberHeight | Int | required | Its height, in dp. |
titleWeight | Int | required | Titles' weight: 500, 600 or 700. |
padBoost | Int | required | Extra padding the style adds, in dp. |
chipOutlined | Boolean | required | Whether chips are outlined rather than filled. |
SelectCall
Read it for the action one tap on a component runs, with its arguments filled in.
data class SelectCall(val name: String, val args: Map<String, String>)
| Field | Type | Default | Meaning |
|---|---|---|---|
name | String | required | The registered action to run (open_screen for a path). |
args | Map<String, String> | required | Its arguments, filled from the tapped item; a path travels as path. |
Notes for Java
Every entry point works from Java with only the arguments you need. The companion functions are static (@JvmStatic) and the functions with defaults have Java overloads (@JvmOverloads), so GraineInApp.init(application, config), GraineInApp.get(), new GraineConfig(baseUrl, publishableKey), ActionResult.ok("Done.") and new GraineScreen("checkout") read as they do in Kotlin. GraineCallback is a functional interface: pass a lambda (value, error) -> { … }; it is called once, on the main thread, with the value or the error. GraineListener and GraineCallListener have default methods, so a Java listener overrides only the callbacks it wants. Kotlin Maps and Lists are Java's java.util.Map and java.util.List. A companion function without @JvmStatic — FrictionConfig.fromJson — is reached through FrictionConfig.Companion.
Java
What's new in 0.9.0
- Calls over WebRTC, opt-in. Add
ai.graine:inapp-android-rtc— the same version as the SDK — and calls in both directions carry their audio over WebRTC: steadier on a weak network, prepared at the tap or while the phone rings, and carried on over the WebSocket inside the same call when WebRTC cannot set one up.GraineCallsConfig.callTransportchooses;calls.transportandcalls.transportFallbackReasonsay how a call went; the agent's In-app call audio can switch WebRTC off without a release (Adding WebRTC calls). chat.reconnecting: say Reconnecting…, not Offline, while a dropped chat is made again.answerWidgeton each agent: answer a component from a view of your own, on whichever conversation is live — the call or the chat.- What was said, and the captions switch:
voice.captions(GraineCaption),voice.captionsOn,setCaptionsOnandclearCaptions; the voice bar shows its caption only while the switch is on. ChatRows: the rows the SDK's chat window draws — day lines, one row per earlier call, a bubble per message — for a chat of your own.voice.attached, andvoice.viewOpened()/viewClosed()for a voice interface of your own.- Variables over about 1 KB are trimmed to fit, the biggest left off first and
who_calledandcall_reasonlast, instead of all being dropped. scheduleCall's own refusals say the booking sheet's words — “Pick a time at least 5 minutes from now”, “Pick a time within 14 days”, “Booking a call isn't available right now”.- The full list of changes is in the Changelog.
What's new in 0.8.0
- Booking a call, drawn by the SDK.
presentBooking(activity, reason) { call, error -> }shows the booking sheet over an activity — a sheet up from the bottom on a phone, a centred panel on a tablet — in the agent's design: the day, the time (Android's own time picker, its minute wheel in 5-minute steps), the phone's time zone, What's it about? (optional) and Book call.donehears the booked call, ornullwhen the user closed it without booking; a refusal stays in the sheet as one plain line.GraineBookingDialogis the sheet itself. Still no dependencies. bookingAvailable, on each agent: Graine rings this phone, the agent lets users book, and the user is identified. Show your own “Call me” button only then.- The chat window offers “Book a call” under the booked calls in its Upcoming calls card, or on its own when none are booked — never when the design hides the card. A booking refreshes the card.
- Calls hold up on slow networks. Sending never waits for the network: at most 300 ms of the user's audio waits, the oldest dropped first. Hanging up lets go of the microphone at once, and a close the network cannot carry is forced within about a second.
- A dead connection ends the call: nothing at all arriving for 50 seconds ends it as failed — “Nothing arrived for 50 seconds: the connection was lost.”
- A call starts at once on the configuration already kept on the device, and refreshes it behind the call.
- The prompt's variables no longer travel in the address of a call's or a chat's connection (they ride its one-time pass); the direct connection still carries them.
- The full list of changes is in the Changelog.
Every symbol, A to Z
Every public symbol of the Android SDK — 958 in all — by name, each linked to where this page documents it: a type or function first, then its members.
ActionResult—data·error·message·ok·refused·statusActionResult.Status—ERROR·OK·REFUSED·wireAppearance—argb·DEFAULT_ACCENT·DEFAULT_ORIGIN·encodeURIComponent·isDark·luminance·palette·resolve·tintCallPhase—CONNECTING·IDLE·LISTENING·of·RECONNECTING·SPEAKING·THINKINGCallStatusText—connecting·DEFAULT_MISSED·DEFAULT_SCHEDULED·ended·missed·missedText·ringing·scheduled·scheduledTextCallTokens—acceptFill·acceptIcon·avatar·avatarFill·avatarShape·avatarText·background·backgroundTo·buttonActiveFill·buttonActiveIcon·buttonFill·buttonIcon·callbackFill·callbackIcon·captions·controls·declineFill·declineIcon·endFill·endIcon·endShape·ringtone·statusText·text·textMuted·timerChatRows—build·callLabel·callSpoken·DATE·dayLabel·offersNewConversation·offersStarters·TODAY·YESTERDAYChatRows.Row—Day·key·Turn·VoiceChatSource—addChangeListener·failed·messages·online·removeChangeListener·send·shown·typingChatTokens—avatar·background·composerBackground·composerBorder·composerPlaceholder·composerText·headerBackground·headerSubtext·headerText·newConversation·pattern·patternColor·placeholder·showHistory·showTimestamps·showUpcomingCalls·size·subtitle·suggestions·title·upcomingTitleConnectionQuality—classify·EXCELLENT·GOOD·LOST·POOR·UNKNOWNConnectivity—FAILED·LOADING·READY·RETRYINGConnectStage—DIALLING·from·REGISTERING·RETRYING·SWITCHING·wireDesign—androidShadow·BUBBLES·CHROMES·CLASSIC_DARK·CLASSIC_LIGHT·DEFAULT_PRESET·EASINGS·fontFamily·fontFor·GLYPH_ASSET_VERSION·glyphUrl·ICON_STYLES·LAUNCHER_GLYPH·LAUNCHER_SHAPES·LAUNCHER_SIZE·launcherCorner·motionMs·normaliseHexDesignConfig—accent·bubbles·call·chat·chrome·density·font·glass·headingFont·iconStyle·launcherIcon·launcherShape·motion·neutral·preset·roundness·scheme·shadow·toJsonFontPair—body·titleFontScale—body·caption·display·titleFontWeights—body·strong·titleFrictionConfig—cooldownMs·cooldownSeconds·enabled·fromJson·idleAfterMs·maxPerSession·message·openLauncher·requireOutstandingField·screensFrictionConfig.Screen—idleAfterMs·ignore·messageFrictionRules—cooldownMs·cooldownSeconds·enabled·forScreen·idleAfterMs·maxPerSession·message·openLauncher·requireOutstandingField·screensFrictionRules.ForScreen—idleAfterMs·ignore·messageFrictionSignal—cooldownSeconds·data·detail·maxPerSession·name·openLauncher·screenGraineAgentBarView—accent·avatarUrl·chat·draggable·graine·keyboardAware·name·onClose·onPlacementChange·placementOffset·radius·resetPlacement·scheme·widgetViewGraineAnswer—data·summary·widgetGraineBookingDialogGraineCall—callerName·callId·direction·hosted·reason·variablesGraineCall.Direction—INCOMING·OUTGOINGGraineCallActivityGraineCallback—onResultGraineCallListener—onAgentSpeaking·onCallCaption·onCallControls·onCallOutcome·onCallState·onIncomingCallGraineCallOutcome—ANSWERED·busy·callId·DECLINED·durationSeconds·ended·endReason·error·failed·hosted·kind·MISSED·outcome·reason·wireGraineCallOutcome.EndReason—FAILED·LOCAL·REMOTE·wireGraineCallOutcome.Kind—ANSWERED·DECLINED·ENDED·FAILED·MISSED·wireGraineCallPermission—FULL_SCREEN·MICROPHONE·NOTIFICATIONS·of·wireGraineCallPermissions—allGranted·fullScreen·microphone·notificationsGraineCallPermissionsCallback—onResultGraineCallPermissionStatus—BLOCKED·DENIED·GRANTED·isSatisfied·NOT_NEEDED·wireGraineCallReceiverGraineCalls—ACTION_ANSWER·ACTION_CALL_BACK·ACTION_DECLINE·ACTION_HANGUP·ACTION_MISSED·ACTION_SHOW·addListener·agentSpeaking·answer·call·callAgent·callBack·callPermissions·callSetupProblems·caption·config·configure·connectedAt·decline·end·EXTRA_AGENT·EXTRA_CALL_ID·EXTRA_CALLER_NAME·EXTRA_REASON·handlePush·MIC_REQUEST·MISSED_REASON·muted·onNewToken·onRequestPermissionsResult·openCallSettings·removeListener·requestCallPermissions·setMuted·setSpeaker·speaker·startCall·state·transport·transportFallbackReasonGraineCallsConfig—callActivity·callTransport·displayName·ringTimeoutMs·ringtone·smallIconGraineCallServiceGraineCallSetupProblem—code·messageGraineCallsHost—addSink·answer·callPermissions·callSetupProblems·configure·decline·enableVoIPPushes·end·handlePush·installId·microphoneDenied·needsMicrophonePermission·onNewIntent·onNewToken·onRequestPermissionsResult·openCallSettings·removeSink·requestCallPermissions·resolveAction·restore·setActions·setIdentity·setMemory·setMuted·setScreen·setSpeaker·setVariables·snapshot·startCallGraineCallsHost.Sink—onEventGraineCallsHostException—codeGraineCallSource—AGENT·API·APP·DASHBOARD·from·Unknown·wireGraineCallState—ACTIVE·CONNECTING·ENDED·IDLE·RINGINGGraineCallStatus—ANSWERED·CANCELLED·COMPLETED·DECLINED·EXPIRED·FAILED·from·KNOWN·MISSED·NO_DEVICE·RINGING·SCHEDULED·Unknown·wireGraineCallTransport—AUTO·WEBRTC·WEBSOCKETGraineCaption—live·role·textGraineChat—cancelUpcomingCall·clear·close·CLOSE_UNSEEN_MS·connect·connected·connecting·newConversation·open·openedSize·reconnecting·refreshUpcomingCalls·UPCOMING_EVERY_MS·upcomingCallsGraineChatActivity—openGraineChatSize—COMPACT·from·FULL·LARGE·wireGraineChatTurn—at·channel·component·delivery·id·live·proactive·restored·role·textGraineChatTurn.Channel—CHAT·VOICEGraineChatTurn.Delivery—FAILED·SENDING·SENTGraineChatView—constructor·onClose·scheme·source·widgetViewGraineConfig—appVersion·baseUrl·captureTaps·chatSize·debug·endOnBackground·fonts·friction·publishableKey·timing·transport·variablesGraineConfig.Timing—bootMs·endWatchdogMs·hardCapMs·retryDelaysMs·slowConnectMs·stallMsGraineError—code·message·retryAfterSecondsGraineErrorCode—DEVICE_LIMIT·from·IDENTITY_REQUIRED·INVALID_KEY·INVALID_REQUEST·KNOWN·NETWORK·NOT_FOUND·ORIGIN_REFUSED·OUTSIDE_WINDOW·RATE_LIMITED·SCHEDULING_OFF·TOO_FAR·TOO_MANY_PENDING·TOO_SOON·UNAVAILABLE·Unknown·wireGraineField—error·label·name·optional·status·valueGraineField.Status—EMPTY·FILLED·INVALID·LOCKED·PENDING·wireGraineInApp—addListener·all·answerWidget·bookingAvailable·calls·cancelScheduledCall·chat·checkActions·clearPendingWidget·clearScreen·config·configureFriction·currentScreen·currentVariables·DEFAULT·design·find·forgetMemory·frictionRules·get·identify·init·installId·memories·memoryEnabled·name·noteInteraction·pendingWidget·presentBooking·previewDesign·registerAction·registerDeferredAction·removeField·removeListener·reportEvent·scheduleCall·scheduledCalls·schedulingRules·session·setMemory·setScreen·setVariables·tap·themeTokens·track·updateField·voiceGraineLauncherView—accent·graine·label·schemeGraineListener—onActionCompleted·onActionRequested·onCallState·onCaption·onChatMessage·onChatState·onConnectivity·onConnectProgress·onEnded·onError·onFriction·onMicDenied·onNetwork·onQuality·onReconnecting·onSessionReady·onTransport·onWidget·onWidgetPendingGraineLoaderView—configureGraineMemory—createdAt·id·text·updatedAtGraineScheduledCall—at·canCancel·id·isUpcoming·reason·source·statusGraineSchedulingRules—callName·earliest·hostedRinging·latest·maxDaysAhead·maxPendingPerUser·minLeadMinutes·parse·platforms·RING_SECONDS_MAX·RING_SECONDS_MIN·ringsAndroid·ringTimeoutSeconds·usersCanBookGraineScreen—availableActions·data·error·fields·journey·screen·titleGraineScreen.Journey—name·next·of·stepGraineVersion—NAMEGraineVoice—answerWidget·attach·attached·callFailed·captions·captionsOn·clearCaptions·connected·connecting·connectivity·connectStage·detach·end·error·MIC_REQUEST·muted·online·onRequestPermissionsResult·phase·quality·ready·reconnecting·reportEvent·retry·setCaptionsOn·setMuted·slowConnect·start·viewClosed·viewOpenedGraineWidget—adopt·data·fallbackText·id·kind·onSelect·styleGraineWidgetView—accent·answered·closedWidget·dismiss·graine·isLoading·onChange·present·presentPending·radius·reopen·scheme·theme·widgetGraineWidgetView.Theme—border·muted·onAccent·surface·textKeyboardLift—liftForLauncherGlyph—id·path·strokeLoaderLook—color·kind·periodMs·px·skeleton·textLoaders—DEFAULT_KIND·DEFAULT_TEXT·KINDS·MOMENTS·normalise·resolve·SIZES·SPEEDS·TEXT_MAXLoaderStyle—color·kind·size·skeleton·speed·textLoaderText—component·connecting·forMoment·reconnecting·thinkingLogo—box·CLASSIC_HAIRLINE·ON_WHITE·plate·PLATE_WHITELogo.Plate—fill·outlineLogo.Stroke—color·widthLogoPlate—ACCENT·from·WHITE·wireMasking—addRule·clearCustomRules·deep·maskNativeAppearance—accent·heading·logoPlate·logoUrl·radiusPlacement—clampOffset·DRAG_SLOP·isTap·offsetDuringDragPlacement.Bounds—barHeight·baseBottom·topMargin·windowHeightPlatformGrammar—buttonRadius·chipOutlined·chipRadius·grabberHeight·grabberWidth·padBoost·pillButtons·radius·separatorInset·titleWeightResolvedDesign—config·dark·light·parse·pick·toJsonRole—AGENT·CUSTOMERSelectCall—args·nameSessionConfig—accent·agentId·appearance·declaredActions·design·friction·identity·memoryEnabled·pageVersion·preferredTransport·schedulingRules·variables·webcallAgentId·wsHostSessionIdentity—devicesPerUser·fieldThemeColors—accent·accentSoft·agentBubble·agentBubbleText·background·border·danger·onAccent·scrim·success·surface·surfaceRaised·text·textMuted·userBubble·userBubbleText·warningThemeElevation—card·floating·hardOffsetThemeFont—body·heading·letterSpacing·lineHeight·scale·weightsThemeGlass—blur·surfaceOpacityThemeIcons—launcher·stroke·styleThemeMotion—bounce·durationMs·easingThemeOffset—x·yThemeRadius—lg·md·sm·xlThemeShadow—blur·color·opacity·spread·yThemeShape—borderWidth·launcher·radiusThemeSpacing—cardPadding·gap·unitThemeTokens—bubbles·call·chat·chrome·colors·elevation·font·glass·icons·isDark·motion·preset·scheme·shape·spacing·toJson·vTransport—from·WEBRTC·WEBSOCKET·wireWidgetAnswer—data·summaryWidgetLook—accent·button·columns·density·facts·imageSize·maxSelect·platform·preset·radius·select·surfaceWidgetPalette—border·mode·muted·onAccent·surface·textWidgetSelectAction—action·args·isEmpty·path·sayWidgetStyle—accent·button·columns·density·facts·imageSize·maxSelect·platform·preset·radius·select·surface

