Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions docs/en/apis/sparkling-sdk-android.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,12 +86,43 @@ Configuration object passed to both container types.
|----------|-------------|
| `scheme` | The `hybrid://...` URL to load. |
| `sparklingUIProvider` | Implements `SparklingUIProvider` for custom loading/error/toolbar views. |
| `screenOrientationPolicy` | Optional typed orientation policy for a full-page `SparklingActivity`. |
| `threadStrategy` | Optional per-container `SparklingThreadStrategy`. Overrides the global default. |
| `hybridSchemeParam` | Parsed scheme parameters (auto-populated from `scheme`). |
| `lynxViewport` | Optional `SparklingLynxViewport(widthPx, heightPx)` fixed viewport in physical pixels. Programmatic configuration overrides parsed scheme dimensions. |
| `containerId` | Unique container identifier (auto-generated). |
| `resourceFetcherConfig` | Optional per-page typed resource fetchers. Overrides the global factory. |

## Screen orientation

`SparklingScreenOrientationPolicy` provides the Java-friendly `SYSTEM`,
`PORTRAIT`, and `LANDSCAPE` values for full-page containers:

```java
SparklingContext sparklingContext = new SparklingContext();
sparklingContext.setScreenOrientationPolicy(
SparklingScreenOrientationPolicy.LANDSCAPE);
```

An optional application-wide default can be set with
`SparklingHybridConfig.Builder.setDefaultScreenOrientationPolicy(...)`.
Resolution order is:

1. `SparklingContext.screenOrientationPolicy`, including an explicit `SYSTEM`;
2. the canonical scheme `screen_orientation` value;
3. the global default;
4. Android's existing system/default behavior when all values are unset.

The canonical `portrait` and `landscape` values map to the corresponding typed
policies. Unknown canonical values preserve the existing `SYSTEM` behavior
instead of falling through to the global default. `SparklingActivity` applies
the resolved policy through Android's public `requestedOrientation` API before
creating its content.

The policy intentionally does not rotate an Activity that hosts an embedded
`SparklingView`. An embedded view does not own its host Activity; the host must
apply any desired orientation policy itself.

## Typed resource fetchers

Use `SparklingResourceFetcherConfig` to provide Lynx generic, media, and
Expand Down
29 changes: 29 additions & 0 deletions docs/zh/apis/sparkling-sdk-android.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,11 +79,40 @@ Sparkling 创建的 `LynxView` 也必须使用完全相同的 density 构造**
|------|------|
| `scheme` | 要加载的 `hybrid://...` URL。 |
| `sparklingUIProvider` | 实现 `SparklingUIProvider` 以自定义加载/错误/工具栏视图。 |
| `screenOrientationPolicy` | 全页 `SparklingActivity` 可选的类型安全方向策略。 |
| `threadStrategy` | 可选的容器级 `SparklingThreadStrategy`,优先于全局默认值。 |
| `hybridSchemeParam` | 解析后的 scheme 参数(从 `scheme` 自动填充)。 |
| `lynxViewport` | 可选的 `SparklingLynxViewport(widthPx, heightPx)`,以物理像素指定固定 viewport。程序化配置会覆盖 scheme 中解析的尺寸。 |
| `containerId` | 唯一的容器标识符(自动生成)。 |

## 屏幕方向

`SparklingScreenOrientationPolicy` 为全页容器提供 Java 友好的 `SYSTEM`、
`PORTRAIT` 和 `LANDSCAPE`:

```java
SparklingContext sparklingContext = new SparklingContext();
sparklingContext.setScreenOrientationPolicy(
SparklingScreenOrientationPolicy.LANDSCAPE);
```

宿主也可以通过
`SparklingHybridConfig.Builder.setDefaultScreenOrientationPolicy(...)`
设置可选的应用级默认值。解析优先级为:

1. `SparklingContext.screenOrientationPolicy`,包括显式设置的 `SYSTEM`;
2. canonical scheme 的 `screen_orientation`;
3. 全局默认值;
4. 全部未设置时沿用 Android 当前的系统/默认行为。

canonical scheme 中的 `portrait` 和 `landscape` 会映射到对应的类型安全策略。
未知 canonical 值继续保持现有的 `SYSTEM` 行为,不会回退到全局默认值。
`SparklingActivity` 在创建内容前通过 Android 公开的 `requestedOrientation`
API 应用最终策略。

该策略不会旋转承载嵌入式 `SparklingView` 的 Activity。嵌入式 View
不拥有宿主 Activity;需要固定方向时,应由宿主自行应用方向策略。

高级宿主如果已经使用 `LynxKitInitParams`,也可以设置其 `lynxViewport` 属性。优先级依次为:
init params、`SparklingContext.lynxViewport`、canonical scheme 的 `width` 和 `height`。
三种入口都只接受完整的正数宽高组合。
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// Copyright (c) 2026 TikTok Pte. Ltd.
// Licensed under the Apache License Version 2.0 that can be found in the
// LICENSE file in the root directory of this source tree.
package com.tiktok.sparkling

import android.content.Intent
import android.content.pm.ActivityInfo
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import org.junit.Assert.assertEquals
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(AndroidJUnit4::class)
class SparklingScreenOrientationInstrumentedTest {
@Test
fun fullPageActivityReceivesPerContainerOrientationPolicy() {
val instrumentation = InstrumentationRegistry.getInstrumentation()
val context =
SparklingContext().apply {
containerId = "instrumented-orientation-landscape"
screenOrientationPolicy = SparklingScreenOrientationPolicy.LANDSCAPE
}
SparklingContextTransferStation.saveSparklingContext(context)
val intent =
Intent(instrumentation.targetContext, SparklingActivity::class.java).apply {
putExtra(Sparkling.SPARKLING_CONTEXT_CONTAINER_ID, context.containerId)
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
}

val activity = instrumentation.startActivitySync(intent) as SparklingActivity

try {
instrumentation.waitForIdleSync()
assertEquals(
ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE,
activity.requestedOrientation,
)
} finally {
instrumentation.runOnMainSync {
activity.finish()
}
SparklingContextTransferStation.releaseSparklingContext(context.containerId)
assertEquals(
null,
SparklingContextTransferStation.getSparklingContext(context.containerId),
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,29 @@ import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsControllerCompat
import com.tiktok.sparkling.Sparkling.Companion.SPARKLING_CONTEXT_CONTAINER_ID
import com.tiktok.sparkling.hybridkit.HybridCommon
import com.tiktok.sparkling.hybridkit.utils.ColorUtil

class SparklingActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val containerId = intent.getStringExtra(SPARKLING_CONTEXT_CONTAINER_ID)
val sparklingContext = SparklingContextTransferStation.getSparklingContext(containerId)
applyScreenOrientationPolicy(sparklingContext)
super.onCreate(savedInstanceState)
initStatusBar(sparklingContext)
setContentView(R.layout.activity_sparkling)
initToolBar(sparklingContext)
initSparklingFragment(sparklingContext)
}

private fun applyScreenOrientationPolicy(sparklingContext: SparklingContext?) {
val policy =
sparklingContext?.resolveScreenOrientationPolicy(
HybridCommon.hybridConfig?.defaultScreenOrientationPolicy,
) ?: return
requestedOrientation = policy.toRequestedOrientation()
}

private fun initStatusBar(sparklingContext: SparklingContext?) {
val param = sparklingContext?.hybridSchemeParam ?: return
val controller = WindowInsetsControllerCompat(window, window.decorView)
Expand Down Expand Up @@ -91,12 +101,6 @@ class SparklingActivity : AppCompatActivity() {
if (it.hideNavBar || (it.transStatusBar && !it.showNavBarInTransStatusBar)) {
supportActionBar?.hide()
}
requestedOrientation =
when (it.screenOrientation) {
"portrait" -> android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
"landscape" -> android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
else -> android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
}
}

val fragment = SparklingFragment.newInstance()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,4 +106,11 @@ class SparklingContext : HybridContext() {
var lynxViewport: SparklingLynxViewport? = null
var threadStrategy: SparklingThreadStrategy? = null
var resourceFetcherConfig: SparklingResourceFetcherConfig? = null

/**
* Optional orientation policy for a full-page Sparkling container.
*
* Embedded SparklingViews do not change their host Activity orientation.
*/
var screenOrientationPolicy: SparklingScreenOrientationPolicy? = null
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Copyright (c) 2026 TikTok Pte. Ltd.
// Licensed under the Apache License Version 2.0 that can be found in the
// LICENSE file in the root directory of this source tree.
package com.tiktok.sparkling

import android.content.pm.ActivityInfo

/**
* Controls the screen orientation of a full-page [SparklingActivity].
*
* This policy does not change the Activity that hosts an embedded [SparklingView].
*/
enum class SparklingScreenOrientationPolicy {
SYSTEM,
PORTRAIT,
LANDSCAPE,
}

internal fun SparklingContext.resolveScreenOrientationPolicy(
globalDefault: SparklingScreenOrientationPolicy?,
): SparklingScreenOrientationPolicy? =
screenOrientationPolicy
?: hybridSchemeParam?.screenOrientation?.toLegacyScreenOrientationPolicy()
?: globalDefault

internal fun SparklingScreenOrientationPolicy.toRequestedOrientation(): Int =
when (this) {
SparklingScreenOrientationPolicy.SYSTEM -> ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
SparklingScreenOrientationPolicy.PORTRAIT -> ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
SparklingScreenOrientationPolicy.LANDSCAPE -> ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
}

private fun String.toLegacyScreenOrientationPolicy(): SparklingScreenOrientationPolicy =
when (this) {
"portrait" -> SparklingScreenOrientationPolicy.PORTRAIT
"landscape" -> SparklingScreenOrientationPolicy.LANDSCAPE
else -> SparklingScreenOrientationPolicy.SYSTEM
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package com.tiktok.sparkling.hybridkit.config
import android.content.Context
import android.webkit.WebSettings
import android.webkit.WebView
import com.tiktok.sparkling.SparklingScreenOrientationPolicy
import com.tiktok.sparkling.hybridkit.HybridContext
import com.tiktok.sparkling.hybridkit.service.IKitBridgeService
import com.tiktok.sparkling.hybridkit.utils.HybridLogger
Expand All @@ -17,6 +18,7 @@ open class SparklingHybridConfig private constructor(
val bridgeConfig: IBridgeConfig?,
val logConfig: LogConfig?,
val debugConfig: DebugConfig?,
val defaultScreenOrientationPolicy: SparklingScreenOrientationPolicy?,
) {
companion object {
inline fun build(
Expand All @@ -33,6 +35,7 @@ open class SparklingHybridConfig private constructor(
private var bridgeConfig: IBridgeConfig? = null
private var logConfig: LogConfig? = null
private var debugConfig: DebugConfig? = null
private var defaultScreenOrientationPolicy: SparklingScreenOrientationPolicy? = null

fun setDebugConfig(debugConfig: DebugConfig) {
this.debugConfig = debugConfig
Expand All @@ -54,7 +57,20 @@ open class SparklingHybridConfig private constructor(
this.logConfig = logConfig
}

fun build() = SparklingHybridConfig(baseInfoConfig, lynxConfig, webConfig, bridgeConfig, logConfig, debugConfig)
fun setDefaultScreenOrientationPolicy(policy: SparklingScreenOrientationPolicy?) {
defaultScreenOrientationPolicy = policy
}

fun build() =
SparklingHybridConfig(
baseInfoConfig,
lynxConfig,
webConfig,
bridgeConfig,
logConfig,
debugConfig,
defaultScreenOrientationPolicy,
)
}
}

Expand Down
Loading
Loading