mirror of
https://github.com/d4rken-org/capod.git
synced 2026-09-14 18:26:11 -04:00
Add ignore battery optimizations if monitor mode is ALWAYS
This commit is contained in:
@@ -23,6 +23,8 @@
|
||||
android:maxSdkVersion="30" />
|
||||
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
|
||||
|
||||
<uses-permission-sdk-23 android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
|
||||
|
||||
<uses-feature
|
||||
android:name="android.hardware.bluetooth_le"
|
||||
android:required="true" />
|
||||
|
||||
@@ -3,9 +3,11 @@ package eu.darken.capod.common.permissions
|
||||
import android.content.Context
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Build
|
||||
import android.os.PowerManager
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.core.content.ContextCompat
|
||||
import eu.darken.capod.R
|
||||
import eu.darken.capod.common.BuildConfigWrap
|
||||
import eu.darken.capod.common.withinApiLevel
|
||||
|
||||
enum class Permission(
|
||||
@@ -14,6 +16,9 @@ enum class Permission(
|
||||
@StringRes val labelRes: Int,
|
||||
@StringRes val descriptionRes: Int,
|
||||
val permissionId: String,
|
||||
val isGranted: (Context) -> Boolean = {
|
||||
ContextCompat.checkSelfPermission(it, permissionId) == PackageManager.PERMISSION_GRANTED
|
||||
},
|
||||
) {
|
||||
BLUETOOTH(
|
||||
minApiLevel = Build.VERSION_CODES.BASE,
|
||||
@@ -47,10 +52,20 @@ enum class Permission(
|
||||
labelRes = R.string.permission_background_location_label,
|
||||
descriptionRes = R.string.permission_background_location_description,
|
||||
permissionId = "android.permission.ACCESS_BACKGROUND_LOCATION",
|
||||
),
|
||||
IGNORE_BATTERY_OPTIMIZATION(
|
||||
minApiLevel = Build.VERSION_CODES.BASE,
|
||||
labelRes = R.string.permission_ignore_battery_optimizations_label,
|
||||
descriptionRes = R.string.permission_ignore_battery_optimizations_description,
|
||||
permissionId = "android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS",
|
||||
isGranted = {
|
||||
val pwm = it.getSystemService(Context.POWER_SERVICE) as PowerManager
|
||||
pwm.isIgnoringBatteryOptimizations(BuildConfigWrap.APPLICATION_ID)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fun Permission.isRequired(context: Context): Boolean = when {
|
||||
!withinApiLevel(minApiLevel, maxApiLevel) -> false
|
||||
else -> ContextCompat.checkSelfPermission(context, permissionId) != PackageManager.PERMISSION_GRANTED
|
||||
else -> !isGranted(context)
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package eu.darken.capod.main.core
|
||||
|
||||
import android.content.Context
|
||||
import dagger.Reusable
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import eu.darken.capod.common.permissions.Permission
|
||||
import eu.darken.capod.common.permissions.isRequired
|
||||
import javax.inject.Inject
|
||||
|
||||
@Reusable
|
||||
class PermissionTool @Inject constructor(
|
||||
@ApplicationContext private val context: Context,
|
||||
private val generalSettings: GeneralSettings,
|
||||
) {
|
||||
|
||||
suspend fun missingPermissions(): Set<Permission> = Permission.values()
|
||||
.filter { it != Permission.IGNORE_BATTERY_OPTIMIZATION || generalSettings.monitorMode.value == MonitorMode.ALWAYS }
|
||||
.filter { it.isRequired(context) }
|
||||
.toSet()
|
||||
}
|
||||
@@ -1,6 +1,9 @@
|
||||
package eu.darken.capod.main.ui.overview
|
||||
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.provider.Settings
|
||||
import android.view.View
|
||||
import androidx.activity.result.ActivityResultLauncher
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
@@ -10,11 +13,13 @@ import eu.darken.capod.R
|
||||
import eu.darken.capod.common.debug.logging.log
|
||||
import eu.darken.capod.common.lists.differ.update
|
||||
import eu.darken.capod.common.lists.setupDefaults
|
||||
import eu.darken.capod.common.permissions.Permission
|
||||
import eu.darken.capod.common.uix.Fragment3
|
||||
import eu.darken.capod.common.viewbinding.viewBinding
|
||||
import eu.darken.capod.databinding.MainFragmentBinding
|
||||
import javax.inject.Inject
|
||||
|
||||
|
||||
@AndroidEntryPoint
|
||||
class OverviewFragment : Fragment3(R.layout.main_fragment) {
|
||||
|
||||
@@ -25,9 +30,11 @@ class OverviewFragment : Fragment3(R.layout.main_fragment) {
|
||||
lateinit var adapter: OverviewAdapter
|
||||
|
||||
lateinit var permissionLauncher: ActivityResultLauncher<String>
|
||||
var awaitingIgnoreBatteryOptimization = false
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
awaitingIgnoreBatteryOptimization = savedInstanceState?.getBoolean("awaitingIgnoreBatteryOptimization") ?: false
|
||||
|
||||
permissionLauncher = registerForActivityResult(ActivityResultContracts.RequestPermission()) { granted ->
|
||||
log { "Request for $id was granted=$granted" }
|
||||
@@ -56,9 +63,33 @@ class OverviewFragment : Fragment3(R.layout.main_fragment) {
|
||||
adapter.update(it)
|
||||
}
|
||||
|
||||
vm.requestPermissionevent.observe2(ui) {
|
||||
permissionLauncher.launch(it.permissionId)
|
||||
vm.requestPermissionEvent.observe2(ui) {
|
||||
if (it == Permission.IGNORE_BATTERY_OPTIMIZATION) {
|
||||
awaitingIgnoreBatteryOptimization = true
|
||||
startActivity(
|
||||
Intent(
|
||||
Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS,
|
||||
Uri.parse("package:${requireContext().packageName}")
|
||||
)
|
||||
)
|
||||
} else {
|
||||
permissionLauncher.launch(it.permissionId)
|
||||
}
|
||||
}
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
outState.putBoolean("awaitingIgnoreBatteryOptimization", awaitingIgnoreBatteryOptimization)
|
||||
super.onSaveInstanceState(outState)
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
if (awaitingIgnoreBatteryOptimization) {
|
||||
awaitingIgnoreBatteryOptimization = false
|
||||
log { "awaitingIgnoreBatteryOptimization=true" }
|
||||
vm.onPermissionResult(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,18 @@
|
||||
package eu.darken.capod.main.ui.overview
|
||||
|
||||
import android.content.Context
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import eu.darken.capod.common.coroutine.DispatcherProvider
|
||||
import eu.darken.capod.common.debug.autoreport.DebugSettings
|
||||
import eu.darken.capod.common.debug.logging.log
|
||||
import eu.darken.capod.common.debug.recording.core.RecorderModule
|
||||
import eu.darken.capod.common.livedata.SingleLiveEvent
|
||||
import eu.darken.capod.common.navigation.navVia
|
||||
import eu.darken.capod.common.permissions.Permission
|
||||
import eu.darken.capod.common.permissions.isRequired
|
||||
import eu.darken.capod.common.uix.ViewModel3
|
||||
import eu.darken.capod.main.core.GeneralSettings
|
||||
import eu.darken.capod.main.core.MonitorMode
|
||||
import eu.darken.capod.main.core.PermissionTool
|
||||
import eu.darken.capod.main.ui.overview.cards.PermissionCardVH
|
||||
import eu.darken.capod.main.ui.overview.cards.pods.BasicSingleApplePodsCardVH
|
||||
import eu.darken.capod.main.ui.overview.cards.pods.DualApplePodsCardVH
|
||||
@@ -38,13 +35,12 @@ import javax.inject.Inject
|
||||
@HiltViewModel
|
||||
class OverviewFragmentVM @Inject constructor(
|
||||
handle: SavedStateHandle,
|
||||
@ApplicationContext private val context: Context,
|
||||
dispatcherProvider: DispatcherProvider,
|
||||
private val recorderModule: RecorderModule,
|
||||
private val monitorControl: MonitorControl,
|
||||
private val podMonitor: PodMonitor,
|
||||
private val permissionTool: PermissionTool,
|
||||
private val generalSettings: GeneralSettings,
|
||||
private val debugSettings: DebugSettings,
|
||||
debugSettings: DebugSettings,
|
||||
) : ViewModel3(dispatcherProvider = dispatcherProvider) {
|
||||
|
||||
private val updateTicker = channelFlow<Unit> {
|
||||
@@ -55,10 +51,8 @@ class OverviewFragmentVM @Inject constructor(
|
||||
}
|
||||
|
||||
private val permissionCheckTrigger = MutableStateFlow(UUID.randomUUID())
|
||||
private val requiredPermissions: Flow<List<Permission>> = permissionCheckTrigger
|
||||
.map {
|
||||
Permission.values().filter { it.isRequired(context) }
|
||||
}
|
||||
private val requiredPermissions: Flow<Collection<Permission>> = permissionCheckTrigger
|
||||
.map { permissionTool.missingPermissions() }
|
||||
.onEach {
|
||||
log(TAG) { "Missing permissions: $it" }
|
||||
if (it.isEmpty() && generalSettings.monitorMode.value != MonitorMode.MANUAL) {
|
||||
@@ -67,7 +61,7 @@ class OverviewFragmentVM @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
val requestPermissionevent = SingleLiveEvent<Permission>()
|
||||
val requestPermissionEvent = SingleLiveEvent<Permission>()
|
||||
|
||||
private val pods: Flow<List<PodDevice>> = requiredPermissions
|
||||
.flatMapLatest { permissions ->
|
||||
@@ -99,15 +93,6 @@ class OverviewFragmentVM @Inject constructor(
|
||||
) { tick, permissions, pods, isDebugMode ->
|
||||
val items = mutableListOf<OverviewAdapter.Item>()
|
||||
|
||||
permissions
|
||||
.map {
|
||||
PermissionCardVH.Item(
|
||||
permission = it,
|
||||
onRequest = { requestPermissionevent.postValue(it) }
|
||||
)
|
||||
}
|
||||
.forEach { items.add(it) }
|
||||
|
||||
val now = Instant.now()
|
||||
pods
|
||||
.map {
|
||||
@@ -135,6 +120,15 @@ class OverviewFragmentVM @Inject constructor(
|
||||
}
|
||||
.run { items.addAll(this) }
|
||||
|
||||
permissions
|
||||
.map {
|
||||
PermissionCardVH.Item(
|
||||
permission = it,
|
||||
onRequest = { requestPermissionEvent.postValue(it) }
|
||||
)
|
||||
}
|
||||
.run { items.addAll(this) }
|
||||
|
||||
items
|
||||
}
|
||||
.catch { errorEvents.postValue(it) }
|
||||
@@ -144,10 +138,6 @@ class OverviewFragmentVM @Inject constructor(
|
||||
if (granted) permissionCheckTrigger.value = UUID.randomUUID()
|
||||
}
|
||||
|
||||
fun toggleDebugLog() = launch {
|
||||
recorderModule.startRecorder()
|
||||
}
|
||||
|
||||
fun goToSettings() = launch {
|
||||
OverviewFragmentDirections.actionOverviewFragmentToSettingsFragment().navVia(this@OverviewFragmentVM)
|
||||
}
|
||||
|
||||
@@ -16,10 +16,9 @@ import eu.darken.capod.common.debug.logging.log
|
||||
import eu.darken.capod.common.debug.logging.logTag
|
||||
import eu.darken.capod.common.flow.setupCommonEventHandlers
|
||||
import eu.darken.capod.common.flow.withPrevious
|
||||
import eu.darken.capod.common.permissions.Permission
|
||||
import eu.darken.capod.common.permissions.isRequired
|
||||
import eu.darken.capod.main.core.GeneralSettings
|
||||
import eu.darken.capod.main.core.MonitorMode
|
||||
import eu.darken.capod.main.core.PermissionTool
|
||||
import eu.darken.capod.monitor.core.MonitorComponent
|
||||
import eu.darken.capod.monitor.core.MonitorCoroutineScope
|
||||
import eu.darken.capod.monitor.ui.MonitorNotifications
|
||||
@@ -41,6 +40,7 @@ class MonitorWorker @AssistedInject constructor(
|
||||
private val notificationManager: NotificationManager,
|
||||
private val generalSettings: GeneralSettings,
|
||||
private val mediaControl: MediaControl,
|
||||
private val permissionTool: PermissionTool,
|
||||
) : CoroutineWorker(context, params) {
|
||||
|
||||
private val workerScope = MonitorCoroutineScope()
|
||||
@@ -91,14 +91,21 @@ class MonitorWorker @AssistedInject constructor(
|
||||
}
|
||||
|
||||
private suspend fun doDoWork() {
|
||||
val missingPermissions = Permission.values().filter { it.isRequired(context) }
|
||||
if (missingPermissions.isNotEmpty()) {
|
||||
log(TAG, WARN) { "Aborting, missing permissions: $missingPermissions" }
|
||||
val permissionsMissingOnStart = permissionTool.missingPermissions()
|
||||
if (permissionsMissingOnStart.isNotEmpty()) {
|
||||
log(TAG, WARN) { "Aborting, missing permissions: $permissionsMissingOnStart" }
|
||||
return
|
||||
}
|
||||
|
||||
generalSettings.monitorMode.flow
|
||||
.flatMapLatest { monitorMode ->
|
||||
val missingPermsFlow = permissionTool.missingPermissions()
|
||||
if (missingPermsFlow.isNotEmpty()) {
|
||||
log(TAG, WARN) { "Aborting, permissions are missing for $monitorMode: $missingPermsFlow" }
|
||||
workerScope.coroutineContext.cancelChildren()
|
||||
return@flatMapLatest emptyFlow()
|
||||
}
|
||||
|
||||
bluetoothManager2
|
||||
.isBluetoothEnabled
|
||||
.flatMapLatest { bluetoothManager2.connectedDevices() }
|
||||
|
||||
@@ -24,8 +24,10 @@
|
||||
<string name="permission_bluetooth_scan_description">Required to be able to discover and pair nearby Bluetooth devices.</string>
|
||||
<string name="permission_bluetooth_label">BLUETOOTH</string>
|
||||
<string name="permission_bluetooth_description">Allows applications to connect to paired bluetooth devices.</string>
|
||||
<string name="permission_access_fine_location_description">Allows an app to access precise location.</string>
|
||||
<string name="permission_access_fine_location_label">ACCESS_FINE_LOCATION</string>
|
||||
<string name="permission_access_fine_location_description">Allows an app to access precise location.</string>
|
||||
<string name="permission_background_location_label">ACCESS_BACKGROUND_LOCATION</string>
|
||||
<string name="permission_background_location_description">Allows an app to access location in the background.</string>
|
||||
|
||||
<string name="pods_dual_left_label">Left pod</string>
|
||||
<string name="pods_dual_right_label">Right pod</string>
|
||||
@@ -101,10 +103,10 @@
|
||||
<string name="settings_autoplay_description">Start music playback music when wearing the device (if supported).</string>
|
||||
<string name="pods_dual_case_status_short">L %1$s | C %2$s | R %3$s</string>
|
||||
<string name="pods_single_basic_status_short">Headphones: %1$s</string>
|
||||
<string name="permission_background_location_label">ACCESS_BACKGROUND_LOCATION</string>
|
||||
<string name="permission_background_location_description">Allows an app to access location in the background.</string>
|
||||
<string name="settings_fake_data_label">Fake data</string>
|
||||
<string name="settings_fake_data_description">Show fake data, i.e. simulate device that don\'t exist.</string>
|
||||
<string name="settings_debug_label">Debug settings</string>
|
||||
<string name="settings_debug_description">Additional settings to help troubleshoot issues with the app.</string>
|
||||
<string name="permission_ignore_battery_optimizations_label">IGNORE_BATTERY_OPTIMIZATIONS</string>
|
||||
<string name="permission_ignore_battery_optimizations_description">IGNORE_BATTERY_OPTIMIZATIONS</string>
|
||||
</resources>
|
||||
Reference in New Issue
Block a user