Skip to content
Open
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
9fb1e52
chore(android): add Gradle build scaffold and dependency catalog
Vedd-Patel Mar 22, 2026
2866e73
chore(android): add AndroidManifest and network security config
Vedd-Patel Mar 22, 2026
2af57bb
feat(android): add utility classes — TokenManager, ShiftStateManager,…
Vedd-Patel Mar 22, 2026
a1b31f0
feat(android): add data layer — Room entities, DAO, API service, repo…
Vedd-Patel Mar 22, 2026
ecf9de4
feat(android): add Hilt DI modules — AppModule, DatabaseModule
Vedd-Patel Mar 22, 2026
59b672c
feat(android): add app entry point and Material 3 theme
Vedd-Patel Mar 22, 2026
5a3750a
chore(android): add Gradle wrapper and resource files
Vedd-Patel Mar 22, 2026
d1e1d7c
chore(android): add README with setup instructions
Vedd-Patel Mar 22, 2026
2bf305e
chore(android): add local.properties.example for setup guidance
Vedd-Patel Mar 22, 2026
7a7ebf3
chore(android): remove duplicate vehicle_positions package
Vedd-Patel Mar 22, 2026
ae578b2
fix: add stub implementations for empty foundation files
Vedd-Patel Mar 26, 2026
9c64f2c
fix: resolve MasterKeys deprecation and secure HTTP logging
Vedd-Patel Mar 26, 2026
2558eba
test: add unit tests for LocationStateHolder and ServiceEventBus
Vedd-Patel Mar 26, 2026
666e13d
fix: update auth response field names and handle missing token failure
Vedd-Patel Mar 26, 2026
ec08c13
chore: guard local.properties load for CI and update dependencies
Vedd-Patel Mar 26, 2026
2a9824d
chore: remove default example instrumented test
Vedd-Patel Mar 26, 2026
746cf4b
fix(android): change refreshToken() return type to Response<RefreshTo…
Vedd-Patel Mar 29, 2026
7ce39ef
fix(android): make bearing/speed/accuracy nullable to match server om…
Vedd-Patel Mar 29, 2026
3bbd2a2
fix(android): rethrow CancellationException in postLocation/refreshTo…
Vedd-Patel Mar 29, 2026
482ee28
fix(android): exclude EncryptedSharedPreferences and DataStore from b…
Vedd-Patel Mar 29, 2026
c618a7f
fix(android): log dropped events when ServiceEventBus buffer is full
Vedd-Patel Mar 29, 2026
c90d171
fix(android): separate ActiveTrackingScreen and ActiveTrackingViewMod…
Vedd-Patel Mar 29, 2026
be0ae90
fix(android): fix copy-paste bug in LocationStateHolderTest, improve …
Vedd-Patel Mar 29, 2026
438f26c
fix(android): lazy-init EncryptedSharedPreferences with Keystore reco…
Vedd-Patel Mar 29, 2026
fe8f829
test(android): add instrumented tests for TokenManager, ShiftStateMan…
Vedd-Patel Mar 29, 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
90 changes: 90 additions & 0 deletions android/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# Built application files
*.apk
*.aar
*.ap_
*.aab

# Files for the ART/Dalvik VM
*.dex

# Java class files
*.class

# Generated files
bin/
gen/
out/

# Gradle files
.gradle/
build/
/build
/captures

# Local configuration file
local.properties
**/local.properties

# Proguard folder generated by Eclipse
proguard/

# Log Files
*.log

# Android Studio Navigation editor temp files
.navigation/

# Android Studio captures folder
captures/

# IntelliJ
*.iml
.idea/
/.idea/caches
/.idea/libraries
/.idea/modules.xml
/.idea/workspace.xml
/.idea/navEditor.xml
/.idea/assetWizardSettings.xml

# External native build folder generated in Android Studio 2.2 and later
.externalNativeBuild
.cxx/

# Google Services (e.g. APIs)
google-services.json

# Freeline
freeline.py
freeline/
freeline_project_description.json

# fastlane
fastlane/report.xml
fastlane/Preview.html
fastlane/screenshots
fastlane/test_output
fastlane/readme.md

# Version control
vcs.xml

# lint
lint/intermediates/
lint/generated/
lint/outputs/
lint/tmp/

# Mac
.DS_Store
**/.DS_Store

# Kotlin
*.kotlin_module

# Keystore files — never commit signing keys
*.jks
*.keystore

# Room database exports — generated, not needed in VCS
app/schemas/
33 changes: 33 additions & 0 deletions android/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Vehicle Positions Android App

## Setup

1. Clone the repo
2. Copy `local.properties.example` to `local.properties`
3. Fill in your values:
- `MAPS_API_KEY` — Google Maps API key
- `BASE_URL` — Server base URL
- `sdk.dir` — Android SDK path

## Building
```bash
cd android
./gradlew assembleDebug
```

## Local Testing

Start the backend server:
```bash
JWT_SECRET=devsecret12345678901234567890123 go run .
```

Get a test JWT:
```bash
curl -X POST http://localhost:8080/api/v1/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"driver@test.com","password":"password"}'
```

Temporarily add the JWT to `TokenManager.kt` init block for testing.
JWT injection is handled by the login flow (separate PR).
1 change: 1 addition & 0 deletions android/app/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/build
123 changes: 123 additions & 0 deletions android/app/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import java.util.Properties

val localProperties = Properties().apply {
val file = rootProject.file("local.properties")
if (file.exists()) load(file.inputStream())
}

plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.compose)
alias(libs.plugins.hilt)
alias(libs.plugins.ksp)
}

android {
namespace = "org.onebusaway.vehiclepositions"
compileSdk = 36

defaultConfig {
applicationId = "org.onebusaway.vehiclepositions"
minSdk = 29
targetSdk = 36
versionCode = 1
versionName = "1.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
manifestPlaceholders["MAPS_API_KEY"] =
localProperties["MAPS_API_KEY"]?.toString() ?: ""

// BASE_URL reads from local.properties for debug/local dev
// Falls back to production HTTPS URL for CI and release builds
val baseUrl = localProperties["BASE_URL"]?.toString()
?: "https://your-production-server.com/"
buildConfigField("String", "BASE_URL", "\"$baseUrl\"")
}

buildTypes {
debug {
isMinifyEnabled = false
}
release {
isMinifyEnabled = true
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}

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

kotlin {
jvmToolchain(17)
}

buildFeatures {
compose = true
buildConfig = true
}
}

dependencies {
// Existing Compose + AndroidX
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.lifecycle.runtime.ktx)
implementation(libs.androidx.activity.compose)
implementation(platform(libs.androidx.compose.bom))
implementation(libs.androidx.compose.ui)
implementation(libs.androidx.compose.ui.graphics)
implementation(libs.androidx.compose.ui.tooling.preview)
implementation(libs.androidx.compose.material3)

// Tests
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
androidTestImplementation(platform(libs.androidx.compose.bom))
androidTestImplementation(libs.androidx.compose.ui.test.junit4)
debugImplementation(libs.androidx.compose.ui.tooling)
debugImplementation(libs.androidx.compose.ui.test.manifest)

// Maps + Location
implementation(libs.material)
implementation(libs.maps)
implementation(libs.location)
implementation(libs.maps.compose)
implementation(libs.accompanist.permissions)
implementation(libs.hilt.navigation.compose)

// Hilt
implementation(libs.hilt.android)
ksp(libs.hilt.compiler)

// Room
implementation(libs.room.runtime)
implementation(libs.room.ktx)
ksp(libs.room.compiler)

// Security
implementation(libs.security.crypto)

// Retrofit + OkHttp
implementation(libs.retrofit)
implementation(libs.retrofit.gson)
implementation(libs.okhttp)
implementation(libs.okhttp.logging)

// Lifecycle
implementation(libs.lifecycle.viewmodel)
implementation(libs.lifecycle.runtime)

// Coroutines
implementation(libs.coroutines.android)

// DataStore
implementation(libs.datastore)

// Navigation
implementation(libs.navigation.fragment)
implementation(libs.navigation.ui)
}
54 changes: 54 additions & 0 deletions android/app/proguard-rules.pro
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# Preserve line numbers in stack traces for crash reporting
-keepattributes SourceFile,LineNumberTable
-renamesourcefileattribute SourceFile

# ── Retrofit ──────────────────────────────────────────────────────────────────
# Reflects on annotated interface methods at runtime
-keepattributes Signature
-keepattributes Exceptions
-keep class retrofit2.** { *; }
-keepclassmembers,allowshrinking,allowobfuscation interface * {
@retrofit2.http.* <methods>;
}

# ── Gson ──────────────────────────────────────────────────────────────────────
# Maps JSON keys to field names — obfuscation breaks this
-keepattributes *Annotation*
-keepclassmembers class org.onebusaway.vehiclepositions.data.remote.** {
<fields>;
}
-keep class org.onebusaway.vehiclepositions.data.remote.** { *; }
-dontwarn sun.misc.**
-keep class com.google.gson.** { *; }
-keep class * implements com.google.gson.TypeAdapterFactory
-keep class * implements com.google.gson.JsonSerializer
-keep class * implements com.google.gson.JsonDeserializer

# ── OkHttp ────────────────────────────────────────────────────────────────────
-dontwarn okhttp3.**
-dontwarn okio.**
-keep class okhttp3.** { *; }
-keep interface okhttp3.** { *; }

# ── Room ──────────────────────────────────────────────────────────────────────
# Annotation processor generates code at compile time; R8 doesn't need to touch it
-keep class androidx.room.** { *; }
-dontwarn androidx.room.paging.**

# ── Hilt ──────────────────────────────────────────────────────────────────────
-keep class dagger.hilt.** { *; }
-keep class javax.inject.** { *; }
-keep class * extends dagger.hilt.android.internal.managers.ActivityComponentManager { *; }

# ── Coroutines ────────────────────────────────────────────────────────────────
# Uses volatile fields internally for state management
-keepclassmembernames class kotlinx.** {
volatile <fields>;
}

# ── Google Play Services ───────────────────────────────────────────────────────
-keep class com.google.android.gms.** { *; }
-dontwarn com.google.android.gms.**

# ── DataStore ─────────────────────────────────────────────────────────────────
-keep class androidx.datastore.** { *; }
54 changes: 54 additions & 0 deletions android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">

<!-- Location Permissions -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />

<!-- Foreground Service -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission
android:name="android.permission.FOREGROUND_SERVICE_LOCATION"
tools:ignore="ProtectedPermissions" />

<!-- Notifications (Android 13+) -->
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />

<!-- Internet -->
<uses-permission android:name="android.permission.INTERNET" />

<application
android:name=".MyApp"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.VehiclePositions"
android:usesCleartextTraffic="false"
android:networkSecurityConfig="@xml/network_security_config">

<!-- Google Maps API Key -->
<meta-data
android:name="com.google.android.maps.v2.API_KEY"
android:value="${MAPS_API_KEY}" />

<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>

<service
android:name=".service.LocationForegroundService"
android:foregroundServiceType="location"
android:exported="false" />

</application>

</manifest>
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package org.onebusaway.vehiclepositions

import android.os.Bundle
import androidx.activity.ComponentActivity
import dagger.hilt.android.AndroidEntryPoint

// Full implementation added in Screen 1 PR
@AndroidEntryPoint
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package org.onebusaway.vehiclepositions

import android.app.Application
import dagger.hilt.android.HiltAndroidApp

@HiltAndroidApp
class MyApp : Application()
Loading
Loading