Compare commits

...

16 commits

Author SHA1 Message Date
kenway214
9e750ef2d1 sm8350-common: Expose aux cameras to com.snapchat.android
Signed-off-by: kenway214 <kenway214@outlook.com>
2025-04-07 23:44:59 +00:00
drkphnx
a6a0bbf8e1 sm8250-common:move gamebar to system settings and add icon as well
Signed-off-by: drkphnx <dark.phnx12@gmail.com>
2025-04-07 23:44:59 +00:00
John Galt
b226c659cb sm8350-common: CameraProvider: set saner values
1500 is WAAAAY too high on haydn. Set max to a saner of 750.

Also prior to this change, 80 was default for torch, so set 80 as
default.
2025-04-07 23:44:59 +00:00
Cyber Knight
c165248466 sm8350-common: sepolicy: Address a cameraserver neverallow
- For some reason, allowing cameraserver to access sysfs_leds on lahaina results in a neverallow.
- Hence, allow cameraserver to access a new type, sysfs_torch which only accesses the relevant nodes we utilize to alleviate the neverallow.

Change-Id: I8625b32f2bb501bbf85f0c026dca22a8e0bcc939
Signed-off-by: Cyber Knight <cyberknight755@gmail.com>
2025-04-07 23:44:49 +00:00
John Galt
51659a044d sm8350-common: camera: Add enabled bool for finished workarounds
Change-Id: Iede122113f17789cdf88896cb32bc30f574ec54f
Signed-off-by: Cyber Knight <cyberknight755@gmail.com>
2025-04-07 23:44:21 +00:00
bengris32
c873db5b07 sm8350-common: camera: Implement setTorchModeExt
Change-Id: Id61420be75b7efd1d13a4b0ee1d103ebd3835516
Signed-off-by: electimon <electimon@gmail.com>
Signed-off-by: Cyber Knight <cyberknight755@gmail.com>
2025-04-07 23:44:04 +00:00
bengris32
77e324ab72 sm8350-common: camera: Implement supportsSetTorchModeExt
Change-Id: I2ec2f4a30723763e6123a1b742468752f38e3d2f
Signed-off-by: bengris32 <bengris32@protonmail.ch>
Signed-off-by: Cyber Knight <cyberknight755@gmail.com>
2025-04-07 23:44:04 +00:00
Dhina17
acf4649586 sm8350-common: Implement torch light control
[RealJohnGalt: Modify for oneplus usage and add toggle switch]
[cyberknight777/ralph950412: Adapt nodes and SEPolicy for Xiaomi SM8350]
[Hexdare: Adapt nodes and SEPolicy for Haydn]

Change-Id: Icd32d1f6aedb55462c9df4d7cc63a2a4c4e4263e
Signed-off-by: ralph950412 <ralph950412@gmail.com>
Signed-off-by: Hexdare <mohammedasimuddin786@gmail.com>
2025-04-07 23:44:04 +00:00
kenway214
3c102e80f4 sm8350-common: Introduce GameBar v2.0
Signed-off-by: kenway214 <kenway214@outlook.com>
Signed-off-by: Hexdare <mohammedasimuddin786@gmail.com>
2025-04-07 23:43:42 +00:00
Chenyang Zhong
9fcc609526 sm8350-common: rootdir: import diag related usb entries
Looks like apps like Network Signal Guru connects to the diag-router
through an emulated(?) USB device.

Signed-off-by: Chenyang Zhong <zhongcy95@gmail.com>
Signed-off-by: 0mar99 <omarag9099@gmail.com>
2025-04-07 23:41:35 +00:00
EndCredits
8fa7797f8d sm8350-common: Allow some domain to access diag hal
Signed-off-by: EndCredits <endcredits@crepuscular-aosp.icu>
Signed-off-by: 0mar99 <omarag9099@gmail.com>
2025-04-07 23:41:35 +00:00
Jack Pham
5684a5e044 sm8350-common: Configure USB Diag over FFS
On targets where diag-router is used, override the
vendor.usb.diag.func.name property to 'ffs' to
instantiate the Diag function instances to use
F_FS instead of the legacy f_diag driver.

Change-Id: I529a081a0d6988628944a9020b61c061baa877a4
2025-04-07 23:41:35 +00:00
danielml
de2fb061fd sm8350-common: Fix vendor.qti.diaghal@1.0 elf checks
Change-Id: I6336f5a2fda3721e03bfc21030a9b1092d0c9828
Signed-off-by: RobertGarciaa <chae0218@naver.com>
2025-04-07 23:41:35 +00:00
Chenyang Zhong
350a96ba76 sm8350-common: Import diag HAL
Apps like Network Signal Guru needs diag HAL.

PR:
PixelExperience-Devices/device_xiaomi_venus#1

Signed-off-by: Chenyang Zhong <zhongcy95@gmail.com>
Signed-off-by: 0mar99 <omarag9099@gmail.com>
Co-Authored-By: David Wheatley <hi@davwheat.dev>
2025-04-07 23:41:27 +00:00
Alucard-Storm
892334b356 sm8350-common: build vendor.qti.hardware.capabilityconfigstore@1.0
F linker  : CANNOT LINK EXECUTABLE "/vendor/bin/hw/vendor.qti.hardware.capabilityconfigstore@1.0-service": library "vendor.qti.hardware.capabilityconfigstore@1.0.so" not found: needed by main executable
2025-04-07 23:39:14 +00:00
johnmart19
d9e11d3f64 sm8350-common: Enable VoNR Calls support 2025-04-07 23:38:38 +00:00
38 changed files with 3762 additions and 0 deletions

View file

@ -46,6 +46,9 @@ TARGET_NO_BOOTLOADER := true
# Display
TARGET_SCREEN_DENSITY ?= 440
# Camera
TARGET_CAMERA_SERVICE_EXT_LIB := //$(COMMON_PATH):libcameraservice_extension.xiaomi_sm8350
# Filesystem
TARGET_FS_CONFIG_GEN := $(COMMON_PATH)/config.fs

22
camera/Android.bp Normal file
View file

@ -0,0 +1,22 @@
//
// Copyright (C) 2022 The LineageOS Project
//
// 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.
cc_library_static {
name: "libcameraservice_extension.xiaomi_sm8350",
srcs: ["CameraProviderExtension.cpp"],
include_dirs: [
"frameworks/av/services/camera/libcameraservice/common"
],
}

View file

@ -0,0 +1,74 @@
/*
* Copyright (C) 2024 LibreMobileOS Foundation
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "CameraProviderExtension.h"
#include <fstream>
#define TORCH_BRIGHTNESS "brightness"
#define TORCH_MAX_BRIGHTNESS "max_brightness"
#define TOGGLE_SWITCH "/sys/devices/platform/soc/c440000.qcom,spmi/spmi-0/spmi0-02/c440000.qcom,spmi:qcom,pm8350c@2:qcom,flash_led@ee00/leds/led:switch_1/brightness"
static std::string kTorchLedPath = "/sys/devices/platform/soc/c440000.qcom,spmi/spmi-0/spmi0-02/c440000.qcom,spmi:qcom,pm8350c@2:qcom,flash_led@ee00/leds/led:torch_1";
/**
* Write value to path and close file.
*/
template <typename T>
static void set(const std::string& path, const T& value) {
std::ofstream file(path);
file << value;
}
/**
* Read value from the path and close file.
*/
template <typename T>
static T get(const std::string& path, const T& def) {
std::ifstream file(path);
T result;
file >> result;
return file.fail() ? def : result;
}
bool supportsTorchStrengthControlExt() {
return true;
}
bool supportsSetTorchModeExt() {
return false;
}
int32_t getTorchDefaultStrengthLevelExt() {
return 80;
}
int32_t getTorchMaxStrengthLevelExt() {
// In our device, both LEDs has same maximum value
// so get from one.
auto node = kTorchLedPath + "/" + TORCH_MAX_BRIGHTNESS;
return 750;
}
int32_t getTorchStrengthLevelExt() {
// We write same value in the both LEDs,
// so get from one.
auto node = kTorchLedPath + "/" + TORCH_BRIGHTNESS;
return get(node, 0);
}
void setTorchStrengthLevelExt(int32_t torchStrength, bool enabled) {
set(TOGGLE_SWITCH, 0);
auto node = kTorchLedPath + "/" + TORCH_BRIGHTNESS;
set(node, torchStrength);
if (enabled)
set(TOGGLE_SWITCH, 255);
}
void setTorchModeExt(bool enabled) {
int32_t strength = getTorchDefaultStrengthLevelExt();
setTorchStrengthLevelExt(enabled ? strength : 0, enabled);
}

View file

@ -127,6 +127,11 @@ PRODUCT_COPY_FILES += \
frameworks/native/data/etc/android.hardware.camera.full.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.camera.full.xml \
frameworks/native/data/etc/android.hardware.camera.raw.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.camera.raw.xml
# Config Store
PRODUCT_PACKAGES += \
vendor.qti.hardware.capabilityconfigstore@1.0 \
vendor.qti.hardware.capabilityconfigstore@1.0.vendor
# DebugFS
PRODUCT_SET_DEBUGFS_RESTRICTIONS := true

View file

@ -40,6 +40,7 @@ lib_fixups: lib_fixups_user_type = {
'libmmosal',
'vendor.qti.hardware.wifidisplaysession@1.0',
'vendor.qti.imsrtpservice@3.0',
'vendor.qti.diaghal@1.0',
): lib_fixup_vendor_suffix,
}

View file

@ -17,6 +17,7 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.lineageos.settings"
xmlns:tools="http://schemas.android.com/tools"
android:versionCode="1"
android:versionName="1.0"
android:sharedUserId="android.uid.system">
@ -24,6 +25,8 @@
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.GET_TASKS" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" tools:ignore="ProtectedPermissions" />
<protected-broadcast android:name="com.android.systemui.doze.pulse" />
@ -203,5 +206,54 @@
android:name="android.service.quicksettings.action.QS_TILE"/>
</intent-filter>
</service>
<!-- GameBar Overlay -->
<activity
android:name=".gameoverlay.GameOverlaySettingsActivity"
android:label="@string/game_overlay_title"
android:theme="@style/Theme.SubSettingsBase"
android:exported="true">
<intent-filter>
<action android:name="com.android.settings.action.IA_SETTINGS" />
</intent-filter>
<meta-data
android:name="com.android.settings.category"
android:value="com.android.settings.category.ia.system" />
<meta-data
android:name="com.android.settings.summary"
android:resource="@string/game_overlay_summary" />
<meta-data
android:name="com.android.settings.icon"
android:resource="@drawable/ic_gamebar" />
</activity>
<!-- GameBar Overlay Tile Service -->
<service
android:name=".gameoverlay.GameOverlayTileService"
android:label="@string/game_overlay_tile_label"
android:icon="@drawable/ic_gamebar_overlay_tile"
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE"
android:exported="true">
<intent-filter>
<action android:name="android.service.quicksettings.action.QS_TILE" />
</intent-filter>
<meta-data
android:name="android.service.quicksettings.TOGGLEABLE_TILE"
android:value="true" />
</service>
<!-- GameBar BootReceiver -->
<receiver
android:name="org.lineageos.settings.gameoverlay.GameOverlayBootReceiver"
android:exported="true"
android:enabled="true"
android:permission="android.permission.RECEIVE_BOOT_COMPLETED">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
<action android:name="android.intent.action.LOCKED_BOOT_COMPLETED" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</receiver>
</application>
</manifest>

View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24.0dp"
android:height="24.0dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0"
android:tint="?android:attr/colorControlNormal">
<path
android:fillColor="#FF000000"
android:pathData="M7,6H17A6,6 0 0,1 23,12A6,6 0 0,1 17,18C15.22,18 13.63,17.23 12.53,16H11.47C10.37,17.23 8.78,18 7,18A6,6 0 0,1 1,12A6,6 0 0,1 7,6M6,9V11H4V13H6V15H8V13H10V11H8V9H6M15.5,12A1.5,1.5 0 0,0 14,13.5A1.5,1.5 0 0,0 15.5,15A1.5,1.5 0 0,0 17,13.5A1.5,1.5 0 0,0 15.5,12M18.5,9A1.5,1.5 0 0,0 17,10.5A1.5,1.5 0 0,0 18.5,12A1.5,1.5 0 0,0 20,10.5A1.5,1.5 0 0,0 18.5,9Z" />
</vector>

View file

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="40dp"
android:height="40dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M189,800Q129,800 86.5,757Q44,714 42,653Q42,644 43,635Q44,626 46,617L130,281Q144,227 187,193.5Q230,160 285,160L675,160Q730,160 773,193.5Q816,227 830,281L914,617Q916,626 917.5,635.5Q919,645 919,654Q919,715 875.5,757.5Q832,800 771,800Q729,800 693,778Q657,756 639,718L611,660Q606,650 596,645Q586,640 575,640L385,640Q374,640 364,645Q354,650 349,660L321,718Q303,756 267,778Q231,800 189,800ZM540.12,433.33Q554.33,433.33 563.83,423.72Q573.33,414.1 573.33,399.88Q573.33,385.67 563.72,376.17Q554.1,366.67 539.88,366.67Q525.67,366.67 516.17,376.28Q506.67,385.9 506.67,400.12Q506.67,414.33 516.28,423.83Q525.9,433.33 540.12,433.33ZM620.12,353.33Q634.33,353.33 643.83,343.72Q653.33,334.1 653.33,319.88Q653.33,305.67 643.72,296.17Q634.1,286.67 619.88,286.67Q605.67,286.67 596.17,296.28Q586.67,305.9 586.67,320.12Q586.67,334.33 596.28,343.83Q605.9,353.33 620.12,353.33ZM620.12,513.33Q634.33,513.33 643.83,503.72Q653.33,494.1 653.33,479.88Q653.33,465.67 643.72,456.17Q634.1,446.67 619.88,446.67Q605.67,446.67 596.17,456.28Q586.67,465.9 586.67,480.12Q586.67,494.33 596.28,503.83Q605.9,513.33 620.12,513.33ZM700.12,433.33Q714.33,433.33 723.83,423.72Q733.33,414.1 733.33,399.88Q733.33,385.67 723.72,376.17Q714.1,366.67 699.88,366.67Q685.67,366.67 676.17,376.28Q666.67,385.9 666.67,400.12Q666.67,414.33 676.28,423.83Q685.9,433.33 700.12,433.33ZM340.08,496.67Q351.67,496.67 359.17,489.11Q366.67,481.56 366.67,470L366.67,426.67L410,426.67Q421.56,426.67 429.11,419.09Q436.67,411.51 436.67,399.92Q436.67,388.33 429.11,380.83Q421.56,373.33 410,373.33L366.67,373.33L366.67,330Q366.67,318.44 359.09,310.89Q351.51,303.33 339.92,303.33Q328.33,303.33 320.83,310.89Q313.33,318.44 313.33,330L313.33,373.33L270,373.33Q258.44,373.33 250.89,380.91Q243.33,388.49 243.33,400.08Q243.33,411.67 250.89,419.17Q258.44,426.67 270,426.67L313.33,426.67L313.33,470Q313.33,481.56 320.91,489.11Q328.49,496.67 340.08,496.67Z"/>
</vector>

View file

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<fragment
android:id="@+id/game_overlay_fragment"
android:name="org.lineageos.settings.gameoverlay.GameOverlayFragment"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>

View file

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/game_overlay_root"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#80000000"
android:padding="8dp"
android:orientation="vertical">
</LinearLayout>

View file

@ -25,4 +25,95 @@
<item>1</item>
<item>2</item>
</string-array>
<!-- Update Interval -->
<string-array name="fps_overlay_update_interval_entries">
<item>Every 500ms</item>
<item>Every second</item>
<item>Every 2 seconds</item>
<item>Every 5 seconds</item>
</string-array>
<string-array name="fps_overlay_update_interval_values">
<item>500</item>
<item>1000</item>
<item>2000</item>
<item>5000</item>
</string-array>
<!-- Position -->
<string-array name="fps_overlay_position_entries">
<item>Top Left</item>
<item>Top Center</item>
<item>Top Right</item>
<item>Bottom Left</item>
<item>Bottom Center</item>
<item>Bottom Right</item>
<item>Custom Draggable</item>
</string-array>
<string-array name="fps_overlay_position_values">
<item>top_left</item>
<item>top_center</item>
<item>top_right</item>
<item>bottom_left</item>
<item>bottom_center</item>
<item>bottom_right</item>
<item>draggable</item>
</string-array>
<!-- Overlay color -->
<string-array name="fps_overlay_color_entries">
<item>White</item>
<item>Crimson</item>
<item>Fruit Salad</item>
<item>Royal Blue</item>
<item>Amber</item>
<item>Teal</item>
<item>Electric Violet</item>
<item>Magenta</item>
</string-array>
<string-array name="fps_overlay_color_values">
<item>#FFFFFF</item>
<item>#DC143C</item>
<item>#4CAF50</item>
<item>#4169E1</item>
<item>#FFBF00</item>
<item>#008080</item>
<item>#8A2BE2</item>
<item>#FF1493</item>
</string-array>
<!-- Overlay format -->
<string-array name="game_overlay_format_entries">
<item>Full</item>
<item>Minimal</item>
</string-array>
<string-array name="game_overlay_format_values">
<item>full</item>
<item>minimal</item>
</string-array>
<!-- Split Mode -->
<string-array name="game_overlay_split_mode_entries">
<item>Side-by-Side</item>
<item>Stacked</item>
</string-array>
<string-array name="game_overlay_split_mode_values">
<item>side_by_side</item>
<item>stacked</item>
</string-array>
<!-- Long press timeouts -->
<string-array name="game_overlay_longpress_entries">
<item>1 second</item>
<item>3 seconds</item>
<item>5 seconds</item>
<item>10 seconds</item>
</string-array>
<string-array name="game_overlay_longpress_values">
<item>1000</item>
<item>3000</item>
<item>5000</item>
<item>10000</item>
</string-array>
</resources>

View file

@ -72,4 +72,13 @@
<string name="htsr_enable_summary">Increases touch polling rate to decrease latency</string>
<string name="htsr_enable_summary_not_supported">Increase Touch Responsiveness is currently not supported by the kernel</string>
<!-- GameBar Overlay -->
<string name="game_overlay_title">GameBar</string>
<string name="game_overlay_summary">Enable the system overlay (FPS, Temp, etc.)</string>
<string name="overlay_permission_required">Overlay permission is required</string>
<string name="overlay_permission_granted">Overlay permission granted</string>
<string name="overlay_permission_denied">Overlay permission denied</string>
<string name="game_overlay_tile_label">GameBar</string>
<string name="game_overlay_tile_description">Toggle the game overlay</string>
</resources>

View file

@ -0,0 +1,221 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:settings="http://schemas.android.com/apk/res-auto">
<SwitchPreference
android:key="game_overlay_enable"
android:title="Enable GameBar Overlay"
android:summary="@string/game_overlay_summary"
android:defaultValue="false" />
<PreferenceCategory
android:title="Overlay Features"
android:dependency="game_overlay_enable">
<SwitchPreference
android:key="game_overlay_fps_enable"
android:title="FPS Overlay"
android:summary="Show current FPS on screen"
android:defaultValue="false" />
<SwitchPreference
android:key="game_overlay_temp_enable"
android:title="Device Temperature"
android:summary="Show device (battery) temperature"
android:defaultValue="false" />
<SwitchPreference
android:key="game_overlay_cpu_usage_enable"
android:title="CPU Usage"
android:summary="Show current CPU usage percentage"
android:defaultValue="false" />
<SwitchPreference
android:key="game_overlay_cpu_clock_enable"
android:title="CPU Clock Speeds"
android:summary="Show current CPU clock speeds for each core"
android:defaultValue="false" />
<SwitchPreference
android:key="game_overlay_cpu_temp_enable"
android:title="CPU Temperature"
android:summary="Show CPU temperature (thermal_zone0)"
android:defaultValue="false" />
<SwitchPreference
android:key="game_overlay_ram_enable"
android:title="RAM Usage"
android:summary="Show current RAM usage in MB"
android:defaultValue="false" />
<SwitchPreference
android:key="game_overlay_gpu_usage_enable"
android:title="GPU Usage"
android:summary="Show GPU usage percentage"
android:defaultValue="false" />
<SwitchPreference
android:key="game_overlay_gpu_clock_enable"
android:title="GPU Clock Speed"
android:summary="Show current GPU clock frequency"
android:defaultValue="false" />
<SwitchPreference
android:key="game_overlay_gpu_temp_enable"
android:title="GPU Temperature"
android:summary="Show current GPU temperature"
android:defaultValue="false" />
</PreferenceCategory>
<PreferenceCategory
android:title="Customization"
android:dependency="game_overlay_enable">
<SeekBarPreference
android:key="game_overlay_text_size"
android:title="Text Size"
android:summary="Adjust the size of overlay text"
android:defaultValue="16"
android:max="32"
android:min="12" />
<SeekBarPreference
android:key="game_overlay_background_alpha"
android:title="Background Transparency"
android:summary="Adjust the transparency of the background"
android:defaultValue="128"
android:max="255"
android:min="0" />
<SeekBarPreference
android:key="game_overlay_corner_radius"
android:title="Overlay Corner Radius"
android:summary="Adjust how rounded the overlay corners should be"
android:defaultValue="16"
android:max="100"
android:min="0" />
<SeekBarPreference
android:key="game_overlay_padding"
android:title="Overlay Padding"
android:summary="Adjust the space around the stats"
android:defaultValue="12"
android:max="64"
android:min="0" />
<SeekBarPreference
android:key="game_overlay_item_spacing"
android:title="Item Spacing"
android:summary="Adjust spacing between overlay lines"
android:defaultValue="8"
android:max="50"
android:min="0" />
<ListPreference
android:key="game_overlay_update_interval"
android:title="Update Interval"
android:summary="Set how often the overlay values update"
android:defaultValue="1000"
android:entries="@array/fps_overlay_update_interval_entries"
android:entryValues="@array/fps_overlay_update_interval_values" />
<ListPreference
android:key="game_overlay_title_color"
android:title="Stat Title Color"
android:summary="Color for 'FPS', 'Temp', 'CPU', etc. text"
android:defaultValue="#FFFFFF"
android:entries="@array/fps_overlay_color_entries"
android:entryValues="@array/fps_overlay_color_values" />
<ListPreference
android:key="game_overlay_value_color"
android:title="Stat Value Color"
android:summary="Color for numeric stats (e.g., '29', '32.0°C')"
android:defaultValue="#4CAF50"
android:entries="@array/fps_overlay_color_entries"
android:entryValues="@array/fps_overlay_color_values" />
<ListPreference
android:key="game_overlay_position"
android:title="Overlay Position"
android:summary="Select the position of the overlay on screen"
android:defaultValue="top_left"
android:entries="@array/fps_overlay_position_entries"
android:entryValues="@array/fps_overlay_position_values" />
<ListPreference
android:key="game_overlay_format"
android:title="Overlay Format"
android:summary="Choose between Full or Minimal display"
android:defaultValue="full"
android:entries="@array/game_overlay_format_entries"
android:entryValues="@array/game_overlay_format_values" />
</PreferenceCategory>
<PreferenceCategory
android:title="Split Config"
android:dependency="game_overlay_enable">
<ListPreference
android:key="game_overlay_split_mode"
android:title="Split Mode"
android:summary="Choose Side-by-Side or Stacked arrangement"
android:defaultValue="stacked"
android:entries="@array/game_overlay_split_mode_entries"
android:entryValues="@array/game_overlay_split_mode_values" />
</PreferenceCategory>
<PreferenceCategory
android:title="Overlay Gesture Controls"
android:dependency="game_overlay_enable">
<SwitchPreference
android:key="game_overlay_single_tap_toggle"
android:title="Enable Single Tap to Toggle"
android:summary="Tap once to switch between full and minimal overlay formats"
android:defaultValue="false" />
<SwitchPreference
android:key="game_overlay_doubletap_capture"
android:title="Enable Double Tap to Capture"
android:summary="Double-tap overlay to start/stop capture logs"
android:defaultValue="false" />
<SwitchPreference
android:key="game_overlay_longpress_enable"
android:title="Enable Long Press"
android:summary="Long-press overlay to access Gamebar settings"
android:defaultValue="false" />
<ListPreference
android:key="game_overlay_longpress_timeout"
android:title="Long Press Duration"
android:summary="Set the duration required to long-press the overlay"
android:defaultValue="1000"
android:entries="@array/game_overlay_longpress_entries"
android:entryValues="@array/game_overlay_longpress_values"
android:dependency="game_overlay_longpress_enable" />
</PreferenceCategory>
<PreferenceCategory
android:title="Capture Logs"
android:dependency="game_overlay_enable">
<Preference
android:key="game_overlay_capture_start"
android:title="Start Logging"
android:summary="Begin capturing FPS and performance data in real-time" />
<Preference
android:key="game_overlay_capture_stop"
android:title="Stop Logging"
android:summary="Stop capturing FPS and performance data in real-time" />
<Preference
android:key="game_overlay_capture_export"
android:title="Export GameBar Log Data"
android:summary="Save the captured FPS and performance data as a CSV file" />
</PreferenceCategory>
</PreferenceScreen>

View file

@ -0,0 +1,90 @@
/*
* Copyright (C) 2025 kenway215
*
* 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.
*/
package org.lineageos.settings;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.provider.Settings;
import android.service.quicksettings.TileService;
import android.util.Log;
import java.util.HashMap;
import java.util.Map;
import org.lineageos.settings.gameoverlay.GameOverlaySettingsActivity;
import org.lineageos.settings.gameoverlay.GameOverlayTileService;
public final class TileHandlerActivity extends Activity {
private static final String TAG = "TileHandlerActivity";
// Map QS Tile services to their corresponding activity
private static final Map<String, Class<?>> TILE_ACTIVITY_MAP = new HashMap<>();
static {
TILE_ACTIVITY_MAP.put(GameOverlayTileService.class.getName(), GameOverlaySettingsActivity.class);
}
@Override
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final Intent intent = getIntent();
if (intent == null || !TileService.ACTION_QS_TILE_PREFERENCES.equals(intent.getAction())) {
Log.e(TAG, "Invalid or null intent received");
finish();
return;
}
final ComponentName qsTile = intent.getParcelableExtra(Intent.EXTRA_COMPONENT_NAME);
if (qsTile == null) {
Log.e(TAG, "No QS tile component found in intent");
finish();
return;
}
final String qsName = qsTile.getClassName();
final Intent targetIntent = new Intent();
// Check if the tile is mapped to an activity
if (TILE_ACTIVITY_MAP.containsKey(qsName)) {
targetIntent.setClass(this, TILE_ACTIVITY_MAP.get(qsName));
Log.d(TAG, "Launching settings activity for QS tile: " + qsName);
} else {
// Default: Open app settings for the QS tile's package
final String packageName = qsTile.getPackageName();
if (packageName == null) {
Log.e(TAG, "QS tile package name is null");
finish();
return;
}
targetIntent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
targetIntent.setData(Uri.fromParts("package", packageName, null));
Log.d(TAG, "Opening app info for package: " + packageName);
}
// Ensure proper navigation behavior
targetIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP |
Intent.FLAG_ACTIVITY_CLEAR_TASK |
Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(targetIntent);
finish();
}
}

View file

@ -0,0 +1,95 @@
/*
* 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.
*/
package org.lineageos.settings.gameoverlay;
import android.app.ActivityManager;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Build;
import android.util.Log;
import java.lang.reflect.Method;
import java.util.List;
public class ForegroundAppDetector {
private static final String TAG = "ForegroundAppDetector";
public static String getForegroundPackageName(Context context) {
String pkg = tryGetRunningTasks(context);
if (pkg != null) {
return pkg;
}
pkg = tryReflectActivityTaskManager();
if (pkg != null) {
return pkg;
}
return "Unknown";
}
private static String tryGetRunningTasks(Context context) {
try {
if (context.checkSelfPermission("android.permission.GET_TASKS")
== PackageManager.PERMISSION_GRANTED) {
ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
List<ActivityManager.RunningTaskInfo> tasks = am.getRunningTasks(1);
if (tasks != null && !tasks.isEmpty()) {
ActivityManager.RunningTaskInfo top = tasks.get(0);
if (top.topActivity != null) {
return top.topActivity.getPackageName();
}
}
} else {
Log.w(TAG, "GET_TASKS permission not granted to this system app?");
}
} catch (Exception e) {
Log.e(TAG, "tryGetRunningTasks error: ", e);
}
return null;
}
private static String tryReflectActivityTaskManager() {
try {
Class<?> atmClass = Class.forName("android.app.ActivityTaskManager");
Method getServiceMethod = atmClass.getDeclaredMethod("getService");
getServiceMethod.setAccessible(true);
Object atmService = getServiceMethod.invoke(null);
Method getTasksMethod = atmService.getClass().getMethod("getTasks", int.class);
@SuppressWarnings("unchecked")
List<?> taskList = (List<?>) getTasksMethod.invoke(atmService, 1);
if (taskList != null && !taskList.isEmpty()) {
Object firstTask = taskList.get(0);
Class<?> rtiClass = firstTask.getClass();
Method getTopActivityMethod = rtiClass.getDeclaredMethod("getTopActivity");
Object compName = getTopActivityMethod.invoke(firstTask);
if (compName != null) {
Method getPackageNameMethod = compName.getClass().getMethod("getPackageName");
String pkgName = (String) getPackageNameMethod.invoke(compName);
return pkgName;
}
}
} catch (Exception e) {
Log.e(TAG, "tryReflectActivityTaskManager error: ", e);
}
return null;
}
}

View file

@ -0,0 +1,132 @@
/*
* 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.
*/
package org.lineageos.settings.gameoverlay;
import android.os.Environment;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;
public class GameDataExport {
private static GameDataExport sInstance;
public static synchronized GameDataExport getInstance() {
if (sInstance == null) {
sInstance = new GameDataExport();
}
return sInstance;
}
private boolean mCapturing = false;
private final List<String[]> mStatsRows = new ArrayList<>();
private static final String[] CSV_HEADER = {
"DateTime",
"PackageName",
"FPS",
"Battery_Temp",
"CPU_Usage",
"CPU_Temp",
"GPU_Usage",
"GPU_Clock",
"GPU_Temp"
};
private GameDataExport() {
}
public void startCapture() {
mCapturing = true;
mStatsRows.clear();
mStatsRows.add(CSV_HEADER);
}
public void stopCapture() {
mCapturing = false;
}
public boolean isCapturing() {
return mCapturing;
}
public void addOverlayData(String dateTime,
String packageName,
String fps,
String batteryTemp,
String cpuUsage,
String cpuTemp,
String gpuUsage,
String gpuClock,
String gpuTemp) {
if (!mCapturing) return;
String[] row = {
dateTime,
packageName,
fps,
batteryTemp,
cpuUsage,
cpuTemp,
gpuUsage,
gpuClock,
gpuTemp
};
mStatsRows.add(row);
}
public void exportDataToCsv() {
if (mStatsRows.size() <= 1) {
return;
}
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(new Date());
File outFile = new File(Environment.getExternalStorageDirectory(), "GameBar_log_" + timeStamp + ".csv");
BufferedWriter bw = null;
try {
bw = new BufferedWriter(new FileWriter(outFile, true));
for (String[] row : mStatsRows) {
bw.write(toCsvLine(row));
bw.newLine();
}
bw.flush();
} catch (IOException ignored) {
} finally {
if (bw != null) {
try { bw.close(); } catch (IOException ignored) {}
}
}
}
private String toCsvLine(String[] columns) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < columns.length; i++) {
sb.append(columns[i]);
if (i < columns.length - 1) {
sb.append(",");
}
}
return sb.toString();
}
}

View file

@ -0,0 +1,776 @@
/*
* 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.
*/
package org.lineageos.settings.gameoverlay;
import android.app.usage.UsageStatsManager;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.Color;
import android.graphics.PixelFormat;
import android.graphics.drawable.GradientDrawable;
import android.os.Handler;
import android.os.Looper;
import android.provider.Settings;
import android.util.TypedValue;
import android.view.GestureDetector;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.WindowManager;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import androidx.preference.PreferenceManager;
import org.lineageos.settings.R;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;
public class GameOverlay {
private static GameOverlay sInstance;
public static synchronized GameOverlay getInstance(Context context) {
if (sInstance == null) {
sInstance = new GameOverlay(context.getApplicationContext());
}
return sInstance;
}
private static final String FPS_PATH = "/sys/class/drm/sde-crtc-0/measured_fps";
private static final String BATTERY_TEMP_PATH= "/sys/class/power_supply/battery/temp";
private static final String PREF_KEY_X = "game_overlay_x";
private static final String PREF_KEY_Y = "game_overlay_y";
private final Context mContext;
private final WindowManager mWindowManager;
private final Handler mHandler;
private View mOverlayView;
private LinearLayout mRootLayout;
private WindowManager.LayoutParams mLayoutParams;
private boolean mIsShowing = false;
private int mTextSizeSp = 16;
private int mBackgroundAlpha = 128;
private int mCornerRadius = 16;
private int mPaddingDp = 12;
private String mTitleColorHex = "#FFFFFF";
private String mValueColorHex = "#FFFFFF";
private String mPosition = "top_left";
private String mSplitMode = "stacked";
private String mOverlayFormat = "full";
private int mUpdateIntervalMs = 1000;
private boolean mDraggable = false;
private boolean mShowBatteryTemp= false;
private boolean mShowCpuUsage = false;
private boolean mShowCpuClock = false;
private boolean mShowCpuTemp = false;
private boolean mShowRam = false;
private boolean mShowFps = false;
private boolean mShowGpuUsage = false;
private boolean mShowGpuClock = false;
private boolean mShowGpuTemp = false;
private boolean mLongPressEnabled = false;
private long mLongPressThresholdMs = 1000;
private boolean mPressActive = false;
private float mDownX, mDownY;
private static final float TOUCH_SLOP = 20f;
private GestureDetector mGestureDetector;
private boolean mDoubleTapCaptureEnabled = false;
private boolean mSingleTapToggleEnabled = false;
private GradientDrawable mBgDrawable;
private int mItemSpacingDp = 8;
private final Runnable mLongPressRunnable = new Runnable() {
@Override
public void run() {
if (mPressActive) {
openOverlaySettings();
mPressActive = false;
}
}
};
private final Runnable mUpdateRunnable = new Runnable() {
@Override
public void run() {
if (mIsShowing) {
updateStats();
mHandler.postDelayed(this, mUpdateIntervalMs);
}
}
};
private GameOverlay(Context context) {
mContext = context;
mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
mHandler = new Handler(Looper.getMainLooper());
mBgDrawable = new GradientDrawable();
applyBackgroundStyle();
}
public void applyPreferences() {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mContext);
mShowFps = prefs.getBoolean("game_overlay_fps_enable", false);
mShowBatteryTemp = prefs.getBoolean("game_overlay_temp_enable", false);
mShowCpuUsage = prefs.getBoolean("game_overlay_cpu_usage_enable", false);
mShowCpuClock = prefs.getBoolean("game_overlay_cpu_clock_enable", false);
mShowCpuTemp = prefs.getBoolean("game_overlay_cpu_temp_enable", false);
mShowRam = prefs.getBoolean("game_overlay_ram_enable", false);
mShowGpuUsage = prefs.getBoolean("game_overlay_gpu_usage_enable", false);
mShowGpuClock = prefs.getBoolean("game_overlay_gpu_clock_enable", false);
mShowGpuTemp = prefs.getBoolean("game_overlay_gpu_temp_enable", false);
mDoubleTapCaptureEnabled = prefs.getBoolean("game_overlay_doubletap_capture", false);
mSingleTapToggleEnabled = prefs.getBoolean("game_overlay_single_tap_toggle", false);
updateSplitMode(prefs.getString("game_overlay_split_mode", "stacked"));
updateTextSize(prefs.getInt("game_overlay_text_size", 16));
updateBackgroundAlpha(prefs.getInt("game_overlay_background_alpha", 128));
updateCornerRadius(prefs.getInt("game_overlay_corner_radius", 16));
updatePadding(prefs.getInt("game_overlay_padding", 12));
updateTitleColor(prefs.getString("game_overlay_title_color", "#FFFFFF"));
updateValueColor(prefs.getString("game_overlay_value_color", "#4CAF50"));
updateOverlayFormat(prefs.getString("game_overlay_format", "full"));
updateUpdateInterval(prefs.getString("game_overlay_update_interval", "1000"));
updatePosition(prefs.getString("game_overlay_position", "top_left"));
int spacing = prefs.getInt("game_overlay_item_spacing", 8);
updateItemSpacing(spacing);
mLongPressEnabled = prefs.getBoolean("game_overlay_longpress_enable", false);
String lpTimeoutStr = prefs.getString("game_overlay_longpress_timeout", "1000");
try {
long lpt = Long.parseLong(lpTimeoutStr);
setLongPressThresholdMs(lpt);
} catch (NumberFormatException ignored) {}
}
public void show() {
if (mIsShowing) return;
applyPreferences();
mLayoutParams = new WindowManager.LayoutParams(
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
PixelFormat.TRANSLUCENT
);
if ("draggable".equals(mPosition)) {
mDraggable = true;
loadSavedPosition(mLayoutParams);
if (mLayoutParams.x == 0 && mLayoutParams.y == 0) {
mLayoutParams.gravity = Gravity.TOP | Gravity.START;
mLayoutParams.x = 0;
mLayoutParams.y = 100;
}
} else {
mDraggable = false;
applyPosition(mLayoutParams, mPosition);
}
mOverlayView = new LinearLayout(mContext);
mOverlayView.setLayoutParams(new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT
));
mRootLayout = (LinearLayout) mOverlayView;
applySplitMode();
applyBackgroundStyle();
applyPadding();
mGestureDetector = new GestureDetector(mContext, new GestureDetector.SimpleOnGestureListener() {
@Override
public boolean onDoubleTap(MotionEvent e) {
if (mDoubleTapCaptureEnabled) {
if (GameDataExport.getInstance().isCapturing()) {
GameDataExport.getInstance().stopCapture();
Toast.makeText(mContext, "Capture Stopped", Toast.LENGTH_SHORT).show();
} else {
GameDataExport.getInstance().startCapture();
Toast.makeText(mContext, "Capture Started", Toast.LENGTH_SHORT).show();
}
return true;
}
return super.onDoubleTap(e);
}
@Override
public boolean onSingleTapConfirmed(MotionEvent e) {
if (mSingleTapToggleEnabled) {
mOverlayFormat = "full".equals(mOverlayFormat) ? "minimal" : "full";
Toast.makeText(mContext, "Overlay Format: " + mOverlayFormat, Toast.LENGTH_SHORT).show();
updateStats();
return true;
}
return super.onSingleTapConfirmed(e);
}
});
mOverlayView.setOnTouchListener((v, event) -> {
if (mGestureDetector != null && mGestureDetector.onTouchEvent(event)) {
return true;
}
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
if (mDraggable) {
initialX = mLayoutParams.x;
initialY = mLayoutParams.y;
initialTouchX = event.getRawX();
initialTouchY = event.getRawY();
}
if (mLongPressEnabled) {
mPressActive = true;
mDownX = event.getRawX();
mDownY = event.getRawY();
mHandler.postDelayed(mLongPressRunnable, mLongPressThresholdMs);
}
return true;
case MotionEvent.ACTION_MOVE:
if (mLongPressEnabled && mPressActive) {
float dx = Math.abs(event.getRawX() - mDownX);
float dy = Math.abs(event.getRawY() - mDownY);
if (dx > TOUCH_SLOP || dy > TOUCH_SLOP) {
mPressActive = false;
mHandler.removeCallbacks(mLongPressRunnable);
}
}
if (mDraggable) {
int deltaX = (int) (event.getRawX() - initialTouchX);
int deltaY = (int) (event.getRawY() - initialTouchY);
mLayoutParams.x = initialX + deltaX;
mLayoutParams.y = initialY + deltaY;
mWindowManager.updateViewLayout(mOverlayView, mLayoutParams);
}
return true;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
if (mLongPressEnabled && mPressActive) {
mPressActive = false;
mHandler.removeCallbacks(mLongPressRunnable);
}
if (mDraggable) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mContext);
prefs.edit()
.putInt(PREF_KEY_X, mLayoutParams.x)
.putInt(PREF_KEY_Y, mLayoutParams.y)
.apply();
}
return true;
}
return false;
});
mWindowManager.addView(mOverlayView, mLayoutParams);
mIsShowing = true;
startUpdates();
}
private int initialX, initialY;
private float initialTouchX, initialTouchY;
public void hide() {
if (!mIsShowing) return;
mHandler.removeCallbacksAndMessages(null);
if (mOverlayView != null) {
mWindowManager.removeView(mOverlayView);
mOverlayView = null;
}
mIsShowing = false;
}
private void updateStats() {
if (!mIsShowing || mRootLayout == null) return;
mRootLayout.removeAllViews();
List<View> statViews = new ArrayList<>();
// 1) FPS
float fpsVal = parseFps();
String fpsStr = fpsVal >= 0 ? String.format(Locale.getDefault(), "%.1f", fpsVal) : "N/A";
if (mShowFps) {
statViews.add(createStatLine("FPS", fpsStr));
}
// 2) Battery temp
String batteryTempStr = "N/A";
if (mShowBatteryTemp) {
String tmp = readLine(BATTERY_TEMP_PATH);
if (tmp != null && !tmp.isEmpty()) {
try {
int raw = Integer.parseInt(tmp.trim());
float c = raw / 10f;
batteryTempStr = String.format(Locale.getDefault(), "%.1f", c);
} catch (NumberFormatException ignored) {}
}
statViews.add(createStatLine("Temp", batteryTempStr + "°C"));
}
// 3) CPU usage
String cpuUsageStr = "N/A";
if (mShowCpuUsage) {
cpuUsageStr = GameOverlayCpuInfo.getCpuUsage();
String display = "N/A".equals(cpuUsageStr) ? "N/A" : cpuUsageStr + "%";
statViews.add(createStatLine("CPU", display));
}
// 4) CPU freq
if (mShowCpuClock) {
List<String> freqs = GameOverlayCpuInfo.getCpuFrequencies();
if (!freqs.isEmpty()) {
statViews.add(buildCpuFreqView(freqs));
}
}
// 5) CPU temp
String cpuTempStr = "N/A";
if (mShowCpuTemp) {
cpuTempStr = GameOverlayCpuInfo.getCpuTemp();
statViews.add(createStatLine("CPU Temp", "N/A".equals(cpuTempStr) ? "N/A" : cpuTempStr + "°C"));
}
// 6) RAM usage
String ramStr = "N/A";
if (mShowRam) {
ramStr = GameOverlayMemInfo.getRamUsage();
statViews.add(createStatLine("RAM", "N/A".equals(ramStr) ? "N/A" : ramStr + " MB"));
}
// 7) GPU usage
String gpuUsageStr = "N/A";
if (mShowGpuUsage) {
gpuUsageStr = GameOverlayGpuInfo.getGpuUsage();
statViews.add(createStatLine("GPU", "N/A".equals(gpuUsageStr) ? "N/A" : gpuUsageStr + "%"));
}
// 8) GPU clock
String gpuClockStr = "N/A";
if (mShowGpuClock) {
gpuClockStr = GameOverlayGpuInfo.getGpuClock();
statViews.add(createStatLine("GPU Freq", "N/A".equals(gpuClockStr) ? "N/A" : gpuClockStr + "MHz"));
}
// 9) GPU temp
String gpuTempStr = "N/A";
if (mShowGpuTemp) {
gpuTempStr = GameOverlayGpuInfo.getGpuTemp();
statViews.add(createStatLine("GPU Temp", "N/A".equals(gpuTempStr) ? "N/A" : gpuTempStr + "°C"));
}
if ("side_by_side".equals(mSplitMode)) {
mRootLayout.setOrientation(LinearLayout.HORIZONTAL);
if ("minimal".equals(mOverlayFormat)) {
for (int i = 0; i < statViews.size(); i++) {
mRootLayout.addView(statViews.get(i));
if (i < statViews.size() - 1) {
mRootLayout.addView(createDotView());
}
}
} else {
for (View view : statViews) {
mRootLayout.addView(view);
}
}
} else {
mRootLayout.setOrientation(LinearLayout.VERTICAL);
for (View view : statViews) {
mRootLayout.addView(view);
}
}
if (GameDataExport.getInstance().isCapturing()) {
String dateTime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()).format(new Date());
String pkgName = ForegroundAppDetector.getForegroundPackageName(mContext);
GameDataExport.getInstance().addOverlayData(
dateTime,
pkgName, // PackageName
fpsStr, // FPS
batteryTempStr, // Battery_Temp
cpuUsageStr, // CPU_Usage
cpuTempStr, // CPU_Temp
gpuUsageStr, // GPU_Usage
gpuClockStr, // GPU_Clock
gpuTempStr // GPU_Temp
);
}
if (mLayoutParams != null) {
mWindowManager.updateViewLayout(mOverlayView, mLayoutParams);
}
}
private View buildCpuFreqView(List<String> freqs) {
LinearLayout freqContainer = new LinearLayout(mContext);
freqContainer.setOrientation(LinearLayout.HORIZONTAL);
int spacingPx = dpToPx(mContext, mItemSpacingDp);
LinearLayout.LayoutParams outerLp = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT
);
outerLp.setMargins(spacingPx, spacingPx/2, spacingPx, spacingPx/2);
freqContainer.setLayoutParams(outerLp);
if ("full".equals(mOverlayFormat)) {
TextView labelTv = new TextView(mContext);
labelTv.setTextSize(TypedValue.COMPLEX_UNIT_SP, mTextSizeSp);
try {
labelTv.setTextColor(Color.parseColor(mTitleColorHex));
} catch (Exception e) {
labelTv.setTextColor(Color.WHITE);
}
labelTv.setText("CPU Freq ");
freqContainer.addView(labelTv);
}
LinearLayout verticalFreqs = new LinearLayout(mContext);
verticalFreqs.setOrientation(LinearLayout.VERTICAL);
for (String freqLine : freqs) {
LinearLayout lineLayout = new LinearLayout(mContext);
lineLayout.setOrientation(LinearLayout.HORIZONTAL);
TextView freqTv = new TextView(mContext);
freqTv.setTextSize(TypedValue.COMPLEX_UNIT_SP, mTextSizeSp);
try {
freqTv.setTextColor(Color.parseColor(mValueColorHex));
} catch (Exception e) {
freqTv.setTextColor(Color.WHITE);
}
freqTv.setText(freqLine);
lineLayout.addView(freqTv);
LinearLayout.LayoutParams lineLp = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT
);
lineLp.setMargins(spacingPx, spacingPx/4, spacingPx, spacingPx/4);
lineLayout.setLayoutParams(lineLp);
verticalFreqs.addView(lineLayout);
}
freqContainer.addView(verticalFreqs);
return freqContainer;
}
private LinearLayout createStatLine(String title, String rawValue) {
LinearLayout lineLayout = new LinearLayout(mContext);
lineLayout.setOrientation(LinearLayout.HORIZONTAL);
if ("full".equals(mOverlayFormat)) {
TextView tvTitle = new TextView(mContext);
tvTitle.setTextSize(TypedValue.COMPLEX_UNIT_SP, mTextSizeSp);
try {
tvTitle.setTextColor(Color.parseColor(mTitleColorHex));
} catch (Exception e) {
tvTitle.setTextColor(Color.WHITE);
}
tvTitle.setText(title.isEmpty() ? "" : title + " ");
TextView tvValue = new TextView(mContext);
tvValue.setTextSize(TypedValue.COMPLEX_UNIT_SP, mTextSizeSp);
try {
tvValue.setTextColor(Color.parseColor(mValueColorHex));
} catch (Exception e) {
tvValue.setTextColor(Color.WHITE);
}
tvValue.setText(rawValue);
lineLayout.addView(tvTitle);
lineLayout.addView(tvValue);
} else {
TextView tvMinimal = new TextView(mContext);
tvMinimal.setTextSize(TypedValue.COMPLEX_UNIT_SP, mTextSizeSp);
try {
tvMinimal.setTextColor(Color.parseColor(mValueColorHex));
} catch (Exception e) {
tvMinimal.setTextColor(Color.WHITE);
}
tvMinimal.setText(rawValue);
lineLayout.addView(tvMinimal);
}
int spacingPx = dpToPx(mContext, mItemSpacingDp);
LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT
);
lp.setMargins(spacingPx, spacingPx/2, spacingPx, spacingPx/2);
lineLayout.setLayoutParams(lp);
return lineLayout;
}
private View createDotView() {
TextView dotView = new TextView(mContext);
dotView.setTextSize(TypedValue.COMPLEX_UNIT_SP, mTextSizeSp);
try {
dotView.setTextColor(Color.parseColor(mValueColorHex));
} catch (Exception e) {
dotView.setTextColor(Color.WHITE);
}
dotView.setText(" . ");
return dotView;
}
private float parseFps() {
String line = readLine(FPS_PATH);
if (line != null && line.startsWith("fps:")) {
String[] parts = line.split("\\s+");
if (parts.length >= 2) {
try {
return Float.parseFloat(parts[1].trim());
} catch (NumberFormatException ignored) {}
}
}
return -1f;
}
public void setShowBatteryTemp(boolean show) { mShowBatteryTemp = show; }
public void setShowCpuUsage(boolean show) { mShowCpuUsage = show; }
public void setShowCpuClock(boolean show) { mShowCpuClock = show; }
public void setShowCpuTemp(boolean show) { mShowCpuTemp = show; }
public void setShowRam(boolean show) { mShowRam = show; }
public void setShowFps(boolean show) { mShowFps = show; }
public void setShowGpuUsage(boolean show) { mShowGpuUsage = show; }
public void setShowGpuClock(boolean show) { mShowGpuClock = show; }
public void setShowGpuTemp(boolean show) { mShowGpuTemp = show; }
public void updateTextSize(int sp) {
mTextSizeSp = sp;
}
public void updateCornerRadius(int radius) {
mCornerRadius = radius;
applyBackgroundStyle();
}
public void updateBackgroundAlpha(int alpha) {
mBackgroundAlpha = alpha;
applyBackgroundStyle();
}
public void updatePadding(int dp) {
mPaddingDp = dp;
applyPadding();
}
public void updateTitleColor(String hex) {
mTitleColorHex = hex;
}
public void updateValueColor(String hex) {
mValueColorHex = hex;
}
public void updateOverlayFormat(String format) {
mOverlayFormat = format;
if (mIsShowing) {
updateStats();
}
}
public void updateItemSpacing(int dp) {
mItemSpacingDp = dp;
if (mIsShowing) {
updateStats();
}
}
private void applyBackgroundStyle() {
int color = Color.argb(mBackgroundAlpha, 0, 0, 0);
mBgDrawable.setColor(color);
mBgDrawable.setCornerRadius(mCornerRadius);
if (mOverlayView != null) {
mOverlayView.setBackground(mBgDrawable);
}
}
private void applyPadding() {
if (mRootLayout != null) {
int px = dpToPx(mContext, mPaddingDp);
mRootLayout.setPadding(px, px, px, px);
}
}
public void updatePosition(String pos) {
mPosition = pos;
if (mIsShowing && mOverlayView != null && mLayoutParams != null) {
if ("draggable".equals(mPosition)) {
mDraggable = true;
loadSavedPosition(mLayoutParams);
if (mLayoutParams.x == 0 && mLayoutParams.y == 0) {
mLayoutParams.gravity = Gravity.TOP | Gravity.START;
mLayoutParams.x = 0;
mLayoutParams.y = 100;
}
} else {
mDraggable = false;
applyPosition(mLayoutParams, mPosition);
}
mWindowManager.updateViewLayout(mOverlayView, mLayoutParams);
}
}
public void updateSplitMode(String mode) {
mSplitMode = mode;
if (mIsShowing && mOverlayView != null) {
applySplitMode();
updateStats();
}
}
public void updateUpdateInterval(String intervalStr) {
try {
mUpdateIntervalMs = Integer.parseInt(intervalStr);
} catch (NumberFormatException e) {
mUpdateIntervalMs = 1000;
}
if (mIsShowing) {
startUpdates();
}
}
public void setLongPressEnabled(boolean enabled) {
mLongPressEnabled = enabled;
}
public void setLongPressThresholdMs(long ms) {
mLongPressThresholdMs = ms;
}
public void setDoubleTapCaptureEnabled(boolean enabled) {
mDoubleTapCaptureEnabled = enabled;
}
public void setSingleTapToggleEnabled(boolean enabled) {
mSingleTapToggleEnabled = enabled;
}
private void startUpdates() {
mHandler.removeCallbacksAndMessages(null);
mHandler.post(mUpdateRunnable);
}
private void applySplitMode() {
if (mRootLayout == null) return;
if ("side_by_side".equals(mSplitMode)) {
mRootLayout.setOrientation(LinearLayout.HORIZONTAL);
} else {
mRootLayout.setOrientation(LinearLayout.VERTICAL);
}
}
private void loadSavedPosition(WindowManager.LayoutParams lp) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mContext);
int savedX = prefs.getInt(PREF_KEY_X, Integer.MIN_VALUE);
int savedY = prefs.getInt(PREF_KEY_Y, Integer.MIN_VALUE);
if (savedX != Integer.MIN_VALUE && savedY != Integer.MIN_VALUE) {
lp.gravity = Gravity.TOP | Gravity.START;
lp.x = savedX;
lp.y = savedY;
}
}
private void applyPosition(WindowManager.LayoutParams lp, String pos) {
switch (pos) {
case "top_left":
lp.gravity = Gravity.TOP | Gravity.START;
lp.x = 0;
lp.y = 100;
break;
case "top_center":
lp.gravity = Gravity.TOP | Gravity.CENTER_HORIZONTAL;
lp.y = 100;
break;
case "top_right":
lp.gravity = Gravity.TOP | Gravity.END;
lp.x = 0;
lp.y = 100;
break;
case "bottom_left":
lp.gravity = Gravity.BOTTOM | Gravity.START;
lp.x = 0;
lp.y = 100;
break;
case "bottom_center":
lp.gravity = Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL;
lp.y = 100;
break;
case "bottom_right":
lp.gravity = Gravity.BOTTOM | Gravity.END;
lp.x = 0;
lp.y = 100;
break;
default:
lp.gravity = Gravity.TOP | Gravity.START;
lp.x = 0;
lp.y = 100;
break;
}
}
private String readLine(String path) {
try (BufferedReader br = new BufferedReader(new FileReader(path))) {
return br.readLine();
} catch (IOException e) {
return null;
}
}
private void openOverlaySettings() {
try {
Intent intent = new Intent(mContext, GameOverlaySettingsActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mContext.startActivity(intent);
} catch (Exception e) {
}
}
private static int dpToPx(Context context, int dp) {
float scale = context.getResources().getDisplayMetrics().density;
return Math.round(dp * scale);
}
}

View file

@ -0,0 +1,46 @@
/*
* 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.
*/
package org.lineageos.settings.gameoverlay;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import androidx.preference.PreferenceManager;
public class GameOverlayBootReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (Intent.ACTION_BOOT_COMPLETED.equals(action)
|| Intent.ACTION_LOCKED_BOOT_COMPLETED.equals(action)) {
restoreOverlayState(context);
}
}
private void restoreOverlayState(Context context) {
var prefs = PreferenceManager.getDefaultSharedPreferences(context);
boolean enabled = prefs.getBoolean("game_overlay_enable", false);
GameOverlay overlay = GameOverlay.getInstance(context);
if (!enabled) {
overlay.hide();
return;
}
overlay.applyPreferences();
overlay.show();
}
}

View file

@ -0,0 +1,130 @@
/*
* 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.
*/
package org.lineageos.settings.gameoverlay;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
public class GameOverlayCpuInfo {
private static long sPrevIdle = -1;
private static long sPrevTotal = -1;
private static final String CPU_TEMP_PATH = "/sys/class/thermal/thermal_zone0/temp";
public static String getCpuUsage() {
String line = readLine("/proc/stat");
if (line == null || !line.startsWith("cpu ")) return "N/A";
String[] parts = line.split("\\s+");
if (parts.length < 8) return "N/A";
try {
long user = Long.parseLong(parts[1]);
long nice = Long.parseLong(parts[2]);
long system = Long.parseLong(parts[3]);
long idle = Long.parseLong(parts[4]);
long iowait = Long.parseLong(parts[5]);
long irq = Long.parseLong(parts[6]);
long softirq = Long.parseLong(parts[7]);
long steal = parts.length > 8 ? Long.parseLong(parts[8]) : 0;
long total = user + nice + system + idle + iowait + irq + softirq + steal;
if (sPrevTotal != -1 && total != sPrevTotal) {
long diffTotal = total - sPrevTotal;
long diffIdle = idle - sPrevIdle;
long usage = 100 * (diffTotal - diffIdle) / diffTotal;
sPrevTotal = total;
sPrevIdle = idle;
return String.valueOf(usage);
} else {
sPrevTotal = total;
sPrevIdle = idle;
return "N/A";
}
} catch (NumberFormatException e) {
return "N/A";
}
}
public static List<String> getCpuFrequencies() {
List<String> result = new ArrayList<>();
String cpuDirPath = "/sys/devices/system/cpu/";
java.io.File cpuDir = new java.io.File(cpuDirPath);
java.io.File[] files = cpuDir.listFiles((dir, name) -> name.matches("cpu\\d+"));
if (files == null || files.length == 0) {
return result;
}
List<java.io.File> cpuFolders = new ArrayList<>();
Collections.addAll(cpuFolders, files);
cpuFolders.sort(Comparator.comparingInt(GameOverlayCpuInfo::extractCpuNumber));
for (java.io.File cpu : cpuFolders) {
String freqPath = cpu.getAbsolutePath() + "/cpufreq/scaling_cur_freq";
String freqStr = readLine(freqPath);
if (freqStr != null && !freqStr.isEmpty()) {
try {
int khz = Integer.parseInt(freqStr.trim());
int mhz = khz / 1000;
result.add(cpu.getName() + ": " + mhz + " MHz");
} catch (NumberFormatException e) {
result.add(cpu.getName() + ": N/A");
}
} else {
result.add(cpu.getName() + ": offline or frequency not available");
}
}
return result;
}
public static String getCpuTemp() {
String line = readLine(CPU_TEMP_PATH);
if (line == null) return "N/A";
line = line.trim();
try {
float raw = Float.parseFloat(line);
float c = raw / 1000f;
return String.format("%.1f", c);
} catch (NumberFormatException e) {
return "N/A";
}
}
private static int extractCpuNumber(java.io.File cpuFolder) {
String name = cpuFolder.getName().replace("cpu", "");
try {
return Integer.parseInt(name);
} catch (NumberFormatException e) {
return -1;
}
}
private static String readLine(String path) {
try (BufferedReader br = new BufferedReader(new FileReader(path))) {
return br.readLine();
} catch (IOException e) {
return null;
}
}
}

View file

@ -0,0 +1,358 @@
/*
* 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.
*/
package org.lineageos.settings.gameoverlay;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.provider.Settings;
import android.widget.Toast;
import androidx.preference.ListPreference;
import androidx.preference.Preference;
import androidx.preference.PreferenceFragmentCompat;
import androidx.preference.SeekBarPreference;
import androidx.preference.SwitchPreference;
import org.lineageos.settings.R;
public class GameOverlayFragment extends PreferenceFragmentCompat {
private GameOverlay mOverlay;
private SwitchPreference mMasterSwitch;
private SwitchPreference mFpsSwitch;
private SwitchPreference mBatteryTempSwitch;
private SwitchPreference mCpuUsageSwitch;
private SwitchPreference mCpuClockSwitch;
private SwitchPreference mCpuTempSwitch;
private SwitchPreference mRamSwitch;
private SwitchPreference mGpuUsageSwitch;
private SwitchPreference mGpuClockSwitch;
private SwitchPreference mGpuTempSwitch;
private Preference mCaptureStartPref;
private Preference mCaptureStopPref;
private Preference mCaptureExportPref;
private SwitchPreference mDoubleTapCapturePref;
private SwitchPreference mSingleTapTogglePref;
private SwitchPreference mLongPressEnablePref;
private ListPreference mLongPressTimeoutPref;
private SeekBarPreference mTextSizePref;
private SeekBarPreference mBgAlphaPref;
private SeekBarPreference mCornerRadiusPref;
private SeekBarPreference mPaddingPref;
private SeekBarPreference mItemSpacingPref;
private ListPreference mUpdateIntervalPref;
private ListPreference mTextColorPref;
private ListPreference mTitleColorPref;
private ListPreference mValueColorPref;
private ListPreference mPositionPref;
private ListPreference mSplitModePref;
private ListPreference mOverlayFormatPref;
@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
setPreferencesFromResource(R.xml.game_overlay_preferences, rootKey);
mOverlay = GameOverlay.getInstance(getContext());
mMasterSwitch = findPreference("game_overlay_enable");
mFpsSwitch = findPreference("game_overlay_fps_enable");
mBatteryTempSwitch = findPreference("game_overlay_temp_enable");
mCpuUsageSwitch = findPreference("game_overlay_cpu_usage_enable");
mCpuClockSwitch = findPreference("game_overlay_cpu_clock_enable");
mCpuTempSwitch = findPreference("game_overlay_cpu_temp_enable");
mRamSwitch = findPreference("game_overlay_ram_enable");
mGpuUsageSwitch = findPreference("game_overlay_gpu_usage_enable");
mGpuClockSwitch = findPreference("game_overlay_gpu_clock_enable");
mGpuTempSwitch = findPreference("game_overlay_gpu_temp_enable");
mCaptureStartPref = findPreference("game_overlay_capture_start");
mCaptureStopPref = findPreference("game_overlay_capture_stop");
mCaptureExportPref = findPreference("game_overlay_capture_export");
mDoubleTapCapturePref = findPreference("game_overlay_doubletap_capture");
mSingleTapTogglePref = findPreference("game_overlay_single_tap_toggle");
mLongPressEnablePref = findPreference("game_overlay_longpress_enable");
mLongPressTimeoutPref = findPreference("game_overlay_longpress_timeout");
mTextSizePref = findPreference("game_overlay_text_size");
mBgAlphaPref = findPreference("game_overlay_background_alpha");
mCornerRadiusPref = findPreference("game_overlay_corner_radius");
mPaddingPref = findPreference("game_overlay_padding");
mItemSpacingPref = findPreference("game_overlay_item_spacing");
mUpdateIntervalPref = findPreference("game_overlay_update_interval");
mTextColorPref = findPreference("game_overlay_text_color");
mTitleColorPref = findPreference("game_overlay_title_color");
mValueColorPref = findPreference("game_overlay_value_color");
mPositionPref = findPreference("game_overlay_position");
mSplitModePref = findPreference("game_overlay_split_mode");
mOverlayFormatPref = findPreference("game_overlay_format");
if (mMasterSwitch != null) {
mMasterSwitch.setOnPreferenceChangeListener((pref, newValue) -> {
boolean enabled = (boolean) newValue;
if (enabled) {
if (Settings.canDrawOverlays(getContext())) {
mOverlay.applyPreferences();
mOverlay.show();
} else {
Toast.makeText(getContext(), R.string.overlay_permission_required, Toast.LENGTH_SHORT).show();
return false;
}
} else {
mOverlay.hide();
}
return true;
});
}
if (mFpsSwitch != null) {
mFpsSwitch.setOnPreferenceChangeListener((pref, newValue) -> {
mOverlay.setShowFps((boolean) newValue);
return true;
});
}
if (mBatteryTempSwitch != null) {
mBatteryTempSwitch.setOnPreferenceChangeListener((pref, newValue) -> {
mOverlay.setShowBatteryTemp((boolean) newValue);
return true;
});
}
if (mCpuUsageSwitch != null) {
mCpuUsageSwitch.setOnPreferenceChangeListener((pref, newValue) -> {
mOverlay.setShowCpuUsage((boolean) newValue);
return true;
});
}
if (mCpuClockSwitch != null) {
mCpuClockSwitch.setOnPreferenceChangeListener((pref, newValue) -> {
mOverlay.setShowCpuClock((boolean) newValue);
return true;
});
}
if (mCpuTempSwitch != null) {
mCpuTempSwitch.setOnPreferenceChangeListener((pref, newValue) -> {
mOverlay.setShowCpuTemp((boolean) newValue);
return true;
});
}
if (mRamSwitch != null) {
mRamSwitch.setOnPreferenceChangeListener((pref, newValue) -> {
mOverlay.setShowRam((boolean) newValue);
return true;
});
}
if (mGpuUsageSwitch != null) {
mGpuUsageSwitch.setOnPreferenceChangeListener((pref, newValue) -> {
mOverlay.setShowGpuUsage((boolean) newValue);
return true;
});
}
if (mGpuClockSwitch != null) {
mGpuClockSwitch.setOnPreferenceChangeListener((pref, newValue) -> {
mOverlay.setShowGpuClock((boolean) newValue);
return true;
});
}
if (mGpuTempSwitch != null) {
mGpuTempSwitch.setOnPreferenceChangeListener((pref, newValue) -> {
mOverlay.setShowGpuTemp((boolean) newValue);
return true;
});
}
if (mCaptureStartPref != null) {
mCaptureStartPref.setOnPreferenceClickListener(pref -> {
GameDataExport.getInstance().startCapture();
Toast.makeText(getContext(), "Started logging Data", Toast.LENGTH_SHORT).show();
return true;
});
}
if (mCaptureStopPref != null) {
mCaptureStopPref.setOnPreferenceClickListener(pref -> {
GameDataExport.getInstance().stopCapture();
Toast.makeText(getContext(), "Stopped logging Data", Toast.LENGTH_SHORT).show();
return true;
});
}
if (mCaptureExportPref != null) {
mCaptureExportPref.setOnPreferenceClickListener(pref -> {
GameDataExport.getInstance().exportDataToCsv();
Toast.makeText(getContext(), "Exported log data to file", Toast.LENGTH_SHORT).show();
return true;
});
}
if (mDoubleTapCapturePref != null) {
mDoubleTapCapturePref.setOnPreferenceChangeListener((pref, newValue) -> {
mOverlay.setDoubleTapCaptureEnabled((boolean) newValue);
return true;
});
}
if (mSingleTapTogglePref != null) {
mSingleTapTogglePref.setOnPreferenceChangeListener((pref, newValue) -> {
mOverlay.setSingleTapToggleEnabled((boolean) newValue);
return true;
});
}
if (mLongPressEnablePref != null) {
mLongPressEnablePref.setOnPreferenceChangeListener((pref, newValue) -> {
mOverlay.setLongPressEnabled((boolean) newValue);
return true;
});
}
if (mLongPressTimeoutPref != null) {
mLongPressTimeoutPref.setOnPreferenceChangeListener((pref, newValue) -> {
if (newValue instanceof String) {
long ms = Long.parseLong((String) newValue);
mOverlay.setLongPressThresholdMs(ms);
}
return true;
});
}
if (mTextSizePref != null) {
mTextSizePref.setOnPreferenceChangeListener((pref, newValue) -> {
if (newValue instanceof Integer) {
mOverlay.updateTextSize((Integer) newValue);
}
return true;
});
}
if (mBgAlphaPref != null) {
mBgAlphaPref.setOnPreferenceChangeListener((pref, newValue) -> {
if (newValue instanceof Integer) {
mOverlay.updateBackgroundAlpha((Integer) newValue);
}
return true;
});
}
if (mCornerRadiusPref != null) {
mCornerRadiusPref.setOnPreferenceChangeListener((pref, newValue) -> {
if (newValue instanceof Integer) {
mOverlay.updateCornerRadius((Integer) newValue);
}
return true;
});
}
if (mPaddingPref != null) {
mPaddingPref.setOnPreferenceChangeListener((pref, newValue) -> {
if (newValue instanceof Integer) {
mOverlay.updatePadding((Integer) newValue);
}
return true;
});
}
if (mItemSpacingPref != null) {
mItemSpacingPref.setOnPreferenceChangeListener((pref, newValue) -> {
if (newValue instanceof Integer) {
mOverlay.updateItemSpacing((Integer) newValue);
}
return true;
});
}
if (mUpdateIntervalPref != null) {
mUpdateIntervalPref.setOnPreferenceChangeListener((pref, newValue) -> {
if (newValue instanceof String) {
mOverlay.updateUpdateInterval((String) newValue);
}
return true;
});
}
if (mTextColorPref != null) {
mTextColorPref.setOnPreferenceChangeListener((pref, newValue) -> true);
}
if (mTitleColorPref != null) {
mTitleColorPref.setOnPreferenceChangeListener((pref, newValue) -> {
if (newValue instanceof String) {
mOverlay.updateTitleColor((String) newValue);
}
return true;
});
}
if (mValueColorPref != null) {
mValueColorPref.setOnPreferenceChangeListener((pref, newValue) -> {
if (newValue instanceof String) {
mOverlay.updateValueColor((String) newValue);
}
return true;
});
}
if (mPositionPref != null) {
mPositionPref.setOnPreferenceChangeListener((pref, newValue) -> {
if (newValue instanceof String) {
mOverlay.updatePosition((String) newValue);
}
return true;
});
}
if (mSplitModePref != null) {
mSplitModePref.setOnPreferenceChangeListener((pref, newValue) -> {
if (newValue instanceof String) {
mOverlay.updateSplitMode((String) newValue);
}
return true;
});
}
if (mOverlayFormatPref != null) {
mOverlayFormatPref.setOnPreferenceChangeListener((pref, newValue) -> {
if (newValue instanceof String) {
mOverlay.updateOverlayFormat((String) newValue);
}
return true;
});
}
}
@Override
public void onResume() {
super.onResume();
if (!hasUsageStatsPermission(requireContext())) {
requestUsageStatsPermission();
}
}
private boolean hasUsageStatsPermission(Context context) {
android.app.AppOpsManager appOps = (android.app.AppOpsManager)
context.getSystemService(Context.APP_OPS_SERVICE);
if (appOps == null) return false;
int mode = appOps.checkOpNoThrow(
android.app.AppOpsManager.OPSTR_GET_USAGE_STATS,
android.os.Process.myUid(),
context.getPackageName()
);
return (mode == android.app.AppOpsManager.MODE_ALLOWED);
}
private void requestUsageStatsPermission() {
Intent intent = new Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS);
startActivity(intent);
}
}

View file

@ -0,0 +1,80 @@
/*
* 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.
*/
package org.lineageos.settings.gameoverlay;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
public class GameOverlayGpuInfo {
private static final String GPU_USAGE_PATH = "/sys/class/kgsl/kgsl-3d0/gpu_busy_percentage";
private static final String GPU_CLOCK_PATH = "/sys/class/kgsl/kgsl-3d0/gpuclk";
private static final String GPU_TEMP_PATH = "/sys/class/kgsl/kgsl-3d0/temp";
public static String getGpuUsage() {
String line = readLine(GPU_USAGE_PATH);
if (line == null) {
return "N/A";
}
line = line.replace("%", "").trim();
try {
int val = Integer.parseInt(line);
return String.valueOf(val);
} catch (NumberFormatException e) {
return "N/A";
}
}
public static String getGpuClock() {
String line = readLine(GPU_CLOCK_PATH);
if (line == null) {
return "N/A";
}
line = line.trim();
try {
long hz = Long.parseLong(line);
long mhz = hz / 1_000_000;
return String.valueOf(mhz);
} catch (NumberFormatException e) {
return "N/A";
}
}
public static String getGpuTemp() {
String line = readLine(GPU_TEMP_PATH);
if (line == null) {
return "N/A";
}
line = line.trim();
try {
float raw = Float.parseFloat(line);
float c = raw / 1000f;
return String.format("%.1f", c);
} catch (NumberFormatException e) {
return "N/A";
}
}
private static String readLine(String path) {
try (BufferedReader br = new BufferedReader(new FileReader(path))) {
return br.readLine();
} catch (IOException e) {
return null;
}
}
}

View file

@ -0,0 +1,65 @@
/*
* 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.
*/
package org.lineageos.settings.gameoverlay;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
public class GameOverlayMemInfo {
public static String getRamUsage() {
long memTotal = 0;
long memAvailable = 0;
try (BufferedReader br = new BufferedReader(new FileReader("/proc/meminfo"))) {
String line;
while ((line = br.readLine()) != null) {
if (line.startsWith("MemTotal:")) {
memTotal = parseMemValue(line);
} else if (line.startsWith("MemAvailable:")) {
memAvailable = parseMemValue(line);
}
if (memTotal > 0 && memAvailable > 0) {
break;
}
}
} catch (IOException e) {
return "N/A";
}
if (memTotal == 0) {
return "N/A";
}
long usedKb = (memTotal - memAvailable);
long usedMb = usedKb / 1024;
return String.valueOf(usedMb);
}
private static long parseMemValue(String line) {
String[] parts = line.split("\\s+");
if (parts.length < 3) {
return 0;
}
try {
return Long.parseLong(parts[1]);
} catch (NumberFormatException e) {
return 0;
}
}
}

View file

@ -0,0 +1,56 @@
/*
* 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.
*/
package org.lineageos.settings.gameoverlay;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.provider.Settings;
import android.widget.Toast;
import com.android.settingslib.collapsingtoolbar.CollapsingToolbarBaseActivity;
import org.lineageos.settings.R;
public class GameOverlaySettingsActivity extends CollapsingToolbarBaseActivity {
private static final int OVERLAY_PERMISSION_REQUEST_CODE = 1234;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_game_overlay);
setTitle(getString(R.string.game_overlay_title));
if (!Settings.canDrawOverlays(this)) {
Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
Uri.parse("package:" + getPackageName()));
startActivityForResult(intent, OVERLAY_PERMISSION_REQUEST_CODE);
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == OVERLAY_PERMISSION_REQUEST_CODE) {
if (Settings.canDrawOverlays(this)) {
Toast.makeText(this, R.string.overlay_permission_granted, Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(this, R.string.overlay_permission_denied, Toast.LENGTH_SHORT).show();
}
}
}
}

View file

@ -0,0 +1,73 @@
/*
* 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.
*/
package org.lineageos.settings.gameoverlay;
import android.service.quicksettings.Tile;
import android.service.quicksettings.TileService;
import androidx.preference.PreferenceManager;
import org.lineageos.settings.R;
public class GameOverlayTileService extends TileService {
private GameOverlay mOverlay;
@Override
public void onCreate() {
super.onCreate();
mOverlay = GameOverlay.getInstance(this);
}
@Override
public void onStartListening() {
super.onStartListening();
boolean enabled = PreferenceManager.getDefaultSharedPreferences(this)
.getBoolean("game_overlay_enable", false);
updateTileState(enabled);
}
@Override
public void onClick() {
super.onClick();
boolean currentlyEnabled = PreferenceManager.getDefaultSharedPreferences(this)
.getBoolean("game_overlay_enable", false);
boolean newState = !currentlyEnabled;
PreferenceManager.getDefaultSharedPreferences(this)
.edit()
.putBoolean("game_overlay_enable", newState)
.commit();
updateTileState(newState);
if (newState) {
mOverlay.show();
} else {
mOverlay.hide();
}
}
private void updateTileState(boolean enabled) {
Tile tile = getQsTile();
if (tile == null) return;
tile.setState(enabled ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE);
tile.setLabel(getString(R.string.game_overlay_tile_label));
tile.setContentDescription(getString(R.string.game_overlay_tile_description));
tile.updateTile();
}
}

View file

@ -124,6 +124,12 @@ vendor/bin/hw/vendor.qti.hardware.capabilityconfigstore@1.0-service
vendor/etc/init/vendor.qti.hardware.capabilityconfigstore@1.0-service.rc
vendor/lib64/hw/vendor.qti.hardware.capabilityconfigstore@1.0-impl.so
# Diag HAL
vendor/etc/vintf/manifest/vendor.qti.diag.hal.service.xml
vendor/bin/diag-router
vendor/etc/init/vendor.qti.diag.rc
vendor/lib64/vendor.qti.diaghal@1.0.so;MODULE_SUFFIX=_vendor
# Display - from sunstone V816.0.8.0.UMQMIXM
vendor/etc/display/DPU660.xml|b26dd73e361546d89bf3d7082a471703dc6ac2cb
vendor/etc/display/DPU670.xml|e5131a60ceff29ca5eb561eec7eddadef1d0486f

File diff suppressed because it is too large Load diff

View file

@ -146,3 +146,7 @@ firmware_directories /vendor/firmware_mnt/image/
# Battery
/sys/class/qcom-battery reverse_chg_mode 0644 system system
# Torch control
/sys/devices/platform/soc/c440000.qcom,spmi/spmi-0/spmi0-02/c440000.qcom,spmi:qcom,pm8350c@2:qcom,flash_led@ee00/leds/led:torch_1 brightness 0660 cameraserver camera
/sys/devices/platform/soc/c440000.qcom,spmi/spmi-0/spmi0-02/c440000.qcom,spmi:qcom,pm8350c@2:qcom,flash_led@ee00/leds/led:switch_1 0660 cameraserver camera

1
sepolicy/vendor/cameraserver.te vendored Normal file
View file

@ -0,0 +1 @@
allow cameraserver sysfs_torch:file rw_file_perms;

View file

@ -4,3 +4,11 @@ allow devicesettings_app vendor_sysfs_graphics:file rw_file_perms;
allow devicesettings_app vendor_sysfs_kgsl:dir search;
allow devicesettings_app vendor_sysfs_kgsl:{ file lnk_file } rw_file_perms;
allow devicesettings_app vendor_sysfs_battery_supply:dir search;
allow devicesettings_app vendor_sysfs_battery_supply:file r_file_perms;
allow devicesettings_app proc_stat:file { read open getattr };
allow devicesettings_app vendor_sysfs_kgsl_gpuclk:file { read open getattr };
allow devicesettings_app vendor_sysfs_kgsl:file { read open getattr };

View file

@ -62,3 +62,11 @@
/data/vendor/mac_addr(/.*)? u:object_r:vendor_wifi_vendor_data_file:s0
/data/vendor/wlan_logs(/.*)? u:object_r:vendor_wifi_vendor_data_file:s0
/vendor/bin/nv_mac u:object_r:vendor_wcnss_service_exec:s0
# Process and system statistics files
/proc/stat u:object_r:proc_stat:s0
/sys/class/kgsl/kgsl-3d0/gpu_busy_percentage u:object_r:vendor_sysfs_kgsl:s0
/sys/class/kgsl/kgsl-3d0/temp u:object_r:vendor_sysfs_kgsl:s0
/sys/class/kgsl/kgsl-3d0/gpuclk u:object_r:vendor_sysfs_kgsl_gpuclk:s0
/sys/class/power_supply/battery/ u:object_r:vendor_sysfs_battery_supply:s0
/sys/class/graphics/fb0/measured_fps u:object_r:vendor_sysfs_graphics:s0

View file

@ -13,6 +13,11 @@ genfscon sysfs /devices/platform/soc/soc:fingerprint_fpc
# USB
genfscon sysfs /devices/platform/soc/soc:qcom,pmic_glink/soc:qcom,pmic_glink:qcom,ucsi/typec u:object_r:vendor_sysfs_usb_c:s0
# Torch control
genfscon sysfs /devices/platform/soc/c440000.qcom,spmi/spmi-0/spmi0-02/c440000.qcom,spmi:qcom,pm8350c@2:qcom,flash_led@ee00/leds/led:torch_1/brightness u:object_r:sysfs_torch:s0
genfscon sysfs /devices/platform/soc/c440000.qcom,spmi/spmi-0/spmi0-02/c440000.qcom,spmi:qcom,pm8350c@2:qcom,flash_led@ee00/leds/led:torch_1/max_brightness u:object_r:sysfs_torch:s0
genfscon sysfs /devices/platform/soc/c440000.qcom,spmi/spmi-0/spmi0-02/c440000.qcom,spmi:qcom,pm8350c@2:qcom,flash_led@ee00/leds/led:switch_1/brightness u:object_r:sysfs_torch:s0
# Wakeup nodes
genfscon sysfs /devices/platform/goodix_ts.0/wakeup u:object_r:sysfs_wakeup:s0
genfscon sysfs /devices/platform/soc/17300000.qcom,lpass/subsys6/wakeup u:object_r:sysfs_wakeup:s0

View file

@ -11,3 +11,5 @@ allow hal_sensors_default vendor_sysfs_udfps:file rw_file_perms;
hal_client_domain(hal_sensors_default, hal_audio)
allow hal_sensors_default hal_audio_hwservice:hwservice_manager find;
allow hal_sensors_default vendor_diag_device:chr_file rw_file_perms;

View file

@ -8,3 +8,10 @@ allow system_app vendor_sysfs_graphics:file { getattr open write };
# HTSR
allow system_app sysfs_htsr:file { read write open setattr getattr };
allow system_app sysfs_htsr:dir { search open read };
#GameBar
allow system_app vendor_sysfs_graphics:file r_file_perms;
allow system_app vendor_sysfs_battery_supply:dir search;
allow system_app proc_stat:file r_file_perms;
allow system_app vendor_sysfs_kgsl:file r_file_perms;
allow system_app vendor_sysfs_kgsl_gpuclk:file r_file_perms;

1
sepolicy/vendor/torch.te vendored Normal file
View file

@ -0,0 +1 @@
type sysfs_torch, fs_type, sysfs_type;

1
sepolicy/vendor/untrusted_app.te vendored Normal file
View file

@ -0,0 +1 @@
allow untrusted_app vendor_diag_device:chr_file rw_file_perms;

1
sepolicy/vendor/vendor_dpmd.te vendored Normal file
View file

@ -0,0 +1 @@
hal_client_domain(vendor_dpmd, vendor_hal_diaghal);

View file

@ -4,6 +4,9 @@ vendor.bluetooth.soc=hastings
# CNE
persist.vendor.cne.feature=1
# Camera
vendor.camera.aux.packagelist=org.lineageos.aperture,com.android.camera,com.google.camera,com.snapchat.android
# Disable Skia tracing by default
debug.hwui.skia_atrace_enabled=false
@ -62,6 +65,11 @@ ro.vendor.qti.sys.fw.bservice_enable=true
DEVICE_PROVISIONED=1
ril.subscription.types=NV,RUIM
# Radio VoNR Calling
persist.radio.is_vonr_enabled_0=true
persist.radio.is_vonr_enabled_1=true
persist.vendor.vonr_setting_support=1
# Sensors
persist.vendor.sensors.enable.mag_filter=true

View file

@ -275,6 +275,7 @@ ro.incremental.enable=yes
vendor.sys.thermal.data.path=/data/vendor/thermal/
# USB
vendor.usb.diag.func.name=ffs
vendor.usb.use_ffs_mtp=1
# Wifi