Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
e3b20d9
Initial update to Android 15
GroveOfGraves Feb 9, 2026
ce1893f
Update AGP version
GroveOfGraves Feb 16, 2026
8d6a655
Update to latest AGP
GroveOfGraves Feb 17, 2026
eabb94a
Remove some deprecated config options
GroveOfGraves Feb 17, 2026
72a7d5f
Remove Jetifier
GroveOfGraves Feb 19, 2026
5834b9e
Update additional configs
GroveOfGraves Feb 19, 2026
ca67f2d
Patch to target API 36
GroveOfGraves Mar 2, 2026
45f49f2
Add a service that forwards the necessary ports for the desktop app (#1)
GroveOfGraves Mar 2, 2026
d39d7ce
Changes to versioning, hardcoded strings, and logic updates (#2)
ElitheEpicBoss3 Mar 2, 2026
b133d33
Feat/foreground service changes (#3)
GroveOfGraves Mar 2, 2026
6d5cb3f
Opt in to predictive back gesture (#4)
GroveOfGraves Mar 2, 2026
b8808b8
eli-branch (#5)
ElitheEpicBoss3 Mar 2, 2026
e4649c0
local-eli (#6)
ElitheEpicBoss3 Mar 3, 2026
c09177b
cool-local-eli-branch (#7)
ElitheEpicBoss3 Mar 3, 2026
4969a0b
Update to newer Gradle version (#8)
GroveOfGraves Mar 3, 2026
eae995a
Feat/vision & camera updates (#9)
GroveOfGraves Mar 3, 2026
be87937
Update http server (#10)
Major-Q Mar 3, 2026
80abfa5
local-eli-branch (#11)
ElitheEpicBoss3 Mar 3, 2026
c941af7
Feat/update tests (#12)
GroveOfGraves Mar 3, 2026
16cd45f
local (#13)
ElitheEpicBoss3 Mar 3, 2026
e1e40c4
Fix crashing on record on some tablets (#14)
GroveOfGraves Mar 3, 2026
6640fef
Fix deprecated async functionality, and some minor improvements (#15)
GroveOfGraves Mar 4, 2026
e478d09
warning-city (#16)
ElitheEpicBoss3 Mar 4, 2026
d9acdeb
Cleanup http server (#17)
Major-Q Mar 5, 2026
7ce81a7
merge-fix (#18)
ElitheEpicBoss3 Mar 5, 2026
0bd13bb
more-warnings (#19)
ElitheEpicBoss3 Mar 5, 2026
881b3bf
Add langauges (#20)
Major-Q Mar 5, 2026
d59c599
Feat/new tests (#21)
GroveOfGraves Mar 6, 2026
8444da8
properly-warned (#23)
ElitheEpicBoss3 Mar 6, 2026
9b4b4f1
Fix SyncActivityTest to pick correct permissions depending on OS vers…
GroveOfGraves Mar 6, 2026
87c7210
final-warning-check (#25)
ElitheEpicBoss3 Mar 6, 2026
17a2827
Fix broken QR code scanning (#26)
GroveOfGraves Mar 6, 2026
c0d4ba3
banner-fix (#28)
ElitheEpicBoss3 Mar 6, 2026
668e814
Update play button (#29)
Major-Q Mar 6, 2026
ae0e1a2
status-bar (#30)
ElitheEpicBoss3 Mar 6, 2026
a864135
Patch/tests compatibility (#31)
GroveOfGraves Mar 6, 2026
53afb39
Update README #Building section to reflect SDK 36 configuration and A…
Copilot Mar 6, 2026
6b32cb5
docs: consolidate duplicate Testing sections and fix README heading c…
Copilot Mar 6, 2026
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
112 changes: 101 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,110 @@ Simple Scripture recording on Android Devices

Synchronizes (over local WiFi) with HearThis for Windows. Can display, record and play back Scripture.

#Building
Currently builds and works using Android Studio 2.2.1.
I have Android SDK platforms installed for 6.0 (Marshmallow/23) and 4.3 (Jelly Bean/18); possibly only the latter is needed. SDK tools I have installed are Android SDK Platform-Tools 24.0.4, Android SDK Tools 25.2.2, Android Support Library, rev 23.2.1, (Documentation for Android SDK, version 1), (Google USB Driver, rev 11), Intel X86 Emulator Accelerator (HAXM installer), 6.0.4), Android Support Repository 38.0.0, Google Repository 36). Ones in parens are those I think are most likely not needed; there may be others that could be omitted. Launching the full SDK manager shows a lot more options installed; hopefully none are relevant. I have not yet had opportunity to attempt a minimal install on a fresh system.
# Building
The project targets Android SDK 36 (Android 16) to meet Google Play's current requirements. The simplest way to get started is to install the latest stable version of Android Studio and open this repository as the project — Android Studio will handle the rest automatically.

#Testing
HearThis Android only has minimal automated tests and I have had extreme difficulty getting even that far. I can't get any of them to work with the current version of Android Studio (not that I've spent much effort so far). Both sets ran earlier after many struggles to set up the right build configurations.
> **Important:** use the latest stable version of Android Studio. As of development, this is **Android Studio Panda 2 | 2025.3.2**. Older versions may not recognise or be able to download the required Gradle version, causing the build to fail.

1. In app\src\test\java\org\sil\hearthis\BookButtonTest.java are some simple tests designed to run without needing an emulator or real android but directly in JUnit. One way to run these tests is to right-click BookButtonTest in the Project panel on the top left and choose "Run 'BookButtonTest'".
To run tests in multiple files, I had to edit build configurations (Run/Edit configurations). If you do this right after a right-click on org.sil.hearthis and "Run tests in '...'" the configuration it is trying to use will be selected. I was not able to get anywhere with running tests by 'All in package', but if you choose 'all in directory' and configure the directory to be a path to <HearThisAndroid>\app\src\test\java\org\sil\hearthis, it runs the right tests. Possibly the problem is that I have the test directory in the wrong place.
Unfortunately wherever it saves the build configurations does not seem to be checked in.
2. In app\src\androidTest\java\org\sil\hearthis\MainActivityTest.java are some very minimal tests designed to run on an emulator or real device. I believe these also worked once.
Build requirements:

The second group of tests currently all fail; my recent attempts to run the others result in reports that no tests are found to run.
There are also some tests in app\src\test\java\org\sil\hearthis\RecordActivityUnitTest.java. I am not sure these ever worked.
- **Android Studio** — latest stable release (Panda 2 | 2025.3.2 or later); open the repository root as the project and let Android Studio sync and configure everything automatically
- **Android SDK Platform 36** — install via the SDK Manager in Android Studio (*SDK Platforms* tab, API level 36)
- **Android SDK Build-Tools** — any version compatible with API 36 (install the latest available via the SDK Manager)
- **Android SDK Platform-Tools** — the latest stable version
- **Minimum SDK**: API 23 (Android 6.0 Marshmallow) — required by the CameraX and ML Kit libraries
- **Java 17** — the project is compiled with Java 17 source and target compatibility; make sure the JDK in Android Studio is set to 17 or later
- **Gradle 9.3.1** and **Android Gradle Plugin 9.1.0** — these are managed automatically by the Gradle wrapper; no manual installation is needed

# Testing
The automated test suite has been significantly expanded and all tests are expected to pass with the current configuration.

**Unit tests** (no device or emulator required) live in `app/src/test/java/org/sil/hearthis/` and use JUnit 4 with Robolectric for any tests that need Android context. To run them in Android Studio, right-click the `org.sil.hearthis` package under that directory and choose *Run tests in 'org.sil.hearthis'*.

- `BookButtonTest.java` — tests BookButton state and progress logic
- `RecordActivityUnitTest.java` — tests the scroll-position calculation in RecordActivity (plain JUnit, no Android context needed)
- `AcceptFileHandlerTest.java` — tests the HTTP file upload handler, including path traversal protection
- `HearThisPreferencesTest.java` — tests that application preferences are correctly persisted and retrieved
- `LevelMeterViewTest.java` — tests the level update throttle logic in the audio level meter view
- `RealScriptProviderTest.java` — tests the scripture data parsing logic

**Instrumentation tests** (require an emulator or connected Android device) live in `app/src/androidTest/java/org/sil/hearthis/`. To run them, right-click the package under that directory and choose *Run tests in 'org.sil.hearthis'*.

- `MainActivityTest.java` — tests that the main activity launches and resolves to the correct next screen
- `BookSelectionTest.java` — tests the navigation flow from the book chooser to the chapter chooser
- `ProjectSelectionTest.java` — tests project listing and selection in `ChooseProjectActivity`
- `RecordActivityTest.java` — covers loading, navigation, the recording workflow, and state persistence in `RecordActivity`
- `SyncActivityTest.java` — tests initial UI state, CameraX initialisation, and `SyncService` integration in `SyncActivity`

Shared test utilities (`TestFileSystem`, `TestScriptProvider`) live in `app/src/sharedTest/java/` and are automatically included in both test source sets via `sourceSets` configuration in `app/build.gradle`.

Test library notes:

- Robolectric 4.14.1 and Java dynamic proxies are used in place of Mockito for cleaner, warning-free unit testing
- All `androidx.test` libraries are updated to versions compatible with API 36 (`espresso-core` 3.7.0, `junit-ext` 1.3.0, `uiautomator` 2.3.0)
- `espresso-intents` is included for testing intent-based navigation flows between activities

### Http Server

The application was using a deprecated http server library. We updated the server with a new library called NanoHTTPD. This allows the server to be future-proof.

Along with updating the server library we ensured that the server ran more efficiently. Tasks we looked into and updated are the following:

- Guard Against Double Start and Stop in SyncServer
- Fix Path Traversal Vulnerability in File Handlers
- Remove Static Listener Memory Leaks
- Make Notification Listener List Thread Safe
- Fix UI Thread Violations in SyncActivity
- Improve File Upload Error Handling
- Improve Resource and Stream Management
- Remove Hardcoded Device Name
- Improve Server Lifecycle Management

### Internationalization

Updated application to support various languages. The application currently supports the following:

- English
- Spanish
- French
- German
- Chinese (simplified)

### Edge-to-edge

Making the application compliant with Android visual constraints, including dynamically changing the status bar to fit within the screen layout.

### Warnings

Walked through all the files cleaning up the warnings, mainly being newer code standards, lambda functions instead of function definitions, and updating deprecated libraries and functions.

### Camera and Scanning

The application was using a deprecated `play-services-vision` (GMS Vision) library for QR code scanning. This was replaced with two modern Jetpack and ML Kit libraries.

- **ML Kit Barcode Scanning** (`com.google.mlkit:barcode-scanning`): An on-device barcode scanning library that does not require Google Play Services to be installed on the device. It replaced `play-services-vision`, which Google has deprecated in favour of the standalone ML Kit suite.
- **CameraX** (`androidx.camera:camera-core`, `camera-camera2`, `camera-lifecycle`, `camera-view`): A Jetpack camera library that correctly manages the camera lifecycle and simplifies camera integration. It replaced the legacy `Camera` and `CameraSource` APIs that were coupled to the old GMS Vision workflow.

These changes required raising the minimum SDK from 21 to 23, as both CameraX and ML Kit require at least API 23.

### Build System

The build system was significantly updated to target API 36. A series of incremental changes were made across several commits.

- Upgraded Android Gradle Plugin from 7.3.1 to 9.1.0
- Upgraded Gradle wrapper from 9.2.1 to 9.3.1
- Updated `compileSdk` and `targetSdk` from 33 to 36
- Updated `minSdk` from 18 to 21 in the initial Android 15 update, then further to 23 when the camera and scanning libraries were added (see Camera and Scanning section above)
- Replaced the deprecated `jcenter()` Maven repository with `mavenCentral()`
- Removed Jetifier, which is no longer needed now that all dependencies use AndroidX-native artifacts
- Added Java 17 source and target compatibility via `compileOptions`
- Added an explicit `namespace` declaration to `app/build.gradle`, which is required by newer AGP versions
- Removed `useLibrary 'org.apache.http.legacy'`, which was only needed by the old Apache HTTP server and became unnecessary after switching to NanoHTTPD
- Cleaned up stale and deprecated entries in `gradle.properties`

### Audio suggestion

The audio functionality could be updated. On one specific device the chapter view would lag and then have audio errors. We believe that the efficiency of the audio functionality should be improved, though it does work. If this is needed for Google Play we are not sure, but it is the next big thing to update.

# License

Expand Down
130 changes: 109 additions & 21 deletions app/build.gradle
Original file line number Diff line number Diff line change
@@ -1,42 +1,130 @@
apply plugin: 'com.android.application'

android {
compileSdkVersion 33
namespace 'org.sil.hearthis'
compileSdk 36

defaultConfig {
applicationId "org.sil.hearthis"
minSdkVersion 18 // gradle insists it can't be smaller than this
targetSdkVersion 33
minSdk 23 // Increased from 21 to support modern CameraX and ML Kit components
targetSdk 36

testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
}

buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.txt'
}
}

// Required for using the obsolete HttpClient class.
useLibrary 'org.apache.http.legacy'
compileOptions {
sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17
}

testOptions {
unitTests {
includeAndroidResources = true
}
}

sourceSets {
test {
java.srcDirs += 'src/sharedTest/java'
}
androidTest {
java.srcDirs += 'src/sharedTest/java'
}
}
}

dependencies {
testImplementation 'junit:junit:4.13.2'
implementation 'androidx.appcompat:appcompat:1.0.0'
//compile 'com.android.support:support-v7:27.1.1'

// allows barcode reading (SyncActivity)
implementation 'com.google.android.gms:play-services-vision:11.8.0'

testImplementation 'org.mockito:mockito-core:3.11.2'
testImplementation 'org.hamcrest:hamcrest-library:2.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.4'
// Set this dependency to use JUnit 4 rules
androidTestImplementation 'androidx.test:rules:1.5.0'
// Set this dependency to build and run Espresso tests
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.0'
// Set this dependency to build and run UI Automator tests
androidTestImplementation 'androidx.test.uiautomator:uiautomator:2.2.0'
implementation 'androidx.appcompat:appcompat:1.7.1'
implementation 'androidx.preference:preference:1.2.1'
implementation 'com.google.android.material:material:1.13.0'

// ML Kit Barcode Scanning (modern replacement for GMS Vision)
implementation 'com.google.mlkit:barcode-scanning:17.3.0'

// CameraX (modern replacement for legacy Camera/CameraSource)
def camerax_version = "1.5.3"
implementation "androidx.camera:camera-core:${camerax_version}"
implementation "androidx.camera:camera-camera2:${camerax_version}"
implementation "androidx.camera:camera-lifecycle:${camerax_version}"
implementation "androidx.camera:camera-view:${camerax_version}"

// HTTP Server for syncing
implementation 'org.nanohttpd:nanohttpd:2.3.1'

testImplementation 'org.hamcrest:hamcrest-library:3.0'
testImplementation 'org.robolectric:robolectric:4.16.1'

// Testing dependencies (Updated for Android 16/API 36)
androidTestImplementation 'androidx.test.ext:junit:1.3.0'
androidTestImplementation 'androidx.test:rules:1.7.0'
androidTestImplementation 'androidx.test:runner:1.7.0'
androidTestImplementation 'androidx.test:core:1.7.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.7.0'
androidTestImplementation 'androidx.test.espresso:espresso-intents:3.7.0'
androidTestImplementation 'androidx.test.uiautomator:uiautomator:2.3.0'
}

/**
* Portable task to setup ADB tunnels for emulator syncing.
*/
tasks.register('setupSyncTunnels') {
group = 'sync'
description = 'Setup ADB port forwarding and reverse tunneling for emulator syncing'

doLast {
Properties properties = new Properties()
def localPropertiesFile = project.rootProject.file('local.properties')
if (localPropertiesFile.exists()) {
localPropertiesFile.withInputStream { properties.load(it) }
}

def sdkDir = properties.getProperty('sdk.dir')
def adb = "adb"
if (sdkDir) {
adb = "${sdkDir}/platform-tools/adb"
if (System.getProperty("os.name").toLowerCase().contains("windows")) {
adb += ".exe"
}
}

println "Setting up ADB tunnels using: ${adb}"

def runAdb = { List<String> args ->
def fullArgs = [adb] + args
def process = fullArgs.execute()
def out = new StringBuilder()
def err = new StringBuilder()
process.consumeProcessOutput(out, err)
process.waitFor()
if (out) println "ADB Out: ${out.toString().trim()}"
if (err) println "ADB Err: ${err.toString().trim()}"
return process.exitValue()
}

// Desktop -> Emulator (Syncing data)
runAdb(['forward', 'tcp:8087', 'tcp:8087'])
// Emulator -> Desktop (QR code "Hello" notification)
runAdb(['reverse', 'tcp:11007', 'tcp:11007'])

println "Current ADB forwards:"
runAdb(['forward', '--list'])
}
}

// Automatically run the setupSyncTunnels task before every build.
tasks.named("preBuild") {
dependsOn("setupSyncTunnels")
}

// Add this at the bottom of the file
tasks.withType(JavaCompile).configureEach {
options.compilerArgs << "-Xlint:deprecation"
}
Loading