Skip to content

Commit 85be93a

Browse files
committed
feat: add usage and notification access checks in ExperimentalRpcScreen
1 parent 55e5911 commit 85be93a

File tree

5 files changed

+84
-54
lines changed

5 files changed

+84
-54
lines changed

app/src/main/java/com/my/kizzy/Kizzy.kt

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import androidx.activity.viewModels
1919
import androidx.compose.animation.ExperimentalAnimationApi
2020
import androidx.compose.material3.Scaffold
2121
import androidx.compose.runtime.Composable
22+
import androidx.compose.runtime.MutableState
2223
import androidx.compose.runtime.collectAsState
2324
import androidx.compose.runtime.getValue
2425
import androidx.compose.runtime.mutableStateOf
@@ -64,7 +65,10 @@ import xyz.dead8309.feature_experimental_rpc.apps.ExperimentalRpcAppsScreen
6465
@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter")
6566
@OptIn(ExperimentalAnimationApi::class)
6667
@Composable
67-
internal fun ComponentActivity.Kizzy() {
68+
internal fun ComponentActivity.Kizzy(
69+
usageAccessStatus: MutableState<Boolean>,
70+
notificationListenerAccess: MutableState<Boolean>,
71+
) {
6872
Scaffold()
6973
{
7074
val navController = rememberAnimatedNavController()
@@ -74,8 +78,8 @@ internal fun ComponentActivity.Kizzy() {
7478
) {
7579
animatedComposable(Routes.SETUP) {
7680
StartUp(
77-
usageAccessStatus = MainActivity.usageAccessStatus,
78-
mediaControlStatus = MainActivity.notificationListenerAccess,
81+
usageAccessStatus = usageAccessStatus,
82+
mediaControlStatus = notificationListenerAccess,
7983
navigateToLanguages = {
8084
navController.navigate(Routes.LANGUAGES) {
8185
launchSingleTop = true
@@ -113,8 +117,8 @@ internal fun ComponentActivity.Kizzy() {
113117
showBadge = showBadge,
114118
features = homeFeaturesProvider(
115119
navigateTo = { navController.navigate(it) },
116-
hasUsageAccess = MainActivity.usageAccessStatus,
117-
hasNotificationAccess = MainActivity.notificationListenerAccess,
120+
hasUsageAccess = usageAccessStatus,
121+
hasNotificationAccess = notificationListenerAccess,
118122
userVerified = user?.verified == true
119123
),
120124
user = user,
@@ -143,7 +147,7 @@ internal fun ComponentActivity.Kizzy() {
143147
val viewModel by viewModels<AppsScreenViewModel>()
144148
AppsRPC(
145149
onBackPressed = { navController.popBackStack() },
146-
hasUsageAccess = MainActivity.usageAccessStatus.value,
150+
hasUsageAccess = usageAccessStatus.value,
147151
state = viewModel.state.collectAsState().value,
148152
updateAppEnabled = viewModel::updateAppEnabled,
149153
)
@@ -161,6 +165,7 @@ internal fun ComponentActivity.Kizzy() {
161165
MediaRPC(
162166
onBackPressed = { navController.popBackStack() },
163167
state = viewModel.state.collectAsState().value,
168+
hasNotificationAccess = notificationListenerAccess.value,
164169
updateMediaAppEnabled = viewModel::updateMediaAppEnabled
165170
)
166171
}
@@ -247,6 +252,8 @@ internal fun ComponentActivity.Kizzy() {
247252
state = experimentalRpcViewModel.value.uiState.collectAsState().value,
248253
onEvent = experimentalRpcViewModel.value::onEvent,
249254
onBackPressed = { navController.popBackStack() },
255+
hasUsageAccess = usageAccessStatus.value,
256+
hasNotificationAccess = notificationListenerAccess.value,
250257
navigateToAppSelection = {
251258
navController.navigate(Routes.EXPERIMENTAL_RPC_APPS)
252259
},

app/src/main/java/com/my/kizzy/MainActivity.kt

Lines changed: 32 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,17 @@ import android.content.Context
55
import android.content.pm.PackageManager
66
import android.os.Build
77
import android.os.Bundle
8+
import android.provider.Settings
89
import androidx.activity.compose.setContent
910
import androidx.appcompat.app.AppCompatActivity
1011
import androidx.appcompat.app.AppCompatDelegate
1112
import androidx.compose.material3.windowsizeclass.ExperimentalMaterial3WindowSizeClassApi
1213
import androidx.compose.material3.windowsizeclass.calculateWindowSizeClass
13-
import androidx.compose.runtime.*
14+
import androidx.compose.runtime.MutableState
15+
import androidx.compose.runtime.mutableStateOf
1416
import androidx.core.os.LocaleListCompat
1517
import androidx.core.view.ViewCompat
1618
import androidx.core.view.WindowCompat
17-
import com.my.kizzy.feature_media_rpc.hasNotificationAccess
1819
import com.my.kizzy.preference.getLanguageConfig
1920
import com.my.kizzy.ui.theme.KizzyTheme
2021
import com.my.kizzy.ui.theme.LocalDarkTheme
@@ -25,6 +26,8 @@ import kotlinx.coroutines.runBlocking
2526

2627
@AndroidEntryPoint
2728
class MainActivity : AppCompatActivity() {
29+
private lateinit var usageAccessStatus: MutableState<Boolean>
30+
private lateinit var notificationListenerAccess: MutableState<Boolean>
2831

2932
@OptIn(ExperimentalMaterial3WindowSizeClassApi::class)
3033
override fun onCreate(savedInstanceState: Bundle?) {
@@ -50,7 +53,10 @@ class MainActivity : AppCompatActivity() {
5053
isHighContrastModeEnabled = LocalDarkTheme.current.isHighContrastModeEnabled,
5154
isDynamicColorEnabled = LocalDynamicColorSwitch.current,
5255
) {
53-
Kizzy()
56+
Kizzy(
57+
usageAccessStatus = usageAccessStatus,
58+
notificationListenerAccess = notificationListenerAccess,
59+
)
5460
}
5561
}
5662
}
@@ -62,31 +68,36 @@ class MainActivity : AppCompatActivity() {
6268
usageAccessStatus.value = hasUsageAccess()
6369
}
6470

71+
@Suppress("DEPRECATION")
72+
private fun Context.hasUsageAccess(): Boolean {
73+
return try {
74+
val packageManager: PackageManager = this.packageManager
75+
val applicationInfo = packageManager.getApplicationInfo(this.packageName, 0)
76+
val appOpsManager = this.getSystemService(APP_OPS_SERVICE) as AppOpsManager
77+
val mode = appOpsManager.checkOpNoThrow(
78+
AppOpsManager.OPSTR_GET_USAGE_STATS,
79+
applicationInfo.uid,
80+
applicationInfo.packageName
81+
)
82+
mode == AppOpsManager.MODE_ALLOWED
83+
} catch (_: PackageManager.NameNotFoundException) {
84+
false
85+
}
86+
}
87+
88+
private fun Context.hasNotificationAccess(): Boolean {
89+
val enabledNotificationListeners = Settings.Secure.getString(
90+
this.contentResolver, "enabled_notification_listeners"
91+
)
92+
return enabledNotificationListeners != null && enabledNotificationListeners.contains(this.packageName)
93+
}
6594

6695
companion object {
67-
lateinit var usageAccessStatus: MutableState<Boolean>
68-
lateinit var notificationListenerAccess: MutableState<Boolean>
6996
fun setLanguage(locale: String) {
7097
val localeListCompat =
7198
if (locale.isEmpty()) LocaleListCompat.getEmptyLocaleList()
7299
else LocaleListCompat.forLanguageTags(locale)
73100
AppCompatDelegate.setApplicationLocales(localeListCompat)
74101
}
75-
76-
@Suppress("DEPRECATION")
77-
fun Context.hasUsageAccess(): Boolean {
78-
return try {
79-
val packageManager: PackageManager = this.packageManager
80-
val applicationInfo = packageManager.getApplicationInfo(this.packageName, 0)
81-
val appOpsManager = this.getSystemService(Context.APP_OPS_SERVICE) as AppOpsManager
82-
val mode = appOpsManager.checkOpNoThrow(
83-
AppOpsManager.OPSTR_GET_USAGE_STATS,
84-
applicationInfo.uid,
85-
applicationInfo.packageName)
86-
mode == AppOpsManager.MODE_ALLOWED
87-
} catch (e: PackageManager.NameNotFoundException) {
88-
false
89-
}
90-
}
91102
}
92103
}

feature_experimental_rpc/src/main/java/xyz/dead8309/feature_experimental_rpc/ExperimentalRpcScreen.kt

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
package xyz.dead8309.feature_experimental_rpc
1414

1515
import android.content.Intent
16+
import android.provider.Settings
17+
import androidx.compose.animation.AnimatedVisibility
1618
import androidx.compose.foundation.layout.Arrangement
1719
import androidx.compose.foundation.layout.Column
1820
import androidx.compose.foundation.layout.fillMaxSize
@@ -21,9 +23,11 @@ import androidx.compose.foundation.layout.padding
2123
import androidx.compose.foundation.layout.size
2224
import androidx.compose.foundation.lazy.LazyColumn
2325
import androidx.compose.material.icons.Icons
26+
import androidx.compose.material.icons.filled.AppsOutage
2427
import androidx.compose.material.icons.filled.PauseCircle
2528
import androidx.compose.material.icons.filled.PlayCircle
2629
import androidx.compose.material.icons.filled.Timer
30+
import androidx.compose.material.icons.filled.Warning
2731
import androidx.compose.material.icons.outlined.AppSettingsAlt
2832
import androidx.compose.material.icons.outlined.Apps
2933
import androidx.compose.material.icons.outlined.Image
@@ -62,6 +66,7 @@ import com.my.kizzy.ui.components.SettingItem
6266
import com.my.kizzy.ui.components.Subtitle
6367
import com.my.kizzy.ui.components.SwitchBar
6468
import com.my.kizzy.ui.components.preference.PreferenceSwitch
69+
import com.my.kizzy.ui.components.preference.PreferencesHint
6570

6671
private val completions = listOf(
6772
TemplateKeys.MEDIA_TITLE to R.string.completion_media_title,
@@ -75,6 +80,8 @@ private val completions = listOf(
7580
fun ExperimentalRpcScreen(
7681
onBackPressed: () -> Unit,
7782
state: UiState,
83+
hasUsageAccess: Boolean,
84+
hasNotificationAccess: Boolean,
7885
navigateToAppSelection: () -> Unit,
7986
onEvent: (UiEvent) -> Unit,
8087
) {
@@ -104,9 +111,38 @@ fun ExperimentalRpcScreen(
104111
) { paddingValues ->
105112

106113
Column(modifier = Modifier.padding(paddingValues)) {
114+
AnimatedVisibility(
115+
visible = !hasUsageAccess
116+
) {
117+
PreferencesHint(
118+
title = stringResource(id = R.string.usage_access),
119+
description = stringResource(id = R.string.usage_access_desc),
120+
icon = Icons.Default.AppsOutage,
121+
) {
122+
when (hasUsageAccess) {
123+
false -> context.startActivity(Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS))
124+
else -> Unit
125+
}
126+
}
127+
}
128+
AnimatedVisibility(
129+
visible = !hasNotificationAccess
130+
) {
131+
PreferencesHint(
132+
title = stringResource(id = R.string.permission_required),
133+
description = stringResource(id = R.string.request_for_notification_access),
134+
icon = Icons.Default.Warning,
135+
) {
136+
if (!hasNotificationAccess) {
137+
context.startActivity(Intent(Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS))
138+
}
139+
}
140+
}
141+
107142
SwitchBar(
108143
title = stringResource(id = R.string.enable_experimental_rpc),
109144
isChecked = experimentalRpcRunning,
145+
enabled = hasUsageAccess && hasNotificationAccess,
110146
) {
111147
experimentalRpcRunning = !experimentalRpcRunning
112148
when (experimentalRpcRunning) {

feature_media_rpc/src/main/java/com/my/kizzy/feature_media_rpc/HasNotificationAccess.kt

Lines changed: 0 additions & 23 deletions
This file was deleted.

feature_media_rpc/src/main/java/com/my/kizzy/feature_media_rpc/MediaRpc.kt

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ import com.my.kizzy.ui.components.preference.PreferencesHint
7979
fun MediaRPC(
8080
onBackPressed: () -> Unit,
8181
state: MediaAppsState,
82+
hasNotificationAccess: Boolean,
8283
updateMediaAppEnabled: (String) -> Unit,
8384
) {
8485
val context = LocalContext.current
@@ -89,7 +90,6 @@ fun MediaRPC(
8990
var isTimestampsEnabled by remember { mutableStateOf(Prefs[MEDIA_RPC_ENABLE_TIMESTAMPS, false]) }
9091
var hideOnPause by remember { mutableStateOf(Prefs[MEDIA_RPC_HIDE_ON_PAUSE, false]) }
9192
var isShowPlaybackState by remember { mutableStateOf(Prefs[MEDIA_RPC_SHOW_PLAYBACK_STATE, false]) }
92-
var hasNotificationAccess by remember { mutableStateOf(context.hasNotificationAccess()) }
9393

9494
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior(
9595
rememberTopAppBarState(),
@@ -141,9 +141,8 @@ fun MediaRPC(
141141
description = stringResource(id = R.string.request_for_notification_access),
142142
icon = Icons.Default.Warning,
143143
) {
144-
when (context.hasNotificationAccess()) {
145-
true -> hasNotificationAccess = true
146-
false -> context.startActivity(Intent(Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS))
144+
if (!hasNotificationAccess) {
145+
context.startActivity(Intent(Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS))
147146
}
148147
}
149148
}

0 commit comments

Comments
 (0)