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
55 changes: 55 additions & 0 deletions .github/workflows/android-prepaint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
name: Android prepaint viewer

on:
push:
paths:
- "android-prepaint/**"
- "bin/termux_prepaint_url.grease"
- "tests/test_termux_prepaint_url.grease"
- "docs/prepaint-display-contract.md"
- ".github/workflows/android-prepaint.yml"
pull_request:
paths:
- "android-prepaint/**"
- "bin/termux_prepaint_url.grease"
- "tests/test_termux_prepaint_url.grease"
- "docs/prepaint-display-contract.md"
- ".github/workflows/android-prepaint.yml"

permissions:
contents: read

jobs:
build-apk:
runs-on: ubuntu-latest
defaults:
run:
working-directory: android-prepaint
steps:
- uses: actions/checkout@v4

- name: Exercise the exact Termux command boundary
working-directory: ${{ github.workspace }}
run: sh tests/test_termux_prepaint_url.grease

- uses: actions/setup-java@v4
with:
distribution: temurin
java-version: "17"

- uses: android-actions/setup-android@v3

- name: Install Android platform
run: sdkmanager "platforms;android-36" "build-tools;36.0.0"

- uses: gradle/actions/setup-gradle@v4
with:
gradle-version: "8.13"

- name: Test, lint, and build debug APK
run: gradle --no-daemon :app:testDebugUnitTest :app:lintDebug :app:verifyPrepaintBoundary

- uses: actions/upload-artifact@v4
with:
name: ib-prepaint-viewer-apk
path: android-prepaint/app/build/outputs/apk/debug/app-debug.apk
3 changes: 3 additions & 0 deletions .github/workflows/idric-core.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ jobs:
- name: Install system dependencies through Grease
run: sh bin/ci_browser_foundation.grease install-dependencies

- name: Exercise ICU search and bounded network pre-decision
run: sh bin/ci_browser_foundation.grease exercise-prepaint-network

- name: Restore Idric compiler
id: idric-cache
uses: actions/cache/restore@v4
Expand Down
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
android-prepaint/.gradle/
android-prepaint/app/build/
android-prepaint/dist/
android-prepaint/local.properties
src/build/
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,11 @@ Python and Ithon are not IB implementation layers. A disposable comparison may e

Android or other native code is a narrow platform boundary for facilities Idriç and Grease cannot yet reach directly: NativeActivity, EGL, renderers, kernel-enforced filesystem operations, clipboard and share handoff, and similar FFI edges. Those adapters do not own browser state.

`android-prepaint/` is a deliberately small phone-visible harness for the Idriç
information prepaint. It uses native Android views rather than `WebView`, applies
a fixed dark presentation to extracted text, preserves fetched image colors, and
replaces partial projections with later complete revisions. Its first live path
passes an exact URL through Termux to Grease, ICU, and the Idriç HTML prepaint
producer, then validates the bounded result before changing the visible page.

The current Idriç core covers ordered history values, rebuildable indexes, storage classification and read policy, and the renderer-independent inspector model. Scientific-media work adds HTML-first arXiv harvesting, ordered image downloads, caption and alternate-text naming, PDF fallback, and a low-priority second naming pass. The task, category, and generic inference records documented above remain design boundaries rather than claims of completed implementation.
85 changes: 85 additions & 0 deletions android-prepaint/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# IB Prepaint APK

This is a deliberately small display harness for IB's renderer-neutral information
view. It paints successive prepaint revisions as a dark, linear document made from
native Android text, rows, forms, links, and images. It also accepts an ordinary
UTF-8 text file directly: prose becomes readable paragraphs and a line containing
one absolute HTTP(S) URL becomes a tappable link.

It is not a browser engine. The APK has:

- no `WebView`;
- no network permission;
- no HTML, CSS, or JavaScript parser;
- no durable tab or history ownership;
- no tint, inversion, or crop applied to fetched images.

The bundled `sample.prepaint` is a small article rather than a developer-workbench
fixture. It waits 1.4 seconds, then atomically replaces the useful prefix with the
complete projection. **Replay** runs the repaint again. Hack Regular is bundled for
all visible text. **Open** accepts either another local `.prepaint` artifact or a
plain text file through Android's document picker; the app does not need broad
storage access. A rejected structured artifact or unreadable file leaves the
current page visible.

The bottom input accepts either one absolute HTTP(S) URL or search words. An
absolute URL is preserved byte-for-byte after surrounding whitespace is removed;
other input becomes a percent-encoded Google search URL. Live addresses are
bounded to 2,048 visible ASCII characters. The APK sends that URL as one argument
to `~/opt/ib/bin/termux_prepaint_url.grease` through Termux's
`RUN_COMMAND` service. Grease runs ICU, passes the fetched HTML to the Idriç
information extractor, and returns a complete `ib-prepaint` artifact. The APK
still has no network permission and does not substitute WebView or an Android HTTP
stack.

Termux 0.109 or newer must be installed with `allow-external-apps=true`, and IB
must be granted Termux's **Run commands in Termux environment** permission. The IB
checkout is expected at `~/opt/ib`; `icu` must be on Termux's `PATH`; and
`src/build/exec/ib-html-prepaint` must have been built from `src/HtmlPrepaint.idric`.
Those executable-installation requirements are intentionally explicit rather than
silently falling back to another fetcher.

Build the Termux-side renderer once with:

```sh
cd ~/opt/ib/src
idris2 HtmlPrepaint.idric -o ib-html-prepaint
```

Each request has a private nonce and increasing identifier. The viewer accepts a
result only for the newest request, only when Termux reports untruncated stdout
and stderr, only on a clean exit, and only when the structured artifact names the
exact requested URL and ends in a complete revision. The result is additionally
bounded to 64 KiB. Any missing executable, rejected permission, stale response,
truncation, command failure, or malformed artifact leaves the existing page
visible.

## Build

With Android SDK 36 and Gradle 8.13 available:

```text
cd android-prepaint
gradle --no-daemon :app:testDebugUnitTest :app:lintDebug :app:verifyPrepaintBoundary
```

The APK is written to `app/build/outputs/apk/debug/app-debug.apk`. The package name
is `org.isomorphisms.ib.prepaint`, so this harness can remain installed beside the
IB storage inspector or a later browser shell.

The boundary check rejects `android.webkit`, `WebView`, an Internet permission, or
a debug APK larger than 2 MiB.

## Boundary

Idriç owns HTML extraction and the `InformationView`; Grease owns ICU execution
and its temporary files. The Android boundary recognizes an exact address versus
search input, invokes the fixed Termux command, and validates the returned
artifact without owning browser state.
This APK parses the disposable display interchange described in
`docs/prepaint-display-contract.md`, plus the deliberately narrower plain-text
fallback, and maps the resulting blocks to Android views. It does not parse HTML.
The interchange is a cache format, not canonical browsing state.

Hack is from Source Foundry's Hack project. Its license is retained in
`licenses/Hack-LICENSE.md`.
57 changes: 57 additions & 0 deletions android-prepaint/app/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
plugins {
id("com.android.application")
}

android {
namespace = "org.isomorphisms.ib.prepaint"
compileSdk = 36

defaultConfig {
applicationId = "org.isomorphisms.ib.prepaint"
minSdk = 26
targetSdk = 36
versionCode = 3
versionName = "0.3.0"
}

buildTypes {
getByName("release") {
isMinifyEnabled = false
}
}

compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
}

dependencies {
testImplementation("junit:junit:4.13.2")
}

tasks.register("verifyPrepaintBoundary") {
dependsOn("assembleDebug")
doLast {
val implementationFiles = fileTree("src/main") {
include("**/*.java", "**/*.xml")
}
val implementation = implementationFiles.files.joinToString("\n") { it.readText() }
check(!implementation.contains("android.webkit")) {
"The prepaint harness must not depend on android.webkit."
}
check(!implementation.contains("WebView")) {
"The prepaint harness must not contain a WebView."
}
check(!file("src/main/AndroidManifest.xml").readText()
.contains("android.permission.INTERNET")) {
"The prepaint harness must not request Internet access."
}

val apks = fileTree("build/outputs/apk/debug") { include("*.apk") }.files
check(apks.size == 1) { "Expected exactly one debug APK, found ${apks.size}." }
check(apks.single().length() <= 2L * 1024L * 1024L) {
"Debug APK exceeds the 2 MiB harness budget."
}
}
}
30 changes: 30 additions & 0 deletions android-prepaint/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="com.termux.permission.RUN_COMMAND" />

<queries>
<package android:name="com.termux" />
</queries>

<uses-feature
android:name="android.hardware.touchscreen"
android:required="false" />

<application
android:allowBackup="false"
android:label="IB Prepaint"
android:supportsRtl="true"
android:theme="@style/Theme.IBPrepaint"
android:usesCleartextTraffic="false">
<activity
android:name=".PrepaintActivity"
android:configChanges="keyboardHidden|orientation|screenSize|density"
android:exported="true"
android:launchMode="singleTop">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
Binary file not shown.
18 changes: 18 additions & 0 deletions android-prepaint/app/src/main/assets/sample.prepaint
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
ib-prepaint 1
revision 1 partial
requested-url https://example.test/field-notes
resolved-url https://example.test/field-notes
title Field notes
heading 1 Small pages first
text Useful article text can appear before a site finishes loading everything else.
end
revision 2 complete
requested-url https://example.test/field-notes
resolved-url https://example.test/field-notes
title Field notes
heading 1 Small pages first
text Useful article text can appear before a site finishes loading everything else.
text The pre-paint keeps ordinary paragraphs readable, preserves links, and leaves fetched images in their original colors.
image resource:sample_image An open book below green hills and a blue sky. A bundled image painted without tint, inversion, or crop. /full-image
link Read the source /article
end
Loading
Loading