From db84c3f5548357b0125300358e6d9c0ae8f6dd07 Mon Sep 17 00:00:00 2001 From: Tomek Zebrowski Date: Sun, 1 Feb 2026 15:55:22 +0100 Subject: [PATCH 1/2] feat: add permissions setup window, --- .../org/obd/graphs/activity/MainActivity.kt | 9 +++ .../main/java/org/obd/graphs/Permissions.kt | 74 +++++++++++++++++++ .../obd/graphs/bl/gps/GpsMetricsEmitter.kt | 4 +- .../graphs/profile/DefaultProfileService.kt | 6 +- 4 files changed, 91 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/org/obd/graphs/activity/MainActivity.kt b/app/src/main/java/org/obd/graphs/activity/MainActivity.kt index b7327e8e..7a8701e4 100644 --- a/app/src/main/java/org/obd/graphs/activity/MainActivity.kt +++ b/app/src/main/java/org/obd/graphs/activity/MainActivity.kt @@ -41,6 +41,7 @@ import org.obd.graphs.BuildConfig import org.obd.graphs.ExceptionHandler import org.obd.graphs.MAIN_ACTIVITY_EVENT_DESTROYED import org.obd.graphs.MAIN_ACTIVITY_EVENT_PAUSE +import org.obd.graphs.Permissions import org.obd.graphs.R import org.obd.graphs.bl.datalogger.dataLogger import org.obd.graphs.bl.drag.dragRacingMetricsProcessor @@ -166,8 +167,10 @@ class MainActivity : displayAppSignature(this) navigateToLastVisitedScreen() + validatePermissions() } + override fun onResume() { super.onResume() screen.setupWindowManager(this) @@ -276,4 +279,10 @@ class MainActivity : } } } + + private fun validatePermissions() { + if (Permissions.isAnyPermissionMissing(this)) { + Permissions.showPermissionOnboarding(this) + } + } } diff --git a/common/src/main/java/org/obd/graphs/Permissions.kt b/common/src/main/java/org/obd/graphs/Permissions.kt index 14489598..a3926be1 100644 --- a/common/src/main/java/org/obd/graphs/Permissions.kt +++ b/common/src/main/java/org/obd/graphs/Permissions.kt @@ -34,6 +34,42 @@ private const val BLUETOOTH_REQUEST_CODE = 1002 private const val NOTIFICATION_REQUEST_CODE = 1003 object Permissions { + /** + * Returns TRUE if any required permission is missing. + * Reuses your existing individual checks. + */ + fun isAnyPermissionMissing(context: Context): Boolean { + // Reuse hasLocationPermissions + if (!hasLocationPermissions(context)) return true + + // Reuse hasNotificationPermissions + if (!hasNotificationPermissions(context)) return true + + // Reuse Bluetooth check + val btPerms = getBluetoothPermissions() + return !EasyPermissions.hasPermissions(context, *btPerms) + } + + fun showPermissionOnboarding(activity: Activity) { + val message = + """ + To provide full functionality, please allow the following in the next steps: + + • Location: To track your trip via GPS. + • Bluetooth: To connect to your OBD adapter. + • Notifications: To keep the logger running in the background. + """.trimIndent() + + androidx.appcompat.app.AlertDialog + .Builder(activity) + .setTitle("Setup Required") + .setMessage(message) + .setPositiveButton("Begin Setup") { _, _ -> + requestAll(activity) + }.setNegativeButton("Later", null) + .show() + } + /** * Returns TRUE if all required location permissions are granted. * Also performs a diagnostic check to warn if the user has selected "Approximate" location. @@ -165,4 +201,42 @@ object Permissions { } return perms.toTypedArray() } + + /** + * Aggregates and requests all required permissions for the application. + * Use this at app startup to minimize the number of pop-ups. + */ + private fun requestAll(activity: Activity) { + val perms = mutableListOf() + + perms.add(Manifest.permission.ACCESS_COARSE_LOCATION) + perms.add(Manifest.permission.ACCESS_FINE_LOCATION) + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + perms.add(Manifest.permission.BLUETOOTH_SCAN) + perms.add(Manifest.permission.BLUETOOTH_CONNECT) + } + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + perms.add(Manifest.permission.POST_NOTIFICATIONS) + } + + val missingPermissions = + perms.filter { + ContextCompat.checkSelfPermission(activity, it) != PackageManager.PERMISSION_GRANTED + } + + if (missingPermissions.isNotEmpty()) { + Log.i(TAG, "Requesting missing permissions: $missingPermissions") + + EasyPermissions.requestPermissions( + activity, + "This app requires Location, Bluetooth, and Notification permissions to function correctly.", + 1000, // Use a generic ALL_PERMISSIONS_REQUEST_CODE + *missingPermissions.toTypedArray(), + ) + } else { + Log.v(TAG, "All permissions already granted.") + } + } } diff --git a/datalogger/src/main/java/org/obd/graphs/bl/gps/GpsMetricsEmitter.kt b/datalogger/src/main/java/org/obd/graphs/bl/gps/GpsMetricsEmitter.kt index 3bd284d8..74ff10fd 100644 --- a/datalogger/src/main/java/org/obd/graphs/bl/gps/GpsMetricsEmitter.kt +++ b/datalogger/src/main/java/org/obd/graphs/bl/gps/GpsMetricsEmitter.kt @@ -94,7 +94,6 @@ internal class GpsMetricsEmitter : MetricsProcessor { } override fun onStopped() { - Log.i(TAG, "Stopping GPS updates") stopGpsUpdates() } @@ -147,6 +146,9 @@ internal class GpsMetricsEmitter : MetricsProcessor { private fun stopGpsUpdates() { try { + + Log.i(TAG, "Stopping GPS updates") + locationListener?.let { locationManager?.removeUpdates(it) } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { diff --git a/profile/src/main/java/org/obd/graphs/profile/DefaultProfileService.kt b/profile/src/main/java/org/obd/graphs/profile/DefaultProfileService.kt index ac475688..a82af806 100644 --- a/profile/src/main/java/org/obd/graphs/profile/DefaultProfileService.kt +++ b/profile/src/main/java/org/obd/graphs/profile/DefaultProfileService.kt @@ -164,7 +164,11 @@ internal class DefaultProfileService : } if (pref.startsWith("profile_") || pref == getInstallationVersion()) { - Log.v(PROFILE_AUTO_SAVER_LOG_TAG, "Skipping: $pref") + if (Log.isLoggable(PROFILE_AUTO_SAVER_LOG_TAG,Log.VERBOSE)) { + Log.v(PROFILE_AUTO_SAVER_LOG_TAG, "Skipping: $pref") + } else { + // + } } else { val profileName = getCurrentProfile() ss?.edit { From 4aebfe8c0ebcd0a6019a64d3a673a419703dff63 Mon Sep 17 00:00:00 2001 From: Tomek Zebrowski Date: Sun, 1 Feb 2026 16:16:53 +0100 Subject: [PATCH 2/2] feat: incorporate battery optimization permission --- .../org/obd/graphs/activity/MainActivity.kt | 25 --------- .../main/java/org/obd/graphs/Permissions.kt | 51 ++++++++++++++++--- 2 files changed, 44 insertions(+), 32 deletions(-) diff --git a/app/src/main/java/org/obd/graphs/activity/MainActivity.kt b/app/src/main/java/org/obd/graphs/activity/MainActivity.kt index 7a8701e4..34aa6a3d 100644 --- a/app/src/main/java/org/obd/graphs/activity/MainActivity.kt +++ b/app/src/main/java/org/obd/graphs/activity/MainActivity.kt @@ -20,15 +20,10 @@ import android.content.BroadcastReceiver import android.content.Context import android.content.Intent import android.content.res.Configuration -import android.net.Uri -import android.os.Build import android.os.Bundle -import android.os.PowerManager import android.os.StrictMode import android.os.StrictMode.ThreadPolicy import android.os.StrictMode.VmPolicy -import android.provider.Settings -import android.util.Log import android.view.View import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatActivity @@ -162,7 +157,6 @@ class MainActivity : setupLeftNavigationPanel() supportActionBar?.hide() setupMetricsProcessors() - setupBatteryOptimization() backupManager = BackupManager(this) displayAppSignature(this) @@ -261,25 +255,6 @@ class MainActivity : } } - private fun setupBatteryOptimization() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - val registered = (getSystemService(POWER_SERVICE) as PowerManager).isIgnoringBatteryOptimizations(packageName) - Log.i( - LOG_TAG, - "Checking permissions related to battery optimization. Ignoring Battery Optimization for package=$packageName, " + - "registered=$registered", - ) - if (!registered) { - startActivity( - Intent().apply { - action = Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS - data = Uri.parse("package:$packageName") - }, - ) - } - } - } - private fun validatePermissions() { if (Permissions.isAnyPermissionMissing(this)) { Permissions.showPermissionOnboarding(this) diff --git a/common/src/main/java/org/obd/graphs/Permissions.kt b/common/src/main/java/org/obd/graphs/Permissions.kt index a3926be1..81ced753 100644 --- a/common/src/main/java/org/obd/graphs/Permissions.kt +++ b/common/src/main/java/org/obd/graphs/Permissions.kt @@ -17,11 +17,16 @@ package org.obd.graphs import android.Manifest +import android.annotation.SuppressLint import android.app.Activity import android.content.Context +import android.content.Intent import android.content.pm.PackageManager import android.location.LocationManager +import android.net.Uri import android.os.Build +import android.os.PowerManager +import android.provider.Settings import android.util.Log import androidx.core.content.ContextCompat import androidx.core.location.LocationManagerCompat @@ -33,19 +38,18 @@ private const val LOCATION_REQUEST_CODE = 1001 private const val BLUETOOTH_REQUEST_CODE = 1002 private const val NOTIFICATION_REQUEST_CODE = 1003 -object Permissions { + @SuppressLint("ObsoleteSdkInt") + object Permissions { + /** * Returns TRUE if any required permission is missing. * Reuses your existing individual checks. */ fun isAnyPermissionMissing(context: Context): Boolean { - // Reuse hasLocationPermissions if (!hasLocationPermissions(context)) return true - - // Reuse hasNotificationPermissions if (!hasNotificationPermissions(context)) return true + if (!isBatteryOptimizationEnabled(context)) return true - // Reuse Bluetooth check val btPerms = getBluetoothPermissions() return !EasyPermissions.hasPermissions(context, *btPerms) } @@ -54,7 +58,7 @@ object Permissions { val message = """ To provide full functionality, please allow the following in the next steps: - + • Battery Optimization: To ensure data logging isn't interrupted in the background. • Location: To track your trip via GPS. • Bluetooth: To connect to your OBD adapter. • Notifications: To keep the logger running in the background. @@ -65,11 +69,29 @@ object Permissions { .setTitle("Setup Required") .setMessage(message) .setPositiveButton("Begin Setup") { _, _ -> - requestAll(activity) + requestAll(activity) + + if (!isBatteryOptimizationEnabled(activity)) { + requestBatteryOptimization(activity) + } }.setNegativeButton("Later", null) .show() } + + + /** + * Checks if the app is already ignoring battery optimizations. + */ + @SuppressLint("ObsoleteSdkInt") + fun isBatteryOptimizationEnabled(context: Context): Boolean { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + val powerManager = context.getSystemService(Context.POWER_SERVICE) as? PowerManager + return powerManager?.isIgnoringBatteryOptimizations(context.packageName) ?: true + } + return true + } + /** * Returns TRUE if all required location permissions are granted. * Also performs a diagnostic check to warn if the user has selected "Approximate" location. @@ -239,4 +261,19 @@ object Permissions { Log.v(TAG, "All permissions already granted.") } } + + /** + * Triggers the battery optimization intent. + * Note: This must be called from an Activity. + */ + private fun requestBatteryOptimization(activity: Activity) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + Log.i(TAG, "Requesting to ignore battery optimizations.") + val intent = Intent().apply { + action = Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS + data = Uri.parse("package:${activity.packageName}") + } + activity.startActivity(intent) + } + } }