A native Android WebView shell around the existing CCE Software v1.0
web app (Continuous & Comprehensive Evaluation for Maharashtra schools).
The HTML/CSS/JS you already built lives unmodified under
app/src/main/assets/www/ — this project just wraps it in a hardened
native app so it can be distributed as an APK / installed on school
tablets and phones.
CCE-School-Android/
├── app/
│ ├── build.gradle # release signing, minify, R8 config
│ ├── proguard-rules.pro
│ └── src/main/
│ ├── AndroidManifest.xml # allowBackup=false, no cleartext, etc.
│ ├── java/.../MainActivity.kt # hardened WebView shell
│ ├── res/xml/ # network security + backup config
│ └── assets/www/
│ ├── index.html # your CCE_School_Edition.html
│ └── xlsx.full.min.js
├── keystore.properties.sample # copy → keystore.properties, fill in
├── .github/workflows/android-build.yml
└── gradlew / gradlew.bat
./gradlew assembleDebug # unsigned debug APK
./gradlew assembleRelease # release APK (needs keystore.properties, below)
Release builds are only signed if keystore.properties exists at the
project root (it’s git-ignored on purpose — never commit a real keystore
or its passwords).
cp keystore.properties.sample keystore.properties
# edit keystore.properties with your real keystore path + passwords
keytool -genkey -v -keystore release.keystore -alias cce-release \
-keyalg RSA -keysize 2048 -validity 10000
./gradlew assembleRelease
.github/workflows/android-build.yml builds a debug APK on every push/PR
to main and uploads it as a workflow artifact, so you get an installable
build from GitHub without needing a local Android Studio setup.
This wraps a client-side web app in a WebView, so it’s worth being precise about what “secure” does and doesn’t mean here.
What this project actually hardens:
file:// origin. Local assets are served over a virtual
https://appassets.androidplatform.net/assets/... origin via
androidx.webkit.WebViewAssetLoader, instead of the classic
file:///android_asset/.... This sidesteps the well-known WebView
file-scheme vulnerabilities (arbitrary local file read via crafted
pages, broken same-origin behavior) entirely, rather than trying to
patch around them.allowFileAccess, allowContentAccess,
allowFileAccessFromFileURLs, and allowUniversalAccessFromFileURLs are
all explicitly false. Mixed content is blocked
(MIXED_CONTENT_NEVER_ALLOW). Safe Browsing is on.network_security_config.xml +
usesCleartextTraffic="false"). Every remote resource the page loads
(Firebase, Google Fonts) is already https://, so nothing needed an
exception.shouldOverrideUrlLoading
refuses to navigate the WebView itself to any host other than the app’s
virtual origin — so a compromised/malicious link can’t hijack the app
into displaying an arbitrary external site in place of your UI. It does
not block the page’s own <script src="https://..."> resource loads
(Firebase SDK, fonts), since those aren’t top-level navigations.BuildConfig.DEBUG gate on
setWebContentsDebuggingEnabled), so a release APK can’t be inspected
via chrome://inspect.allowBackup="false"), so device backups
(Google One, adb backup) can’t be used to lift the app’s local storage
(license data, cached login info) off a device.What this does not and cannot protect against, and you should plan around rather than assume away:
index.html/xlsx.full.min.js, not compiled code. Unzipping the APK
(unzip app-release.apk) gets you the exact source. ProGuard only
obfuscates the thin native Kotlin shell, not the web app. If you need to
keep evaluation logic or license validation truly private, it has to be
enforced server-side, not client-side — any client-side “unlock code”
scheme can be patched out of a decompiled/rebuilt APK by a determined
user, regardless of how the code is packaged.cce-software.firebaseapp.com). Make sure your Firestore security
rules, not this app, are what actually stop one school from reading or
writing another school’s data.keystore.properties) but can’t
generate or protect the keystore itself; treat that file/keystore like a
password, not like source code.Only INTERNET and ACCESS_NETWORK_STATE are requested — no storage,
camera, contacts, or location access, since the app doesn’t need any of
those.