SolPan is built with privacy and security as first-class concerns. This document outlines the security architecture and best practices implemented.
- GPS Coordinates: User's real-time location
- Handling: Only collected when app has permission
- Storage: Never persisted to disk
- Transmission: Only to Google Play Services (on-device)
- Retention: Transient (current session only)
- Selected Tilt Mode: User preference
- Handling: User-selected value
- Storage: Encrypted via DataStore
- Transmission: Not transmitted
- Retention: Until user clears app data
- Accelerometer/Magnetometer: Device orientation
- Handling: Processed locally
- Storage: Never stored
- Transmission: Not transmitted
- Retention: Current event only
<!-- AndroidManifest.xml -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />Justification: Accurate GPS location is core to solar panel orientation calculation. No alternative exists.
Runtime Permission Handling:
if (ActivityCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION)
!= PackageManager.PERMISSION_GRANTED
) {
ActivityCompat.requestPermissions(
activity,
arrayOf(
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION
),
REQUEST_LOCATION_PERMISSION
)
}<uses-permission android:name="android.permission.VIBRATE" />Justification: Haptic feedback improves UX but app functions without it.
❌ Not used:
- INTERNET (no network calls)
- CAMERA (no camera access needed)
- CONTACTS (no contact access)
- READ_EXTERNAL_STORAGE (no file access)
Always check before accessing gated APIs:
private fun startLocationUpdates() {
if (ActivityCompat.checkSelfPermission(
context,
Manifest.permission.ACCESS_FINE_LOCATION
) != PackageManager.PERMISSION_GRANTED
) {
return // Don't proceed without permission
}
fusedLocationClient.requestLocationUpdates(...)
}All persistent data is encrypted:
// Production: Encrypted
val preferencesDataStore = encryptedDataStore(
filename = "user_prefs",
encryptionProvider = MasterKeysKt.getMasterKey(context)
)
// Non-sensitive: User selection, not location
val preferences = userPreferencesRepository.userPreferencesFlow
.map { it.selectedTiltMode }
.collect { ... }- ❌ GPS coordinates (transient only)
- ❌ Sensor readings (not persisted)
- ❌ Orientation history (real-time only)
- ❌ API keys (in BuildConfig)
- ❌ Analytics session IDs (transmitted securely)
<!-- AndroidManifest.xml -->
<application android:allowBackup="false" ... >Disables cloud backup to prevent sensitive data exposure.
SolPan makes no direct HTTP requests. All external services go through:
- Google Play Services: Uses Google's infrastructure (encrypted)
- Firebase: Uses Google's infrastructure (encrypted)
If future versions add network calls:
<!-- network_security_config.xml -->
<domain-config>
<domain includeSubdomains="true">example.com</domain>
<pin-set expiration="2026-12-31">
<pin digest="SHA-256">BASE64_OF_CERT_HASH</pin>
</pin-set>
</domain-config>- Minimum: TLS 1.2
- Preferred: TLS 1.3
- Cleartext: Disabled (HTTPS only)
// Latitude validation
fun isValidLatitude(lat: Double): Boolean {
return lat in -90.0..90.0
}
// Longitude validation
fun isValidLongitude(lon: Double): Boolean {
return lon in -180.0..180.0
}
// Always validate location before use
val location = Location("").apply {
latitude = newLat
longitude = newLon
}
if (isValidLatitude(newLat) && isValidLongitude(newLon)) {
// Safe to use
}Never leak sensitive info in exceptions:
// ❌ Bad: Logs location
try {
processLocation(location)
} catch (e: Exception) {
Log.e("SolPan", "Failed with location: $location") // Leaks GPS data!
}
// ✅ Good: Generic error
try {
processLocation(location)
} catch (e: Exception) {
Log.e("SolPan", "Location processing failed") // Generic, safe
// Optionally send to analytics with location stripped
}-keep class app.mobilemobile.solpan.** { *; }
-keepattributes SourceFile,LineNumberTable
-renamesourcefileattribute SourceFile
-dontshrink # Keep debugging info if neededPrevents reverse engineering of sensitive logic.
All dependencies vetted for:
- Active maintenance
- Known vulnerabilities (Dependabot checks)
- Privacy implications
| Library | Version | Security Status |
|---|---|---|
| Jetpack Compose | 2026.04.00 | ✅ Actively maintained |
| Navigation 3 | 1.2.0-alpha01 | ✅ Google (secure) |
| DataStore | 1.3.0-alpha07 | ✅ Encrypted by default |
| Accompanist | 0.37.3 | ✅ Community maintained |
| Firebase | 34.12.0 | ✅ Google services |
| Commons Suncalc | 3.11 | ✅ Well-established library |
Configured with GitHub's Dependabot:
- Checks daily for vulnerabilities
- Creates PRs for security updates
- Automates patching process
// Exclude PII from analytics
Firebase.analytics.setUserProperty("privacy_mode", "strict")Tracked Events (non-identifying):
- App opened
- Tutorial completed
- Permission denied/granted
- Tilt mode selected
- App closed
NOT Tracked:
- GPS coordinates
- User ID (when not explicitly set)
- Device identifiers beyond Firebase's automatic ID
// Disable automatic error tracking if sensitive
Firebase.crashlytics.isCrashlyticsCollectionEnabled = trueStack traces are secure and Google-managed.
val trace = Firebase.performance.newTrace("solar_calc")
trace.start()
SolarCalculator.calculatePosition(...)
trace.stop()Only non-PII metrics recorded (latency, throughput).
<application
android:allowBackup="false"
android:debuggable="false" <!-- Production only -->
android:usesCleartextTraffic="false"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
...
>if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
// Use runtime permissions API
requestPermissions(...)
} else {
// Manifest permissions only (legacy)
}All UI elements have descriptions:
Icon(
imageVector = Icons.Default.LocationOn,
contentDescription = "Current location: 51.5074°N, 0.1278°W", // Screen reader
)Screen readers get full semantic descriptions without exposing raw coordinates to attackers.
@Test
fun `validateLatitude rejects invalid values`() {
assertFalse(isValidLatitude(91.0))
assertFalse(isValidLatitude(-91.0))
}
@Test
fun `locationRepository never persists GPS data`() {
val repo = DefaultLocationRepository()
repo.updateLocation(testLocation)
assertNull(repo.getStoredLocation()) // No persistence
}Permission Testing:
- Deny location permission
- Verify app doesn't crash
- Verify "Permission Denied" message shown
- Verify user can request again
Data Verification:
- Grant location permission
- Use app
- Check device storage (should be empty except preferences)
- Check network (no outgoing location data)
android {
buildTypes {
release {
minifyEnabled true
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
debuggable = false // NO debugging in production
}
}
}- Signed with private key (kept secure)
- Published via Google Play (automatic security scanning)
- Version code incremented with each release
- Security patch releases published immediately
- Immediate: Create security patch
- Within 24h: Test on multiple devices
- Within 48h: Submit to Play Store
- Notify Users: In-app message if needed
- Post-Mortem: Document learning
If location data ever exposed:
- Disable location collection immediately
- Notify users via Play Store
- Offer app reset
- Improve validation/security
- Third-party audit
✅ Compliant:
- No personal data collection beyond what's needed
- User can opt out (deny location permission)
- No tracking of individuals
- No data shared with third parties
- Automatic data deletion on uninstall
✅ Transparency:
- Privacy policy available
- Clear permission requests
- Settings for data preferences
✅ Compliant:
- Minimal data collection
- Right to delete (clear app data)
- Right to know (open source)
- Right to opt-out (permission denial)
- Review Permissions: Only enable location when using app
- Check Battery Optimization: Allow Play Services location in background settings if desired
- Verify App Version: Always update from official Play Store
- Check OS Updates: Keep Android OS current
- Monitor Battery: Unusual drain may indicate permission abuse
- Add secure enclave support (Keystore for secrets)
- Implement certificate pinning (if network added)
- Add runtime security monitoring
- Implement obfuscation for sensitive algorithms
- Add integrity verification (SafetyNet attestation)
- Android Security & Privacy Documentation
- OWASP Mobile Security Guidelines
- Google Play Security Best Practices
- GDPR Compliance Guide
- Firebase Security Guide
SolPan maintains strong security and privacy through:
- Minimal data collection (location only, when needed)
- No persistence of sensitive data
- Encrypted storage of user preferences
- Runtime permission checks on all sensitive APIs
- Third-party vetting and vulnerability scanning
- Secure transport (Google Play Services, Firebase)
- Clear privacy policy and compliance
- Regular security updates and monitoring
Users can trust SolPan with their location data—it's only used locally and never stored or shared.