Skip to content

Commit 1e45772

Browse files
committed
add option to put the provider service into the background
1 parent 850ffb3 commit 1e45772

File tree

9 files changed

+177
-2
lines changed

9 files changed

+177
-2
lines changed

app/src/main/AndroidManifest.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
<uses-permission android:name="android.permission.WAKE_LOCK" />
1111
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
1212
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
13+
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
1314

1415
<uses-permission
1516
android:name="android.permission.READ_EXTERNAL_STORAGE"
@@ -127,6 +128,11 @@
127128
android:exported="false"
128129
android:foregroundServiceType="dataSync" />
129130

131+
<service
132+
android:name=".service.ProviderService"
133+
android:exported="false"
134+
android:foregroundServiceType="specialUse" />
135+
130136
<service
131137
android:name=".service.LogcatService"
132138
android:exported="false" />

app/src/main/kotlin/com/dergoogler/mmrl/datastore/UserPreferencesDataSource.kt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,4 +273,13 @@ class UserPreferencesDataSource @Inject constructor(
273273
)
274274
}
275275
}
276+
277+
278+
suspend fun setUseProviderAsBackgroundService(value: Boolean) = withContext(Dispatchers.IO) {
279+
userPreferences.updateData {
280+
it.copy(
281+
useProviderAsBackgroundService = value
282+
)
283+
}
284+
}
276285
}

app/src/main/kotlin/com/dergoogler/mmrl/datastore/model/UserPreferences.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ data class UserPreferences @OptIn(ExperimentalSerializationApi::class) construct
5454
val injectEruda: List<String> = emptyList(),
5555
val allowedFsModules: List<String> = emptyList(),
5656
val allowedKsuModules: List<String> = emptyList(),
57+
val useProviderAsBackgroundService: Boolean = false,
5758
) {
5859
@Composable
5960
fun isDarkMode() = when (darkMode) {

app/src/main/kotlin/com/dergoogler/mmrl/repository/UserPreferencesRepository.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,4 +103,7 @@ class UserPreferencesRepository @Inject constructor(
103103

104104
suspend fun setModulesMenu(value: ModulesMenu) =
105105
userPreferencesDataSource.setModulesMenu(value)
106+
107+
suspend fun setUseProviderAsBackgroundService(value: Boolean) =
108+
userPreferencesDataSource.setUseProviderAsBackgroundService(value)
106109
}
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
package com.dergoogler.mmrl.service
2+
3+
import android.content.ComponentName
4+
import android.content.Context
5+
import android.content.Intent
6+
import android.os.Build
7+
import androidx.core.app.NotificationCompat
8+
import androidx.core.app.ServiceCompat
9+
import androidx.lifecycle.LifecycleService
10+
import androidx.lifecycle.lifecycleScope
11+
import com.dergoogler.mmrl.Compat
12+
import com.dergoogler.mmrl.R
13+
import com.dergoogler.mmrl.app.utils.NotificationUtils
14+
import com.dergoogler.mmrl.datastore.model.UserPreferences
15+
import com.dergoogler.mmrl.datastore.model.WorkingMode
16+
import kotlinx.coroutines.flow.MutableStateFlow
17+
import kotlinx.coroutines.launch
18+
import timber.log.Timber
19+
20+
21+
class ProviderService : LifecycleService() {
22+
override fun onCreate() {
23+
Timber.d("onCreate")
24+
super.onCreate()
25+
isActive.value = true
26+
setForeground()
27+
}
28+
29+
override fun onDestroy() {
30+
ServiceCompat.stopForeground(this, ServiceCompat.STOP_FOREGROUND_REMOVE)
31+
isActive.value = false
32+
Timber.d("onDestroy")
33+
super.onDestroy()
34+
}
35+
36+
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
37+
super.onStartCommand(intent, flags, startId)
38+
39+
if (intent == null) {
40+
Timber.w("onStartCommand: intent is null")
41+
return START_NOT_STICKY
42+
}
43+
44+
val mode = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
45+
intent.getSerializableExtra(WORKING_MODE_KEY, WorkingMode::class.java)
46+
?: WorkingMode.MODE_NON_ROOT
47+
} else {
48+
@Suppress("DEPRECATION")
49+
intent.getSerializableExtra(WORKING_MODE_KEY) as WorkingMode
50+
}
51+
52+
lifecycleScope.launch {
53+
isActive.value = Compat.init(mode)
54+
}
55+
56+
return START_STICKY
57+
}
58+
59+
private fun baseNotificationBuilder() =
60+
NotificationCompat.Builder(this, NotificationUtils.CHANNEL_ID_DOWNLOAD)
61+
.setSmallIcon(R.drawable.launcher_outline)
62+
63+
private fun setForeground() {
64+
val notification = baseNotificationBuilder()
65+
.setContentTitle("Provider Service is running")
66+
.setSilent(true)
67+
.setOngoing(true)
68+
.setGroup(GROUP_KEY)
69+
.setGroupSummary(true)
70+
.build()
71+
72+
startForeground(NotificationUtils.NOTIFICATION_ID_DOWNLOAD, notification)
73+
}
74+
75+
companion object {
76+
val isActive = MutableStateFlow(false)
77+
private const val GROUP_KEY = "PROVIDER_SERVICE_GROUP_KEY"
78+
private const val WORKING_MODE_KEY = "WORKING_MODE"
79+
80+
suspend fun start(
81+
context: Context,
82+
preferences: UserPreferences,
83+
): Boolean? {
84+
if (preferences.useProviderAsBackgroundService) {
85+
val intent = Intent().apply {
86+
component = ComponentName(
87+
context.packageName,
88+
ProviderService::class.java.name
89+
)
90+
putExtra(WORKING_MODE_KEY, preferences.workingMode)
91+
}
92+
context.startService(intent)
93+
return null
94+
}
95+
96+
return Compat.init(preferences.workingMode)
97+
}
98+
99+
fun stop(
100+
context: Context,
101+
) {
102+
val intent = Intent(context, ProviderService::class.java)
103+
context.stopService(intent)
104+
}
105+
}
106+
}

app/src/main/kotlin/com/dergoogler/mmrl/ui/activity/MainActivity.kt

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
1717
import androidx.core.view.WindowCompat
1818
import androidx.lifecycle.compose.collectAsStateWithLifecycle
1919
import androidx.lifecycle.lifecycleScope
20-
import com.dergoogler.mmrl.Compat
2120
import com.dergoogler.mmrl.R
2221
import com.dergoogler.mmrl.app.Const
2322
import com.dergoogler.mmrl.background.BootBroadcastReceiver
@@ -26,6 +25,7 @@ import com.dergoogler.mmrl.datastore.model.WorkingMode
2625
import com.dergoogler.mmrl.datastore.model.WorkingMode.Companion.isRoot
2726
import com.dergoogler.mmrl.datastore.model.WorkingMode.Companion.isSetup
2827
import com.dergoogler.mmrl.network.NetworkUtils
28+
import com.dergoogler.mmrl.service.ProviderService
2929
import com.dergoogler.mmrl.ui.activity.terminal.action.ActionActivity
3030
import com.dergoogler.mmrl.ui.activity.terminal.install.InstallActivity
3131
import com.dergoogler.mmrl.ui.activity.webui.WebUIActivity
@@ -82,7 +82,10 @@ class MainActivity : MMRLComponentActivity() {
8282

8383
modulesRepository.getBlacklist()
8484

85-
Compat.init(preferences.workingMode)
85+
ProviderService.start(
86+
context = baseContext,
87+
preferences = preferences,
88+
)
8689

8790
if (!preferences.workingMode.isSetup) BootBroadcastReceiver.startWorkManagers(
8891
this@MainActivity,

app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/settings/other/OtherScreen.kt

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,29 @@
11
package com.dergoogler.mmrl.ui.screens.settings.other
22

33
import androidx.compose.runtime.Composable
4+
import androidx.compose.runtime.getValue
5+
import androidx.compose.runtime.mutableStateOf
6+
import androidx.compose.runtime.remember
7+
import androidx.compose.runtime.rememberCoroutineScope
8+
import androidx.compose.runtime.setValue
9+
import androidx.compose.ui.platform.LocalContext
410
import androidx.compose.ui.res.stringResource
511
import com.dergoogler.mmrl.R
12+
import com.dergoogler.mmrl.service.ProviderService
613
import com.dergoogler.mmrl.ui.component.SettingsScaffold
714
import com.dergoogler.mmrl.ui.component.listItem.ListSwitchItem
815
import com.dergoogler.mmrl.ui.providable.LocalSettings
16+
import com.dergoogler.mmrl.ui.providable.LocalSnackbarHost
917
import com.dergoogler.mmrl.ui.providable.LocalUserPreferences
1018
import com.dergoogler.mmrl.ui.screens.settings.appearance.items.DownloadPathItem
19+
import kotlinx.coroutines.delay
20+
import kotlinx.coroutines.launch
1121

1222
@Composable
1323
fun OtherScreen() {
24+
val context = LocalContext.current
25+
val scope = rememberCoroutineScope()
26+
val snackbarHost = LocalSnackbarHost.current
1427
val viewModel = LocalSettings.current
1528
val userPreferences = LocalUserPreferences.current
1629

@@ -28,5 +41,30 @@ fun OtherScreen() {
2841
checked = userPreferences.useDoh,
2942
onChange = viewModel::setUseDoh
3043
)
44+
45+
var useProviderAsBackgroundService by remember(userPreferences.useProviderAsBackgroundService) {
46+
mutableStateOf(userPreferences.useProviderAsBackgroundService)
47+
}
48+
49+
ListSwitchItem(
50+
title = stringResource(id = R.string.settings_provider_service),
51+
desc = stringResource(id = R.string.settings_provider_service_desc),
52+
checked = useProviderAsBackgroundService,
53+
onChange = viewModel::setUseProviderAsBackgroundService,
54+
base = {
55+
learnMoreText = R.string.stop_service
56+
learnMore = {
57+
ProviderService.stop(context)
58+
scope.launch {
59+
while (ProviderService.isActive.value) {
60+
delay(100)
61+
}
62+
useProviderAsBackgroundService = false
63+
viewModel.setUseProviderAsBackgroundService(false)
64+
snackbarHost.showSnackbar("Provider service stopped")
65+
}
66+
}
67+
}
68+
)
3169
}
3270
}

app/src/main/kotlin/com/dergoogler/mmrl/viewmodel/SettingsViewModel.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,4 +228,10 @@ class SettingsViewModel @Inject constructor(
228228
userPreferencesRepository.setAllowedKsuModules(value)
229229
}
230230
}
231+
232+
fun setUseProviderAsBackgroundService(value: Boolean) {
233+
viewModelScope.launch {
234+
userPreferencesRepository.setUseProviderAsBackgroundService(value)
235+
}
236+
}
231237
}

app/src/main/res/values/strings.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -396,4 +396,7 @@
396396
<string name="failed_to_update_sepolicy">Failed to update SELinux rules for: %s</string>
397397
<string name="su_not_allowed">Granting superuser isn\'t allowed for: %s</string>
398398
<string name="failed_to_grant_root">Failed to Grant Root</string>
399+
<string name="settings_provider_service">Provider Service</string>
400+
<string name="settings_provider_service_desc">Keep the provider running in the background to significantly reduce startup times and improve overall responsiveness</string>
401+
<string name="stop_service">Stop Service</string>
399402
</resources>

0 commit comments

Comments
 (0)