GameBar: Add device overlay support and comprehensive documentation

- Implement GameBarConfig for centralized hardware path management
- Replace hardcoded sysfs paths with configurable resources
- Add config.xml with device-specific overlay support
- Update CPU/GPU/RAM/Battery info classes to use config
- README guide with build/integration guide

Signed-off-by: kenway214 <kenway214@outlook.com>
This commit is contained in:
kenway214
2025-10-24 19:54:05 +05:30
parent aca1b4d4ed
commit 605c66b093
9 changed files with 415 additions and 38 deletions

258
README.md
View File

@@ -1 +1,257 @@
Initial Release of GameBar
# GameBar - Real-time Performance Overlay for Android
[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
[![Platform](https://img.shields.io/badge/Platform-Android-green.svg)](https://www.android.com)
[![API](https://img.shields.io/badge/API-33%2B-brightgreen.svg)](https://android-arsenal.com/api?level=33)
GameBar is a comprehensive real-time performance monitoring overlay for Android devices. It provides detailed system metrics including FPS, CPU/GPU usage, temperatures, and memory statistics with a customizable floating overlay.
## Features
- **Real-time FPS Monitoring** - Track frame rates with multiple measurement methods
- **CPU Metrics** - Usage percentage, per-core frequencies, and temperature
- **GPU Metrics** - Usage, clock speed, and temperature
- **Memory Stats** - RAM usage, speed, and temperature
- **Battery Temperature** - Monitor device thermal status
- **Customizable Overlay** - Adjustable position, size, colors, and transparency
- **Per-App Configuration** - Auto-enable GameBar for specific applications
- **Logging & Analytics** - Record and analyze performance data
- **Gesture Controls** - Double-tap screenshot, long-press actions
- **Device-Specific Overlays** - Easy hardware path configuration per device
## Screenshots
*Coming soon*
## Requirements
- Android 13 (API 33) or higher
- System-level permissions (privileged app)
- LineageOS or AOSP-based ROM
## Building
### Prerequisites
- AOSP/LineageOS build environment
- Android SDK Platform 33+
- Soong build system
### Integration into Device Tree
1. **Clone the repository** into your ROM source:
```bash
cd packages/apps
git clone https://github.com/yourusername/GameBar.git
```
2. **Include in device makefile**:
Add to your `device.mk`:
```makefile
# GameBar Performance Overlay
$(call inherit-product, packages/apps/GameBar/gamebar.mk)
```
3. **Create device-specific overlay** (IMPORTANT):
Create the overlay directory structure:
```bash
mkdir -p device/<vendor>/<device>/overlay/packages/apps/GameBar/res/values
```
Create `device/<vendor>/<device>/overlay/packages/apps/GameBar/res/values/config.xml`:
**Example overlay configuration:** [View config.xml example](LINK_HERE)
4. **Configure hardware paths**:
Edit your device overlay `config.xml`:
```xml
<resources>
<!-- Find your device's thermal zones -->
<!-- Run: adb shell "ls /sys/class/thermal/thermal_zone*/type" -->
<!-- CPU temperature path -->
<string name="config_cpu_temp_path">/sys/class/thermal/thermal_zone19/temp</string>
<!-- GPU temperature path -->
<string name="config_gpu_temp_path">/sys/class/kgsl/kgsl-3d0/temp</string>
<!-- RAM temperature path -->
<string name="config_ram_temp_path">/sys/class/thermal/thermal_zone78/temp</string>
<!-- Adjust dividers if needed (usually 1000 for millidegrees, 10 for decidegrees) -->
<integer name="config_cpu_temp_divider">1000</integer>
</resources>
```
5. **Customize init.rc** (if needed):
Edit `packages/apps/GameBar/init/init.gamebar.rc` to match your device's hardware paths.
Ensure permissions are set for all sysfs nodes used by GameBar.
6. **Build**:
```bash
# Clean build (recommended for first build)
m clean
m GameBar
# Or build entire ROM
brunch <device>
```
## Finding Device-Specific Paths
Use these ADB commands to find the correct paths for your device:
```bash
# Find CPU thermal zones
adb shell "for i in /sys/class/thermal/thermal_zone*/type; do echo \$i: \$(cat \$i); done" | grep -i cpu
# Find GPU thermal zones
adb shell "for i in /sys/class/thermal/thermal_zone*/type; do echo \$i: \$(cat \$i); done" | grep -i gpu
# Find RAM/DDR thermal zones
adb shell "for i in /sys/class/thermal/thermal_zone*/type; do echo \$i: \$(cat \$i); done" | grep -i ddr
# Check GPU paths
adb shell "ls -la /sys/class/kgsl/kgsl-3d0/"
# Check FPS path
adb shell "cat /sys/class/drm/sde-crtc-0/measured_fps"
# Check RAM frequency path
adb shell "cat /sys/devices/system/cpu/bus_dcvs/DDR/cur_freq"
```
## Configuration
### Overlay Customization
GameBar supports runtime customization through Settings:
- **Display Options**: Toggle individual metrics (FPS, CPU, GPU, RAM, temperatures)
- **Visual Style**: Adjust text size, colors, background transparency, corner radius
- **Position**: Choose from 9 predefined positions or enable draggable mode
- **Update Interval**: Set refresh rate (500ms - 5000ms)
- **FPS Method**: Choose between new (SurfaceFlinger) or legacy (sysfs) measurement
### Per-App Auto-Enable
Configure GameBar to automatically activate for specific apps:
1. Open GameBar Settings
2. Navigate to "Per-App GameBar" → "Configure Apps"
3. Select apps from the list
4. GameBar will auto-enable when those apps are in foreground
## Usage
### Quick Settings Tile
1. Add GameBar tile to Quick Settings
2. Tap to toggle overlay on/off
3. Long-press tile to open settings
### Gesture Controls
- **Double-tap overlay**: Capture screenshot
- **Single-tap**: Toggle visibility
- **Long-press**: Configurable action (hide, screenshot, or settings)
- **Drag**: Move overlay (when draggable mode enabled)
### Logging & Analytics
GameBar includes comprehensive logging features:
- **Global Logging**: Record all system metrics continuously
- **Per-App Logging**: Separate logs for each configured app
- **Analytics**: View FPS distribution, frame time graphs, temperature charts
- **Export**: Share logs as CSV files
## SELinux Policy
GameBar includes SELinux policies for:
- Access to sysfs nodes (thermal, kgsl, drm)
- Overlay window permissions
- System service interactions
- File provider for log sharing
Policies are automatically included via `sepolicy/SEPolicy.mk`.
## Permissions
Required permissions (granted automatically as system app):
- `SYSTEM_ALERT_WINDOW` - Overlay display
- `PACKAGE_USAGE_STATS` - Foreground app detection
- `WRITE_EXTERNAL_STORAGE` - Log file storage
- `ACCESS_SURFACE_FLINGER` - FPS measurement
- `WRITE_SECURE_SETTINGS` - Configuration persistence
## Troubleshooting
### Overlay not showing
- Check overlay permission in Settings → Apps → GameBar
- Verify SELinux is not blocking (check `adb logcat | grep avc`)
- Ensure init.rc permissions are applied (`adb shell ls -l /sys/class/...`)
### Metrics showing "N/A"
- Verify sysfs paths in overlay config.xml match your device
- Check file permissions: `adb shell cat /sys/class/thermal/thermal_zone*/temp`
- Review logcat for file access errors
### Temperature values incorrect
- Adjust divider values in config.xml
- Most devices use 1000 (millidegrees) or 10 (decidegrees)
- Test: `adb shell cat <temp_path>` and divide manually
### Build errors
- Ensure `org.lineageos.settings.resources` is available in your ROM
- Check SettingsLib is included in build
- Verify all resource files are present in res/ directory
## Contributing
Contributions are welcome! Please:
1. Fork the repository
2. Create a feature branch
3. Make your changes
4. Test on your device
5. Submit a pull request
## License
```
Copyright (C) 2025 kenway214
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
```
## Credits
- Original concept inspired by various gaming overlays
- Built for LineageOS and AOSP-based ROMs
- Community contributions welcome
## Support
- **Issues**: [GitHub Issues](https://github.com/kenway214/packages_apps_GameBar/issues)
- **XDA Thread**: *Coming soon*
- **Telegram**: [Pandemonium](https://t.me/pandemonium_haydn)
---
**Note**: This is a system application that requires privileged access. It must be built as part of your ROM and cannot be installed as a regular APK.

View File

@@ -1,6 +1,31 @@
# GameBar init script
# NOTE: Customize these paths for your device's hardware
on boot
# FPS measurement path (adjust paths)
chown system graphics /sys/class/drm/sde-crtc-0/measured_fps
chmod 0660 /sys/class/drm/sde-crtc-0/measured_fps
# Battery temperature path (adjust paths)
chown system system /sys/class/power_supply/battery/temp
chmod 0660 /sys/class/power_supply/battery/temp
chmod 0660 /sys/class/power_supply/battery/temp
# CPU temperature path (adjust thermal zone number)
chown system system /sys/class/thermal/thermal_zone48/temp
chmod 0660 /sys/class/thermal/thermal_zone48/temp
# GPU paths (adjust paths)
chown system system /sys/class/kgsl/kgsl-3d0/gpu_busy_percentage
chmod 0660 /sys/class/kgsl/kgsl-3d0/gpu_busy_percentage
chown system system /sys/class/kgsl/kgsl-3d0/gpuclk
chmod 0660 /sys/class/kgsl/kgsl-3d0/gpuclk
chown system system /sys/class/kgsl/kgsl-3d0/temp
chmod 0660 /sys/class/kgsl/kgsl-3d0/temp
# RAM frequency path (adjust paths)
chown system system /sys/devices/system/cpu/bus_dcvs/DDR/cur_freq
chmod 0660 /sys/devices/system/cpu/bus_dcvs/DDR/cur_freq
# RAM temperature path (adjust thermal zone number)
chown system system /sys/class/thermal/thermal_zone27/temp
chmod 0660 /sys/class/thermal/thermal_zone27/temp

38
res/values/config.xml Normal file
View File

@@ -0,0 +1,38 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
SPDX-FileCopyrightText: 2025 kenway214
SPDX-License-Identifier: Apache-2.0
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<!-- GameBar Device Paths Configuration -->
<!-- FPS Measurement -->
<string name="config_fps_sysfs_path" translatable="false">/sys/class/drm/sde-crtc-0/measured_fps</string>
<!-- Battery Temperature -->
<string name="config_battery_temp_path" translatable="false">/sys/class/power_supply/battery/temp</string>
<integer name="config_battery_temp_divider">10</integer>
<!-- CPU Configuration -->
<string name="config_cpu_base_path" translatable="false">/sys/devices/system/cpu</string>
<string name="config_cpu_temp_path" translatable="false">/sys/class/thermal/thermal_zone48/temp</string>
<integer name="config_cpu_temp_divider">1000</integer>
<!-- GPU Configuration -->
<string name="config_gpu_usage_path" translatable="false">/sys/class/kgsl/kgsl-3d0/gpu_busy_percentage</string>
<string name="config_gpu_clock_path" translatable="false">/sys/class/kgsl/kgsl-3d0/gpuclk</string>
<string name="config_gpu_temp_path" translatable="false">/sys/class/kgsl/kgsl-3d0/temp</string>
<integer name="config_gpu_temp_divider">1000</integer>
<integer name="config_gpu_clock_divider">1000000</integer>
<!-- RAM Configuration -->
<string name="config_ram_freq_path" translatable="false">/sys/devices/system/cpu/bus_dcvs/DDR/cur_freq</string>
<string name="config_ram_temp_path" translatable="false">/sys/class/thermal/thermal_zone27/temp</string>
<integer name="config_ram_temp_divider">1000</integer>
<!-- Proc filesystem paths -->
<string name="config_proc_stat_path" translatable="false">/proc/stat</string>
<string name="config_proc_meminfo_path" translatable="false">/proc/meminfo</string>
</resources>

View File

@@ -63,8 +63,6 @@ class GameBar private constructor(context: Context) {
return sInstance?.isShowing == true
}
private const val FPS_PATH = "/sys/class/drm/sde-crtc-0/measured_fps"
private const val BATTERY_TEMP_PATH = "/sys/class/power_supply/battery/temp"
private const val PREF_KEY_X = "game_bar_x"
private const val PREF_KEY_Y = "game_bar_y"
private const val TOUCH_SLOP = 30f
@@ -74,6 +72,11 @@ class GameBar private constructor(context: Context) {
private val windowManager: WindowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
private val handler: Handler = Handler(Looper.getMainLooper())
init {
// Initialize config with context
GameBarConfig.init(context)
}
private var overlayView: View? = null
private var rootLayout: LinearLayout? = null
private var layoutParams: WindowManager.LayoutParams? = null
@@ -459,11 +462,11 @@ class GameBar private constructor(context: Context) {
// 2) Battery temp - Always collect for logging
var batteryTempStr = "N/A"
val tmp = readLine(BATTERY_TEMP_PATH)
val tmp = readLine(GameBarConfig.batteryTempPath)
if (!tmp.isNullOrEmpty()) {
try {
val raw = tmp.trim().toInt()
val celsius = raw / 10f
val celsius = raw / GameBarConfig.batteryTempDivider.toFloat()
batteryTempStr = String.format(Locale.getDefault(), "%.1f", celsius)
} catch (ignored: NumberFormatException) {}
}

View File

@@ -0,0 +1,65 @@
/*
* SPDX-FileCopyrightText: 2025 kenway214
* SPDX-License-Identifier: Apache-2.0
*/
package com.android.gamebar
import android.content.Context
/**
* Centralized configuration for GameBar hardware paths and conversion factors.
* All values are loaded from resources to support device-specific overlays.
*/
object GameBarConfig {
private lateinit var context: Context
fun init(ctx: Context) {
context = ctx.applicationContext
}
// FPS paths
val fpsSysfsPath: String
get() = context.getString(R.string.config_fps_sysfs_path)
// Battery configuration
val batteryTempPath: String
get() = context.getString(R.string.config_battery_temp_path)
val batteryTempDivider: Int
get() = context.resources.getInteger(R.integer.config_battery_temp_divider)
// CPU configuration
val cpuBasePath: String
get() = context.getString(R.string.config_cpu_base_path)
val cpuTempPath: String
get() = context.getString(R.string.config_cpu_temp_path)
val cpuTempDivider: Int
get() = context.resources.getInteger(R.integer.config_cpu_temp_divider)
// GPU configuration
val gpuUsagePath: String
get() = context.getString(R.string.config_gpu_usage_path)
val gpuClockPath: String
get() = context.getString(R.string.config_gpu_clock_path)
val gpuTempPath: String
get() = context.getString(R.string.config_gpu_temp_path)
val gpuTempDivider: Int
get() = context.resources.getInteger(R.integer.config_gpu_temp_divider)
val gpuClockDivider: Int
get() = context.resources.getInteger(R.integer.config_gpu_clock_divider)
// RAM configuration
val ramFreqPath: String
get() = context.getString(R.string.config_ram_freq_path)
val ramTempPath: String
get() = context.getString(R.string.config_ram_temp_path)
val ramTempDivider: Int
get() = context.resources.getInteger(R.integer.config_ram_temp_divider)
// Proc filesystem paths
val procStatPath: String
get() = context.getString(R.string.config_proc_stat_path)
val procMeminfoPath: String
get() = context.getString(R.string.config_proc_meminfo_path)
}

View File

@@ -17,10 +17,8 @@ object GameBarCpuInfo {
private var prevIdle = -1L
private var prevTotal = -1L
private const val CPU_TEMP_PATH = "/sys/class/thermal/thermal_zone48/temp"
fun getCpuUsage(): String {
val line = readLine("/proc/stat")
val line = readLine(GameBarConfig.procStatPath)
if (line == null || !line.startsWith("cpu ")) return "N/A"
val parts = line.split("\\s+".toRegex())
if (parts.size < 8) return "N/A"
@@ -56,8 +54,7 @@ object GameBarCpuInfo {
fun getCpuFrequencies(): List<String> {
val result = mutableListOf<String>()
val cpuDirPath = "/sys/devices/system/cpu/"
val cpuDir = File(cpuDirPath)
val cpuDir = File(GameBarConfig.cpuBasePath)
val files = cpuDir.listFiles { _, name -> name.matches(Regex("cpu\\d+")) }
if (files.isNullOrEmpty()) {
return result
@@ -85,12 +82,11 @@ object GameBarCpuInfo {
}
fun getCpuTemp(): String {
val line = readLine(CPU_TEMP_PATH) ?: return "N/A"
val line = readLine(GameBarConfig.cpuTempPath) ?: return "N/A"
val cleanLine = line.trim()
return try {
val raw = cleanLine.toFloat()
// Device reports in millidegrees (e.g., 33849 = 33.849°C)
val celsius = raw / 1000f
val celsius = raw / GameBarConfig.cpuTempDivider.toFloat()
// Sanity check: CPU temp should be between 0 and 150°C
if (celsius > 0f && celsius < 150f) {
String.format(Locale.getDefault(), "%.1f", celsius)

View File

@@ -114,7 +114,7 @@ class GameBarFpsMeter private constructor(context: Context) {
private fun readLegacyFps(): Float {
try {
BufferedReader(FileReader("/sys/class/drm/sde-crtc-0/measured_fps")).use { br ->
BufferedReader(FileReader(GameBarConfig.fpsSysfsPath)).use { br ->
val line = br.readLine()
if (line != null && line.startsWith("fps:")) {
val parts = line.split("\\s+".toRegex())

View File

@@ -12,12 +12,8 @@ import java.io.IOException
object GameBarGpuInfo {
private const val GPU_USAGE_PATH = "/sys/class/kgsl/kgsl-3d0/gpu_busy_percentage"
private const val GPU_CLOCK_PATH = "/sys/class/kgsl/kgsl-3d0/gpuclk"
private const val GPU_TEMP_PATH = "/sys/class/kgsl/kgsl-3d0/temp"
fun getGpuUsage(): String {
val line = readLine(GPU_USAGE_PATH) ?: return "N/A"
val line = readLine(GameBarConfig.gpuUsagePath) ?: return "N/A"
val cleanLine = line.replace("%", "").trim()
return try {
val value = cleanLine.toInt()
@@ -28,26 +24,26 @@ object GameBarGpuInfo {
}
fun getGpuClock(): String {
val line = readLine(GPU_CLOCK_PATH) ?: return "N/A"
val line = readLine(GameBarConfig.gpuClockPath) ?: return "N/A"
val cleanLine = line.trim()
return try {
val hz = cleanLine.toLong()
val mhz = hz / 1_000_000
mhz.toString()
try {
val hz = line.trim().toLong()
val mhz = hz / GameBarConfig.gpuClockDivider
return "$mhz".toString()
} catch (e: NumberFormatException) {
"N/A"
return "N/A"
}
}
fun getGpuTemp(): String {
val line = readLine(GPU_TEMP_PATH) ?: return "N/A"
val line = readLine(GameBarConfig.gpuTempPath) ?: return "N/A"
val cleanLine = line.trim()
return try {
val raw = cleanLine.toFloat()
val celsius = raw / 1000f
String.format("%.1f", celsius)
try {
val raw = line.trim().toInt()
val celsius = raw / GameBarConfig.gpuTempDivider.toFloat()
return String.format(Locale.getDefault(), "%.1f", celsius)
} catch (e: NumberFormatException) {
"N/A"
return "N/A"
}
}

View File

@@ -17,7 +17,7 @@ object GameBarMemInfo {
var memAvailable = 0L
try {
BufferedReader(FileReader("/proc/meminfo")).use { br ->
BufferedReader(FileReader(GameBarConfig.procMeminfoPath)).use { br ->
var line: String?
while (br.readLine().also { line = it } != null) {
line?.let {
@@ -57,9 +57,8 @@ object GameBarMemInfo {
}
fun getRamSpeed(): String {
val path = "/sys/devices/system/cpu/bus_dcvs/DDR/cur_freq"
try {
BufferedReader(FileReader(path)).use { br ->
BufferedReader(FileReader(GameBarConfig.ramFreqPath)).use { br ->
val line = br.readLine()
if (!line.isNullOrEmpty()) {
try {
@@ -81,14 +80,13 @@ object GameBarMemInfo {
}
fun getRamTemp(): String {
val path = "/sys/class/thermal/thermal_zone27/temp"
try {
BufferedReader(FileReader(path)).use { br ->
BufferedReader(FileReader(GameBarConfig.ramTempPath)).use { br ->
val line = br.readLine()
if (!line.isNullOrEmpty()) {
try {
val raw = line.trim().toInt()
val celsius = raw / 1000f
val celsius = raw / GameBarConfig.ramTempDivider.toFloat()
return String.format("%.1f°C", celsius)
} catch (ignored: NumberFormatException) {
}