Add ignore battery optimizations if monitor mode is ALWAYS

This commit is contained in:
darken
2022-01-07 14:43:00 +01:00
parent 0da842f6a3
commit c706747f11
7 changed files with 103 additions and 36 deletions
+2
View File
@@ -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() }
+5 -3
View File
@@ -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>