# SumSheet view-SDK for Android

Open and display `.xlsx` / `.xlsm` workbooks inside your own Android app,
computed by the same engine and drawn by the same page that ship in SumSheet
on Google Play (version 2026.3.32, build 9). The workbook is
opened and recalculated by a native Rust core; the grid, charts and ribbon are
a WebView page bundled with the SDK. No server, no network: everything runs on
the device.

Android, `arm64-v8a` only (the core is not built for other ABIs). The page is
the same editor SumSheet ships: the workbook opens read-to-edit, with the
ribbon, formula bar and sheet tabs. The SDK carries the same release number as
the store build and is released together with it.

## What is in the box

| Path | What | Size |
|---|---|---|
| `jniLibs/arm64-v8a/libfastsheet_mobile_core.so` | the spreadsheet core (JNI) | 54 MB |
| `assets/f1/` | the page: `index.html`, `zagruzka.mobile.js`, `preload.mobile.js`, `main.mobile.js` | 35 MB |
| `assets/sample.xlsx` | a small workbook to prove the integration | 7 KB |
| `bridge/FastSheetBridge.kt` | Kotlin wrapper over the core's JNI surface | |
| `bridge/SumSheetWebBridge.kt` | routes page letters to the core, files, clipboard, pickers | |
| `example/SumSheetViewerActivity.kt` | reference host activity (steps 2 and 3 as code) | |
| `example/AndroidManifest.xml`, `example/build.gradle.kts` | what the reference host needs | |
| `VERSION` | version, build, date, checksums | |

The binary and the page are byte-for-byte the ones inside the Play AAB
(`SumSheet-2026.3.32-code9`); see `VERSION` for SHA-256.

## Requirements

- Android Gradle Plugin 8.x, Kotlin 2.x, `minSdk` 27 or higher, `compileSdk` 36.
- Dependencies: `androidx.webkit:webkit:1.12.1` (document-start script) and
  `androidx.appcompat:appcompat:1.7.0` (used by the reference activity).
- A device or emulator with an `arm64-v8a` system image and Android System
  WebView (any recent version).
- Roughly 90 MB of APK for arm64 — ship it in an App Bundle so Play delivers
  only the arm64 split.

## Steps 1-2-3

### 1. Add to your app

Copy into your application module (`app/src/main/…`):

```
jniLibs/                        -> app/src/main/jniLibs/
assets/                         -> app/src/main/assets/
bridge/FastSheetBridge.kt       -> app/src/main/java/md/layers/mobile/docengine/FastSheetBridge.kt
bridge/SumSheetWebBridge.kt     -> app/src/main/java/md/layers/mobile/docengine/SumSheetWebBridge.kt
```

Keep the bridge files' package exactly as it is (`md.layers.mobile.docengine`)
and do not rename `FastSheetBridge`: the JNI symbol names in the core are
derived from `md.layers.mobile.docengine.FastSheetBridge`. Renaming compiles
fine and fails at run time with `UnsatisfiedLinkError`.

`SumSheetWebBridge.kt` contains an intentional NUL byte inside a regular
expression (file-name sanitising); some text tools skip the file silently —
copy it as a file, do not paste it through an editor.

In `app/build.gradle.kts` add:

```kotlin
android {
  defaultConfig { ndk { abiFilters += listOf("arm64-v8a") } }
}
dependencies {
  implementation("androidx.webkit:webkit:1.12.1")
  implementation("androidx.appcompat:appcompat:1.7.0")
}
```

### 2. Create the WebView

Copy `example/SumSheetViewerActivity.kt` into your package (change the
`package` line) and register the activity in your manifest
(`example/AndroidManifest.xml` shows the element). What the activity does,
and what any host must do:

```kotlin
val wv = WebView(this)
wv.settings.javaScriptEnabled = true
wv.settings.domStorageEnabled = true                  // page keeps its UI language here
wv.settings.allowFileAccess = true                    // page is loaded from file:///android_asset
wv.settings.allowFileAccessFromFileURLs = true
wv.settings.allowUniversalAccessFromFileURLs = true

// before the page runs (needs androidx.webkit): the bridge door, the workbook, READY
val workbook = JSONObject().put("имя", name).put("base64", Base64.encodeToString(bytes, Base64.NO_WRAP))
WebViewCompat.addDocumentStartJavaScript(wv, """
  window.ReactNativeWebView = { postMessage: function (m) { F1Native.postMessage(m); } };
  window.__F1_КНИГА__ = $workbook;
  window.__F1_MOBILE_BRIDGE_READY__ = true;
""", setOf("*"))

val bridge = SumSheetWebBridge(this, wv)              // Activity + WebView
wv.addJavascriptInterface(bridge, "F1Native")
wv.loadUrl("file:///android_asset/f1/index.html")
setContentView(wv)
```

Order matters: settings, the document-start script and `addJavascriptInterface`
before `loadUrl`. Forward `onActivityResult` to `bridge.onActivityResult(...)`
(the page's open/save dialogs are system pickers started by the bridge) and
call `bridge.close()` when the WebView goes away — it frees the native
workbook.

### 3. Open a workbook

The page takes the workbook as `{имя, base64}` in the document-start script,
so any source works:

```kotlin
// the bundled sample
startActivity(Intent(this, SumSheetViewerActivity::class.java))

// a file the user picked, shared, or tapped ("Open with"):
startActivity(Intent(Intent.ACTION_VIEW).apply {
  setDataAndType(uri, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
  addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
  setClass(this@MyActivity, SumSheetViewerActivity::class.java)
})
```

The reference activity reads `intent.data` with `contentResolver` and falls
back to `assets/sample.xlsx` when there is no URI. To appear in the system
"Open with" list for `.xlsx`/`.xlsm`, add the second intent-filter from
`example/AndroidManifest.xml`.

## Host contract (what the page expects from you)

Defined in the repository as `apps/desktop-shell/mobile/ДОГОВОР-МОСТА.md`; the
short form:

- `window.ReactNativeWebView.postMessage(text)` — the page's only door out.
  Route it to `F1Native.postMessage` (the bridge). The page sends
  `{"id":n,"kind":"<kind>","payload":{…}}`; the bridge answers through
  `window.__F1_MOBILE_RECEIVE__(json)`. You do not touch either.
- `window.__F1_КНИГА__` — `{имя, base64}` of the workbook to open. Set before
  the page runs. Without it the page opens empty.
- `window.__F1_MOBILE_BRIDGE_READY__ = true` — tells the page the bridge is
  installed; the page waits for it before talking to the core.
- Kinds the bridge serves: `core.*` (the Rust core), `fs.*` (files under the
  app's `filesDir`; "Save" writes there), `clipboard.*`, `file.pick` /
  `file.save` (system pickers via `onActivityResult`), `fonts.list`.
- UI language follows the device language (`navigator.language`); the user's
  choice made in the page is kept in DOM storage, hence `domStorageEnabled`.

## Verified

Followed literally on an empty single-activity Kotlin project (AGP 8.11.1,
Kotlin 2.1.20, Gradle 8.14). See the bottom of this file for the numbers of
the release you hold; the first release (2026.3.30) was checked on an arm64
API 34 emulator and a Samsung Galaxy A22 5G (Android 13) — the bundled
sample and a workbook handed over via `ACTION_VIEW` both opened with the grid,
ribbon and sheet tabs identical to the Play build installed next to it.

## Known limits

- `arm64-v8a` only. No x86_64 emulator images, no 32-bit devices.
- The core is 54 MB uncompressed and loads on first use (about a second).
- The page is the full editor, not a lightweight viewer: RAM and first-open
  time are those of the SumSheet app (several seconds for large workbooks).
- Macros (`.xlsm`) open with formulas recalculated; VBA does not run.

## Terms

Free to evaluate and to build integration prototypes. Licensing for
production use and support: contact us via sumoffice.com.
