This document provides a comprehensive analysis of all permissions used in the Android Tracker app, including their purpose, implementation patterns, code usage analysis, and recommendations for optimization. This enhanced version includes detailed code usage patterns and identifies potential permission handling gaps.
- Manifest Location:
app/src/main/AndroidManifest.xml:16 - Purpose: Precise location tracking for GPS functionality
- Critical: Yes - Core functionality
- API Level: All
- Code Usage:
- Check:
Context.hasLocationPermissionextension inContextExtensions.kt:167 - Implementation: Used in
FusedLocationCollectionTrigger.kt:24for location callbacks - Validation:
LocationAndSensorsManager.ktchecks permission before requesting location updates - Service:
TrackerService.ktvalidates permissions array before starting - Map:
MapSensorController.kt:115uses with location client for map updates
- Check:
- Manifest Location:
app/src/main/AndroidManifest.xml:17 - Purpose: Network-based location access
- Critical: Medium - Fallback for fine location
- API Level: All
- Code Usage: Implicitly used alongside fine location permission
- Manifest Location:
app/src/main/AndroidManifest.xml:18 - Purpose: Location access when app is in background
- Critical: Yes - For automatic tracking
- API Level: API 29+ (Android 10+)
- Code Usage:
- Check:
Context.hasBackgroundLocationPermissionextension inContextExtensions.kt:171 - Request:
TrackerFirstRun.kt:53handles conditional background location request for API 30+ - Warning:
TrackerPreferencePage.kt:73shows warning when auto-tracking enabled without background permission - Rationale: Custom rationale dialog in
PermissionManager.ktexplains need for continuous tracking
- Check:
- Manifest Location:
app/src/main/AndroidManifest.xml:22 - Purpose: Detect user activity (walking, driving, etc.)
- Critical: Medium - Enhanced tracking accuracy
- API Level: API 29+ (Android 10+)
- Code Usage:
- Check:
Context.hasActivityPermissionextension inContextExtensions.kt:175 - Request:
TrackerFirstRun.kt:154conditionally requests based on activity settings - Manager:
PermissionManager.kt:123has dedicatedcheckActivityPermissions()method - Service:
ActivityWatcherService.ktuses for background activity monitoring - API:
ActivityRequestManagerhandles activity detection via Google Play Services
- Check:
- Manifest Location:
app/src/main/AndroidManifest.xml:19 - Purpose: Check network connectivity status
- Critical: Medium - For upload/sync features
- API Level: All
- Code Usage: Used via
Context.connectivityManagerextension for network checks
- Manifest Location:
app/src/main/AndroidManifest.xml:20 - Purpose: Access telephony information for dual-SIM support
- Critical: Low - Enhanced cellular data collection
- API Level: All
- Code Usage:
- Check:
Context.hasReadPhonePermissionextension inContextExtensions.kt:179 - Usage:
CellDataProducer.kt:45checks permission before accessing dual-SIM data - Request:
TrackerFirstRun.kt:160conditionally requests when dual-SIM detected - Utility:
TelephonyUtils.ktprovides phone count detection methods - Conditional: Only accesses advanced telephony features when permission granted
- Check:
- Manifest Location:
app/src/main/AndroidManifest.xml:21 - Purpose: Keep CPU awake during tracking sessions
- Critical: Yes - Prevents sleep during tracking
- API Level: All
- Code Usage: Used via
Context.powerManagerextension for wake lock management
- Manifest Location:
app/src/main/AndroidManifest.xml:23 - Purpose: Access WiFi network information
- Critical: Medium - WiFi-based location
- API Level: All
- Code Usage:
- Producer:
WifiDataProducer.kt:76usescontext.wifiManagerfor scan operations - Component:
WifiTrackerComponent.ktprocesses WiFi scan data - Data:
CollectionTempData.kt:102validates WiFi permissions before data access - Info:
WifiInfo.kthandles signal level calculations and channel width
- Producer:
- Manifest Location:
tracker/src/main/AndroidManifest.xml:5 - Purpose: Enable/disable WiFi scanning
- Critical: Low - WiFi scan control
- API Level: All
- Code Usage:
WifiDataProducer.kt:55callswifiManager.startScan()with deprecation suppression
- Manifest Location:
app/src/main/AndroidManifest.xml:24 - Purpose: Start services after device boot
- Critical: Medium - Auto-start functionality
- API Level: All
- Code Usage: Enables automatic service restart via
ActivityWatcherService.kt
- Manifest Location:
app/src/main/AndroidManifest.xml:25 - Purpose: Run long-running background services
- Critical: Yes - Core tracking functionality
- API Level: API 28+ (Android 9+)
- Code Usage:
- TrackerService: Main tracking service runs as foreground service
- ActivityWatcherService: Background activity monitoring service
- API:
TrackerServiceApi.kt:20usesstartForegroundServiceextension - Extensions:
ContextExtensions.kt:108provides typed service start methods
- Manifest Location:
app/src/main/AndroidManifest.xml:26 - Purpose: Display notifications to user
- Critical: Yes - User feedback and status
- API Level: API 33+ (Android 13+)
- Code Usage:
AppFirstRun.kt:17requests notification permission on first launch
- Manifest Location:
impexp/src/main/AndroidManifest.xml:4 - Purpose: Export data to external storage
- Critical: Medium - Data export functionality
- API Level: Up to API 29 (scoped storage replaces this)
- Code Usage: Used in import/export module for data backup operations
File: sbase/src/main/java/com/adsamcik/tracker/shared/base/extension/ContextExtensions.kt
// Core permission checking function
fun Context.hasSelfPermission(permission: String): Boolean =
ContextCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED
// Convenience properties for common permissions
inline val Context.hasLocationPermission: Boolean
get() = hasSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION)
inline val Context.hasBackgroundLocationPermission: Boolean
get() = Build.VERSION.SDK_INT < Build.VERSION_CODES.Q ||
hasSelfPermission(Manifest.permission.ACCESS_BACKGROUND_LOCATION)
inline val Context.hasActivityPermission: Boolean
get() = Build.VERSION.SDK_INT < Build.VERSION_CODES.Q ||
hasSelfPermission(Manifest.permission.ACTIVITY_RECOGNITION)
inline val Context.hasReadPhonePermission: Boolean
get() = hasSelfPermission(Manifest.permission.READ_PHONE_STATE)System Service Extensions:
inline val Context.telephonyManager: TelephonyManager get() = getSystemServiceTyped(Context.TELEPHONY_SERVICE)
inline val Context.wifiManager: WifiManager get() = getSystemServiceTyped(Context.WIFI_SERVICE)
inline val Context.locationManager: LocationManager get() = getSystemServiceTyped(Context.LOCATION_SERVICE)File: tracker/src/main/java/com/adsamcik/tracker/tracker/data/collection/CollectionTempData.kt
The app implements centralized permission validation for data access:
fun getWifiData(component: TrackerDataConsumerComponent): WifiScanData {
validatePermissions(component, TrackerComponentRequirement.WIFI)
return get(TrackerComponentRequirement.WIFI)
}
fun getCellData(component: TrackerDataConsumerComponent): CellScanData {
validatePermissions(component, TrackerComponentRequirement.CELL)
return get(TrackerComponentRequirement.CELL)
}File: sutils/src/main/java/com/adsamcik/tracker/shared/utils/permission/PermissionManager.kt
Central permission management using Dexter library:
// Main permission check with rationale dialog
fun checkPermissionsWithRationaleDialog(permissionRequest: PermissionRequest)
// Specialized methods for common permissions
fun checkActivityPermissions(context: Context, callback: PermissionResultCallback)
// Default rationale dialog with Material Design
private fun defaultDialog(context: Context, token: Token, permissions: Collection<PermissionData>)File: sutils/src/main/java/com/adsamcik/tracker/shared/utils/permission/PermissionRequest.kt
Builder pattern for permission requests:
class PermissionRequest {
class Builder {
fun permission(permission: PermissionData): Builder
fun permissions(permissions: Collection<PermissionData>): Builder
fun onRationale(callback: RationaleCallback): Builder
fun onResult(callback: PermissionResultCallback): Builder
}
companion object {
fun with(activity: FragmentActivity): Builder
fun with(context: Context): Builder
fun from(request: PermissionRequest): Builder // Copy existing request
}
}File: tracker/src/main/java/com/adsamcik/tracker/tracker/module/TrackerFirstRun.kt
Smart permission requesting based on device capabilities and user preferences:
private fun trackingPermissionRequest(context: Context, onResult: PermissionResultCallback) {
val permissionsList = mutableListOf<PermissionData>()
// Only request activity permission on API 29+ if feature enabled
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q &&
preferences.getBooleanRes(R.string.settings_activity_enabled_key, ...)) {
permissionsList.add(PermissionData(Manifest.permission.ACTIVITY_RECOGNITION) { ... })
}
// Only request phone permission for dual-SIM devices
if (TelephonyUtils.getPhoneCount(context.telephonyManager) > 1 &&
preferences.getBooleanRes(R.string.settings_cell_enabled_key, ...)) {
permissionsList.add(PermissionData(Manifest.permission.READ_PHONE_STATE) { ... })
}
}File: tracker/src/main/java/com/adsamcik/tracker/tracker/component/TrackerTimerManager.kt
Component-based permission validation:
fun checkSelectedPermissions(context: Context, callback: PermissionResultCallback) {
val selected = getSelected(context)
val requiredPermissions = selected.requiredPermissions.map {
PermissionData(it) { context ->
val timerName = context.getString(selected.titleRes)
context.getString(R.string.permissions_tracker_timer_message, timerName)
}
}
if (requiredPermissions.isEmpty()) {
callback(PermissionRequestResult(emptyList(), emptyList()))
} else {
PermissionManager.checkPermissionsWithRationaleDialog(
PermissionRequest.with(context)
.permissions(requiredPermissions)
.onResult(callback)
.build()
)
}
}File: tracker/src/main/java/com/adsamcik/tracker/tracker/component/producer/CellDataProducer.kt
Conditional API usage based on permissions:
override suspend fun onDataUpdated(tempData: CollectionTempData) {
val context = requireNotNull(context)
if (!Assist.isAirplaneModeEnabled(context)) {
val telephonyManager = requireNotNull(telephonyManager)
val scanData = if (context.hasReadPhonePermission) {
val subscriptionManager = requireNotNull(subscriptionManager)
// Requires suppress due to lint not recognizing extension property
@Suppress("MissingPermission")
getScanData(telephonyManager, subscriptionManager)
} else {
getScanData(telephonyManager) // Limited functionality without permission
}
if (scanData != null) {
tempData.setCellData(scanData)
}
}
}
@RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
private fun getScanData(telephonyManager: TelephonyManager, subscriptionManager: SubscriptionManager)File: tracker/src/main/java/com/adsamcik/tracker/tracker/component/producer/WifiDataProducer.kt
WiFi scanning with proper permission handling and version compatibility:
@Synchronized
private fun requestScan() {
val now = SystemClock.elapsedRealtime()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
// Rate-limited scanning on Android 9+ (4 scans per 2 minutes)
if (now - lastScanRequest > Time.SECOND_IN_MILLISECONDS * 15 &&
(scanTime == -1L || now - scanTimeRelative > Time.SECOND_IN_MILLISECONDS * 10)) {
@Suppress("deprecation")
isScanRequested = wifiManager.startScan()
lastScanRequest = now
}
} else {
if (!isScanRequested) {
@Suppress("deprecation")
wifiManager.startScan()
lastScanRequest = now
}
}
}File: map/src/main/java/com/adsamcik/tracker/map/presentation/sensors/LocationAndSensorsManager.kt
Modern Flow-based location handling with permission checks:
@RequiresPermission(anyOf = [
Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.ACCESS_FINE_LOCATION
])
fun locationUpdates(): Flow<Location> = callbackFlow {
if (!context.hasLocationPermission) {
close()
return@callbackFlow
}
val locationClient = LocationServices.getFusedLocationProviderClient(context)
// Implementation with proper permission handling
}File: sutils/src/main/java/com/adsamcik/tracker/shared/utils/fragment/CorePermissionFragment.kt
Modern Activity Result API integration:
abstract class CorePermissionFragment : CoreUIFragment() {
private var currentPermissionRequest: PermissionRequest? = null
private val permissionLauncher: ActivityResultLauncher<Array<String>> =
registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { permissions ->
val request = currentPermissionRequest ?: return@registerForActivityResult
currentPermissionRequest = null
val permissionArray = permissions.keys.toTypedArray()
val grantResults = permissions.values.map { if (it) 0 else -1 }.toIntArray()
val result = PermissionRequestResult.newFromResult(permissionArray, grantResults, request)
request.resultCallback.invoke(result)
}
fun requestPermissions(request: PermissionRequest) {
require(request.permissionList.isNotEmpty())
require(currentPermissionRequest == null) { "Another permission request is already in progress" }
currentPermissionRequest = request
val permissionNames = request.permissionList.map { it.name }.toTypedArray()
permissionLauncher.launch(permissionNames)
}
}Issue: TrackerService.kt validates permissions array but doesn't handle individual permission failures gracefully.
Location: tracker/src/main/java/com/adsamcik/tracker/tracker/service/TrackerService.kt
Recommendation: Implement per-component permission checking with graceful degradation.
// Current: hasSelfPermissions(permissionList) - all or nothing
// Suggested: Individual component enabling based on available permissionsGap: MapSensorController.kt:115 accesses location without recent permission recheck.
Location: map/src/main/java/com/adsamcik/tracker/map/MapSensorController.kt
Recommendation: Add runtime permission validation before each location request.
// Add permission recheck before location requests
if (context.hasLocationPermission) {
@Suppress("MissingPermission")
locationClient.requestLocationUpdates(...)
}Opportunity: CorePermissionFragment.kt implements Activity Result API but isn't used consistently.
Recommendation: Migrate all permission requests to modern API for better user experience.
Benefits: Better user experience, more reliable permission handling, future Android compatibility.
Issue: WifiDataProducer.kt uses deprecated WiFi scanning methods.
Location: tracker/src/main/java/com/adsamcik/tracker/tracker/component/producer/WifiDataProducer.kt
Recommendation: Implement new WiFi scanning APIs for Android 10+ with proper permission handling.
// Current: @Suppress("deprecation") wifiManager.startScan()
// Suggested: Use WifiManager.isScanAlwaysAvailable() and proper rate limitingGap: Background location request timing could be optimized based on user interaction patterns.
Location: tracker/src/main/java/com/adsamcik/tracker/tracker/module/TrackerFirstRun.kt
Recommendation: Implement smart background permission prompting based on usage analytics.
Strength: CollectionTempData.kt properly validates permissions before data access.
Opportunity: Extend this pattern to all data access points consistently.
Gap: Permission checks happen repeatedly without caching. Recommendation: Implement permission state caching with proper invalidation.
Strength: TestAssist.kt provides UI automation for permission dialogs in tests.
Opportunity: Expand test coverage for permission scenarios.
The app uses a sophisticated permission management system with:
- Dexter Library: Simplifies permission handling with callbacks and rationale dialogs
- Centralized Management:
PermissionManagerclass handles all requests with consistent UI - Contextual Rationales: User-friendly explanations for each permission type
- Graceful Degradation: App functions with reduced features when permissions denied
- Smart Requesting: Conditional permission requests based on device capabilities and user settings
- Extension Properties: Convenient permission checking via Context extensions
- Component Validation: Data components validate permissions before access
- Modern API Support: Activity Result API integration for better user experience
- First Run: Sequential permission requests with educational dialogs
- Runtime Checks: Before accessing protected APIs using extension properties
- Rationale Dialogs: Explain why permissions are needed with custom messages
- Graceful Fallback: Disable features when permissions unavailable
- Component Validation: Data components validate permissions before access
- Service Integration: Foreground services properly handle permission requirements
- Settings Integration: Permission status reflected in app settings with re-request options
- Consistent API: Extension properties provide consistent permission checking
- Proper Annotations:
@RequiresPermissionannotations document API requirements - Graceful Degradation: Conditional feature usage based on permission availability
- Modern Patterns: Flow-based APIs with proper permission integration
- Comprehensive Coverage: All dangerous permissions properly handled
- Permission Caching: Repeated permission checks could be optimized
- Error Recovery: Better handling of permission revocation during app execution
- User Education: More contextual permission education opportunities
- Testing Coverage: More comprehensive permission testing scenarios
-
Essential Core (request immediately):
- ACCESS_FINE_LOCATION
- FOREGROUND_SERVICE
- POST_NOTIFICATIONS
-
Enhanced Features (request when needed):
- ACCESS_BACKGROUND_LOCATION
- ACTIVITY_RECOGNITION
- READ_PHONE_STATE
-
Optional Features (request on feature use):
- WRITE_EXTERNAL_STORAGE
- CHANGE_WIFI_STATE
- Clear explanations of benefits using real user scenarios
- Progressive disclosure of advanced features with visual examples
- Visual demonstrations of permission impact on app functionality
- Option to enable permissions later in settings with guided flow
- Privacy-first messaging emphasizing local data storage
- Just-in-Time: Request permissions when user accesses related features
- Contextual: Explain permissions in context of user goals
- Progressive: Start with core permissions, add enhancements later
- Recoverable: Allow easy re-enabling of previously denied permissions
- Implement consistent permission rechecking before API access
- Migrate to Activity Result API across all permission requests
- Add per-component permission handling in TrackerService
- Optimize WiFi scanning permission usage for modern Android versions
- Implement permission state caching with proper invalidation
- Create permission education screens with visual examples
- Implement smart permission re-prompting based on usage patterns
- Add granular feature toggles in settings with permission status
- Implement permission analytics to track grant/deny rates
- Add contextual permission requests based on user behavior
- Implement runtime permission degradation with feature fallbacks
- Add A/B testing for permission request flows
- Create contextual permission requesting based on user behavior analytics
- Implement permission impact visualization in settings
- Advanced privacy controls with data usage transparency
- API 29+: Background location requires separate request after foreground location
- API 33+: Notification permission must be explicitly requested
- Scoped Storage: WRITE_EXTERNAL_STORAGE becoming obsolete, migrate to SAF
- WiFi Changes: Android 10+ restricts WiFi scanning, implement new APIs
- Activity Recognition: API 29+ requires runtime permission
- Foreground Services: Enhanced restrictions in Android 12+
- All location data remains on device by default
- No data transmission without explicit user consent
- Comprehensive privacy policy covers all permissions and data usage
- Permission rationales clearly explain data usage and benefits
- Users can fully control data collection granularity
The app successfully uses modern Android file access patterns:
- Document Provider API for file imports/exports
- FileProvider for secure file sharing
- No legacy storage permissions required
- Scoped storage compliance
- Location permissions (privacy concerns, battery impact, user scrutiny)
- Background location (high privacy sensitivity, Play Store review focus)
- READ_PHONE_STATE (access to device identifiers, user confusion)
- ACTIVITY_RECOGNITION (movement pattern tracking, contextual privacy)
- Storage permissions (file system access, data security concerns)
- WiFi permissions (network scanning capabilities, location inference)
- Network state, notification permissions (expected app functionality)
- System permissions (wake lock, boot completed - standard for tracking apps)
- Foreground service (user-visible operation, expected for tracking)
- Request only permissions actually needed for current functionality
- Implement feature-gated permission requests
- Provide clear opt-out mechanisms for all optional features
- Regular audit of permission usage vs. declared permissions
- Local-first data storage architecture
- Encryption for sensitive stored data
- Secure file sharing via FileProvider
- No unnecessary data collection or retention
The Tracker app demonstrates excellent technical implementation of Android's permission system with proper API usage, graceful degradation, and modern development patterns. The code shows sophisticated permission handling with centralized management, contextual validation, and user-friendly rationale dialogs.
Key Strengths:
- Comprehensive permission validation architecture
- Smart conditional permission requesting
- Modern API integration with fallback support
- Proper component-level permission checking
- Excellent code organization and reusability
Primary Opportunities:
- Enhanced user education and permission timing
- Migration to consistently use modern Activity Result API
- Improved permission caching and performance optimization
- More contextual permission requests based on user workflow
The recommended improvements focus on user experience enhancement while maintaining the solid technical foundation already established. The phased implementation approach will improve permission grant rates while ensuring continued compliance with Android and Google Play requirements.