Skip to content
Merged
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
9 changes: 0 additions & 9 deletions .claude/settings.local.json

This file was deleted.

52 changes: 52 additions & 0 deletions .github/workflows/android-ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
name: Android CI

# Compile both flavors, run lint, and run the JVM unit test suite on every
# push and pull request. Deliberately skips the native (llama.cpp) build and
# instrumented tests: the standard flavor never needs the submodule, and the
# mindpulseDev Java compile doesn't trigger CMake.
on:
push:
pull_request:

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
build:
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v4
# submodules intentionally NOT checked out (llama.cpp is dev-native only)

- name: Set up JDK 17
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: '17'

- name: Set up Gradle (with cache)
uses: gradle/actions/setup-gradle@v3

- name: Compile production (standard) flavor
run: ./gradlew :app:compileStandardReleaseJavaWithJavac --console=plain

- name: Compile mindpulseDev flavor (Java only)
run: ./gradlew :app:compileMindpulseDevDebugJavaWithJavac --console=plain

- name: Lint (standard)
run: ./gradlew :app:lintStandardDebug --console=plain

- name: Unit tests (both flavors)
run: ./gradlew :app:testStandardDebugUnitTest :app:testMindpulseDevDebugUnitTest --console=plain

- name: Upload lint and test reports
if: always()
uses: actions/upload-artifact@v4
with:
name: reports
path: |
app/build/reports/lint-results-*.txt
app/build/reports/tests/
retention-days: 14
18 changes: 18 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,21 @@ output.json
/gradlew
/app/release
.idea/

# Local machine settings (contains local paths / participant hashes)
.claude/settings.local.json

# On-device VLM model weights (hundreds of MB; side-loaded to the device)
models/
*.gguf

# Decrypted participant data / device pulls — NEVER commit (PII)
recentlog/
Screenshot_*.png

# Device-state artifacts and local dev prefs
tools/prefs.xml
nul

# Local correspondence / scratch notes
email_to_garrett_dashboard.md
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[submodule "app/src/main/cpp/llama.cpp"]
path = app/src/main/cpp/llama.cpp
url = https://github.com/ggml-org/llama.cpp.git
1 change: 1 addition & 0 deletions .idea/vcs.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

57 changes: 57 additions & 0 deletions STRESS_TEST_UPLOAD.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# Upload Stress Test (100,000 files)

This procedure creates a large synthetic backlog on a connected Android device and verifies upload drain behavior.

## Prerequisites

- ADB installed (`platform-tools`) and device visible in `adb devices`
- App installed and enrolled on device
- Device on charger and stable network
- Recommended: run against staging backend first

## 1) Generate Backlog + Trigger Upload + Monitor

From repo root:

```powershell
powershell -ExecutionPolicy Bypass -File .\tools\stress\run_upload_stress.ps1 `
-PackageName "edu.wisc.chm.screenomics" `
-Count 100000 `
-ChunkSize 2000 `
-FilePrefix "stress" `
-TriggerUpload `
-ContinueWithoutWifi $true `
-MonitorMinutes 180 `
-PollSeconds 60
```

Notes:

- Files are created in `/sdcard/Android/data/<package>/files/encrypt`
- Generated filenames are `stress_<timestamp>_image.jpg` so they flow through current upload path
- Script retriggers `UploadService` automatically if drain progress stalls

## 2) Observe Upload Logs

```powershell
adb logcat -d -s SCREENOMICS_UPLOAD A11yCaptureService
```

Look for:

- `Backlog drain mode enabled`
- `Progress: ...`
- Decreasing remaining synthetic file count in script output

## 3) Cleanup Synthetic Files

```powershell
powershell -ExecutionPolicy Bypass -File .\tools\stress\cleanup_stress_files.ps1 `
-PackageName "edu.wisc.chm.screenomics" `
-FilePrefix "stress"
```

## Optional Parameters

- `-AdbPath "C:\Android\platform-tools\adb.exe"` if `adb` is not in PATH
- `-DeviceSerial "<serial>"` if multiple devices are connected
61 changes: 55 additions & 6 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -20,27 +20,58 @@ android {
flavorDimensions "mindpulse"

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

ndkVersion "27.2.12479018"

defaultConfig {
applicationId "edu.wisc.chm.screenomics"
minSdkVersion 29
targetSdkVersion 35
versionCode 27
versionName "1.21"
versionCode 28
versionName "1.22"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
buildConfigField "boolean", "MINDPULSE_ENABLED", "false"
}

externalNativeBuild {
cmake {
path "src/main/cpp/CMakeLists.txt"
version "3.22.1"
}
}
productFlavors {
standard {
dimension "mindpulse"
// Production flavor: no VLM native stack. CMake only builds the tiny
// vlm_noop placeholder (excluded from packaging below), keeping the
// llama.cpp checkout and NDK toolchain out of the release APK.
externalNativeBuild {
cmake {
targets "vlm_noop"
}
}
}
mindpulseDev {
dimension "mindpulse"
applicationIdSuffix ".mindpulse"
versionNameSuffix "-mindpulse"
buildConfigField "boolean", "MINDPULSE_ENABLED", "true"
// On-device VLM (llama.cpp) is dev-only; arm64 test devices only.
// NOTE: -march is forced at compile time (bypasses ggml runtime CPU
// dispatch). i8mm (ARMv8.6) removed — it SIGILLs on armv8.2 cores.
ndk {
abiFilters 'arm64-v8a'
}
externalNativeBuild {
cmake {
targets "vlm_bridge"
cppFlags "-std=c++17 -O3 -march=armv8.2-a+dotprod+fp16"
arguments "-DANDROID_STL=c++_shared", "-DVLM_ENABLED=ON"
}
}
}
}
signingConfigs {
Expand All @@ -54,6 +85,9 @@ android {
}
}
buildTypes {
debug {
signingConfig signingConfigs.debug
}
release {
minifyEnabled true
shrinkResources true
Expand All @@ -64,12 +98,23 @@ android {
}
}
kotlinOptions {
jvmTarget = '1.8'
jvmTarget = '17'
}

packaging {
jniLibs {
useLegacyPackaging = false
// Placeholder target built by the standard flavor; never ship it.
excludes += "**/libvlm_noop.so"
}
}

testOptions {
unitTests {
// Robolectric (Encryptor/Logger tests) needs resources; plain JUnit
// tests (GlassPhotoAssembler) need android.util.Log to no-op.
includeAndroidResources = true
returnDefaultValues = true
}
}

Expand All @@ -91,6 +136,7 @@ dependencies {
implementation 'com.squareup.okhttp3:okhttp:4.11.0'
implementation 'com.journeyapps:zxing-android-embedded:4.3.0'
testImplementation 'junit:junit:4.13.2'
testImplementation 'org.robolectric:robolectric:4.12.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
def camerax_version = "1.3.4"
Expand All @@ -109,6 +155,9 @@ dependencies {
implementation 'androidx.activity:activity:1.8.2'
implementation 'com.google.android.play:app-update:2.1.0'

// ML Kit OCR (on-device text recognition) - mindpulseDev only
mindpulseDevImplementation 'com.google.mlkit:text-recognition:16.0.1'

// Firebase temporarily disabled - uncomment after getting new google-services.json
// implementation platform('com.google.firebase:firebase-bom:30.1.0')
// implementation 'com.google.firebase:firebase-analytics'
Expand Down Expand Up @@ -182,7 +231,7 @@ def zipalignOutputsTask = tasks.register("zipalignArtifacts") {
}

tasks.configureEach { task ->
if (task.name.startsWith("assemble") || task.name.startsWith("bundle")) {
if ((task.name.startsWith("assemble") || task.name.startsWith("bundle")) && !task.name.contains("Debug")) {
task.finalizedBy(zipalignOutputsTask)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ public void useAppContext() {
// Context of the app under test.
Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();

assertEquals("com.screenomics", appContext.getPackageName());
// applicationId is edu.wisc.chm.screenomics (+ ".mindpulse" for the mindpulseDev flavor),
// not the "com.screenomics" namespace — assert flavor-agnostically.
assertTrue(appContext.getPackageName().startsWith("edu.wisc.chm.screenomics"));
}
}
Loading
Loading