Skip to content

Commit d3a55d5

Browse files
Fix specialUse FGS property, notification permission, cleaner UI
1 parent eba44f1 commit d3a55d5

4 files changed

Lines changed: 175 additions & 65 deletions

File tree

‎app/src/main/AndroidManifest.xml‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
66
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
77
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
8+
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
89
<uses-permission android:name="android.permission.PACKAGE_USAGE_STATS"
910
tools:ignore="ProtectedPermissions" />
1011

@@ -28,7 +29,11 @@
2829
<service
2930
android:name=".OverlayService"
3031
android:exported="false"
31-
android:foregroundServiceType="specialUse" />
32+
android:foregroundServiceType="specialUse">
33+
<property
34+
android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
35+
android:value="Live performance telemetry overlay drawn on top of games and apps." />
36+
</service>
3237

3338
</application>
3439
</manifest>

‎app/src/main/java/com/joebuildsthings/gamedeck/MainActivity.kt‎

Lines changed: 33 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
11
package com.joebuildsthings.gamedeck
22

3+
import android.Manifest
34
import android.content.Intent
5+
import android.content.pm.PackageManager
46
import android.net.Uri
7+
import android.os.Build
58
import android.os.Bundle
69
import android.provider.Settings
710
import androidx.activity.ComponentActivity
11+
import androidx.activity.compose.rememberLauncherForActivityResult
812
import androidx.activity.compose.setContent
13+
import androidx.activity.result.contract.ActivityResultContracts
914
import androidx.compose.foundation.background
1015
import androidx.compose.foundation.layout.*
1116
import androidx.compose.material.icons.Icons
@@ -16,9 +21,11 @@ import androidx.compose.material3.*
1621
import androidx.compose.runtime.*
1722
import androidx.compose.ui.Modifier
1823
import androidx.compose.ui.graphics.vector.ImageVector
24+
import androidx.compose.ui.platform.LocalContext
1925
import androidx.compose.ui.text.font.FontWeight
2026
import androidx.compose.ui.unit.dp
2127
import androidx.compose.ui.unit.sp
28+
import androidx.core.content.ContextCompat
2229
import com.joebuildsthings.gamedeck.screens.GamesScreen
2330
import com.joebuildsthings.gamedeck.screens.HomeScreen
2431
import com.joebuildsthings.gamedeck.screens.SettingsScreen
@@ -64,7 +71,11 @@ class MainActivity : ComponentActivity() {
6471
val intent = Intent(this, OverlayService::class.java).apply {
6572
action = if (enable) OverlayService.ACTION_START else OverlayService.ACTION_STOP
6673
}
67-
if (enable) startForegroundService(intent) else startService(intent)
74+
if (enable) {
75+
ContextCompat.startForegroundService(this, intent)
76+
} else {
77+
startService(intent)
78+
}
6879
}
6980
}
7081

@@ -74,13 +85,27 @@ fun GameDeckApp(
7485
onRequestOverlay: () -> Unit,
7586
onToggleOverlay: (Boolean) -> Unit,
7687
) {
88+
val context = LocalContext.current
7789
val tabs = listOf(
7890
Tab("Home", Icons.Filled.Home),
7991
Tab("Games", Icons.Filled.SportsEsports),
8092
Tab("Settings", Icons.Filled.Settings),
8193
)
8294
var current by remember { mutableIntStateOf(0) }
8395

96+
val notifLauncher = rememberLauncherForActivityResult(
97+
ActivityResultContracts.RequestPermission()
98+
) { }
99+
100+
LaunchedEffect(Unit) {
101+
if (Build.VERSION.SDK_INT >= 33) {
102+
val granted = ContextCompat.checkSelfPermission(
103+
context, Manifest.permission.POST_NOTIFICATIONS
104+
) == PackageManager.PERMISSION_GRANTED
105+
if (!granted) notifLauncher.launch(Manifest.permission.POST_NOTIFICATIONS)
106+
}
107+
}
108+
84109
Scaffold(
85110
containerColor = BgDark,
86111
topBar = {
@@ -90,30 +115,25 @@ fun GameDeckApp(
90115
Text(
91116
"GameDeck",
92117
color = InkLight,
93-
fontWeight = FontWeight.SemiBold,
94-
fontSize = 18.sp,
118+
fontWeight = FontWeight.Bold,
119+
fontSize = 19.sp,
120+
letterSpacing = (-0.5).sp,
95121
)
96122
Spacer(Modifier.width(10.dp))
97-
Text(
98-
"v0.1",
99-
color = MutedGrey,
100-
fontSize = 11.sp,
101-
)
123+
Text("v0.1", color = MutedGrey, fontSize = 11.sp)
102124
}
103125
},
104-
colors = TopAppBarDefaults.topAppBarColors(
105-
containerColor = BgDark,
106-
)
126+
colors = TopAppBarDefaults.topAppBarColors(containerColor = BgDark)
107127
)
108128
},
109129
bottomBar = {
110-
NavigationBar(containerColor = BgDark) {
130+
NavigationBar(containerColor = BgDark, tonalElevation = 0.dp) {
111131
tabs.forEachIndexed { i, tab ->
112132
NavigationBarItem(
113133
selected = current == i,
114134
onClick = { current = i },
115135
icon = { Icon(tab.icon, contentDescription = tab.label) },
116-
label = { Text(tab.label, fontSize = 11.sp) },
136+
label = { Text(tab.label, fontSize = 11.sp, fontWeight = FontWeight.Medium) },
117137
colors = NavigationBarItemDefaults.colors(
118138
selectedIconColor = AccentRed,
119139
selectedTextColor = AccentRed,

‎app/src/main/java/com/joebuildsthings/gamedeck/OverlayService.kt‎

Lines changed: 37 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import android.app.NotificationManager
66
import android.app.Service
77
import android.content.Context
88
import android.content.Intent
9+
import android.content.pm.ServiceInfo
910
import android.graphics.PixelFormat
1011
import android.os.Build
1112
import android.os.IBinder
@@ -50,12 +51,31 @@ class OverlayService : Service() {
5051
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
5152
when (intent?.action) {
5253
ACTION_START -> {
53-
startForeground(NOTIFICATION_ID, buildNotification())
54-
if (overlayView == null) showOverlay()
54+
try {
55+
val notification = buildNotification()
56+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
57+
startForeground(
58+
NOTIFICATION_ID,
59+
notification,
60+
ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE
61+
)
62+
} else {
63+
startForeground(NOTIFICATION_ID, notification)
64+
}
65+
if (overlayView == null) showOverlay()
66+
} catch (t: Throwable) {
67+
android.util.Log.e("GameDeck", "Overlay start failed", t)
68+
stopSelf()
69+
}
5570
}
5671
ACTION_STOP -> {
5772
removeOverlay()
58-
stopForeground(STOP_FOREGROUND_REMOVE)
73+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
74+
stopForeground(STOP_FOREGROUND_REMOVE)
75+
} else {
76+
@Suppress("DEPRECATION")
77+
stopForeground(true)
78+
}
5979
stopSelf()
6080
}
6181
}
@@ -68,15 +88,17 @@ class OverlayService : Service() {
6888
}
6989

7090
private fun buildNotification(): Notification {
91+
val mgr = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
7192
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
72-
val mgr = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
7393
if (mgr.getNotificationChannel(CHANNEL_ID) == null) {
7494
val ch = NotificationChannel(
7595
CHANNEL_ID,
7696
"GameDeck Overlay",
7797
NotificationManager.IMPORTANCE_LOW
78-
)
79-
ch.setShowBadge(false)
98+
).apply {
99+
setShowBadge(false)
100+
enableVibration(false)
101+
}
80102
mgr.createNotificationChannel(ch)
81103
}
82104
}
@@ -85,6 +107,7 @@ class OverlayService : Service() {
85107
.setContentText("Overlay running")
86108
.setSmallIcon(android.R.drawable.ic_menu_info_details)
87109
.setOngoing(true)
110+
.setSilent(true)
88111
.build()
89112
}
90113

@@ -150,8 +173,14 @@ private fun Pill() {
150173
.clip(RoundedCornerShape(20.dp))
151174
.background(AccentRedDim)
152175
.border(1.dp, AccentRed, RoundedCornerShape(20.dp))
153-
.padding(horizontal = 12.dp, vertical = 6.dp)
176+
.padding(horizontal = 14.dp, vertical = 7.dp)
154177
) {
155-
Text("-- FPS", color = AccentRedBright, fontSize = 12.sp, fontWeight = FontWeight.SemiBold)
178+
Text(
179+
"-- FPS",
180+
color = AccentRedBright,
181+
fontSize = 12.sp,
182+
fontWeight = FontWeight.SemiBold,
183+
letterSpacing = 1.sp
184+
)
156185
}
157186
}

0 commit comments

Comments
 (0)