Skip to content

Latest commit

 

History

History
603 lines (494 loc) · 26 KB

File metadata and controls

603 lines (494 loc) · 26 KB

Android Tracker Permissions Research

Executive Summary

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.

Permission Inventory

Location Permissions

ACCESS_FINE_LOCATION

  • 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.hasLocationPermission extension in ContextExtensions.kt:167
    • Implementation: Used in FusedLocationCollectionTrigger.kt:24 for location callbacks
    • Validation: LocationAndSensorsManager.kt checks permission before requesting location updates
    • Service: TrackerService.kt validates permissions array before starting
    • Map: MapSensorController.kt:115 uses with location client for map updates

ACCESS_COARSE_LOCATION

  • 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

ACCESS_BACKGROUND_LOCATION

  • 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.hasBackgroundLocationPermission extension in ContextExtensions.kt:171
    • Request: TrackerFirstRun.kt:53 handles conditional background location request for API 30+
    • Warning: TrackerPreferencePage.kt:73 shows warning when auto-tracking enabled without background permission
    • Rationale: Custom rationale dialog in PermissionManager.kt explains need for continuous tracking

Activity & Recognition Permissions

ACTIVITY_RECOGNITION

  • 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.hasActivityPermission extension in ContextExtensions.kt:175
    • Request: TrackerFirstRun.kt:154 conditionally requests based on activity settings
    • Manager: PermissionManager.kt:123 has dedicated checkActivityPermissions() method
    • Service: ActivityWatcherService.kt uses for background activity monitoring
    • API: ActivityRequestManager handles activity detection via Google Play Services

Network & State Permissions

ACCESS_NETWORK_STATE

  • 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.connectivityManager extension for network checks

READ_PHONE_STATE

  • 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.hasReadPhonePermission extension in ContextExtensions.kt:179
    • Usage: CellDataProducer.kt:45 checks permission before accessing dual-SIM data
    • Request: TrackerFirstRun.kt:160 conditionally requests when dual-SIM detected
    • Utility: TelephonyUtils.kt provides phone count detection methods
    • Conditional: Only accesses advanced telephony features when permission granted

System & Hardware Permissions

WAKE_LOCK

  • 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.powerManager extension for wake lock management

ACCESS_WIFI_STATE

  • 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:76 uses context.wifiManager for scan operations
    • Component: WifiTrackerComponent.kt processes WiFi scan data
    • Data: CollectionTempData.kt:102 validates WiFi permissions before data access
    • Info: WifiInfo.kt handles signal level calculations and channel width

CHANGE_WIFI_STATE

  • 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:55 calls wifiManager.startScan() with deprecation suppression

Service & Notification Permissions

RECEIVE_BOOT_COMPLETED

  • 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

FOREGROUND_SERVICE

  • 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:20 uses startForegroundService extension
    • Extensions: ContextExtensions.kt:108 provides typed service start methods

POST_NOTIFICATIONS

  • 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:17 requests notification permission on first launch

Storage & Data Permissions

WRITE_EXTERNAL_STORAGE

  • 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

Code Usage Analysis

Permission Checking Utilities

ContextExtensions.kt

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)

CollectionTempData.kt Permission Validation

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)
}

Permission Request Management

PermissionManager.kt

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>)

PermissionRequest.kt System

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
    }
}

TrackerFirstRun.kt

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) { ... })
    }
}

TrackerTimerManager.kt

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()
        )
    }
}

Data Producers & Permission Handling

CellDataProducer.kt

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)

WifiDataProducer.kt

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
        }
    }
}

LocationAndSensorsManager.kt

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
}

Modern Permission API Integration

CorePermissionFragment.kt

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)
    }
}

Permission Handling Gaps & Optimization Opportunities

1. Service Permission Validation

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 permissions

2. Location Permission Timing

Gap: 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(...)
}

3. Modern Permission API Migration

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.

4. WiFi Permission Optimization

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 limiting

5. Background Permission Strategy

Gap: 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.

6. Component Permission Validation

Strength: CollectionTempData.kt properly validates permissions before data access. Opportunity: Extend this pattern to all data access points consistently.

7. Permission Caching

Gap: Permission checks happen repeatedly without caching. Recommendation: Implement permission state caching with proper invalidation.

8. Testing Support

Strength: TestAssist.kt provides UI automation for permission dialogs in tests. Opportunity: Expand test coverage for permission scenarios.

Permission Request Strategy

Current Implementation Architecture

The app uses a sophisticated permission management system with:

  1. Dexter Library: Simplifies permission handling with callbacks and rationale dialogs
  2. Centralized Management: PermissionManager class handles all requests with consistent UI
  3. Contextual Rationales: User-friendly explanations for each permission type
  4. Graceful Degradation: App functions with reduced features when permissions denied
  5. Smart Requesting: Conditional permission requests based on device capabilities and user settings
  6. Extension Properties: Convenient permission checking via Context extensions
  7. Component Validation: Data components validate permissions before access
  8. Modern API Support: Activity Result API integration for better user experience

Permission Flow Architecture

  1. First Run: Sequential permission requests with educational dialogs
  2. Runtime Checks: Before accessing protected APIs using extension properties
  3. Rationale Dialogs: Explain why permissions are needed with custom messages
  4. Graceful Fallback: Disable features when permissions unavailable
  5. Component Validation: Data components validate permissions before access
  6. Service Integration: Foreground services properly handle permission requirements
  7. Settings Integration: Permission status reflected in app settings with re-request options

Code Quality Observations

Strengths

  • Consistent API: Extension properties provide consistent permission checking
  • Proper Annotations: @RequiresPermission annotations 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

Areas for Improvement

  • 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

UX Recommendations

Permission Grouping Strategy

  1. Essential Core (request immediately):

    • ACCESS_FINE_LOCATION
    • FOREGROUND_SERVICE
    • POST_NOTIFICATIONS
  2. Enhanced Features (request when needed):

    • ACCESS_BACKGROUND_LOCATION
    • ACTIVITY_RECOGNITION
    • READ_PHONE_STATE
  3. Optional Features (request on feature use):

    • WRITE_EXTERNAL_STORAGE
    • CHANGE_WIFI_STATE

Educational Approach

  • 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

Permission Timing Optimization

  • 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

Implementation Roadmap

Phase 1: Core Improvements (Immediate)

  • 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

Phase 2: Enhanced UX (Short-term)

  • 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

Phase 3: Advanced Features (Long-term)

  • 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

Technical Notes

Android Version Considerations

  • 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+

Privacy Compliance

  • 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

File System Access Modernization

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

Risk Assessment

High Risk Permissions

  • 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)

Medium Risk Permissions

  • ACTIVITY_RECOGNITION (movement pattern tracking, contextual privacy)
  • Storage permissions (file system access, data security concerns)
  • WiFi permissions (network scanning capabilities, location inference)

Low Risk Permissions

  • 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)

Security Considerations

Permission Scope Minimization

  • 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

Data Protection

  • Local-first data storage architecture
  • Encryption for sensitive stored data
  • Secure file sharing via FileProvider
  • No unnecessary data collection or retention

Conclusion

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.