Skip to content

Commit

Permalink
Request to ignore battery optimizations
Browse files Browse the repository at this point in the history
  • Loading branch information
sdsantos committed Oct 16, 2024
1 parent 569ee0e commit b16ffac
Show file tree
Hide file tree
Showing 13 changed files with 241 additions and 34 deletions.
1 change: 0 additions & 1 deletion composeApp/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@ kotlin {
// This makes instrumented tests depend on commonTest and androidUnitTest sources
sourceSetTree.set(KotlinSourceSetTree.test)
}
instrumentedTestVariant { }
}

iosX64()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ class OnboardingTest {
clickOnText("True")

wait { onNodeWithText("Automated testing").isDisplayed() }
clickOnTag("Yes-AutoTest")
clickOnTag("No-AutoTest")

wait { onNodeWithText("Crash Reporting").isDisplayed() }
clickOnTag("Yes-CrashReporting")
Expand All @@ -63,7 +63,7 @@ class OnboardingTest {
}

assertEquals(
true,
false,
preferences.getValueByKey(SettingsKey.AUTOMATED_TESTING_ENABLED).first(),
)
assertEquals(true, preferences.getValueByKey(SettingsKey.SEND_CRASH).first())
Expand Down
1 change: 1 addition & 0 deletions composeApp/src/androidMain/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />

<application
android:name=".AndroidApplication"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import android.net.Uri
import android.os.BatteryManager
import android.os.Build
import android.os.LocaleList
import android.os.PowerManager
import androidx.core.content.FileProvider
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.SharedPreferencesMigration
Expand All @@ -25,6 +26,7 @@ import okio.Path.Companion.toPath
import org.ooni.engine.AndroidNetworkTypeFinder
import org.ooni.engine.AndroidOonimkallBridge
import org.ooni.probe.background.AppWorkerManager
import org.ooni.probe.config.AndroidBatteryOptimization
import org.ooni.probe.data.models.FileSharing
import org.ooni.probe.di.Dependencies
import org.ooni.probe.shared.Platform
Expand Down Expand Up @@ -53,9 +55,12 @@ class AndroidApplication : Application() {
configureDescriptorAutoUpdate = appWorkerManager::configureDescriptorAutoUpdate,
fetchDescriptorUpdate = appWorkerManager::fetchDescriptorUpdate,
shareFile = ::shareFile,
batteryOptimization = batteryOptimization,
)
}

private val mainActivityLifecycleCallbacks = MainActivityLifecycleCallbacks()

override fun onCreate() {
super.onCreate()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
Expand All @@ -65,6 +70,7 @@ class AndroidApplication : Application() {
LocaleList.forLanguageTags(getString(R.string.supported_languages)),
)
}
registerActivityLifecycleCallbacks(mainActivityLifecycleCallbacks)
}

private val platformInfo by lazy {
Expand Down Expand Up @@ -194,4 +200,14 @@ class AndroidApplication : Application() {
false
}
}

private val batteryOptimization by lazy {
AndroidBatteryOptimization(
powerManager = getSystemService(PowerManager::class.java),
packageName = packageName,
requestCall = { callback ->
mainActivityLifecycleCallbacks.activity?.requestIgnoreBatteryOptimization(callback)
},
)
}
}
29 changes: 29 additions & 0 deletions composeApp/src/androidMain/kotlin/org/ooni/probe/MainActivity.kt
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
package org.ooni.probe

import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.provider.Settings
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.activity.result.contract.ActivityResultContract
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
Expand Down Expand Up @@ -60,4 +65,28 @@ class MainActivity : ComponentActivity() {
}
}
}

// Battery Optimization

private var ignoreBatteryOptimizationCallback: (() -> Unit)? = null

private val ignoreBatteryOptimizationContract =
registerForActivityResult(object : ActivityResultContract<Unit, Unit>() {
@SuppressLint("BatteryLife")
override fun createIntent(
context: Context,
input: Unit,
) = Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS)
.setData(Uri.parse("package:$packageName"))

override fun parseResult(
resultCode: Int,
intent: Intent?,
) {}
}) { ignoreBatteryOptimizationCallback?.invoke() }

fun requestIgnoreBatteryOptimization(callback: () -> Unit) {
ignoreBatteryOptimizationCallback = callback
ignoreBatteryOptimizationContract.launch(Unit)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package org.ooni.probe

import android.app.Activity
import android.app.Application.ActivityLifecycleCallbacks
import android.os.Bundle

class MainActivityLifecycleCallbacks : ActivityLifecycleCallbacks {
var activity: MainActivity? = null
private set

override fun onActivityCreated(
activity: Activity,
savedInstanceState: Bundle?,
) {
this.activity = activity as? MainActivity
}

override fun onActivityStarted(activity: Activity) {}

override fun onActivityResumed(activity: Activity) {}

override fun onActivityPaused(activity: Activity) {}

override fun onActivityStopped(activity: Activity) {}

override fun onActivitySaveInstanceState(
activity: Activity,
outState: Bundle,
) {}

override fun onActivityDestroyed(activity: Activity) {
this.activity = null
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package org.ooni.probe.config

import android.os.PowerManager

class AndroidBatteryOptimization(
private val powerManager: PowerManager,
private val packageName: String,
private val requestCall: (() -> Unit) -> Unit,
) : BatteryOptimization {
override val isSupported = true

override val isIgnoring: Boolean
get() = powerManager.isIgnoringBatteryOptimizations(packageName)

override fun requestIgnore(onResponse: (Boolean) -> Unit) {
requestCall {
onResponse(isIgnoring)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,8 @@
<string name="Modal_CustomURL_Title_NotSaved">Are you sure?</string>
<string name="Modal_CustomURL_NotSaved">Your URLs will not be saved when you leave this screen. Are you sure you want to leave this screen?</string>

<string name="Modal_Autorun_BatteryOptimization">OONI Probe cannot run automatically without battery optimization. Do you want to try again?</string>

<!-- New Strings -->
<string name="back">Back</string>
<string name="refresh">refresh</string>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package org.ooni.probe.config

interface BatteryOptimization {
val isSupported: Boolean get() = false

val isIgnoring: Boolean
get() = throw IllegalStateException("Battery Optimization not supported")

fun requestIgnore(onResponse: (Boolean) -> Unit) {
throw IllegalStateException("Battery Optimization not supported")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import org.ooni.engine.NetworkTypeFinder
import org.ooni.engine.OonimkallBridge
import org.ooni.engine.TaskEventMapper
import org.ooni.probe.Database
import org.ooni.probe.config.BatteryOptimization
import org.ooni.probe.data.disk.DeleteFiles
import org.ooni.probe.data.disk.DeleteFilesOkio
import org.ooni.probe.data.disk.ReadFile
Expand Down Expand Up @@ -111,6 +112,7 @@ class Dependencies(
val fetchDescriptorUpdate: suspend (List<InstalledTestDescriptorModel>?) -> Unit,
val localeDirection: (() -> LayoutDirection)? = null,
private val shareFile: (FileSharing) -> Boolean,
private val batteryOptimization: BatteryOptimization,
) {
// Common

Expand Down Expand Up @@ -426,6 +428,7 @@ class Dependencies(
platformInfo = platformInfo,
preferenceRepository = preferenceRepository,
launchUrl = { launchUrl(it, null) },
batteryOptimization = batteryOptimization,
)

fun proxyViewModel(onBack: () -> Unit) = ProxyViewModel(onBack, preferenceRepository)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.LocalContentColor
Expand All @@ -48,6 +49,7 @@ import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.DialogProperties
import co.touchlab.kermit.Logger
import dev.icerock.moko.permissions.DeniedAlwaysException
import dev.icerock.moko.permissions.DeniedException
Expand All @@ -58,8 +60,11 @@ import dev.icerock.moko.permissions.compose.PermissionsControllerFactory
import dev.icerock.moko.permissions.compose.rememberPermissionsControllerFactory
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import ooniprobe.composeapp.generated.resources.Modal_Autorun_BatteryOptimization
import ooniprobe.composeapp.generated.resources.Modal_Cancel
import ooniprobe.composeapp.generated.resources.Modal_EnableNotifications_Paragraph
import ooniprobe.composeapp.generated.resources.Modal_EnableNotifications_Title
import ooniprobe.composeapp.generated.resources.Modal_OK
import ooniprobe.composeapp.generated.resources.Onboarding_AutomatedTesting_Paragraph
import ooniprobe.composeapp.generated.resources.Onboarding_AutomatedTesting_Title
import ooniprobe.composeapp.generated.resources.Onboarding_Crash_Button_No
Expand Down Expand Up @@ -97,9 +102,9 @@ fun OnboardingScreen(
state: OnboardingViewModel.State,
onEvent: (OnboardingViewModel.Event) -> Unit,
) {
val pagerState = rememberPagerState(0, pageCount = { state.stepList.size })
val pagerState = rememberPagerState(0, pageCount = { state.totalSteps })
LaunchedEffect(state.stepIndex) {
pagerState.animateScrollToPage(state.stepIndex)
pagerState.scrollToPage(state.stepIndex)
}
var showQuiz by remember { mutableStateOf(false) }

Expand All @@ -108,10 +113,9 @@ fun OnboardingScreen(
state = pagerState,
userScrollEnabled = false,
modifier = Modifier.fillMaxSize(),
) { stepIndex ->
val step = state.stepList[state.stepIndex]
) {
Surface(
color = step.surfaceColor,
color = state.step.surfaceColor,
contentColor = LocalCustomColors.current.onOnboarding,
) {
Column(
Expand All @@ -120,7 +124,7 @@ fun OnboardingScreen(
.padding(WindowInsets.navigationBars.asPaddingValues())
.padding(bottom = 48.dp),
) {
when (state.stepList[stepIndex]) {
when (state.step) {
OnboardingViewModel.Step.WhatIs ->
WhatIsStep(onEvent)

Expand All @@ -130,8 +134,8 @@ fun OnboardingScreen(
onShowQuiz = { showQuiz = true },
)

OnboardingViewModel.Step.AutomatedTesting ->
AutomatedTestingStep(onEvent)
is OnboardingViewModel.Step.AutomatedTesting ->
AutomatedTestingStep(state.step.showBatteryOptimizationDialog, onEvent)

OnboardingViewModel.Step.CrashReporting ->
CrashReportingStep(onEvent)
Expand Down Expand Up @@ -245,7 +249,10 @@ fun ColumnScope.HeadsUpStep(
}

@Composable
fun ColumnScope.AutomatedTestingStep(onEvent: (OnboardingViewModel.Event) -> Unit) {
fun ColumnScope.AutomatedTestingStep(
showBatteryOptimizationDialog: Boolean,
onEvent: (OnboardingViewModel.Event) -> Unit,
) {
OnboardingImage(OrganizationConfig.onboardingImages.image3)
OnboardingTitle(Res.string.Onboarding_AutomatedTesting_Title)
Column(
Expand All @@ -263,15 +270,37 @@ fun ColumnScope.AutomatedTestingStep(onEvent: (OnboardingViewModel.Event) -> Uni
onClick = { onEvent(OnboardingViewModel.Event.AutoTestNoClicked) },
modifier = Modifier
.padding(horizontal = 8.dp)
.weight(1f),
.weight(1f)
.testTag("No-AutoTest"),
)
OnboardingMainButton(
text = Res.string.Onboarding_Crash_Button_Yes,
onClick = { onEvent(OnboardingViewModel.Event.AutoTestYesClicked) },
modifier = Modifier
.padding(horizontal = 8.dp)
.weight(1f)
.testTag("Yes-AutoTest"),
.weight(1f),
)
}

if (showBatteryOptimizationDialog) {
AlertDialog(
onDismissRequest = { },
text = { Text(stringResource(Res.string.Modal_Autorun_BatteryOptimization)) },
confirmButton = {
TextButton(onClick = {
onEvent(OnboardingViewModel.Event.BatteryOptimizationOkClicked)
}) {
Text(stringResource(Res.string.Modal_OK))
}
},
dismissButton = {
TextButton(onClick = {
onEvent(OnboardingViewModel.Event.BatteryOptimizationCancelClicked)
}) {
Text(stringResource(Res.string.Modal_Cancel))
}
},
properties = DialogProperties(dismissOnClickOutside = false, dismissOnBackPress = false),
)
}
}
Expand Down
Loading

0 comments on commit b16ffac

Please sign in to comment.