Android IntegrationANDROID SDK
HeheBid Android SDK Integration Guide
Section titled “HeheBid Android SDK Integration Guide”Overview & System Requirements
Section titled “Overview & System Requirements”| Environment | Minimum Requirement |
|---|---|
| Android Studio | Android Studio Giraffe (2023.1) or later |
| Gradle / AGP | Gradle 9.4.1 / Android Gradle Plugin 9.2.x |
| JDK | 17 (minimum for AGP 9.2.x; SDK source compatibility is Java 11) |
| compileSdkVersion | 36 (Android API 36 / 36.1 platform required) |
| Android SDK Build Tools | 36.0.0 |
| minSdkVersion | 24 (Android 7.0) |
| targetSdkVersion | 36 recommended; not enforced at build time—set according to your app’s release policy |
| Language | Kotlin or Java (examples use Kotlin) |
Prerequisites (App Key & Placement ID)
Section titled “Prerequisites (App Key & Placement ID)”Before integrating the SDK, you need a HeheBid Platform developer account. Register your app, create Placement IDs, and obtain the required credentials: App Key and Placement ID.
App Key: A unique identifier assigned to your app. You can obtain it after creating the app in the developer console.
Placement ID: A unique identifier for each ad slot. You can create multiple Placement IDs. View all Placement IDs under Traffic Management → Ad Slot Management.
Obtaining the SDK
Section titled “Obtaining the SDK”Public distribution is GitHub hehesdk/android-release + JitPack (prebuilt AARs).
Use a published git tag for the version (latest public: 0.2.6).
Option 1: Remote Maven Dependency (Recommended)
Section titled “Option 1: Remote Maven Dependency (Recommended)”If your project uses Gradle dependencyResolutionManagement, configure repositories in settings.gradle.kts:
dependencyResolutionManagement { repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) repositories { google() mavenCentral() maven("https://jitpack.io") }}Add the remote dependency in your App module’s build.gradle.kts:
dependencies { // Module artifact under the android-release repo — one colon before the version implementation("com.github.hehesdk.android-release:hehesdk:0.2.6")
// Media3 is not declared in the remote POM. Add it explicitly if you want the SDK // to prefer Media3 for video ads. Without it, the SDK falls back to system MediaPlayer. implementation("androidx.media3:media3-exoplayer:1.10.1")}Option 2: Local AAR Dependency
Section titled “Option 2: Local AAR Dependency”Copy the release artifact hehesdk-release.aar into your app’s app/libs/ directory (provided by Hehe, or built via :hehesdk:assembleRelease).
In your App module’s build.gradle.kts:
dependencies { implementation(files("libs/hehesdk-release.aar")) // Flat AARs do not transitively resolve dependencies — declare them explicitly implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3") implementation("com.google.android.gms:play-services-ads-identifier:18.0.1") implementation("androidx.appcompat:appcompat:1.7.1") implementation("androidx.core:core-ktx:1.18.0") implementation("com.google.android.material:material:1.13.0")
// Optional: add when the host needs the Media3 playback path implementation("androidx.media3:media3-exoplayer:1.10.1")}AndroidManifest Configuration
Section titled “AndroidManifest Configuration”As an Android Library, the SDK automatically declares the following permissions in the merged Manifest. Integrators generally do not need to add them again (if stripped by tooling, ensure they are retained):
| Permission | Purpose |
|---|---|
android.permission.INTERNET |
Network requests |
android.permission.ACCESS_NETWORK_STATE |
Network state |
android.permission.READ_PHONE_STATE |
Device identifier (collected per protocol) |
android.permission.READ_BASIC_PHONE_STATE |
Device identifier (API 33+) |
com.google.android.gms.permission.AD_ID |
Google Advertising ID (GAID) |
When integrating via a mediation adapter (AdMob / MAX / TopOn / TradPlus), also set the App Key in Manifest (the adapter auto-inits Hehe — do not call AdSDK.init again):
<meta-data android:name="com.hehessp.sdk.APP_KEY" android:value="YOUR_HEHE_APP_KEY" />For direct SDK APIs on this page (BannerAd / InterstitialAd, etc.), pass the App Key via AdConfig instead.
Initializing the SDK
Section titled “Initializing the SDK”After SDK integration, you must call AdSDK.init before loading any ads. We recommend calling it in Application.onCreate or in the first Activity.onCreate that uses ads.
Sample Code
Section titled “Sample Code”import com.hehessp.sdk.AdSDKimport com.hehessp.sdk.InitCallbackimport com.hehessp.sdk.core.config.AdConfig
val config = AdConfig(appKey = "YOUR_APP_KEY")
AdSDK.init(applicationContext, config, object : InitCallback { override fun onSuccess() { // Server init request succeeded; endpoint, sdk_enabled, etc. are applied internally // Call loadAd after this, or when getServerInitState() == SUCCESS }
override fun onFailure(code: Int, message: String) { // Init failed: see Error Codes for code; message is the error constant name (e.g. NETWORK_TIMEOUT) }})Query SDK version
Section titled “Query SDK version”val info = AdSDK.getVersionInfo()val name = info.sdkName // e.g. "hehessp"val version = info.sdkVersion // aligned with the release tag, e.g. "0.2.6"Ad Unit: Banner
Section titled “Ad Unit: Banner”Before You Start
Section titled “Before You Start”- Create a Banner ad slot in the console and obtain the
placementId. - Complete
AdSDK.init. - Reserve an ad container in your layout (e.g.
FrameLayout).
Step 1: Create a BannerAd Instance with Callbacks
Section titled “Step 1: Create a BannerAd Instance with Callbacks”private var bannerAd: BannerAd? = null
// In Activityval container = findViewById<FrameLayout>(R.id.banner_root)bannerAd = BannerAd( activity = this, container = container, config = BannerAdConfig( placementId = "YOUR_BANNER_PLACEMENT_ID" ), listener = object : BannerAdListener { override fun onAdLoaded() { // Ad loaded and automatically attached to the container }
override fun onAdLoadFailed(code: Int, message: String) { // Load failed }
override fun onAdDisplayed(ad: Ad) { // Ad displayed and impression recorded }
override fun onAdDisplayFailed(code: Int, message: String) { // Display failed }
override fun onAdClicked(ad: Ad) { // Click }
override fun onAdHidden(ad: Ad) { // Closed (if creative supports close, or destroy triggers session release) } })Step 2: Load and Display
Section titled “Step 2: Load and Display”bannerAd?.loadAd()Banner size rules:
- The SDK prefers
w/h(in dp) from initplacementsthat match the ad responseplacementId. - After converting
w/hto px, the SDK sets a fixed width/height on the ad View. With aFrameLayoutcontainer, the ad View is horizontally centered and is not scaled to the host container width. - If init does not provide a valid size, the SDK falls back to
BannerAdConfig.bannerSize. Default isSMALL=320x50; also supportsMEDIUM=320x100andLARGE=300x200. bannerSizeis also used for the request’scontainer_width/container_height(primarily for creative selection). Integrators usually do not need to pass it explicitly.
Layout example (use FrameLayout for banner_root; width fills the available area, height wraps the fixed-size ad View mounted by the SDK):
<FrameLayout android:id="@+id/banner_root" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="bottom" />Ensure the page can accommodate the dp width/height configured for that Banner placement in the console. The current SDK does not automatically shrink an ad View that exceeds the container.
Step 3: Destroy
Section titled “Step 3: Destroy”Release resources in Activity.onDestroy to avoid memory leaks:
override fun onDestroy() { bannerAd?.destroy() bannerAd = null super.onDestroy()}Ad Unit: Interstitial
Section titled “Ad Unit: Interstitial”Before You Start
Section titled “Before You Start”- Create an Interstitial ad slot in the console and obtain the
placementId. - Complete
AdSDK.init.
Step 1: Load with Callbacks
Section titled “Step 1: Load with Callbacks”private var interstitialAd: InterstitialAd? = null
InterstitialAd.load( placementId = "YOUR_INTERSTITIAL_PLACEMENT_ID", listener = object : InterstitialAdListener { override fun onAdLoaded(ad: InterstitialAd) { // Ad loaded; save the instance and call show(activity) at the right time interstitialAd = ad }
override fun onAdLoadFailed(code: Int, message: String) { // Load failed (including video/playable prefetch failure) }
override fun onAdDisplayed(ad: Ad) { // Ad displayed and impression recorded }
override fun onAdDisplayFailed(code: Int, message: String) { // Display failed }
override fun onAdClicked(ad: Ad) { // Click }
override fun onAdHidden(ad: Ad) { // User closed interstitial; finish Activity or preload the next ad here } })Java integration example:
InterstitialAd.load("YOUR_INTERSTITIAL_PLACEMENT_ID", new InterstitialAdListener() { @Override public void onAdLoaded(@NonNull InterstitialAd ad) { interstitialAd = ad; } // Override other callbacks as needed});Step 2: Show the Ad
Section titled “Step 2: Show the Ad”After receiving the instance in onAdLoaded, or when the user triggers display:
val ok = interstitialAd?.show(this) ?: falseif (!ok) { // Not ready, destroyed, expired, etc.; failure reason is also returned via onAdDisplayFailed}You can also check interstitialAd?.isReady() before showing.
After a successful show(activity), the current ad is consumed and isReady() returns false. To show another interstitial, call InterstitialAd.load(...) again. Prefer passing the current page Activity. For SDK-managed full-screen interstitials, show returns false if the Activity is already destroyed. If the Activity is finishing but not yet destroyed, the SDK still attempts to launch the full-screen host; launch failures are reported via onAdDisplayFailed.
Step 3: Destroy
Section titled “Step 3: Destroy”The SDK automatically calls destroy() after terminal callbacks such as onAdLoadFailed, onAdDisplayFailed, and onAdHidden. No manual destroy is needed after those states.
Manually call destroy() as a fallback in these cases:
- You received an instance in
onAdLoadedbut the user leaves the page beforeshow - The host
Activity.onDestroyruns while still holding anInterstitialAdreference—handle carefully to avoid memory leaks
override fun onDestroy() { interstitialAd?.destroy() interstitialAd = null super.onDestroy()}Note:
destroy()does not forcibly close an open full-screen interstitial Activity. Typically, finish the current page afteronAdHiddenand callInterstitialAd.load(...)again to load the next ad.
Ad Unit: Native
Section titled “Ad Unit: Native”Before You Start
Section titled “Before You Start”- Create a Native ad slot in the console and obtain the
placementId. - Complete
AdSDK.init. - Use
HeHeNativeContaineras the native ad root container in your layout, and declare required widgets with the SDK’s fixed ids.
Step 1: Load with Callbacks
Section titled “Step 1: Load with Callbacks”private var nativeAd: NativeAd? = null
NativeAd.load( placementId = "YOUR_NATIVE_PLACEMENT_ID", listener = object : NativeAdListener { override fun onAdLoaded(ad: NativeAd) { // Ad loaded; save the instance and call show(activity, container) at the right time nativeAd = ad }
override fun onAdLoadFailed(code: Int, message: String) { // Load failed (including icon/image asset prefetch failure) }
override fun onAdDisplayed(ad: Ad) { // Container visible and impression recorded }
override fun onAdDisplayFailed(code: Int, message: String) { // Display failed (e.g. required View missing, incomplete native_layout) }
override fun onAdClicked(ad: Ad) { // Full-card click } })Step 2: Layout Template and Display
Section titled “Step 2: Layout Template and Display”Native ads use a fixed XML template: you customize the layout and styling inside HeHeNativeContainer, while the SDK handles request, asset prefetch, View binding, impression, click, and landing-page navigation.
Layout example (root must be HeHeNativeContainer; child Views use SDK fixed ids):
<com.hehessp.sdk.ad.nativead.HeHeNativeContainer android:id="@+id/native_root" android:layout_width="match_parent" android:layout_height="wrap_content">
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical">
<TextView android:id="@+id/hehe_native_title" android:layout_width="wrap_content" android:layout_height="wrap_content" />
<ImageView android:id="@+id/hehe_native_icon" android:layout_width="100dp" android:layout_height="100dp" />
<TextView android:id="@+id/hehe_native_desc" android:layout_width="wrap_content" android:layout_height="wrap_content" />
<ImageView android:id="@+id/hehe_native_image" android:layout_width="match_parent" android:layout_height="wrap_content" android:adjustViewBounds="true" />
<TextView android:id="@+id/hehe_native_cta" android:layout_width="wrap_content" android:layout_height="wrap_content" />
<RatingBar android:id="@+id/hehe_native_rating" android:layout_width="wrap_content" android:layout_height="wrap_content" style="?android:attr/ratingBarStyleSmall" />
</LinearLayout>
</com.hehessp.sdk.ad.nativead.HeHeNativeContainer>View id contract (ids are defined in the SDK’s res/values/ids.xml; your layout must use the same names):
| View id | Type | Required |
|---|---|---|
hehe_native_title |
TextView | Required |
hehe_native_icon |
ImageView | Required |
hehe_native_cta |
TextView / Button | Required |
hehe_native_desc |
TextView | Optional |
hehe_native_image |
ImageView | Optional |
hehe_native_rating |
TextView / RatingBar | Optional |
- If a required View is missing or has the wrong type,
show()returnsfalseand callbacksonAdDisplayFailed(code=1210, message="RENDER_FAIL"). - Missing optional Views do not cause errors; the SDK hides a View when the corresponding asset is empty.
In onAdLoaded or when the user triggers display, call:
val container = findViewById<HeHeNativeContainer>(R.id.native_root)val ok = nativeAd?.show(this, container) ?: falseif (!ok) { // Not ready, destroyed, expired, invalid Activity, etc.; failure reason is also returned via onAdDisplayFailed}You can also check nativeAd?.isReady() before showing.
Display notes:
show(activity, container)must receive a valid, non-destroyed Activity for landing-page navigation. Do not useApplicationContextorFLAG_ACTIVITY_NEW_TASKfor navigation.- Orientation is resolved automatically from the SDK’s
appContextat init; you do not need to pass it. - The SDK prefers the legacy
nativefields and is compatible with servernative_layout. title, icon.url, and cta_text are required assets. For older versions, missingcta_textstill displays"OK". If image.url is present, it must be available at load time, otherwiseonAdLoadFailedis called. - Impression triggers
onAdDisplayedonce after the container is attached, visible, andgetGlobalVisibleRect()has a visible area. - The full-card
HeHeNativeContaineris clickable; clicks triggeronAdClickedand landing-page navigation. - The SDK automatically mounts an ad disclosure badge at the bottom-left of the container.
- Unlike interstitial, the same
NativeAdinstance can callshow()multiple times (show does not consume the creative); impression is reported only once.
Mediation Platforms & Custom Rendering API
Section titled “Mediation Platforms & Custom Rendering API”The legacy NativeAd.load(placementId, listener) behavior is unchanged. When you need independent assets, use the request-options overload:
NativeAd.load( placementId = "YOUR_NATIVE_PLACEMENT_ID", options = NativeAdRequestOptions( shouldReturnUrlsForImageAssets = false ), listener = object : NativeAdListener { override fun onAdLoaded(ad: NativeAd) { val assets = ad.getAssets() ?: return val adChoicesView = ad.createAdChoicesView(this@MainActivity) ?: return
// Bind assets with your external View system; notify the SDK only after real events. bindAssets(assets, adChoicesView) } })NativeAdAssets fields:
| Field | Description |
|---|---|
headline |
Required title |
body |
Optional description; null when blank |
callToAction |
CTA; falls back to legacy "OK" when missing |
icon |
Required image asset |
mainImage |
Optional main image; currently at most one |
starRating |
Non-null only when finite and in (0, 5]; current 0 is treated as not provided |
advertiser/store/price |
Not confirmed in the current protocol; returns null (no guessing) |
Image assets include material uri, optional bitmap, width/height, and scale=1.0:
- Default
shouldReturnUrlsForImageAssets=falsereturns a prefetched Bitmap; callers must not mutate or recycle it. - When set to true, only URI is returned and Bitmap is null; the current path still reuses the legacy prefetch pipeline, and you must ensure CDN URL validity covers the ad lifetime.
shouldRequestMultipleImagesandpreferredMediaAspectRatioare not supported by the backend today; they are safely ignored (no image duplication or cropping).
After your external View system detects events, call on the main thread:
val impressionAccepted = nativeAd?.recordImpression() == trueval clickAccepted = nativeAd?.performClick(currentActivity) == true- Impression shares the same one-shot gate as legacy
show(); the first valid call returns true. - Click reuses the SDK’s original tracking and landing flow; invalid, finishing, or destroyed Activity returns false, and the SDK does not retain the Activity.
createAdChoicesView(context)returns a new unmounted View each time; returns null when state is invalid, not on the main thread, or creation fails.- After
destroy(), assets, impression, click, and disclosure View creation all become unavailable; previously obtained Bitmaps must not be used further.
Common show failure codes:
| code | Scenario |
|---|---|
| 1200 | Ad expired |
| 1201 | Ad instance already destroyed |
| 1202 | show called before a successful load |
| 1203 | Host Activity is finishing or destroyed |
| 1210 | Render failure (missing required View, missing native_layout, asset binding failure, etc.) |
Common load failure codes:
| code | Scenario |
|---|---|
| 1113 | Native required fields, asset URI, or prefetched image invalid |
Step 3: Destroy
Section titled “Step 3: Destroy”After onAdLoadFailed, the SDK automatically calls destroy(). onAdDisplayFailed does not auto-destroy; release as needed or call NativeAd.load(...) again.
Manually call destroy() as a fallback in these cases:
- You received an instance in
onAdLoadedbut the user leaves the page and no longer needs display - The host
Activity.onDestroyruns while still holding aNativeAdreference—avoid memory leaks
override fun onDestroy() { nativeAd?.destroy() nativeAd = null super.onDestroy()}Note: From
NativeAd.load()untildestroy(), the SDK holds the listener. Prefer a static inner class or a standalone class forNativeAdListener, and access the Activity viaWeakReference, to avoid anonymous inner classes capturing the Activity and causing leaks.
Ad Unit: Rewarded Video
Section titled “Ad Unit: Rewarded Video”Before You Start
Section titled “Before You Start”- Create a Rewarded Video ad slot in the console (
placement_type=3) and obtain theplacementId. - Complete
AdSDK.init.
Step 1: Load with Callbacks
Section titled “Step 1: Load with Callbacks”private var rewardAd: RewardAd? = null
RewardAd.load( placementId = "YOUR_REWARDED_PLACEMENT_ID", listener = object : RewardAdListener { override fun onAdLoaded(ad: RewardAd) { // Ad loaded; save the instance and call show(activity) at the right time rewardAd = ad }
override fun onAdLoadFailed(code: Int, message: String) { // Load failed (including video asset prefetch failure) }
override fun onAdDisplayed(ad: Ad) { // Ad displayed and impression recorded }
override fun onAdDisplayFailed(code: Int, message: String) { // Display failed }
override fun onAdClicked(ad: Ad) { // Click }
override fun onAdHidden(ad: Ad) { // User closed the ad; finish Activity or preload the next ad here }
override fun onRewardEarned(ad: Ad, reward: RewardItem) { // Reward earned: reward.name / reward.amount } })Step 2: Show the Ad
Section titled “Step 2: Show the Ad”After receiving the instance in onAdLoaded, or when the user triggers display:
val ok = rewardAd?.show(this) ?: falseif (!ok) { // Not ready, destroyed, expired, etc.; failure reason is also returned via onAdDisplayFailed}You can also check rewardAd?.isReady() before showing. After a successful show(activity), the current ad is consumed and isReady() returns false. To show another rewarded video, call RewardAd.load(...) again. Prefer passing the current page Activity. For SDK-managed full-screen rewarded ads, show returns false if the Activity is already destroyed. If the Activity is finishing but not yet destroyed, the SDK still attempts to launch the full-screen host; launch failures are reported via onAdDisplayFailed.
Step 3: Destroy
Section titled “Step 3: Destroy”The SDK automatically calls destroy() after terminal callbacks such as onAdLoadFailed, onAdDisplayFailed, and onAdHidden. No manual destroy is needed after those states. onRewardEarned does not trigger auto-destroy; the ad is destroyed by the SDK after close via onAdHidden.
Manually call destroy() as a fallback in these cases:
- You received an instance in
onAdLoadedbut the user leaves the page beforeshow - The host
Activity.onDestroyruns while still holding aRewardAdreference—handle carefully to avoid memory leaks
override fun onDestroy() { rewardAd?.destroy() rewardAd = null super.onDestroy()}Reward and close-button notes:
- The server may send
reward.reward_time(seconds): after video playback starts, the SDK triggers the reward flow when that offset is reached.- When
reward.verifyis non-empty, the SDK sends an unsigned GET to that URL and treatscode==0in the unified response body as success before callingonRewardEarned. Whenverifyis empty, verification is skipped and the callback fires directly. Verification failure / network errors do not callback.- The close button is delayed by
render_control.close_offset_secs. Closing beforereward_timeshows an “abandon reward” confirmation; after the offset, close is immediate. The dialog only checks whetherreward_timehas elapsed—verify success/failure only affects whetheronRewardEarnedis called.- From
RewardAd.load()untildestroy(), the SDK holds the listener. Prefer a static inner class orWeakReferenceto the Activity to avoid anonymous inner classes capturing the Activity and causing leaks.
Error Codes
Section titled “Error Codes”All public failure callbacks currently return the error constant name string as message. Prefer code for business logic and message for logging/troubleshooting.
Initialization & Network
Section titled “Initialization & Network”| code | message | Scenario |
|---|---|---|
1000 |
UNKNOWN |
Unknown error |
1001 |
APP_KEY_EMPTY |
AdConfig.appKey is empty |
1002 |
INVALID_PARAMS |
Other AdConfig parameters are invalid |
1003 |
ALREADY_INITIALIZED |
Duplicate AdSDK.init call |
1010 |
NETWORK_UNAVAILABLE |
No network, DNS failure, or host unreachable |
1011 |
NETWORK_TIMEOUT |
Network request timed out |
1012 |
NETWORK_ERROR |
Other network I/O failure |
1020 |
RESPONSE_PARSE_ERROR |
SSP response JSON or model parse failure |
Ad Load
Section titled “Ad Load”| code | message | Scenario |
|---|---|---|
1100 |
SDK_NOT_INITIALIZED |
Ad requested before initialization |
1101 |
SDK_DISABLED |
Server init returned sdk_enabled=false |
1102 |
PLACEMENT_ID_EMPTY |
Placement ID is empty |
1103 |
API_CLIENT_UNAVAILABLE |
Internal ad request client unavailable |
1110 |
MATERIAL_TIMEOUT |
Required asset prefetch timed out |
1111 |
MATERIAL_DOWNLOAD_FAIL |
Required asset download failed |
1112 |
MATERIAL_EXTRACT_FAIL |
Playable zip extract or entry HTML locate failed |
1113 |
NATIVE_ASSET_INVALID |
Native required fields, asset URI, or prefetched image invalid |
Ad Display
Section titled “Ad Display”| code | message | Scenario |
|---|---|---|
1200 |
AD_EXPIRED |
Ad expired |
1201 |
AD_DESTROYED |
Ad instance already destroy()ed |
1202 |
AD_NOT_READY |
Display called before a successful load |
1203 |
HOST_ACTIVITY_DESTROYED |
Host Activity cannot be used for this display; interstitial/rewarded only reject already destroyed; Native and other host-View entry points also reject finishing |
1210 |
RENDER_FAIL |
Render failure or display host launch failure |
SSP Business Errors
Section titled “SSP Business Errors”| code | message | Scenario |
|---|---|---|
3000 |
SSP_ERROR |
Generic server error; unknown 3xxx business codes are normalized to this |
3001 |
NO_AD_FILL |
No ad fill |
3002 |
INVALID_REQUEST |
Request body parse error |
3003 |
INVALID_APP_KEY |
App delisted or does not exist |
3004 |
INVALID_PLACEMENT |
Placement closed or does not exist |
3005 |
INVALID_SIGNATURE |
Signature failure |
3006 |
FREQUENCY_LIMITED |
Frequency capped |
3007 |
REQUEST_EXPIRED |
X-Timestamp differs too much from server time |
3008 |
REPLAY_ATTACK_DETECTED |
Server detected duplicate X-Nonce |
3009 |
RATE_LIMIT_EXCEEDED |
Server rate limited |
3010 |
INVALID_PLACEMENT_TYPE |
Placement type not supported by this SDK version |
5000 |
SERVER_INTERNAL_ERROR |
SSP ad server internal error |
Unsupported Ad Types
Section titled “Unsupported Ad Types”The current version does not provide public APIs for the following ad types. Do not call non-existent classes based on other SDK documentation:
| Type | Status |
|---|---|
| Splash | Planned |